mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
626 lines
22 KiB
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>
|