gecko/browser/components/places/content/menu.xml

624 lines
21 KiB
XML
Executable File

<?xml version="1.0"?>
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Places Menupopup View.
#
# The Initial Developer of the Original Code is Google Inc.
# Portions created by the Initial Developer are Copyright (C) 2005-2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Annie Sullivan <annie.sullivan@gmail.com>
# Ben Goodger <beng@google.com>
# Asaf Romano <mano@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
<bindings id="placesMenuBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="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">
<binding id="places-menupopup"
extends="chrome://global/content/bindings/popup.xml#popup">
<implementation>
<destructor><![CDATA[
this._resultNode = null;
if (this._result) {
this._result.viewer = null;
this._result = null;
}
]]></destructor>
<field name="_initialized">false</field>
<method name="_ensureInitialized">
<body><![CDATA[
if (this._initialized)
return;
this._controller = new PlacesController(this);
this.controllers.appendController(this._controller);
// This function should only be called for top-level menus like the bookmarks menu.
// Submenus get their _result and _resultNode from their parents.
if (this.hasAttribute("place")) {
// Do the initial build.
this.place = this.place;
}
this._initialized = true;
]]></body>
</method>
<property name="controller"
readonly="true"
onget="return this._controller;"/>
<field name="_built">false</field>
<method name="onPopupShowing">
<parameter name="aEvent"/>
<body><![CDATA[
var popup = aEvent.target;
var resultNode = popup._resultNode;
resultNode.containerOpen = true;
if (!popup._built)
this._rebuild(popup);
if (!popup._DNDObserver)
popup._DNDObserver = new PlacesMenuDNDObserver(this, popup);
]]></body>
</method>
<field name="_selection">null</field>
<!-- nsIPlacesView -->
<method name="getResult">
<body><![CDATA[
return this._result;
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="getResultNode">
<body><![CDATA[
this._ensureInitialized();
return this._resultNode;
]]></body>
</method>
<method name="_cleanMenu">
<parameter name="aPopup"/>
<body><![CDATA[
// Find static menuitems that should go at the start
// and end of the menu, marked by builder="start" and
// builder="end" attributes, and keep track of their indices.
// All of the items between the start and end should be removed.
var items = [];
aPopup._startMarker = -1;
aPopup._endMarker = -1;
for (var i = 0; i < aPopup.childNodes.length; ++i) {
var item = aPopup.childNodes[i];
if (item.getAttribute("builder") == "start") {
aPopup._startMarker = i;
continue;
}
if (item.getAttribute("builder") == "end") {
aPopup._endMarker = i;
continue;
}
if ((aPopup._startMarker != -1) && (aPopup._endMarker == -1))
items.push(item);
}
// If static items at the beginning were found, remove all items between
// them and the static content at the end.
for (var i = 0; i < items.length; ++i) {
// skip the empty menu item
if (aPopup._emptyMenuItem != items[i]) {
aPopup.removeChild(items[i]);
if (this._endMarker > 0)
--this._endMarker;
}
}
// If no static items were found at the beginning, remove all items before
// the static items at the end.
if (aPopup._startMarker == -1) {
var end = aPopup._endMarker == -1 ?
aPopup.childNodes.length - 1 : aPopup._endMarker - 1;
for (var i = end; i >=0; i--) {
// skip the empty menu item
if (aPopup._emptyMenuItem != aPopup.childNodes[i]) {
aPopup.removeChild(aPopup.childNodes[i]);
if (aPopup._endMarker > 0)
--aPopup._endMarker;
}
}
}
]]></body>
</method>
<method name="removeItem">
<parameter name="child"/>
<body><![CDATA[
if (PlacesUtils.nodeIsContainer(child.node)) {
for (var i=0; i < this._containerNodesMap.length; i++) {
if (this._containerNodesMap[i].resultNode == child.node) {
this._containerNodesMap.splice(i, 1);
break;
}
}
}
child.parentNode.removeChild(child);
]]></body>
</method>
<method name="insertNewItem">
<parameter name="aChild"/>
<parameter name="aParentPopup"/>
<parameter name="aBefore"/>
<body><![CDATA[
var element =
PlacesUtils.createMenuItemForNode(aChild, this._containerNodesMap);
if (aBefore)
aParentPopup.insertBefore(element, aBefore);
else {
// Add the new element to the menu. If there is static content at
// the end of the menu, add the element before that. Otherwise,
// just add to the end.
if (aParentPopup._endMarker != -1)
aParentPopup.insertBefore(element,
aParentPopup.childNodes[this._endMarker++]);
else
aParentPopup.appendChild(element);
}
]]></body>
</method>
<method name="_showEmptyMenuItem">
<parameter name="aPopup"/>
<body><![CDATA[
if (aPopup._emptyMenuItem) {
aPopup._emptyMenuItem.hidden = false;
return;
}
var label = PlacesUtils.getString("bookmarksMenuEmptyFolder");
aPopup._emptyMenuItem = document.createElement("menuitem");
aPopup._emptyMenuItem.setAttribute("label", label);
aPopup._emptyMenuItem.setAttribute("disabled", true);
aPopup.appendChild(aPopup._emptyMenuItem);
]]></body>
</method>
<method name="_rebuild">
<parameter name="aPopup"/>
<body><![CDATA[
this._cleanMenu(aPopup);
var cc = aPopup._resultNode.childCount;
if (cc > 0) {
if (aPopup._emptyMenuItem)
aPopup._emptyMenuItem.hidden = true;
for (var i = 0; i < cc; ++i) {
var child = aPopup._resultNode.getChild(i);
this.insertNewItem(child, aPopup, null);
}
}
else {
// This menu is empty. If there is no static content, add
// an element to show it is empty.
if (aPopup._startMarker == -1 && aPopup._endMarker == -1)
this._showEmptyMenuItem(aPopup);
}
aPopup._built = true;
]]></body>
</method>
<!-- nsINavHistoryResultViewer -->
<field name="_viewer"><![CDATA[({
_self: this,
_getPopupForContainer:
function PMV__getPopupForContainer(aNode) {
if (this._self._resultNode == aNode)
return this._self;
for (var i=0; i < this._self._containerNodesMap.length; i++) {
if (this._self._containerNodesMap[i].resultNode == aNode)
return this._self._containerNodesMap[i].domNode;
}
throw("Container view not found");
},
get result() {
return this._self._result;
},
set result(val) {
this._built = false;
this._self._containerNodesMap = [];
this._self._resultNode = val.root;
return this._self._result = val;
},
itemInserted: function PMV_itemInserted(aParentNode, aNode, aIndex) {
var popup = this._getPopupForContainer(aParentNode);
if (!popup._built)
return;
var index = popup._startMarker + 1 + aIndex;
var before = popup.childNodes[index] || null;
this._self.insertNewItem(aNode, popup, before);
if (popup._emptyMenuItem)
popup._emptyMenuItem.hidden = true;
},
itemRemoved: function PMV_itemRemoved(aParentNode, aNode, aIndex) {
var popup = this._getPopupForContainer(aParentNode);
if (!popup._built)
return;
var children = popup.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].node == aNode) {
this._self.removeItem(children[i]);
if (!popup.hasChildNodes() ||
(popup.childNodes.length == 1 &&
popup.firstChild == popup._emptyMenuItem)) {
this._self._showEmptyMenuItem(popup);
}
}
}
},
itemMoved:
function PMV_itemMoved(aItem, aOldParent, aOldIndex, aNewParent,
aNewIndex) {
// This cannot actually happen yet (see IDL)
if (aNewParent != aOldParent)
return;
var popup = this._getPopupForContainer(aNewParent);
var index = popup._startMarker + 1 + aNewIndex;
var children = popup.childNodes;
for (var i = 0; i < children.length; i++) {
var menuItem = children[i];
if (menuItem.node == aItem) {
popup.removeChild(menuItem);
popup.insertBefore(menuItem, children[index]);
return;
}
}
},
itemChanged: function PMV_itemChanged(aNode) {
// this check can be removed once we fix bug #382397
var parentNode = aNode.parent;
if (!parentNode)
return;
var popup = this._getPopupForContainer(parentNode);
if (!popup._built)
return;
var children = popup.childNodes;
var menuitem;
for (var i = 0; i < children.length; i++) {
if (children[i].node == aNode) {
menuitem = children[i];
break;
}
}
if (PlacesUtils.nodeIsSeparator(aNode)) {
// nothing to do when a separator changes
return;
}
var iconURI = aNode.icon;
if (iconURI) {
var spec = iconURI.spec;
if (menuitem.getAttribute("image") != spec)
menuitem.setAttribute("image", spec);
}
else
menuitem.removeAttribute("image");
var title = aNode.title;
if (menuitem.getAttribute("label") != title)
menuitem.setAttribute("label", title);
if (!menuitem.hasAttribute("livemark") &&
PlacesUtils.nodeIsLivemarkContainer(aNode))
menuitem.setAttribute("livemark", "true");
},
itemReplaced:
function PMV_itemReplaced(aParentNode, aOldNode, aNewNode, aIndex) {
var popup = this._getPopupForContainer(aParentNode);
if (!popup._built)
return;
var children = popup.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].node == aOldNode) {
var next = children[i].nextSibling;
this._self.removeItem(children[i]);
this._self.insertNewItem(aNewNode, popup, next);
return;
}
}
},
containerOpened: function PMV_containerOpened(aNode) {
this.invalidateContainer(aNode);
},
containerClosed: function PMV_containerClosed(aNode) {
this.invalidateContainer(aNode);
},
invalidateContainer: function PMV_invalidateContainer(aContainer) {
if (!this._self._built)
return;
function isChildOf(node, container) {
var parent = node.parent;
while (parent) {
if (parent == container)
return true;
parent = parent.parent;
}
return false;
}
var popupToRebuild = null;
for (var i=0; i < this._self._containerNodesMap.length; i++) {
var node = this._self._containerNodesMap[i].resultNode;
if (node == aContainer)
popupToRebuild = this._self._containerNodesMap[i].domNode;
if (isChildOf(node, aContainer)) {
this._self._containerNodesMap.splice(i,1);
i--;
}
}
if (popupToRebuild)
popupToRebuild._built = false;
else
this._self._built = false;
},
invalidateAll: function PMV_invalidateAll() {
this._self._containerNodesMap.splice(0);
this._self._built = false;
},
sortingChanged: function PMV_sortingChanged(aSortingMode) {
}
})]]></field>
<!-- nsIPlacesView -->
<property name="place">
<getter><![CDATA[
return this.getAttribute("place");
]]></getter>
<setter><![CDATA[
this.setAttribute("place", val);
var queries = { }, options = { };
PlacesUtils.history.queryStringToQueries(val, queries, { }, options);
if (!queries.value.length)
queries.value = [PlacesUtils.history.getNewQuery()];
var result =
PlacesUtils.history.executeQueries(queries.value,
queries.value.length,
options.value);
result.viewer = this._viewer;
return val;
]]></setter>
</property>
<!-- nsIPlacesView -->
<property name="hasSelection">
<getter><![CDATA[
return this._selection != null;
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="hasSingleSelection">
<getter><![CDATA[
return this.hasSelection;
]]></getter>
</property>
<!-- nsIPlacesView -->
<method name="getSelectionNodes">
<body><![CDATA[
return this.hasSelection ? [this.selectedNode] : [];
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="getRemovableSelectionRanges">
<body><![CDATA[
return [this.getSelectionNodes()];
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="getCopyableSelection">
<body><![CDATA[
return this.getSelectionNodes();
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="getDragableSelection">
<body><![CDATA[
if (PlacesUtils.nodeIsReadOnly(this._resultNode))
return null;
return this.getSelectionNodes();
]]></body>
</method>
<!-- nsIPlacesView -->
<property name="selectedNode">
<getter><![CDATA[
return this.hasSelection ? this._selection : null;
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="selectedURINode">
<getter><![CDATA[
var node = this.selectedNode;
return node && PlacesUtils.nodeIsURI(node) ? node : null;
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="insertionPoint">
<getter><![CDATA[
if (this._cachedInsertionPoint !== undefined)
return this._cachedInsertionPoint;
// By default, the insertion point is at the top level, at the end.
var index = -1;
var folderId = 0;
if (PlacesUtils.nodeIsFolder(this._resultNode))
folderId = PlacesUtils.getConcreteItemId(this._resultNode);
if (this.hasSelection) {
if (PlacesUtils.nodeIsFolder(this.selectedNode)) {
// If there is a folder selected, the insertion point is the
// end of the folder.
folderId = PlacesUtils.getConcreteItemId(this.selectedNode);
} else {
// If there is another type of node selected, the insertion point
// is after that node.
folderId = PlacesUtils.getConcreteItemId(this.selectedNode.parent);
index = PlacesUtils.getIndexOfNode(this.selectedNode)
}
}
this._cachedInsertionPoint = new InsertionPoint(folderId, index);
return this._cachedInsertionPoint;
]]></getter>
</property>
<!-- nsIPlacesView -->
<method name="selectAll">
<body><![CDATA[
// Nothing
]]></body>
</method>
<method name="saveSelection">
<parameter name="mode"/>
<body><![CDATA[
]]></body>
</method>
<method name="restoreSelection">
<body><![CDATA[
]]></body>
</method>
<property name="selType" readonly="true" onget="return 'single';"/>
<method name="buildContextMenu">
<parameter name="aPopup"/>
<body><![CDATA[
this._ensureInitialized();
this.focus();
return this.controller.buildContextMenu(aPopup);
]]></body>
</method>
<method name="destroyContextMenu">
<parameter name="aPopup"/>
<body>
<![CDATA[
if (window.content)
window.content.focus();
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="popupshowing" phase="capturing">
this._ensureInitialized();
if (event.target._resultNode)
this.onPopupShowing(event);
</handler>
<handler event="popuphidden">
var popup = event.target;
if (!popup._resultNode)
return;
// UI performance: folder queries are cheap, keep the resultnode open
// so we don't rebuild its contents whenever the popup is reopened.
if (!PlacesUtils.nodeIsFolder(popup._resultNode))
popup._resultNode.containerOpen = false;
// The autoopened attribute is set for folders which have been
// automatically opened when dragged over. Turn off this attribute
// when the folder closes because it is no longer applicable.
popup.removeAttribute("autoopened");
</handler>
<!-- Set selected node on DOMMenuItemActive/contextmenu events
so that they're set up when command and click events fire. -->
<handler event="DOMMenuItemActive"><![CDATA[
// Set the selection to the node that was activated. If that
// node has a command but no data associated with it, it should
// act on the entire menu.
if (event.target.parentNode._resultNode) {
this._cachedInsertionPoint = undefined;
this._selection = event.target.node ||
event.target.parentNode._resultNode;
}
]]></handler>
<handler event="contextmenu"><![CDATA[
// DOMMenuItemActive is not dispatched for disabled menuitems and
// menuseparators. Set the selection here manually.
var popupNode = document.popupNode;
// |popupNode == menupopup| happens when the area between
// menuseparators is clicked.
this._selection = popupNode.node || popupNode.parentNode._resultNode;
this._cachedInsertionPoint = undefined;
]]></handler>
</handlers>
</binding>
</bindings>