<!-- ***** BEGIN LICENSE BLOCK *****
   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
   -
   - The contents of this file are subject to the Mozilla Public License Version
   - 1.1 (the "License"); you may not use this file except in compliance with
   - the License. You may obtain a copy of the License at
   - http://www.mozilla.org/MPL/
   -
   - Software distributed under the License is distributed on an "AS IS" basis,
   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   - for the specific language governing rights and limitations under the
   - License.
   -
   - The Original Code is Mozilla XForms support.
   -
   - The Initial Developer of the Original Code is
   - IBM Corporation.
   - Portions created by the Initial Developer are Copyright (C) 2006
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   -  Doron Rosenberg <doronr@us.ibm.com>
   -  Alexander Surkov <surkov@dc.baikal.ru>
   -  Merle Sterling <msterlin@us.ibm.com>
   -
   - Alternatively, the contents of this file may be used under the terms of
   - either the GNU General Public License Version 2 or later (the "GPL"), or
   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
   - in which case the provisions of the GPL or the LGPL are applicable instead
   - of those above. If you wish to allow use of your version of this file only
   - under the terms of either the GPL or the LGPL, and not to allow others to
   - use your version of this file under the terms of the MPL, indicate your
   - decision by deleting the provisions above and replace them with the notice
   - and other provisions required by the GPL or the LGPL. If you do not delete
   - the provisions above, a recipient may use your version of this file under
   - the terms of any one of the MPL, the GPL or the LGPL.
   -
   - ***** END LICENSE BLOCK ***** -->

<!DOCTYPE bindings [
<!ENTITY % xformsDTD SYSTEM "chrome://xforms/locale/xforms.dtd">
  %xformsDTD;
]>

