home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2005 October / maximum-cd-2005-10.iso / Software / Apps / FirefoxSetup1.0.6.exe / browser.xpi / bin / chrome / browser.jar / content / browser / bookmarks / bookmarksMenu.js < prev    next >
Encoding:
Text File  |  2004-10-12  |  35.2 KB  |  1,029 lines

  1.  
  2. var BookmarksMenu = {
  3.   _selection:null,
  4.   _target:null,
  5.   _orientation:null,
  6.  
  7.   /////////////////////////////////////////////////////////////////////////////
  8.   // prepare the bookmarks menu for display
  9.   onShowMenu: function (aTarget)
  10.   {
  11.     this.showOpenInTabsMenuItem(aTarget);
  12.     this.showEmptyItem(aTarget);
  13.   },
  14.  
  15.   /////////////////////////////////////////////////////////////////////////////
  16.   // remove arbitary elements created in this.onShowMenu()
  17.   onHideMenu: function (aTarget)
  18.   {
  19.     this.hideOpenInTabsMenuItem(aTarget);
  20.     this.hideEmptyItem(aTarget);
  21.   },
  22.  
  23.   /////////////////////////////////////////////////////////////////////////////
  24.   // shows the 'Open in Tabs' menu item if validOpenInTabsMenuItem is true -->
  25.   showOpenInTabsMenuItem: function (aTarget)
  26.   {
  27.     if (!this.validOpenInTabsMenuItem(aTarget) ||
  28.         aTarget.lastChild.getAttribute("class") == "openintabs-menuitem")
  29.       return;
  30.     var element = document.createElementNS(XUL_NS, "menuseparator");
  31.     element.setAttribute("class", "openintabs-menuseparator");
  32.     aTarget.appendChild(element);
  33.     element = document.createElementNS(XUL_NS, "menuitem");
  34.     element.setAttribute("class", "openintabs-menuitem");
  35.     element.setAttribute("label", BookmarksUtils.getLocaleString("cmd_bm_openfolder"));
  36.     element.setAttribute("accesskey", BookmarksUtils.getLocaleString("cmd_bm_openfolder_accesskey"));
  37.     aTarget.appendChild(element);
  38.   },
  39.  
  40.   realHideOpenInTabsMenuItem: function (aParent)
  41.   {
  42.     if (!aParent.hasChildNodes())
  43.       return;
  44.     child = aParent.lastChild;
  45.     var removed = 0;
  46.     while (child) {
  47.       var cclass = child.getAttribute("class");
  48.       if (cclass == "openintabs-menuitem" || cclass == "openintabs-menuseparator") {
  49.         var prevchild = child.previousSibling;
  50.         aParent.removeChild(child);
  51.         child = prevchild;
  52.         removed++;
  53.         if (removed == 2)
  54.           break;
  55.       } else {
  56.         child = child.previousSibling;
  57.       }
  58.     }
  59.   },
  60.  
  61.   /////////////////////////////////////////////////////////////////////////////
  62.   // hides the 'Open in Tabs' on popuphidden so that we won't duplicate it -->
  63.   hideOpenInTabsMenuItem: function (aTarget)
  64.   {
  65.     BookmarksMenu.realHideOpenInTabsMenuItem(aTarget);
  66.   },
  67.  
  68.   /////////////////////////////////////////////////////////////////////////////
  69.   // returns false if...
  70.   // - the parent is the bookmark menu or the chevron
  71.   // - the menupopup contains ony one bookmark
  72.   validOpenInTabsMenuItem: function (aTarget)
  73.   {
  74.     var rParent = RDF.GetResource(aTarget.parentNode.id)
  75.     var type = BookmarksUtils.resolveType(rParent);
  76.     if (type != "Folder" && type != "PersonalToolbarFolder" && type != "Livemark")
  77.       return false;
  78.     var count = 0;
  79.     if (!aTarget.hasChildNodes())
  80.       return false;
  81.     var curr = aTarget.firstChild;
  82.     do {
  83.       type = BookmarksUtils.resolveType(curr.id);
  84.       if (type == "Bookmark" && ++count == 2)
  85.         return true;
  86.       curr = curr.nextSibling;
  87.     } while (curr);
  88.     return false;
  89.   },
  90.  
  91.   /////////////////////////////////////////////////////////////////////////////
  92.   // show an empty item if the menu is empty
  93.   showEmptyItem: function (aTarget)
  94.   {
  95.     if(aTarget.hasChildNodes())
  96.       return;
  97.  
  98.     var EmptyMsg = BookmarksUtils.getLocaleString("emptyFolder");
  99.     var emptyElement = document.createElementNS(XUL_NS, "menuitem");
  100.     emptyElement.setAttribute("id", "empty-menuitem");
  101.     emptyElement.setAttribute("label", EmptyMsg);
  102.     emptyElement.setAttribute("disabled", "true");
  103.  
  104.     aTarget.appendChild(emptyElement);
  105.   },
  106.  
  107.   /////////////////////////////////////////////////////////////////////////////
  108.   // remove the empty element
  109.   hideEmptyItem: function (aTarget)
  110.   {
  111.     if (!aTarget.hasChildNodes())
  112.       return;
  113.  
  114.     // if the user drags to the menu while it's open (i.e. on the toolbar),
  115.     // the bookmark gets added either before or after the Empty menu item
  116.     // before the menu is hidden.  So we need to test both first and last.
  117.     if (aTarget.firstChild.id == "empty-menuitem")
  118.       aTarget.removeChild(aTarget.firstChild);
  119.     else if (aTarget.lastChild.id == "empty-menuitem")
  120.       aTarget.removeChild(aTarget.lastChild);
  121.   },
  122.  
  123.   //////////////////////////////////////////////////////////////////////////
  124.   // Fill a context menu popup with menuitems appropriate for the current
  125.   // selection.
  126.   createContextMenu: function (aEvent)
  127.   {
  128.     var target = document.popupNode;
  129.  
  130.     if (!this.isBTBookmark(target.id)) {
  131.       target.removeAttribute("open");
  132.       return false;
  133.     }
  134.  
  135.     var targettype = BookmarksUtils.resolveType(target.id);
  136.  
  137.     if (targettype == "ImmutableFolder") {
  138.       // no context; see bug#... (popups getting stuck because "open"
  139.       // attribute doesn't get removed)
  140.       target.removeAttribute("open");
  141.       return false;
  142.     }
  143.  
  144.     var bt = document.getElementById("bookmarks-ptf");
  145.     bt.focus(); // buttons in the bt have -moz-user-focus: ignore
  146.  
  147.     this._selection   = this.getBTSelection(target);
  148.     this._orientation = this.getBTOrientation(aEvent, target);
  149.     if (targettype != "ImmutableBookmark")
  150.       this._target = this.getBTTarget(target, this._orientation);
  151.  
  152.     // walk up the tree until we find a database node
  153.     var p = target;
  154.     while (p && !p.database)
  155.       p = p.parentNode;
  156.     if (p)
  157.       this._db = p.database;
  158.  
  159.     BookmarksCommand.createContextMenu(aEvent, this._selection, this._db);
  160.     this.onCommandUpdate();
  161.     aEvent.target.addEventListener("mousemove", BookmarksMenuController.onMouseMove, false);
  162.     return true;
  163.   },
  164.  
  165.   /////////////////////////////////////////////////////////////////////////
  166.   // Clean up after closing the context menu popup
  167.   destroyContextMenu: function (aEvent)
  168.   {
  169.     if (content)
  170.       content.focus();
  171.     // XXXpch: see bug 210910, it should be done properly in the backend
  172.     BookmarksMenuDNDObserver.mCurrentDragOverTarget = null;
  173.     BookmarksMenuDNDObserver.onDragCloseTarget();
  174.  
  175.     BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode);
  176.  
  177.     aEvent.target.removeEventListener("mousemove", BookmarksMenuController.onMouseMove, false)
  178.   },
  179.  
  180.   /////////////////////////////////////////////////////////////////////////////
  181.   // returns the formatted selection from aNode
  182.   getBTSelection: function (aNode)
  183.   {
  184.     var item;
  185.     switch (aNode.id) {
  186.     case "bookmarks-ptf":
  187.       item = BMSVC.getBookmarksToolbarFolder().Value;
  188.       break;
  189.     case "bookmarks-menu":
  190.       item = "NC:BookmarksRoot";
  191.       break;
  192.     default:
  193.       item = aNode.id;
  194.       if (!this.isBTBookmark(item))
  195.         return {length:0};
  196.     }
  197.     var parent           = this.getBTContainer(aNode);
  198.     var isExpanded       = aNode.hasAttribute("open") && aNode.open;
  199.     var selection        = {};
  200.     selection.item       = [RDF.GetResource(item)];
  201.     selection.parent     = [RDF.GetResource(parent)];
  202.     selection.isExpanded = [isExpanded];
  203.     selection.length     = selection.item.length;
  204.     BookmarksUtils.checkSelection(selection);
  205.     return selection;
  206.   },
  207.  
  208.   /////////////////////////////////////////////////////////////////////////
  209.   // returns the insertion target from aNode
  210.   getBTTarget: function (aNode, aOrientation)
  211.   {
  212.     var item, parent, index;
  213.     switch (aNode.id) {
  214.     case "bookmarks-ptf":
  215.       parent = BMSVC.getBookmarksToolbarFolder().Value;
  216.       item = BookmarksToolbar.getLastVisibleBookmark();
  217.       break;
  218.     case "bookmarks-menu":
  219.       parent = "NC:BookmarksRoot";
  220.       break;
  221.     case "bookmarks-chevron":
  222.       parent = BMSVC.getBookmarksToolbarFolder().Value;
  223.       break;
  224.     default:
  225.       if (aOrientation == BookmarksUtils.DROP_ON)
  226.         parent = aNode.id
  227.       else {
  228.         parent = this.getBTContainer(aNode);
  229.         item = aNode;
  230.       }
  231.     }
  232.  
  233.     parent = RDF.GetResource(parent);
  234.     if (aOrientation == BookmarksUtils.DROP_ON)
  235.       return BookmarksUtils.getTargetFromFolder(parent);
  236.  
  237.     item = RDF.GetResource(item.id);
  238.     RDFC.Init(BMDS, parent);
  239.     index = RDFC.IndexOf(item);
  240.     if (aOrientation == BookmarksUtils.DROP_AFTER)
  241.       ++index;
  242.  
  243.     return { parent: parent, index: index };
  244.   },
  245.  
  246.   /////////////////////////////////////////////////////////////////////////
  247.   // returns the parent resource of a node in the personal toolbar.
  248.   // this is determined by inspecting the source element and walking up the 
  249.   // DOM tree to find the appropriate containing node.
  250.   getBTContainer: function (aNode)
  251.   {
  252.     var parent;
  253.     var item = aNode.id;
  254.     if (!this.isBTBookmark(item))
  255.       return "NC:BookmarksRoot"
  256.     parent = aNode.parentNode.parentNode;
  257.     parent = parent.id;
  258.     switch (parent) {
  259.     case "bookmarks-chevron":
  260.     case "bookmarks-stack":
  261.     case "bookmarks-toolbar":
  262.       return BMSVC.getBookmarksToolbarFolder().Value;
  263.     case "bookmarks-menu":
  264.       return "NC:BookmarksRoot";
  265.     default:
  266.       return parent;
  267.     }
  268.   },
  269.  
  270.   ///////////////////////////////////////////////////////////////////////////
  271.   // returns true if the node is a bookmark, a folder or a bookmark separator
  272.   isBTBookmark: function (aURI)
  273.   {
  274.     if (!aURI)
  275.       return false;
  276.     var type = BookmarksUtils.resolveType(aURI);
  277.     return (type == "BookmarkSeparator"     ||
  278.             type == "Bookmark"              ||
  279.             type == "Folder"                ||
  280.             type == "PersonalToolbarFolder" ||
  281.             type == "Livemark"              ||
  282.             type == "ImmutableBookmark"     ||
  283.             type == "ImmutableFolder"       ||
  284.             aURI == "bookmarks-ptf")
  285.   },
  286.  
  287.   /////////////////////////////////////////////////////////////////////////
  288.   // returns true if the node is a container. -->
  289.   isBTContainer: function (aTarget)
  290.   {
  291.     return  aTarget.localName == "menu" || (aTarget.localName == "toolbarbutton" &&
  292.            (aTarget.getAttribute("container") == "true"));
  293.   },
  294.  
  295.   /////////////////////////////////////////////////////////////////////////
  296.   // returns BookmarksUtils.DROP_BEFORE, DROP_ON or DROP_AFTER accordingly
  297.   // to the event coordinates. Skin authors could break us, we'll cross that 
  298.   // bridge when they turn us 90degrees.  -->
  299.   getBTOrientation: function (aEvent, aTarget)
  300.   {
  301.     var target
  302.     if (!aTarget)
  303.       target = aEvent.target;
  304.     else
  305.       target = aTarget;
  306.     if (target.localName == "menu"                 &&
  307.         target.parentNode.localName != "menupopup" ||
  308.         target.id == "bookmarks-chevron")
  309.       return BookmarksUtils.DROP_ON;
  310.     if (target.id == "bookmarks-ptf") {
  311.       return target.hasChildNodes()?
  312.              BookmarksUtils.DROP_AFTER:BookmarksUtils.DROP_ON;
  313.     }
  314.  
  315.     var overButtonBoxObject = target.boxObject.QueryInterface(Components.interfaces.nsIBoxObject);
  316.     var overParentBoxObject = target.parentNode.boxObject.QueryInterface(Components.interfaces.nsIBoxObject);
  317.  
  318.     var size, border;
  319.     var coordValue, clientCoordValue;
  320.     switch (target.localName) {
  321.       case "toolbarseparator":
  322.       case "toolbarbutton":
  323.         size = overButtonBoxObject.width;
  324.         coordValue = overButtonBoxObject.x;
  325.         clientCoordValue = aEvent.clientX;
  326.         break;
  327.       case "menuseparator": 
  328.       case "menu":
  329.       case "menuitem":
  330.         size = overButtonBoxObject.height;
  331.         coordValue = overButtonBoxObject.screenY;
  332.         clientCoordValue = aEvent.screenY;
  333.         break;
  334.       default: return BookmarksUtils.DROP_ON;
  335.     }
  336.     if (this.isBTContainer(target))
  337.       if (target.localName == "toolbarbutton") {
  338.         // the DROP_BEFORE area excludes the label
  339.         var iconNode = document.getAnonymousElementByAttribute(target, "class", "toolbarbutton-icon");
  340.         border = parseInt(document.defaultView.getComputedStyle(target,"").getPropertyValue("padding-left")) +
  341.                  parseInt(document.defaultView.getComputedStyle(iconNode     ,"").getPropertyValue("width"));
  342.         border = Math.min(size/5,Math.max(border,4));
  343.       } else
  344.         border = size/5;
  345.     else
  346.       border = size/2;
  347.  
  348.     // in the first region?
  349.     if (clientCoordValue-coordValue < border)
  350.       return BookmarksUtils.DROP_BEFORE;
  351.     // in the last region?
  352.     else if (clientCoordValue-coordValue >= size-border)
  353.       return BookmarksUtils.DROP_AFTER;
  354.     else // must be in the middle somewhere
  355.       return BookmarksUtils.DROP_ON;
  356.   },
  357.  
  358.   /////////////////////////////////////////////////////////////////////////
  359.   // expand the folder targeted by the context menu.
  360.   expandBTFolder: function ()
  361.   {
  362.     var target = document.popupNode.lastChild;
  363.     if (document.popupNode.open)
  364.       target.hidePopup();
  365.     else
  366.       target.showPopup(document.popupNode);
  367.   },
  368.  
  369.   onCommandUpdate: function ()
  370.   {
  371.     var selection = this._selection;
  372.     var target    = this._target;
  373.     BookmarksController.onCommandUpdate(selection, target);
  374.     if (document.popupNode.id == "bookmarks-ptf") {
  375.       // disabling 'cut' and 'copy' on the empty area of the personal toolbar
  376.       var commandNode = document.getElementById("cmd_cut");
  377.       commandNode.setAttribute("disabled", "true");
  378.       commandNode = document.getElementById("cmd_copy");
  379.       commandNode.setAttribute("disabled", "true");
  380.     }
  381.   },
  382.  
  383.   ///////////////////////////////////////////////////////////////
  384.   // Load a bookmark in menus or toolbar buttons
  385.   // aTarget may not the aEvent target (see Open in tabs command)
  386.   loadBookmark: function (aEvent, aTarget, aDS)
  387.   {
  388.     if (aTarget.getAttribute("class") == "openintabs-menuitem")
  389.       aTarget = aTarget.parentNode.parentNode;
  390.       
  391.     // Check for invalid bookmarks (most likely a static menu item like "Manage Bookmarks")
  392.     if (!this.isBTBookmark(aTarget.id))
  393.       return;
  394.     var rSource   = RDF.GetResource(aTarget.id);
  395.     var selection = BookmarksUtils.getSelectionFromResource(rSource);
  396.     var browserTarget = whereToOpenLink(aEvent);
  397.     BookmarksCommand.openBookmark(selection, browserTarget, aDS);
  398.     aEvent.preventBubble();
  399.   },
  400.  
  401.   ////////////////////////////////////////////////
  402.   // loads a bookmark with the mouse middle button
  403.   loadBookmarkMiddleClick: function (aEvent, aDS)
  404.   {
  405.     if (aEvent.button != 1)
  406.       return;
  407.     // unlike for command events, we have to close the menus manually
  408.     BookmarksMenuDNDObserver.mCurrentDragOverTarget = null;
  409.     BookmarksMenuDNDObserver.onDragCloseTarget();
  410.     this.loadBookmark(aEvent, aEvent.target, aDS);
  411.   }
  412. }
  413.  
  414. var BookmarksMenuController = {
  415.  
  416.   supportsCommand: BookmarksController.supportsCommand,
  417.  
  418.   isCommandEnabled: function (aCommand)
  419.   {
  420.     var selection = BookmarksMenu._selection;
  421.     var target    = BookmarksMenu._target;
  422.     if (selection)
  423.       return BookmarksController.isCommandEnabled(aCommand, selection, target);
  424.     return false;
  425.   },
  426.  
  427.   doCommand: function (aCommand)
  428.   {
  429.     if (content)
  430.       content.focus();
  431.     BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode);
  432.  
  433.     var element = document.popupNode.firstChild;
  434.     if (element && element.localName == "menupopup")
  435.       element.hidePopup();
  436.  
  437.     var selection = BookmarksMenu._selection;
  438.     var target    = BookmarksMenu._target;
  439.     var db        = BookmarksMenu._db;
  440.     switch (aCommand) {
  441.     case "cmd_bm_expandfolder":
  442.       BookmarksMenu.expandBTFolder();
  443.       break;
  444.     default:
  445.       BookmarksController.doCommand(aCommand, selection, target, db);
  446.     }
  447.   },
  448.  
  449.   onMouseMove: function (aEvent)
  450.   {
  451.     var command = aEvent.target.getAttribute("command");
  452.     var isDisabled = aEvent.target.getAttribute("disabled")
  453.     if (isDisabled != "true" && (command == "cmd_bm_newfolder" || command == "cmd_paste")) {
  454.       BookmarksMenuDNDObserver.onDragSetFeedBack(document.popupNode, BookmarksMenu._orientation);
  455.     } else {
  456.       BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode);
  457.     }
  458.   }
  459. }
  460.  
  461. var BookmarksMenuDNDObserver = {
  462.  
  463.   ////////////////////
  464.   // Public methods //
  465.   ////////////////////
  466.  
  467.   onDragStart: function (aEvent, aXferData, aDragAction)
  468.   {
  469.     var target = aEvent.target;
  470.  
  471.     // Prevent dragging from invalid regions
  472.  
  473.     // can't drag from the empty areas
  474.     if (target.id == "bookmarks-menu" ||
  475.         target.id == "bookmarks-chevron" ||
  476.         target.id == "bookmarks-ptf")
  477.       return false;
  478.  
  479.     if (!BookmarksMenu.isBTBookmark(target.id))
  480.       return false;
  481.  
  482.     // Prevent dragging out of menupopups on non Win32 platforms. 
  483.     // a) on Mac drag from menus is generally regarded as being satanic
  484.     // b) on Linux, this causes an X-server crash, (bug 151336)
  485.     // c) on Windows, there is no hang or crash associated with this, so we'll leave 
  486.     // the functionality there. 
  487.     if (navigator.platform != "Win32" && target.localName != "toolbarbutton")
  488.       return;
  489.  
  490.     // a drag start is fired when leaving an open toolbarbutton(type=menu) 
  491.     // (see bug 143031)
  492.     if (this.isContainer(target)) {
  493.       if (this.isPlatformNotSupported) 
  494.         return;
  495.       if (!aEvent.shiftKey && !aEvent.altKey && !aEvent.ctrlKey)
  496.         return;
  497.       // menus open on mouse down
  498.       target.firstChild.hidePopup();
  499.     }
  500.     var selection  = BookmarksMenu.getBTSelection(target);
  501.     aXferData.data = BookmarksUtils.getXferDataFromSelection(selection);
  502.   },
  503.  
  504.   onDragOver: function(aEvent, aFlavour, aDragSession) 
  505.   {
  506.     var orientation = BookmarksMenu.getBTOrientation(aEvent)
  507.     if (aDragSession.canDrop)
  508.       this.onDragSetFeedBack(aEvent.target, orientation);
  509.     if (orientation != this.mCurrentDropPosition) {
  510.       // emulating onDragExit and onDragEnter events since the drop region
  511.       // has changed on the target.
  512.       this.onDragExit(aEvent, aDragSession);
  513.       this.onDragEnter(aEvent, aDragSession);
  514.     }
  515.     if (this.isPlatformNotSupported)
  516.       return;
  517.     if (this.isTimerSupported)
  518.       return;
  519.     this.onDragOverCheckTimers();
  520.   },
  521.  
  522.   onDragEnter: function (aEvent, aDragSession)
  523.   {
  524.     var target = aEvent.target;
  525.     var orientation = BookmarksMenu.getBTOrientation(aEvent);
  526.     if (target.localName == "menupopup" || target.id == "bookmarks-ptf")
  527.       target = target.parentNode;
  528.     if (aDragSession.canDrop) {
  529.       this.onDragSetFeedBack(target, orientation);
  530.       this.onDragEnterSetTimer(target, aDragSession);
  531.     }
  532.     this.mCurrentDragOverTarget = target;
  533.     this.mCurrentDropPosition   = orientation;
  534.   },
  535.  
  536.   onDragExit: function (aEvent, aDragSession)
  537.   {
  538.     var target = aEvent.target;
  539.     if (target.localName == "menupopup" || target.id == "bookmarks-ptf")
  540.       target = target.parentNode;
  541.     this.onDragRemoveFeedBack(target);
  542.     this.onDragExitSetTimer(target, aDragSession);
  543.     this.mCurrentDragOverTarget = null;
  544.     this.mCurrentDropPosition = null;
  545.   },
  546.  
  547.   onDrop: function (aEvent, aXferData, aDragSession)
  548.   {
  549.     var target = aEvent.target;
  550.     this.onDragRemoveFeedBack(target);
  551.  
  552.     var selection = BookmarksUtils.getSelectionFromXferData(aDragSession);
  553.  
  554.     var orientation = BookmarksMenu.getBTOrientation(aEvent);
  555.  
  556.     // For RTL PersonalBar bookmarks buttons, orientation should be inverted (only in drop case)
  557.     var PBStyle = window.getComputedStyle(document.getElementById("PersonalToolbar"),'');
  558.     var isHorizontal = (target.localName == "toolbarbutton");
  559.     if ((PBStyle.direction == 'rtl') && isHorizontal) {
  560.       if (orientation == BookmarksUtils.DROP_AFTER)
  561.         orientation = BookmarksUtils.DROP_BEFORE;
  562.       else if (orientation == BookmarksUtils.DROP_BEFORE)
  563.         orientation = BookmarksUtils.DROP_AFTER;
  564.     }
  565.  
  566.     var selTarget   = BookmarksMenu.getBTTarget(target, orientation);
  567.  
  568.     // we can only test for kCopyAction if the source is a bookmark
  569.     var checkCopy = aDragSession.isDataFlavorSupported("moz/rdfitem");
  570.  
  571.     const kDSIID      = Components.interfaces.nsIDragService;
  572.     const kCopyAction = kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_LINK;
  573.  
  574.     // hide the 'open in tab' menuseparator because bookmarks
  575.     // can be inserted after it if they are dropped after the last bookmark
  576.     // a more comprehensive fix would be in the menupopup template builder
  577.     var menuSeparator = null;
  578.     var menuTarget = (target.localName == "toolbarbutton" ||
  579.                       target.localName == "menu")         && 
  580.                      orientation == BookmarksUtils.DROP_ON?
  581.                      target.lastChild:target.parentNode;
  582.     if (menuTarget.hasChildNodes() &&
  583.         menuTarget.lastChild.getAttribute("class") == "openintabs-menuitem") {
  584.       menuSeparator = menuTarget.lastChild.previousSibling;
  585.       menuTarget.removeChild(menuSeparator);
  586.     }
  587.  
  588.     // doCopy defaults to true; check if we should make it false.
  589.     // we make it false only if all the selection items have valid parent
  590.     // bookmark DS containers (i.e. aren't generated via aggregation)
  591.     var doCopy = true;
  592.     if (checkCopy && !(aDragSession.dragAction & kCopyAction))
  593.       doCopy = BookmarksUtils.shouldCopySelection("drag", selection);
  594.  
  595.     if (doCopy)
  596.       BookmarksUtils.insertAndCheckSelection("drag", selection, selTarget);
  597.     else
  598.       BookmarksUtils.moveAndCheckSelection("drag", selection, selTarget);
  599.  
  600.     // show again the menuseparator
  601.     if (menuSeparator)
  602.       menuTarget.insertBefore(menuSeparator, menuTarget.lastChild);
  603.  
  604.   },
  605.  
  606.   canDrop: function (aEvent, aDragSession)
  607.   {
  608.     var target = aEvent.target;
  609.     if (!BookmarksMenu.isBTBookmark(target.id))
  610.       return false;
  611.  
  612.     var btype = BookmarksUtils.resolveType(target.id);
  613.  
  614.     return target.id == "bookmarks-menu"               ||
  615.            target.id == "bookmarks-chevron"            ||
  616.            target.id == "bookmarks-ptf" ||
  617.           (target.id != "NC:SystemBookmarksStaticRoot" &&
  618.            btype == "Folder" ||
  619.            btype == "Bookmark");
  620.   },
  621.  
  622.   canHandleMultipleItems: true,
  623.  
  624.   getSupportedFlavours: function () 
  625.   {
  626.     var flavourSet = new FlavourSet();
  627.     flavourSet.appendFlavour("moz/rdfitem");
  628.     flavourSet.appendFlavour("text/x-moz-url");
  629.     flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
  630.     flavourSet.appendFlavour("text/unicode");
  631.     return flavourSet;
  632.   }, 
  633.   
  634.  
  635.   ////////////////////////////////////
  636.   // Private methods and properties //
  637.   ////////////////////////////////////
  638.  
  639.   springLoadedMenuDelay: 350, // milliseconds
  640.   isPlatformNotSupported: navigator.platform.indexOf("Mac") != -1, // see bug 136524
  641.   isTimerSupported: navigator.platform.indexOf("Win") == -1,
  642.  
  643.   mCurrentDragOverTarget: null,
  644.   mCurrentDropPosition: null,
  645.   loadTimer  : null,
  646.   closeTimer : null,
  647.   loadTarget : null,
  648.   closeTarget: null,
  649.  
  650.   _observers : null,
  651.   get mObservers ()
  652.   {
  653.     if (!this._observers) {
  654.       this._observers = [
  655.         document.getElementById("bookmarks-ptf"),
  656.         document.getElementById("bookmarks-menu").firstChild,
  657.         document.getElementById("bookmarks-chevron").parentNode
  658.       ]
  659.     }
  660.     return this._observers;
  661.   },
  662.  
  663.   getObserverForNode: function (aNode)
  664.   {
  665.     if (!aNode)
  666.       return null;
  667.     var node = aNode;
  668.     var observer;
  669.     while (node) {
  670.       for (var i=0; i < this.mObservers.length; i++) {
  671.         observer = this.mObservers[i];
  672.         if (observer == node)
  673.           return observer;
  674.       }
  675.       node = node.parentNode;
  676.     }
  677.     return null;
  678.   },
  679.  
  680.   onDragCloseMenu: function (aNode)
  681.   {
  682.     var children = aNode.childNodes;
  683.     for (var i = 0; i < children.length; i++) {
  684.       if (this.isContainer(children[i]) && 
  685.           children[i].getAttribute("open") == "true") {
  686.         this.onDragCloseMenu(children[i].lastChild);
  687.         if (children[i] != this.mCurrentDragOverTarget || this.mCurrentDropPosition != BookmarksUtils.DROP_ON)
  688.           children[i].lastChild.hidePopup();
  689.       }
  690.     } 
  691.   },
  692.  
  693.   onDragCloseTarget: function ()
  694.   {
  695.     var currentObserver = this.getObserverForNode(this.mCurrentDragOverTarget);
  696.     // close all the menus not hovered by the mouse
  697.     for (var i=0; i < this.mObservers.length; i++) {
  698.       if (currentObserver != this.mObservers[i]) {
  699.         this.onDragCloseMenu(this.mObservers[i]);
  700.         if (this.mObservers[i].parentNode.id == "bookmarks-menu")
  701.           this.mObservers[i].hidePopup();
  702.       } else
  703.         this.onDragCloseMenu(this.mCurrentDragOverTarget.parentNode);
  704.     }
  705.   },
  706.  
  707.   onDragLoadTarget: function (aTarget) 
  708.   {
  709.     if (!this.mCurrentDragOverTarget)
  710.       return;
  711.     // Load the current menu
  712.     if (this.mCurrentDropPosition == BookmarksUtils.DROP_ON && 
  713.         this.isContainer(aTarget))
  714.       aTarget.lastChild.showPopup(aTarget);
  715.   },
  716.  
  717.   onDragOverCheckTimers: function ()
  718.   {
  719.     var now = new Date().getTime();
  720.     if (this.closeTimer && now-this.springLoadedMenuDelay>this.closeTimer) {
  721.       this.onDragCloseTarget();
  722.       this.closeTimer = null;
  723.     }
  724.     if (this.loadTimer && (now-this.springLoadedMenuDelay>this.loadTimer)) {
  725.       this.onDragLoadTarget(this.loadTarget);
  726.       this.loadTimer = null;
  727.     }
  728.   },
  729.  
  730.   onDragEnterSetTimer: function (aTarget, aDragSession)
  731.   {
  732.     if (this.isPlatformNotSupported)
  733.       return;
  734.     if (this.isTimerSupported) {
  735.       var targetToBeLoaded = aTarget;
  736.       clearTimeout(this.loadTimer);
  737.       if (aTarget == aDragSession.sourceNode)
  738.         return;
  739.       var This = this;
  740.       this.loadTimer=setTimeout(function () {This.onDragLoadTarget(targetToBeLoaded)}, This.springLoadedMenuDelay);
  741.     } else {
  742.       var now = new Date().getTime();
  743.       this.loadTimer  = now;
  744.       this.loadTarget = aTarget;
  745.     }
  746.   },
  747.  
  748.   onDragExitSetTimer: function (aTarget, aDragSession)
  749.   {
  750.     if (this.isPlatformNotSupported)
  751.       return;
  752.     var This = this;
  753.     if (this.isTimerSupported) {
  754.       clearTimeout(this.closeTimer)
  755.       this.closeTimer=setTimeout(function () {This.onDragCloseTarget()}, This.springLoadedMenuDelay);
  756.     } else {
  757.       var now = new Date().getTime();
  758.       this.closeTimer  = now;
  759.       this.closeTarget = aTarget;
  760.       this.loadTimer = null;
  761.  
  762.       // If user isn't rearranging within the menu, close it
  763.       // To do so, we exploit a Mac bug: timeout set during
  764.       // drag and drop on Windows and Mac are fired only after that the drop is released.
  765.       // timeouts will pile up, we may have a better approach but for the moment, this one
  766.       // correctly close the menus after a drop/cancel outside the personal toolbar.
  767.       // The if statement in the function has been introduced to deal with rare but reproducible
  768.       // missing Exit events.
  769.       if (aDragSession.sourceNode.localName != "menuitem" && aDragSession.sourceNode.localName != "menu")
  770.         setTimeout(function () { if (This.mCurrentDragOverTarget) {This.onDragRemoveFeedBack(This.mCurrentDragOverTarget); This.mCurrentDragOverTarget=null} This.loadTimer=null; This.onDragCloseTarget() }, 0);
  771.     }
  772.   },
  773.  
  774.   onDragSetFeedBack: function (aTarget, aOrientation)
  775.   {
  776.    switch (aTarget.localName) {
  777.       case "toolbarseparator":
  778.       case "toolbarbutton":
  779.         switch (aOrientation) {
  780.           case BookmarksUtils.DROP_BEFORE: 
  781.             aTarget.setAttribute("dragover-left", "true");
  782.             break;
  783.           case BookmarksUtils.DROP_AFTER:
  784.             aTarget.setAttribute("dragover-right", "true");
  785.             break;
  786.           case BookmarksUtils.DROP_ON:
  787.             aTarget.setAttribute("dragover-top"   , "true");
  788.             aTarget.setAttribute("dragover-bottom", "true");
  789.             aTarget.setAttribute("dragover-left"  , "true");
  790.             aTarget.setAttribute("dragover-right" , "true");
  791.             break;
  792.         }
  793.         break;
  794.       case "menuseparator": 
  795.       case "menu":
  796.       case "menuitem":
  797.         switch (aOrientation) {
  798.           case BookmarksUtils.DROP_BEFORE: 
  799.             aTarget.setAttribute("dragover-top", "true");
  800.             break;
  801.           case BookmarksUtils.DROP_AFTER:
  802.             aTarget.setAttribute("dragover-bottom", "true");
  803.             break;
  804.           case BookmarksUtils.DROP_ON:
  805.             break;
  806.         }
  807.         break;
  808.       case "hbox"     : 
  809.         // hit between the last visible bookmark and the chevron
  810.         var newTarget = BookmarksToolbar.getLastVisibleBookmark();
  811.         if (newTarget)
  812.           newTarget.setAttribute("dragover-right", "true");
  813.         break;
  814.       case "stack"    :
  815.       case "menupopup": break; 
  816.      default: dump("No feedback for: "+aTarget.localName+"\n");
  817.     }
  818.   },
  819.  
  820.   onDragRemoveFeedBack: function (aTarget)
  821.   { 
  822.     var newTarget;
  823.     var bt;
  824.     if (aTarget.id == "bookmarks-ptf") { 
  825.       // hit when dropping in the bt or between the last visible bookmark 
  826.       // and the chevron
  827.       newTarget = BookmarksToolbar.getLastVisibleBookmark();
  828.       if (newTarget)
  829.         newTarget.removeAttribute("dragover-right");
  830.     } else if (aTarget.id == "bookmarks-stack") {
  831.       newTarget = BookmarksToolbar.getLastVisibleBookmark();
  832.       newTarget.removeAttribute("dragover-right");
  833.     } else {
  834.       aTarget.removeAttribute("dragover-left");
  835.       aTarget.removeAttribute("dragover-right");
  836.       aTarget.removeAttribute("dragover-top");
  837.       aTarget.removeAttribute("dragover-bottom");
  838.     }
  839.   },
  840.  
  841.   onDropSetFeedBack: function (aTarget)
  842.   {
  843.     //XXX Not yet...
  844.   },
  845.  
  846.   isContainer: function (aTarget)
  847.   {
  848.     return aTarget.localName == "menu"          || 
  849.            aTarget.localName == "toolbarbutton" &&
  850.            aTarget.getAttribute("type") == "menu";
  851.   }
  852. }
  853.  
  854. var BookmarksToolbar = 
  855. {
  856.   /////////////////////////////////////////////////////////////////////////////
  857.   // make bookmarks toolbar act like menus
  858.   openedMenuButton:null,
  859.   autoOpenMenu: function (aEvent)
  860.   {
  861.     var target = aEvent.target;
  862.     if (BookmarksToolbar.openedMenuButton != target &&
  863.         target.nodeName == "toolbarbutton" &&
  864.         target.type == "menu") {
  865.       BookmarksToolbar.openedMenuButton.open = false;
  866.       target.open = true;
  867.     }
  868.   },
  869.   setOpenedMenu: function (aEvent)
  870.   {
  871.     if (aEvent.target.parentNode.localName == 'toolbarbutton') {
  872.       if (!this.openedMenuButton)
  873.         aEvent.currentTarget.addEventListener("mouseover", this.autoOpenMenu, true);
  874.       this.openedMenuButton = aEvent.target.parentNode;
  875.     }
  876.   },
  877.   unsetOpenedMenu: function (aEvent)
  878.   {
  879.     if (aEvent.target.parentNode.localName == 'toolbarbutton') {
  880.       aEvent.currentTarget.removeEventListener("mouseover", this.autoOpenMenu, true);
  881.       this.openedMenuButton = null;
  882.     }
  883.   },
  884.  
  885.   /////////////////////////////////////////////////////////////////////////////
  886.   // returns the node of the last visible bookmark on the toolbar -->
  887.   getLastVisibleBookmark: function ()
  888.   {
  889.     var buttons = document.getElementById("bookmarks-ptf");
  890.     var button = buttons.firstChild;
  891.     if (!button)
  892.       return null; // empty bookmarks toolbar
  893.     do {
  894.       if (button.collapsed)
  895.         return button.previousSibling;
  896.       button = button.nextSibling;
  897.     } while (button)
  898.     return buttons.lastChild;
  899.   },
  900.  
  901.   updateOverflowMenu: function (aMenuPopup)
  902.   {
  903.     var hbox = document.getElementById("bookmarks-ptf");
  904.     for (var i = 0; i < hbox.childNodes.length; i++) {
  905.       var button = hbox.childNodes[i];
  906.       var menu = aMenuPopup.childNodes[i];
  907.       if (menu.collapsed == button.collapsed)
  908.         menu.collapsed = !menu.collapsed;
  909.     }
  910.   },
  911.  
  912.   resizeFunc: function(event) 
  913.   { 
  914.     var buttons = document.getElementById("bookmarks-ptf");
  915.     if (!buttons)
  916.       return;
  917.     var chevron = document.getElementById("bookmarks-chevron");
  918.     var width = window.innerWidth;
  919.     var myToolbar = buttons.parentNode.parentNode.parentNode;
  920.     for (var i = myToolbar.childNodes.length-1; i >= 0; i--){
  921.       var anItem = myToolbar.childNodes[i];
  922.       if (anItem.id == "personal-bookmarks") {
  923.         break;
  924.       }
  925.       width -= anItem.boxObject.width;
  926.     }
  927.     var chevronWidth = 0;
  928.     chevron.collapsed = false;
  929.     chevronWidth = chevron.boxObject.width;
  930.     chevron.collapsed = true;
  931.     var overflowed = false;
  932.  
  933.     var isLTR=window.getComputedStyle(document.getElementById("PersonalToolbar"),'').direction=='ltr';
  934.  
  935.     for (var i=0; i<buttons.childNodes.length; i++) {
  936.       var button = buttons.childNodes[i];
  937.       button.collapsed = overflowed;
  938.       
  939.       if (i == buttons.childNodes.length - 1) // last ptf item...
  940.         chevronWidth = 0;
  941.       var offset = isLTR ? button.boxObject.x 
  942.                          : width - button.boxObject.x;
  943.       if (offset + button.boxObject.width + chevronWidth > width) {
  944.          overflowed = true;
  945.         // This button doesn't fit. Show it in the menu. Hide it in the toolbar.
  946.         if (!button.collapsed)
  947.           button.collapsed = true;
  948.         if (chevron.collapsed) {
  949.           chevron.collapsed = false;
  950.           var overflowPadder = document.getElementById("overflow-padder");
  951.           offset = isLTR ? buttons.boxObject.x 
  952.                          : width - buttons.boxObject.x - buttons.boxObject.width;
  953.           overflowPadder.width = width - chevron.boxObject.width - offset;
  954.         }
  955.       }
  956.     }
  957.     BookmarksToolbarRDFObserver._overflowTimerInEffect = false;
  958.   },
  959.  
  960.   // Fill in tooltips for personal toolbar
  961.   fillInBTTooltip: function (tipElement)
  962.   {
  963.  
  964.     var title = tipElement.label;
  965.     var url = tipElement.statusText;
  966.  
  967.     if (!title && !url) {
  968.       // bail out early if there is nothing to show
  969.       return false;
  970.     }
  971.  
  972.     var tooltipTitle = document.getElementById("btTitleText");
  973.     var tooltipUrl = document.getElementById("btUrlText"); 
  974.     if (title && title != url) {
  975.       tooltipTitle.removeAttribute("hidden");
  976.       tooltipTitle.setAttribute("value", title);
  977.     } else  {
  978.       tooltipTitle.setAttribute("hidden", "true");
  979.     }
  980.     if (url) {
  981.       tooltipUrl.removeAttribute("hidden");
  982.       tooltipUrl.setAttribute("value", url);
  983.     } else {
  984.       tooltipUrl.setAttribute("hidden", "true");
  985.     }
  986.     return true; // show tooltip
  987.   }
  988. }
  989.  
  990. // Implement nsIRDFObserver so we can update our overflow state when items get
  991. // added/removed from the toolbar
  992. var BookmarksToolbarRDFObserver =
  993. {
  994.   onAssert: function (aDataSource, aSource, aProperty, aTarget)
  995.   {
  996.     if (aProperty.Value == NC_NS+"BookmarksToolbarFolder") {
  997.       var bt = document.getElementById("bookmarks-ptf");
  998.       if (bt) {
  999.         bt.ref = aSource.Value;
  1000.         document.getElementById("bookmarks-chevron").ref = aSource.Value;
  1001.       }
  1002.     }
  1003.     this.setOverflowTimeout(aSource, aProperty);
  1004.   },
  1005.   onUnassert: function (aDataSource, aSource, aProperty, aTarget)
  1006.   {
  1007.     this.setOverflowTimeout(aSource, aProperty);
  1008.   },
  1009.   onChange: function (aDataSource, aSource, aProperty, aOldTarget, aNewTarget) {},
  1010.   onMove: function (aDataSource, aOldSource, aNewSource, aProperty, aTarget) {},
  1011.   onBeginUpdateBatch: function (aDataSource) {},
  1012.   onEndUpdateBatch: function (aDataSource)
  1013.   {
  1014.     this._overflowTimerInEffect = true;
  1015.     setTimeout(BookmarksToolbar.resizeFunc, 0);
  1016.   },
  1017.   _overflowTimerInEffect: false,
  1018.   setOverflowTimeout: function (aSource, aProperty)
  1019.   {
  1020.     if (this._overflowTimerInEffect)
  1021.       return;
  1022.     if (aSource != BMSVC.getBookmarksToolbarFolder()
  1023.         || aProperty.Value == NC_NS+"LastModifiedDate")
  1024.       return;
  1025.     this._overflowTimerInEffect = true;
  1026.     setTimeout(BookmarksToolbar.resizeFunc, 0);
  1027.   }
  1028. }
  1029.