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

1319 lines
47 KiB
XML

<?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 Toolbar 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>
# Myk Melez <myk@mozilla.org>
# Marco Bonardo <mak77@bonardo.net>
#
# 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 *****
<!DOCTYPE bindings [
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
%browserDTD;
]>
<bindings id="placesToolbarBindings"
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-bar">
<resources>
<stylesheet src="chrome://browser/skin/places/places.css"/>
</resources>
<content>
<xul:toolbarbutton class="bookmark-item bookmarks-toolbar-customize"
mousethrough="never"
label="&bookmarksToolbarItem.label;"/>
<xul:hbox flex="1">
<xul:hbox align="center">
<xul:image class="toolbar-drop-indicator"
mousethrough="always"
collapsed="true"/>
</xul:hbox>
<xul:scrollbox orient="horizontal"
class="bookmarks-toolbar-items"
flex="1">
<children/>
</xul:scrollbox>
<xul:toolbarbutton type="menu"
class="chevron"
mousethrough="never"
collapsed="true"
tooltiptext="&bookmarksToolbarChevron.tooltip;"
onpopupshowing="_onChevronPopupShowing(event);">
<xul:menupopup anonid="chevronPopup"
popupsinherittooltip="true"
xbl:inherits="tooltip"
#ifndef XP_MACOSX
context="placesContext"
#endif
/>
</xul:toolbarbutton>
</xul:hbox>
</content>
<implementation implements="nsIAccessibleProvider, nsITimerCallback, nsIDOMEventListener">
<constructor><![CDATA[
this._init();
]]></constructor>
<destructor><![CDATA[
this._scrollbox.removeEventListener("overflow", this, false);
this._scrollbox.removeEventListener("underflow", this, false);
window.removeEventListener("resize", this, false);
if (this._result) {
this._result.viewer = null;
this._result = null;
}
]]></destructor>
<property name="controller"
readonly="true"
onget="return this._controller;"/>
<method name="_init">
<body><![CDATA[
this._controller = new PlacesController(this);
this.controllers.appendController(this._controller);
this._scrollbox.addEventListener("overflow", this, false);
this._scrollbox.addEventListener("underflow", this, false);
window.addEventListener("resize", this, false);
if (this.hasAttribute("place")) {
// Do the initial build.
this.place = this.place;
}
]]></body>
</method>
<field name="_scrollbox">
document.getAnonymousElementByAttribute(this, "class",
"bookmarks-toolbar-items")
</field>
<field name="_dropIndicator">
document.getAnonymousElementByAttribute(this, "class",
"toolbar-drop-indicator")
</field>
<field name="_chevron">
document.getAnonymousElementByAttribute(this, "class", "chevron")
</field>
<field name="_chevronPopup">
document.getAnonymousElementByAttribute(this, "anonid", "chevronPopup")
</field>
<field name="_openedMenuButton">null</field>
<field name="_allowPopupShowing">true</field>
<field name="_result">null</field>
<field name="_resultNode">null</field>
<field name="_isRTL">
document.defaultView.getComputedStyle(this.parentNode, "")
.direction == "rtl"
</field>
<!-- nsIPlacesView -->
<method name="getResult">
<body><![CDATA[
return this._result;
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="getResultNode">
<body><![CDATA[
return this._result.root;
]]></body>
</method>
<method name="_rebuild">
<body><![CDATA[
// Clear out references to existing nodes, since they will be removed
// and re-added.
if (this._overFolder.node)
this._clearOverFolder();
this._openedMenuButton = null;
while (this.hasChildNodes())
this.removeChild(this.firstChild);
var rootNode = this._result.root;
var cc = rootNode.childCount;
for (let i = 0; i < cc; ++i)
this.insertNewItem(rootNode.getChild(i), null);
if (this._chevronPopup.hasAttribute("type")) {
// Chevron has already been initialized, but since we are forcing
// a rebuild of the toolbar, it has to be rebuilt.
// Otherwise, it will be initialized when the toolbar overflows.
this._chevronPopup.place = this.place;
}
while (chevronPopup.hasChildNodes())
this._chevronPopup.removeChild(this._chevronPopup.lastChild);
]]></body>
</method>
<method name="insertNewItem">
<parameter name="aChild"/>
<parameter name="aBefore"/>
<body><![CDATA[
var type = aChild.type;
var button;
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
button = document.createElement("toolbarseparator");
else {
button = document.createElement("toolbarbutton");
button.className = "bookmark-item";
button.setAttribute("label", aChild.title);
var iconURI = aChild.icon;
var iconURISpec = "";
if (iconURI) {
iconURISpec = iconURI.spec;
button.setAttribute("image", iconURISpec);
}
if (PlacesUtils.containerTypes.indexOf(type) != -1) {
button.setAttribute("type", "menu");
button.setAttribute("container", "true");
if (PlacesUtils.nodeIsQuery(aChild)) {
button.setAttribute("query", "true");
if (PlacesUtils.nodeIsTagQuery(aChild))
button.setAttribute("tagContainer", "true");
}
else if (PlacesUtils.nodeIsLivemarkContainer(aChild))
button.setAttribute("livemark", "true");
var popup = document.createElement("menupopup");
popup.setAttribute("placespopup", "true");
button.appendChild(popup);
popup._result = this._result;
popup._resultNode = asContainer(aChild);
#ifndef XP_MACOSX
popup.setAttribute("context", "placesContext");
#endif
this._containerNodesMap.push({ resultNode: aChild,
domNode: popup });
}
else if (PlacesUtils.nodeIsURI(aChild)) {
button.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
}
}
button.node = aChild;
button.node.viewIndex = 0;
if (aBefore)
this.insertBefore(button, aBefore);
else
this.appendChild(button);
]]></body>
</method>
<method name="removeItem">
<parameter name="child"/>
<body><![CDATA[
if (PlacesUtils.nodeIsContainer(child.node)) {
for (let i = 0; i < this._containerNodesMap.length; i++) {
if (this._containerNodesMap[i].resultNode == child.node) {
this._containerNodesMap.splice(i, 1);
break;
}
}
}
// if document.popupNode pointed to this child, null it out,
// otherwise controller's command-updating may rely on the removed
// item still being "selected".
if (document.popupNode == child)
document.popupNode = null;
child.parentNode.removeChild(child);
]]></body>
</method>
<method name="_updateChevronPopupNodesVisibility">
<body><![CDATA[
for (let i = 0; i < this._chevronPopup.childNodes.length; i++) {
this._chevronPopup.childNodes[i].hidden =
this.childNodes[i].style.visibility != "hidden";
}
]]></body>
</method>
<method name="_onChevronPopupShowing">
<parameter name="aEvent"/>
<body><![CDATA[
// Handle popupshowing only for the chevron popup, not for
// nested ones.
if (aEvent.target != this._chevronPopup)
return;
this._updateChevronPopupNodesVisibility();
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
// Both overflow/underflow and resize events should not be handled
// for descendant nodes.
if (aEvent.target != aEvent.currentTarget)
return;
switch (aEvent.type) {
case "resize":
// This handler updates nodes visibility in both the toolbar
// and the chevron popup when a window resize does not change
// the overflow status of the toolbar.
break;
case "overflow":
// Ignore purely vertical overflows.
if (aEvent.detail == 0)
return;
// Attach the popup binding to the chevron popup if it has not yet
// been initialized.
if (!this._chevronPopup.hasAttribute("type")) {
this._chevronPopup.setAttribute("place", this.place);
this._chevronPopup.setAttribute("type", "places");
}
this._chevron.collapsed = false;
break;
case "underflow":
// Ignore purely vertical underflows.
if (aEvent.detail == 0)
return;
this._chevron.collapsed = true;
break;
}
this.updateChevron();
]]></body>
</method>
<method name="updateChevron">
<body><![CDATA[
// If the chevron is collapsed there's nothing to update.
if (this._chevron.collapsed)
return;
// XXX (bug 508816) Scrollbox does not handle correctly RTL mode.
// This workarounds the issue scrolling the box to the right.
if (this._isRTL)
this._scrollbox.scrollLeft = this._scrollbox.scrollWidth;
// Update the chevron on a timer. This will avoid repeated work when
// lot of changes happen in a small timeframe.
if (this._updateChevronTimer)
this._updateChevronTimer.cancel();
this._updateChevronTimer = this._setTimer(100);
]]></body>
</method>
<method name="_updateChevronTimerCallback">
<body><![CDATA[
var scrollRect = this._scrollbox.getBoundingClientRect();
var childOverflowed = false;
for (let i = 0; i < this.childNodes.length; i++) {
let child = this.childNodes[i];
// Once a child overflows, all the next ones will.
if (!childOverflowed) {
let childRect = child.getBoundingClientRect();
childOverflowed = this._isRTL ? (childRect.left < scrollRect.left)
: (childRect.right > scrollRect.right);
}
child.style.visibility = childOverflowed ? "hidden" : "visible";
}
// We rebuild the chevron on popupShowing, so if it is open
// we must update it.
if (this._chevron.open)
this._updateChevronPopupNodesVisibility();
]]></body>
</method>
<!-- nsIPlacesView -->
<property name="place">
<getter><![CDATA[
return this.getAttribute("place");
]]></getter>
<setter><![CDATA[
this.setAttribute("place", val);
var history = PlacesUtils.history;
var queries = { }, options = { };
history.queryStringToQueries(val, queries, { }, options);
if (!queries.value.length)
queries.value = [history.getNewQuery()];
try {
var result =
history.executeQueries(queries.value, queries.value.length,
options.value);
result.viewer = this._viewer;
}
catch(ex) {
// Invalid query, or had no results.
// This is valid, eg: user deletes their bookmarks toolbar folder.
}
return val;
]]></setter>
</property>
<!-- nsIPlacesView -->
<property name="hasSelection">
<getter><![CDATA[
return this.selectedNode != null;
]]></getter>
</property>
<!-- nsIPlacesView -->
<method name="getSelectionNodes">
<body><![CDATA[
var selectedNode = this.selectedNode;
return selectedNode ? [selectedNode] : [];
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="getRemovableSelectionRanges">
<body><![CDATA[
// On static content the current selectedNode would be the selection's
// parent node. We don't want to allow removing a node when the
// selection is not explicit.
if (document.popupNode &&
(document.popupNode == "menupopup" || !document.popupNode.node))
return [];
return [this.getSelectionNodes()];
]]></body>
</method>
<!-- nsIPlacesView -->
<method name="getDraggableSelection">
<body><![CDATA[
return [this._draggedNode];
]]></body>
</method>
<!-- nsIPlacesView -->
<property name="selectedNode">
<getter><![CDATA[
if (this._contextMenuShown) {
var popupNode = document.popupNode;
if (popupNode == this)
return this.getResultNode();
return popupNode.node || popupNode.parentNode._resultNode || null;
}
return null;
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="insertionPoint">
<getter><![CDATA[
// By default, the insertion point is at the top level, at the end.
var index = PlacesUtils.bookmarks.DEFAULT_INDEX;
var container = this._result.root;
var orientation = Ci.nsITreeView.DROP_BEFORE;
var isTag = false;
var selectedNode = this.selectedNode;
if (selectedNode) {
var popupNode = document.popupNode;
if (!popupNode.node) {
// If a static menuitem is selected the insertion point
// is inside the folder, at the end.
container = selectedNode;
orientation = Ci.nsITreeView.DROP_ON;
}
else {
// In all other cases the insertion point is before that node.
container = selectedNode.parent;
index = PlacesUtils.getIndexOfNode(selectedNode);
isTag = PlacesUtils.nodeIsTagQuery(selectedNode.parent);
}
}
if (PlacesControllerDragHelper.disallowInsertion(container))
return null;
return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
index, orientation, isTag);
]]></getter>
</property>
<!-- nsIPlacesView -->
<method name="selectAll">
<body><![CDATA[
// Nothing
]]></body>
</method>
<method name="selectItems">
<body><![CDATA[
// Nothing
]]></body>
</method>
<!-- nsINavHistoryResultViewer -->
<field name="_viewer"><![CDATA[({
_self: this,
_getPopupForContainer:
function PMV__getPopupForContainer(aNode) {
if (this._self._resultNode == aNode)
return this._self;
for (let 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) {
// some methods (e.g. getURLsFromContainer) temporarily null out the
// viewer when they do temporary changes to the view, this does _not_
// call setResult(null), but then, we're called again with the result
// object which is already set for this viewer. At that point,
// we should do nothing.
if (this._self._result != val) {
if (this._self._result)
this._self._result.root.containerOpen = false;
this._self._containerNodesMap = [];
this._self._result = val;
if (val) // this calls _rebuild through invalidateContainer
val.root.containerOpen = true;
}
return val;
},
itemInserted: function TV_V_itemInserted(aParentNode, aNode, aIndex) {
// don't insert new items into the toolbar
// if the parent is not the root
if (aParentNode == this._self.getResultNode()) {
var children = this._self.childNodes;
this._self.insertNewItem(aNode,
aIndex < children.length ? children[aIndex] : null);
this._self.updateChevron();
}
else {
var popup = this._getPopupForContainer(aParentNode);
if (!popup._built)
return;
var before = popup.childNodes[aIndex] || null;
this._self.insertNewItemToPopup(aNode, popup, before);
if (popup._emptyMenuItem)
popup._emptyMenuItem.hidden = true;
}
},
itemRemoved: function TV_V_itemRemoved(aParentNode, aNode, aIndex) {
if (aParentNode == this._self.getResultNode()) {
var children = this._self.childNodes;
for (let i = 0; i < children.length; i++) {
if (children[i].node == aNode) {
this._self.removeItem(children[i]);
this._self.updateChevron();
return;
}
}
}
else {
var popup = this._getPopupForContainer(aParentNode);
if (!popup._built)
return;
var children = popup.childNodes;
for (let i = popup._startMarker + 1; 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);
}
if (popup._endMarker != -1)
popup._endMarker--;
return;
}
}
}
},
itemMoved:
function TV_V_itemMoved(aItem, aOldParent, aOldIndex, aNewParent,
aNewIndex) {
// This cannot actually happen yet (see IDL)
if (aNewParent != aOldParent)
return;
if (aNewParent == this._self.getResultNode()) {
var children = this._self.childNodes;
for (let i = 0; i < children.length; i++) {
var button = children[i];
if (button.node == aItem) {
this._self.removeChild(button);
this._self.insertBefore(button, children[aNewIndex]);
// If the chevron popup is open, keep it in sync.
if (this._self._chevron.open) {
var chevronPopup = this._self._chevronPopup;
var menuitem = chevronPopup.childNodes[i];
chevronPopup.removeChild(menuitem);
chevronPopup.insertBefore(menuitem,
chevronPopup.childNodes[aNewIndex]);
}
this._self.updateChevron();
return;
}
}
}
else {
var popup = this._getPopupForContainer(aNewParent);
var children = popup.childNodes;
for (let i = popup._startMarker + 1; i < children.length; i++) {
var menuItem = children[i];
if (menuItem.node == aItem) {
popup.removeChild(menuItem);
popup.insertBefore(menuItem, children[aNewIndex]);
return;
}
}
}
},
itemChanged: function TV_V_itemChanged(aNode) {
// this check can be removed once we fix bug #382397
var parentNode = aNode.parent;
if (!parentNode)
return;
if (PlacesUtils.nodeIsSeparator(aNode)) {
// nothing to do when a separator changes
return;
}
var element;
var onToolbar = false;
if (parentNode == this._self.getResultNode()) {
onToolbar = true;
var children = this._self.childNodes;
for (let i = 0; i < children.length; i++) {
if (children[i].node == aNode) {
element = children[i];
break;
}
}
// Don't replace title on toolbarbuttons
var title = aNode.title;
}
else {
var popup = this._getPopupForContainer(parentNode);
if (!popup._built)
return;
var children = popup.childNodes;
for (let i = popup._startMarker + 1; i < children.length; i++) {
if (children[i].node == aNode) {
element = children[i];
break;
}
}
var title = PlacesUIUtils.getBestTitle(aNode);
}
var iconURI = aNode.icon;
if (iconURI) {
var spec = iconURI.spec;
if (element.getAttribute("image") != spec)
element.setAttribute("image", spec);
}
else
element.removeAttribute("image");
if (element.getAttribute("label") != title) {
element.setAttribute("label", title);
if (onToolbar)
this._self.updateChevron();
}
if (PlacesUtils.nodeIsLivemarkContainer(aNode)) {
if (!element.hasAttribute("livemark"))
element.setAttribute("livemark", "true");
// If this is a livemark container check if the status menuitem has
// to be added or removed.
PlacesUIUtils.ensureLivemarkStatusMenuItem(element.firstChild);
}
else if (PlacesUtils.nodeIsURI(aNode)) {
element.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aNode.uri));
}
},
itemReplaced:
function TV_V_itemReplaced(aParentNode, aOldNode, aNewNode, aIndex) {
if (aParentNode == this._self.getResultNode()) {
var children = this._self.childNodes;
for (let 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, next);
this._self.updateChevron();
return;
}
}
}
},
containerOpened: function TV_V_containerOpened(aNode) {
this.invalidateContainer(aNode);
},
containerClosed: function TV_V_containerClosed(aNode) {
this.invalidateContainer(aNode);
},
invalidateContainer: function TV_V_invalidateContainer(aContainer) {
if (aContainer == this._self.getResultNode()) {
this._self._containerNodesMap.splice(0);
this._self._rebuild();
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 (let 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;
// if the menupopup is open we should live-update it
if (popupToRebuild.parentNode.open)
this._self._rebuildPopup(popupToRebuild);
}
},
invalidateAll: function TV_V_invalidateAll() {
this._self._containerNodesMap.splice(0);
this._self._rebuild();
},
sortingChanged: function TV_V_sortingChanged(aSortingMode) {
}
})]]></field>
<property name="selType" onget="return 'single';"/>
<method name="buildContextMenu">
<parameter name="aPopup"/>
<body><![CDATA[
this._contextMenuShown = true;
window.updateCommands("places");
return this.controller.buildContextMenu(aPopup);
]]></body>
</method>
<method name="destroyContextMenu">
<parameter name="aPopup"/>
<body><![CDATA[
this._contextMenuShown = false;
if (window.content)
window.content.focus();
]]></body>
</method>
<method name="_showEmptyMenuItem">
<parameter name="aPopup"/>
<body><![CDATA[
if (aPopup._emptyMenuItem) {
aPopup._emptyMenuItem.hidden = false;
return;
}
var label = PlacesUIUtils.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="insertNewItemToPopup">
<parameter name="aChild"/>
<parameter name="aParentPopup"/>
<parameter name="aBefore"/>
<body><![CDATA[
var element =
PlacesUIUtils.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) {
let lastNode = aParentPopup.childNodes[aParentPopup._endMarker];
aParentPopup.insertBefore(element, lastNode);
}
else
aParentPopup.appendChild(element);
}
if (aParentPopup._endMarker != -1)
aParentPopup._endMarker++;
]]></body>
</method>
<method name="_containerPopupShowing">
<parameter name="aPopup"/>
<body><![CDATA[
if (!aPopup._built)
this._rebuildPopup(aPopup);
]]></body>
</method>
<method name="_rebuildPopup">
<parameter name="aPopup"/>
<body><![CDATA[
PlacesUIUtils.cleanPlacesPopup(aPopup);
// If this is a livemark container check if the status menuitem has
// to be added or removed.
if (PlacesUtils.nodeIsLivemarkContainer(aPopup._resultNode))
PlacesUIUtils.ensureLivemarkStatusMenuItem(aPopup);
var resultNode = aPopup._resultNode;
if (!resultNode.containerOpen)
resultNode.containerOpen = true;
var cc = resultNode.childCount;
if (cc > 0) {
if (aPopup._emptyMenuItem)
aPopup._emptyMenuItem.hidden = true;
for (let i = 0; i < cc; ++i) {
var child = resultNode.getChild(i);
this.insertNewItemToPopup(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>
<field name="_overFolder"><![CDATA[
(
// Menu buttons should be opened when the mouse drags over them, and
// closed when the mouse drags off. This object manages opening and
// closing of folders when the mouse hovers.
{ node: null, openTimer: null, hoverTime: 350, closeTimer: null }
);
]]></field>
<method name="_clearOverFolder">
<body><![CDATA[
// The mouse is no longer dragging over the stored menubutton.
// Close the menubutton, clear out drag styles, and clear all
// timers for opening/closing it.
if (this._overFolder.node && this._overFolder.node.lastChild) {
if (!this._overFolder.node.lastChild.hasAttribute("dragover")) {
this._overFolder.node.lastChild.hidePopup();
}
this._overFolder.node.removeAttribute("dragover");
this._overFolder.node = null;
}
if (this._overFolder.openTimer) {
this._overFolder.openTimer.cancel();
this._overFolder.openTimer = null;
}
if (this._overFolder.closeTimer) {
this._overFolder.closeTimer.cancel();
this._overFolder.closeTimer = null;
}
]]></body>
</method>
<method name="_getDropPoint">
<parameter name="aEvent"/>
<body><![CDATA[
// This function returns information about where to drop when
// dragging over the toolbar.
// The returned object has 3 properties:
// - ip: the insertion point for the bookmarks service.
// - beforeIndex: child index to drop before, for the drop indicator.
// - folderNode: the folder to drop into, if applicable.
var result = this.getResult();
if (!PlacesUtils.nodeIsFolder(result.root))
return null;
var dropPoint = { ip: null, beforeIndex: null, folderNode: null };
var xulNode = aEvent.target;
if (xulNode.node) {
let nodeRect = xulNode.getBoundingClientRect();
let nodeIndex = Array.indexOf(this.childNodes, xulNode);
if (PlacesUtils.nodeIsFolder(xulNode.node) &&
!PlacesUtils.nodeIsReadOnly(xulNode.node)) {
// This is a folder.
// If we are in the middle of it, drop inside it.
// Otherwise, drop before it, with regards to RTL mode.
let threshold = nodeRect.width * 0.25;
if (this._isRTL ? (aEvent.clientX > nodeRect.right - threshold)
: (aEvent.clientX < nodeRect.left + threshold)) {
// Drop before this folder.
dropPoint.ip =
new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
nodeIndex, Ci.nsITreeView.DROP_BEFORE);
dropPoint.beforeIndex = nodeIndex;
}
else if (this._isRTL ? (aEvent.clientX > nodeRect.left + threshold)
: (aEvent.clientX < nodeRect.right - threshold)) {
// Drop inside this folder.
dropPoint.ip =
new InsertionPoint(PlacesUtils.getConcreteItemId(xulNode.node),
-1, Ci.nsITreeView.DROP_ON,
PlacesUtils.nodeIsTagQuery(xulNode.node));
dropPoint.beforeIndex = nodeIndex;
dropPoint.folderNode = xulNode;
}
else {
// Drop after this folder.
let beforeIndex =
(nodeIndex == this.childNodes.length - 1) ? -1 : nodeIndex + 1;
dropPoint.ip =
new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
beforeIndex, Ci.nsITreeView.DROP_BEFORE);
dropPoint.beforeIndex = beforeIndex;
}
}
else {
// This is a non-folder node or a read-only folder.
// Drop before it with regards to RTL mode.
let threshold = nodeRect.width * 0.5;
if (this._isRTL ? (aEvent.clientX > nodeRect.left + threshold)
: (aEvent.clientX < nodeRect.left + threshold)) {
// Drop before this bookmark.
dropPoint.ip =
new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
nodeIndex, Ci.nsITreeView.DROP_BEFORE);
dropPoint.beforeIndex = nodeIndex;
}
else {
// Drop after this bookmark.
let beforeIndex =
nodeIndex == this.childNodes.length - 1 ? -1 : nodeIndex + 1;
dropPoint.ip =
new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
beforeIndex, Ci.nsITreeView.DROP_BEFORE);
dropPoint.beforeIndex = beforeIndex;
}
}
}
else {
// We are most likely dragging on the empty area of the
// toolbar, we should drop after the last node.
dropPoint.ip =
new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
-1, Ci.nsITreeView.DROP_BEFORE);
dropPoint.beforeIndex = -1;
}
return dropPoint;
]]></body>
</method>
<method name="_setTimer">
<parameter name="aTime"/>
<body><![CDATA[
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
return timer;
]]></body>
</method>
<!-- nsITimerCallback -->
<method name="notify">
<parameter name="aTimer"/>
<body><![CDATA[
// Function to process all timer notifications.
if (aTimer == this._updateChevronTimer) {
this._updateChevronTimer = null;
this._updateChevronTimerCallback();
}
// * Timer to turn off indicator bar.
else if (aTimer == this._ibTimer) {
this._dropIndicator.collapsed = true;
this._ibTimer = null;
}
// * Timer to open a menubutton that's being dragged over.
else if (aTimer == this._overFolder.openTimer) {
// Set the autoopen attribute on the folder's menupopup so that
// the menu will automatically close when the mouse drags off of it.
this._overFolder.node.lastChild.setAttribute("autoopened", "true");
this._overFolder.node.open = true;
this._overFolder.openTimer = null;
}
// * Timer to close a menubutton that's been dragged off of.
else if (aTimer == this._overFolder.closeTimer) {
// Close the menubutton if we are not dragging over it or one of
// its children. The autoopened attribute will let the menu know to
// close later if the menu is still being dragged over.
var currentNode = PlacesControllerDragHelper.currentDropTarget;
var inHierarchy = false;
while (currentNode) {
if (currentNode == this) {
inHierarchy = true;
break;
}
currentNode = currentNode.parentNode;
}
// The _clearOverFolder() function will close the menu for _overFolder.node.
// So null it out if we don't want to close it.
if (inHierarchy)
this._overFolder.node = null;
// Clear out the folder and all associated timers.
this._clearOverFolder();
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="mouseover"><![CDATA[
var button = event.target;
if (button.parentNode == this && button.node &&
PlacesUtils.nodeIsURI(button.node))
window.XULBrowserWindow.setOverLink(event.target.node.uri, null);
]]></handler>
<handler event="mouseout"><![CDATA[
window.XULBrowserWindow.setOverLink("", null);
]]></handler>
<handler event="dragstart"><![CDATA[
// sub menus have their own d&d handlers
var draggedDOMNode = event.target;
if (draggedDOMNode.parentNode != this || !draggedDOMNode.node)
return;
if (draggedDOMNode.localName == "toolbarbutton" &&
draggedDOMNode.getAttribute("type") == "menu") {
// If the drag gesture on a container is toward down we open instead
// of dragging.
if (this._mouseDownTimer) {
this._mouseDownTimer.cancel();
this._mouseDownTimer = null;
}
var translateY = this._cachedMouseMoveEvent.clientY - event.clientY;
var translateX = this._cachedMouseMoveEvent.clientX - event.clientX;
if ((translateY) >= Math.abs(translateX/2)) {
// Don't start the drag
event.preventDefault();
// Open the menu
draggedDOMNode.open = true;
return;
}
// if the menu is open, close it
if (draggedDOMNode.open) {
draggedDOMNode.firstChild.hidePopup();
draggedDOMNode.open = false;
}
}
// activate the view and cache the dragged node
this._draggedNode = draggedDOMNode.node;
this.focus();
this._controller.setDataTransfer(event);
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
// Cache the dataTransfer
var dt = PlacesControllerDragHelper.currentDataTransfer =
event.dataTransfer;
var ip = this.insertionPoint;
if (!ip || !PlacesControllerDragHelper.canDrop(ip)) {
this._dropIndicator.collapsed = true;
PlacesControllerDragHelper.currentDataTransfer = null;
return;
}
PlacesControllerDragHelper.currentDropTarget = event.target;
var dropPoint = this._getDropPoint(event);
if (this._ibTimer) {
this._ibTimer.cancel();
this._ibTimer = null;
}
if (dropPoint.folderNode ||
event.originalTarget == this._chevron) {
// Dropping over a menubutton or chevron button
// set styles and timer to open relative menupopup
var overNode = dropPoint.folderNode || this._chevron;
if (this._overFolder.node != overNode) {
this._clearOverFolder();
this._overFolder.node = overNode;
this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
}
if (!this._overFolder.node.hasAttribute("dragover"))
this._overFolder.node.setAttribute("dragover", "true");
this._dropIndicator.collapsed = true;
}
else {
// Dragging over a normal toolbarbutton,
// show indicator bar and move it to the appropriate drop point.
let ind = this._dropIndicator;
let halfInd = ind.clientWidth / 2;
let translateX;
if (this._isRTL) {
halfInd = Math.ceil(halfInd);
translateX = 0 - this._scrollbox.getBoundingClientRect().right -
halfInd;
if (dropPoint.beforeIndex == -1)
translateX += this.lastChild.getBoundingClientRect().left;
else if (this.childNodes.length) {
translateX += this.childNodes[dropPoint.beforeIndex]
.getBoundingClientRect().right;
}
}
else {
halfInd = Math.floor(halfInd);
translateX = 0 - this._scrollbox.getBoundingClientRect().left +
halfInd;
if (dropPoint.beforeIndex == -1)
translateX += this.lastChild.getBoundingClientRect().right;
else if (this.childNodes.length) {
translateX += this.childNodes[dropPoint.beforeIndex]
.getBoundingClientRect().left;
}
}
ind.style.MozTransform = "translate(" + Math.round(translateX) + "px)";
ind.style.MozMarginStart = (-ind.clientWidth) + "px";
ind.collapsed = false;
// Clear out old folder information
this._clearOverFolder();
}
dt.effectAllowed = "all";
event.preventDefault();
event.stopPropagation();
]]></handler>
<handler event="drop"><![CDATA[
PlacesControllerDragHelper.currentDataTransfer = event.dataTransfer;
PlacesControllerDragHelper.currentDropTarget = event.target;
var ip = this.insertionPoint;
if (!ip || !PlacesControllerDragHelper.canDrop(ip))
return;
var dropPoint = this._getDropPoint(event);
if (!dropPoint)
return;
PlacesControllerDragHelper.onDrop(dropPoint.ip);
event.stopPropagation();
]]></handler>
<handler event="dragleave"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = null;
PlacesControllerDragHelper.currentDataTransfer = null;
// Set timer to turn off indicator bar (if we turn it off
// here, dragenter might be called immediately after, creating
// flicker.)
if (this._ibTimer)
this._ibTimer.cancel();
this._ibTimer = this._setTimer(10);
// If we hovered over a folder, close it now
if (this._overFolder.node)
this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
]]></handler>
<handler event="dragend"><![CDATA[
this._draggedNode = null;
]]></handler>
<handler event="popupshowing" phase="capturing"><![CDATA[
if (!this._allowPopupShowing) {
this._allowPopupShowing = true;
event.preventDefault();
return;
}
var popup = event.originalTarget;
// Avoid handling popupshowing of inner views
if (popup._resultNode && PlacesUIUtils.getViewForNode(popup) == this)
this._containerPopupShowing(popup);
var parent = popup.parentNode;
if (parent.localName == "toolbarbutton")
this._openedMenuButton = parent;
]]></handler>
<handler event="popuphidden"><![CDATA[
var popup = event.originalTarget;
// Avoid handling popuphidden of inner views
if (popup._resultNode && PlacesUIUtils.getViewForNode(popup) == this) {
// 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;
}
var parent = popup.parentNode;
if (parent.localName == "toolbarbutton") {
this._openedMenuButton = null;
// Clear the dragover attribute if present, if we are dragging into a
// folder in the hierachy of current opened popup we don't clear
// this attribute on clearOverFolder. See Notify for closeTimer.
if (parent.hasAttribute("dragover"))
parent.removeAttribute("dragover");
}
]]></handler>
#ifdef XP_UNIX
#ifndef XP_MACOSX
<handler event="mousedown"><![CDATA[
var target = event.target;
if (event.button == 0 &&
target.localName == "toolbarbutton" &&
target.getAttribute("type") == "menu") {
this._allowPopupShowing = false;
// On Linux we can open the popup only after a delay.
// Indeed as soon as the menupopup opens we are unable to start a
// drag event. See bug 500081 for details.
this._mouseDownTimer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
var callback = {
_self: this,
_target: target,
notify: function(timer) {
this._target.open = true;
this._self._mouseDownTimer = null;
}
};
this._mouseDownTimer.initWithCallback(callback, 300,
Ci.nsITimer.TYPE_ONE_SHOT);
}
]]></handler>
#endif
#endif
<handler event="mouseup"><![CDATA[
if (event.button != 0)
return;
if (this._mouseDownTimer) {
// On a click (down/up) we should open the menu popup
this._mouseDownTimer.cancel();
this._mouseDownTimer = null;
event.target.open = true;
}
]]></handler>
<handler event="mousemove"><![CDATA[
// Used in dragStart to prevent dragging folders when dragging down
this._cachedMouseMoveEvent = event;
if (this._openedMenuButton == null ||
PlacesControllerDragHelper.getSession())
return;
var target = event.originalTarget;
if (this._openedMenuButton != target &&
target.localName == "toolbarbutton" &&
target.type == "menu") {
this._openedMenuButton.open = false;
target.open = true;
}
]]></handler>
</handlers>
</binding>
</bindings>