/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape 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/NPL/
 *
 * 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 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Ben Goodger <ben@netscape.com> (Original Author)
 *
 * 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 NPL, 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 NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var gBookmarksShell = null;

///////////////////////////////////////////////////////////////////////////////
// Tracks the selected item, the cell last clicked on, and the number of clicks
// given to it. Used to activate inline edit mode.
var gSelectionTracker = { currentItem: null, currentCell: null, clickCount: 0 };

///////////////////////////////////////////////////////////////////////////////
// Class which defines methods for a bookmarks UI implementation based around
// a treeview. Subclasses BookmarksBase in bookmarksOverlay.js. Some methods
// are required by the base class, others are for event handling. Window specific
// glue code should go into the BookmarksWindow class in bookmarks.js
function BookmarksTree (aID)
{
  this.id = aID;
}

BookmarksTree.prototype = {
  __proto__: BookmarksUIElement.prototype,

  // XXX - change this to .element and move into base. 
  get tree ()
  {
    return document.getElementById(this.id);
  },

  /////////////////////////////////////////////////////////////////////////////
  // This method constructs a menuitem for a context menu for the given command.
  // This is implemented by the client so that it can intercept menuitem naming
  // as appropriate.
  createMenuItem: function (aDisplayName, aCommandName, aItemNode)
  {
    const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    var xulElement = document.createElementNS(kXULNS, "menuitem");
    xulElement.setAttribute("cmd", aCommandName);
    xulElement.setAttribute("command", "cmd_" + aCommandName.substring(NC_NS_CMD.length));

    switch (aCommandName) {
    case NC_NS_CMD + "open":
      xulElement.setAttribute("label", aDisplayName);
      xulElement.setAttribute("default", "true");
      break;
    case NC_NS_CMD + "openfolder":
      aDisplayName = aItemNode.getAttribute("open") == "true" ? this.getLocaleString("cmd_openfolder2") : aDisplayName;
      xulElement.setAttribute("label", aDisplayName);
      xulElement.setAttribute("default", "true");
      break;
    case NC_NS_CMD + "renamebookmark":
      if (!document.popupNode.hasAttribute("type")) {
        xulElement.setAttribute("label", this.getLocaleString("cmd_renamebookmark2"));
        xulElement.setAttribute("cmd", (NC_NS_CMD + "editurl"));
      }
      else
        xulElement.setAttribute("label", aDisplayName);
      break;
    default:
      xulElement.setAttribute("label", aDisplayName);
      break;
    }
    return xulElement;
  },

  // XXX - ideally this would be in the base. this.tree needs to change to 
  // this.element and then we can do just that. 
  setRoot: function (aRoot)
  {
    this.tree.setAttribute("ref", aRoot);
  },

  // Command implementation
  commands: {
    openFolder: function (aSelectedItem)
    {
      if (aSelectedItem.getAttribute("open") == "true")
        aSelectedItem.removeAttribute("open");
      else
        aSelectedItem.setAttribute("open", "true");
    },

    // Things Needed to Satisfy Mac Weenies:
    // 1) need to implement timed single click edit. This could be Hard.
    // 2) need to implement some other method of key access apart from F2.
    //    mpt claims that 'Cmd+U' is the excel equivalent.
    editCell: function (aSelectedItem, aCell)
    {
      // XXX throw up properties dialog with name selected so user can rename
      //     that way, until tree conversion allows us to use IL again. 
      goDoCommand("cmd_properties");
      return; // Disable inline edit for now.

      var editCell = aSelectedItem.firstChild.childNodes[aCell];
      if (editCell.getAttribute("editable") != "true")
        return;
      
      // Cause the inline edit cell binding to be used.
      editCell.setAttribute("class", "treecell-indent treecell-editable");
      var editColGroup = document.getElementById("theColumns");
      var count = 0;
      var property = "";
      for (var i = 0; i < editColGroup.childNodes.length; ++i) {
        var currCol = editColGroup.childNodes[i];
        if (currCol.getAttribute("hidden") == "true")
          return;
        if (count == aCell) {
          property = currCol.getAttribute("resource");
          break;
        }
        ++count;

        // Deal with interleaved column resizer splitters
        if (currCol.nextSibling.localName == "splitter") ++i;
      }

      if (property) {
        editCell.setMode("edit");
        editCell.addObserver(this.postModifyCallback, "accept",
                             [editCell, aSelectedItem, property]);
      }
    },

    ///////////////////////////////////////////////////////////////////////////
    // Called after an inline-edit cell has left inline-edit mode, and data
    // needs to be modified in the datasource.
    postModifyCallback: function (aParams)
    {
      var selItemURI = NODE_ID(aParams[1]);
      gBookmarksShell.propertySet(selItemURI, aParams[2], aParams[3]);
      gBookmarksShell.selectFolderItem(NODE_ID(gBookmarksShell.findRDFNode(aParams[1], false)),
                                       selItemURI, false);
      gBookmarksShell.tree.focus();
      gSelectionTracker.clickCount = 0;
      
      // Set the cell back to use the standard treecell binding.
      var editCell = aParams[0];
      editCell.setAttribute("class", "treecell-indent");
    },

    ///////////////////////////////////////////////////////////////////////////
    // New Folder Creation
    // Strategy: create a dummy row with edit fields to harvest information
    //           from the user, then destroy these rows and create an item
    //           in its place.

    ///////////////////////////////////////////////////////////////////////////
    // Edit folder name & update the datasource if name is valid
    onEditFolderName: function (aParams, aTopic)
    {
      var name = aParams[3];
      var shell = gBookmarksShell.commands; // suck
      var dummyItem = aParams[2];
      var relativeNode = aParams[1];
      var parentNode = relativeNode ? gBookmarksShell.findRDFNode(relativeNode, false) : gBookmarksShell.tree;

      dummyItem.parentNode.removeChild(dummyItem);

      if (!shell.validateNameAndTopic(name, aTopic, relativeNode, dummyItem)) {
        gBookmarksShell.tree.selectItem(relativeNode);
        gBookmarksShell.tree.focus();
        return;
      }

      if (relativeNode) {
        // If we're attempting to create a folder as a subfolder of an open folder,
        // we need to set the parentFolder to be relativeNode, which will be the
        // parent of the new folder, rather than the parent of the relativeNode,
        // which will result in the folder being created in an incorrect position
        // (adjacent to the relativeNode).
        var selKids = ContentUtils.childByLocalName(relativeNode, "treechildren");
        if (selKids && selKids.hasChildNodes() && selKids.lastChild == dummyItem)
          parentNode = relativeNode;
      }

      var args = [{ property: NC_NS + "parent",
                    resource: NODE_ID(parentNode) },
                  { property: NC_NS + "Name",
                    literal:  name }];

      const kBMDS = gBookmarksShell.RDF.GetDataSource("rdf:bookmarks");
      kBMDS.AddObserver(newFolderRDFObserver);
      var relId = relativeNode ? NODE_ID(relativeNode) : "NC:BookmarksRoot";
      BookmarksUtils.doBookmarksCommand(relId, NC_NS_CMD + "newfolder", args);
      kBMDS.RemoveObserver(newFolderRDFObserver);
      var newFolderItem = document.getElementById(newFolderRDFObserver._newFolderURI);
      gBookmarksShell.tree.focus();
      gBookmarksShell.tree.selectItem(newFolderItem);
      // Can't use newFolderItem because it may not have been created yet. Hack, huh?
      var index = gBookmarksShell.tree.getIndexOfItem(relativeNode);
      gBookmarksShell.tree.ensureIndexIsVisible(index+1);
      gSelectionTracker.clickCount = 0;
    },

    ///////////////////////////////////////////////////////////////////////////
    // Performs simple validation on what the user has entered:
    //  1) prevents entering an empty string
    //  2) in the case of a canceled operation, remove the dummy item and
    //     restore selection.
    validateNameAndTopic: function (aName, aTopic, aOldSelectedItem, aDummyItem)
    {
      // Don't allow user to enter an empty string "";
      if (!aName) return false;

      // If the user hit escape, go no further.
      if (aTopic == "reject") {
        if (aOldSelectedItem)
          gBookmarksShell.tree.selectItem(aOldSelectedItem);
        return false;
      }
      return true;
    },

    ///////////////////////////////////////////////////////////////////////////
    // Creates a dummy item that can be placed in edit mode to retrieve data
    // to create new bookmarks/folders.
    createBookmarkItem: function (aMode, aSelectedItem)
    {
      /////////////////////////////////////////////////////////////////////////
      // HACK HACK HACK HACK HACK         
      // Disable Inline-Edit for now and just use a dialog. 
      
      // XXX - most of this is just copy-pasted from the other two folder
      //       creation functions. Yes it's ugly, but it'll do the trick for 
      //       now as this is in no way intended to be a long-term solution.

      const kPromptSvcContractID = "@mozilla.org/embedcomp/prompt-service;1";
      const kPromptSvcIID = Components.interfaces.nsIPromptService;
      const kPromptSvc = Components.classes[kPromptSvcContractID].getService(kPromptSvcIID);
      
      var defaultValue  = gBookmarksShell.getLocaleString("ile_newfolder");
      var dialogTitle   = gBookmarksShell.getLocaleString("newfolder_dialog_title");
      var dialogMsg     = gBookmarksShell.getLocaleString("newfolder_dialog_msg");
      var stringValue   = { value: defaultValue };
      if (kPromptSvc.prompt(window, dialogTitle, dialogMsg, stringValue, null, { value: 0 })) {
        var relativeNode = gBookmarksShell.tree;
        var parentNode;
        if (aSelectedItem && aSelectedItem.localName != "tree") {
          // By default, create adjacent to the selected item
          relativeNode = aSelectedItem;
          if (relativeNode.getAttribute("container") == "true" && 
              relativeNode.getAttribute("open") == "true") {
            // But if it's an open container, the relative node should be the last child. 
            var treechildren = ContentUtils.childByLocalName(relativeNode, "treechildren");
            if (treechildren && treechildren.hasChildNodes())
              relativeNode = treechildren.lastChild; // folder non-empty, set relativeNode
            parentNode = aSelectedItem; // no matter what, folder is open, so make it parent
          } else {
            parentNode = relativeNode ? gBookmarksShell.findRDFNode(relativeNode, false) : gBookmarksShell.tree;
          }
        }

        var args = [{ property: NC_NS + "parent",
                      resource: NODE_ID(parentNode) },
                    { property: NC_NS + "Name",
                      literal:  stringValue.value }];
        
        const kBMDS = gBookmarksShell.RDF.GetDataSource("rdf:bookmarks");
        kBMDS.AddObserver(newFolderRDFObserver);
        var relId = relativeNode ? NODE_ID(relativeNode) : "NC:BookmarksRoot";
        BookmarksUtils.doBookmarksCommand(relId, NC_NS_CMD + "newfolder", args);
        kBMDS.RemoveObserver(newFolderRDFObserver);
        var newFolderItem = document.getElementById(newFolderRDFObserver._newFolderURI);
        gBookmarksShell.tree.focus();
        gBookmarksShell.tree.selectItem(newFolderItem);
        // Can't use newFolderItem because it may not have been created yet. Hack, huh?
        var index = gBookmarksShell.tree.getIndexOfItem(relativeNode);
        gBookmarksShell.tree.ensureIndexIsVisible(index+1);
      }
      
      return; 
      
      // HACK HACK HACK HACK HACK         
      /////////////////////////////////////////////////////////////////////////

      /* Disable inline edit for now
      const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      var dummyItem = document.createElementNS(kXULNS, "treeitem");
      dummyItem = gBookmarksShell.createBookmarkFolderDecorations(dummyItem);
      dummyItem.setAttribute("class", "bookmark-item");

      var dummyRow    = document.createElementNS(kXULNS, "treerow");
      var dummyCell   = document.createElementNS(kXULNS, "treecell");
      var dummyCell2  = document.createElementNS(kXULNS, "treecell");
      dummyCell.setAttribute("label", gBookmarksShell.getLocaleString("ile_newfolder") + "  ");
      dummyCell.setAttribute("type", NC_NS + "Folder");
      dummyCell.setAttribute("editable", "true");
      dummyCell.setAttribute("class", "treecell-indent treecell-editable");
      dummyRow.appendChild(dummyCell);
      dummyItem.appendChild(dummyRow);
      
      var relativeNode = null;

      // If there are selected items, try to create the dummy item relative to the 
      // best item, and position the bookmark there when created. Otherwise just
      // append to the root. 
      if (aSelectedItem && aSelectedItem.localName != "tree") {
        // By default, create adjacent to the selected item
        relativeNode = aSelectedItem;
        if (relativeNode.getAttribute("container") == "true" && 
            relativeNode.getAttribute("open") == "true") {
          // But if it's an open container, the relative node should be the last child. 
          var treechildren = ContentUtils.childByLocalName(relativeNode, "treechildren");
          if (treechildren && treechildren.hasChildNodes()) 
            relativeNode = treechildren.lastChild;
        }

        if (aSelectedItem.getAttribute("container") == "true") {
          if (aSelectedItem.getAttribute("open") == "true") {
            var treechildren = ContentUtils.childByLocalName(aSelectedItem, "treechildren");
            if (!treechildren) {
              treechildren = document.createElementNS(kXULNS, "treechildren");
              aSelectedItem.appendChild(treechildren);
            }
            // Insert new item after last item.
            treechildren.appendChild(dummyItem);
          }
          else {
            if (aSelectedItem.nextSibling)
              aSelectedItem.parentNode.insertBefore(dummyItem, aSelectedItem.nextSibling);
            else
              aSelectedItem.parentNode.appendChild(dummyItem);
          }
          var index = gBookmarksShell.tree.getIndexOfItem(dummyItem);
          gBookmarksShell.tree.ensureIndexIsVisible(index);
        }
        else {
          if (aSelectedItem.nextSibling)
            aSelectedItem.parentNode.insertBefore(dummyItem, aSelectedItem.nextSibling);
          else
            aSelectedItem.parentNode.appendChild(dummyItem);
        }
      }
      else {
        // No items in the tree. Append to the root. 
        var rootKids = document.getElementById("treechildren-bookmarks");
        rootKids.appendChild(dummyItem);
      }

      dummyCell.setMode("edit");
      dummyCell.addObserver(this.onEditFolderName, "accept", [dummyCell, relativeNode, dummyItem]);
      dummyCell.addObserver(this.onEditFolderName, "reject", [dummyCell, relativeNode, dummyItem]);
      */
    }
  },

  /////////////////////////////////////////////////////////////////////////////
  // Evaluates an event to determine whether or not it affords opening a tree
  // item. Typically, this is when the left mouse button is used, and provided
  // the click-rate matches that specified by our owning tree class. For example,
  // some trees open an item when double clicked (bookmarks/history windows) and
  // others on a single click (sidebar panels).
  isValidOpenEvent: function (aEvent)
  {
    return !(aEvent.type == "click" && 
             (aEvent.button != 0 || aEvent.detail != this.openClickCount))
  },

  /////////////////////////////////////////////////////////////////////////////
  // For the given selection, selects the best adjacent element. This method is
  // useful when an action such as a cut or a deletion is performed on a
  // selection, and focus/selection needs to be restored after the operation
  // is performed.
  getNextElement: function (aElement)
  {
    if (aElement.nextSibling)
      return aElement.nextSibling;
    else if (aElement.previousSibling)
      return aElement.previousSibling;
    else
      return aElement.parentNode.parentNode;
  },

  selectElement: function (aElement)
  {
    this.tree.selectItem(aElement);
  },

  //////////////////////////////////////////////////////////////////////////////
  // Add the treeitem element specified by aURI to the tree's current selection.
  addItemToSelection: function (aURI)
  {
    var item = document.getElementById(aURI) // XXX flawed for multiple ids
    this.tree.addItemToSelection(item);
  },

  /////////////////////////////////////////////////////////////////////////////
  // Return a set of DOM nodes that represent the selection in the tree widget.
  // This method is takes a node parameter which is the popupNode for the
  // document. If the popupNode is not contained by the selection, the
  // popupNode is selected and the new selection returned.
  getSelection: function ()
  {
    // Note that we don't just the selectedItems NodeList here because that
    // is a reference to a LIVE DOM NODE LIST. We want to maintain control
    // over what is in the selection array ourselves.
    return [].concat(this.tree.selectedItems);
  },

  getBestItem: function ()
  {
    var seln = this.getSelection ();
    if (seln.length < 1) {
      var kids = ContentUtils.childByLocalName(this.tree, "treechildren");
      return kids.lastChild || this.tree;
    }
    else
      return seln[0];
    return this.tree;
  },

  /////////////////////////////////////////////////////////////////////////////
  // Return a set of DOM nodes that represent the selection in the tree widget.
  // This method is takes a node parameter which is the popupNode for the
  // document. If the popupNode is not contained by the selection, the
  // popupNode is selected and the new selection returned.
  getContextSelection: function (aItemNode)
  {
    // How a context-click works:
    // if the popup node is contained by the selection, the context menu is
    // built for that selection. However, if the popup node is invoked on a
    // non-selected node, unless modifiers are pressed**, the previous
    // selection is discarded and that node selected.
    var selectedItems = this.tree.selectedItems;
    for (var i = 0; i < selectedItems.length; ++i) {
      if (selectedItems[i] == aItemNode)
        return selectedItems;
    }
    if (aItemNode.localName == "treeitem")
      this.tree.selectItem(aItemNode);
    return this.tree.selectedItems.length ? this.tree.selectedItems : [this.tree];
  },

  getSelectedFolder: function ()
  {
    var selectedItem = this.getBestItem();
    if (!selectedItem) return "NC:BookmarksRoot";
    while (selectedItem && selectedItem.nodeType == Node.ELEMENT_NODE) {
      if (selectedItem.getAttribute("container") == "true" && 
          selectedItem.getAttribute("open") == "true")
        return NODE_ID(selectedItem);
      selectedItem = selectedItem.parentNode.parentNode;
    }
    return "NC:BookmarksRoot";
  },

  /////////////////////////////////////////////////////////////////////////////
  // For a given start DOM element, find the enclosing DOM element that contains
  // the template builder RDF resource decorations (id, ref, etc).
  findRDFNode: function (aStartNode, aIncludeStartNodeFlag)
  {
    var temp = aIncludeStartNodeFlag ? aStartNode : aStartNode.parentNode;
    while (temp && temp.localName != "treeitem")
      temp = temp.parentNode;
    return temp || this.tree;
  },

  /////////////////////////////////////////////////////////////////////////////
  // Tree click events. This handles when to go into inline-edit mode for
  // editable cells.
  treeClicked: function (aEvent)
  {
    // We are disabling Inline Edit for now. It's too buggy in the old XUL tree widget.
    // A more solid implementation will follow the conversion to tree
/*
    if (this.tree.selectedItems.length > 1 || aEvent.detail > 1 || aEvent.button != 0) {
      gSelectionTracker.clickCount = 0;
      return;
    }
    if (gSelectionTracker.currentItem == this.tree.currentItem &&
        gSelectionTracker.currentCell == aEvent.target)
      ++gSelectionTracker.clickCount;
    else
      gSelectionTracker.clickCount = 0;

    if (!this.tree.currentItem)
      return;

    gSelectionTracker.currentItem = this.tree.currentItem;
    gSelectionTracker.currentCell = aEvent.target;

    if (gSelectionTracker.currentItem.getAttribute("type") != NC_NS + "Bookmark" &&
        gSelectionTracker.currentItem.getAttribute("type") != NC_NS + "Folder")
      return;

    var row = gSelectionTracker.currentItem.firstChild;
    if (row) {
      for (var i = 0; i < row.childNodes.length; ++i) {
        if (row.childNodes[i] == gSelectionTracker.currentCell) {
          // Don't allow inline-edit of cells other than name for folders.
          // XXX - so so skeezy. Change this to look for NC:Name or some such.
          if (gSelectionTracker.currentItem.getAttribute("type") != NC_NS + "Bookmark" && i)
            return;
          // Don't allow editing of the root folder name
          if (gSelectionTracker.currentItem.id == "NC:BookmarksRoot")
            return;
          if (gSelectionTracker.clickCount == 1 && this.openClickCount > 1)
            gBookmarksShell.commands.editCell(this.tree.currentItem, i);
          break;
        }
      }
    }
*/
  },

  treeOpen: function (aEvent)
  {
    if (this.isValidOpenEvent(aEvent)) {
      var rdfNode = this.findRDFNode(aEvent.target, true);
      if (rdfNode.getAttribute("container") != "true")
        this.open(aEvent, rdfNode);
    }
  },

  /////////////////////////////////////////////////////////////////////////////
  // Tree key events. This handles when to go into inline-edit mode for editable
  // cells, when to load a URL, etc.
  treeKeyPress: function (aEvent)
  {
    if (this.tree.selectedItems.length > 1) return;

 /* Disabling Inline Edit
    if (aEvent.keyCode == 113 && aEvent.shiftKey) {
      const kNodeId = NODE_ID(this.tree.currentItem);
      if (this.resolveType(kNodeId) == NC_NS + "Bookmark")
        gBookmarksShell.commands.editCell (this.tree.currentItem, 1);
    }
    else */
    if (aEvent.keyCode == 113)
      goDoCommand("cmd_rename");
    else if (aEvent.keyCode == 13) // && this.tree.currentItem.firstChild.getAttribute("inline-edit") != "true")
      goDoCommand(aEvent.altKey ? "cmd_properties" : "cmd_open");
  },

  selectFolderItem: function (aFolderURI, aItemURI, aAdditiveFlag)
  {
    var folder = document.getElementById(aFolderURI);
    var kids = ContentUtils.childByLocalName(folder, "treechildren");
    if (!kids) return;

    var item = kids.firstChild;
    while (item) {
      if (item.id == aItemURI) break;
      item = item.nextSibling;
    }
    if (!item) return;

    this.tree[aAdditiveFlag ? "addItemToSelection" : "selectItem"](item);
  },

  /////////////////////////////////////////////////////////////////////////////
  // Command handling & Updating.
  controller: {
    supportsCommand: function (aCommand)
    {
      switch(aCommand) {
      case "cmd_undo":
      case "cmd_redo":
        return false;
      case "cmd_bm_cut":
      case "cmd_bm_copy":
      case "cmd_bm_paste":
      case "cmd_bm_delete":
      case "cmd_bm_selectAll":
        return true;
      case "cmd_open":
      case "cmd_openfolder":
      case "cmd_openfolderinnewwindow":
      case "cmd_newbookmark":
      case "cmd_newfolder":
      case "cmd_newseparator":
      case "cmd_find":
      case "cmd_properties":
      case "cmd_rename":
      case "cmd_setnewbookmarkfolder":
      case "cmd_setpersonaltoolbarfolder":
      case "cmd_setnewsearchfolder":
      case "cmd_import":
      case "cmd_export":
      case "cmd_bm_fileBookmark":
        return true;
      default:
        return false;
      }
    },

    isCommandEnabled: function (aCommand)
    {
      var numSelectedItems = gBookmarksShell.tree.selectedItems.length;
      var seln, firstSelected, folderType, bItemCountCorrect;
      switch(aCommand) {
      case "cmd_undo":
      case "cmd_redo":
        return false;
      case "cmd_bm_paste":
        return gBookmarksShell.canPaste();
      case "cmd_bm_cut":
      case "cmd_bm_copy":
      case "cmd_bm_delete":
        return numSelectedItems >= 1;
      case "cmd_bm_selectAll":
        return true;
      case "cmd_open":
         seln = gBookmarksShell.tree.selectedItems;
        return numSelectedItems == 1 && seln[0].getAttribute("type") == NC_NS + "Bookmark";
      case "cmd_openfolder":
      case "cmd_openfolderinnewwindow":
        seln = gBookmarksShell.tree.selectedItems;
        return numSelectedItems == 1 && seln[0].getAttribute("type") == NC_NS + "Folder";
      case "cmd_find":
      case "cmd_newbookmark":
      case "cmd_newfolder":
      case "cmd_newseparator":
      case "cmd_import":
      case "cmd_export":
        return true;
      case "cmd_properties":
      case "cmd_rename":
        seln = gBookmarksShell.tree.selectedItems;
        return numSelectedItems == 1 && seln[0].getAttribute("type") != NC_NS + "BookmarkSeparator";
      case "cmd_setnewbookmarkfolder":
        seln = gBookmarksShell.tree.selectedItems;
        firstSelected = seln.length ? seln[0] : gBookmarksShell.tree;
        folderType = firstSelected.getAttribute("type") == (NC_NS + "Folder");
        bItemCountCorrect = seln.length ? numSelectedItems == 1 : true;
        return bItemCountCorrect && !(NODE_ID(firstSelected) == "NC:NewBookmarkFolder") && folderType;
      case "cmd_setpersonaltoolbarfolder":
        seln = gBookmarksShell.tree.selectedItems;
        firstSelected = seln.length ? seln[0] : gBookmarksShell.tree;
        folderType = firstSelected.getAttribute("type") == (NC_NS + "Folder");
        bItemCountCorrect = seln.length ? numSelectedItems == 1 : true;
        return bItemCountCorrect && !(NODE_ID(firstSelected) == "NC:PersonalToolbarFolder") && folderType;
      case "cmd_setnewsearchfolder":
        seln = gBookmarksShell.tree.selectedItems;
        firstSelected = seln.length ? seln[0] : gBookmarksShell.tree;
        folderType = firstSelected.getAttribute("type") == (NC_NS + "Folder");
        bItemCountCorrect = seln.length ? numSelectedItems == 1 : true;
        return bItemCountCorrect == 1 && !(NODE_ID(firstSelected) == "NC:NewSearchFolder") && folderType;
      case "cmd_bm_fileBookmark":
        seln = gBookmarksShell.tree.selectedItems;
        return seln.length > 0;
      default:
        return false;
      }
    },

    doCommand: function (aCommand)
    {
      switch(aCommand) {
      case "cmd_undo":
      case "cmd_redo":
        break;
      case "cmd_bm_paste":
      case "cmd_bm_copy":
      case "cmd_bm_cut":
      case "cmd_bm_delete":
      case "cmd_newbookmark":
      case "cmd_newfolder":
      case "cmd_newseparator":
      case "cmd_properties":
      case "cmd_rename":
      case "cmd_open":
      case "cmd_openfolder":
      case "cmd_openfolderinnewwindow":
      case "cmd_setnewbookmarkfolder":
      case "cmd_setpersonaltoolbarfolder":
      case "cmd_setnewsearchfolder":
      case "cmd_find":
      case "cmd_import":
      case "cmd_export":
      case "cmd_bm_fileBookmark":
        gBookmarksShell.execCommand(aCommand.substring("cmd_".length));
        break;
      case "cmd_bm_selectAll":
        gBookmarksShell.tree.selectAll();
        break;
      }
    },

    onEvent: function (aEvent)
    {
      switch (aEvent) {
      case "tree-select":
        this.onCommandUpdate();
        break;
      }
    },

    onCommandUpdate: function ()
    {
      var commands = ["cmd_properties", "cmd_rename", "cmd_bm_copy",
                      "cmd_bm_paste", "cmd_bm_cut", "cmd_bm_delete",
                      "cmd_setpersonaltoolbarfolder", 
                      "cmd_setnewbookmarkfolder",
                      "cmd_setnewsearchfolder", "cmd_bm_fileBookmark", 
                      "cmd_openfolderinnewwindow", "cmd_openfolder"];
      for (var i = 0; i < commands.length; ++i)
        goUpdateCommand(commands[i]);
    }
  }
};

var newFolderRDFObserver = {
  _newFolderURI: null,
  onAssert: function (aDS, aSource, aProperty, aValue)
  {
    try {
      var value = aValue.QueryInterface(Components.interfaces.nsIRDFResource);
      if (aDS.URI == "rdf:bookmarks" && aProperty.Value == RDF_NS + "type" &&
          value.Value == NC_NS + "Folder")
        this._newFolderURI = aSource.Value;
    }
    catch (e) {
      // Failures are OK, the value could be a literal instead of a resource.
    }
  },

  onUnassert: function (aDS, aSource, aProperty, aTarget) { },
  onChange: function (aDS, aSource, aProperty, aOldTarget, aNewTarget) { },
  onMove: function (aDS, aOldSource, aNewSource, aProperty, aTarget) { },
  beginUpdateBatch: function (aDS) { },
  endUpdateBatch: function (aDS) { }
};

