Bug 367187 - Places context menu fx2-parity. r=sspitzer.

This commit is contained in:
mozilla.mano%sent.com 2007-01-21 23:22:52 +00:00
parent d18853a6e2
commit dae01d62ef
3 changed files with 223 additions and 161 deletions

View File

@ -681,168 +681,219 @@ PlacesController.prototype = {
},
#endif
/**
* Gather information about the selection according to the following
* Gathers information about the selected nodes according to the following
* rules:
* "link" single selection is URI
* "links" all selected items are links, and there are at least 2
* "folder" selection is a folder
* "query" selection is a query
* "remotecontainer" selection is a remote container
* "separator" selection is a separator line
* "host" selection is a host
* "mutable" selection can have items inserted or reordered
* "mixed" selection contains more than one type
* "allLivemarks" selection is a query containing every livemark
* "multiselect" seleciton contains more than one item
* In addition, a property is set corresponding to each of the selected
* items' annotation names.
* "link" node is a URI
* "bookmark" node is a bookamrk
* "folder" node is a folder
* "query" node is a query
* "remotecontainer" node is a remote container
* "separator" node is a separator line
* "host" node is a host
* "mutable" node can have items inserted or reordered
* "allLivemarks" node is a query containing every livemark
*
* @returns an object with each of the properties above set if the selection
* matches that rule.
* Note: This can be slow, so don't call it anywhere performance critical!
* @returns an array of objects corresponding the selected nodes. Each
* object has each of the properties above set if its corresponding
* node matches the rule. In addition, the annotations names for each
* node are set on its corresponding object as properties.
* Notes:
* 1) This can be slow, so don't call it anywhere performance critical!
* 2) A single-object array corresponding the root node is returned if
* there's no selection.
*/
_buildSelectionMetadata: function PC__buildSelectionMetadata() {
var metadata = { };
var v = this._view;
var hasSingleSelection = v.hasSingleSelection;
if (v.selectedURINode && hasSingleSelection)
metadata["link"] = true;
if (hasSingleSelection) {
var selectedNode = v.selectedNode;
if (PlacesUtils.nodeIsFolder(selectedNode))
metadata["folder"] = true;
if (PlacesUtils.nodeIsQuery(selectedNode))
metadata["query"] = true;
if (PlacesUtils.nodeIsRemoteContainer(selectedNode))
metadata["remotecontainer"] = true;
if (PlacesUtils.nodeIsSeparator(selectedNode))
metadata["separator"] = true;
if (PlacesUtils.nodeIsHost(selectedNode))
metadata["host"] = true;
}
// Mutability is whether or not a container can have selected items
// inserted or reordered. It does _not_ dictate whether or not the container
// can have items removed from it, since some containers that aren't
// reorderable can have items removed from them, e.g. a history list.
//
// The mutability property starts out set to true, and is removed if
// any component of the selection is found to be part of a readonly
// container.
metadata["mutable"] = true;
var metadata = [];
var nodes = [];
var root = this._view.getResult().root;
if (this._view.hasSelection)
nodes = this._view.getSelectionNodes();
else // See the second note above
nodes = [root];
/**
* Determines whether or not a node is a readonly folder.
* @param node
* The node to test.
* @returns true if the node is a readonly folder.
*/
function folderIsReadOnly(node) {
return PlacesUtils.nodeIsFolder(node) &&
PlacesUtils.bookmarks.getFolderReadonly(asFolder(node).folderId);
}
var foundNonURI = false;
var nodes = v.getSelectionNodes();
var root = v.getResult().root;
if (v.hasSelection)
var lastParent = nodes[0].parent, lastType = nodes[0].type;
else {
// If there is no selection, mutability is determined by the readonly-ness
// of the result root. See note above on mutability.
if (folderIsReadOnly(root))
delete metadata["mutable"];
}
// Walk the selection, gathering metadata about the selected items.
for (var i = 0; i < nodes.length; ++i) {
for (var i=0; i < nodes.length; i++) {
var nodeData = {};
var node = nodes[i];
if (!PlacesUtils.nodeIsURI(node))
foundNonURI = true;
// If there is a selection, mutability is determined by the readonly-ness
// of the selected item, or the parent of the selection. See note above
// on mutability.
if (PlacesUtils.nodeIsReadOnly(node) || folderIsReadOnly(node.parent || root))
delete metadata["mutable"];
var nodeType = node.type;
var uri = null;
if (PlacesUtils.nodeIsURI(node))
uri = PlacesUtils._uri(node.uri);
if (PlacesUtils.nodeIsFolder(node))
uri = PlacesUtils.bookmarks.getFolderURI(asFolder(node).folderId);
// We don't use the nodeIs* methods here to avoid going through the type
// property way too often
switch(nodeType) {
case Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY:
nodeData["query"] = true;
break;
case Ci.nsINavHistoryResultNode.RESULT_TYPE_REMOTE_CONTAINER:
nodeData["remotecontainer"] = true;
break;
case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
nodeData["folder"] = true;
uri = PlacesUtils.bookmarks.getFolderURI(asFolder(node).folderId);
// See nodeIsRemoteContainer
if (asContainer(node).remoteContainerType != "")
nodeData["remotecontainer"] = true;
break;
case Ci.nsINavHistoryResultNode.RESULT_TYPE_HOST:
nodeData["host"] = true;
break;
case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR:
nodeData["separator"] = true;
break;
case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI:
case Ci.nsINavHistoryResultNode.RESULT_TYPE_VISIT:
case Ci.nsINavHistoryResultNode.RESULT_TYPE_FULL_VISIT:
nodeData["link"] = true;
if (PlacesUtils.bookmarks.isBookmarked(PlacesUtils._uri(node.uri)))
nodeData["bookmark"] = true;
break;
}
// Mutability is whether or not a container can have selected items
// inserted or reordered. It does _not_ dictate whether or not the
// container can have items removed from it, since some containers that
// aren't reorderable can have items removed from them, e.g. a history
// list.
if (!PlacesUtils.nodeIsReadOnly(node) &&
!PlacesUtils.folderIsReadonly(node.parent || root))
nodeData["mutable"] = true;
// annotations
if (uri) {
var names = PlacesUtils.annotations.getPageAnnotationNames(uri, { });
for (var j = 0; j < names.length; ++j)
metadata[names[i]] = true;
nodeData[names[i]] = true;
}
else if (PlacesUtils.nodeIsQuery(node)) {
// Various queries might live in the left-hand side of the organizer window.
// If this one happens to have collected all the livemark feeds, allow its
// context menu to contain "Reload All Livemarks". That will usually only
// mean the Subscriptions folder, but if some other folder happens to use
// the same query, it's fine too. Queries have very limited data (no
// annotations), so we're left checking the query URI directly.
uri = PlacesUtils._uri(node.uri);
else if (nodeType = Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
// Various queries might live in the left-hand side of the organizer
// window. If this one happens to have collected all the livemark feeds,
// allow its context menu to contain "Reload All Livemarks". That will
// usually only mean the Subscriptions folder, but if some other folder
// happens to use the same query, it's fine too. Queries have very
// limited data (no annotations), so we're left checking the query URI
// directly.
uri = PlacesUtils._uri(nodes[i].uri);
if (uri.spec == ORGANIZER_SUBSCRIPTIONS_QUERY)
metadata["allLivemarks"] = true;
nodeData["allLivemarks"] = true;
}
if (nodes[i].parent != lastParent || nodes[i].type != lastType)
metadata["mixed"] = true;
metadata.push(nodeData);
}
if (v.selType != "single")
metadata["multiselect"] = true;
if (!foundNonURI && nodes.length > 1)
metadata["links"] = true;
return metadata;
},
/**
* Determines if a menuitem should be shown or not by comparing the rules
* that govern the item's display with the state of the selection.
* @param metadata
* metadata about the selection.
* @param rules
* rules that govern the item's display
* @returns true if the conditions are satisfied and the item can be
* displayed, false otherwise.
* Determines if a context-menu item should be shown
* @param aMenuItem
* the context menu item
* @param aMetaData
* meta data about the selection
* @returns true if the conditions (see buildContextMenu) are satisfied
* and the item can be displayed, false otherwise.
*/
_shouldShowMenuItem: function(metadata, rules) {
for (var i = 0; i < rules.length; ++i) {
if (rules[i] in metadata)
return true;
_shouldShowMenuItem: function(aMenuItem, aMetaData) {
var selectiontype = aMenuItem.getAttribute("selectiontype");
if (selectiontype == "multiple" && aMetaData.length == 1)
return false;
if (selectiontype == "single" && aMetaData.length != 1)
return false;
var forceHideRules = aMenuItem.getAttribute("forcehideselection").split("|");
for (var i = 0; i < aMetaData.length; ++i) {
for (var j=0; j < forceHideRules.length; ++j) {
if (forceHideRules[j] in aMetaData[i])
return false;
}
}
return false;
if (aMenuItem.hasAttribute("selection")) {
var showRules = aMenuItem.getAttribute("selection").split("|");
var anyMatched = false;
function metaDataNodeMatches(metaDataNode, rules) {
for (var i=0; i < rules.length; i++) {
if (rules[i] in metaDataNode)
return true;
}
return false;
}
for (var i = 0; i < aMetaData.length; ++i) {
if (metaDataNodeMatches(aMetaData[i], showRules))
anyMatched = true;
else
return false;
}
return anyMatched;
}
return !aMenuItem.hidden;
},
/**
* Build a context menu for the selection, ensuring that the content of the
* selection is correct and enabling/disabling items according to the state
* of the commands.
* @param popup
* Detects information (meta-data rules) about the current selection in the
* view (see _buildSelectionMetadata) and sets the visibility state for each
* of the menu-items in the given popup with the following rules applied:
* 1) The "selectiontype" attribute may be set on a menu-item to "single"
* if the menu-item should be visible only if there is a single node
* selected, or to "multiple" if the menu-item should be visible only if
* multiple nodes are selected. If the attribute is not set or if it is
* set to an invalid value, the menu-item may be visible for both types of
* selection.
* 2) The "selection" attribute may be set on a menu-item to the various
* meta-data rules for which it may be visible. The rules should be
* separated with the | character.
* 3) A menu-item may be visible only if at least one of the rules set in
* its selection attribute apply to each of the selected nodes in the
* view.
* 4) The "forcehideselection" attribute may be set on a menu-item to rules
* for which it should be hidden. This attribute takes priority over the
* selection attribute. A menu-item would be hidden if at least one of the
* given rules apply to one of the selected nodes. The rules should be
* separated with the | character.
* 5) The visibility state of a menu-item is unchanged if none of these
* attribute are set.
* 6) These attributes should not be set on separators for which the
* visibility state is "auto-detected."
* @param aPopup
* The menupopup to build children into.
*/
buildContextMenu: function PC_buildContextMenu(popup) {
// Determine availability/enabled state of commands
buildContextMenu: function PC_buildContextMenu(aPopup) {
var metadata = this._buildSelectionMetadata();
var lastVisible = null;
for (var i = 0; i < popup.childNodes.length; ++i) {
var item = popup.childNodes[i];
var rules = item.getAttribute("selection");
item.hidden = !this._shouldShowMenuItem(metadata, rules.split("|"));
if (!item.hidden)
lastVisible = item;
var separator = null;
var visibleItemsBeforeSep = false;
for (var i = 0; i < aPopup.childNodes.length; ++i) {
var item = aPopup.childNodes[i];
if (item.localName != "menuseparator") {
item.hidden = !this._shouldShowMenuItem(item, metadata)
if (!item.hidden) {
visibleItemsBeforeSep = true;
// Show the separator above the menu-item if any
if (separator) {
separator.hidden = false;
separator = null;
}
}
}
else { // menuseparator
// Initially hide it. It will be unhidden if there will be at least one
// visible menu-item above and below it.
item.hidden = true;
// We won't show the separator at all if no items are visible above it
if (visibleItemsBeforeSep)
separator = item;
// New separator, count again:
visibleItemsBeforeSep = false;
}
}
if (lastVisible.localName == "menuseparator")
lastVisible.hidden = true;
return true;
},
/**
* Select all links in the current view.
*/

View File

@ -97,32 +97,38 @@
onpopupshowing="this._view = PlacesUtils.getViewForNode(document.popupNode);
this._view.buildContextMenu(this);"
onpopuphiding="this._view.destroyContextMenu();">
<!-- the rules defined in the selection attribute control whether or not
the menu item is _visible_ regardless of whether or not the command
is enabled. -->
<menuitem id="placesContext_open"
command="placesCmd_open"
label="&cmd.open.label;"
accesskey="&cmd.open.accesskey;"
default="true"
selectiontype="single"
selection="link"/>
<menuitem id="placesContext_open:newwindow"
command="placesCmd_open:window"
label="&cmd.open_window.label;"
accesskey="&cmd.open_window.accesskey;"
selectiontype="single"
selection="link"/>
<menuitem id="placesContext_open:newtab"
command="placesCmd_open:tab"
label="&cmd.open_tab.label;"
accesskey="&cmd.open_tab.accesskey;"
selectiontype="single"
selection="link"/>
<menuitem id="placesContext_open:tabs"
<menuitem id="placesContext_openFolder:tabs"
command="placesCmd_open:tabs"
label="&cmd.open_tabs.label;"
accesskey="&cmd.open_tabs.accesskey;"
selection="folder|links"/>
<menuseparator id="placesContext_openSeparator"
selection="link|links|folder"/>
selectiontype="single"
selection="folder"/>
<menuitem id="placesContext_openLinks:tabs"
command="placesCmd_open:tabs"
label="&cmd.open_tabs.label;"
accesskey="&cmd.open_tabs.accesskey;"
selectiontype="multiple"
selection="link"/>
<menuseparator id="placesContext_openSeparator"/>
<menuitem id="placesContext_new:folder"
command="placesCmd_new:folder"
label="&cmd.new_folder.label;"
@ -133,49 +139,42 @@
label="&cmd.new_separator.label;"
accesskey="&cmd.new_separator.accesskey;"
selection="mutable"/>
<menuseparator id="placesContext_newSeparator"
selection="mutable"/>
<menuitem id="placesContext_undo"
command="cmd_undo"
label="&undoCmd.label;"
accesskey="&undoCmd.accesskey;"
selection="separator|link|links|folder|mixed"/>
<menuseparator id="placesContext_newSeparator"/>
<menuitem id="placesContext_cut"
command="cmd_cut"
label="&cutCmd.label;"
accesskey="&cutCmd.accesskey;"
selection="separator|link|folder|mixed"
forcehideselection="livemark/bookmarkFeedURI"/>
<menuitem id="placesContext_copy"
command="cmd_copy"
label="&copyCmd.label;"
accesskey="&copyCmd.accesskey;"
selection="separator|link|links|folder|mixed"/>
selection="separator|link|folder"/>
<menuitem id="placesContext_paste"
command="cmd_paste"
label="&pasteCmd.label;"
accesskey="&pasteCmd.accesskey;"
selection="mutable"/>
<menuseparator id="placesContext_editSeparator"/>
<menuitem id="placesContext_delete"
command="cmd_delete"
label="&deleteCmd.label;"
accesskey="&deleteCmd.accesskey;"
selection="host|separator|link|links|folder|mixed"/>
<menuseparator id="placesContext_editSeparator"
selection="link|links|folder|mixed|mutable"/>
<menuitem id="placesContext_selectAll"
command="cmd_selectAll"
label="&selectAllCmd.label;"
accesskey="&selectAllCmd.accesskey;"
selection="multiselect"/>
<menuseparator id="placesContext_selectSeparator"
selection="multiselect"/>
selection="host|separator|link|folder"
forcehideselection="livemark/bookmarkFeedURI"/>
<menuseparator id="placesContext_deleteSeparator"/>
<menuitem id="placesContext_reload"
command="placesCmd_reload"
label="&cmd.reload.label;"
accesskey="&cmd.reload.accesskey;"
selection="livemark/feedURI|livemark/bookmarkFeedURI|allLivemarks"/>
selection="livemark/feedURI|allLivemarks"/>
<menuitem id="placesContext_sortby:name"
command="placesCmd_sortby:name"
label="&cmd.sortby_name.label;"
accesskey="&cmd.context_sortby_name.accesskey;"
selection="mutable"/>
<menuseparator id="placesContext_sortSeparator"
selection="mutable|livemark/feedURI|livemark/bookmarkFeedURI|allLivemarks"/>
<menuseparator id="placesContext_sortSeparator"/>
<menuitem id="placesContext_show:info"
command="placesCmd_show:info"
#ifdef XP_MACOSX
@ -185,7 +184,8 @@
label="&cmd.show_infoWin.label;"
accesskey="&cmd.show_infoWin.accesskey;"
#endif
selection="link|folder|query"/>
selection="bookmark|folder|query"
forcehideselection="livemark/bookmarkFeedURI"/>
</popup>
</overlay>

View File

@ -308,6 +308,17 @@ var PlacesUtils = {
"@mozilla.org/browser/livemark-service;2");
},
/**
* Determines whether or not a node is a readonly folder.
* @param aNode
* The node to test.
* @returns true if the node is a readonly folder.
*/
folderIsReadonly: function(aNode) {
return this.nodeIsFolder(aNode) &&
this.bookmarks.getFolderReadonly(asFolder(aNode).folderId);
},
/**
* Gets the index of a node within its parent container
* @param aNode