home *** CD-ROM | disk | FTP | other *** search
/ ftp.swcp.com / ftp.swcp.com.zip / ftp.swcp.com / mac / mozilla-macos9-1.3.1.sea.bin / Mozilla1.3.1 / Chrome / comm.jar / content / communicator / bookmarks / bookmarksTree.js < prev    next >
Text File  |  2003-06-08  |  31KB  |  760 lines

  1. /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: NPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Netscape Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/NPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is mozilla.org code.
  16.  *
  17.  * The Initial Developer of the Original Code is 
  18.  * Netscape Communications Corporation.
  19.  * Portions created by the Initial Developer are Copyright (C) 1998
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Ben Goodger <ben@netscape.com> (Original Author)
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or 
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the NPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the NPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. var gBookmarksShell = null;
  40.  
  41. ///////////////////////////////////////////////////////////////////////////////
  42. // Tracks the selected item, the cell last clicked on, and the number of clicks
  43. // given to it. Used to activate inline edit mode.
  44. var gSelectionTracker = { currentItem: null, currentCell: null, clickCount: 0 };
  45.  
  46. ///////////////////////////////////////////////////////////////////////////////
  47. // Class which defines methods for a bookmarks UI implementation based around
  48. // a treeview. Subclasses BookmarksBase in bookmarksOverlay.js. Some methods
  49. // are required by the base class, others are for event handling. Window specific
  50. // glue code should go into the BookmarksWindow class in bookmarks.js
  51. function BookmarksTree (aID)
  52. {
  53.   this.id = aID;
  54. }
  55.  
  56. BookmarksTree.prototype = {
  57.   __proto__: BookmarksUIElement.prototype,
  58.  
  59.   // XXX - change this to .element and move into base. 
  60.   get tree ()
  61.   {
  62.     return document.getElementById(this.id);
  63.   },
  64.  
  65.   /////////////////////////////////////////////////////////////////////////////
  66.   // This method constructs a menuitem for a context menu for the given command.
  67.   // This is implemented by the client so that it can intercept menuitem naming
  68.   // as appropriate.
  69.   createMenuItem: function (aDisplayName, aCommandName, aItemNode)
  70.   {
  71.     const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  72.     var xulElement = document.createElementNS(kXULNS, "menuitem");
  73.     xulElement.setAttribute("cmd", aCommandName);
  74.     xulElement.setAttribute("command", "cmd_" + aCommandName.substring(NC_NS_CMD.length));
  75.  
  76.     switch (aCommandName) {
  77.     case NC_NS_CMD + "open":
  78.       xulElement.setAttribute("label", aDisplayName);
  79.       xulElement.setAttribute("default", "true");
  80.       break;
  81.     case NC_NS_CMD + "openfolder":
  82.       aDisplayName = aItemNode.getAttribute("open") == "true" ? this.getLocaleString("cmd_openfolder2") : aDisplayName;
  83.       xulElement.setAttribute("label", aDisplayName);
  84.       xulElement.setAttribute("default", "true");
  85.       break;
  86.     case NC_NS_CMD + "renamebookmark":
  87.       if (!document.popupNode.hasAttribute("type")) {
  88.         xulElement.setAttribute("label", this.getLocaleString("cmd_renamebookmark2"));
  89.         xulElement.setAttribute("cmd", (NC_NS_CMD + "editurl"));
  90.       }
  91.       else
  92.         xulElement.setAttribute("label", aDisplayName);
  93.       break;
  94.     default:
  95.       xulElement.setAttribute("label", aDisplayName);
  96.       break;
  97.     }
  98.     return xulElement;
  99.   },
  100.  
  101.   // XXX - ideally this would be in the base. this.tree needs to change to 
  102.   // this.element and then we can do just that. 
  103.   setRoot: function (aRoot)
  104.   {
  105.     this.tree.setAttribute("ref", aRoot);
  106.   },
  107.  
  108.   // Command implementation
  109.   commands: {
  110.     openFolder: function (aSelectedItem)
  111.     {
  112.       if (aSelectedItem.getAttribute("open") == "true")
  113.         aSelectedItem.removeAttribute("open");
  114.       else
  115.         aSelectedItem.setAttribute("open", "true");
  116.     },
  117.  
  118.     // Things Needed to Satisfy Mac Weenies:
  119.     // 1) need to implement timed single click edit. This could be Hard.
  120.     // 2) need to implement some other method of key access apart from F2.
  121.     //    mpt claims that 'Cmd+U' is the excel equivalent.
  122.     editCell: function (aSelectedItem, aCell)
  123.     {
  124.       // XXX throw up properties dialog with name selected so user can rename
  125.       //     that way, until tree conversion allows us to use IL again. 
  126.       goDoCommand("cmd_properties");
  127.       return; // Disable inline edit for now.
  128.  
  129.       var editCell = aSelectedItem.firstChild.childNodes[aCell];
  130.       if (editCell.getAttribute("editable") != "true")
  131.         return;
  132.       
  133.       // Cause the inline edit cell binding to be used.
  134.       editCell.setAttribute("class", "treecell-indent treecell-editable");
  135.       var editColGroup = document.getElementById("theColumns");
  136.       var count = 0;
  137.       var property = "";
  138.       for (var i = 0; i < editColGroup.childNodes.length; ++i) {
  139.         var currCol = editColGroup.childNodes[i];
  140.         if (currCol.getAttribute("hidden") == "true")
  141.           return;
  142.         if (count == aCell) {
  143.           property = currCol.getAttribute("resource");
  144.           break;
  145.         }
  146.         ++count;
  147.  
  148.         // Deal with interleaved column resizer splitters
  149.         if (currCol.nextSibling.localName == "splitter") ++i;
  150.       }
  151.  
  152.       if (property) {
  153.         editCell.setMode("edit");
  154.         editCell.addObserver(this.postModifyCallback, "accept",
  155.                              [editCell, aSelectedItem, property]);
  156.       }
  157.     },
  158.  
  159.     ///////////////////////////////////////////////////////////////////////////
  160.     // Called after an inline-edit cell has left inline-edit mode, and data
  161.     // needs to be modified in the datasource.
  162.     postModifyCallback: function (aParams)
  163.     {
  164.       var selItemURI = NODE_ID(aParams[1]);
  165.       gBookmarksShell.propertySet(selItemURI, aParams[2], aParams[3]);
  166.       gBookmarksShell.selectFolderItem(NODE_ID(gBookmarksShell.findRDFNode(aParams[1], false)),
  167.                                        selItemURI, false);
  168.       gBookmarksShell.tree.focus();
  169.       gSelectionTracker.clickCount = 0;
  170.       
  171.       // Set the cell back to use the standard treecell binding.
  172.       var editCell = aParams[0];
  173.       editCell.setAttribute("class", "treecell-indent");
  174.     },
  175.  
  176.     ///////////////////////////////////////////////////////////////////////////
  177.     // New Folder Creation
  178.     // Strategy: create a dummy row with edit fields to harvest information
  179.     //           from the user, then destroy these rows and create an item
  180.     //           in its place.
  181.  
  182.     ///////////////////////////////////////////////////////////////////////////
  183.     // Edit folder name & update the datasource if name is valid
  184.     onEditFolderName: function (aParams, aTopic)
  185.     {
  186.       var name = aParams[3];
  187.       var shell = gBookmarksShell.commands; // suck
  188.       var dummyItem = aParams[2];
  189.       var relativeNode = aParams[1];
  190.       var parentNode = relativeNode ? gBookmarksShell.findRDFNode(relativeNode, false) : gBookmarksShell.tree;
  191.  
  192.       dummyItem.parentNode.removeChild(dummyItem);
  193.  
  194.       if (!shell.validateNameAndTopic(name, aTopic, relativeNode, dummyItem)) {
  195.         gBookmarksShell.tree.selectItem(relativeNode);
  196.         gBookmarksShell.tree.focus();
  197.         return;
  198.       }
  199.  
  200.       if (relativeNode) {
  201.         // If we're attempting to create a folder as a subfolder of an open folder,
  202.         // we need to set the parentFolder to be relativeNode, which will be the
  203.         // parent of the new folder, rather than the parent of the relativeNode,
  204.         // which will result in the folder being created in an incorrect position
  205.         // (adjacent to the relativeNode).
  206.         var selKids = ContentUtils.childByLocalName(relativeNode, "treechildren");
  207.         if (selKids && selKids.hasChildNodes() && selKids.lastChild == dummyItem)
  208.           parentNode = relativeNode;
  209.       }
  210.  
  211.       var args = [{ property: NC_NS + "parent",
  212.                     resource: NODE_ID(parentNode) },
  213.                   { property: NC_NS + "Name",
  214.                     literal:  name }];
  215.  
  216.       const kBMDS = gBookmarksShell.RDF.GetDataSource("rdf:bookmarks");
  217.       kBMDS.AddObserver(newFolderRDFObserver);
  218.       var relId = relativeNode ? NODE_ID(relativeNode) : "NC:BookmarksRoot";
  219.       BookmarksUtils.doBookmarksCommand(relId, NC_NS_CMD + "newfolder", args);
  220.       kBMDS.RemoveObserver(newFolderRDFObserver);
  221.       var newFolderItem = document.getElementById(newFolderRDFObserver._newFolderURI);
  222.       gBookmarksShell.tree.focus();
  223.       gBookmarksShell.tree.selectItem(newFolderItem);
  224.       // Can't use newFolderItem because it may not have been created yet. Hack, huh?
  225.       var index = gBookmarksShell.tree.getIndexOfItem(relativeNode);
  226.       gBookmarksShell.tree.ensureIndexIsVisible(index+1);
  227.       gSelectionTracker.clickCount = 0;
  228.     },
  229.  
  230.     ///////////////////////////////////////////////////////////////////////////
  231.     // Performs simple validation on what the user has entered:
  232.     //  1) prevents entering an empty string
  233.     //  2) in the case of a canceled operation, remove the dummy item and
  234.     //     restore selection.
  235.     validateNameAndTopic: function (aName, aTopic, aOldSelectedItem, aDummyItem)
  236.     {
  237.       // Don't allow user to enter an empty string "";
  238.       if (!aName) return false;
  239.  
  240.       // If the user hit escape, go no further.
  241.       if (aTopic == "reject") {
  242.         if (aOldSelectedItem)
  243.           gBookmarksShell.tree.selectItem(aOldSelectedItem);
  244.         return false;
  245.       }
  246.       return true;
  247.     },
  248.  
  249.     ///////////////////////////////////////////////////////////////////////////
  250.     // Creates a dummy item that can be placed in edit mode to retrieve data
  251.     // to create new bookmarks/folders.
  252.     createBookmarkItem: function (aMode, aSelectedItem)
  253.     {
  254.       /////////////////////////////////////////////////////////////////////////
  255.       // HACK HACK HACK HACK HACK         
  256.       // Disable Inline-Edit for now and just use a dialog. 
  257.       
  258.       // XXX - most of this is just copy-pasted from the other two folder
  259.       //       creation functions. Yes it's ugly, but it'll do the trick for 
  260.       //       now as this is in no way intended to be a long-term solution.
  261.  
  262.       const kPromptSvcContractID = "@mozilla.org/embedcomp/prompt-service;1";
  263.       const kPromptSvcIID = Components.interfaces.nsIPromptService;
  264.       const kPromptSvc = Components.classes[kPromptSvcContractID].getService(kPromptSvcIID);
  265.       
  266.       var defaultValue  = gBookmarksShell.getLocaleString("ile_newfolder");
  267.       var dialogTitle   = gBookmarksShell.getLocaleString("newfolder_dialog_title");
  268.       var dialogMsg     = gBookmarksShell.getLocaleString("newfolder_dialog_msg");
  269.       var stringValue   = { value: defaultValue };
  270.       if (kPromptSvc.prompt(window, dialogTitle, dialogMsg, stringValue, null, { value: 0 })) {
  271.         var relativeNode = gBookmarksShell.tree;
  272.         var parentNode;
  273.         if (aSelectedItem && aSelectedItem.localName != "tree") {
  274.           // By default, create adjacent to the selected item
  275.           relativeNode = aSelectedItem;
  276.           if (relativeNode.getAttribute("container") == "true" && 
  277.               relativeNode.getAttribute("open") == "true") {
  278.             // But if it's an open container, the relative node should be the last child. 
  279.             var treechildren = ContentUtils.childByLocalName(relativeNode, "treechildren");
  280.             if (treechildren && treechildren.hasChildNodes())
  281.               relativeNode = treechildren.lastChild; // folder non-empty, set relativeNode
  282.             parentNode = aSelectedItem; // no matter what, folder is open, so make it parent
  283.           } else {
  284.             parentNode = relativeNode ? gBookmarksShell.findRDFNode(relativeNode, false) : gBookmarksShell.tree;
  285.           }
  286.         }
  287.  
  288.         var args = [{ property: NC_NS + "parent",
  289.                       resource: NODE_ID(parentNode) },
  290.                     { property: NC_NS + "Name",
  291.                       literal:  stringValue.value }];
  292.         
  293.         const kBMDS = gBookmarksShell.RDF.GetDataSource("rdf:bookmarks");
  294.         kBMDS.AddObserver(newFolderRDFObserver);
  295.         var relId = relativeNode ? NODE_ID(relativeNode) : "NC:BookmarksRoot";
  296.         BookmarksUtils.doBookmarksCommand(relId, NC_NS_CMD + "newfolder", args);
  297.         kBMDS.RemoveObserver(newFolderRDFObserver);
  298.         var newFolderItem = document.getElementById(newFolderRDFObserver._newFolderURI);
  299.         gBookmarksShell.tree.focus();
  300.         gBookmarksShell.tree.selectItem(newFolderItem);
  301.         // Can't use newFolderItem because it may not have been created yet. Hack, huh?
  302.         var index = gBookmarksShell.tree.getIndexOfItem(relativeNode);
  303.         gBookmarksShell.tree.ensureIndexIsVisible(index+1);
  304.       }
  305.       
  306.       return; 
  307.       
  308.       // HACK HACK HACK HACK HACK         
  309.       /////////////////////////////////////////////////////////////////////////
  310.  
  311.       /* Disable inline edit for now
  312.       const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  313.       var dummyItem = document.createElementNS(kXULNS, "treeitem");
  314.       dummyItem = gBookmarksShell.createBookmarkFolderDecorations(dummyItem);
  315.       dummyItem.setAttribute("class", "bookmark-item");
  316.  
  317.       var dummyRow    = document.createElementNS(kXULNS, "treerow");
  318.       var dummyCell   = document.createElementNS(kXULNS, "treecell");
  319.       var dummyCell2  = document.createElementNS(kXULNS, "treecell");
  320.       dummyCell.setAttribute("label", gBookmarksShell.getLocaleString("ile_newfolder") + "  ");
  321.       dummyCell.setAttribute("type", NC_NS + "Folder");
  322.       dummyCell.setAttribute("editable", "true");
  323.       dummyCell.setAttribute("class", "treecell-indent treecell-editable");
  324.       dummyRow.appendChild(dummyCell);
  325.       dummyItem.appendChild(dummyRow);
  326.       
  327.       var relativeNode = null;
  328.  
  329.       // If there are selected items, try to create the dummy item relative to the 
  330.       // best item, and position the bookmark there when created. Otherwise just
  331.       // append to the root. 
  332.       if (aSelectedItem && aSelectedItem.localName != "tree") {
  333.         // By default, create adjacent to the selected item
  334.         relativeNode = aSelectedItem;
  335.         if (relativeNode.getAttribute("container") == "true" && 
  336.             relativeNode.getAttribute("open") == "true") {
  337.           // But if it's an open container, the relative node should be the last child. 
  338.           var treechildren = ContentUtils.childByLocalName(relativeNode, "treechildren");
  339.           if (treechildren && treechildren.hasChildNodes()) 
  340.             relativeNode = treechildren.lastChild;
  341.         }
  342.  
  343.         if (aSelectedItem.getAttribute("container") == "true") {
  344.           if (aSelectedItem.getAttribute("open") == "true") {
  345.             var treechildren = ContentUtils.childByLocalName(aSelectedItem, "treechildren");
  346.             if (!treechildren) {
  347.               treechildren = document.createElementNS(kXULNS, "treechildren");
  348.               aSelectedItem.appendChild(treechildren);
  349.             }
  350.             // Insert new item after last item.
  351.             treechildren.appendChild(dummyItem);
  352.           }
  353.           else {
  354.             if (aSelectedItem.nextSibling)
  355.               aSelectedItem.parentNode.insertBefore(dummyItem, aSelectedItem.nextSibling);
  356.             else
  357.               aSelectedItem.parentNode.appendChild(dummyItem);
  358.           }
  359.           var index = gBookmarksShell.tree.getIndexOfItem(dummyItem);
  360.           gBookmarksShell.tree.ensureIndexIsVisible(index);
  361.         }
  362.         else {
  363.           if (aSelectedItem.nextSibling)
  364.             aSelectedItem.parentNode.insertBefore(dummyItem, aSelectedItem.nextSibling);
  365.           else
  366.             aSelectedItem.parentNode.appendChild(dummyItem);
  367.         }
  368.       }
  369.       else {
  370.         // No items in the tree. Append to the root. 
  371.         var rootKids = document.getElementById("treechildren-bookmarks");
  372.         rootKids.appendChild(dummyItem);
  373.       }
  374.  
  375.       dummyCell.setMode("edit");
  376.       dummyCell.addObserver(this.onEditFolderName, "accept", [dummyCell, relativeNode, dummyItem]);
  377.       dummyCell.addObserver(this.onEditFolderName, "reject", [dummyCell, relativeNode, dummyItem]);
  378.       */
  379.     }
  380.   },
  381.  
  382.   /////////////////////////////////////////////////////////////////////////////
  383.   // Evaluates an event to determine whether or not it affords opening a tree
  384.   // item. Typically, this is when the left mouse button is used, and provided
  385.   // the click-rate matches that specified by our owning tree class. For example,
  386.   // some trees open an item when double clicked (bookmarks/history windows) and
  387.   // others on a single click (sidebar panels).
  388.   isValidOpenEvent: function (aEvent)
  389.   {
  390.     return !(aEvent.type == "click" && 
  391.              (aEvent.button != 0 || aEvent.detail != this.openClickCount))
  392.   },
  393.  
  394.   /////////////////////////////////////////////////////////////////////////////
  395.   // For the given selection, selects the best adjacent element. This method is
  396.   // useful when an action such as a cut or a deletion is performed on a
  397.   // selection, and focus/selection needs to be restored after the operation
  398.   // is performed.
  399.   getNextElement: function (aElement)
  400.   {
  401.     if (aElement.nextSibling)
  402.       return aElement.nextSibling;
  403.     else if (aElement.previousSibling)
  404.       return aElement.previousSibling;
  405.     else
  406.       return aElement.parentNode.parentNode;
  407.   },
  408.  
  409.   selectElement: function (aElement)
  410.   {
  411.     this.tree.selectItem(aElement);
  412.   },
  413.  
  414.   //////////////////////////////////////////////////////////////////////////////
  415.   // Add the treeitem element specified by aURI to the tree's current selection.
  416.   addItemToSelection: function (aURI)
  417.   {
  418.     var item = document.getElementById(aURI) // XXX flawed for multiple ids
  419.     this.tree.addItemToSelection(item);
  420.   },
  421.  
  422.   /////////////////////////////////////////////////////////////////////////////
  423.   // Return a set of DOM nodes that represent the selection in the tree widget.
  424.   // This method is takes a node parameter which is the popupNode for the
  425.   // document. If the popupNode is not contained by the selection, the
  426.   // popupNode is selected and the new selection returned.
  427.   getSelection: function ()
  428.   {
  429.     // Note that we don't just the selectedItems NodeList here because that
  430.     // is a reference to a LIVE DOM NODE LIST. We want to maintain control
  431.     // over what is in the selection array ourselves.
  432.     return [].concat(this.tree.selectedItems);
  433.   },
  434.  
  435.   getBestItem: function ()
  436.   {
  437.     var seln = this.getSelection ();
  438.     if (seln.length < 1) {
  439.       var kids = ContentUtils.childByLocalName(this.tree, "treechildren");
  440.       return kids.lastChild || this.tree;
  441.     }
  442.     else
  443.       return seln[0];
  444.     return this.tree;
  445.   },
  446.  
  447.   /////////////////////////////////////////////////////////////////////////////
  448.   // Return a set of DOM nodes that represent the selection in the tree widget.
  449.   // This method is takes a node parameter which is the popupNode for the
  450.   // document. If the popupNode is not contained by the selection, the
  451.   // popupNode is selected and the new selection returned.
  452.   getContextSelection: function (aItemNode)
  453.   {
  454.     // How a context-click works:
  455.     // if the popup node is contained by the selection, the context menu is
  456.     // built for that selection. However, if the popup node is invoked on a
  457.     // non-selected node, unless modifiers are pressed**, the previous
  458.     // selection is discarded and that node selected.
  459.     var selectedItems = this.tree.selectedItems;
  460.     for (var i = 0; i < selectedItems.length; ++i) {
  461.       if (selectedItems[i] == aItemNode)
  462.         return selectedItems;
  463.     }
  464.     if (aItemNode.localName == "treeitem")
  465.       this.tree.selectItem(aItemNode);
  466.     return this.tree.selectedItems.length ? this.tree.selectedItems : [this.tree];
  467.   },
  468.  
  469.   getSelectedFolder: function ()
  470.   {
  471.     var selectedItem = this.getBestItem();
  472.     if (!selectedItem) return "NC:BookmarksRoot";
  473.     while (selectedItem && selectedItem.nodeType == Node.ELEMENT_NODE) {
  474.       if (selectedItem.getAttribute("container") == "true" && 
  475.           selectedItem.getAttribute("open") == "true")
  476.         return NODE_ID(selectedItem);
  477.       selectedItem = selectedItem.parentNode.parentNode;
  478.     }
  479.     return "NC:BookmarksRoot";
  480.   },
  481.  
  482.   /////////////////////////////////////////////////////////////////////////////
  483.   // For a given start DOM element, find the enclosing DOM element that contains
  484.   // the template builder RDF resource decorations (id, ref, etc).
  485.   findRDFNode: function (aStartNode, aIncludeStartNodeFlag)
  486.   {
  487.     var temp = aIncludeStartNodeFlag ? aStartNode : aStartNode.parentNode;
  488.     while (temp && temp.localName != "treeitem")
  489.       temp = temp.parentNode;
  490.     return temp || this.tree;
  491.   },
  492.  
  493.   /////////////////////////////////////////////////////////////////////////////
  494.   // Tree click events. This handles when to go into inline-edit mode for
  495.   // editable cells.
  496.   treeClicked: function (aEvent)
  497.   {
  498.     // We are disabling Inline Edit for now. It's too buggy in the old XUL tree widget.
  499.     // A more solid implementation will follow the conversion to tree
  500. /*
  501.     if (this.tree.selectedItems.length > 1 || aEvent.detail > 1 || aEvent.button != 0) {
  502.       gSelectionTracker.clickCount = 0;
  503.       return;
  504.     }
  505.     if (gSelectionTracker.currentItem == this.tree.currentItem &&
  506.         gSelectionTracker.currentCell == aEvent.target)
  507.       ++gSelectionTracker.clickCount;
  508.     else
  509.       gSelectionTracker.clickCount = 0;
  510.  
  511.     if (!this.tree.currentItem)
  512.       return;
  513.  
  514.     gSelectionTracker.currentItem = this.tree.currentItem;
  515.     gSelectionTracker.currentCell = aEvent.target;
  516.  
  517.     if (gSelectionTracker.currentItem.getAttribute("type") != NC_NS + "Bookmark" &&
  518.         gSelectionTracker.currentItem.getAttribute("type") != NC_NS + "Folder")
  519.       return;
  520.  
  521.     var row = gSelectionTracker.currentItem.firstChild;
  522.     if (row) {
  523.       for (var i = 0; i < row.childNodes.length; ++i) {
  524.         if (row.childNodes[i] == gSelectionTracker.currentCell) {
  525.           // Don't allow inline-edit of cells other than name for folders.
  526.           // XXX - so so skeezy. Change this to look for NC:Name or some such.
  527.           if (gSelectionTracker.currentItem.getAttribute("type") != NC_NS + "Bookmark" && i)
  528.             return;
  529.           // Don't allow editing of the root folder name
  530.           if (gSelectionTracker.currentItem.id == "NC:BookmarksRoot")
  531.             return;
  532.           if (gSelectionTracker.clickCount == 1 && this.openClickCount > 1)
  533.             gBookmarksShell.commands.editCell(this.tree.currentItem, i);
  534.           break;
  535.         }
  536.       }
  537.     }
  538. */
  539.   },
  540.  
  541.   treeOpen: function (aEvent)
  542.   {
  543.     if (this.isValidOpenEvent(aEvent)) {
  544.       var rdfNode = this.findRDFNode(aEvent.target, true);
  545.       if (rdfNode.getAttribute("container") != "true")
  546.         this.open(aEvent, rdfNode);
  547.     }
  548.   },
  549.  
  550.   /////////////////////////////////////////////////////////////////////////////
  551.   // Tree key events. This handles when to go into inline-edit mode for editable
  552.   // cells, when to load a URL, etc.
  553.   treeKeyPress: function (aEvent)
  554.   {
  555.     if (this.tree.selectedItems.length > 1) return;
  556.  
  557.  /* Disabling Inline Edit
  558.     if (aEvent.keyCode == 113 && aEvent.shiftKey) {
  559.       const kNodeId = NODE_ID(this.tree.currentItem);
  560.       if (this.resolveType(kNodeId) == NC_NS + "Bookmark")
  561.         gBookmarksShell.commands.editCell (this.tree.currentItem, 1);
  562.     }
  563.     else */
  564.     if (aEvent.keyCode == 113)
  565.       goDoCommand("cmd_rename");
  566.     else if (aEvent.keyCode == 13) // && this.tree.currentItem.firstChild.getAttribute("inline-edit") != "true")
  567.       goDoCommand(aEvent.altKey ? "cmd_properties" : "cmd_open");
  568.   },
  569.  
  570.   selectFolderItem: function (aFolderURI, aItemURI, aAdditiveFlag)
  571.   {
  572.     var folder = document.getElementById(aFolderURI);
  573.     var kids = ContentUtils.childByLocalName(folder, "treechildren");
  574.     if (!kids) return;
  575.  
  576.     var item = kids.firstChild;
  577.     while (item) {
  578.       if (item.id == aItemURI) break;
  579.       item = item.nextSibling;
  580.     }
  581.     if (!item) return;
  582.  
  583.     this.tree[aAdditiveFlag ? "addItemToSelection" : "selectItem"](item);
  584.   },
  585.  
  586.   /////////////////////////////////////////////////////////////////////////////
  587.   // Command handling & Updating.
  588.   controller: {
  589.     supportsCommand: function (aCommand)
  590.     {
  591.       switch(aCommand) {
  592.       case "cmd_undo":
  593.       case "cmd_redo":
  594.         return false;
  595.       case "cmd_bm_cut":
  596.       case "cmd_bm_copy":
  597.       case "cmd_bm_paste":
  598.       case "cmd_bm_delete":
  599.       case "cmd_bm_selectAll":
  600.         return true;
  601.       case "cmd_open":
  602.       case "cmd_openfolder":
  603.       case "cmd_openfolderinnewwindow":
  604.       case "cmd_newbookmark":
  605.       case "cmd_newfolder":
  606.       case "cmd_newseparator":
  607.       case "cmd_find":
  608.       case "cmd_properties":
  609.       case "cmd_rename":
  610.       case "cmd_setnewbookmarkfolder":
  611.       case "cmd_setpersonaltoolbarfolder":
  612.       case "cmd_setnewsearchfolder":
  613.       case "cmd_import":
  614.       case "cmd_export":
  615.       case "cmd_bm_fileBookmark":
  616.         return true;
  617.       default:
  618.         return false;
  619.       }
  620.     },
  621.  
  622.     isCommandEnabled: function (aCommand)
  623.     {
  624.       var numSelectedItems = gBookmarksShell.tree.selectedItems.length;
  625.       var seln, firstSelected, folderType, bItemCountCorrect;
  626.       switch(aCommand) {
  627.       case "cmd_undo":
  628.       case "cmd_redo":
  629.         return false;
  630.       case "cmd_bm_paste":
  631.         return gBookmarksShell.canPaste();
  632.       case "cmd_bm_cut":
  633.       case "cmd_bm_copy":
  634.       case "cmd_bm_delete":
  635.         return numSelectedItems >= 1;
  636.       case "cmd_bm_selectAll":
  637.         return true;
  638.       case "cmd_open":
  639.          seln = gBookmarksShell.tree.selectedItems;
  640.         return numSelectedItems == 1 && seln[0].getAttribute("type") == NC_NS + "Bookmark";
  641.       case "cmd_openfolder":
  642.       case "cmd_openfolderinnewwindow":
  643.         seln = gBookmarksShell.tree.selectedItems;
  644.         return numSelectedItems == 1 && seln[0].getAttribute("type") == NC_NS + "Folder";
  645.       case "cmd_find":
  646.       case "cmd_newbookmark":
  647.       case "cmd_newfolder":
  648.       case "cmd_newseparator":
  649.       case "cmd_import":
  650.       case "cmd_export":
  651.         return true;
  652.       case "cmd_properties":
  653.       case "cmd_rename":
  654.         seln = gBookmarksShell.tree.selectedItems;
  655.         return numSelectedItems == 1 && seln[0].getAttribute("type") != NC_NS + "BookmarkSeparator";
  656.       case "cmd_setnewbookmarkfolder":
  657.         seln = gBookmarksShell.tree.selectedItems;
  658.         firstSelected = seln.length ? seln[0] : gBookmarksShell.tree;
  659.         folderType = firstSelected.getAttribute("type") == (NC_NS + "Folder");
  660.         bItemCountCorrect = seln.length ? numSelectedItems == 1 : true;
  661.         return bItemCountCorrect && !(NODE_ID(firstSelected) == "NC:NewBookmarkFolder") && folderType;
  662.       case "cmd_setpersonaltoolbarfolder":
  663.         seln = gBookmarksShell.tree.selectedItems;
  664.         firstSelected = seln.length ? seln[0] : gBookmarksShell.tree;
  665.         folderType = firstSelected.getAttribute("type") == (NC_NS + "Folder");
  666.         bItemCountCorrect = seln.length ? numSelectedItems == 1 : true;
  667.         return bItemCountCorrect && !(NODE_ID(firstSelected) == "NC:PersonalToolbarFolder") && folderType;
  668.       case "cmd_setnewsearchfolder":
  669.         seln = gBookmarksShell.tree.selectedItems;
  670.         firstSelected = seln.length ? seln[0] : gBookmarksShell.tree;
  671.         folderType = firstSelected.getAttribute("type") == (NC_NS + "Folder");
  672.         bItemCountCorrect = seln.length ? numSelectedItems == 1 : true;
  673.         return bItemCountCorrect == 1 && !(NODE_ID(firstSelected) == "NC:NewSearchFolder") && folderType;
  674.       case "cmd_bm_fileBookmark":
  675.         seln = gBookmarksShell.tree.selectedItems;
  676.         return seln.length > 0;
  677.       default:
  678.         return false;
  679.       }
  680.     },
  681.  
  682.     doCommand: function (aCommand)
  683.     {
  684.       switch(aCommand) {
  685.       case "cmd_undo":
  686.       case "cmd_redo":
  687.         break;
  688.       case "cmd_bm_paste":
  689.       case "cmd_bm_copy":
  690.       case "cmd_bm_cut":
  691.       case "cmd_bm_delete":
  692.       case "cmd_newbookmark":
  693.       case "cmd_newfolder":
  694.       case "cmd_newseparator":
  695.       case "cmd_properties":
  696.       case "cmd_rename":
  697.       case "cmd_open":
  698.       case "cmd_openfolder":
  699.       case "cmd_openfolderinnewwindow":
  700.       case "cmd_setnewbookmarkfolder":
  701.       case "cmd_setpersonaltoolbarfolder":
  702.       case "cmd_setnewsearchfolder":
  703.       case "cmd_find":
  704.       case "cmd_import":
  705.       case "cmd_export":
  706.       case "cmd_bm_fileBookmark":
  707.         gBookmarksShell.execCommand(aCommand.substring("cmd_".length));
  708.         break;
  709.       case "cmd_bm_selectAll":
  710.         gBookmarksShell.tree.selectAll();
  711.         break;
  712.       }
  713.     },
  714.  
  715.     onEvent: function (aEvent)
  716.     {
  717.       switch (aEvent) {
  718.       case "tree-select":
  719.         this.onCommandUpdate();
  720.         break;
  721.       }
  722.     },
  723.  
  724.     onCommandUpdate: function ()
  725.     {
  726.       var commands = ["cmd_properties", "cmd_rename", "cmd_bm_copy",
  727.                       "cmd_bm_paste", "cmd_bm_cut", "cmd_bm_delete",
  728.                       "cmd_setpersonaltoolbarfolder", 
  729.                       "cmd_setnewbookmarkfolder",
  730.                       "cmd_setnewsearchfolder", "cmd_bm_fileBookmark", 
  731.                       "cmd_openfolderinnewwindow", "cmd_openfolder"];
  732.       for (var i = 0; i < commands.length; ++i)
  733.         goUpdateCommand(commands[i]);
  734.     }
  735.   }
  736. };
  737.  
  738. var newFolderRDFObserver = {
  739.   _newFolderURI: null,
  740.   onAssert: function (aDS, aSource, aProperty, aValue)
  741.   {
  742.     try {
  743.       var value = aValue.QueryInterface(Components.interfaces.nsIRDFResource);
  744.       if (aDS.URI == "rdf:bookmarks" && aProperty.Value == RDF_NS + "type" &&
  745.           value.Value == NC_NS + "Folder")
  746.         this._newFolderURI = aSource.Value;
  747.     }
  748.     catch (e) {
  749.       // Failures are OK, the value could be a literal instead of a resource.
  750.     }
  751.   },
  752.  
  753.   onUnassert: function (aDS, aSource, aProperty, aTarget) { },
  754.   onChange: function (aDS, aSource, aProperty, aOldTarget, aNewTarget) { },
  755.   onMove: function (aDS, aOldSource, aNewSource, aProperty, aTarget) { },
  756.   beginUpdateBatch: function (aDS) { },
  757.   endUpdateBatch: function (aDS) { }
  758. };
  759.  
  760.