Fixed drag and drop support for toolbars and menus. Added lots more visual feedback when dragging and dropping.

bug=318052 r=beng
This commit is contained in:
annie.sullivan%gmail.com 2006-02-17 18:31:06 +00:00
parent 20ca11ad9e
commit 0edcf1db07
9 changed files with 726 additions and 85 deletions

View File

@ -1,6 +1,7 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://browser/content/places/browserShim.css"?>
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
<!DOCTYPE overlay SYSTEM "chrome://browser/locale/places/places.dtd">

View File

@ -1305,11 +1305,17 @@ var PlacesController = {
/**
* Get a TransferDataSet containing the content of the selection that can be
* dropped elsewhere.
* @param dragAction
* The action to happen when dragging, i.e. copy
* @returns A TransferDataSet object that can be dragged and dropped
* elsewhere.
*/
getTransferData: function PC_getTransferData() {
var nodes = this._activeView.getCopyableSelection();
getTransferData: function PC_getTransferData(dragAction) {
var nodes = null;
if (dragAction == Ci.nsIDragService.DRAGDROP_ACTION_COPY)
nodes = this._activeView.getCopyableSelection();
else
nodes = this._activeView.getDragableSelection();
var dataSet = new TransferDataSet();
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
@ -1450,10 +1456,15 @@ var PlacesController = {
* Drop functions are passed the view that is being dropped on.
*/
var PlacesControllerDragHelper = {
/**
* DOM Element currently being dragged over
*/
currentDropTarget: null,
/**
* @returns The current active drag session. Returns null if there is none.
*/
_getSession: function VO__getSession() {
getSession: function VO__getSession() {
var dragService =
Cc["@mozilla.org/widget/dragservice;1"].
getService(Ci.nsIDragService);
@ -1476,7 +1487,7 @@ var PlacesControllerDragHelper = {
!PlacesController.nodeIsFolder(parent))
return false;
var session = this._getSession();
var session = this.getSession();
if (session) {
if (orientation != NHRVO.DROP_ON)
var types = view.supportedDropTypes;
@ -1531,7 +1542,7 @@ var PlacesControllerDragHelper = {
*/
onDrop: function PCDH_onDrop(sourceView, targetView, insertionPoint,
visibleInsertCount) {
var session = this._getSession();
var session = this.getSession();
var copy = session.dragAction & Ci.nsIDragService.DRAGDROP_ACTION_COPY;
var transactions = [];
var xferable = this._initTransferable(targetView,

View File

@ -51,8 +51,6 @@
<field name="_selection">null</field>
<field name="_result">null</field>
<filed name="_resultNode">null</filed>
<method name="setResultAndNode">
<parameter name="result"/>
<parameter name="resultNode"/>
@ -68,29 +66,53 @@
]]></body>
</method>
<!-- These are the indices of the start and end
of dynamic content in the menu. For index,
if this is a bookmark menu and it has two
static entries at the top, "Bookmark this page"
and "Bookmark all tabs", _startMarker will be
2. If it has an "open in tabs" item at the end,
_endMarker will be the index of that item.
If there is no static content in the menu,
_startMarker and _endMarker are -1. -->
<field name="_startMarker">-1</field>
<field name="_endMarker">-1</field>
<method name="_cleanMenu">
<body><![CDATA[
var foundStartMarker = false;
var foundEndMarker = false;
// 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 = [];
this._startMarker = -1;
this._endMarker = -1;
for (var i = 0; i < this.childNodes.length; ++i) {
var item = this.childNodes[i];
if (item.getAttribute("builder") == "start") {
foundStartMarker = true;
this._startMarker = i;
continue;
}
if (item.getAttribute("builder") == "end") {
foundEndMarker = true;
this._endMarker = i;
continue;
}
if (foundStartMarker && !foundEndMarker)
if ((this._startMarker != -1) && (this._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)
items[i].parentNode.removeChild(items[i]);
if (!foundStartMarker && !foundEndMarker) {
while (this.hasChildNodes())
this.removeChild(this.firstChild);
// If no static items were found at the beginning, remove all items before
// the static items at the end.
if (this._startMarker == -1) {
var end = (this._endMarker == -1) ? this.childNodes.length - 1 : this._endMarker - 1;
for (var i = end; i >=0; i--) {
this.removeChild(this.childNodes[i]);
}
}
LOG("KIDS = " + this.childNodes.length);
]]></body>
@ -98,6 +120,13 @@
<method name="_rebuild">
<body><![CDATA[
// Make sure not to hold onto any references to menu nodes when we
// rebuild, since rebuilding deletes all the nodes in the menu and
// re-adds them. If we use a reference to a deleted node, all kinds
// of exceptions and asserts will fire.
if (this._DNDObserver._overFolder.node)
this._DNDObserver._clearOverFolder();
this._cleanMenu();
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if (PlacesController.nodeIsContainer(this._resultNode))
@ -134,23 +163,49 @@
// else if (nodeIsQuery) ... add menu to build kids
if (element) {
element.node = child;
this.appendChild(element);
// 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 (this._endMarker != -1)
this.insertBefore(element, this.childNodes[this._endMarker++]);
else
this.appendChild(element);
}
if (child.icon)
element.setAttribute("image", child.icon.spec);
}
} else {
var bundle = document.getElementById("placeBundle");
var label = bundle.getString("bookmarksMenuEmptyFolder");
var element = null;
element = document.createElementNS(XULNS, "menuitem");
element.setAttribute("label", label);
element.setAttribute("disabled", true);
this.appendChild(element);
// This menu is empty. If there is no static content, add
// an element to show it is empty.
if (this._startMarker == -1 && this._endMarker == -1) {
var bundle = document.getElementById("placeBundle");
var label = bundle.getString("bookmarksMenuEmptyFolder");
var element = null;
element = document.createElementNS(XULNS, "menuitem");
element.setAttribute("label", label);
element.setAttribute("disabled", true);
this.appendChild(element);
}
}
]]></body>
</method>
<!-- Sometimes calling hidePopup() on a menu can leave submenus
open. This calls hidePopup() on the menu and recursively
hides its submenus as well. -->
<method name="hidePopupAndChildPopups">
<body><![CDATA[
for (var i = 0; i < this.childNodes.length; i++) {
if (this.childNodes[i].getAttribute("type") == "menu" &&
this.childNodes[i].lastChild &&
this.childNodes[i].lastChild.getAttribute("type") == "places")
this.childNodes[i].lastChild.hidePopupAndChildPopups();
}
this.hidePopup();
]]></body>
</method>
<property name="isBookmarks">
<getter><![CDATA[
return PlacesController.nodeIsFolder(this.getResult());
@ -182,7 +237,15 @@
</method>
<method name="getCopyableSelection">
<body><![CDATA[
<body><![CDATA[
return this.getSelectionNodes();
]]></body>
</method>
<method name="getDragableSelection">
<body><![CDATA[
if (PlacesController.nodeIsReadOnly(this._resultNode))
return null;
return this.getSelectionNodes();
]]></body>
</method>
@ -250,6 +313,263 @@
<body><![CDATA[
]]></body>
</method>
<field name="_DNDObserver"><![CDATA[({
// Inside the _DNDObserver object's functions, this points to
// the _DNDObserver object. _self points to the menu xbl object.
_self: this,
// Subfolders 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.
_overFolder: {node: null, openTimer: null, hoverTime: 350, closeTimer: null},
// If this menu's parent auto-opened it because it was dragged over, but didn't
// close it because the mouse dragged into it, the menu should close itself
// onDragExit. This timer is set in dragExit to close the menu.
_closeMenuTimer: null,
_setTimer: function TBV_DO_setTimer(time) {
// There is a problem in Windows where timers don't fire while the
// mouse is dragging. QI-ing the timer to nsITimerInternal and setting
// idle to false makes the timer fire.
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(this, time, timer.TYPE_ONE_SHOT);
timer.QueryInterface(Ci.nsITimerInternal);
timer.idle = false;
return timer;
},
// Function to process all timer notifications.
notify: function TBV_DO_notify(timer) {
// Timer to open a submenu that's being dragged over.
if (timer == this._overFolder.openTimer) {
this._overFolder.node.lastChild.setAttribute("autoopened", "true");
this._overFolder.node.lastChild.showPopup(this._overFolder.node);
this._overFolder.openTimer = null;
}
// Timer to close a submenu that's been dragged off of.
if (timer == this._overFolder.closeTimer) {
// Only close the submenu if the mouse isn't being dragged over any
// of its child menus.
var draggingOverChild = this._draggingOverChildNode(this._overFolder.node);
if (draggingOverChild)
this._overFolder.node = null;
this._clearOverFolder();
// 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();
}
// Timer to close this menu after the drag exit.
if (timer == this._closeMenuTimer) {
if (!this._draggingOverChildNode(this._self)) {
this._self.hidePopupAndChildPopups();
// 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();
}
}
},
// Helper function that determines if the mouse is currently being dragged
// over a child node of this menu. This is necessary so that the menu
// doesn't close while the mouse is dragging over one of its submenus.
_draggingOverChildNode: function TBV_DO_draggingOverChildNode(node) {
var currentNode = PlacesControllerDragHelper.currentDropTarget;
while (currentNode) {
if (currentNode == node)
return true;
currentNode = currentNode.parentNode;
}
return false;
},
// 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 TBV_DO_closeParentMenus() {
var parent = this._self.parentNode;
while(parent) {
if (parent.nodeName == "menupopup" &&
parent.getAttribute("type") == "places") {
if (this._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.
_clearOverFolder: function TBV_DO_clearOverFolder() {
if (this._overFolder.node && this._overFolder.node.lastChild) {
if (!this._overFolder.node.lastChild.hasAttribute("dragover"))
this._overFolder.node.lastChild.hidePopupAndChildPopups();
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;
}
},
// This function returns information about where to drop when
// dragging over this menu--insertion point, child index to drop
// before, and folder to drop into.
_getDropPoint: function TBV_DO_getDropPoint(event) {
// Can't drop if the menu isn't a folder
var resultNode = this._self._resultNode;
if (!PlacesController.nodeIsFolder(this._self._result.root) ||
!PlacesController.nodeIsFolder(resultNode))
return null;
asFolder(resultNode);
var dropPoint = { ip: null, beforeIndex: null, folderNode: null };
// Loop through all the nodes to see which one this should
// get dropped in/above/below.
// Ignore static content at the top and bottom of the menu.
var start = (this._self._startMarker != -1) ? (this._self._startMarker + 1) : 0;
var end = (this._self._endMarker != -1) ? this.self._endMarker : this._self.childNodes.length;
for (var i = start; i < end; i++) {
var xulNode = this._self.childNodes[i];
var nodeY = xulNode.boxObject.y - this._self.boxObject.y;
var nodeHeight = xulNode.boxObject.height;
if (xulNode.node &&
PlacesController.nodeIsFolder(xulNode.node) &&
!PlacesController.nodeIsReadOnly(xulNode.node)) {
// This is a folder. If the mouse is in the top 25% of the
// node, drop above the folder. If it's in the middle
// 50%, drop into the folder. If it's past that, drop below.
if (event.clientY < nodeY + (nodeHeight * 0.25)) {
// Drop above this folder.
dropPoint.ip = new InsertionPoint(resultNode.folderId, i - start, -1);
dropPoint.beforeIndex = i;
return dropPoint;
}
else if (event.clientY < nodeY + (nodeHeight * 0.75)) {
// Drop inside this folder.
dropPoint.ip = new InsertionPoint(asFolder(xulNode.node).folderId, -1, 1);
dropPoint.beforeIndex = i;
dropPoint.folderNode = xulNode;
return dropPoint;
}
} else{
// This is a non-folder node. If the mouse is above the middle,
// drop above the folder. Otherwise, drop below.
if (event.clientY < nodeY + (nodeHeight / 2)) {
// Drop above this bookmark.
dropPoint.ip = new InsertionPoint(resultNode.folderId, i - start, -1);
dropPoint.beforeIndex = i;
return dropPoint;
}
}
}
// Should drop below the last node.
dropPoint.ip = new InsertionPoint(resultNode.folderId, -1, 1);
dropPoint.beforeIndex = -1;
return dropPoint;
},
// This function clears all of the dragover styles that were set when
// a menuitem was dragged over.
_clearStyles: function TBV_DO_clearStyles() {
this._self.removeAttribute("dragover");
for (var i = 0; i < this._self.childNodes.length; i++) {
this._self.childNodes[i].removeAttribute("dragover-top");
this._self.childNodes[i].removeAttribute("dragover-bottom");
this._self.childNodes[i].removeAttribute("dragover-into");
}
},
onDragStart: function TBV_DO_onDragStart(event, xferData, dragAction) {
PlacesController.activeView = this._self;
this._self._selection = event.target.node;
if (event.ctrlKey)
dragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_COPY;
xferData.data = PlacesController.getTransferData(dragAction.action);
},
canDrop: function TBV_DO_canDrop(event, session) {
return PlacesControllerDragHelper.canDrop(this._self, -1);
},
onDragOver: function TBV_DO_onDragOver(event, flavor, session) {
PlacesControllerDragHelper.currentDropTarget = event.target;
var dropPoint = this._getDropPoint(event);
if (dropPoint == null)
return;
this._clearStyles();
if (dropPoint.folderNode) {
// Dragging over a folder; set the appropriate styles.
if (this._overFolder.node != dropPoint.folderNode) {
this._clearOverFolder();
this._overFolder.node = dropPoint.folderNode;
this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
}
dropPoint.folderNode.setAttribute("dragover-into", "true");
}
else {
// Dragging over a menuitem, set dragover-top/bottom to show where
// the item will be dropped and clear out any old folder info.
if (dropPoint.beforeIndex == -1) {
if (this._self.endMatch)
this._self.childNodes[this._self.endMatch].setAttribute("dragover-top", "true");
else
this._self.lastChild.setAttribute("dragover-bottom", "true");
}
else {
this._self.childNodes[dropPoint.beforeIndex].setAttribute("dragover-top", "true");
}
// Clear out old folder information
this._clearOverFolder();
}
this._self.setAttribute("dragover", "true");
},
onDrop: function TBV_DO_onDrop(event, dropData, session) {
var dropPoint = this._getDropPoint(event);
if (dropPoint == null)
return;
PlacesController.activeView = this._self;
PlacesControllerDragHelper.onDrop(null, this._self,
dropPoint.ip, 1);
this._self._rebuild();
},
onDragExit: function TBV_DO_onDragExit(event, session) {
PlacesControllerDragHelper.currentDropTarget = null;
this._clearStyles();
// Close any folder being hovered over
if (this._overFolder.node)
this._overFolder.closeTimer = this._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.
if (this._self.hasAttribute("autoopened"))
this._closeMenuTimer = this._setTimer(this._overFolder.hoverTime);
},
getSupportedFlavours: function TBV_DO_getSupportedFlavours() {
var flavorSet = new FlavourSet();
for (var i = 0; i < this._self.supportedDropTypes.length; ++i)
flavorSet.appendFlavour(this._self.supportedDropTypes[i]);
return flavorSet;
},
})]]></field>
</implementation>
<handlers>
<handler event="popupshowing">
@ -263,16 +583,35 @@
this._resultNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
this._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.
this.removeAttribute("autoopened");
}
</handler>
<handler event="click"><![CDATA[
if (event.target.localName != "menuitem" &&
event.target.localName != "menu")
if ((event.target.localName != "menuitem" &&
event.target.localName != "menu") ||
event.target.parentNode != this)
return;
this._selection = event.target.node;
PlacesController.activeView = this;
PlacesController.mouseLoadURI(event);
]]></handler>
<handler event="draggesture"><![CDATA[
if (event.target.localName == "menuitem")
// TODO--allow menu drag if shift (or alt??) key is down
nsDragAndDrop.startDrag(event, this._DNDObserver);
]]></handler>
<handler event="dragover"><![CDATA[
nsDragAndDrop.dragOver(event, this._DNDObserver);
]]></handler>
<handler event="dragdrop"><![CDATA[
nsDragAndDrop.drop(event, this._DNDObserver);
]]></handler>
<handler event="dragexit"><![CDATA[
nsDragAndDrop.dragExit(event, this._DNDObserver);
]]></handler>
</handlers>
</binding>

View File

@ -7,6 +7,32 @@
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:vbox>
<xul:hbox class="toolbar-drop-indicator-bar">
<xul:hbox class="toolbar-drop-indicator"/>
</xul:hbox>
<xul:hbox flex="1">
<children/>
<xul:hbox mousethrough="always"
flex="1"
pack="end">
<xul:toolbarbutton type="menu"
class="chevron"
mousethrough="never"
collapsed="true">
<xul:menupopup type="places"
context="placesContext"/>
</xul:toolbarbutton>
</xul:hbox>
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<constructor><![CDATA[
]]></constructor>
@ -40,6 +66,9 @@
this._rebuild();
]]></body>
</method>
<field name="_dropIndicatorBar">document.getAnonymousNodes(this)[0].firstChild</field>
<field name="_chevron">document.getAnonymousNodes(this)[0].childNodes[1].firstChild.firstChild</field>
<field name="_selection">null</field>
@ -52,12 +81,17 @@
]]></body>
</method>
<field name="_chevron">null</field>
<method name="_rebuild">
<body><![CDATA[
// Clear out references to existing nodes, since we'll be deleting and re-adding.
if (this._DNDObserver._overFolder.node)
this._DNDObserver._clearOverFolder();
this._openedMenuButton = null;
while (this.hasChildNodes())
this.removeChild(this.firstChild);
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
this._result.root.containerOpen = true;
var cc = this._result.root.childCount;
for (var i = 0; i < cc; ++i) {
var child = this._result.root.getChild(i);
@ -84,18 +118,7 @@
this.appendChild(button);
}
var overflowPadder = document.createElementNS(XULNS, "hbox");
overflowPadder.setAttribute("mousethrough", "always");
overflowPadder.setAttribute("flex", "1");
overflowPadder.setAttribute("pack", "end");
this.appendChild(overflowPadder);
this._chevron = document.createElementNS(XULNS, "toolbarbutton");
this._chevron.setAttribute("type", "menu");
this._chevron.setAttribute("class", "chevron");
this._chevron.setAttribute("mousethrough", "never");
this._chevron.setAttribute("collapsed", "true");
overflowPadder.appendChild(this._chevron);
var popup = document.createElementNS(XULNS, "menupopup");
var popup = this._chevron.firstChild;
popup.setAttribute("type", "places");
// This is set here and not in the XBL constructor for the menu because
// it doesn't get initialized properly in the constructor.
@ -104,7 +127,6 @@
popup._resultNode = this._result.root;
var t = this;
popup.popupShowingCallback = function() {t.chevronPopupShowing();};
this._chevron.appendChild(popup);
this.updateChevron();
]]></body>
@ -124,7 +146,7 @@
<method name="getElementWidth">
<parameter name="element"/>
<body><![CDATA[
var style = document.defaultView.getComputedStyle(element, '');
var style = document.defaultView.getComputedStyle(element, "");
var leftMargin = style.getPropertyValue("margin-left");
leftMargin = leftMargin ? Math.round(parseFloat(leftMargin)) : 0;
var rightMargin = style.getPropertyValue("margin-right");
@ -147,11 +169,11 @@
var totalWidth = this.boxObject.width;
var spaceLeft = totalWidth;
var overflowed = false;
for (var i = 0; i < this.childNodes.length - 1; i++) {
for (var i = 0; i < this.childNodes.length; i++) {
var child = this.childNodes[i];
child.collapsed = false;
spaceLeft -= this.getElementWidth(child);
var spaceNeeded = (i == this.childNodes.length - 2) ? 0 : chevronWidth;
var spaceNeeded = (i == this.childNodes.length - 1) ? 0 : chevronWidth;
if (spaceLeft < spaceNeeded) {
overflowed = true;
child.collapsed = true;
@ -167,7 +189,7 @@
<body><![CDATA[
this._result = this._places.executeQueries(queries, queries.length,
options);
this._result.root.containerOpen = true;
this._result.root.containerOpen = true;
this._rebuild();
]]></body>
</method>
@ -208,6 +230,14 @@
]]></body>
</method>
<method name="getDragableSelection">
<body><![CDATA[
if (PlacesController.nodeIsReadOnly(this._result.root))
return null;
return this.getSelectionNodes();
]]></body>
</method>
<property name="selectedNode">
<getter><![CDATA[
return this.hasSelection ? this._selection : null;
@ -238,7 +268,7 @@
index = PlacesController.getIndexOfNode(this.selectedNode)
}
}
return new InsertionPoint(folderId, index);
return new InsertionPoint(folderId, index, 1);
]]></getter>
</property>
@ -326,47 +356,224 @@
}
})]]></field>
<field name="_DNDObserver"><![CDATA[({
// XXXben ew.
// Inside the _DNDObserver object's functions, this points to
// the _DNDObserver object. _self points to the toolbar xbl object.
_self: this,
// Menu buttons 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.
_overFolder: {node: null, openTimer: null, hoverTime: 350, closeTimer: null},
// timer for turning of indicator bar, to get rid of flicker
_ibTimer: null,
_setTimer: function TBV_DO_setTimer(time) {
// There is a problem in Windows where timers don't fire while the
// mouse is dragging. QI-ing the timer to nsITimerInternal and setting
// idle to false makes the timer fire.
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(this, time, timer.TYPE_ONE_SHOT);
timer.QueryInterface(Ci.nsITimerInternal);
timer.idle = false;
return timer;
},
// Function to process all timer notifications.
notify: function TBV_DO_notify(timer) {
// Timer to turn off indicator bar.
if (timer == this._ibTimer) {
ib = this._self._dropIndicatorBar.removeAttribute('dragging');
this._ibTimer = null;
}
// Timer to open a menubutton that's being dragged over.
if (timer == 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.
if (timer == this._overFolder.closeTimer) {
// Only close the menubutton if the drag session isn't currently 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._self) {
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();
}
},
// 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.
_clearOverFolder: function TBV_DO_clearOverFolder() {
if (this._overFolder.node && this._overFolder.node.lastChild) {
if (!this._overFolder.node.lastChild.hasAttribute("dragover")) {
this._overFolder.node.lastChild.hidePopupAndChildPopups();
}
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;
}
},
// This function returns information about where to drop when
// dragging over this menu--insertion point, child index to drop
// before, and folder to drop into.
_getDropPoint: function TBV_DO_getDropPoint(event) {
// Can't drop if the toolbar isn't a folder.
var result = this._self.getResult();
if (!PlacesController.nodeIsFolder(result.root))
return null;
asFolder(result.root);
var dropPoint = { ip: null, beforeIndex: null, folderNode: null };
// Loop through all the nodes to see which one this should
// get dropped in/next to
for (var i = 0; i < this._self.childNodes.length; i++) {
var xulNode = this._self.childNodes[i];
if (PlacesController.nodeIsFolder(xulNode.node) &&
!PlacesController.nodeIsReadOnly(xulNode.node)) {
ASSERT(xulNode.getAttribute("type") == "menu");
// This is a folder. If the mouse is in the left 25% of the
// node, drop to the left of the folder. If it's in the middle
// 50%, drop into the folder. If it's past that, drop to the right.
if (event.clientX < xulNode.boxObject.x + (xulNode.boxObject.width * 0.25)) {
// Drop to the left of this folder.
dropPoint.ip = new InsertionPoint(result.root.folderId, i, -1);
dropPoint.beforeIndex = i;
return dropPoint;
}
else if (event.clientX < xulNode.boxObject.x + (xulNode.boxObject.width * 0.75)) {
// Drop inside this folder.
dropPoint.ip = new InsertionPoint(asFolder(xulNode.node).folderId, -1, 1);
dropPoint.beforeIndex = i;
dropPoint.folderNode = xulNode;
return dropPoint;
}
} else{
// This is a non-folder node. If the mouse is left of the middle,
// drop to the left of the folder. If it's right, drop to the right.
if (event.clientX < xulNode.boxObject.x + (xulNode.boxObject.width / 2)) {
// Drop to the left of this bookmark.
dropPoint.ip = new InsertionPoint(result.root.folderId, i, -1);
dropPoint.beforeIndex = i;
return dropPoint;
}
}
}
// Should drop to the right of the last node.
dropPoint.ip = new InsertionPoint(result.root.folderId, -1, 1);
dropPoint.beforeIndex = -1;
return dropPoint;
},
onDragStart: function TBV_DO_onDragStart(event, xferData, dragAction) {
xferData.data = PlacesController.getTransferData();
// XXXben - the drag wrapper should do this automatically.
if (event.ctrlKey)
PlacesController.activeView = this._self;
if (event.ctrlKey) {
dragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_COPY;
}
xferData.data = PlacesController.getTransferData(dragAction.action);
},
canDrop: function TBV_DO_canDrop(event, session) {
return PlacesControllerDragHelper.canDrop(this._self, -1);
},
onDragOver: function TBV_DO_onDragOver(event, flavor, session) {
},
onDrop: function TBV_DO_onDrop(event, dropData, session) {
var result = this._self.getResult();
if (!PlacesController.nodeIsFolder(result.root))
return;
result.root.QueryInterface(Ci.nsINavHistoryFolderResultNode);
var destContainer = result.root.QueryInterface(Ci.nsINavHistoryFolderResultNode).folderId;
var destIndex = -1;
var orientation = 1;
if (event.target.localName == "toolbarbutton") {
if (PlacesController.nodeIsFolder(event.target.node)) {
event.target.node.QueryInterface(Ci.nsINavHistoryFolderResultNode);
destContainer = event.target.node.folderId;
destIndex = -1;
orientation = 0;
}
else {
destIndex = PlacesController.getIndexOfNode(event.target.node);
var bo = event.target.boxObject;
orientation = (event.screenX < bo.screenX + (bo.width / 2)) ? -1 : 1;
if (orientation == 1)
++destIndex;
}
PlacesControllerDragHelper.currentDropTarget = event.target;
var dropPoint = this._getDropPoint(event);
var ib = this._self._dropIndicatorBar;
if (this._ibTimer) {
this._ibTimer.cancel();
this._ibTimer = null;
}
if (dropPoint.folderNode) {
// Dropping over a menubutton, set styles and timer to open folder.
if (this._overFolder.node != dropPoint.folderNode) {
this._clearOverFolder();
this._overFolder.node = dropPoint.folderNode;
this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
}
if (!this._overFolder.node.hasAttribute("dragover"))
this._overFolder.node.setAttribute("dragover", "true");
ib.removeAttribute("dragging");
}
else {
// Dragging over a normal toolbarbutton,
// show indicator bar and move it to the appropriate drop point.
if (!ib.hasAttribute("dragging"))
ib.setAttribute("dragging", "true");
var ind = ib.firstChild;
var direction = document.defaultView.getComputedStyle(this._self.parentNode, "").direction;
if (direction == "ltr") {
if (dropPoint.beforeIndex == -1)
ind.style.marginLeft = this._self.lastChild.boxObject.x +
this._self.lastChild.boxObject.width - this._self.boxObject.x - 7 + 'px';
else
ind.style.marginLeft = this._self.childNodes[dropPoint.beforeIndex].boxObject.x -
this._self.boxObject.x - 7 + 'px';
} else {
if (dropPoint.beforeIndex == -1)
ind.style.marginRight = '0px';
else
ind.style.marginRight = (this._self.childNodes[this._self.childNodes.length - 1].boxObject.x +
this._self.childNodes[this._self.childNodes.length - 1].boxObject.width) -
(this._self.childNodes[dropPoint.beforeIndex].boxObject.x) - 5 + 'px';
}
// Clear out old folder information
this._clearOverFolder();
}
var ip = new InsertionPoint(destContainer, destIndex);
PlacesControllerDragHelper.onDrop(null, this._self,
ip, orientation);
},
onDrop: function TBV_DO_onDrop(event, dropData, session) {
var dropPoint = this._getDropPoint(event);
if (dropPoint == null)
return;
PlacesController.activeView = this._self;
PlacesControllerDragHelper.onDrop(null, this._self,
dropPoint.ip, 1);
},
onDragExit: function TBV_DO_onDragExit(event, session) {
// 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);
// Close any folder being hovered over
if (this._overFolder.node)
this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
PlacesControllerDragHelper.currentDropTarget = null;
},
getSupportedFlavours: function TBV_DO_getSupportedFlavours() {
var flavorSet = new FlavourSet();
for (var i = 0; i < this._self.supportedDropTypes.length; ++i)
@ -375,6 +582,30 @@
},
})]]></field>
<method name="checkForMenuEvent">
<parameter name="event"/>
<parameter name="action"/>
<body><![CDATA[
// It seems that even if the menu drag/drop event
// handlers set their phase to capturing, toolbarbutton
// menu events come to the toolbar first, and don't bubble.
// So if this is a menu/menuitem, try to send the event to its
// xbl handler.
if (event.target.localName.indexOf("menu") == 0) {
var parent = event.target.parentNode;
// XULDocument has no getAttribute() function, so check for it before calling.
while (parent && parent.getAttribute) {
if (parent.getAttribute("type") == "places") {
nsDragAndDrop[action](event, parent._DNDObserver);
return true;
}
parent = parent.parentNode;
}
}
return false;
]]></body>
</method>
<method name="saveSelection">
<parameter name="mode"/>
@ -401,29 +632,34 @@
PlacesController.mouseLoadURI(event);
</handler>
<handler event="draggesture"><![CDATA[
// XXXben ew.
if (event.target.localName == "toolbarbutton" &&
!event.target.hasAttribute("type"))
nsDragAndDrop.startDrag(event, this._DNDObserver);
]]></handler>
<handler event="dragover"><![CDATA[
// XXXben ew.
nsDragAndDrop.dragOver(event, this._DNDObserver);
if (!this.checkForMenuEvent(event, "dragOver"))
nsDragAndDrop.dragOver(event, this._DNDObserver);
]]></handler>
<handler event="dragdrop"><![CDATA[
// XXXben ew.
nsDragAndDrop.drop(event, this._DNDObserver);
if (!this.checkForMenuEvent(event, "drop"))
nsDragAndDrop.drop(event, this._DNDObserver);
]]></handler>
<handler event="dragexit"><![CDATA[
if (!this.checkForMenuEvent(event, "dragExit"))
nsDragAndDrop.dragExit(event, this._DNDObserver);
]]></handler>
<handler event="popupshowing"><![CDATA[
if (event.target.parentNode.localName == "toolbarbutton")
if (event.target.parentNode.localName == "toolbarbutton" &&
!PlacesControllerDragHelper.getSession())
this._openedMenuButton = event.target.parentNode;
]]></handler>
<handler event="popuphidden"><![CDATA[
if (event.target.parentNode.localName == "toolbarbutton")
if (event.target.parentNode.localName == "toolbarbutton" &&
!PlacesControllerDragHelper.getSession())
this._openedMenuButton = null;
]]></handler>
<handler event="mousemove"><![CDATA[
if (this._openedMenuButton == null)
if (this._openedMenuButton == null || PlacesControllerDragHelper.getSession())
return;
var target = event.target;

View File

@ -274,6 +274,16 @@
return this.getSelectionNodes();
]]></body>
</method>
<method name="getDragableSelection">
<body><![CDATA[
var nodes = this.getSelectionNodes();
for (var i = nodes.length - 1; i >= 0; i--) {
if (PlacesController.nodeIsReadOnly(nodes[i].parent))
nodes.splice(i, 1);
}
return nodes;
]]></body>
</method>
<!-- AVI Method -->
<property name="selectedNode">
@ -438,11 +448,11 @@
}
}
// Stuff the encoded selection into the transferable data object
xferData.data = PlacesController.getTransferData();
// XXXben - the drag wrapper should do this automatically.
if (event.ctrlKey)
dragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_COPY;
// Stuff the encoded selection into the transferable data object
xferData.data = PlacesController.getTransferData(dragAction.action);
]]></body>
</method>

View File

@ -21,6 +21,8 @@ classic.jar:
skin/classic/browser/places/bookmarks_toolbar.png (skin-win/bookmarks_toolbar.png)
skin/classic/browser/places/livemark_item.png (skin-win/livemark_item.png)
skin/classic/browser/places/places-icon.png (skin-win/places-icon.png)
skin/classic/browser/places/toolbar_dropmarker.png (skin-win/toolbar_dropmarker.png)
skin/classic/browser/places/folder_drag_over.png (skin-win/folder_drag_over.png)
skin/classic/browser/places/bookmarkProperties.css (skin-win/bookmarkProperties.css)
en-US.jar:

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

View File

@ -232,3 +232,45 @@ treechildren::-moz-tree-cell-text(title, separator, selected, focus) {
.no-margin-button {
min-width:0em;
}
/* Personal toolbar */
.toolbar-drop-indicator {
height: 16px;
width: 5px;
margin-bottom: -8px;
position: relative;
background: url('chrome://browser/skin/places/toolbar_dropmarker.png') 50% 50% no-repeat;
}
.toolbar-drop-indicator-bar {
display: none;
height: 16px;
margin-bottom: -16px;
margin-left: 4px;
position: relative;
}
.toolbar-drop-indicator-bar[dragging="true"] {
display: -moz-box;
}
toolbarbutton.bookmark-item[dragover="true"][open="true"] {
-moz-appearance: none;
background: Highlight !important;
color: HighlightText !important;
list-style-image: url('chrome://browser/skin/places/folder_drag_over.png') !important;
-moz-image-region: rect(0px, 16px, 16px, 0px) !important;
}
.bookmark-item[dragover-into="true"] {
background: Highlight !important;
color: HighlightText !important;
}
.bookmark-item[dragover-top="true"] {
-moz-border-top-colors: #000000;
}
.bookmark-item[dragover-bottom="true"] {
-moz-border-bottom-colors: #000000;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B