gecko-dev/toolkit/content/widgets/menulist.xml

626 lines
22 KiB
XML

<?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/. -->
<bindings id="menulistBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="menulist-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/content/menulist.css"/>
<stylesheet src="chrome://global/skin/menulist.css"/>
</resources>
</binding>
<binding id="menulist" display="xul:menu" role="xul:menulist"
extends="chrome://global/content/bindings/menulist.xml#menulist-base">
<content sizetopopup="pref">
<xul:hbox class="menulist-label-box" flex="1">
<xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
<xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
</xul:hbox>
<xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
<children includes="menupopup"/>
</content>
<handlers>
<handler event="command" phase="capturing"
action="if (event.target.parentNode.parentNode == this) this.selectedItem = event.target;"/>
<handler event="popupshowing">
<![CDATA[
if (event.target.parentNode == this) {
this.menuBoxObject.activeChild = null;
if (this.selectedItem)
// Not ready for auto-setting the active child in hierarchies yet.
// For now, only do this when the outermost menupopup opens.
this.menuBoxObject.activeChild = this.mSelectedInternal;
}
]]>
</handler>
<handler event="keypress" modifiers="shift any" group="system">
<![CDATA[
if (!event.defaultPrevented &&
(event.keyCode == KeyEvent.DOM_VK_UP ||
event.keyCode == KeyEvent.DOM_VK_DOWN ||
event.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN ||
event.keyCode == KeyEvent.DOM_VK_HOME ||
event.keyCode == KeyEvent.DOM_VK_END ||
event.keyCode == KeyEvent.DOM_VK_BACK_SPACE ||
event.charCode > 0)) {
// Moving relative to an item: start from the currently selected item
this.menuBoxObject.activeChild = this.mSelectedInternal;
if (this.menuBoxObject.handleKeyPress(event)) {
this.menuBoxObject.activeChild.doCommand();
event.preventDefault();
}
}
]]>
</handler>
</handlers>
<implementation implements="nsIDOMXULMenuListElement, nsIDOMEventListener">
<constructor>
this.setInitialSelection()
</constructor>
<method name="setInitialSelection">
<body>
<![CDATA[
var popup = this.menupopup;
if (popup) {
var arr = popup.getElementsByAttribute('selected', 'true');
var editable = this.editable;
var value = this.value;
if (!arr.item(0) && value)
arr = popup.getElementsByAttribute(editable ? 'label' : 'value', value);
if (arr.item(0))
this.selectedItem = arr[0];
else if (!editable)
this.selectedIndex = 0;
}
]]>
</body>
</method>
<property name="value" onget="return this.getAttribute('value');">
<setter>
<![CDATA[
// if the new value is null, we still need to remove the old value
if (val == null)
return this.selectedItem = val;
var arr = null;
var popup = this.menupopup;
if (popup)
arr = popup.getElementsByAttribute('value', val);
if (arr && arr.item(0))
this.selectedItem = arr[0];
else {
this.selectedItem = null;
this.setAttribute('value', val);
}
return val;
]]>
</setter>
</property>
<property name="inputField" readonly="true" onget="return null;"/>
<property name="crop" onset="this.setAttribute('crop',val); return val;"
onget="return this.getAttribute('crop');"/>
<property name="image" onset="this.setAttribute('image',val); return val;"
onget="return this.getAttribute('image');"/>
<property name="label" readonly="true" onget="return this.getAttribute('label');"/>
<property name="description" onset="this.setAttribute('description',val); return val;"
onget="return this.getAttribute('description');"/>
<property name="editable" onset="this.setAttribute('editable',val); return val;"
onget="return this.getAttribute('editable') == 'true';"/>
<property name="open" onset="this.menuBoxObject.openMenu(val);
return val;"
onget="return this.hasAttribute('open');"/>
<property name="itemCount" readonly="true"
onget="return this.menupopup ? this.menupopup.childNodes.length : 0"/>
<property name="menupopup" readonly="true">
<getter>
<![CDATA[
var popup = this.firstChild;
while (popup && popup.localName != "menupopup")
popup = popup.nextSibling;
return popup;
]]>
</getter>
</property>
<field name="menuBoxObject" readonly="true">
this.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject)
</field>
<field name="mSelectedInternal">
null
</field>
<method name="contains">
<parameter name="item"/>
<body>
<![CDATA[
if (!item)
return false;
var parent = item.parentNode;
return (parent && parent.parentNode == this);
]]>
</body>
</method>
<property name="selectedIndex">
<getter>
<![CDATA[
// Quick and dirty. We won't deal with hierarchical menulists yet.
if (!this.selectedItem ||
!this.mSelectedInternal.parentNode ||
this.mSelectedInternal.parentNode.parentNode != this)
return -1;
var children = this.mSelectedInternal.parentNode.childNodes;
var i = children.length;
while (i--)
if (children[i] == this.mSelectedInternal)
break;
return i;
]]>
</getter>
<setter>
<![CDATA[
var popup = this.menupopup;
if (popup && 0 <= val) {
if (val < popup.childNodes.length)
this.selectedItem = popup.childNodes[val];
}
else
this.selectedItem = null;
return val;
]]>
</setter>
</property>
<property name="selectedItem">
<getter>
<![CDATA[
return this.mSelectedInternal;
]]>
</getter>
<setter>
<![CDATA[
var oldval = this.mSelectedInternal;
if (oldval == val)
return val;
if (val && !this.contains(val))
return val;
if (oldval) {
oldval.removeAttribute('selected');
if (document instanceof Components.interfaces.nsIDOMXULDocument) {
document.removeBroadcastListenerFor(oldval, this, "value");
document.removeBroadcastListenerFor(oldval, this, "label");
document.removeBroadcastListenerFor(oldval, this, "image");
document.removeBroadcastListenerFor(oldval, this, "description");
}
else
oldval.removeEventListener("DOMAttrModified", this, false);
}
this.mSelectedInternal = val;
if (val) {
val.setAttribute('selected', 'true');
this.setAttribute('value', val.getAttribute('value'));
this.setAttribute('image', val.getAttribute('image'));
this.setAttribute('label', val.getAttribute('label'));
this.setAttribute('description', val.getAttribute('description'));
// DOMAttrModified listeners slow down setAttribute calls within
// the document, see bug 395496
if (document instanceof Components.interfaces.nsIDOMXULDocument) {
document.addBroadcastListenerFor(val, this, "value");
document.addBroadcastListenerFor(val, this, "label");
document.addBroadcastListenerFor(val, this, "image");
document.addBroadcastListenerFor(val, this, "description");
}
else
val.addEventListener("DOMAttrModified", this, false);
}
else {
this.removeAttribute('value');
this.removeAttribute('image');
this.removeAttribute('label');
this.removeAttribute('description');
}
var event = document.createEvent("Events");
event.initEvent("select", true, true);
this.dispatchEvent(event);
var event = document.createEvent("Events");
event.initEvent("ValueChange", true, true);
this.dispatchEvent(event);
return val;
]]>
</setter>
</property>
<method name="handleEvent">
<parameter name="aEvent"/>
<body>
<![CDATA[
if (aEvent.type == "DOMAttrModified" &&
aEvent.target == this.mSelectedInternal) {
var attrName = aEvent.attrName;
switch (attrName) {
case "value":
case "label":
case "image":
case "description":
this.setAttribute(attrName, aEvent.newValue);
}
}
]]>
</body>
</method>
<method name="getIndexOfItem">
<parameter name="item"/>
<body>
<![CDATA[
var popup = this.menupopup;
if (popup) {
var children = popup.childNodes;
var i = children.length;
while (i--)
if (children[i] == item)
return i;
}
return -1;
]]>
</body>
</method>
<method name="getItemAtIndex">
<parameter name="index"/>
<body>
<![CDATA[
var popup = this.menupopup;
if (popup) {
var children = popup.childNodes;
if (index >= 0 && index < children.length)
return children[index];
}
return null;
]]>
</body>
</method>
<method name="appendItem">
<parameter name="label"/>
<parameter name="value"/>
<parameter name="description"/>
<body>
<![CDATA[
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var popup = this.menupopup ||
this.appendChild(document.createElementNS(XULNS, "menupopup"));
var item = document.createElementNS(XULNS, "menuitem");
item.setAttribute("label", label);
item.setAttribute("value", value);
if (description)
item.setAttribute("description", description);
popup.appendChild(item);
return item;
]]>
</body>
</method>
<method name="insertItemAt">
<parameter name="index"/>
<parameter name="label"/>
<parameter name="value"/>
<parameter name="description"/>
<body>
<![CDATA[
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var popup = this.menupopup ||
this.appendChild(document.createElementNS(XULNS, "menupopup"));
var item = document.createElementNS(XULNS, "menuitem");
item.setAttribute("label", label);
item.setAttribute("value", value);
if (description)
item.setAttribute("description", description);
if (index >= 0 && index < popup.childNodes.length)
popup.insertBefore(item, popup.childNodes[index]);
else
popup.appendChild(item);
return item;
]]>
</body>
</method>
<method name="removeItemAt">
<parameter name="index"/>
<body>
<![CDATA[
var popup = this.menupopup;
if (popup && 0 <= index && index < popup.childNodes.length) {
var remove = popup.childNodes[index];
popup.removeChild(remove);
return remove;
}
return null;
]]>
</body>
</method>
<method name="removeAllItems">
<body>
<![CDATA[
this.selectedItem = null;
var popup = this.menupopup;
if (popup)
this.removeChild(popup);
]]>
</body>
</method>
<destructor>
<![CDATA[
if (this.mSelectedInternal) {
if (document instanceof Components.interfaces.nsIDOMXULDocument) {
document.removeBroadcastListenerFor(this.mSelectedInternal, this, "value");
document.removeBroadcastListenerFor(this.mSelectedInternal, this, "label");
document.removeBroadcastListenerFor(this.mSelectedInternal, this, "image");
document.removeBroadcastListenerFor(this.mSelectedInternal, this, "description");
}
else
this.mSelectedInternal.removeEventListener("DOMAttrModified", this, false);
}
]]>
</destructor>
</implementation>
</binding>
<binding id="menulist-editable" extends="chrome://global/content/bindings/menulist.xml#menulist">
<content sizetopopup="pref">
<xul:hbox class="menulist-editable-box textbox-input-box" xbl:inherits="context,disabled,readonly,focused" flex="1">
<html:input class="menulist-editable-input" anonid="input" allowevents="true"
xbl:inherits="value=label,value,disabled,tabindex,readonly,placeholder"/>
</xul:hbox>
<xul:dropmarker class="menulist-dropmarker" type="menu"
xbl:inherits="open,disabled,parentfocused=focused"/>
<children includes="menupopup"/>
</content>
<implementation>
<method name="_selectInputFieldValueInList">
<body>
<![CDATA[
if (this.hasAttribute("disableautoselect"))
return;
// Find and select the menuitem that matches inputField's "value"
var arr = null;
var popup = this.menupopup;
if (popup)
arr = popup.getElementsByAttribute('label', this.inputField.value);
this.setSelectionInternal(arr ? arr.item(0) : null);
]]>
</body>
</method>
<method name="setSelectionInternal">
<parameter name="val"/>
<body>
<![CDATA[
// This is called internally to set selected item
// without triggering infinite loop
// when using selectedItem's setter
if (this.mSelectedInternal == val)
return val;
if (this.mSelectedInternal)
this.mSelectedInternal.removeAttribute('selected');
this.mSelectedInternal = val;
if (val)
val.setAttribute('selected', 'true');
//Do NOT change the "value", which is owned by inputField
return val;
]]>
</body>
</method>
<field name="mInputField">null</field>
<property name="inputField" readonly="true">
<getter><![CDATA[
if (!this.mInputField)
this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
return this.mInputField;
]]></getter>
</property>
<property name="label" onset="this.inputField.value = val; return val;"
onget="return this.inputField.value;"/>
<property name="value" onget="return this.inputField.value;">
<setter>
<![CDATA[
// Override menulist's value setter to refer to the inputField's value
// (Allows using "menulist.value" instead of "menulist.inputField.value")
this.inputField.value = val;
this.setAttribute('value', val);
this.setAttribute('label', val);
this._selectInputFieldValueInList();
return val;
]]>
</setter>
</property>
<property name="selectedItem">
<getter>
<![CDATA[
// Make sure internally-selected item
// is in sync with inputField.value
this._selectInputFieldValueInList();
return this.mSelectedInternal;
]]>
</getter>
<setter>
<![CDATA[
var oldval = this.mSelectedInternal;
if (oldval == val)
return val;
if (val && !this.contains(val))
return val;
// This doesn't touch inputField.value or "value" and "label" attributes
this.setSelectionInternal(val);
if (val) {
// Editable menulist uses "label" as its "value"
var label = val.getAttribute('label');
this.inputField.value = label;
this.setAttribute('value', label);
this.setAttribute('label', label);
}
else {
this.inputField.value = "";
this.removeAttribute('value');
this.removeAttribute('label');
}
var event = document.createEvent("Events");
event.initEvent("select", true, true);
this.dispatchEvent(event);
var event = document.createEvent("Events");
event.initEvent("ValueChange", true, true);
this.dispatchEvent(event);
return val;
]]>
</setter>
</property>
<property name="disableautoselect"
onset="if (val) this.setAttribute('disableautoselect','true');
else this.removeAttribute('disableautoselect'); return val;"
onget="return this.hasAttribute('disableautoselect');"/>
<property name="editor" readonly="true">
<getter><![CDATA[
const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
return this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
]]></getter>
</property>
<property name="readOnly" onset="this.inputField.readOnly = val;
if (val) this.setAttribute('readonly', 'true');
else this.removeAttribute('readonly'); return val;"
onget="return this.inputField.readOnly;"/>
<method name="select">
<body>
this.inputField.select();
</body>
</method>
</implementation>
<handlers>
<handler event="focus" phase="capturing">
<![CDATA[
this.setAttribute('focused','true');
]]>
</handler>
<handler event="blur" phase="capturing">
<![CDATA[
this.removeAttribute('focused');
]]>
</handler>
<handler event="popupshowing">
<![CDATA[
// editable menulists elements aren't in the focus order,
// so when the popup opens we need to force the focus to the inputField
if (event.target.parentNode == this) {
if (document.commandDispatcher.focusedElement != this.inputField)
this.inputField.focus();
this.menuBoxObject.activeChild = null;
if (this.selectedItem)
// Not ready for auto-setting the active child in hierarchies yet.
// For now, only do this when the outermost menupopup opens.
this.menuBoxObject.activeChild = this.mSelectedInternal;
}
]]>
</handler>
<handler event="keypress">
<![CDATA[
// open popup if key is up arrow, down arrow, or F4
if (!event.ctrlKey && !event.shiftKey) {
if (event.keyCode == KeyEvent.DOM_VK_UP ||
event.keyCode == KeyEvent.DOM_VK_DOWN ||
(event.keyCode == KeyEvent.DOM_VK_F4 && !event.altKey)) {
event.preventDefault();
this.open = true;
}
}
]]>
</handler>
</handlers>
</binding>
<binding id="menulist-compact" display="xul:menu"
extends="chrome://global/content/bindings/menulist.xml#menulist">
<content sizetopopup="false">
<xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
<xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
<xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
<children includes="menupopup"/>
</content>
</binding>
<binding id="menulist-description" display="xul:menu"
extends="chrome://global/content/bindings/menulist.xml#menulist">
<content sizetopopup="pref">
<xul:hbox class="menulist-label-box" flex="1">
<xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
<xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
<xul:label class="menulist-label menulist-description" xbl:inherits="value=description" crop="right" flex="10000"/>
</xul:hbox>
<xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
<children includes="menupopup"/>
</content>
</binding>
</bindings>