<?xml version="1.0" encoding="utf-8"?>

<!-- ***** 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
   - Novell, Inc.
   - Portions created by the Initial Developer are Copyright (C) 2005
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   -  Allan Beaufour <abeaufour@novell.com>
   -  Alexander Surkov <surkov.alexander@gmail.com>
   -  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 ***** -->

<bindings xmlns="http://www.mozilla.org/xbl"
          xmlns:html="http://www.w3.org/1999/xhtml">

  <!-- RANGE: BASE
    This binding is base for xforms range controls. It assumes successors
    bindings implement getElementControl() method that returns the object:
    {
      get/set value(); // get/set value
      set readonly(); // makes range disabled
      get/set start(); // get/set @start attribute
      get/set end(); // get/set @end attribute
      get/set step(); // get/set @step attribute
      focus() // set the focus
      set(); // set start, end, step, value
    }

    Successor bindings should override isInRange and adjustRangeValues to do
    what is appropriate for the particular datatype they use.
  -->
  <binding id="xformswidget-range-base"
           extends="chrome://xforms/content/xforms.xml#xformswidget-base">

    <implementation>
      <method name="refresh">
        <body>
        <![CDATA[
          // The start/end/step attrs are all optional and if any are omitted
          // we need to adjust their values.
          if (!this.adjustRangeValues(this.start, this.end, this.step))
            return;

          this.changed = false;

          this.control.readonly = this.accessors.isReadonly();

          var value = this.accessors.getValue();
          var inrange = this.isInRange(value);
          this.accessors.setInRange(inrange);

          this.control.set(this.start, this.end, this.step, value);
        ]]>
        </body>
      </method>

      <!-- Determine whether or not the current value is between the start
           and end values for the range so we can dispatch xforms-in-range
           or xforms-out-of-range events.
      -->
      <method name="isInRange">
        <body>
          return true;
        </body>
      </method>

       <!-- The start/end/step attrs are all optional and if any are omitted
            we need to adjust their values.
       -->
      <method name="adjustRangeValues">
        <body>
          return true;
        </body>
      </method>

      <method name="focus">
        <body>
          this.control.focus();
        </body>
      </method>


      <property name="start"
                onget="return this.control.start;"
                onset="this.setAttribute('start', val); this.control.start = val;"/>

      <property name="end"
                onget="return this.control.end;"
                onset="this.setAttribute('end', val); this.control.end = val;"/>

      <property name="step"
                onget="return this.control.step;"
                onset="this.setAttribute('step', val); this.control.step = val;"/>

      <property name="incremental">
        <getter>
          return this.getAttribute("incremental") == "true";
        </getter>
        <setter>
          if (val)
            this.setAttribute("incremental", "true");
          else
            this.removeAttribute("incremental");
        </setter>
      </property>

      <property name="accessors" readonly="true">
        <getter>
        <![CDATA[
          if (!this._accessors && this.delegate) {
            this._accessors = this.delegate.getXFormsAccessors().
              QueryInterface(Components.interfaces.nsIXFormsRangeAccessors);
          }
          return this._accessors;
        ]]>
        </getter>
      </property>

      <method name="getCurrentValue">
        <body>
          return this.control.value;
        </body>
      </method>

      <!--
        updateInstanceData updates the instance data bound to this control
        under certain conditions.

        If aIncremental is false, it means that this function was called due
        to a blur event on the control.  In this case, we should update the
        instance data only if the control is NOT incremental and the user has
        interacted with the control since the last refresh.

        If aIncremental is true, it means that this function was called because
        the user interacted with the control (either through mouse or keyboard
        action) and we set a flag to indicate this fact.  If the control is
        incremental, we update the value of the bound instance data.
      -->
      <method name="updateInstanceData">
        <parameter name="aIncremental"/>
        <body>
        <![CDATA[
          if (!this.accessors.hasBoundNode())
            return;

          if (aIncremental) {
            this.changed = true;
            if (!this.incremental)
              return;
          } else {
            if (!this.changed)
              return;
          }

          this.accessors.setValue(this.control.value);
          this.changed = false;
        ]]>
        </body>
      </method>

      <!--
        We need to remember if the user has interacted with this control.
        We don't want to change the value of the node that this control is
        bound to unless the user caused the change.
      -->
      <field name="changed">false</field>

    </implementation>
  </binding>

  <!-- RANGE: <NUMBER>
    This binding is base for xforms range controls with numeric types.
    Successor bindings should implement a getElementControl() method
    that returns numeric types.
  -->
  <binding id="xformswidget-range-numeric-base"
           extends="chrome://xforms/content/range.xml#xformswidget-range-base">

    <implementation implements="nsIAccessibleProvider">
      <!-- Any range type that uses a slider widget as the element control
        should return Components.interfaces.nsIAccessibleProvider.XFormsRange
        as its accessible type.
      -->
      <property name="accessibleType" readonly="true">
        <getter>
          return Components.interfaces.nsIAccessibleProvider.XFormsSliderRange;
        </getter>
      </property>

      <method name="getCurrentValue">
        <body>
          var value = this.control.value;
          return isNaN(value) ? "" : value;
        </body>
      </method>

      <method name="isInRange">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          var value = parseFloat(aValue);
          if (isNaN(value))
            return false;
          return value >= this.start && value <= this.end;
        ]]>
        </body>
      </method>

      <!-- A range control with numeric types is currently implemented as a
           slider and a slider with a large number of steps does not look
           nice or perform very well; therefore, we limit the maximum number
           of steps to a reasonable value of 20. The start/end/step values
           will be adjusted accordingly if any are omitted.
      -->
      <method name="adjustRangeValues">
        <parameter name="aStart"/>
        <parameter name="aEnd"/>
        <parameter name="aStep"/>
        <body>
          <![CDATA[
          // Start > End is an error.
          if (!isNaN(aStart) && !isNaN(aEnd)) {
            if (aStart > aEnd) {
              this.delegate.reportError("rangeBeginEndError");
              return false;
            }
          }

          if (isNaN(aStep)) {
            // Step is not specified and will depend on the values of start
            // and end (which also may not be specified).
            if (isNaN(aStart) && isNaN(aEnd)) {
              // None of the range attributes are specified! Show a default
              // slider with 20 steps.
              this.start = 0;
              this.end = 20;
              this.step = 1;
            } else if (!isNaN(aStart) && isNaN(aEnd)) {
              // Start is specified but End is not. Set end to start + 20 with
              // a default step of 1 to maintain our maximum range of 20 steps.
              this.start = aStart;
              this.end = this.start + 20;
              this.step = 1;
            } else if (isNaN(aStart) && !isNaN(aEnd)) {
              // End is specified but Start is not. Use a default step of 1
              // and adjust start to maintain the maximum range of 20 steps.
              this.start = aEnd - 20;
              this.end = aEnd;
              this.step = 1;
            } else {
              // Both Start and End are specified.  Calculate a step value
              // that will maintain our maximum range of 20 steps.
              this.start = aStart;
              this.end = aEnd;
              this.step = Math.round((aEnd - aStart) / 20);
            }
          } else {
            // Step is specified.  Adjust start and end to maintain our
            // maximum range of 20 steps.
            if (isNaN(aStart) && isNaN(aEnd)) {
              // Neither Start nor End is specified. Calculate values for start
              // and end such that the slider has a maximum of 20 steps of size
              // aStep.
              this.start = 0;
              this.end = this.start + (aStep * 20);
              this.step = aStep;
            } else if (!isNaN(aStart) && isNaN(aEnd)) {
              // Begin is specified but End is not. Adjust end such that we
              // have 20 steps of size aStep between start and end.
              this.start = aStart;
              this.end = this.start + (aStep * 20);
              this.step = aStep;
            } else if (isNaN(aStart) && !isNaN(aEnd)) {
              // End is specified but Start is not. Adjust start such that we
              // have 20 steps of size aStep between start and end.
              this.start = aEnd - (aStep * 20);
              this.end = aEnd;
              this.step = aStep;
            } else {
              // Both Start and End are specified, but the specified Step
              // value may not be suitable for maintaining the maximum
              // range of 20 steps.
              if ((aEnd - aStart) / aStep > 20) {
                // Honor the specified start and end values, but adjust
                // the Step value.
                this.step = Math.round((aEnd - aStart) / 20);
              } else {
                this.step = aStep;
              }
              this.start = aStart;
              this.end = aEnd;
            }
          }
          return true;
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

</bindings>
