# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- # ***** 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 mozilla.org code. # # The Initial Developer of the Original Code is # Pierre Chanial . # Portions created by the Initial Developer are Copyright (C) 1998 # the Initial Developer. All Rights Reserved. # # Contributor(s): # David Hyatt # # 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 ***** var BookmarksMenu = { _selection:null, _target:null, _orientation:null, ///////////////////////////////////////////////////////////////////////////// // prepare the bookmarks menu for display onShowMenu: function (aTarget) { this.showOpenInTabsMenuItem(aTarget); this.showEmptyItem(aTarget); }, ///////////////////////////////////////////////////////////////////////////// // remove arbitary elements created in this.onShowMenu() onHideMenu: function (aTarget) { this.hideOpenInTabsMenuItem(aTarget); this.hideEmptyItem(aTarget); }, ///////////////////////////////////////////////////////////////////////////// // shows the 'Open All in Tabs' menu item if validOpenInTabsMenuItem is true --> showOpenInTabsMenuItem: function (aTarget) { if (!this.validOpenInTabsMenuItem(aTarget) || aTarget.lastChild.getAttribute("class") == "openintabs-menuitem") return; var element = document.createElementNS(gXUL_NS, "menuseparator"); element.setAttribute("class", "openintabs-menuseparator"); aTarget.appendChild(element); element = document.createElementNS(gXUL_NS, "menuitem"); element.setAttribute("class", "openintabs-menuitem"); element.setAttribute("label", BookmarksUtils.getLocaleString("cmd_bm_openfolder")); element.setAttribute("accesskey", BookmarksUtils.getLocaleString("cmd_bm_openfolder_accesskey")); aTarget.appendChild(element); }, realHideOpenInTabsMenuItem: function (aParent) { if (!aParent.hasChildNodes()) return; var child = aParent.lastChild; var removed = 0; while (child) { var cclass = child.getAttribute("class"); if (cclass == "openintabs-menuitem" || cclass == "openintabs-menuseparator") { var prevchild = child.previousSibling; aParent.removeChild(child); child = prevchild; removed++; if (removed == 2) break; } else { child = child.previousSibling; } } }, #ifdef XP_MACOSX // FIXME: Work around a Mac menu bug with onpopuphidden. onpopuphiding/hidden on mac fire // before oncommand executes on a menuitem. For now we're applying a XUL workaround // since it's too late to fix this in the Mac widget code for 1.5. hideOpenInTabsMenuItem: function (aTarget) { setTimeout(function() { BookmarksMenu.realHideOpenInTabsMenuItem(aTarget); }, 0); }, #else ///////////////////////////////////////////////////////////////////////////// // hides the 'Open All in Tabs' on popuphidden so that we won't duplicate it --> hideOpenInTabsMenuItem: function (aTarget) { BookmarksMenu.realHideOpenInTabsMenuItem(aTarget); }, #endif ///////////////////////////////////////////////////////////////////////////// // returns false if... // - the parent is the bookmark menu or the chevron // - the menupopup contains ony one bookmark validOpenInTabsMenuItem: function (aTarget) { var rParent = RDF.GetResource(aTarget.parentNode.id) var type = BookmarksUtils.resolveType(rParent); if (type != "Folder" && type != "PersonalToolbarFolder" && type != "Livemark") return false; var count = 0; if (!aTarget.hasChildNodes()) return false; var curr = aTarget.firstChild; do { type = BookmarksUtils.resolveType(curr.id); if (type == "Bookmark" && ++count == 2) return true; curr = curr.nextSibling; } while (curr); return false; }, ///////////////////////////////////////////////////////////////////////////// // show an empty item if the menu is empty showEmptyItem: function (aTarget) { if(aTarget.hasChildNodes()) return; var EmptyMsg = BookmarksUtils.getLocaleString("emptyFolder"); var emptyElement = document.createElementNS(gXUL_NS, "menuitem"); emptyElement.setAttribute("id", "empty-menuitem"); emptyElement.setAttribute("label", EmptyMsg); emptyElement.setAttribute("disabled", "true"); aTarget.appendChild(emptyElement); }, ///////////////////////////////////////////////////////////////////////////// // remove the empty element hideEmptyItem: function (aTarget) { if (!aTarget.hasChildNodes()) return; // if the user drags to the menu while it's open (i.e. on the toolbar), // the bookmark gets added either before or after the Empty menu item // before the menu is hidden. So we need to test both first and last. if (aTarget.firstChild.id == "empty-menuitem") aTarget.removeChild(aTarget.firstChild); else if (aTarget.lastChild.id == "empty-menuitem") aTarget.removeChild(aTarget.lastChild); }, ////////////////////////////////////////////////////////////////////////// // Fill a context menu popup with menuitems appropriate for the current // selection. createContextMenu: function (aEvent) { var target = document.popupNode; if (!this.isBTBookmark(target.id)) { target.removeAttribute("open"); return false; } var targettype = BookmarksUtils.resolveType(target.id); if (targettype == "ImmutableFolder") { // no context; see bug#... (popups getting stuck because "open" // attribute doesn't get removed) target.removeAttribute("open"); return false; } // -moz-user-focus: ignore; is set on toolbars document.getElementById("PersonalToolbar").focus(); this._selection = this.getBTSelection(target); this._orientation = this.getBTOrientation(aEvent, target); if (targettype != "ImmutableBookmark") this._target = this.getBTTarget(target, this._orientation); // walk up the tree until we find a database node var p = target; while (p && !p.database) p = p.parentNode; if (p) this._db = p.database; BookmarksCommand.createContextMenu(aEvent, this._selection, this._db); this.onCommandUpdate(); aEvent.target.addEventListener("mousemove", BookmarksMenuController.onMouseMove, false); return true; }, ///////////////////////////////////////////////////////////////////////// // Clean up after closing the context menu popup destroyContextMenu: function (aEvent) { // note that this method is called after doCommand. // let's focus the content and dismiss the popup chain (needed when the user // type escape or if he/she clicks outside the context menu) if (content) content.focus(); // XXXpch: see bug 210910, it should be done properly in the backend BookmarksMenuDNDObserver.mCurrentDragOverTarget = null; BookmarksMenuDNDObserver.onDragCloseTarget(); // if the user types escape, we need to remove the feedback BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode); aEvent.target.removeEventListener("mousemove", BookmarksMenuController.onMouseMove, false); this._target = null; this._selection = null; }, ///////////////////////////////////////////////////////////////////////////// // returns the formatted selection from aNode getBTSelection: function (aNode) { var item; switch (aNode.id) { case "bookmarks-ptf": item = BMSVC.getBookmarksToolbarFolder().Value; break; case "bookmarks-menu": item = "NC:BookmarksRoot"; break; default: item = aNode.id; if (!this.isBTBookmark(item)) return {length:0}; } var parent = this.getBTContainer(aNode); var isExpanded = aNode.hasAttribute("open") && aNode.open; var selection = {}; selection.item = [RDF.GetResource(item)]; selection.parent = [RDF.GetResource(parent)]; selection.isExpanded = [isExpanded]; selection.length = selection.item.length; BookmarksUtils.checkSelection(selection); return selection; }, ///////////////////////////////////////////////////////////////////////// // returns the insertion target from aNode getBTTarget: function (aNode, aOrientation) { var item, parent, index; switch (aNode.id) { case "bookmarks-ptf": parent = BMSVC.getBookmarksToolbarFolder().Value; item = BookmarksToolbar.getLastVisibleBookmark(); break; case "bookmarks-menu": parent = "NC:BookmarksRoot"; break; case "bookmarks-chevron": parent = BMSVC.getBookmarksToolbarFolder().Value; break; default: if (aOrientation == BookmarksUtils.DROP_ON) parent = aNode.id else { parent = this.getBTContainer(aNode); item = aNode; } } parent = RDF.GetResource(parent); if (aOrientation == BookmarksUtils.DROP_ON) return BookmarksUtils.getTargetFromFolder(parent); item = RDF.GetResource(item.id); RDFC.Init(BMDS, parent); index = RDFC.IndexOf(item); if (aOrientation == BookmarksUtils.DROP_AFTER) ++index; return { parent: parent, index: index }; }, ///////////////////////////////////////////////////////////////////////// // returns the parent resource of a node in the personal toolbar. // this is determined by inspecting the source element and walking up the // DOM tree to find the appropriate containing node. getBTContainer: function (aNode) { var parent; var item = aNode.id; if (!this.isBTBookmark(item)) return "NC:BookmarksRoot" parent = aNode.parentNode.parentNode; parent = parent.id; switch (parent) { case "bookmarks-chevron": case "bookmarks-stack": case "bookmarks-toolbar": return BMSVC.getBookmarksToolbarFolder().Value; case "bookmarks-menu": return "NC:BookmarksRoot"; default: return parent; } }, /////////////////////////////////////////////////////////////////////////// // returns true if the node is a bookmark, a folder or a bookmark separator isBTBookmark: function (aURI) { if (!aURI || aURI == "bookmarkAllCmd") return false; var type = BookmarksUtils.resolveType(aURI); return (type == "BookmarkSeparator" || type == "Bookmark" || type == "Folder" || type == "PersonalToolbarFolder" || type == "Livemark" || type == "ImmutableBookmark" || type == "ImmutableFolder" || aURI == "bookmarks-ptf") }, ///////////////////////////////////////////////////////////////////////// // returns true if the node is a container. --> isBTContainer: function (aTarget) { return aTarget.localName == "menu" || (aTarget.localName == "toolbarbutton" && (aTarget.getAttribute("container") == "true")); }, ///////////////////////////////////////////////////////////////////////// // returns BookmarksUtils.DROP_BEFORE, DROP_ON or DROP_AFTER accordingly // to the event coordinates. Skin authors could break us, we'll cross that // bridge when they turn us 90degrees. --> getBTOrientation: function (aEvent, aTarget) { var target if (!aTarget) target = aEvent.target; else target = aTarget; if (target.localName == "menu" && target.parentNode.localName != "menupopup" || target.id == "bookmarks-chevron") return BookmarksUtils.DROP_ON; if (target.id == "bookmarks-ptf") { return target.hasChildNodes()? BookmarksUtils.DROP_AFTER:BookmarksUtils.DROP_ON; } var overButtonBoxObject = target.boxObject.QueryInterface(Components.interfaces.nsIBoxObject); var overParentBoxObject = target.parentNode.boxObject.QueryInterface(Components.interfaces.nsIBoxObject); var size, border; var coordValue, clientCoordValue; switch (target.localName) { case "toolbarseparator": case "toolbarbutton": size = overButtonBoxObject.width; coordValue = overButtonBoxObject.x; clientCoordValue = aEvent.clientX; break; case "menuseparator": case "menu": case "menuitem": size = overButtonBoxObject.height; coordValue = overButtonBoxObject.screenY; clientCoordValue = aEvent.screenY; break; default: return BookmarksUtils.DROP_ON; } if (this.isBTContainer(target)) if (target.localName == "toolbarbutton") { // the DROP_BEFORE area excludes the label var iconNode = document.getAnonymousElementByAttribute(target, "class", "toolbarbutton-icon"); border = parseInt(document.defaultView.getComputedStyle(target,"").getPropertyValue("padding-left")) + parseInt(document.defaultView.getComputedStyle(iconNode ,"").getPropertyValue("width")); border = Math.min(size/5,Math.max(border,4)); } else border = size/5; else border = size/2; // in the first region? if (clientCoordValue-coordValue < border) return BookmarksUtils.DROP_BEFORE; // in the last region? else if (clientCoordValue-coordValue >= size-border) return BookmarksUtils.DROP_AFTER; else // must be in the middle somewhere return BookmarksUtils.DROP_ON; }, ///////////////////////////////////////////////////////////////////////// // expand the folder targeted by the context menu. expandBTFolder: function () { var target = document.popupNode.lastChild; if (document.popupNode.open) target.hidePopup(); else target.showPopup(document.popupNode); }, onCommandUpdate: function () { var selection = this._selection; var target = this._target; BookmarksController.onCommandUpdate(selection, target); if (document.popupNode.id == "bookmarks-ptf") { // disabling 'cut' and 'copy' on the empty area of the personal toolbar var commandNode = document.getElementById("cmd_cut"); commandNode.setAttribute("disabled", "true"); commandNode = document.getElementById("cmd_copy"); commandNode.setAttribute("disabled", "true"); } }, /////////////////////////////////////////////////////////////// // Load a bookmark in menus or toolbar buttons // aTarget may not the aEvent target (see Open all in tabs command) loadBookmark: function (aEvent, aTarget, aDS) { if (aTarget.getAttribute("class") == "openintabs-menuitem") aTarget = aTarget.parentNode.parentNode; // Check for invalid bookmarks (most likely a static menu item like "Manage Bookmarks") if (!this.isBTBookmark(aTarget.id)) return; var rSource = RDF.GetResource(aTarget.id); var selection = BookmarksUtils.getSelectionFromResource(rSource); var browserTarget = whereToOpenLink(aEvent); BookmarksCommand.openBookmark(selection, browserTarget, aDS); aEvent.stopPropagation(); }, //////////////////////////////////////////////// // loads a bookmark with the mouse middle button loadBookmarkMiddleClick: function (aEvent, aDS) { if (aEvent.button != 1) return; // unlike for command events, we have to close the menus manually BookmarksMenuDNDObserver.mCurrentDragOverTarget = null; BookmarksMenuDNDObserver.onDragCloseTarget(); this.loadBookmark(aEvent, aEvent.target, aDS); } } var BookmarksMenuController = { supportsCommand: BookmarksController.supportsCommand, isCommandEnabled: function (aCommand) { var selection = BookmarksMenu._selection; var target = BookmarksMenu._target; if (selection) return BookmarksController.isCommandEnabled(aCommand, selection, target); return false; }, doCommand: function (aCommand) { // we needed to focus the element that has the bm command controller // to get here. Now, let's focus the content before performing the command: // if a modal dialog is called from now, the content will be focused again // automatically after dismissing the dialog if (content) content.focus(); BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode); // if a dialog opens, the "open" attribute of a menuitem-container // clicked on won't be removed. We do it manually. var element = document.popupNode.firstChild; if (element && element.localName == "menupopup") element.hidePopup(); var selection = BookmarksMenu._selection; var target = BookmarksMenu._target; var db = BookmarksMenu._db; switch (aCommand) { case "cmd_bm_expandfolder": BookmarksMenu.expandBTFolder(); break; default: BookmarksController.doCommand(aCommand, selection, target, db); } }, onMouseMove: function (aEvent) { var command = aEvent.target.getAttribute("command"); var isDisabled = aEvent.target.getAttribute("disabled") if (isDisabled != "true" && (command == "cmd_bm_newfolder" || command == "cmd_paste")) { BookmarksMenuDNDObserver.onDragSetFeedBack(document.popupNode, BookmarksMenu._orientation); } else { BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode); } } } var BookmarksMenuDNDObserver = { //////////////////// // Public methods // //////////////////// onDragStart: function (aEvent, aXferData, aDragAction) { var target = aEvent.target; // Prevent dragging from invalid regions // can't drag from the empty areas if (target.id == "bookmarks-menu" || target.id == "bookmarks-chevron" || target.id == "bookmarks-ptf") return false; if (!BookmarksMenu.isBTBookmark(target.id)) return false; // Prevent dragging out of menupopups on non Win32 platforms. // a) on Mac drag from menus is generally regarded as being satanic // b) on Linux, this causes an X-server crash, (bug 151336) // c) on Windows, there is no hang or crash associated with this, so we'll leave // the functionality there. if (navigator.platform != "Win32" && target.localName != "toolbarbutton") return false; // a drag start is fired when leaving an open toolbarbutton(type=menu) // (see bug 143031) if (this.isContainer(target)) { if (this.isPlatformNotSupported) return false; if (!aEvent.shiftKey && !aEvent.altKey && !aEvent.ctrlKey) return false; // menus open on mouse down target.firstChild.hidePopup(); } var selection = BookmarksMenu.getBTSelection(target); aXferData.data = BookmarksUtils.getXferDataFromSelection(selection); return true; }, onDragOver: function(aEvent, aFlavour, aDragSession) { var orientation = BookmarksMenu.getBTOrientation(aEvent) if (aDragSession.canDrop) this.onDragSetFeedBack(aEvent.target, orientation); if (orientation != this.mCurrentDropPosition) { // emulating onDragExit and onDragEnter events since the drop region // has changed on the target. this.onDragExit(aEvent, aDragSession); this.onDragEnter(aEvent, aDragSession); } if (this.isPlatformNotSupported) return; if (this.isTimerSupported || !aDragSession.sourceNode) return; this.onDragOverCheckTimers(); }, onDragEnter: function (aEvent, aDragSession) { var target = aEvent.target; var orientation = BookmarksMenu.getBTOrientation(aEvent); if (target.localName == "menupopup" || target.id == "bookmarks-ptf") target = target.parentNode; if (aDragSession.canDrop) { this.onDragSetFeedBack(target, orientation); this.onDragEnterSetTimer(target, aDragSession); } this.mCurrentDragOverTarget = target; this.mCurrentDropPosition = orientation; }, onDragExit: function (aEvent, aDragSession) { var target = aEvent.target; if (target.localName == "menupopup" || target.id == "bookmarks-ptf") target = target.parentNode; this.onDragRemoveFeedBack(target); this.onDragExitSetTimer(target, aDragSession); this.mCurrentDragOverTarget = null; this.mCurrentDropPosition = null; }, onDrop: function (aEvent, aXferData, aDragSession) { var target = aEvent.target; this.onDragRemoveFeedBack(target); var selection = BookmarksUtils.getSelectionFromXferData(aDragSession); var orientation = BookmarksMenu.getBTOrientation(aEvent); // For RTL PersonalBar bookmarks buttons, orientation should be inverted (only in drop case) var PBStyle = window.getComputedStyle(document.getElementById("PersonalToolbar"),''); var isHorizontal = (target.localName == "toolbarbutton"); if ((PBStyle.direction == 'rtl') && isHorizontal) { if (orientation == BookmarksUtils.DROP_AFTER) orientation = BookmarksUtils.DROP_BEFORE; else if (orientation == BookmarksUtils.DROP_BEFORE) orientation = BookmarksUtils.DROP_AFTER; } var selTarget = BookmarksMenu.getBTTarget(target, orientation); // we can only test for kCopyAction if the source is a bookmark var checkCopy = aDragSession.isDataFlavorSupported("moz/rdfitem"); const kDSIID = Components.interfaces.nsIDragService; const kCopyAction = kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_LINK; // hide the 'open in tab' menuseparator because bookmarks // can be inserted after it if they are dropped after the last bookmark // a more comprehensive fix would be in the menupopup template builder var menuSeparator = null; var menuTarget = (target.localName == "toolbarbutton" || target.localName == "menu") && orientation == BookmarksUtils.DROP_ON? target.lastChild:target.parentNode; if (menuTarget.hasChildNodes() && menuTarget.lastChild.getAttribute("class") == "openintabs-menuitem") { menuSeparator = menuTarget.lastChild.previousSibling; menuTarget.removeChild(menuSeparator); } // doCopy defaults to true; check if we should make it false. // we make it false only if all the selection items have valid parent // bookmark DS containers (i.e. aren't generated via aggregation) var doCopy = true; if (checkCopy && !(aDragSession.dragAction & kCopyAction)) doCopy = BookmarksUtils.shouldCopySelection("drag", selection); if (doCopy) BookmarksUtils.insertAndCheckSelection("drag", selection, selTarget); else BookmarksUtils.moveAndCheckSelection("drag", selection, selTarget); // show again the menuseparator if (menuSeparator) menuTarget.insertBefore(menuSeparator, menuTarget.lastChild); }, canDrop: function (aEvent, aDragSession) { var target = aEvent.target; if (!BookmarksMenu.isBTBookmark(target.id)) return false; var btype = BookmarksUtils.resolveType(target.id); return target.id == "bookmarks-menu" || target.id == "bookmarks-chevron" || target.id == "bookmarks-ptf" || (target.id != "NC:SystemBookmarksStaticRoot" && btype == "Folder" || btype == "Bookmark"); }, canHandleMultipleItems: true, getSupportedFlavours: function () { var flavourSet = new FlavourSet(); flavourSet.appendFlavour("moz/rdfitem"); flavourSet.appendFlavour("text/x-moz-url"); flavourSet.appendFlavour("application/x-moz-file", "nsIFile"); flavourSet.appendFlavour("text/unicode"); return flavourSet; }, //////////////////////////////////// // Private methods and properties // //////////////////////////////////// springLoadedMenuDelay: 350, // milliseconds isPlatformNotSupported: navigator.platform.indexOf("Mac") != -1, // see bug 136524 // Needs to be dynamically overridden (to |true|) in the case of an external drag: see bug 232795. isTimerSupported: navigator.platform.indexOf("Win") == -1, mCurrentDragOverTarget: null, mCurrentDropPosition: null, loadTimer : null, closeTimer : null, loadTarget : null, closeTarget: null, _observers : null, get mObservers () { if (!this._observers) { this._observers = [ document.getElementById("bookmarks-ptf"), // menubar menus haven't an "open" attribute: we can take the child document.getElementById("bookmarks-menu").firstChild, document.getElementById("bookmarks-chevron").parentNode ] } return this._observers; }, getObserverForNode: function (aNode) { if (!aNode) return null; var node = aNode; var observer; while (node) { for (var i=0; i < this.mObservers.length; i++) { observer = this.mObservers[i]; if (observer == node) return observer; } node = node.parentNode; } return null; }, onDragCloseMenu: function (aNode) { var children = aNode.childNodes; for (var i = 0; i < children.length; i++) { if (this.isContainer(children[i]) && children[i].getAttribute("open") == "true") { this.onDragCloseMenu(children[i].lastChild); if (children[i] != this.mCurrentDragOverTarget || this.mCurrentDropPosition != BookmarksUtils.DROP_ON) children[i].lastChild.hidePopup(); } } }, onDragCloseTarget: function () { var currentObserver = this.getObserverForNode(this.mCurrentDragOverTarget); // close all the menus not hovered by the mouse for (var i=0; i < this.mObservers.length; i++) { if (currentObserver != this.mObservers[i]) { this.onDragCloseMenu(this.mObservers[i]); if (this.mObservers[i].parentNode.id == "bookmarks-menu") this.mObservers[i].hidePopup(); } else this.onDragCloseMenu(this.mCurrentDragOverTarget.parentNode); } }, onDragLoadTarget: function (aTarget) { if (!this.mCurrentDragOverTarget) return; // Load the current menu if (this.mCurrentDropPosition == BookmarksUtils.DROP_ON && this.isContainer(aTarget)) aTarget.lastChild.showPopup(aTarget); }, onDragOverCheckTimers: function () { var now = new Date().getTime(); if (this.closeTimer && now-this.springLoadedMenuDelay>this.closeTimer) { this.onDragCloseTarget(); this.closeTimer = null; } if (this.loadTimer && (now-this.springLoadedMenuDelay>this.loadTimer)) { this.onDragLoadTarget(this.loadTarget); this.loadTimer = null; } }, onDragEnterSetTimer: function (aTarget, aDragSession) { if (this.isPlatformNotSupported) return; if (this.isTimerSupported || !aDragSession.sourceNode) { var targetToBeLoaded = aTarget; clearTimeout(this.loadTimer); if (aTarget == aDragSession.sourceNode) return; var This = this; this.loadTimer=setTimeout(function () {This.onDragLoadTarget(targetToBeLoaded)}, This.springLoadedMenuDelay); } else { var now = new Date().getTime(); this.loadTimer = now; this.loadTarget = aTarget; } }, onDragExitSetTimer: function (aTarget, aDragSession) { if (this.isPlatformNotSupported) return; var This = this; if (this.isTimerSupported || !aDragSession.sourceNode) { clearTimeout(this.closeTimer) this.closeTimer=setTimeout(function () {This.onDragCloseTarget()}, This.springLoadedMenuDelay); } else { var now = new Date().getTime(); this.closeTimer = now; this.closeTarget = aTarget; this.loadTimer = null; // If the user isn't rearranging within the menu, close it // To do so, we exploit a Mac bug: timeout set during // drag and drop on Windows and Mac are fired only after that the drop is released. // timeouts will pile up, we may have a better approach but for the moment, this one // correctly close the menus after a drop/cancel outside the personal toolbar. // The if statement in the function has been introduced to deal with rare but reproducible // missing Exit events. if (aDragSession.sourceNode.localName != "menuitem" && aDragSession.sourceNode.localName != "menu") setTimeout(function () { if (This.mCurrentDragOverTarget) {This.onDragRemoveFeedBack(This.mCurrentDragOverTarget); This.mCurrentDragOverTarget=null} This.loadTimer=null; This.onDragCloseTarget() }, 0); } }, onDragSetFeedBack: function (aTarget, aOrientation) { switch (aTarget.localName) { case "toolbarseparator": case "toolbarbutton": switch (aOrientation) { case BookmarksUtils.DROP_BEFORE: aTarget.setAttribute("dragover-left", "true"); break; case BookmarksUtils.DROP_AFTER: aTarget.setAttribute("dragover-right", "true"); break; case BookmarksUtils.DROP_ON: aTarget.setAttribute("dragover-top" , "true"); aTarget.setAttribute("dragover-bottom", "true"); aTarget.setAttribute("dragover-left" , "true"); aTarget.setAttribute("dragover-right" , "true"); break; } break; case "menuseparator": case "menu": case "menuitem": switch (aOrientation) { case BookmarksUtils.DROP_BEFORE: aTarget.setAttribute("dragover-top", "true"); break; case BookmarksUtils.DROP_AFTER: aTarget.setAttribute("dragover-bottom", "true"); break; case BookmarksUtils.DROP_ON: break; } break; case "hbox" : // hit between the last visible bookmark and the chevron var newTarget = BookmarksToolbar.getLastVisibleBookmark(); if (newTarget) newTarget.setAttribute("dragover-right", "true"); break; case "stack" : case "menupopup": break; default: dump("No feedback for: "+aTarget.localName+"\n"); } }, onDragRemoveFeedBack: function (aTarget) { var newTarget; var bt; if (aTarget.id == "bookmarks-ptf") { // hit when dropping in the bt or between the last visible bookmark // and the chevron newTarget = BookmarksToolbar.getLastVisibleBookmark(); if (newTarget) newTarget.removeAttribute("dragover-right"); } else if (aTarget.id == "bookmarks-stack") { newTarget = BookmarksToolbar.getLastVisibleBookmark(); newTarget.removeAttribute("dragover-right"); } else { aTarget.removeAttribute("dragover-left"); aTarget.removeAttribute("dragover-right"); aTarget.removeAttribute("dragover-top"); aTarget.removeAttribute("dragover-bottom"); } }, onDropSetFeedBack: function (aTarget) { //XXX Not yet... }, isContainer: function (aTarget) { return aTarget.localName == "menu" || aTarget.localName == "toolbarbutton" && aTarget.getAttribute("type") == "menu"; } } var BookmarksToolbar = { ///////////////////////////////////////////////////////////////////////////// // make bookmarks toolbar act like menus openedMenuButton:null, autoOpenMenu: function (aEvent) { var target = aEvent.target; if (BookmarksToolbar.openedMenuButton != target && target.nodeName == "toolbarbutton" && target.type == "menu") { BookmarksToolbar.openedMenuButton.open = false; target.open = true; } }, setOpenedMenu: function (aEvent) { if (aEvent.target.parentNode.localName == 'toolbarbutton') { if (!this.openedMenuButton) aEvent.currentTarget.addEventListener("mouseover", this.autoOpenMenu, true); this.openedMenuButton = aEvent.target.parentNode; } }, unsetOpenedMenu: function (aEvent) { if (aEvent.target.parentNode.localName == 'toolbarbutton') { aEvent.currentTarget.removeEventListener("mouseover", this.autoOpenMenu, true); this.openedMenuButton = null; } }, ///////////////////////////////////////////////////////////////////////////// // returns the node of the last visible bookmark on the toolbar --> getLastVisibleBookmark: function () { var buttons = document.getElementById("bookmarks-ptf"); var button = buttons.firstChild; if (!button) return null; // empty bookmarks toolbar do { if (button.collapsed) return button.previousSibling; button = button.nextSibling; } while (button) return buttons.lastChild; }, updateOverflowMenu: function (aMenuPopup) { var hbox = document.getElementById("bookmarks-ptf"); for (var i = 0; i < hbox.childNodes.length; i++) { var button = hbox.childNodes[i]; var menu = aMenuPopup.childNodes[i]; if (menu.collapsed == button.collapsed) menu.collapsed = !menu.collapsed; } }, resizeFunc: function(event) { if (event && event.type == 'focus') window.removeEventListener('focus', BookmarksToolbar.resizeFunc, false); // hack for bug 266737 var buttons = document.getElementById("bookmarks-ptf"); if (!buttons) return; var chevron = document.getElementById("bookmarks-chevron"); var width = window.innerWidth; if (width == 0) { // hack for bug 266737 window.addEventListener('focus', BookmarksToolbar.resizeFunc, false); return; } var myToolbar = buttons.parentNode.parentNode.parentNode; for (var i = myToolbar.childNodes.length-1; i >= 0; i--){ var anItem = myToolbar.childNodes[i]; if (anItem.id == "personal-bookmarks") { break; } width -= anItem.boxObject.width; } var chevronWidth = 0; chevron.collapsed = false; chevronWidth = chevron.boxObject.width; chevron.collapsed = true; var overflowed = false; var isLTR=window.getComputedStyle(document.getElementById("PersonalToolbar"),'').direction=='ltr'; for (var i=0; i width) { overflowed = true; // This button doesn't fit. Show it in the menu. Hide it in the toolbar. if (!button.collapsed) button.collapsed = true; if (chevron.collapsed) { chevron.collapsed = false; var overflowPadder = document.getElementById("overflow-padder"); offset = isLTR ? buttons.boxObject.x : width - buttons.boxObject.x - buttons.boxObject.width; overflowPadder.width = width - chevron.boxObject.width - offset; } } } BookmarksToolbarRDFObserver._overflowTimerInEffect = false; }, // Fill in tooltips for personal toolbar fillInBTTooltip: function (tipElement) { var title = tipElement.label; var url = tipElement.statusText; if (!title && !url) { // bail out early if there is nothing to show return false; } var tooltipTitle = document.getElementById("btTitleText"); var tooltipUrl = document.getElementById("btUrlText"); if (title && title != url) { tooltipTitle.removeAttribute("hidden"); tooltipTitle.setAttribute("value", title); } else { tooltipTitle.setAttribute("hidden", "true"); } if (url) { tooltipUrl.removeAttribute("hidden"); tooltipUrl.setAttribute("value", url); } else { tooltipUrl.setAttribute("hidden", "true"); } return true; // show tooltip } } // Implement nsIRDFObserver so we can update our overflow state when items get // added/removed from the toolbar var BookmarksToolbarRDFObserver = { onAssert: function (aDataSource, aSource, aProperty, aTarget) { if (aProperty.Value == gNC_NS+"BookmarksToolbarFolder") { var bt = document.getElementById("bookmarks-ptf"); if (bt) { bt.ref = aSource.Value; document.getElementById("bookmarks-chevron").ref = aSource.Value; } } this.setOverflowTimeout(aSource, aProperty); }, onUnassert: function (aDataSource, aSource, aProperty, aTarget) { this.setOverflowTimeout(aSource, aProperty); }, onChange: function (aDataSource, aSource, aProperty, aOldTarget, aNewTarget) { this.setOverflowTimeout(aSource, aProperty); }, onMove: function (aDataSource, aOldSource, aNewSource, aProperty, aTarget) {}, onBeginUpdateBatch: function (aDataSource) {}, onEndUpdateBatch: function (aDataSource) { this._overflowTimerInEffect = true; setTimeout(BookmarksToolbar.resizeFunc, 0, null); }, _overflowTimerInEffect: false, setOverflowTimeout: function (aSource, aProperty) { if (this._overflowTimerInEffect) return; if (aProperty.Value == gWEB_NS+"LastModifiedDate") return; this._overflowTimerInEffect = true; setTimeout(BookmarksToolbar.resizeFunc, 0, null); } }