зеркало из https://github.com/mozilla/pjs.git
Bug 298371, make richlistbox multi-selectable p=surkov, r=mano+enndeakin
This commit is contained in:
Родитель
4b3f356119
Коммит
107a292195
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -22,6 +22,7 @@
|
|||
-
|
||||
- Contributor(s):
|
||||
- Doron Rosenberg <doronr@us.ibm.com> (Original Author)
|
||||
- Simon Bünzli <zeniko@gmail.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
|
||||
|
@ -42,7 +43,12 @@
|
|||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="richlistbox">
|
||||
<binding id="richlistbox"
|
||||
extends="chrome://global/content/bindings/listbox.xml#listbox-base">
|
||||
<resources>
|
||||
<stylesheet src="chrome://global/skin/richlistbox.css"/>
|
||||
</resources>
|
||||
|
||||
<content>
|
||||
<xul:scrollbox allowevents="true" orient="vertical" anonid="main-box"
|
||||
flex="1" style="overflow: auto;">
|
||||
|
@ -50,11 +56,7 @@
|
|||
</xul:scrollbox>
|
||||
</content>
|
||||
|
||||
<resources>
|
||||
<stylesheet src="chrome://global/skin/richlistbox.css"/>
|
||||
</resources>
|
||||
|
||||
<implementation implements="nsIAccessibleProvider, nsIDOMXULSelectControlElement">
|
||||
<implementation>
|
||||
<field name="scrollBoxObject">null</field>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
|
@ -74,187 +76,155 @@
|
|||
// remove the template build listener
|
||||
if (this.builder)
|
||||
this.builder.removeListener(this._builderListener);
|
||||
|
||||
this._selectedItem = null;
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<property name="accessibleType" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return Components.interfaces.nsIAccessibleProvider.XULListbox;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<property name="children">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
var childNodes = [];
|
||||
for (var i = 0; i < this.childNodes.length; ++i) {
|
||||
if (this.childNodes[i] instanceof Components.interfaces.nsIDOMXULSelectControlItemElement)
|
||||
childNodes.push(this.childNodes[i]);
|
||||
}
|
||||
return childNodes;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<field name="_builderListener">
|
||||
<![CDATA[
|
||||
({
|
||||
mOuter: this,
|
||||
item: null,
|
||||
willRebuild : function(builder) {},
|
||||
didRebuild : function(builder) {
|
||||
this.mOuter._refreshSelection();
|
||||
}
|
||||
});
|
||||
]]>
|
||||
</field>
|
||||
|
||||
<method name="_refreshSelection">
|
||||
<!-- Overriding baselistbox -->
|
||||
<method name="_fireOnSelect">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// when this method is called, we know that either the selectedItem
|
||||
// we have is null (ctor) or a reference to an element no longer in
|
||||
// the DOM (template).
|
||||
|
||||
// fist look for a last-selected attribute
|
||||
var lastSelected = this.getAttribute("last-selected");
|
||||
if (lastSelected != "") {
|
||||
var element = document.getElementById(lastSelected);
|
||||
|
||||
if (element) {
|
||||
this.selectedItem = element;
|
||||
if (!this._isItemVisible(this.selectedItem))
|
||||
this.scrollBoxObject.scrollToElement(this.selectedItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// cache the selected index
|
||||
var selectedIndex = this._selectedIndex;
|
||||
|
||||
// refreshes selection. Called for example when a template rebuild
|
||||
// happens.
|
||||
if (this.selectedItem) {
|
||||
if (this.selectedItem.hasAttribute("id")) {
|
||||
var id = this.selectedItem.getAttribute("id");
|
||||
var item = document.getElementById(id);
|
||||
|
||||
// if we find no item, clear selection so that the code at the bottom
|
||||
// takes over
|
||||
if (item) {
|
||||
this.selectedItem = item;
|
||||
} else {
|
||||
this.clearSelection();
|
||||
}
|
||||
} else {
|
||||
// if no id, we clear selection so that the below code will select
|
||||
// based on the current index
|
||||
this.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
// if we have no previously selected item or the above if check fails to
|
||||
// find the previous node (which causes it to clear selection)
|
||||
if (!this.selectedItem) {
|
||||
// if the selectedIndex is larger than the row count, select the last
|
||||
// item.
|
||||
if (selectedIndex >= this.getRowCount())
|
||||
this.selectedIndex = this.getRowCount() - 1;
|
||||
else
|
||||
this.selectedIndex = selectedIndex;
|
||||
|
||||
// XXX: downloadmanager needs the following line, else we scroll to
|
||||
// the middle on inital load.
|
||||
this.ensureSelectedElementIsVisible();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="fireActiveItemEvent">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.selectedItem) {
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("DOMMenuItemActive", true, true);
|
||||
this.selectedItem.dispatchEvent(event);
|
||||
}
|
||||
return false;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<field name="_selectedIndex">0</field>
|
||||
<property name="selectedIndex">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.getIndexOf(this.selectedItem);
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (val == -1) {
|
||||
// clear selection
|
||||
this.clearSelection();
|
||||
} else if (val >= 0) {
|
||||
// only set if we get an item returned
|
||||
var item = this.getItemAtIndex(val);
|
||||
if (item)
|
||||
this.selectedItem = item;
|
||||
}
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<field name="_selectedItem">null</field>
|
||||
<property name="selectedItem">
|
||||
<getter>
|
||||
return this._selectedItem;
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (this._selectedItem == val)
|
||||
// make sure not to modify last-selected when suppressing select events
|
||||
// (otherwise we'll lose the selection when a template gets rebuilt)
|
||||
if (this._suppressOnSelect || this.suppressOnSelect)
|
||||
return;
|
||||
|
||||
this._setItemSelection(val);
|
||||
// remember the current item and all selected items with IDs
|
||||
var state = this.currentItem ? this.currentItem.id : "";
|
||||
if (this.selType == "multiple" && this.selectedCount) {
|
||||
function getId(aItem) { return aItem.id; }
|
||||
state += " " + this.selectedItems.filter(getId).map(getId).join(" ");
|
||||
}
|
||||
if (state)
|
||||
this.setAttribute("last-selected", state);
|
||||
else
|
||||
this.removeAttribute("last-selected");
|
||||
|
||||
if (val)
|
||||
this.fireActiveItemEvent();
|
||||
// preserve the index just in case no IDs are available
|
||||
if (this.currentIndex > -1)
|
||||
this._currentIndex = this.currentIndex + 1;
|
||||
|
||||
this._fireOnSelect();
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
|
||||
// always call this (allows a commandupdater without controller)
|
||||
document.commandDispatcher.updateCommands("richlistbox-select");
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- sets selection but doesn't cause any events -->
|
||||
<method name="_setItemSelection">
|
||||
<method name="appendItem">
|
||||
<parameter name="aLabel"/>
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
return this.insertItemAt(-1, aLabel, aValue);
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="insertItemAt">
|
||||
<parameter name="aIndex"/>
|
||||
<parameter name="aLabel"/>
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
const XULNS =
|
||||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
var item =
|
||||
this.ownerDocument.createElementNS(XULNS, "richlistitem");
|
||||
item.setAttribute("value", aValue);
|
||||
|
||||
var label = this.ownerDocument.createElementNS(XULNS, "label");
|
||||
label.setAttribute("value", aLabel);
|
||||
label.setAttribute("flex", "1");
|
||||
label.setAttribute("crop", "end");
|
||||
item.appendChild(label);
|
||||
|
||||
var before = this.getItemAtIndex(aIndex);
|
||||
if (!before)
|
||||
this.appendChild(item);
|
||||
else
|
||||
this.insertBefore(item, before);
|
||||
|
||||
return item;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="getIndexOfItem">
|
||||
<parameter name="aItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// unselect current item
|
||||
if (this._selectedItem)
|
||||
this._selectedItem.selected = false
|
||||
// don't search the children, if we're looking for none of them
|
||||
if (aItem == null)
|
||||
return -1;
|
||||
|
||||
this._selectedItem = aItem;
|
||||
this._selectedIndex = this.getIndexOf(aItem);
|
||||
this.ensureSelectedElementIsVisible();
|
||||
|
||||
if (aItem) {
|
||||
aItem.selected = true;
|
||||
aItem.focus();
|
||||
}
|
||||
return this.children.indexOf(aItem);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearSelection">
|
||||
<method name="getItemAtIndex">
|
||||
<parameter name="aIndex"/>
|
||||
<body>
|
||||
return this.children[aIndex] || null;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="ensureIndexIsVisible">
|
||||
<parameter name="aIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.selectedItem = null;
|
||||
// work around missing implementation in scrollBoxObject
|
||||
return this.ensureElementIsVisible(this.getItemAtIndex(aIndex));
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="ensureElementIsVisible">
|
||||
<parameter name="aElement"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (aElement)
|
||||
this.scrollBoxObject.ensureElementIsVisible(aElement);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="scrollToIndex">
|
||||
<parameter name="aIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var item = this.getItemAtIndex(aIndex);
|
||||
if (item)
|
||||
this.scrollBoxObject.scrollToElement(item);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="getNumberOfVisibleRows">
|
||||
<!-- returns the number of currently visible rows -->
|
||||
<!-- don't rely on this function, if the items' height can vary! -->
|
||||
<body>
|
||||
<![CDATA[
|
||||
var children = this.children;
|
||||
|
||||
for (var top = 0; top < children.length && !this._isItemVisible(children[top]); top++);
|
||||
for (var ix = top; ix < children.length && this._isItemVisible(children[ix]); ix++);
|
||||
|
||||
return ix - top;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="getIndexOfFirstVisibleRow">
|
||||
<body>
|
||||
<![CDATA[
|
||||
var children = this.children;
|
||||
|
||||
for (var ix = 0; ix < children.length; ix++)
|
||||
if (this._isItemVisible(children[ix]))
|
||||
return ix;
|
||||
|
||||
return -1;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -267,34 +237,154 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<method name="goUp">
|
||||
<method name="scrollOnePage">
|
||||
<parameter name="aDirection"/> <!-- Must be -1 or 1 -->
|
||||
<body>
|
||||
<![CDATA[
|
||||
// if nothing selected, we go from the bottom
|
||||
for (var i = this.selectedItem ? this.selectedItem.previousSibling : this.lastChild; i; i = i.previousSibling) {
|
||||
// could have a template element, which would be a sibling
|
||||
if (i instanceof Components.interfaces.nsIDOMXULSelectControlItemElement) {
|
||||
this.selectedItem = i;
|
||||
return true;
|
||||
}
|
||||
var children = this.children;
|
||||
|
||||
if (children.length == 0)
|
||||
return 0;
|
||||
|
||||
// If nothing is selected, we just select the first element
|
||||
// at the extreme we're moving away from
|
||||
if (!this.currentItem)
|
||||
return aDirection == -1 ? children.length : 0;
|
||||
|
||||
// If the current item is visible, scroll by one page so that
|
||||
// the new current item is at approximately the same position as
|
||||
// the existing current item.
|
||||
if (this._isItemVisible(this.currentItem))
|
||||
this.scrollBoxObject.scrollBy(0, this.scrollBoxObject.height * aDirection);
|
||||
|
||||
// Figure out, how many items fully fit into the view port
|
||||
// (including the currently selected one), and determine
|
||||
// the index of the first one lying (partially) outside
|
||||
var height = this.scrollBoxObject.height;
|
||||
var border = this.currentItem.boxObject.y;
|
||||
if (aDirection == -1)
|
||||
border += this.currentItem.boxObject.height;
|
||||
var index = this.currentIndex;
|
||||
while (0 <= index && index < children.length) {
|
||||
var border2 = children[index].boxObject.y;
|
||||
if (aDirection == -1)
|
||||
border2 += children[index].boxObject.height;
|
||||
if ((border2 - border) * aDirection > height)
|
||||
break;
|
||||
index += aDirection;
|
||||
}
|
||||
return false;
|
||||
index -= aDirection;
|
||||
|
||||
return index != this.currentIndex ? index - this.currentIndex : aDirection;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="goDown">
|
||||
<!-- richlistbox specific -->
|
||||
<property name="children" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
var childNodes = [];
|
||||
for (var child = this.firstChild; child; child = child.nextSibling) {
|
||||
if (child instanceof Components.interfaces.nsIDOMXULSelectControlItemElement)
|
||||
childNodes.push(child);
|
||||
}
|
||||
return childNodes;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<field name="_builderListener" readonly="true">
|
||||
<![CDATA[
|
||||
({
|
||||
mOuter: this,
|
||||
item: null,
|
||||
willRebuild: function(builder) { },
|
||||
didRebuild: function(builder) {
|
||||
this.mOuter._refreshSelection();
|
||||
}
|
||||
});
|
||||
]]>
|
||||
</field>
|
||||
|
||||
<method name="_refreshSelection">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// if nothing selected, we go from the top
|
||||
for (var i = this.selectedItem ? this.selectedItem.nextSibling : this.firstChild; i; i = i.nextSibling) {
|
||||
// could have a template element, which would be a sibling
|
||||
if (i instanceof Components.interfaces.nsIDOMXULSelectControlItemElement) {
|
||||
this.selectedItem = i;
|
||||
return true;
|
||||
// when this method is called, we know that either the currentItem
|
||||
// and selectedItems we have are null (ctor) or a reference to an
|
||||
// element no longer in the DOM (template).
|
||||
|
||||
// first look for the last-selected attribute
|
||||
var state = this.getAttribute("last-selected");
|
||||
if (state) {
|
||||
var ids = state.split(" ");
|
||||
|
||||
var suppressSelect = this._suppressOnSelect;
|
||||
this._suppressOnSelect = true;
|
||||
this.clearSelection();
|
||||
for (var i = 1; i < ids.length; i++) {
|
||||
var selectedItem = document.getElementById(ids[i]);
|
||||
if (selectedItem)
|
||||
this.addItemToSelection(selectedItem);
|
||||
}
|
||||
|
||||
var currentItem = document.getElementById(ids[0]);
|
||||
if (!currentItem && this._currentIndex)
|
||||
currentItem = this.getItemAtIndex(Math.min(
|
||||
this._currentIndex - 1, this.getRowCount()));
|
||||
if (currentItem) {
|
||||
this.currentItem = currentItem;
|
||||
if (this.selType != "multiple" && this.selectedCount == 0)
|
||||
this.selectedItem = currentItem;
|
||||
|
||||
if (this.scrollBoxObject.height)
|
||||
this.ensureElementIsVisible(currentItem);
|
||||
else // XXX hack around a bug in ensureElementIsVisible
|
||||
this.ensureElementIsVisible(currentItem.previousSibling);
|
||||
}
|
||||
this._suppressOnSelect = suppressSelect;
|
||||
// XXX actually it's just a refresh, but at least
|
||||
// the Extensions manager expects this:
|
||||
this._fireOnSelect();
|
||||
return;
|
||||
}
|
||||
|
||||
// try to restore the selected items according to their IDs
|
||||
// (applies after a template rebuild, if last-selected was not set)
|
||||
if (this.selectedItems) {
|
||||
for (i = this.selectedCount - 1; i >= 0; i--) {
|
||||
if (this.selectedItems[i] && this.selectedItems[i].id)
|
||||
this.selectedItems[i] = document.getElementById(this.selectedItems[i].id);
|
||||
else
|
||||
this.selectedItems[i] = null;
|
||||
if (!this.selectedItems[i])
|
||||
this.selectedItems.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (this.currentItem && this.currentItem.id)
|
||||
this.currentItem = document.getElementById(this.currentItem.id);
|
||||
else
|
||||
this.currentItem = null;
|
||||
|
||||
// if we have no previously current item or if the above check fails to
|
||||
// find the previous nodes (which causes it to clear selection)
|
||||
if (!this.currentItem && this.selectedCount == 0) {
|
||||
this.currentIndex = this._currentIndex ? this._currentIndex - 1 : 0;
|
||||
|
||||
// cf. listbox constructor:
|
||||
// select items according to their attributes
|
||||
var els = this.getElementsByAttribute("selected", "true");
|
||||
for (i = 0; i < els.length; i++)
|
||||
this.selectedItems.push(els[i]);
|
||||
}
|
||||
|
||||
if (this.selType != "multiple" && this.selectedCount == 0)
|
||||
this.selectedItem = this.currentItem;
|
||||
|
||||
// XXX hack for the Downloads manager (better to focus the list than
|
||||
// the individual items - these usually aren't tabbable anyway, and
|
||||
// we need the keyboard focus for navigation), see bug 363271:
|
||||
this.focus();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -317,148 +407,88 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<method name="scrollOnePage">
|
||||
<parameter name="aDirection"/> <!-- Must be -1 or 1 -->
|
||||
<body>
|
||||
<![CDATA[
|
||||
var children = this.children;
|
||||
|
||||
if (children.length == 0)
|
||||
return false;
|
||||
|
||||
var index = children.indexOf(this.selectedItem);
|
||||
|
||||
// If nothing is selected, we just select the first element
|
||||
// at the extreme we're moving away from
|
||||
if (index == -1) {
|
||||
index = aDirection == -1 ? children.length - 1 : 0;
|
||||
this.selectedItem = children[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the selected item is visible, we scroll by one page so that
|
||||
// the newly selected item is at approximately the same position as
|
||||
// the currently selected one
|
||||
var currentItem = children[index];
|
||||
if (this._isItemVisible(currentItem))
|
||||
this.scrollBoxObject.scrollBy(0, this.scrollBoxObject.height * aDirection);
|
||||
|
||||
// Figure out, how many items fully fit into the view port
|
||||
// (including the currently selected one), and determine
|
||||
// the index of the first one lying (partially) outside
|
||||
var height = this.scrollBoxObject.height;
|
||||
var border = currentItem.boxObject.y;
|
||||
if (aDirection == -1)
|
||||
border += currentItem.boxObject.height;
|
||||
while (index >= 0 && index < children.length) {
|
||||
var border2 = children[index].boxObject.y;
|
||||
if (aDirection == -1)
|
||||
border2 += children[index].boxObject.height;
|
||||
if ((border2 - border) * aDirection > height)
|
||||
break;
|
||||
index += aDirection;
|
||||
}
|
||||
index -= aDirection;
|
||||
|
||||
if (this.selectedItem != children[index]) {
|
||||
this.selectedItem = children[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move by at least one item if the view port is too small
|
||||
if (aDirection == -1)
|
||||
return this.goUp();
|
||||
|
||||
return this.goDown();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="getItemAtIndex">
|
||||
<parameter name="aIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return this.children[aIndex];
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<field name="_currentIndex">null</field>
|
||||
|
||||
<!-- For backwards-compatibility and for convenience.
|
||||
Use getIndexOfItem instead. -->
|
||||
<method name="getIndexOf">
|
||||
<parameter name="aElement"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// don't search the children, if we're looking for none of them
|
||||
if (aElement == null)
|
||||
return -1;
|
||||
|
||||
return this.children.indexOf(aElement);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="ensureElementIsVisible">
|
||||
<parameter name="aElement"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (aElement)
|
||||
this.scrollBoxObject.ensureElementIsVisible(aElement);
|
||||
return this.getIndexOfItem(aElement);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- For backwards-compatibility and for convenience.
|
||||
Use ensureElementIsVisible instead -->
|
||||
<method name="ensureSelectedElementIsVisible">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.selectedItem) {
|
||||
this.ensureElementIsVisible(this.selectedItem);
|
||||
}
|
||||
return this.ensureElementIsVisible(this.selectedItem);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<property name="suppressOnSelect">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.getAttribute("suppressonselect") == "true";
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="_fireOnSelect">
|
||||
<!-- For backwards-compatibility and for convenience.
|
||||
Use moveByOffset instead. -->
|
||||
<method name="goUp">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.selectedItem)
|
||||
this.setAttribute("last-selected", this.selectedItem.getAttribute("id"));
|
||||
else
|
||||
this.removeAttribute("last-selected");
|
||||
|
||||
if (!this.suppressOnSelect) {
|
||||
var event = document.createEvent("Events");
|
||||
event.initEvent("select", true, true);
|
||||
this.dispatchEvent(event);
|
||||
|
||||
// if we have controllers, notify the command dispatcher
|
||||
if (this.controllers.getControllerCount() > 0)
|
||||
document.commandDispatcher.updateCommands("richlistbox-select");
|
||||
}
|
||||
var index = this.currentIndex;
|
||||
this.moveByOffset(-1, true, false);
|
||||
return index != this.currentIndex;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<method name="goDown">
|
||||
<body>
|
||||
<![CDATA[
|
||||
var index = this.currentIndex;
|
||||
this.moveByOffset(1, true, false);
|
||||
return index != this.currentIndex;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- deprecated (is implied by currentItem and selectItem) -->
|
||||
<method name="fireActiveItemEvent"><body/></method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="keypress" keycode="VK_UP" action="goUp(); event.preventDefault();"/>
|
||||
<handler event="keypress" keycode="VK_DOWN" action="goDown(); event.preventDefault();"/>
|
||||
<handler event="keypress" keycode="VK_PAGE_UP" action="scrollOnePage(-1); event.preventDefault();"/>
|
||||
<handler event="keypress" keycode="VK_PAGE_DOWN" action="scrollOnePage(1); event.preventDefault();"/>
|
||||
<handler event="keypress" keycode="VK_HOME" action="clearSelection(); goDown(); event.preventDefault();"/>
|
||||
<handler event="keypress" keycode="VK_END" action="clearSelection(); goUp(); event.preventDefault();"/>
|
||||
<!-- handle keyboard navigation also when a child element has got the focus -->
|
||||
<handler event="keypress" keycode="VK_UP"
|
||||
modifiers="control shift any"
|
||||
action="this.moveByOffset(-1, !event.ctrlKey, event.shiftKey);"
|
||||
preventdefault="true"/>
|
||||
<handler event="keypress" keycode="VK_DOWN"
|
||||
modifiers="control shift any"
|
||||
action="this.moveByOffset(1, !event.ctrlKey, event.shiftKey);"
|
||||
preventdefault="true"/>
|
||||
<handler event="keypress" keycode="VK_HOME"
|
||||
modifiers="control shift any"
|
||||
action="this.moveByOffset(-this.currentIndex, !event.ctrlKey, event.shiftKey);"
|
||||
preventdefault="true"/>
|
||||
<handler event="keypress" keycode="VK_END"
|
||||
modifiers="control shift any"
|
||||
action="this.moveByOffset(this.getRowCount(), !event.ctrlKey, event.shiftKey);"
|
||||
preventdefault="true"/>
|
||||
<handler event="keypress" keycode="VK_PAGE_UP"
|
||||
modifiers="control shift any"
|
||||
action="this.moveByOffset(this.scrollOnePage(-1), !event.ctrlKey, event.shiftKey);"
|
||||
preventdefault="true"/>
|
||||
<handler event="keypress" keycode="VK_PAGE_DOWN"
|
||||
modifiers="control shift any"
|
||||
action="this.moveByOffset(this.scrollOnePage(1), !event.ctrlKey, event.shiftKey);"
|
||||
preventdefault="true"/>
|
||||
|
||||
<handler event="click">
|
||||
<![CDATA[
|
||||
// clicking into nothing should unselect
|
||||
if (event.originalTarget.getAttribute("anonid") == "main-box")
|
||||
if (event.originalTarget.getAttribute("anonid") == "main-box") {
|
||||
this.clearSelection();
|
||||
this.currentItem = null;
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="contextmenu">
|
||||
|
@ -468,119 +498,70 @@
|
|||
if (event.button != 2) {
|
||||
var popup = document.getElementById(this.getAttribute("context"));
|
||||
if (popup)
|
||||
popup.showPopup(this.selectedItem, -1, -1, "context", "bottomleft", "topleft");
|
||||
popup.showPopup(this.currentItem, -1, -1, "context", "bottomleft", "topleft");
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="focus">
|
||||
<![CDATA[
|
||||
if (event.target == this)
|
||||
this.fireActiveItemEvent();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="richlistitem"
|
||||
extends="chrome://global/content/bindings/general.xml#basecontrol">
|
||||
extends="chrome://global/content/bindings/listbox.xml#listitem">
|
||||
<content>
|
||||
<children />
|
||||
<children/>
|
||||
</content>
|
||||
|
||||
<resources>
|
||||
<stylesheet src="chrome://global/skin/richlistbox.css"/>
|
||||
</resources>
|
||||
|
||||
<implementation implements="nsIAccessibleProvider, nsIDOMXULSelectControlItemElement">
|
||||
<implementation>
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
// When we are destructed and we are selected, unselect ourselves so
|
||||
// that richlistbox's selection doesn't point to something not in the DOM.
|
||||
// We don't want to reset last-selected, so we don't call clearSelection().
|
||||
var control = this.control;
|
||||
if (!control)
|
||||
return;
|
||||
// When we are destructed and we are current or selected, unselect ourselves
|
||||
// so that richlistbox's selection doesn't point to something not in the DOM.
|
||||
// We don't want to reset last-selected, so we set _suppressOnSelect.
|
||||
if (this.selected) {
|
||||
this.control._setItemSelection(null);
|
||||
var suppressSelect = control._suppressOnSelect;
|
||||
control._suppressOnSelect = true;
|
||||
control.removeItemFromSelection(this);
|
||||
control._suppressOnSelect = suppressSelect;
|
||||
}
|
||||
if (this.current)
|
||||
control.currentItem = null;
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<!-- ///////////////// nsIAccessibleProvider ///////////////// -->
|
||||
<property name="accessibleType" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return Components.interfaces.nsIAccessibleProvider.XULListitem;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
<!-- ///////////////// nsIDOMXULSelectControlItemElement ///////////////// -->
|
||||
|
||||
<property name="value" onget="return this.getAttribute('value');"
|
||||
onset="this.setAttribute('value', val); return val;"/>
|
||||
|
||||
<property name="label">
|
||||
<property name="label" readonly="true">
|
||||
<!-- Setter purposely not implemented; the getter returns a
|
||||
concatentation of label text to expose via accessibility APIs-->
|
||||
concatentation of label text to expose via accessibility APIs -->
|
||||
<getter>
|
||||
<![CDATA[
|
||||
const XULNS =
|
||||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
var labelText = "";
|
||||
var startEl = document.getAnonymousNodes(this)[0];
|
||||
if (startEl) {
|
||||
var labels =
|
||||
startEl.getElementsByTagNameNS('http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul',
|
||||
'label');
|
||||
var numLabels = labels.length;
|
||||
for (count = 0; count < numLabels; count ++) {
|
||||
var label = labels[count];
|
||||
if (!label.collapsed && !label.hidden &&
|
||||
label.className != 'text-link') {
|
||||
labelText += label.value + ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
var startEl = document.getAnonymousNodes(this);
|
||||
if (!startEl.length)
|
||||
startEl = [this];
|
||||
var labels = startEl[0].getElementsByTagNameNS(XULNS, "label");
|
||||
for (var count = 0; count < labels.length; count++)
|
||||
labelText += labels[count].value + " ";
|
||||
return labelText;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<property name="selected"
|
||||
onget="return this.getAttribute('selected') == 'true';"
|
||||
onset="return this.setAttribute('selected',val);"/>
|
||||
|
||||
<property name="control">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
var parent = this.parentNode;
|
||||
while (parent) {
|
||||
if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
|
||||
return parent;
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
return null;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="click">
|
||||
<![CDATA[
|
||||
var listbox = this.control;
|
||||
if ((event.target == this) && event.ctrlKey && (listbox.selectedItem == this)) {
|
||||
listbox.clearSelection();
|
||||
} else {
|
||||
listbox.selectedItem = this;
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
// This needed to be called before the contextmenu gets shown to handle
|
||||
// someone rightclicking on an unselected item
|
||||
if (event.target == this) {
|
||||
var listbox = this.control;
|
||||
if (listbox) {
|
||||
listbox.selectedItem = this;
|
||||
}
|
||||
}
|
||||
// handle someone right-clicking on an item other than the current one
|
||||
if (event.target == this && this.control)
|
||||
this.control.currentItem = this;
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Doron Rosenberg <doronr@us.ibm.com> (original author)
|
||||
* Simon Bünzli <zeniko@gmail.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
|
||||
|
@ -38,6 +39,7 @@
|
|||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
richlistbox {
|
||||
margin: 2px 4px;
|
||||
background-color: -moz-Field;
|
||||
color: -moz-FieldText;
|
||||
}
|
||||
|
@ -47,7 +49,21 @@ richlistbox[disabled="true"] {
|
|||
}
|
||||
|
||||
richlistitem[selected="true"] {
|
||||
background-color: -moz-Dialog;
|
||||
color: -moz-DialogText;
|
||||
}
|
||||
|
||||
richlistbox:focus > richlistitem[selected="true"] {
|
||||
background-color: Highlight;
|
||||
color: HighlightText;
|
||||
}
|
||||
|
||||
richlistbox[seltype="multiple"]:focus > richlistitem[current="true"] {
|
||||
outline: 1px dotted Highlight;
|
||||
-moz-outline-offset: -1px;
|
||||
}
|
||||
|
||||
richlistbox[seltype="multiple"]:focus > richlistitem[current="true"][selected="true"] {
|
||||
outline: 1px dotted #F3D982; /* TODO: find a suitable system color */
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Doron Rosenberg <doronr@us.ibm.com> (original author)
|
||||
* Simon Bünzli <zeniko@gmail.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
|
||||
|
@ -38,6 +39,7 @@
|
|||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
richlistbox {
|
||||
margin: 2px 4px;
|
||||
background-color: -moz-Field;
|
||||
color: -moz-FieldText;
|
||||
}
|
||||
|
@ -47,7 +49,21 @@ richlistbox[disabled="true"] {
|
|||
}
|
||||
|
||||
richlistitem[selected="true"] {
|
||||
background-color: -moz-Dialog;
|
||||
color: -moz-DialogText;
|
||||
}
|
||||
|
||||
richlistbox:focus > richlistitem[selected="true"] {
|
||||
background-color: Highlight;
|
||||
color: HighlightText;
|
||||
}
|
||||
|
||||
richlistbox[seltype="multiple"]:focus > richlistitem[current="true"] {
|
||||
outline: 1px dotted Highlight;
|
||||
-moz-outline-offset: -1px;
|
||||
}
|
||||
|
||||
richlistbox[seltype="multiple"]:focus > richlistitem[current="true"][selected="true"] {
|
||||
outline: 1px dotted #F3D982; /* TODO: find a suitable system color */
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче