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

628 lines
24 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="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-popup-base"
extends="chrome://global/content/bindings/popup.xml#popup">
<content>
<xul:hbox flex="1">
<xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
<xul:image class="menupopup-drop-indicator" mousethrough="always"/>
</xul:vbox>
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
smoothscroll="false">
<children/>
</xul:arrowscrollbox>
</xul:hbox>
</content>
<implementation>
<field name="_indicatorBar">
document.getAnonymousElementByAttribute(this, "class",
"menupopup-drop-indicator-bar");
</field>
<field name="_scrollBox">
document.getAnonymousElementByAttribute(this, "class",
"popup-internal-box");
</field>
<!-- This is the view that manage the popup -->
<field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>
<!-- Check if we should hide the drop indicator for the target -->
<method name="_hideDropIndicator">
<parameter name="aEvent"/>
<body><![CDATA[
let target = aEvent.target;
// Don't draw the drop indicator outside of markers.
// The markers are hidden, since otherwise sometimes popups acquire
// scrollboxes on OS X, so we can't use them directly.
let firstChildTop = this._startMarker.nextSibling.boxObject.y;
let lastChildBottom = this._endMarker.previousSibling.boxObject.y +
this._endMarker.previousSibling.boxObject.height;
let betweenMarkers = target.boxObject.y >= firstChildTop ||
target.boxObject.y <= lastChildBottom;
// Hide the dropmarker if current node is not a Places node.
return !(target && target._placesNode && betweenMarkers);
]]></body>
</method>
<!-- This function returns information about where to drop when
dragging over this popup insertion point -->
<method name="_getDropPoint">
<parameter name="aEvent"/>
<body><![CDATA[
// Can't drop if the menu isn't a folder
let resultNode = this._placesNode;
if (!PlacesUtils.nodeIsFolder(resultNode) ||
PlacesControllerDragHelper.disallowInsertion(resultNode)) {
return null;
}
var dropPoint = { ip: null, folderElt: null };
// The element we are dragging over
let elt = aEvent.target;
if (elt.localName == "menupopup")
elt = elt.parentNode;
// Calculate positions taking care of arrowscrollbox
let scrollbox = this._scrollBox;
let eventY = aEvent.layerY + (scrollbox.boxObject.y - this.boxObject.y);
let scrollboxOffset = scrollbox.scrollBoxObject.y -
(scrollbox.boxObject.y - this.boxObject.y);
let eltY = elt.boxObject.y - scrollboxOffset;
let eltHeight = elt.boxObject.height;
if (!elt._placesNode) {
// If we are dragging over a non places node drop at the end.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_ON);
// We can set folderElt if we are dropping over a static menu that
// has an internal placespopup.
let isMenu = elt.localName == "menu" ||
(elt.localName == "toolbarbutton" &&
elt.getAttribute("type") == "menu");
if (isMenu && elt.lastChild &&
elt.lastChild.hasAttribute("placespopup"))
dropPoint.folderElt = elt;
return dropPoint;
}
let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
elt._placesNode.title : null;
if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
!PlacesUIUtils.isContentsReadOnly(elt._placesNode)) ||
PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
// This is a folder or a tag container.
if (eventY - eltY < eltHeight * 0.20) {
// If mouse is in the top part of the element, drop above folder.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_BEFORE,
tagName,
elt._placesNode.itemId);
return dropPoint;
}
else if (eventY - eltY < eltHeight * 0.80) {
// If mouse is in the middle of the element, drop inside folder.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(elt._placesNode),
-1,
Ci.nsITreeView.DROP_ON,
tagName);
dropPoint.folderElt = elt;
return dropPoint;
}
}
else if (eventY - eltY <= eltHeight / 2) {
// This is a non-folder node or a readonly folder.
// If the mouse is above the middle, drop above this item.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_BEFORE,
tagName,
elt._placesNode.itemId);
return dropPoint;
}
// Drop below the item.
dropPoint.ip = new InsertionPoint(
PlacesUtils.getConcreteItemId(resultNode),
-1,
Ci.nsITreeView.DROP_AFTER,
tagName,
elt._placesNode.itemId);
return dropPoint;
]]></body>
</method>
<!-- Sub-menus should be opened when the mouse drags over them, and closed
when the mouse drags off. The overFolder object manages opening and
closing of folders when the mouse hovers. -->
<field name="_overFolder"><![CDATA[({
_self: this,
_folder: {elt: null,
openTimer: null,
hoverTime: 350,
closeTimer: null},
_closeMenuTimer: null,
get elt() {
return this._folder.elt;
},
set elt(val) {
return this._folder.elt = val;
},
get openTimer() {
return this._folder.openTimer;
},
set openTimer(val) {
return this._folder.openTimer = val;
},
get hoverTime() {
return this._folder.hoverTime;
},
set hoverTime(val) {
return this._folder.hoverTime = val;
},
get closeTimer() {
return this._folder.closeTimer;
},
set closeTimer(val) {
return this._folder.closeTimer = val;
},
get closeMenuTimer() {
return this._closeMenuTimer;
},
set closeMenuTimer(val) {
return this._closeMenuTimer = val;
},
setTimer: function OF__setTimer(aTime) {
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
return timer;
},
notify: function OF__notify(aTimer) {
// Function to process all timer notifications.
if (aTimer == this._folder.openTimer) {
// Timer to open a submenu that's being dragged over.
this._folder.elt.lastChild.setAttribute("autoopened", "true");
this._folder.elt.lastChild.showPopup(this._folder.elt);
this._folder.openTimer = null;
}
else if (aTimer == this._folder.closeTimer) {
// Timer to close a submenu that's been dragged off of.
// Only close the submenu if the mouse isn't being dragged over any
// of its child menus.
var draggingOverChild = PlacesControllerDragHelper
.draggingOverChildNode(this._folder.elt);
if (draggingOverChild)
this._folder.elt = null;
this.clear();
// Close any parent folders which aren't being dragged over.
// (This is necessary because of the above code that keeps a folder
// open while its children are being dragged over.)
if (!draggingOverChild)
this.closeParentMenus();
}
else if (aTimer == this.closeMenuTimer) {
// Timer to close this menu after the drag exit.
var popup = this._self;
// if we are no more dragging we can leave the menu open to allow
// for better D&D bookmark organization
if (PlacesControllerDragHelper.getSession() &&
!PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) {
popup.hidePopup();
// Close any parent menus that aren't being dragged over;
// otherwise they'll stay open because they couldn't close
// while this menu was being dragged over.
this.closeParentMenus();
}
this._closeMenuTimer = null;
}
},
// Helper function to close all parent menus of this menu,
// as long as none of the parent's children are currently being
// dragged over.
closeParentMenus: function OF__closeParentMenus() {
var popup = this._self;
var parent = popup.parentNode;
while (parent) {
if (parent.localName == "menupopup" && parent._placesNode) {
if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode))
break;
parent.hidePopup();
}
parent = parent.parentNode;
}
},
// 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.
clear: function OF__clear() {
if (this._folder.elt && this._folder.elt.lastChild) {
if (!this._folder.elt.lastChild.hasAttribute("dragover"))
this._folder.elt.lastChild.hidePopup();
// remove menuactive style
this._folder.elt.removeAttribute("_moz-menuactive");
this._folder.elt = null;
}
if (this._folder.openTimer) {
this._folder.openTimer.cancel();
this._folder.openTimer = null;
}
if (this._folder.closeTimer) {
this._folder.closeTimer.cancel();
this._folder.closeTimer = null;
}
}
})]]></field>
<method name="_cleanupDragDetails">
<body><![CDATA[
// Called on dragend and drop.
PlacesControllerDragHelper.currentDropTarget = null;
this._rootView._draggedElt = null;
this.removeAttribute("dragover");
this.removeAttribute("dragstart");
this._indicatorBar.hidden = true;
]]></body>
</method>
</implementation>
<handlers>
<handler event="DOMMenuItemActive"><![CDATA[
let elt = event.target;
if (elt.parentNode != this)
return;
#ifdef XP_MACOSX
// XXX: The following check is a temporary hack until bug 420033 is
// resolved.
let parentElt = elt.parent;
while (parentElt) {
if (parentElt.id == "bookmarksMenuPopup" ||
parentElt.id == "goPopup")
return;
parentElt = parentElt.parentNode;
}
#endif
if (window.XULBrowserWindow) {
let elt = event.target;
let placesNode = elt._placesNode;
var linkURI;
if (placesNode && PlacesUtils.nodeIsURI(placesNode))
linkURI = placesNode.uri;
else if (elt.hasAttribute("targetURI"))
linkURI = elt.getAttribute("targetURI");
if (linkURI)
window.XULBrowserWindow.setOverLink(linkURI, null);
}
]]></handler>
<handler event="DOMMenuItemInactive"><![CDATA[
let elt = event.target;
if (elt.parentNode != this)
return;
if (window.XULBrowserWindow)
window.XULBrowserWindow.setOverLink("", null);
]]></handler>
<handler event="dragstart"><![CDATA[
if (!event.target._placesNode)
return;
let draggedElt = event.target._placesNode;
// Force a copy action if parent node is a query or we are dragging a
// not-removable node.
if (!PlacesControllerDragHelper.canMoveNode(draggedElt))
event.dataTransfer.effectAllowed = "copyLink";
// Activate the view and cache the dragged element.
this._rootView._draggedElt = draggedElt;
this._rootView.controller.setDataTransfer(event);
this.setAttribute("dragstart", "true");
event.stopPropagation();
]]></handler>
<handler event="drop"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = event.target;
let dropPoint = this._getDropPoint(event);
if (dropPoint && dropPoint.ip) {
PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer)
.then(null, Components.utils.reportError);
event.preventDefault();
}
this._cleanupDragDetails();
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = event.target;
let dt = event.dataTransfer;
let dropPoint = this._getDropPoint(event);
if (!dropPoint || !dropPoint.ip ||
!PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
this._indicatorBar.hidden = true;
event.stopPropagation();
return;
}
// Mark this popup as being dragged over.
this.setAttribute("dragover", "true");
if (dropPoint.folderElt) {
// We are dragging over a folder.
// _overFolder should take the care of opening it on a timer.
if (this._overFolder.elt &&
this._overFolder.elt != dropPoint.folderElt) {
// We are dragging over a new folder, let's clear old values
this._overFolder.clear();
}
if (!this._overFolder.elt) {
this._overFolder.elt = dropPoint.folderElt;
// Create the timer to open this folder.
this._overFolder.openTimer = this._overFolder
.setTimer(this._overFolder.hoverTime);
}
// Since we are dropping into a folder set the corresponding style.
dropPoint.folderElt.setAttribute("_moz-menuactive", true);
}
else {
// We are not dragging over a folder.
// Clear out old _overFolder information.
this._overFolder.clear();
}
// Autoscroll the popup strip if we drag over the scroll buttons.
let anonid = event.originalTarget.getAttribute('anonid');
let scrollDir = anonid == "scrollbutton-up" ? -1 :
anonid == "scrollbutton-down" ? 1 : 0;
if (scrollDir != 0) {
this._scrollBox.scrollByIndex(scrollDir, false);
}
// Check if we should hide the drop indicator for this target.
if (dropPoint.folderElt || this._hideDropIndicator(event)) {
this._indicatorBar.hidden = true;
event.preventDefault();
event.stopPropagation();
return;
}
// We should display the drop indicator relative to the arrowscrollbox.
let sbo = this._scrollBox.scrollBoxObject;
let newMarginTop = 0;
if (scrollDir == 0) {
let elt = this.firstChild;
while (elt && event.screenY > elt.boxObject.screenY +
elt.boxObject.height / 2)
elt = elt.nextSibling;
newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY :
sbo.height;
}
else if (scrollDir == 1)
newMarginTop = sbo.height;
// Set the new marginTop based on arrowscrollbox.
newMarginTop += sbo.y - this._scrollBox.boxObject.y;
this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px";
this._indicatorBar.hidden = false;
event.preventDefault();
event.stopPropagation();
]]></handler>
<handler event="dragexit"><![CDATA[
PlacesControllerDragHelper.currentDropTarget = null;
this.removeAttribute("dragover");
// If we have not moved to a valid new target clear the drop indicator
// this happens when moving out of the popup.
let target = event.relatedTarget;
if (!target || !this.contains(target))
this._indicatorBar.hidden = true;
// Close any folder being hovered over
if (this._overFolder.elt) {
this._overFolder.closeTimer = this._overFolder
.setTimer(this._overFolder.hoverTime);
}
// The autoopened attribute is set when this folder was automatically
// opened after the user dragged over it. If this attribute is set,
// auto-close the folder on drag exit.
// We should also try to close this popup if the drag has started
// from here, the timer will check if we are dragging over a child.
if (this.hasAttribute("autoopened") ||
this.hasAttribute("dragstart")) {
this._overFolder.closeMenuTimer = this._overFolder
.setTimer(this._overFolder.hoverTime);
}
event.stopPropagation();
]]></handler>
<handler event="dragend"><![CDATA[
this._cleanupDragDetails();
]]></handler>
</handlers>
</binding>
<!-- Most of this is copied from the arrowpanel binding in popup.xml -->
<binding id="places-popup-arrow"
extends="chrome://browser/content/places/menu.xml#places-popup-base">
<content flip="both" side="top" position="bottomcenter topright">
<xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
xbl:inherits="side,panelopen">
<xul:box anonid="arrowbox" class="panel-arrowbox">
<xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
</xul:box>
<xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
<xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
<xul:image class="menupopup-drop-indicator" mousethrough="always"/>
</xul:vbox>
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
smoothscroll="false">
<children/>
</xul:arrowscrollbox>
</xul:box>
</xul:vbox>
</content>
<implementation>
<constructor><![CDATA[
this.style.pointerEvents = 'none';
]]></constructor>
<method name="adjustArrowPosition">
<body><![CDATA[
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
var anchor = this.anchorNode;
if (!anchor) {
arrow.hidden = true;
return;
}
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
var position = this.alignmentPosition;
var offset = this.alignmentOffset;
this.setAttribute("arrowposition", position);
// if this panel has a "sliding" arrow, we may have previously set margins...
arrowbox.style.removeProperty("transform");
if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
container.orient = "horizontal";
arrowbox.orient = "vertical";
if (position.indexOf("_after") > 0) {
arrowbox.pack = "end";
} else {
arrowbox.pack = "start";
}
arrowbox.style.transform = "translate(0, " + -offset + "px)";
// The assigned side stays the same regardless of direction.
var isRTL = (window.getComputedStyle(this).direction == "rtl");
if (position.indexOf("start_") == 0) {
container.dir = "reverse";
this.setAttribute("side", isRTL ? "left" : "right");
}
else {
container.dir = "";
this.setAttribute("side", isRTL ? "right" : "left");
}
}
else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
container.orient = "";
arrowbox.orient = "";
if (position.indexOf("_end") > 0) {
arrowbox.pack = "end";
} else {
arrowbox.pack = "start";
}
arrowbox.style.transform = "translate(" + -offset + "px, 0)";
if (position.indexOf("before_") == 0) {
container.dir = "reverse";
this.setAttribute("side", "bottom");
}
else {
container.dir = "";
this.setAttribute("side", "top");
}
}
arrow.hidden = false;
]]></body>
</method>
</implementation>
<handlers>
<handler event="popupshowing" phase="target"><![CDATA[
this.adjustArrowPosition();
this.setAttribute("animate", "open");
]]></handler>
<handler event="popupshown" phase="target"><![CDATA[
this.setAttribute("panelopen", "true");
let disablePointerEvents;
if (!this.hasAttribute("disablepointereventsfortransition")) {
let container = document.getAnonymousElementByAttribute(this, "anonid", "container");
let cs = getComputedStyle(container);
let transitionProp = cs.transitionProperty;
let transitionTime = parseFloat(cs.transitionDuration);
disablePointerEvents = (transitionProp.contains("transform") ||
transitionProp == "all") &&
transitionTime > 0;
this.setAttribute("disablepointereventsfortransition", disablePointerEvents);
} else {
disablePointerEvents = this.getAttribute("disablepointereventsfortransition") == "true";
}
if (!disablePointerEvents) {
this.style.removeProperty("pointer-events");
}
]]></handler>
<handler event="transitionend"><![CDATA[
if (event.originalTarget.getAttribute("anonid") == "container" &&
event.propertyName == "transform") {
this.style.removeProperty("pointer-events");
}
]]></handler>
<handler event="popuphiding" phase="target"><![CDATA[
this.setAttribute("animate", "cancel");
]]></handler>
<handler event="popuphidden" phase="target"><![CDATA[
this.removeAttribute("panelopen");
if (this.getAttribute("disablepointereventsfortransition") == "true") {
this.style.pointerEvents = 'none';
}
this.removeAttribute("animate");
]]></handler>
</handlers>
</binding>
</bindings>