diff options
Diffstat (limited to 'toolkit/content/widgets/tree.xml')
-rw-r--r-- | toolkit/content/widgets/tree.xml | 1561 |
1 files changed, 1561 insertions, 0 deletions
diff --git a/toolkit/content/widgets/tree.xml b/toolkit/content/widgets/tree.xml new file mode 100644 index 0000000000..aa17172575 --- /dev/null +++ b/toolkit/content/widgets/tree.xml @@ -0,0 +1,1561 @@ +<?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-base" extends="chrome://global/content/bindings/general.xml#basecontrol"> + <resources> + <stylesheet src="chrome://global/skin/tree.css"/> + </resources> + <implementation> + <method name="_isAccelPressed"> + <parameter name="aEvent"/> + <body><![CDATA[ + return aEvent.getModifierState("Accel"); + ]]></body> + </method> + </implementation> + </binding> + + <binding id="tree" extends="chrome://global/content/bindings/tree.xml#tree-base" role="xul:tree"> + <content hidevscroll="true" hidehscroll="true" clickthrough="never"> + <children includes="treecols"/> + <xul:stack class="tree-stack" flex="1"> + <xul:treerows class="tree-rows" flex="1" xbl:inherits="hidevscroll"> + <children/> + </xul:treerows> + <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;"/> + <xul:scrollcorner xbl:inherits="collapsed=hidevscroll"/> + </xul:hbox> + </content> + + <implementation implements="nsIDOMXULTreeElement, nsIDOMXULMultiSelectControlElement"> + + <!-- ///////////////// nsIDOMXULTreeElement ///////////////// --> + + <property name="columns" + onget="return this.treeBoxObject.columns;"/> + + <property name="view" + onget="return this.treeBoxObject.view ? + this.treeBoxObject.view.QueryInterface(Components.interfaces.nsITreeView) : + null;" + onset="return this.treeBoxObject.view = val;"/> + + <property name="body" + onget="return this.treeBoxObject.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;"/> + + <property name="treeBoxObject" + onget="return this.boxObject;" + readonly="true"/> +# contentView is obsolete (see bug 202391) + <property name="contentView" + onget="return this.view; /*.QueryInterface(Components.interfaces.nsITreeContentView)*/" + readonly="true"/> +# builderView is obsolete (see bug 202393) + <property name="builderView" + onget="return this.view; /*.QueryInterface(Components.interfaces.nsIXULTreeBuilder)*/" + readonly="true"/> + <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.treeBoxObject.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> + + <property name="_cellSelType"> + <getter> + <![CDATA[ + var seltype = this.selType; + if (seltype == "cell" || seltype == "text") + return seltype; + return null; + ]]> + </getter> + </property> + + <method name="_getNextColumn"> + <parameter name="row"/> + <parameter name="left"/> + <body><![CDATA[ + var col = this.view.selection.currentColumn; + if (col) { + col = left ? col.getPrevious() : col.getNext(); + } + else { + col = this.columns.getKeyColumn(); + } + while (col && (col.width == 0 || !col.selectable || + !this.view.isSelectable(row, col))) + col = left ? col.getPrevious() : col.getNext(); + return col; + ]]></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 != Components.interfaces.nsITreeColumn.TYPE_TEXT && + column.type != Components.interfaces.nsITreeColumn.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; + + var box = this.treeBoxObject; + box.ensureCellIsVisible(row, column); + + // Get the coordinates of the text inside the cell. + var textRect = box.getCoordsForCellItem(row, column, "text"); + + // Get the coordinates of the cell itself. + var cellRect = box.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); + var selectText = function selectText() { + input.select(); + input.inputField.focus(); + } + setTimeout(selectText, 0); + + this._editingRow = row; + this._editingColumn = column; + this.setAttribute("editing", "true"); + + box.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 (this._isAccelPressed(event) && this.view.selection.single) { + this.treeBoxObject.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; + } + + var cellSelType = this._cellSelType; + if (cellSelType) { + var column = this.view.selection.currentColumn; + if (!column) + return; + + while ((offset > 0 ? c <= edge : c >= edge) && !this.view.isSelectable(c, column)) + c += offset; + if (offset > 0 ? c > edge : c < edge) + return; + } + + if (!this._isAccelPressed(event)) + this.view.selection.timedSelect(c, this._selectDelay); + else // Ctrl+Up/Down moves the anchor without selecting + this.currentIndex = c; + this.treeBoxObject.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.treeBoxObject.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, + this._isAccelPressed(event)); + this.treeBoxObject.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 == this._isAccelPressed(event)) { + this.treeBoxObject.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.treeBoxObject.ensureRowIsVisible(c); + return; + } + var i = this.treeBoxObject.getFirstVisibleRow(); + var p = this.treeBoxObject.getPageLength(); + + if (offset > 0) { + i += p - 1; + if (c >= i) { + i = c + p; + this.treeBoxObject.ensureRowIsVisible(i > edge ? edge : i); + } + i = i > edge ? edge : i; + + } else if (c <= i) { + i = c <= p ? 0 : c - p; + this.treeBoxObject.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 == this._isAccelPressed(event))) { + 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.treeBoxObject.ensureRowIsVisible(edge); + return; + } + var i = this.treeBoxObject.getFirstVisibleRow(); + var p = this.treeBoxObject.getPageLength(); + + if (offset > 0) { + i += p - 1; + if (c >= i) { + i = c + p; + this.treeBoxObject.ensureRowIsVisible(i > edge ? edge : i); + } + // Extend the selection from the existing pivot, if any + this.view.selection.rangedSelect(-1, i > edge ? edge : i, this._isAccelPressed(event)); + + } else { + + if (c <= i) { + i = c <= p ? 0 : c - p; + this.treeBoxObject.ensureRowIsVisible(i); + } + // Extend the selection from the existing pivot, if any + this.view.selection.rangedSelect(-1, i, this._isAccelPressed(event)); + } + + ]]> + </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 (!this._isAccelPressed(event)) + 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.treeBoxObject.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, this._isAccelPressed(event)); + + this.treeBoxObject.ensureRowIsVisible(edge); + ]]> + </body> + </method> + <method name="_handleEnter"> + <parameter name="event"/> + <body><![CDATA[ + if (this._editingColumn) { + this.stopEditing(true); + this.focus(); + return true; + } + + if (/Mac/.test(navigator.platform)) { + // See if we can edit the cell. + var row = this.currentIndex; + if (this._cellSelType) { + var column = this.view.selection.currentColumn; + var startedEditing = this.startEditing(row, column); + if (startedEditing) + return true; + } + } + return this.changeOpenState(this.currentIndex); + ]]></body> + </method> + </implementation> + + <handlers> + <handler event="touchstart"> + <![CDATA[ + if (event.touches.length > 1) { + // 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). + 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.treeBoxObject.rowHeight); + if (Math.abs(lines) > 0) { + this.treeBoxObject.scrollByLines(lines); + deltaY -= lines * this.treeBoxObject.rowHeight; + this._touchY = event.touches[0].screenY + deltaY; + } + event.preventDefault(); + } + ]]> + </handler> + <handler event="touchend"> + <![CDATA[ + this._touchY = -1; + ]]> + </handler> + <handler event="MozMousePixelScroll" preventdefault="true"/> + <handler event="DOMMouseScroll" preventdefault="true"> + <![CDATA[ + if (this._editingColumn) + return; + if (event.axis == event.HORIZONTAL_AXIS) + return; + + var rows = event.detail; + if (rows == UIEvent.SCROLL_PAGE_UP) + this.treeBoxObject.scrollByPages(-1); + else if (rows == UIEvent.SCROLL_PAGE_DOWN) + this.treeBoxObject.scrollByPages(1); + else + this.treeBoxObject.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.treeBoxObject.ensureRowIsVisible(targetRow); + break; + } + ]]> + </handler> + <handler event="select" phase="target" + action="if (event.originalTarget == this) this.stopEditing(true);"/> + <handler event="focus"> + <![CDATA[ + this.treeBoxObject.focused = true; + if (this.currentIndex == -1 && this.view.rowCount > 0) { + this.currentIndex = this.treeBoxObject.getFirstVisibleRow(); + } + if (this._cellSelType && !this.view.selection.currentColumn) { + var col = this._getNextColumn(this.currentIndex, false); + this.view.selection.currentColumn = col; + } + ]]> + </handler> + <handler event="blur" action="this.treeBoxObject.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> +#ifndef XP_MACOSX + <!-- Use F2 key to enter text editing. --> + <handler event="keydown" keycode="VK_F2"> + <![CDATA[ + if (!this._cellSelType) + return; + var row = this.currentIndex; + var column = this.view.selection.currentColumn; + if (this.startEditing(row, column)) + event.preventDefault(); + ]]> + </handler> +#endif // XP_MACOSX + + <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; + + var cellSelType = this._cellSelType; + var checkContainers = true; + + var currentColumn; + if (cellSelType) { + currentColumn = this.view.selection.currentColumn; + if (currentColumn && !currentColumn.primary) + checkContainers = false; + } + + if (checkContainers) { + if (this.changeOpenState(this.currentIndex, false)) { + event.preventDefault(); + return; + } + var parentIndex = this.view.getParentIndex(this.currentIndex); + if (parentIndex >= 0) { + if (cellSelType && !this.view.isSelectable(parentIndex, currentColumn)) { + return; + } + this.view.selection.select(parentIndex); + this.treeBoxObject.ensureRowIsVisible(parentIndex); + event.preventDefault(); + return; + } + } + + if (cellSelType) { + var col = this._getNextColumn(row, true); + if (col) { + this.view.selection.currentColumn = col; + this.treeBoxObject.ensureCellIsVisible(row, col); + event.preventDefault(); + } + } + ]]> + </handler> + <handler event="keydown" keycode="VK_RIGHT"> + <![CDATA[ + if (this._editingColumn) + return; + + var row = this.currentIndex; + if (row < 0) + return; + + var cellSelType = this._cellSelType; + var checkContainers = true; + + var currentColumn; + if (cellSelType) { + currentColumn = this.view.selection.currentColumn; + if (currentColumn && !currentColumn.primary) + checkContainers = false; + } + + if (checkContainers) { + 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. + if (cellSelType && !this.view.isSelectable(c, currentColumn)) { + let col = this._getNextColumn(c, false); + if (col) { + this.view.selection.currentColumn = col; + } + } + this.view.selection.timedSelect(c, this._selectDelay); + this.treeBoxObject.ensureRowIsVisible(c); + event.preventDefault(); + return; + } + } + + if (cellSelType) { + let col = this._getNextColumn(row, false); + if (col) { + this.view.selection.currentColumn = col; + this.treeBoxObject.ensureCellIsVisible(row, col); + event.preventDefault(); + } + } + ]]> + </handler> + <handler event="keydown" keycode="VK_UP" modifiers="accel any"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByOffset(-1, 0, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_DOWN" modifiers="accel any"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByOffset(1, this.view.rowCount - 1, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_UP" modifiers="accel any, shift"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByOffsetShift(-1, 0, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_DOWN" modifiers="accel any, shift"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByOffsetShift(1, this.view.rowCount - 1, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByPage(-1, 0, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByPage(1, this.view.rowCount - 1, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any, shift"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByPageShift(-1, 0, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any, shift"> + <![CDATA[ + if (this._editingColumn) + return; + _moveByPageShift(1, this.view.rowCount - 1, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_HOME" modifiers="accel any"> + <![CDATA[ + if (this._editingColumn) + return; + _moveToEdge(0, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_END" modifiers="accel any"> + <![CDATA[ + if (this._editingColumn) + return; + _moveToEdge(this.view.rowCount - 1, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_HOME" modifiers="accel any, shift"> + <![CDATA[ + if (this._editingColumn) + return; + _moveToEdgeShift(0, event); + ]]> + </handler> + <handler event="keydown" keycode="VK_END" modifiers="accel any, shift"> + <![CDATA[ + if (this._editingColumn) + return; + _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 && this._isAccelPressed(event))) { + this.view.selection.toggleSelect(c); + event.preventDefault(); + } + } + else if (!this.disableKeyNavigation && event.charCode > 0 && + !event.altKey && !this._isAccelPressed(event) && + !event.metaKey && !event.ctrlKey) { + var l = this._keyNavigate(event); + if (l >= 0) { + this.view.selection.timedSelect(l, this._selectDelay); + this.treeBoxObject.ensureRowIsVisible(l); + } + event.preventDefault(); + } + ]]> + </handler> + </handlers> + </binding> + + <binding id="treecols" role="xul:treecolumns"> + <resources> + <stylesheet src="chrome://global/skin/tree.css"/> + </resources> + <content orient="horizontal"> + <xul:hbox class="tree-scrollable-columns" flex="1"> + <children includes="treecol|splitter"/> + </xul:hbox> + <xul:treecolpicker class="treecol-image" fixed="true" xbl:inherits="tooltiptext=pickertooltiptext"/> + </content> + <implementation> + <constructor><![CDATA[ + // Set resizeafter="farthest" on the splitters if nothing else has been + // specified. + Array.forEach(this.getElementsByTagName("splitter"), function (splitter) { + if (!splitter.hasAttribute("resizeafter")) + splitter.setAttribute("resizeafter", "farthest"); + }); + ]]></constructor> + </implementation> + </binding> + + <binding id="treerows" extends="chrome://global/content/bindings/tree.xml#tree-base"> + <content> + <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;"/> + </content> + <handlers> + <handler event="underflow"> + <![CDATA[ + // Scrollport event orientation + // 0: vertical + // 1: horizontal + // 2: both (not used) + var tree = document.getBindingParent(this); + if (event.detail == 1) + tree.setAttribute("hidehscroll", "true"); + else if (event.detail == 0) + tree.setAttribute("hidevscroll", "true"); + event.stopPropagation(); + ]]> + </handler> + <handler event="overflow"> + <![CDATA[ + var tree = document.getBindingParent(this); + if (event.detail == 1) + tree.removeAttribute("hidehscroll"); + else if (event.detail == 0) + tree.removeAttribute("hidevscroll"); + event.stopPropagation(); + ]]> + </handler> + </handlers> + </binding> + + <binding id="treebody" extends="chrome://global/content/bindings/tree.xml#tree-base"> + <implementation> + <constructor> + if ("_ensureColumnOrder" in this.parentNode) + this.parentNode._ensureColumnOrder(); + </constructor> + + <field name="_lastSelectedRow"> + -1 + </field> + </implementation> + <handlers> + <!-- If there is no modifier key, we select on mousedown, not + click, so that drags work correctly. --> + <handler event="mousedown" clickcount="1"> + <![CDATA[ + if (this.parentNode.disabled) + return; + if (((!this._isAccelPressed(event) || + !this.parentNode.pageUpOrDownMovesSelection) && + !event.shiftKey && !event.metaKey) || + this.parentNode.view.selection.single) { + var b = this.parentNode.treeBoxObject; + var cell = b.getCellAt(event.clientX, event.clientY); + var view = this.parentNode.view; + + // save off the last selected row + this._lastSelectedRow = cell.row; + + if (cell.row == -1) + return; + + if (cell.childElt == "twisty") + return; + + if (cell.col && event.button == 0) { + if (cell.col.cycler) { + view.cycleCell(cell.row, cell.col); + return; + } else if (cell.col.type == Components.interfaces.nsITreeColumn.TYPE_CHECKBOX) { + if (this.parentNode.editable && cell.col.editable && + view.isEditable(cell.row, cell.col)) { + var value = view.getCellValue(cell.row, cell.col); + value = value == "true" ? "false" : "true"; + view.setCellValue(cell.row, cell.col, value); + return; + } + } + } + + var cellSelType = this.parentNode._cellSelType; + if (cellSelType == "text" && cell.childElt != "text" && cell.childElt != "image") + return; + + if (cellSelType) { + if (!cell.col.selectable || + !view.isSelectable(cell.row, cell.col)) { + return; + } + } + + if (!view.selection.isSelected(cell.row)) { + view.selection.select(cell.row); + b.ensureRowIsVisible(cell.row); + } + + if (cellSelType) { + view.selection.currentColumn = cell.col; + } + } + ]]> + </handler> + + <!-- On a click (up+down on the same item), deselect everything + except this item. --> + <handler event="click" button="0" clickcount="1"> + <![CDATA[ + if (this.parentNode.disabled) + return; + var b = this.parentNode.treeBoxObject; + var cell = b.getCellAt(event.clientX, event.clientY); + var view = this.parentNode.view; + + if (cell.row == -1) + return; + + if (cell.childElt == "twisty") { + if (view.selection.currentIndex >= 0 && + view.isContainerOpen(cell.row)) { + var parentIndex = view.getParentIndex(view.selection.currentIndex); + while (parentIndex >= 0 && parentIndex != cell.row) + parentIndex = view.getParentIndex(parentIndex); + if (parentIndex == cell.row) { + var parentSelectable = true; + if (this.parentNode._cellSelType) { + var currentColumn = view.selection.currentColumn; + if (!view.isSelectable(parentIndex, currentColumn)) + parentSelectable = false; + } + if (parentSelectable) + view.selection.select(parentIndex); + } + } + this.parentNode.changeOpenState(cell.row); + return; + } + + if (! view.selection.single) { + var augment = this._isAccelPressed(event); + if (event.shiftKey) { + view.selection.rangedSelect(-1, cell.row, augment); + b.ensureRowIsVisible(cell.row); + return; + } + if (augment) { + view.selection.toggleSelect(cell.row); + b.ensureRowIsVisible(cell.row); + view.selection.currentIndex = cell.row; + return; + } + } + + /* We want to deselect all the selected items except what was + clicked, UNLESS it was a right-click. We have to do this + in click rather than mousedown so that you can drag a + selected group of items */ + + if (!cell.col) return; + + // if the last row has changed in between the time we + // mousedown and the time we click, don't fire the select handler. + // see bug #92366 + if (!cell.col.cycler && this._lastSelectedRow == cell.row && + cell.col.type != Components.interfaces.nsITreeColumn.TYPE_CHECKBOX) { + + var cellSelType = this.parentNode._cellSelType; + if (cellSelType == "text" && cell.childElt != "text" && cell.childElt != "image") + return; + + if (cellSelType) { + if (!cell.col.selectable || + !view.isSelectable(cell.row, cell.col)) { + return; + } + } + + view.selection.select(cell.row); + b.ensureRowIsVisible(cell.row); + + if (cellSelType) { + view.selection.currentColumn = cell.col; + } + } + ]]> + </handler> + + <!-- double-click --> + <handler event="click" clickcount="2"> + <![CDATA[ + if (this.parentNode.disabled) + return; + var tbo = this.parentNode.treeBoxObject; + var view = this.parentNode.view; + var row = view.selection.currentIndex; + + if (row == -1) + return; + + var cell = tbo.getCellAt(event.clientX, event.clientY); + + if (cell.childElt != "twisty") { + view.selection.currentColumn = cell.col; + this.parentNode.startEditing(row, cell.col); + } + + if (this.parentNode._editingColumn || !view.isContainer(row)) + return; + + // Cyclers and twisties respond to single clicks, not double clicks + if (cell.col && !cell.col.cycler && cell.childElt != "twisty") + this.parentNode.changeOpenState(row); + ]]> + </handler> + + </handlers> + </binding> + + <binding id="treecol-base" role="xul:treecolumnitem" + extends="chrome://global/content/bindings/tree.xml#tree-base"> + <implementation> + <constructor> + this.parentNode.parentNode._columnsDirty = true; + </constructor> + + <property name="ordinal"> + <getter><![CDATA[ + var val = this.getAttribute("ordinal"); + if (val == "") + return "1"; + + return "" + (val == "0" ? 0 : parseInt(val)); + ]]></getter> + <setter><![CDATA[ + this.setAttribute("ordinal", val); + return val; + ]]></setter> + </property> + + <property name="_previousVisibleColumn"> + <getter><![CDATA[ + var sib = this.boxObject.previousSibling; + while (sib) { + if (sib.localName == "treecol" && sib.boxObject.width > 0 && sib.parentNode == this.parentNode) + return sib; + sib = sib.boxObject.previousSibling; + } + return null; + ]]></getter> + </property> + + <method name="_onDragMouseMove"> + <parameter name="aEvent"/> + <body><![CDATA[ + var col = document.treecolDragging; + if (!col) return; + + // determine if we have moved the mouse far enough + // to initiate a drag + if (col.mDragGesturing) { + if (Math.abs(aEvent.clientX - col.mStartDragX) < 5 && + Math.abs(aEvent.clientY - col.mStartDragY) < 5) { + return; + } + col.mDragGesturing = false; + col.setAttribute("dragging", "true"); + window.addEventListener("click", col._onDragMouseClick, true); + } + + var pos = {}; + var targetCol = col.parentNode.parentNode._getColumnAtX(aEvent.clientX, 0.5, pos); + + // bail if we haven't mousemoved to a different column + if (col.mTargetCol == targetCol && col.mTargetDir == pos.value) + return; + + var tree = col.parentNode.parentNode; + var sib; + var column; + if (col.mTargetCol) { + // remove previous insertbefore/after attributes + col.mTargetCol.removeAttribute("insertbefore"); + col.mTargetCol.removeAttribute("insertafter"); + column = tree.columns.getColumnFor(col.mTargetCol); + tree.treeBoxObject.invalidateColumn(column); + sib = col.mTargetCol._previousVisibleColumn; + if (sib) { + sib.removeAttribute("insertafter"); + column = tree.columns.getColumnFor(sib); + tree.treeBoxObject.invalidateColumn(column); + } + col.mTargetCol = null; + col.mTargetDir = null; + } + + if (targetCol) { + // set insertbefore/after attributes + if (pos.value == "after") { + targetCol.setAttribute("insertafter", "true"); + } else { + targetCol.setAttribute("insertbefore", "true"); + sib = targetCol._previousVisibleColumn; + if (sib) { + sib.setAttribute("insertafter", "true"); + column = tree.columns.getColumnFor(sib); + tree.treeBoxObject.invalidateColumn(column); + } + } + column = tree.columns.getColumnFor(targetCol); + tree.treeBoxObject.invalidateColumn(column); + col.mTargetCol = targetCol; + col.mTargetDir = pos.value; + } + ]]></body> + </method> + + <method name="_onDragMouseUp"> + <parameter name="aEvent"/> + <body><![CDATA[ + var col = document.treecolDragging; + if (!col) return; + + if (!col.mDragGesturing) { + if (col.mTargetCol) { + // remove insertbefore/after attributes + var before = col.mTargetCol.hasAttribute("insertbefore"); + col.mTargetCol.removeAttribute(before ? "insertbefore" : "insertafter"); + + var sib = col.mTargetCol._previousVisibleColumn; + if (before && sib) { + sib.removeAttribute("insertafter"); + } + + // Move the column only if it will result in a different column + // ordering + var move = true; + + // If this is a before move and the previous visible column is + // the same as the column we're moving, don't move + if (before && col == sib) { + move = false; + } + else if (!before && col == col.mTargetCol) { + // If this is an after move and the column we're moving is + // the same as the target column, don't move. + move = false; + } + + if (move) { + col.parentNode.parentNode._reorderColumn(col, col.mTargetCol, before); + } + + // repaint to remove lines + col.parentNode.parentNode.treeBoxObject.invalidate(); + + col.mTargetCol = null; + } + } else + col.mDragGesturing = false; + + document.treecolDragging = null; + col.removeAttribute("dragging"); + + window.removeEventListener("mousemove", col._onDragMouseMove, true); + window.removeEventListener("mouseup", col._onDragMouseUp, true); + // we have to wait for the click event to fire before removing + // cancelling handler + var clickHandler = function(handler) { + window.removeEventListener("click", handler, true); + }; + window.setTimeout(clickHandler, 0, col._onDragMouseClick); + ]]></body> + </method> + + <method name="_onDragMouseClick"> + <parameter name="aEvent"/> + <body><![CDATA[ + // prevent click event from firing after column drag and drop + aEvent.stopPropagation(); + aEvent.preventDefault(); + ]]></body> + </method> + </implementation> + + <handlers> + <handler event="mousedown" button="0"><![CDATA[ + if (this.parentNode.parentNode.enableColumnDrag) { + var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var cols = this.parentNode.getElementsByTagNameNS(xulns, "treecol"); + + // only start column drag operation if there are at least 2 visible columns + var visible = 0; + for (var i = 0; i < cols.length; ++i) + if (cols[i].boxObject.width > 0) ++visible; + + if (visible > 1) { + window.addEventListener("mousemove", this._onDragMouseMove, true); + window.addEventListener("mouseup", this._onDragMouseUp, true); + document.treecolDragging = this; + this.mDragGesturing = true; + this.mStartDragX = event.clientX; + this.mStartDragY = event.clientY; + } + } + ]]></handler> + <handler event="click" button="0" phase="target"> + <![CDATA[ + if (event.target != event.originalTarget) + return; + + // On Windows multiple clicking on tree columns only cycles one time + // every 2 clicks. + if (/Win/.test(navigator.platform) && event.detail % 2 == 0) + return; + + var tree = this.parentNode.parentNode; + if (tree.columns) { + tree.view.cycleHeader(tree.columns.getColumnFor(this)); + } + ]]> + </handler> + </handlers> + </binding> + + <binding id="treecol" extends="chrome://global/content/bindings/tree.xml#treecol-base"> + <content> + <xul:label class="treecol-text" xbl:inherits="crop,value=label" flex="1" crop="right"/> + <xul:image class="treecol-sortdirection" xbl:inherits="sortDirection,hidden=hideheader"/> + </content> + </binding> + + <binding id="treecol-image" extends="chrome://global/content/bindings/tree.xml#treecol-base"> + <content> + <xul:image class="treecol-icon" xbl:inherits="src"/> + </content> + </binding> + + <binding id="columnpicker" display="xul:button" role="xul:button" + extends="chrome://global/content/bindings/tree.xml#tree-base"> + <content> + <xul:image class="tree-columnpicker-icon"/> + <xul:menupopup anonid="popup"> + <xul:menuseparator anonid="menuseparator"/> + <xul:menuitem anonid="menuitem" label="&restoreColumnOrder.label;"/> + </xul:menupopup> + </content> + + <implementation> + <method name="buildPopup"> + <parameter name="aPopup"/> + <body> + <![CDATA[ + // We no longer cache the picker content, remove the old content. + while (aPopup.childNodes.length > 2) + aPopup.removeChild(aPopup.firstChild); + + var refChild = aPopup.firstChild; + + var tree = this.parentNode.parentNode; + for (var currCol = tree.columns.getFirstColumn(); currCol; + currCol = currCol.getNext()) { + // Construct an entry for each column in the row, unless + // it is not being shown. + var currElement = currCol.element; + if (!currElement.hasAttribute("ignoreincolumnpicker")) { + var popupChild = document.createElement("menuitem"); + popupChild.setAttribute("type", "checkbox"); + var columnName = currElement.getAttribute("display") || + currElement.getAttribute("label"); + popupChild.setAttribute("label", columnName); + popupChild.setAttribute("colindex", currCol.index); + if (currElement.getAttribute("hidden") != "true") + popupChild.setAttribute("checked", "true"); + if (currCol.primary) + popupChild.setAttribute("disabled", "true"); + aPopup.insertBefore(popupChild, refChild); + } + } + + var hidden = !tree.enableColumnDrag; + const anonids = ["menuseparator", "menuitem"]; + for (var i = 0; i < anonids.length; i++) { + var element = document.getAnonymousElementByAttribute(this, "anonid", anonids[i]); + element.hidden = hidden; + } + ]]> + </body> + </method> + </implementation> + + <handlers> + <handler event="command"> + <![CDATA[ + if (event.originalTarget == this) { + var popup = document.getAnonymousElementByAttribute(this, "anonid", "popup"); + this.buildPopup(popup); + popup.showPopup(this, -1, -1, "popup", "bottomright", "topright"); + } + else { + var tree = this.parentNode.parentNode; + tree.stopEditing(true); + var menuitem = document.getAnonymousElementByAttribute(this, "anonid", "menuitem"); + if (event.originalTarget == menuitem) { + tree.columns.restoreNaturalOrder(); + tree._ensureColumnOrder(); + } + else { + var colindex = event.originalTarget.getAttribute("colindex"); + var column = tree.columns[colindex]; + if (column) { + var element = column.element; + if (element.getAttribute("hidden") == "true") + element.setAttribute("hidden", "false"); + else + element.setAttribute("hidden", "true"); + } + } + } + ]]> + </handler> + </handlers> + </binding> +</bindings> |