summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets/menulist.xml
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/widgets/menulist.xml')
-rw-r--r--toolkit/content/widgets/menulist.xml606
1 files changed, 606 insertions, 0 deletions
diff --git a/toolkit/content/widgets/menulist.xml b/toolkit/content/widgets/menulist.xml
new file mode 100644
index 000000000..ccdf3bd26
--- /dev/null
+++ b/toolkit/content/widgets/menulist.xml
@@ -0,0 +1,606 @@
+<?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">
+ <constructor>
+ this.mInputField = null;
+ this.mSelectedInternal = null;
+ this.mAttributeObserver = null;
+ this.menuBoxObject = this.boxObject;
+ 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>
+
+ <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');
+ this.mAttributeObserver.disconnect();
+ }
+
+ this.mSelectedInternal = val;
+ let attributeFilter = ["value", "label", "image", "description"];
+ if (val) {
+ val.setAttribute('selected', 'true');
+ for (let attr of attributeFilter) {
+ if (val.hasAttribute(attr)) {
+ this.setAttribute(attr, val.getAttribute(attr));
+ }
+ else {
+ this.removeAttribute(attr);
+ }
+ }
+
+ this.mAttributeObserver = new MutationObserver(this.handleMutation.bind(this));
+ this.mAttributeObserver.observe(val, { attributeFilter });
+ }
+ else {
+ for (let attr of attributeFilter) {
+ this.removeAttribute(attr);
+ }
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("select", true, true);
+ this.dispatchEvent(event);
+
+ event = document.createEvent("Events");
+ event.initEvent("ValueChange", true, true);
+ this.dispatchEvent(event);
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="handleMutation">
+ <parameter name="aRecords"/>
+ <body>
+ <![CDATA[
+ for (let record of aRecords) {
+ let t = record.target;
+ if (t == this.mSelectedInternal) {
+ let attrName = record.attributeName;
+ switch (attrName) {
+ case "value":
+ case "label":
+ case "image":
+ case "description":
+ if (t.hasAttribute(attrName)) {
+ this.setAttribute(attrName, t.getAttribute(attrName));
+ }
+ else {
+ this.removeAttribute(attrName);
+ }
+ }
+ }
+ }
+ ]]>
+ </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.mAttributeObserver) {
+ this.mAttributeObserver.disconnect();
+ }
+ ]]>
+ </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>
+
+ <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);
+
+ 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-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>
+
+ <binding id="menulist-popuponly" display="xul:menu"
+ extends="chrome://global/content/bindings/menulist.xml#menulist">
+ <content sizetopopup="pref">
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+</bindings>