<bindings id="widgetsBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl">


  <!-- CALENDAR WIDGETS -->

  <!-- CALENDAR BASE
    The widget assumes successor widgets have following interface:
      refresh(aCurrentDay, aDaysRefreshOnly) - update UI, the method is called
          when current date is changed.
        @param aCurrentDay - day of current date
        @param aDaysRefreshOnly - if true then day of current date will be
          updated only.
      focus() - set focus on the widget.
      currentDay - return day of current date.
  -->
  <binding id="calendar-base">
    <implementation implements="nsIAccessibleProvider">
    <!-- nsIAccessibleProvider -->
      <property name="accessibleType" readonly="true">
        <getter>
          return Components.interfaces.nsIAccessibleProvider.XFormsCalendarWidget;
        </getter>
      </property>

    <!-- interface -->
      <!-- Set/get readonly state -->
      <property name="readonly">
        <getter>
          return this.hasAttribute("readonly");
        </getter>
        <setter>
          if (val)
            this.setAttribute("readonly", "readonly");
          else
            this.removeAttribute("readonly");
        </setter>
      </property>

    <!-- The following interface methods serve to manage current date (the date
      you see) -->

      <!-- Return year of current date -->
      <property name="year"
                onget="return parseInt(this.getAttribute('year'));"
                onset="this.setAttribute('year', val); this.refresh();"/>

      <!-- Return month of current date -->
      <property name="month"
        onget="return parseInt(this.getAttribute('month'));"
        onset="this.setDate(this.year, val);"/>

      <!-- Set current date-->
      <method name="setDate">
        <parameter name="aYear"/>
        <parameter name="aMonth"/>
        <parameter name="aDay"/>
        <body>
        <![CDATA[
          var month = parseInt(aMonth) - 1;
          var deltayear = parseInt(month / 12);
          if (!deltayear && month < 0)
            deltayear = -1;

          month %= 12;
          if (month < 0)
            month = (month + 12) % 12;

          this.setAttribute("year", aYear + deltayear);
          this.setAttribute("month", month + 1);
          this.refresh(aDay);
        ]]>
        </body>
      </method>

      <!-- Return current date -->
      <method name="getDate">
        <body>
          if (this.focusedDay == -1)
            return null;
          return new Date(this.year, this.month - 1, this.currentDay);
        </body>
      </method>

    <!-- The following methods serve to manage selected date -->

      <!-- Return/set selected date as string of format 'yyyy-MM-dd' -->
      <property name="value"
                onget="return this.selectedDate ? this.selectedDate.toLocaleFormat('%Y-%m-%d') : null;"
                onset="this.selectedDate = new Date(val.replace(/-/g, '/'));"/>

      <!-- Return day of selected date -->
      <property name="selectedDay" readonly="true"
                onget="return this._selectedDate ? this._selectedDate.getDate() : null;"/>

      <!-- Return month of selected date -->
      <property name="selectedMonth" readonly="true"
                onget="return this._selectedDate ? this._selectedDate.getMonth() + 1 : null;"/>

      <!-- Return year of selected date -->
      <property name="selectedYear" readonly="true"
                onget="return this._selectedDate ? this._selectedDate.getFullYear() : null;"/>

      <!-- Set/return selected date -->
      <property name="selectedDate">
        <getter>
          return this._selectedDate;
        </getter>
        <setter>
          // if passed date is empty or invalid then we use today date.
          if (!val || String(val) == this.invalidDate)
            val = new Date();

          this._selectedDate = val;
          if (!this.isSelectedDate())
            this.setDate(this.selectedYear, this.selectedMonth, this.selectedDay);
          else
            this.refresh(this.selectedDay, true);
        </setter>
      </property>

      <!-- Return true if year and month of current date are the same like for
        selected date -->
      <method name="isSelectedDate">
        <body>
        <![CDATA[
          if (this.selectedYear == this.year && this.selectedMonth == this.month)
            return true;
          return false;
        ]]>
        </body>
      </method>

    <!-- interface for successor widgets -->

      <!-- Return day of the week of the first day of the month -->
      <property name="dayOffset" readonly="true">
        <getter>
          return new Date(this.year, this.month - 1, 1).getDay();
        </getter>
      </property>

      <!-- Return days count in current month -->
      <property name="daysCount" readonly="true"
                onget="return this.getDaysCount(this.month, this.year);"/>

      <!-- Return days count in previous month -->
      <property name="prevDaysCount" readonly="true">
      <getter>
      <![CDATA[
        var month = this.month - 1;
        var year = this.year;
        if (month <= 0) {
          month = 12;
          year--;
        }
        return this.getDaysCount(month, year);
      ]]>
      </getter>
      </property>

      <!-- Return short names of days of the week -->
      <method name="getDaysOfWeekNames">
        <body>
        <![CDATA[
          // shortname defaults
          var dayShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

          // try to get localized short names.
          // May 2005's first day is a Sunday - also, month is 0-indexed in JS
          var day;
          for (var i = 0; i < 7; i++) {
            day = new Date(2005, 4, i+1).toLocaleFormat("%a");
            if (day)
              dayShort[i] = day;
          }
          return dayShort;
        ]]>
        </body>
      </method>

      <!-- Fire 'change' event -->
      <method name="fireChangeEvent">
        <body>
          var event = this.ownerDocument.createEvent("Events");
          event.initEvent("change", true, false);
          this.dispatchEvent(event);
        </body>
      </method>

    <!-- private -->
      <method name="getDaysCount">
        <parameter name="aMonth"/>
        <parameter name="aYear"/>
        <body>
        <![CDATA[
          switch (aMonth) {
            case 1: case 3: case 5: case 7: case 8: case 10: case 12:
              return 31;

            case 2:
              if (aYear % 4 == 0 && aYear % 100 != 0 || aYear % 400 == 0)
                return 29; // leap-year
              return 28;

            case 4: case 6: case 9: case 11:
              return 30;
          }
          return 0;
        ]]>
        </body>
      </method>

      <!-- Selected date -->
      <field name="_selectedDate">null</field>

      <!-- String presentation of invalid javascript date -->
      <field name="invalidDate">String(new Date(undefined))</field>
    </implementation>
  </binding>


  <!-- COMPACT CALENDAR BASE
    The widget is the interface binding for xhtml and xul calendars widgets.
    It assumes that successor widgets have following interface:
      setDayControl(aControl, aType, aLabel) - set type and label for day control
      selectDayControl(aControl) - makes day control selected
      unselectDayControl(aControl) - unselect day control
      isDayControl(aNode) - return true if aNode is a day control
  -->
  <binding id="calendar-compact-base" extends="#calendar-base">

    <implementation>
    <!-- interface -->
      <!-- Set focus on day element of current date -->
      <method name="focus">
        <body>
          if (this.currentDayIndex != -1)
            this._dayElements[this.currentDayIndex].focus();
        </body>
      </method>

      <!-- Updates UI -->
      <method name="refresh">
        <parameter name="aCurrentDay"/>
        <parameter name="aDaysRefreshOnly"/>
        <body>
          this.refreshInternal(aCurrentDay, aDaysRefreshOnly);
        </body>
      </method>

      <!-- Return day of current date -->
      <property name="currentDay" readonly="true">
        <getter>
        <![CDATA[
          var day = this.currentDayIndex - this.dayOffset + 1;
          if (day < 0 || day > this.daysCount)
            return -1;
          return day;
        ]]>
        </getter>
      </property>

    <!-- private -->
      <method name="refreshInternal">
        <parameter name="aCurrentDay"/>
        <parameter name="aDaysRefreshOnly"/>
        <body>
          <![CDATA[
          if (!this._isUIBuilt) {
            this.buildUI();
            this._isUIBuilt = true;
          }

          if (!aDaysRefreshOnly) {
            // set days for previous month
            var dayOffset = this.dayOffset;
            var prevDayCount = this.prevDaysCount;
            for (var i = 0; i < dayOffset; i++) {
              this.setDayControl(this._dayElements[i], "prevMonth",
                                 prevDayCount + i - dayOffset + 1);
            }

            // set days for current month
            var count = this.daysCount + dayOffset;
            for (; i < count; i++) {
              this.setDayControl(this._dayElements[i], "currentMonth",
                                 i - dayOffset + 1);
            }

            // set days for next month
            for (var day = 1; i < this._dayElements.length; i++, day++) {
              this.setDayControl(this._dayElements[i], "nextMonth", day);
            }
          }

          var selectedIndex = this.dayOffset + this.selectedDay - 1;
          var currentIndex = null;
          if (aCurrentDay)
            currentIndex = this.dayOffset + parseInt(aCurrentDay) - 1;
          else
            currentIndex = this.currentDayIndex;

          this.setSelectedDayByIndex(selectedIndex, currentIndex);
        ]]>
        </body>
      </method>

      <method name="setSelectedDayByIndex">
        <parameter name="aSelectedIndex"/>
        <parameter name="aCurrentIndex"/>
        <body>
        <![CDATA[
          if (!aCurrentIndex)
            aCurrentIndex = aSelectedIndex;

          if (this._selectedDayIndex != -1) {
            this.unselectDayControl(this._dayElements[this._selectedDayIndex]);
          }

          if (this.isSelectedDate()) {
            this.selectDayControl(this._dayElements[aSelectedIndex]);
            this._selectedDayIndex = aSelectedIndex;
          } else {
            this._selectedDayIndex = -1;
          }

          this.currentDayIndex = aCurrentIndex;
        ]]>
        </body>
      </method>

      <property name="currentDayIndex"
                onget="return this._currentDayIndex;"
                onset="this.setCurrentDayByIndex(val);"/>

      <method name="setCurrentDayByIndex">
        <parameter name="aIndex"/>
        <body>
          if (this._currentDayIndex == aIndex)
            return;

          var dayElm = null;
          if (this._currentDayIndex != -1) {
            dayElm = this._dayElements[this._currentDayIndex];
            dayElm.setAttribute("tabindex", "-1");
          }

          dayElm = this._dayElements[aIndex];
          dayElm.setAttribute("tabindex", "1");

          this._currentDayIndex = aIndex;
        </body>
      </method>

      <!-- The method alters day of current date on aDay and if
        1) action type is 'currentMonth' then it selects current date.
        2) action type is 'prevMonth' then it alters month of current date on
          previous month.
        3) action type is 'nextMonth' then it alters month of current date on
          next month.
      -->
      <method name="processAction">
        <parameter name="aActionType"/>
        <parameter name="aDay"/>
        <parameter name="aSkipFocus"/>
        <body>
        <![CDATA[
          if (aDay != null)
            aDay = parseInt(aDay);

          switch (aActionType) {
            case "prevMonth":
              this.setDate(this.year, this.month - 1, aDay);
              break;

            case "nextMonth":
              this.setDate(this.year, this.month + 1, aDay);
              break;

            case "currentMonth":
              if (!this.readonly) {
                this.selectedDate = new Date(this.year, this.month - 1, aDay);
                this.fireChangeEvent();
              } else {
                if (aDay != null)
                  this.currentDayIndex = this.dayOffset + aDay - 1;
              }
              break;
          }

          if (!aSkipFocus)
            this.focus();
        ]]>
        </body>
      </method>

      <!-- UI controls array for days -->
      <field name="_dayElements">new Array()</field>

      <!-- Index of _dayElements array item pointing on day of selected date -->
      <field name="_selectedDayIndex">-1</field>

      <!-- Index of _dayElements array item pointing on day of current date -->
      <field name="_currentDayIndex">-1</field>

      <!-- The flag pointing whether UI was builded -->
      <field name="_isUIBuilt">false</field>
    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_LEFT" phase="capturing">
      <![CDATA[
        if (!this.isDayControl(event.originalTarget))
          return;

        if (this.currentDayIndex - 1 >= 0) {
          this.currentDayIndex--;
          this.focus();
        }
      ]]>
      </handler>
      <handler event="keypress" keycode="VK_RIGHT" phase="capturing">
      <![CDATA[
        if (!this.isDayControl(event.originalTarget))
          return;

        if (this.currentDayIndex + 1 < this._dayElements.length) {
          this.currentDayIndex++;
          this.focus();
        }
      ]]>
      </handler>
      <handler event="keypress" keycode="VK_UP" phase="capturing">
      <![CDATA[
        if (!this.isDayControl(event.originalTarget))
          return;

        if (this.currentDayIndex - 7 >= 0) {
          this.currentDayIndex -= 7;
          this.focus();
        }
      ]]>
      </handler>
      <handler event="keypress" keycode="VK_DOWN" phase="capturing">
      <![CDATA[
        if (!this.isDayControl(event.originalTarget))
          return;

        if (this.currentDayIndex + 7 < this._dayElements.length) {
          this.currentDayIndex += 7;
          this.focus();
        }
      ]]>
      </handler>

      <handler event="keypress" keycode="VK_PAGE_UP">
        this.month++;
      </handler>
      <handler event="keypress" keycode="VK_PAGE_DOWN">
        this.month--;
      </handler>
    </handlers>
  </binding>

  <!-- RANGE
       Interface for range widgets that use one or more spinbuttons/numberbox
       combinations to represent a type. The widget assumes that successor
       widgets have the following interface:
       updateLabels() - Update range start and end labels.
       updateValue() - Update the value by putting together the individual
                       values from each spinbutton.
       updateFields() - Update the value of each individual spinbutton.
                        This method is called when a new value is set.
       setSpinButtonMinMax() - Set the min and max attributes of the
                               spinbuttons.
       isInRange(aValue) - Is aValue between the start and end values?
  -->
  <binding id="range">
    <implementation>
      <!-- Start label separator -->
      <field name="_startSeparator">&xforms.range.start.separator.field;</field>

      <!-- End label separator -->
      <field name="_endSeparator">&xforms.range.end.separator.field;</field>

      <!-- interface -->
      <!-- Get/set lower bound of values. -->
      <property name="start"
                onget="return this.getAttribute('start');"
                onset="this.setAttribute('start', val);"/>

      <!-- Get/set upper bound of values. -->
      <property name="end"
                onget="return this.getAttribute('end');"
                onset="this.setAttribute('end', val);"/>

      <!-- Get/set step of values. -->
      <property name="step"
                onget="return this.getAttribute('step');"
                onset="this.setAttribute('step', val);"/>

      <!-- Get/set value. -->
      <property name="value">
        <getter>
          return this.getAttribute('value');
        </getter>
        <setter>
        <![CDATA[
          var value = val;
          if (!value)
            value = this.start;

          this.setAttribute("value", value);
          this.updateFields();
          this.setSpinbuttonMinMax();
          this.fireValueChange();
        ]]>
        </setter>
      </property>


      <!-- Set start/end/step/value -->
      <method name="set">
        <parameter name="aStart"/>
        <parameter name="aEnd"/>
        <parameter name="aStep"/>
        <parameter name="aValue"/>
        <body>
          this.start = aStart;
          this.end = aEnd;
          this.step = aStep;
          this.value = aValue;

          this.updateLabels();
        </body>
      </method>

      <!-- Format a value to the given number of digits by padding with
           leading zeros.
      -->
      <method name="formatValue">
        <parameter name="aValue"/>
        <parameter name="aDigits"/>
        <body>
        <![CDATA[
          var formattedValue = aValue;
          while (formattedValue.length < aDigits) {
            formattedValue = "0" + formattedValue;
          }
          return formattedValue;
        ]]>
        </body>
      </method>

      <!-- Update the text content of range widget labels.
           For example, a range widget may want to have labels
           for the start and end values of the range.
      -->
      <method name="updateLabels">
        <body>
          return true;
        </body>
      </method>

      <!-- Update the widget's value attribute by putting together the
           individual spinbutton values in the correct format for the type.
      -->
      <method name="updateValue">
        <body>
          return true;
        </body>
      </method>


      <!-- Parse the widget's value attribute and update the value of
           of each individual spinbutton.
      -->
      <method name="updateFields">
        <body>
          return true;
        </body>
      </method>

      <!-- Set the min and max attributes for the spinbuttons.
           If any one of the spinbuttons/numberboxes changes value,
           the valid min and max for one or more of the others may
           have to change too.

           Example: In a dateTime range, if the current date is 2007-02-28
           and the year is changed to 2008 (a leap year), the max day for
           the month of February will change from 28 to 29. Likewise, if
           the current date were 2007-03-31 and the month is changed to 04,
           the max day will have to change from 31 to 30.
      -->
      <method name="setSpinbuttonMinMax">
        <body>
          return true;
        </body>
      </method>

      <!-- Is aValue between the start and end values? -->
      <method name="isInRange">
        <parameter name="aValue"/>
        <body>
          return true;
        </body>
      </method>

      <!-- Increment value -->
      <method name="increment">
        <body>
          this.updateSpinbuttons();
        </body>
      </method>

      <!-- Decrement value -->
      <method name="decrement">
        <body>
          this.updateSpinbuttons();
        </body>
      </method>

      <!-- Update the spinbutton min and max attributes and the range
           value when an increment or decrement event occurs.
      -->
      <method name="updateSpinbuttons">
        <body>
          this.setSpinbuttonMinMax();
          this.updateValue();
          this.fireValueChange();
        </body>
      </method>

      <!-- Fire the 'ValueChange' event.
           Range widgets must fire their own change event because the act of
           incrementing/decrementing one spinbutton may change one or more
           of the others at the same time as SetSpinbuttonMinMax will change
           the min and max attributes on the fly.

           The range widget updates its value on each 'change' event from the
           spinbuttons and then fires ValueChange after they have all been
           updated so that the range binding can update the instance data.
      -->
      <method name="fireValueChange">
        <body>
          var event = this.ownerDocument.createEvent("Events");
          event.initEvent("ValueChange", true, false);
          this.dispatchEvent(event);
        </body>
      </method>

      <constructor>
        // Spinup events are generated by the numberbox widget
        // defined in "widgets-xul.xml".
        var upHandler = {
          range: this,
          handleEvent: function(aEvent) {
            this.range.increment();
          }
        };
        // Spindown events are generated by the numberbox widgets
        // defined in "widgets-xul.xml".
        var downHandler = {
          range: this,
          handleEvent: function(aEvent) {
            this.range.decrement();
          }
        };
        // Change events are generated by the numberbox widget
        // defined in "widgets-xul.xml".
        var changeHandler = {
          range: this,
          handleEvent: function(aEvent) {
            this.range.updateValue();
          }
        };
        this.addEventListener("spinup", upHandler, false);
        this.addEventListener("spindown", downHandler, false);
        this.addEventListener("change", changeHandler, false);
      </constructor>
    </implementation>
  </binding>

  <!-- DATETIME -->
  <binding id="datetime" extends="#range">
    <implementation>
    <!-- interface -->
      <!-- dateTime format: yyyy-mm-ddThh:mm:ss[.s+] -->

      <!-- Extract the year from a date or dateTime -->
      <method name="getYear">
        <parameter name="aDate"/>
        <body>
          return parseFloat(aDate.substr(0,4));
        </body>
      </method>

      <!-- Extract the month from a date or dateTime -->
      <method name="getMonth">
        <parameter name="aDate"/>
        <body>
          return parseFloat(aDate.substr(5,2));
        </body>
      </method>

      <!-- Extract the day from a date or dateTime -->
      <method name="getDay">
        <parameter name="aDate"/>
        <body>
          return parseFloat(aDate.substr(8,2));
        </body>
      </method>

      <!-- Extract the hours from a dateTime -->
      <method name="getHours">
        <parameter name="aDateTime"/>
        <body>
          return parseFloat(aDateTime.substr(11,2));
        </body>
      </method>

      <!-- Extract the minutes from a dateTime -->
      <method name="getMinutes">
        <parameter name="aDateTime"/>
        <body>
          return parseFloat(aDateTime.substr(14,2));
        </body>
      </method>

      <!-- Extract the seconds from a dateTime -->
      <method name="getSeconds">
        <parameter name="aDateTime"/>
        <body>
          return parseFloat(aDateTime.substr(17,2));
        </body>
      </method>

      <!-- Get the minimum Year value. -->
      <method name="getYearMin">
        <body>
        <![CDATA[
          // The min value for the year spinbutton will always
          // be the year of the start date.
          return this.getYear(this.start);
        ]]>
        </body>
      </method>

      <!-- Get the maximum Year value. -->
      <method name="getYearMax">
        <body>
        <![CDATA[
          // The max value for the year spinbutton will always
          // be the year of the end date.
          return this.getYear(this.end);
        ]]>
        </body>
      </method>

      <!-- Get the minimum Month value. -->
      <method name="getMonthMin">
        <body>
        <![CDATA[
          // The min value for the month spinbutton depends on the year
          // range and the current value of the year.
          var startYear = this.getYear(this.start);
          var endYear = this.getYear(this.end);
          var currentYear = this.getYear(this.value);
          var minMonth = 0;

          if (startYear == endYear || currentYear == startYear) {
            // Min month is the month of the start date.
            minMonth = this.getMonth(this.start);
          } else {
            // Current year falls between the start and end dates so
            // the minimum month is the first month of the year.
            minMonth = 1;
          }
          return minMonth;
        ]]>
        </body>
      </method>

      <!-- Get the maximum Month value. -->
      <method name="getMonthMax">
        <body>
        <![CDATA[
          // The max value for the month spinbutton depends on the year
          // range and the current value of the year.
          var startYear = this.getYear(this.start);
          var endYear = this.getYear(this.end);
          var currentYear = this.getYear(this.value);
          var maxMonth = 0;

          if (startYear == endYear || currentYear == endYear) {
            // Max month is the month of the end date.
            maxMonth = this.getMonth(this.end);
          } else {
            // Current year falls between the start and end dates so
            // the maximum month is the last month of the year.
            maxMonth = 12;
          }
          return maxMonth;
         ]]>
        </body>
      </method>

      <!-- Get the minimum Day value. -->
      <method name="getDayMin">
        <body>
        <![CDATA[
          // The min value for the day spinbutton depends on the current
          // month and year but may also be constrained by the day of the
          // start date.
          var startYear = this.getYear(this.start);
          var endYear = this.getYear(this.end);
          var currentYear = this.getYear(this.value);
          var startMonth = this.getMonth(this.start);
          var endMonth = this.getMonth(this.end);
          var currentMonth = this.getMonth(this.value);
          var minDay = 0;

          if (startYear == endYear) {
            if (startMonth == endMonth || currentMonth == startMonth) {
              // Start and end month are the same month of the same year or
              // the current month is the start month. Min day is the day of
              // the start date.
              minDay = this.getDay(this.start);
            } else {
              // Current month is between the start and end months.
              // Min day is the first day of the month.
              minDay = 1;
            }
          } else {
            // Start and End year span 1 or more years.
            if (currentYear == startYear) {
              if (currentMonth == startMonth) {
                // Current month is the start month of the start year.
                // Min day is the day of the start date.
                minDay = this.getDay(this.start);
              } else {
                // Current month is between the start month and the last
                // month of the current year.
                // Min day is the first day of the month.
                minDay = 1;
              }
            } else {
              // Current year is between the start and end years.
              // Min day is the first day of the month.
              minDay = 1;
            }
          }
          return minDay;
         ]]>
        </body>
      </method>

      <!-- Get the maximum Day value. -->
      <method name="getDayMax">
        <body>
        <![CDATA[
          // The max value for the day spinbutton depends on the current
          // month and year but may also be constrained by the day of the
          // end date.
          var startYear = this.getYear(this.start);
          var endYear = this.getYear(this.end);
          var currentYear = this.getYear(this.value);
          var startMonth = this.getMonth(this.start);
          var endMonth = this.getMonth(this.end);
          var currentMonth = this.getMonth(this.value);
          var currentDay = this.getDay(this.value);
          var daysCount = this.getDaysCount(currentMonth, currentYear);
          var maxDay = 0;

          if (startYear == endYear) {
            if (startMonth == endMonth || currentMonth == endMonth) {
              // Start and end month are the same month of the same year or
              // the current month is the end month. Max day is the day of
              // the end month.
              maxDay = this.getDay(this.end);
            } else {
              // Current month is between the start and end months.
              // Max day is the number of days in the month.
              maxDay = daysCount;
            }
          } else {
            // Start and End year span 1 or more years.
            if (currentYear == endYear) {
              if (currentMonth == endMonth) {
                // Current month is the end month of the end year.
                // Max day is the day of the end month;
                maxDay = this.getDay(this.end);
              } else {
                // Current month falls between the first month of the
                // current (end) year and the month of the end date.
                // May day is the number of days in the month.
                maxDay = daysCount;
              }
            } else {
              // Current year is between the start and end years.
              // Max day is the number of days in the month.
              maxDay = daysCount;
            }
          }
          return maxDay;
         ]]>
        </body>
      </method>

      <!-- Get the minimum Hours value. -->
      <method name="getHoursMin">
        <body>
        <![CDATA[
          // The min value for the hours spinbutton will always be the
          // hours of the start time.
          return this.getHours(this.start);
        ]]>
        </body>
      </method>

      <!-- Get the maximum Hours value. -->
      <method name="getHoursMax">
        <body>
        <![CDATA[
          // The max value for the hours spinbutton will always be the
          // hours of the end time.
          return this.getHours(this.end);
        ]]>
        </body>
      </method>

      <!-- Get the minimum Minutes value. -->
      <method name="getMinutesMin">
        <body>
        <![CDATA[
          // The min value for the minutes spinbutton depends on the hours
          // range and the current value of the hours.
          var startHours = this.getHours(this.start);
          var endHours = this.getHours(this.end);
          var currentHours = this.getHours(this.value);
          var startMinutes = this.getMinutes(this.start);
          var minMinutes = 0;

          if (startHours == endHours || currentHours == startHours) {
            // Min minutes is the minutes of the start time.
            minMinutes = startMinutes;

          } else {
            // Current hours falls between the start and end hours.
            // Min minutes is the first minute of the hour.
            minMinutes = 0;
          }
          return minMinutes;
        ]]>
        </body>
      </method>

      <!-- Get the maximum Minutes value. -->
      <method name="getMinutesMax">
        <body>
        <![CDATA[
          // The max value for the minutes spinbutton depends on the hours
          // range and the current value of the hours.
          var startHours = this.getHours(this.start);
          var endHours = this.getHours(this.end);
          var currentHours = this.getHours(this.value);
          var endMinutes = this.getMinutes(this.end);
          var maxMinutes = 0;

          if (startHours == endHours || currentHours == endHours) {
            // Max minutes is the minutes of the end time.
            maxMinutes = endMinutes;

          } else {
            // Current hours falls between start hours and end hours.
            // Max minutes is the last minute of the hour.
            maxMinutes = 59;
          }
          return maxMinutes;
        ]]>
        </body>
      </method>

      <!-- Get the minimum Seconds value. -->
      <method name="getSecondsMin">
        <body>
        <![CDATA[
          // The min value for the seconds spinbutton depends on the current
          // hours and minutes but may also be constrained by the seconds of
          // the start time.
          var startHours = this.getHours(this.start);
          var endHours = this.getHours(this.end);
          var currentHours = this.getHours(this.value);
          var startMinutes = this.getMinutes(this.start);
          var endMinutes = this.getMinutes(this.end);
          var currentMinutes = this.getMinutes(this.value);
          var startSeconds = this.getSeconds(this.start);
          var minSeconds = 0;

          if (startHours == endHours || currentHours == startHours) {
            // The minimum seconds value depends on the current minutes.
            if (startMinutes == endMinutes || currentMinutes == startMinutes) {
              // Min seconds is the seconds of the start time.
              minSeconds = startSeconds;
            } else {
              // Current minutes falls between the start and end minutes of
              // the start hours. Min seconds is the first second of a minute.
              minSeconds = 0;
            }
          } else {
            // Current hours falls between the start and end hours.
            // The range of minutes will vary from a minimum of 0 to
            // a maximum of 59 and so will the seconds.
            minSeconds = 0;
          }
          return minSeconds;
        ]]>
        </body>
      </method>

      <!-- Get the maximum Seconds value. -->
      <method name="getSecondsMax">
        <body>
        <![CDATA[
          // The max value for the seconds spinbutton depends on the current
          // hours and minutes but may also be constrained by the seconds of
          // the end time.
          var startHours = this.getHours(this.start);
          var endHours = this.getHours(this.end);
          var currentHours = this.getHours(this.value);
          var startMinutes = this.getMinutes(this.start);
          var endMinutes = this.getMinutes(this.end);
          var currentMinutes = this.getMinutes(this.value);
          var endSeconds = this.getSeconds(this.end);
          var maxSeconds = 0;

          if (startHours == endHours || currentHours == endHours) {
            // The maximum seconds value depends on the current minutes.
            if (startMinutes == endMinutes || currentMinutes == endMinutes) {
              // Max seconds is the seconds of the end time.
              maxSeconds = endSeconds;
            } else {
              // Current minutes falls between the start and end minutes of
              // the end hour. Max seconds is the last second of a minute.
              maxSeconds = 59;
            }
          } else {
            // Current hours falls between the start and end hours.
            // The range of minutes will vary from a minimum of 0 to
            // a maximum of 59 and so will the seconds.
            maxSeconds = 59;
          }
          return maxSeconds;
        ]]>
        </body>
      </method>

      <!-- Get the number of days in the month for a given year. -->
      <method name="getDaysCount">
        <parameter name="aMonth"/>
        <parameter name="aYear"/>
        <body>
        <![CDATA[
          switch (aMonth) {
            case 1: case 3: case 5: case 7: case 8: case 10: case 12:
              return 31;

            case 2:
              if (aYear % 4 == 0 && aYear % 100 != 0 || aYear % 400 == 0)
                return 29; // leap-year
              return 28;

            case 4: case 6: case 9: case 11:
              return 30;
          }
          return 0;
        ]]>
        </body>
      </method>

      <!-- Get a Javacript Date object initialized to the value of aDateTime.
       -->
      <method name="getDate">
        <parameter name="aDateTime"/>
        <body>
          var date = new Date();
          date.setFullYear(this.getYear(aDateTime));
          date.setMonth(this.getMonth(aDateTime));
          date.setDate(this.getDay(aDateTime));
          date.setHours(this.getHours(aDateTime));
          date.setMinutes(this.getMinutes(aDateTime));
          date.setSeconds(this.getSeconds(aDateTime));
          return date;
        </body>
      </method>
    </implementation>
  </binding>

  <!-- DATE -->
  <binding id="date" extends="#datetime">
    <implementation>
    <!-- interface -->
      <!-- date format: yyyy-mm-dd -->

      <!-- Get a Javacript Date object initialized to the value of aDate.
      -->
      <method name="getDate">
        <parameter name="aDate"/>
        <body>
          var date = new Date();
          date.setFullYear(this.getYear(aDate));
          date.setMonth(this.getMonth(aDate));
          date.setDate(this.getDay(aDate));
          return date;
        </body>
      </method>
    </implementation>
  </binding>

  <!-- TIME -->
  <binding id="time" extends="#datetime">
    <implementation>
    <!-- interface -->
      <!-- Time format: hh:mm:ss[.s+] -->

      <!-- Extract the hours from a time -->
      <method name="getHours">
        <parameter name="aTime"/>
        <body>
          return parseFloat(aTime.substr(0,2));
        </body>
      </method>

      <!-- Extract the minutes from a time -->
      <method name="getMinutes">
        <parameter name="aTime"/>
        <body>
          return parseFloat(aTime.substr(3,2));
        </body>
      </method>

      <!-- Extract the seconds from a time -->
      <method name="getSeconds">
        <parameter name="aTime"/>
        <body>
          return parseFloat(aTime.substr(6,2));
        </body>
      </method>

      <!-- Get a Javacript Date object initialized to the value of aTime.
           A JS Date object also accepts a time and we can use a Date
           initialized with a Time to compare times.
       -->
      <method name="getDate">
        <parameter name="aTime"/>
        <body>
          var date = new Date();
          date.setHours(this.getHours(aTime));
          date.setMinutes(this.getMinutes(aTime));                                  
          date.setSeconds(this.getSeconds(aTime));
          return date;
        </body>
      </method>
    </implementation>
  </binding>

  <!-- GREGORIAN
       This widget is the base interface for ranges that support gregorian
       calendar types: gDay, gMonth, gMonthDay, gYear, and gYearMonth.
  -->
  <binding id="gregorian" extends="#range">
    <implementation>
      <method name="getDaysCount">
        <parameter name="aMonth"/>
        <body>
        <![CDATA[
          switch (aMonth) {
            case 1: case 3: case 5: case 7: case 8: case 10: case 12:
              return 31;

            case 2:
              return 28;

            case 4: case 6: case 9: case 11:
              return 30;
          }
          return 0;
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <!-- GMONTHDAY -->
  <binding id="gmonthday" extends="#gregorian">
    <implementation>
    <!-- interface -->
      <!-- gMonthDay format: mm-dd -->

      <!-- Extract the month from a gMonthDay -->
      <method name="getMonth">
        <parameter name="aMonthDay"/>
        <body>
          return parseFloat(aMonthDay.substr(0,2));
        </body>
      </method>

      <!-- Extract the day from a gMonthDay -->
      <method name="getDay">
        <parameter name="aMonthDay"/>
        <body>
          return parseFloat(aMonthDay.substr(3,2));
        </body>
      </method>

      <!-- Get the minimum Month value. -->
      <method name="getMonthMin">
        <body>
        <![CDATA[
          // The min value for the month spinbutton will always be
          // the month of the start gMonthDay.
          return this.getMonth(this.start);
        ]]>
        </body>
      </method>

      <!-- Get the maximum Month value. -->
      <method name="getMonthMax">
        <body>
        <![CDATA[
          // The max value for the month spinbutton will always be
          // the month of the end gMonthDay.
          return this.getMonth(this.end);
        ]]>
        </body>
      </method>

      <!-- Get the minimum Day value. -->
      <method name="getDayMin">
        <body>
        <![CDATA[
          var startMonth = this.getMonth(this.start);
          var endMonth = this.getMonth(this.end);
          var currentMonth = this.getMonth(this.value);
          var minDay = 0;

          if (startMonth == endMonth || currentMonth == startMonth) {
            // Min day is the day of the start gMonthDay.
            minDay = this.getDay(this.start);
          } else {
            // Current month falls between the start and end month.
            // Min day is the first day of the month.
            minDay = 1;
          }
          return minDay;
        ]]>
        </body>
      </method>

      <!-- Get the maximum Day value. -->
      <method name="getDayMax">
        <body>
        <![CDATA[
          var startMonth = this.getMonth(this.start);
          var endMonth = this.getMonth(this.end);
          var currentMonth = this.getMonth(this.value);
          var maxDay = 0;

          if (startMonth == endMonth || currentMonth == endMonth) {
            // Max day is the day of the end gMonthDay.
            maxDay = this.getDay(this.end);
          } else {
            // Current month falls between the start and end month.
            // Max day is the number of days in the month.
            maxDay = this.getDaysCount(currentMonth);
          }
          return maxDay;
        ]]>
        </body>
      </method>

      <!-- Get a Javacript Date object initialized to the value of aMonthDay.
           JS Date objects can be used to compare gMonthDays.
      -->
      <method name="getDate">
        <parameter name="aMonthDay"/>
        <body>
        <![CDATA[
          var date = new Date();
          // A gMonthDay type does not have a year. Any year is acceptable.
          date.setFullYear(2007);
          date.setMonth(this.getMonth(aMonthDay));
          date.setDate(this.getDay(aMonthDay));
          return date;
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <!-- GYEARMONTH -->
  <binding id="gyearmonth" extends="#gregorian">
    <implementation>
    <!-- interface -->
      <!-- gYearMonth format: yyyy-mm -->

      <!-- Extract the year from a gYearMonth -->
      <method name="getYear">
        <parameter name="aYearMonth"/>
        <body>
          return parseFloat(aYearMonth.substr(0,4));
        </body>
      </method>

      <!-- Extract the month from a gYearMonth -->
      <method name="getMonth">
        <parameter name="aYearMonth"/>
        <body>
          return parseFloat(aYearMonth.substr(5,2));
        </body>
      </method>

      <!-- Get the minimum Year value. -->
      <method name="getYearMin">
        <body>
        <![CDATA[
          // The min value for the year spinbutton will always be
          // the year of the start gYearMonth.
          return this.getYear(this.start);
        ]]>
        </body>
      </method>

      <!-- Get the maximum Year value. -->
      <method name="getYearMax">
        <body>
        <![CDATA[
          // The max value for the year spinbutton will always be
          // the year of the end gYearMonth.
          return this.getYear(this.end);
        ]]>
        </body>
      </method>

      <!-- Get the minimum Month value. -->
      <method name="getMonthMin">
        <body>
        <![CDATA[
          var startYear = this.getYear(this.start);
          var endYear = this.getYear(this.end);
          var currentYear = this.getYear(this.value);
          var minMonth = 0;

          if (startYear == endYear || currentYear == endYear) {
            // Min month is the month of the start gYearMonth.
            minMonth = this.getMonth(this.start);
          } else {
            // Current year falls between the start and end year.
            // Min month is the first month of the year.
            minMonth = 1;
          }
          return minMonth;
        ]]>
        </body>
      </method>

      <!-- Get the maximum Month value. -->
      <method name="getMonthMax">
        <body>
        <![CDATA[
          var startYear = this.getYear(this.start);
          var endYear = this.getYear(this.end);
          var currentYear = this.getYear(this.value);
          var maxMonth = 0;

          if (startYear == endYear || currentYear == endYear) {
            // Max month is the month of the end gYearMonth.
            maxMonth = this.getMonth(this.end);
          } else {
            // Current year falls between the start and end year.
            // Max month is the last month of the year.
            maxMonth = 12;
          }
          return maxMonth;
        ]]>
        </body>
      </method>

      <!-- Get a Javacript Date object initialized to the value of aYearMonth.
           JS Date objects can be used to compare gYearMonths.
      -->
      <method name="getDate">
        <parameter name="aYearMonth"/>
        <body>
        <![CDATA[
          var date = new Date();
          date.setFullYear(this.getYear(aYearMonth));
          date.setMonth(this.getMonth(aYearMonth));
          // A gYearMonth type does not have a day. Any day is acceptable.
          date.setDate(1);
          return date;
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <!-- DURATION -->
  <binding id="duration" extends="#range">
    <implementation>
    <!-- interface -->
      <!-- Duration format: PnYnMnDTnHnMnS
           dayTimeDuration: PnDnTHnMnS (days, hours, minutes, seconds).
           yearMonthDuration: PnYnM (years and months)

           - The designator 'P' must always be present.
           - The designator 'T' must be absent if and only if all of the
             time items are absent.
           - If the number of years, months, days, hours, minutes or seconds
             equals zero, the number and its corresponding designator may be
             omitted.
           - There are no restrictions on the number of digits in 'n'.
       -->

      <!-- Set start/end/step/value -->
      <method name="set">
        <parameter name="aStart"/>
        <parameter name="aEnd"/>
        <parameter name="aStep"/>
        <parameter name="aValue"/>
        <body>
          this.start = this.formatDuration(aStart);
          this.end = this.formatDuration(aEnd);
          this.step = aStep;
          if (!aValue)
            this.value = this.start;
          else
            this.value = this.formatDuration(aValue);

          this.updateLabels();
        </body>
      </method>

      <!-- Format a duration value to the proper format. -->
      <method name="formatDuration">
        <parameter name="aDuration"/>
        <body>
          return true;
        </body>
      </method>

      <!-- Extract the years from a duration -->
      <method name="getYears">
        <parameter name="aDuration"/>
        <body>
          return this.getDurationValue(aDuration, "Y");
        </body>
      </method>

      <!-- Extract the months from a duration -->
      <method name="getMonths">
        <parameter name="aDuration"/>
        <body>
          return this.getDurationValue(aDuration, "M");
        </body>
      </method>

      <!-- Extract the days from a duration -->
      <method name="getDays">
        <parameter name="aDuration"/>
        <body>
          return this.getDurationValue(aDuration, "D");
        </body>
      </method>

      <!-- Extract the hours from a duration -->
      <method name="getHours">
        <parameter name="aDuration"/>
        <body>
          return this.getDurationValue(aDuration, "H");
        </body>
      </method>

      <!-- Extract the minutes from a duration -->
      <method name="getMinutes">
        <parameter name="aDuration"/>
        <body>
        <![CDATA[
          // Minutes is represented by the designator 'M', but so is
          // Months, so first extract the time portion of the duration.
          var indexT = aDuration.indexOf("T");
          var time = aDuration.substring(indexT);
          return this.getDurationValue(time, "M");
        ]]>
        </body>
      </method>

      <!-- Extract the seconds from a duration -->
      <method name="getSeconds">
        <parameter name="aDuration"/>
        <body>
          return this.getDurationValue(aDuration, "S");
        </body>
      </method>

      <!-- Get the value of a given duration designator.
           The value precedes the designator and may be any length.
       -->
      <method name="getDurationValue">
        <parameter name="aDuration"/>
        <parameter name="aDesignator"/>
        <body>
        <![CDATA[
          var duration = 0;
          // Find the designator.
          var index = aDuration.indexOf(aDesignator);
          if (index != -1) {
            // Find the starting index of the value by searching
            // backwards until we encounter another designator.
            var i = index - 1;
            while (!this.isDesignator(aDuration.charCodeAt(i)))
              --i;

            // The value is between the two designators.
            duration = parseFloat(aDuration.substring(i + 1, index));
          }

          return duration;
        ]]>
        </body>
      </method>

      <!-- Is the character one of the duration designators?
       -->
      <method name="isDesignator">
        <parameter name="aCharCode"/>
        <body>
        <![CDATA[
          // Char codes: 'P' == 80
          //             'Y' == 89
          //             'M' == 77
          //             'D' == 68
          //             'T' == 84
          //             'H' == 72
          //             'S' == 83
          var isdesignator = false;

          if (aCharCode == 80 || aCharCode == 89 ||
              aCharCode == 77 || aCharCode == 68 ||
              aCharCode == 84 || aCharCode == 72 ||
              aCharCode == 83)
              isdesignator = true;

          return isdesignator;
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <!-- YEARMONTHDURATION -->
  <binding id="yearmonthduration" extends="#duration">
    <implementation>
    <!-- interface -->
      <!-- yearMonthDuration format: PnYnM (years and months) -->

      <!-- Format a yearMonthDuration to the format PnYnM so that we
           have a non-zero number of years and the months range between
           0 - 11.

           If aDuration is null, this method will return a yearMonthDuration
           of zero years and zero months (P0Y0M).
      -->
      <method name="formatDuration">
        <parameter name="aDuration"/>
        <body>
        <![CDATA[
          var years = this.getYears(aDuration);
          var months = this.getMonths(aDuration);

          years = years + Math.floor(months / 12);
          months = months % 12;

          return "P" + years + "Y" + months + "M";
        ]]>
        </body>
      </method>

      <!-- Get the total number of months in a yearMonthDuration. -->
      <method name="getDurationMonths">
        <parameter name="aDuration"/>
        <body>
        <![CDATA[
          var years = this.getYears(aDuration);
          var months = this.getMonths(aDuration);
          var durationMonths = (years * 12) + months;

          return durationMonths;
        ]]>
        </body>
      </method>

      <!-- Get the minimum years value. -->
      <method name="getYearsMin">
        <body>
        <![CDATA[
          // The min value for the years spinbutton will always
          // be the year of the start duration.
          return this.getYears(this.start);
        ]]>
        </body>
      </method>

      <!-- Get the maximum years value. -->
      <method name="getYearsMax">
        <body>
        <![CDATA[
          // The max value for the years spinbutton will always
          // be the year of the end duration.
          return this.getYears(this.end);
        ]]>
        </body>
      </method>

      <!-- Get the minimum months value. -->
      <method name="getMonthsMin">
        <body>
        <![CDATA[
          // The min value for the month spinbutton depends on the year
          // range and the current value of the year.
          var minMonth = 0;

          var startYear = this.getYears(this.start);
          var endYear = this.getYears(this.end);
          var currentYear = this.getYears(this.value);

          if (startYear == endYear || currentYear == startYear) {
            // Min month is the month of the start duration.
            minMonth = this.getMonths(this.start);
          } else {
            // Current year falls between the start and end year.
            minMonth = 0;
          }
          return minMonth;
        ]]>
        </body>
      </method>

      <!-- Get the maximum months value. -->
      <method name="getMonthsMax">
        <body>
        <![CDATA[
          // The max value for the month spinbutton depends on the year
          // range and the current value of the year.
          var maxMonth = 0;

          var startYear = this.getYears(this.start);
          var endYear = this.getYears(this.end);
          var currentYear = this.getYears(this.value);

          if (startYear == endYear || currentYear == endYear) {
            // Max month is the month of the end duration.
            maxMonth = this.getMonths(this.end);
          } else {
            // Current year falls between the start and end year.
            maxMonth = 11;
          }

          return maxMonth;
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <!-- DAYTIMEDURATION -->
  <binding id="daytimeduration" extends="#duration">
    <implementation>
    <!-- interface -->
      <!-- dayTimeDuration format: PnDTnHnMnS (days, hours, minutes, seconds). -->

      <!-- Format a dayTimeDuration to the format PnDTnHnMnS so that we have a
           whole number of days, hours are between 0 - 23, minutes are between
           0 - 59, and seconds are between 0 - 59.

           If aDuration is null, this method will return a dayTimeDuration of
           zero days, zero hours, zero minutes, and zero seconds (P0DT0H0M0S).
      -->
      <method name="formatDuration">
        <parameter name="aDuration"/>
        <body>
        <![CDATA[
          var days = this.getDays(aDuration);
          var hours = this.getHours(aDuration);
          var minutes = this.getMinutes(aDuration);
          var seconds = this.getSeconds(aDuration);

          // If the seconds value exceeds 60, allocate the number of
          // minutes in the seconds to the minutes value.
          minutes = minutes + Math.floor(seconds / 60);
          seconds = seconds % 60;

          // If the minutes value now exceeds 60, allocate the number of
          // hours in the minutes to the hours value.
          hours = hours + Math.floor(minutes / 60);
          minutes = minutes % 60;

          // If the hours value now exceeds 23, allocate the number of
          // days in the hours to the days value.
          days = days + Math.floor(hours / 24);
          hours = hours % 24;

          return "P" + days + "DT" + hours + "H" + minutes + "M" + seconds + "S";
        ]]>
        </body>
      </method>

      <!-- Get the total number of seconds in a dayTimeDuration -->
      <method name="getDurationSeconds">
        <parameter name="aDuration"/>
        <body>
        <![CDATA[
          var days = this.getDays(aDuration);
          var hours = this.getHours(aDuration);
          var minutes = this.getMinutes(aDuration);
          var seconds = this.getSeconds(aDuration);

          var durationSeconds = (days * 86400) +
                                (hours * 3600) +
                                (minutes * 60) +
                                seconds;

          return durationSeconds;
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

</bindings>
