mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
489 lines
19 KiB
XML
489 lines
19 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 eventY = aEvent.layerY;
|
|
let scrollbox = this._scrollBox;
|
|
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;
|
|
}
|
|
else if ((PlacesUtils.nodeIsFolder(elt._placesNode) ||
|
|
PlacesUtils.nodeIsTagQuery(elt._placesNode)) &&
|
|
!PlacesUtils.nodeIsReadOnly(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,
|
|
PlacesUtils.nodeIsTagQuery(elt._placesNode),
|
|
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,
|
|
PlacesUtils.nodeIsTagQuery(elt._placesNode));
|
|
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,
|
|
PlacesUtils.nodeIsTagQuery(elt._placesNode),
|
|
elt._placesNode.itemId);
|
|
return dropPoint;
|
|
}
|
|
|
|
// Drop below the item.
|
|
dropPoint.ip = new InsertionPoint(
|
|
PlacesUtils.getConcreteItemId(resultNode),
|
|
-1,
|
|
Ci.nsITreeView.DROP_AFTER,
|
|
PlacesUtils.nodeIsTagQuery(elt._placesNode),
|
|
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);
|
|
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._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>
|
|
</bindings>
|