diff options
Diffstat (limited to 'toolkit/content/widgets/menulist.xml')
-rw-r--r-- | toolkit/content/widgets/menulist.xml | 606 |
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> |