toolkit/content/widgets/tree.xml
author Matthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 08 Feb 2019 17:46:51 -0800
changeset 458980 047455dcfc2037b755cd2c23434f0b8767b7b352
parent 456101 73a91e84dbec4fe4cd6881c0bdb377f7b0137e42
permissions -rw-r--r--
Bug 1439023 - Don't reset autocomplete controller state when autofill fell back to form history. r=jaws Otherwise the cached value won't be used for filling by satchel and _fillFromAutocompleteRow won't fill a result that isn't a `autofill-profile` style: https://searchfox.org/mozilla-central/rev/03ebbdab952409640c6857d835d3040bf6f9e2db/browser/extensions/formautofill/FormAutofillContent.jsm#283,295 Differential Revision: https://phabricator.services.mozilla.com/D19249

<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE bindings [
<!ENTITY % treeDTD SYSTEM "chrome://global/locale/tree.dtd">
%treeDTD;
]>

<bindings id="treeBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="tree" extends="chrome://global/content/bindings/general.xml#basecontrol">
    <content hidevscroll="true" hidehscroll="true" clickthrough="never">
      <children includes="treecols"/>
      <xul:stack class="tree-stack" flex="1">
        <xul:hbox class="tree-rows" flex="1">
          <xul:hbox flex="1" class="tree-bodybox">
            <children/>
          </xul:hbox>
          <xul:scrollbar height="0" minwidth="0" minheight="0" orient="vertical" xbl:inherits="collapsed=hidevscroll" style="position:relative; z-index:2147483647;"
            oncontextmenu="event.stopPropagation(); event.preventDefault();"
            onclick="event.stopPropagation(); event.preventDefault();"
            ondblclick="event.stopPropagation();"
            oncommand="event.stopPropagation();"/>
          </xul:hbox>
        <xul:textbox anonid="input" class="tree-input" left="0" top="0" hidden="true"/>
      </xul:stack>
      <xul:hbox xbl:inherits="collapsed=hidehscroll">
        <xul:scrollbar orient="horizontal" flex="1" increment="16" style="position:relative; z-index:2147483647;"
          oncontextmenu="event.stopPropagation(); event.preventDefault();"
          onclick="event.stopPropagation(); event.preventDefault();"
          ondblclick="event.stopPropagation();"
          oncommand="event.stopPropagation();"/>
        <xul:scrollcorner xbl:inherits="collapsed=hidevscroll"
          oncontextmenu="event.stopPropagation(); event.preventDefault();"
          onclick="event.stopPropagation(); event.preventDefault();"
          ondblclick="event.stopPropagation();"
          oncommand="event.stopPropagation();"/>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMXULMultiSelectControlElement">
      <property name="body"
                onget="return this.treeBody;"/>

      <property name="editable"
                onget="return this.getAttribute('editable') == 'true';"
                onset="if (val) this.setAttribute('editable', 'true');
                       else this.removeAttribute('editable'); return val;"/>

      <!-- ///////////////// nsIDOMXULSelectControlElement ///////////////// -->

      <!-- ///////////////// nsIDOMXULMultiSelectControlElement ///////////////// -->

      <property name="selType"
                onget="return this.getAttribute('seltype')"
                onset="this.setAttribute('seltype', val); return val;"/>

      <property name="currentIndex"
                onget="return this.view ? this.view.selection.currentIndex: - 1;"
                onset="if (this.view) return this.view.selection.currentIndex = val; return val;"/>

      <field name="pageUpOrDownMovesSelection">
        !/Mac/.test(navigator.platform)
      </field>
      <property name="keepCurrentInView"
                onget="return (this.getAttribute('keepcurrentinview') == 'true');"
                onset="if (val) this.setAttribute('keepcurrentinview', 'true');
                       else this.removeAttribute('keepcurrentinview'); return val;"/>

      <property name="enableColumnDrag"
                onget="return this.hasAttribute('enableColumnDrag');"
                onset="if (val) this.setAttribute('enableColumnDrag', 'true');
                       else this.removeAttribute('enableColumnDrag'); return val;"/>

      <field name="_inputField">null</field>

      <property name="inputField" readonly="true">
        <getter><![CDATA[
          if (!this._inputField)
            this._inputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
          return this._inputField;
        ]]></getter>
      </property>

     <property name="disableKeyNavigation"
                onget="return this.hasAttribute('disableKeyNavigation');"
                onset="if (val) this.setAttribute('disableKeyNavigation', 'true');
                       else this.removeAttribute('disableKeyNavigation'); return val;"/>

      <field name="_editingRow">-1</field>
      <field name="_editingColumn">null</field>

      <property name="editingRow" readonly="true"
                onget="return this._editingRow;"/>
      <property name="editingColumn" readonly="true"
                onget="return this._editingColumn;"/>

      <property name="_selectDelay"
                onset="this.setAttribute('_selectDelay', val);"
                onget="return this.getAttribute('_selectDelay') || 50;"/>
      <field name="_columnsDirty">true</field>
      <field name="_lastKeyTime">0</field>
      <field name="_incrementalString">""</field>

      <field name="_touchY">-1</field>

      <method name="_ensureColumnOrder">
        <body><![CDATA[
          if (!this._columnsDirty)
            return;

          if (this.columns) {
            // update the ordinal position of each column to assure that it is
            // an odd number and 2 positions above its next sibling
            var cols = [];
            var i;
            for (var col = this.columns.getFirstColumn(); col; col = col.getNext())
              cols.push(col.element);
            for (i = 0; i < cols.length; ++i)
              cols[i].setAttribute("ordinal", (i * 2) + 1);

            // update the ordinal positions of splitters to even numbers, so that
            // they are in between columns
            var splitters = this.getElementsByTagName("splitter");
            for (i = 0; i < splitters.length; ++i)
              splitters[i].setAttribute("ordinal", (i + 1) * 2);
          }
          this._columnsDirty = false;
        ]]></body>
      </method>

      <method name="_reorderColumn">
        <parameter name="aColMove"/>
        <parameter name="aColBefore"/>
        <parameter name="aBefore"/>
        <body><![CDATA[
          this._ensureColumnOrder();

          var i;
          var cols = [];
          var col = this.columns.getColumnFor(aColBefore);
          if (parseInt(aColBefore.ordinal) < parseInt(aColMove.ordinal)) {
            if (aBefore)
              cols.push(aColBefore);
            for (col = col.getNext(); col.element != aColMove;
                 col = col.getNext())
              cols.push(col.element);

            aColMove.ordinal = cols[0].ordinal;
            for (i = 0; i < cols.length; ++i)
              cols[i].ordinal = parseInt(cols[i].ordinal) + 2;
          } else if (aColBefore.ordinal != aColMove.ordinal) {
            if (!aBefore)
              cols.push(aColBefore);
            for (col = col.getPrevious(); col.element != aColMove;
                 col = col.getPrevious())
              cols.push(col.element);

            aColMove.ordinal = cols[0].ordinal;
            for (i = 0; i < cols.length; ++i)
              cols[i].ordinal = parseInt(cols[i].ordinal) - 2;
          }
        ]]></body>
      </method>

      <method name="_getColumnAtX">
        <parameter name="aX"/>
        <parameter name="aThresh"/>
        <parameter name="aPos"/>
        <body><![CDATA[
          var isRTL = document.defaultView.getComputedStyle(this)
                              .direction == "rtl";

          if (aPos)
            aPos.value = isRTL ? "after" : "before";

          var columns = [];
          var col = this.columns.getFirstColumn();
          while (col) {
            columns.push(col);
            col = col.getNext();
          }
          if (isRTL)
            columns.reverse();
          var currentX = this.boxObject.x;
          var adjustedX = aX + this.horizontalPosition;
          for (var i = 0; i < columns.length; ++i) {
            col = columns[i];
            var cw = col.element.boxObject.width;
            if (cw > 0) {
              currentX += cw;
              if (currentX - (cw * aThresh) > adjustedX)
                return col.element;
            }
          }

          if (aPos)
            aPos.value = isRTL ? "before" : "after";
          return columns.pop().element;
        ]]></body>
      </method>

      <method name="changeOpenState">
        <parameter name="row"/>
        <!-- Optional parameter openState == true or false to set.
             No openState param == toggle -->
        <parameter name="openState"/>
        <body><![CDATA[
          if (row < 0 || !this.view.isContainer(row)) {
            return false;
          }

          if (this.view.isContainerOpen(row) != openState) {
            this.view.toggleOpenState(row);
            if (row == this.currentIndex) {
              // Only fire event when current row is expanded or collapsed
              // because that's all the assistive technology really cares about.
              var event = document.createEvent("Events");
              event.initEvent("OpenStateChange", true, true);
              this.dispatchEvent(event);
            }
            return true;
          }
          return false;
        ]]></body>
      </method>

      <method name="_keyNavigate">
        <parameter name="event"/>
        <body><![CDATA[
          var key = String.fromCharCode(event.charCode).toLowerCase();
          if (event.timeStamp - this._lastKeyTime > 1000)
            this._incrementalString = key;
          else
            this._incrementalString += key;
          this._lastKeyTime = event.timeStamp;

          var length = this._incrementalString.length;
          var incrementalString = this._incrementalString;
          var charIndex = 1;
          while (charIndex < length && incrementalString[charIndex] == incrementalString[charIndex - 1])
            charIndex++;
          // If all letters in incremental string are same, just try to match the first one
          if (charIndex == length) {
            length = 1;
            incrementalString = incrementalString.substring(0, length);
          }

          var keyCol = this.columns.getKeyColumn();
          var rowCount = this.view.rowCount;
          var start = 1;

          var c = this.currentIndex;
          if (length > 1) {
            start = 0;
            if (c < 0)
              c = 0;
          }

          for (var i = 0; i < rowCount; i++) {
            var l = (i + start + c) % rowCount;
            var cellText = this.view.getCellText(l, keyCol);
            cellText = cellText.substring(0, length).toLowerCase();
            if (cellText == incrementalString)
              return l;
          }
          return -1;
        ]]></body>
      </method>

      <method name="startEditing">
        <parameter name="row"/>
        <parameter name="column"/>
        <body>
          <![CDATA[
            if (!this.editable)
              return false;
            if (row < 0 || row >= this.view.rowCount || !column)
              return false;
            if (column.type != window.TreeColumn.TYPE_TEXT &&
                column.type != window.TreeColumn.TYPE_PASSWORD)
              return false;
            if (column.cycler || !this.view.isEditable(row, column))
              return false;

            // Beyond this point, we are going to edit the cell.
            if (this._editingColumn)
              this.stopEditing();

            var input = this.inputField;

            this.ensureCellIsVisible(row, column);

            // Get the coordinates of the text inside the cell.
            var textRect = this.getCoordsForCellItem(row, column, "text");

            // Get the coordinates of the cell itself.
            var cellRect = this.getCoordsForCellItem(row, column, "cell");

            // Calculate the top offset of the textbox.
            var style = window.getComputedStyle(input);
            var topadj = parseInt(style.borderTopWidth) + parseInt(style.paddingTop);
            input.top = textRect.y - topadj;

            // The leftside of the textbox is aligned to the left side of the text
            // in LTR mode, and left side of the cell in RTL mode.
            var left, widthdiff;
            if (style.direction == "rtl") {
              left = cellRect.x;
              widthdiff = cellRect.x - textRect.x;
            } else {
              left = textRect.x;
              widthdiff = textRect.x - cellRect.x;
            }

            input.left = left;
            input.height = textRect.height + topadj +
                           parseInt(style.borderBottomWidth) +
                           parseInt(style.paddingBottom);
            input.width = cellRect.width - widthdiff;
            input.hidden = false;

            input.value = this.view.getCellText(row, column);

            input.select();
            input.inputField.focus();

            this._editingRow = row;
            this._editingColumn = column;
            this.setAttribute("editing", "true");

            this.invalidateCell(row, column);
            return true;
          ]]>
        </body>
      </method>

      <method name="stopEditing">
        <parameter name="accept"/>
        <body>
          <![CDATA[
            if (!this._editingColumn)
              return;

            var input = this.inputField;
            var editingRow = this._editingRow;
            var editingColumn = this._editingColumn;
            this._editingRow = -1;
            this._editingColumn = null;

            if (accept) {
              var value = input.value;
              this.view.setCellText(editingRow, editingColumn, value);
            }
            input.hidden = true;
            input.value = "";
            this.removeAttribute("editing");
          ]]>
        </body>
      </method>

      <method name="_moveByOffset">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (event.getModifierState("Accel") && this.view.selection.single) {
              this.scrollByLines(offset);
              return;
            }

            var c = this.currentIndex + offset;
            if (offset > 0 ? c > edge : c < edge) {
              if (this.view.selection.isSelected(edge) && this.view.selection.count <= 1)
                return;
              c = edge;
            }

            if (!event.getModifierState("Accel"))
              this.view.selection.timedSelect(c, this._selectDelay);
            else // Ctrl+Up/Down moves the anchor without selecting
              this.currentIndex = c;
            this.ensureRowIsVisible(c);
          ]]>
        </body>
      </method>

      <method name="_moveByOffsetShift">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.selection.single) {
              this.scrollByLines(offset);
              return;
            }

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            var c = this.currentIndex;
            if (c == -1)
                c = 0;

            if (c == edge) {
              if (this.view.selection.isSelected(c))
                return;
            }

            // Extend the selection from the existing pivot, if any
            this.view.selection.rangedSelect(-1, c + offset,
                                             event.getModifierState("Accel"));
            this.ensureRowIsVisible(c + offset);

          ]]>
        </body>
      </method>

      <method name="_moveByPage">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.pageUpOrDownMovesSelection == event.getModifierState("Accel")) {
               this.scrollByPages(offset);
               return;
            }

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            var c = this.currentIndex;
            if (c == -1)
              return;

            if (c == edge && this.view.selection.isSelected(c)) {
              this.ensureRowIsVisible(c);
              return;
            }
            var i = this.getFirstVisibleRow();
            var p = this.getPageLength();

            if (offset > 0) {
              i += p - 1;
              if (c >= i) {
                 i = c + p;
                 this.ensureRowIsVisible(i > edge ? edge : i);
              }
              i = i > edge ? edge : i;
            } else if (c <= i) {
               i = c <= p ? 0 : c - p;
               this.ensureRowIsVisible(i);
            }
            this.view.selection.timedSelect(i, this._selectDelay);
          ]]>
        </body>
      </method>

      <method name="_moveByPageShift">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0) &&
                !(this.pageUpOrDownMovesSelection == event.getModifierState("Accel"))) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            if (this.view.selection.single)
              return;

            var c = this.currentIndex;
            if (c == -1)
              return;
            if (c == edge && this.view.selection.isSelected(c)) {
              this.ensureRowIsVisible(edge);
              return;
            }
            var i = this.getFirstVisibleRow();
            var p = this.getPageLength();

            if (offset > 0) {
              i += p - 1;
              if (c >= i) {
                 i = c + p;
                 this.ensureRowIsVisible(i > edge ? edge : i);
              }
              // Extend the selection from the existing pivot, if any
              this.view.selection.rangedSelect(-1, i > edge ? edge : i, event.getModifierState("Accel"));
            } else {
              if (c <= i) {
                 i = c <= p ? 0 : c - p;
                 this.ensureRowIsVisible(i);
              }
              // Extend the selection from the existing pivot, if any
              this.view.selection.rangedSelect(-1, i, event.getModifierState("Accel"));
            }

          ]]>
        </body>
      </method>

      <method name="_moveToEdge">
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.selection.isSelected(edge) && this.view.selection.count == 1) {
              this.currentIndex = edge;
              return;
            }

            // Normal behaviour is to select the first/last row
            if (!event.getModifierState("Accel"))
              this.view.selection.timedSelect(edge, this._selectDelay);

            // In a multiselect tree Ctrl+Home/End moves the anchor
            else if (!this.view.selection.single)
              this.currentIndex = edge;

            this.ensureRowIsVisible(edge);
          ]]>
        </body>
      </method>

      <method name="_moveToEdgeShift">
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            if (this.view.selection.single ||
                (this.view.selection.isSelected(edge)) && this.view.selection.isSelected(this.currentIndex))
              return;

            // Extend the selection from the existing pivot, if any.
            // -1 doesn't work here, so using currentIndex instead
            this.view.selection.rangedSelect(this.currentIndex, edge, event.getModifierState("Accel"));

            this.ensureRowIsVisible(edge);
          ]]>
        </body>
      </method>
      <method name="_handleEnter">
        <parameter name="event"/>
        <body><![CDATA[
          if (this._editingColumn) {
            this.stopEditing(true);
            this.focus();
            return true;
          }

          return this.changeOpenState(this.currentIndex);
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="underflow">
        <![CDATA[
          // Scrollport event orientation
          // 0: vertical
          // 1: horizontal
          // 2: both (not used)
          if (event.target.tagName != "treechildren")
            return;
          if (event.detail == 1)
            this.setAttribute("hidehscroll", "true");
          else if (event.detail == 0)
            this.setAttribute("hidevscroll", "true");
          event.stopPropagation();
        ]]>
      </handler>
      <handler event="overflow">
        <![CDATA[
          if (event.target.tagName != "treechildren")
            return;
          if (event.detail == 1)
            this.removeAttribute("hidehscroll");
          else if (event.detail == 0)
            this.removeAttribute("hidevscroll");
          event.stopPropagation();
        ]]>
      </handler>
      <handler event="touchstart">
        <![CDATA[
          function isScrollbarElement(target) {
            return (target.localName == "thumb" || target.localName == "slider")
                && target.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          }
          if (event.touches.length > 1 || isScrollbarElement(event.touches[0].target)) {
            // Multiple touch points detected, abort. In particular this aborts
            // the panning gesture when the user puts a second finger down after
            // already panning with one finger. Aborting at this point prevents
            // the pan gesture from being resumed until all fingers are lifted
            // (as opposed to when the user is back down to one finger).
            // Additionally, if the user lands on the scrollbar don't use this
            // code for scrolling, instead allow gecko to handle scrollbar
            // interaction normally.
            this._touchY = -1;
          } else {
            this._touchY = event.touches[0].screenY;
          }
        ]]>
      </handler>
      <handler event="touchmove">
        <![CDATA[
          if (event.touches.length == 1 &&
              this._touchY >= 0) {
            var deltaY = this._touchY - event.touches[0].screenY;
            var lines = Math.trunc(deltaY / this.rowHeight);
            if (Math.abs(lines) > 0) {
              this.scrollByLines(lines);
              deltaY -= lines * this.rowHeight;
              this._touchY = event.touches[0].screenY + deltaY;
            }
            event.preventDefault();
          }
        ]]>
      </handler>
      <handler event="touchend">
        <![CDATA[
          this._touchY = -1;
        ]]>
      </handler>
      <handler event="MozMousePixelScroll">
        <![CDATA[
          if (!(this.getAttribute("allowunderflowscroll") == "true" &&
                this.getAttribute("hidevscroll") == "true"))
            event.preventDefault();
        ]]>
      </handler>
      <handler event="DOMMouseScroll">
        <![CDATA[
          if (!(this.getAttribute("allowunderflowscroll") == "true" &&
                this.getAttribute("hidevscroll") == "true"))
            event.preventDefault();

          if (this._editingColumn)
            return;
          if (event.axis == event.HORIZONTAL_AXIS)
            return;

          var rows = event.detail;
          if (rows == UIEvent.SCROLL_PAGE_UP)
            this.scrollByPages(-1);
          else if (rows == UIEvent.SCROLL_PAGE_DOWN)
            this.scrollByPages(1);
          else
            this.scrollByLines(rows);
        ]]>
      </handler>
      <handler event="MozSwipeGesture" preventdefault="true">
        <![CDATA[
          // Figure out which row to show
          let targetRow = 0;

          // Only handle swipe gestures up and down
          switch (event.direction) {
            case event.DIRECTION_DOWN:
              targetRow = this.view.rowCount - 1;
              // Fall through for actual action
            case event.DIRECTION_UP:
              this.ensureRowIsVisible(targetRow);
              break;
          }
        ]]>
      </handler>
      <handler event="select" phase="target"
               action="if (event.originalTarget == this) this.stopEditing(true);"/>
      <handler event="focus">
        <![CDATA[
          this.focused = true;
          if (this.currentIndex == -1 && this.view.rowCount > 0) {
            this.currentIndex = this.getFirstVisibleRow();
          }
        ]]>
      </handler>
      <handler event="blur" action="this.focused = false;"/>
      <handler event="blur" phase="capturing"
               action="if (event.originalTarget == this.inputField.inputField) this.stopEditing(true);"/>
      <handler event="keydown" keycode="VK_RETURN">
        if (this._handleEnter(event)) {
          event.stopPropagation();
          event.preventDefault();
        }
      </handler>
      <handler event="keydown" keycode="VK_ESCAPE">
        <![CDATA[
          if (this._editingColumn) {
            this.stopEditing(false);
            this.focus();
            event.stopPropagation();
            event.preventDefault();
          }
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_LEFT">
        <![CDATA[
         if (this._editingColumn)
           return;

         var row = this.currentIndex;
         if (row < 0)
           return;

         if (this.changeOpenState(this.currentIndex, false)) {
           event.preventDefault();
           return;
         }
         var parentIndex = this.view.getParentIndex(this.currentIndex);
         if (parentIndex >= 0) {
           this.view.selection.select(parentIndex);
           this.ensureRowIsVisible(parentIndex);
           event.preventDefault();
         }
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_RIGHT">
        <![CDATA[
         if (this._editingColumn)
           return;

          var row = this.currentIndex;
          if (row < 0)
            return;

          if (this.changeOpenState(row, true)) {
            event.preventDefault();
            return;
          }
          var c = row + 1;
          var view = this.view;
          if (c < view.rowCount &&
              view.getParentIndex(c) == row) {
            // If already opened, select the first child.
            // The getParentIndex test above ensures that the children
            // are already populated and ready.
            this.view.selection.timedSelect(c, this._selectDelay);
            this.ensureRowIsVisible(c);
            event.preventDefault();
          }
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_UP" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffset(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_DOWN" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffset(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_UP" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffsetShift(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_DOWN" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffsetShift(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPage(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPage(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPageShift(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPageShift(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_HOME" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdge(0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_END" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdge(this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_HOME" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdgeShift(0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_END" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdgeShift(this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keypress">
        <![CDATA[
         if (this._editingColumn)
           return;

         if (event.charCode == " ".charCodeAt(0)) {
           var c = this.currentIndex;
           if (!this.view.selection.isSelected(c) ||
               (!this.view.selection.single && event.getModifierState("Accel"))) {
             this.view.selection.toggleSelect(c);
             event.preventDefault();
           }
         } else if (!this.disableKeyNavigation && event.charCode > 0 &&
                    !event.altKey && !event.getModifierState("Accel") &&
                    !event.metaKey && !event.ctrlKey) {
           var l = this._keyNavigate(event);
           if (l >= 0) {
             this.view.selection.timedSelect(l, this._selectDelay);
             this.ensureRowIsVisible(l);
           }
           event.preventDefault();
         }
         ]]>
      </handler>
    </handlers>
  </binding>

</bindings>