<?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>