browser/components/bookmarks/content/bookmarksTree.xml
author hg@mozilla.com
Thu, 22 Mar 2007 10:30:00 -0700
changeset 1 9b2a99adc05e53cd4010de512f50118594756650
permissions -rw-r--r--
Free the (distributed) Lizard! Automatic merge from CVS: Module mozilla: tag HG_REPO_INITIAL_IMPORT at 22 Mar 2007 10:30 PDT,

<?xml version="1.0"?>  

#  -*- Mode: HTML; indent-tabs-mode: nil; -*-
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# 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)
#   Blake Ross <blaker@nemtscape.com>
#   Pierre Chanial <chanial@noos.fr> (v 2.0)
#   Joey Minta <jminta@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

<!DOCTYPE bindings [
  <!ENTITY % bookmarksDTD SYSTEM "chrome://browser/locale/bookmarks/bookmarks.dtd" >
  %bookmarksDTD;
]>

<bindings id="bookmarksBindings" 
          xmlns="http://www.mozilla.org/xbl" 
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 
          xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="bookmarks-tree">
    <implementation>
      <constructor><![CDATA[
        // This function only reads in the bookmarks from disk if they have not already been read.
        initServices();
        initBMService();

        BMSVC.readBookmarks();
        
        // We implement nsIController
        this.tree.controllers.appendController(this.controller);
        var olb = document.getAnonymousElementByAttribute(this, "anonid", "bookmarks-tree");
        olb = olb.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
        olb.addObserver(this.builderObserver);

        // Load column settings from persisted attribute
        var colinfostr = this.getAttribute("colinfo");
        var colinfo = colinfostr.split(" ");
        for (var i = 0; i < colinfo.length; ++i) {
          if (colinfo[i] == "") continue;

          var querymarker = colinfo[i].indexOf("?");
          var anonid = colinfo[i].substring(4, querymarker);
          var col = document.getAnonymousElementByAttribute(this, "id", anonid);

          if (!anonid || !col) break;

          var attrstring = colinfo[i].substr(querymarker + 1);

          var attrpairs = attrstring.split("&");
          for (var j = 0; j < attrpairs.length; ++j) {
            var pair = attrpairs[j].split("=");
            col.setAttribute(pair[0], pair[1]);
          }
        }
        
        // Load sort data from preferences
        this.refreshSort();
        
        // Observe for changes in sort from other concurrent UI
        const kPrefSvcContractID = "@mozilla.org/preferences-service;1";
        const kPrefSvcIID = Components.interfaces.nsIPrefService;
        var prefSvc = Components.classes[kPrefSvcContractID].getService(kPrefSvcIID);
        var prefs = prefSvc.getBranch(null);
        const kPrefBranchInternalIID = Components.interfaces.nsIPrefBranch2;
        var bookmarksPrefsInternal = prefs.QueryInterface(kPrefBranchInternalIID);
        bookmarksPrefsInternal.addObserver(this.sortChangedObserver.domain, 
                                           this.sortChangedObserver, false);
      ]]></constructor>
      <destructor><![CDATA[
        
        this.treeBuilder.removeObserver(this.builderObserver);
        this.tree.controllers.removeController(this.controller);

        // Save column settings and sort info to persisted attribute
        var persistString = "";
        
        var sortResource = gNC_NS + "Name";
        var sortDirection = "none";

        var treecols = document.getAnonymousElementByAttribute(this, "anonid", "treecols");
        var child = treecols.firstChild;
        while (child) {
          if (child.localName != "splitter") {
            var formatString = " col:%1%?width=%2%&hidden=%3%&ordinal=%6%";
            formatString = formatString.replace(/%1%/, child.getAttribute("id"));
            formatString = formatString.replace(/%2%/, child.getAttribute("width"));
            formatString = formatString.replace(/%3%/, child.getAttribute("hidden"));

            // While we're walking the columns, if we discover the column that represents the
            // field sorted by, save the resource associated with that column so that we 
            // can save that in prefs (see below)
            if (child.getAttribute("sortActive") == "true") {
              sortResource = child.getAttribute("sort");
              sortDirection = child.getAttribute("sortDirection");
            }
            formatString = formatString.replace(/%6%/, child.getAttribute("ordinal"));
            persistString += formatString;
          }
          child = child.nextSibling;
        }
        this.setAttribute("colinfo", persistString);
        
        document.persist(this.id, "colinfo");
        
        // Unhook the sort change observer for this tree
        const kPrefSvcContractID = "@mozilla.org/preferences-service;1";
        const kPrefSvcIID = Components.interfaces.nsIPrefService;
        var prefSvc = Components.classes[kPrefSvcContractID].getService(kPrefSvcIID);
        var prefs = prefSvc.getBranch(null);
        const kPrefBranchInternalIID = Components.interfaces.nsIPrefBranch2;
        var bookmarksPrefsInternal = prefs.QueryInterface(kPrefBranchInternalIID);
        bookmarksPrefsInternal.removeObserver(this.sortChangedObserver.domain, 
                                              this.sortChangedObserver);

      ]]></destructor>

      <property name="db">
        <getter><![CDATA[
          return this.tree.database;
        ]]></getter>
      </property>      
      
      <field name="sortChangedObserver">
      <![CDATA[
      ({
        outer: this,
        domain: "browser.bookmarks.sort",
        observe: function BMOL_sortChangedObserver(aSubject, aTopic, aPrefName) 
        {
          if (aTopic != "nsPref:changed") return;
          if (aPrefName.substr(0, this.domain.length) != this.domain) return;
                    
          this.outer.refreshSort();
        }      
      })
      ]]>
      </field>
      
      <field name="sorted">false</field>
      <method name="refreshSort">
        <body>
        <![CDATA[
          const kPrefSvcContractID = "@mozilla.org/preferences-service;1";
          const kPrefSvcIID = Components.interfaces.nsIPrefService;
          var prefSvc = Components.classes[kPrefSvcContractID].getService(kPrefSvcIID);
          var bookmarksSortPrefs = prefSvc.getBranch("browser.bookmarks.sort.");
          
          // This ensures that we don't sort twice in the tree that is clicked on 
          // as a result of 1) the click and 2) the pref listener. 
          if (!this.sorted) {
            try {
              var sortResource = bookmarksSortPrefs.getCharPref("resource");
              var sortDirection = bookmarksSortPrefs.getCharPref("direction");
          
              // Walk the columns, when we find a column with a sort resource that matches the supplied
              // data, stop and make sure it's sort active. 
              var treecols = document.getAnonymousElementByAttribute(this, "anonid", "treecols");
              var child = treecols.firstChild;
              while (child) {
                if (child.localName != "splitter") {
                  if (child.getAttribute("sort") == sortResource) {
                    child.setAttribute("sortActive", "true");
                    child.setAttribute("sortDirection", sortDirection);
                    this.treeBuilder.sort(child, false);
                    break;
                  }
                }          
                child = child.nextSibling;
              }
            }
            catch (e) {
              dump("error in refresh sort:"+e)
            }
          }

          this.sorted = false;
        ]]>
        </body>
      </method>
      
      <property name="columns">
        <getter>
        <![CDATA[
          var cols = [];
        
          var treecols = document.getAnonymousElementByAttribute(this, "anonid", "treecols");
          var child = treecols.firstChild;
          while (child) {
            if (child.localName != "splitter") {
              var obj = {
                label: child.getAttribute("label"),
                accesskey: child.getAttribute("accesskey"),
                resource: child.getAttribute("sort"),
                sortActive: child.getAttribute("sortActive") == "true",
                hidden: child.getAttribute("hidden"),
                id: "col" + child.getAttribute("id")
              }
              cols.push(obj);
            }
            child = child.nextSibling;
          }

          return cols;
        ]]>
        </getter>
      </property>
      
      <method name="toggleColumnVisibility">
        <parameter name="aColumnResource"/>
        <body>
        <![CDATA[
          var elt = document.getAnonymousElementByAttribute(this, "sort", aColumnResource);
          if (elt)
            elt.setAttribute("hidden", elt.getAttribute("hidden") != "true");
        ]]>
        </body>
      </method>      

      <property name="tree">
        <getter><![CDATA[
          return document.getAnonymousElementByAttribute(this, "anonid", "bookmarks-tree");
        ]]></getter>
      </property>

      <property name="treeBoxObject">
        <getter><![CDATA[
          return this.tree.boxObject.QueryInterface(Components.interfaces.nsITreeBoxObject);
        ]]></getter>
      </property>

      <property name="treeBuilder">
        <getter><![CDATA[
          return this.tree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
        ]]></getter>
      </property>

      <property name="type">
        <getter><![CDATA[
          if (!this._type) {
            var type = this.getAttribute("type");
            if (!type)
              type = "multi-column";
            this._type = type;
          }
          return this._type;
        ]]></getter>
      </property>

      <property name="currentIndex">
        <getter><![CDATA[
          return this.treeBoxObject.view.selection.currentIndex;
        ]]></getter>
      </property>
    
      <property name="currentResource">
        <getter><![CDATA[
          return this.treeBuilder.getResourceAtIndex(this.currentIndex);
        ]]></getter>
      </property>

      <method name="getRowResource">
        <parameter name="aRow"/>
        <body><![CDATA[
          if (aRow != -1)
            return this.treeBuilder.getResourceAtIndex(aRow);
          else
            return this.getRootResource();
        ]]></body>
      </method>

      <method name="getParentResource">
        <parameter name="aRow"/>
        <body><![CDATA[
          if (aRow != -1) {
            var parentIndex = this.treeBoxObject.view.getParentIndex(aRow);
            return this.getRowResource(parentIndex);
          }
          return this.getRootResource(); // assume its parent is the root
        ]]></body>
      </method>

      <method name="getRootResource">
        <body><![CDATA[
          var tree = document.getAnonymousElementByAttribute(this, "anonid", "bookmarks-tree");
          return RDF.GetResource(tree.ref);
        ]]></body>
      </method>

      <method name="selectResource">
        <parameter name="aResource"/>
        <body><![CDATA[
          var index = this.treeBuilder.getIndexOfResource(aResource);
          if (index != -1) {
            if (!this.treeBoxObject.view.selection.isSelected(index))
              this.treeBoxObject.view.selection.toggleSelect(index);
            return;
          }
         
          var chain = BMSVC.getParentChain(aResource);
          //dump("Chain:"+chain.length+"\n");
          for (var i=0; i<chain.length; i++) {
            var rParent = chain.queryElementAt(i, kRDFRSCIID);
            index = this.treeBuilder.getIndexOfResource(rParent);
            //dump(i+":"+BookmarksUtils.getProperty(rParent, gNC_NS+"Name")+", index:"+index+"\n");
            if (index == -1)
#             rParent is or is a parent of "ref", we go on searching for the
#             first parent in the tree.
              continue;

            if (!this.treeBoxObject.view.isContainerOpen(index))
#             we found one, let's open it.
              this.treeBoxObject.view.toggleOpenState(index);
          }
          if (index == -1)
#           none of the parents were in the tree, bailing
            return;

          index = this.treeBuilder.getIndexOfResource(aResource);
          if (index != -1)
            this.treeBoxObject.view.selection.toggleSelect(index);
        ]]></body>
      </method>

      <method name="focus">
        <body>
          this.tree.focus();
        </body>
      </method>

      <field name="_selection">null</field>
      <field name="_target">   null</field>

      <method name="getTreeSelection">
        <body><![CDATA[
          var selection        = {};
          selection.item       = [];
          selection.parent     = [];
          selection.isExpanded = [];
          var rangeCount = this.treeBoxObject.view.selection.getRangeCount();
          // workaround for bug 171547: if rowCount==0, rangeCount==1
          if (this.treeBuilder.rowCount > 0)
          for (var k = 0; k < rangeCount; ++k) {
            var rangeMin = {};
            var rangeMax = {};
            this.treeBoxObject.view.selection.getRangeAt(k, rangeMin, rangeMax);
            for (var i = rangeMin.value; i <= rangeMax.value; ++i) {
              var selectedItem   = this.getRowResource(i);
              var selectedParent = this.getParentResource(i);
              var isExpanded     = this.treeBoxObject.view.isContainerOpen(i);
              selection.item  .push(selectedItem);
              selection.parent.push(selectedParent);
              selection.isExpanded.push(isExpanded);
            }
          }
          selection.length = selection.item.length;
          BookmarksUtils.checkSelection(selection);
          return selection;
        ]]></body>
      </method>

      <method name="getTreeTarget">
        <parameter name="aItem"/>
        <parameter name="aParent"/>
        <parameter name="aOrientation"/>
        <body><![CDATA[

          if (!aParent || aParent.Value == "NC:BookmarksTopRoot")
            return BookmarksUtils.getTargetFromFolder(RDF.GetResource("NC:BookmarksRoot"))

          if (aOrientation == BookmarksUtils.DROP_ON)
            return BookmarksUtils.getTargetFromFolder(aItem);

          RDFC.Init(this.db, aParent);
          var index = RDFC.IndexOf(aItem);
          if (aOrientation == BookmarksUtils.DROP_AFTER)
            ++index;
          return { parent: aParent, index: index };
        ]]></body>
      </method>

      # This function saves the current selection state before the tree is rebuilt
      # following a command execution. This allows us to remember which item(s)
      # was/were selected so that the user does not need to constantly refocus the 
      # tree to perform a sequence of commands. 
      <field name="_savedSelection">[]</field>
      <method name="saveSelection">
        <body><![CDATA[
          var selection = this.treeBoxObject.view.selection;
          var rangeCount = selection.getRangeCount();
          var ranges = [];
          var min = {}; var max = {};
          for (var i = 0; i < rangeCount; ++i) {
            selection.getRangeAt(i, min, max);
            ranges.push({min: min.value, max: max.value});
          }
          this._savedSelection = ranges;
        ]]></body>
      </method>
      
      # This function restores the selection appropriately after a command executes. 
      # This is necessary because most commands trigger a rebuild of the tree which
      # destroys the selection. The restoration of selection is handled in three 
      # different ways depending on the type of command that has been executed:
      #  1) Commands that remove rows:
      #       The row immediately after the first range in the selection is selected, 
      #       if there is no row immediately after the first range the item before it
      #       is selected
      #  2) Commands that insert rows:
      #       The newly inserted rows are selected
      #  3) Commands that do not change the row count 
      #       The row(s) that was/were operated on remain selected.
      # 
      # The calls to save/restore are placed in the doCommand method and thus all
      # commands must pass through this gate. The result is that this method becomes
      # the POLICY CENTER FOR POST-VIEW/EDIT SELECTION CHANGES.
      <method name="restoreSelection">
        <parameter name="aCommand"/>
        <body><![CDATA[ 
          var oldRanges = this._savedSelection;
          var newRanges = [];

          switch(aCommand) {
          // [Category 1] - Commands that remove rows
          case "cmd_cut":
          case "cmd_delete":
            // Since rows have been removed, the row immediately after the first range 
            // in the original selection now has the index of the first item in the first
            // range. 
            var nextRow = oldRanges[0].min;
            var maxCount = this.treeBoxObject.view.rowCount;
            if (nextRow >= maxCount)
              nextRow = maxCount-1;
            if (nextRow >= 0)
              newRanges.push({min: nextRow, max: nextRow});
            break;
          // [Category 2] - Commands that insert rows
          case "cmd_paste":
          case "cmd_bm_import":
          case "cmd_bm_movebookmark":
          case "cmd_bm_newbookmark":
          case "cmd_bm_newfolder":
          case "cmd_bm_newseparator":
          case "cmd_undo": //XXXpch: doesn't work for insert
          case "cmd_redo": //XXXpch: doesn't work for remove
            // All items inserted will be selected. The implementation of this model
            // is left to |preUpdateTreeSelection|, called when an insert transaction is
            // executed, and |updateTreeSelection| called here. 
            this.updateTreeSelection();
            break;
          // [Category 3] - Commands that do not alter the row count
          case "cmd_copy":
          case "cmd_bm_properties":
          case "cmd_bm_rename":
          case "cmd_bm_setpersonaltoolbarfolder":
          case "cmd_bm_export":
          default:
            // The selection is unchanged.
            return;
          }
          
          var newSelection = this.treeBoxObject.view.selection;
          for (i = 0; i < newRanges.length; ++i)
            newSelection.rangedSelect(newRanges[i].min, newRanges[i].max, true);
        ]]></body>
      </method>

      <field name="_itemToBeToggled">  []</field>
      // keep track of the items that we will select
      // because we can not select rows during a batch.
      <method name="preUpdateTreeSelection">
        <parameter name="aTxn"/>
        <parameter name="aDo"/>
        <body><![CDATA[
          if (aTxn) {
            aTxn = aTxn.wrappedJSObject;
            var type = aTxn.type;
            // Skip transactions that aggregates nested "insert" or "remove" transactions.
            if ((type == "insert") && aDo || (type == "remove") && !aDo)
              this._itemToBeToggled = [aTxn.item];
          } else {
            var txnList;
            var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
                                       .getService(Components.interfaces.nsIBookmarkTransactionManager);
            var txmgr = bkmkTxnSvc.transactionManager;
            if (this.bookmarkTreeTransactionListener.mLastTxnWasDo) {
              txnList = txmgr.getUndoList();
            } else {
              txnList = txmgr.getRedoList();
            }
            var items =[];
            var childList = txnList.getChildListForItem(txnList.numItems-1);
            for (var i=0; i<childList.numItems; i++) {
              items.push(childList.getItem(i).wrappedJSObject.item);
            }
            this._itemToBeToggled = items;
          }
        ]]></body>
      </method>

      <method name="updateTreeSelection">
        <body><![CDATA[
          this.treeBoxObject.view.selection.clearSelection();
          for (var i=0; i<this._itemToBeToggled.length; ++i) {
            index = this.treeBuilder.getIndexOfResource(this._itemToBeToggled[i]);
            if (index != -1 && !this.treeBoxObject.view.selection.isSelected(index))
              this.treeBoxObject.view.selection.toggleSelect(index);
          }
        ]]></body>
      </method>

      <method name="createTreeContextMenu">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var selection = this._selection;
          var target    = this._target;
          BookmarksCommand.createContextMenu(aEvent, selection);
          this.onCommandUpdate();
        ]]></body>
      </method>

      <method name="openItemClick">
        <parameter name="aEvent"/>
        <parameter name="aClickCount"/>
        <body><![CDATA[
          if (aEvent.button == 2 || aEvent.originalTarget.localName != "treechildren")
            return;
          if (aClickCount != this.clickCount && aEvent.button != 1)
            return;

          var row = {};
          var col = {};
          var obj = {};
          this.treeBoxObject.getCellAt(aEvent.clientX, aEvent.clientY, row, col, obj);
          row = row.value;

          if (row == -1 || obj.value == "twisty")
            return;
          var modifKey = aEvent.shiftKey || aEvent.ctrlKey || aEvent.altKey || 
                         aEvent.metaKey  || aEvent.button == 1;
          if (this.clickCount == 2 && !modifKey &&
              this.treeBoxObject.view.isContainer(row))
            return;

          if (this.clickCount == 2 && modifKey) {
            this.treeBoxObject.view.selection.select(row);
            this._selection = this.getTreeSelection();
          }
          var selection = this._selection;

          if (selection.isContainer[0]) {
            if (this.clickCount == 1 && !modifKey) {
              this.treeBoxObject.view.toggleOpenState(row);
              //XXXpch: broken since we have single IDs
              //if (selection.protocol[0] != "file")
                return;
            }
          }
          var browserTarget = whereToOpenLink(aEvent);
          BookmarksCommand.openBookmark(selection, browserTarget, this.db);
        ]]></body>
      </method>

      <method name="openItemKey">
        <body><![CDATA[
          if (this._selection.length != 1) {
            return;
          }
          if (!this._selection.isContainer[0])
            BookmarksCommand.openBookmark(this._selection, "current", this.db)
        ]]></body>
      </method>

      <method name="searchBookmarks">
      <parameter name="aInput"/>
        <body><![CDATA[
          if(!this.originalRef)
              this.originalRef = this.tree.getAttribute("ref");

          if (!aInput)
            this.tree.setAttribute("ref", this.originalRef);
          else
            this.tree.setAttribute("ref",
                                   "find:datasource=rdf:bookmarks&match=http://home.netscape.com/NC-rdf#Name&method=contains&text=" + encodeURIComponent(aInput));
        ]]></body>
      </method>

      <!-- observer -->
      <field name="DNDObserver" readonly="true"><![CDATA[
      ({
        mOuter: this,
        onDragStart: function (aEvent, aXferData, aDragAction)
        {
          if (this.mOuter.tree.getAttribute("sortActive") == "true")
            throw Components.results.NS_OK; 
          var selection = this.mOuter._selection;
          aXferData.data = BookmarksUtils.getXferDataFromSelection(selection);
          if (aEvent.ctrlKey)
            aDragAction.action = kDSIID.DRAGDROP_ACTION_COPY;
        },
        onDragOver: function (aEvent, aFlavour, aDragSession)
        {
          // allow dropping of bookmarks below the tree
          if (aDragSession.sourceNode == this.mOuter)
            aDragSession.canDrop = true;
        },
        // the actual dropping happens in the nsIXULTreeBuilderObserver below
        onDrop: function (aEvent, aXferData, aDragSession) { },
        getSupportedFlavours: function ()
        {
          var flavourSet = new FlavourSet();
          flavourSet.appendFlavour("moz/rdfitem");
          flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
          flavourSet.appendFlavour("text/x-moz-url");
          flavourSet.appendFlavour("text/unicode");
          return flavourSet;
        }
      })
      ]]></field>      
        
      <!-- nsIController -->
      <field name="controller" readonly="true"><![CDATA[
      ({
        mOuter: this,
        
        supportsCommand: BookmarksController.supportsCommand,
        
        isCommandEnabled: function (aCommand)
        {
          // warning: this is not the called function in BookmarksController.onCommandUpdate
          var selection = this.mOuter._selection;
          var target    = this.mOuter._target;
          return BookmarksController.isCommandEnabled(aCommand, selection, target)
        },

        doCommand: function (aCommand)
        {
          var selection = this.mOuter._selection;
          var target    = this.mOuter._target;
          this.mOuter.treeBoxObject.view.selection.selectEventsSuppressed = true;
          this.mOuter._itemToBeToggled = [];
          
          switch (aCommand) {
          case "cmd_selectAll":
            this.mOuter.treeBoxObject.view.selection.selectAll();
            break;
          default:
            this.mOuter.saveSelection();
            BookmarksController.doCommand(aCommand, selection, target);
            this.mOuter.restoreSelection(aCommand);
          }
          this.mOuter.treeBoxObject.view.selection.selectEventsSuppressed = false;
        }
      })
      ]]></field>

      <method name="onCommandUpdate">
        <body><![CDATA[
          var selection = this._selection;
          var target    = this._target;
          BookmarksController.onCommandUpdate(selection, target);
        ]]></body>
      </method>

      <method name="selectionChanged">
        <parameter name="aEvent"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <!-- nsIXULTreeBuilderObserver -->
      <field name="builderObserver"><![CDATA[
      ({
        mOuter: this,

        canDrop: function(index, orientation)
        {
          var dragSession = DS.getCurrentSession();
          if (!dragSession)
            return false;

          var selection = BookmarksUtils.getSelectionFromXferData(dragSession);
          var isBookmark = dragSession.isDataFlavorSupported("moz/rdfitem");
          if (isBookmark && selection.containsImmutable)
            return false;
          if (orientation == BookmarksUtils.DROP_ON)
            return true;
          
          var rsrc = this.mOuter.getRowResource(index);
          var rsrcParent = this.mOuter.getParentResource(index);

          var rtype = BookmarksUtils.resolveType(rsrc);
          var rptype = BookmarksUtils.resolveType(rsrcParent);

          if (!BookmarksUtils.isValidTargetContainer (rsrcParent, selection))
              return false;

          if (index != 0)
            return true;
          if (rsrc.Value != "NC:BookmarksRoot")
            return true;
          return orientation == BookmarksUtils.DROP_BEFORE ? false : this.mOuter.treeBoxObject.view.isContainerOpen(0)
        },

        onDrop: function(row, orientation)
        {
          var dragSession = DS.getCurrentSession();
          if (!dragSession)
            return;
          //var date = Date.now();
          var selection = BookmarksUtils.getSelectionFromXferData(dragSession);
          var rItem     = this.mOuter.getRowResource(row);
          var rParent   = this.mOuter.getParentResource(row);
          var target;
          if (orientation == BookmarksUtils.DROP_AFTER            &&
              this.mOuter.treeBoxObject.view.isContainer(row)     &&
              this.mOuter.treeBoxObject.view.isContainerOpen(row) &&
             !this.mOuter.treeBoxObject.view.isContainerEmpty(row))
            target = { parent: rItem, index: 1 };
          else {
            target = this.mOuter.getTreeTarget(rItem, rParent, orientation);
          }
          this.mOuter.treeBoxObject.view.selection.selectEventsSuppressed = true;
          this.mOuter._itemToBeToggled = [];

          // we can only test for kCopyAction if the source is a bookmark
          var checkCopy = dragSession.isDataFlavorSupported("moz/rdfitem");
          const kCopyAction = kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_LINK;

          // doCopy defaults to true; check if we should make it false.
          // we make it false only if all the selection items have valid parent
          // bookmark DS containers (i.e. aren't generated via aggregation)
          var doCopy = true;
          if (checkCopy && !(dragSession.dragAction & kCopyAction))
            doCopy = BookmarksUtils.shouldCopySelection("drag", selection);

          if (doCopy)
            BookmarksUtils.insertAndCheckSelection("drag", selection, target);
          else
            BookmarksUtils.moveAndCheckSelection  ("drag", selection, target);

          if (this.mOuter._itemToBeToggled.length > 0)
            this.mOuter.updateTreeSelection();
          // use of a timer to speedup
          var This = this.mOuter;
          setTimeout( function (){This.treeBoxObject.view.selection.selectEventsSuppressed = false}, 100)
          //dump("DND time:"+(Date.now()-date)+"\n")
        },

        onToggleOpenState: function (aRow)
        {
          // update the open attribute of the selection
          var selection = this.mOuter._selection;
          if (!selection)
            return;
          var resource = this.mOuter.getRowResource(aRow);
          for (var i=0; i<selection.length; ++i) {
            if (selection.item[i] == resource) {
              selection.isExpanded[i] = !selection.isExpanded[i];
              break;
            }
          }
        },
        
        onCycleHeader: function (aColumnID, aHeaderElement)
        {
          const kPrefSvcContractID = "@mozilla.org/preferences-service;1";
          const kPrefSvcIID = Components.interfaces.nsIPrefService;
          var prefSvc = Components.classes[kPrefSvcContractID].getService(kPrefSvcIID);
          var bookmarksSortPrefs = prefSvc.getBranch("browser.bookmarks.sort.");
          
          // Sorted! http://www.sorted.org.nz/
          this.mOuter.sorted = true;

          bookmarksSortPrefs.setCharPref("resource", aHeaderElement.getAttribute("sort"));
          bookmarksSortPrefs.setCharPref("direction", aHeaderElement.getAttribute("sortDirection"));
        },
    
        onSelectionChanged: function ()
        {
          //dump("ONSELECTION CHANGED\n");
          var selection = this.mOuter.getTreeSelection();
          this.mOuter._selection = selection;
          this.mOuter._target    = this.mOuter.getTreeTarget(selection.item[0], selection.parent[0], BookmarksUtils.DROP_BEFORE);
          this.mOuter.onCommandUpdate();
        },
        
        onCycleCell          : function (aItemIndex, aColumnID)          {},
        onPerformAction      : function (aAction)                        {},
        onPerformActionOnRow : function (aAction, aItemIndex)            {},
        onPerformActionOnCell: function (aAction, aItemIndex, aColumnID) {}

      })
      ]]></field>

      <!-- nsITransactionManager listener -->
      <field name="bookmarkTreeTransactionListener"><![CDATA[
      ({

        mOuter: this,

        mLastTxnWasDo: null,

        willDo: function (aTxmgr, aTxn) {},
        didDo : function (aTxmgr, aTxn) {
          this.mLastTxnWasDo = true;
          this.mOuter.preUpdateTreeSelection(aTxn, true);
        },
        willUndo: function (aTxmgr, aTxn) {},
        didUndo : function (aTxmgr, aTxn) {
          this.mLastTxnWasDo = false;
          this.mOuter.preUpdateTreeSelection(aTxn, false);
        },
        willRedo: function (aTxmgr, aTxn) {},
        didRedo : function (aTxmgr, aTxn) {
          this.mLastTxnWasDo = true;
          this.mOuter.preUpdateTreeSelection(aTxn, true);
        },
        didMerge       : function (aTxmgr, aTxn) {},
        didBeginBatch  : function (aTxmgr, aTxn) {},
        didEndBatch    : function (aTxmgr, aTxn) {
          this.mOuter.preUpdateTreeSelection(aTxn, this.mLastTxnWasDo);
        },
        willMerge      : function (aTxmgr, aTxn) {},
        willBeginBatch : function (aTxmgr, aTxn) {},
        willEndBatch   : function (aTxmgr, aTxn) {}
      })
      ]]></field>
    </implementation>
  </binding>

  <!-- Full Bookmarks Tree, multi-columned -->
  <!-- Localize column labels! -->
  <binding id="bookmarks-tree-full" extends="chrome://browser/content/bookmarks/bookmarksTree.xml#bookmarks-tree">
    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"
                 contextmenu="_child">
      <!-- XXXben need focus event handler for cmd update -->
      <!-- context menu -->
      <menupopup onpopupshowing="this.parentNode.createTreeContextMenu(event);"
                 onpopuphidden="if (content) content.focus()"
                 onclick="event.stopPropagation();"
                 onkeypress="event.stopPropagation();"/>
      <vbox flex="1">
        <tree anonid="bookmarks-tree" flex="1" class="plain" enableColumnDrag="true"
                  datasources="rdf:bookmarks rdf:files rdf:localsearch" ref="NC:BookmarksTopRoot" flags="dont-build-content"
                  onkeypress="if (event.keyCode == 13) this.parentNode.parentNode.openItemKey();"
                  onclick="this.parentNode.parentNode.openItemClick(event, 1);"
                  ondblclick="this.parentNode.parentNode.openItemClick(event, 2);"
                  ondraggesture="if (event.originalTarget.localName == 'treechildren') nsDragAndDrop.startDrag(event, this.parentNode.parentNode.DNDObserver);"
                  onselect="this.treeBoxObject.view.selectionChanged();">
          <template xmlns:nc="http://home.netscape.com/NC-rdf#">
            <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
              <treechildren>
                <treeitem uri="rdf:*">
                  <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type separator">
                    <treecell properties="separator" label="rdf:http://home.netscape.com/NC-rdf#Name"/>
                  </treerow>
                </treeitem>
              </treechildren>
            </rule>
            <rule rdf:type="http://home.netscape.com/NC-rdf#MicsumBookmark">
              <treechildren>
                <treeitem uri="rdf:*">
                  <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                    <treecell src="rdf:http://home.netscape.com/NC-rdf#Icon"
                              label="rdf:http://home.netscape.com/NC-rdf#GeneratedTitle"/>
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#URL" />
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#ShortcutURL" />
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#Description" />
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#BookmarkAddDate" />
                    <treecell label="rdf:http://home.netscape.com/WEB-rdf#LastModifiedDate" />
                    <treecell label="rdf:http://home.netscape.com/WEB-rdf#LastVisitDate"/>
                  </treerow>
                </treeitem>
              </treechildren>
            </rule>
            <rule>
              <treechildren>
                <treeitem uri="rdf:*">
                  <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                    <treecell src="rdf:http://home.netscape.com/NC-rdf#Icon"
                              label="rdf:http://home.netscape.com/NC-rdf#Name"/>
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#URL" />
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#ShortcutURL" />
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#Description" />
                    <treecell label="rdf:http://home.netscape.com/NC-rdf#BookmarkAddDate" />
                    <treecell label="rdf:http://home.netscape.com/WEB-rdf#LastModifiedDate" />
                    <treecell label="rdf:http://home.netscape.com/WEB-rdf#LastVisitDate"/>
                  </treerow>
                </treeitem>
              </treechildren>
            </rule>
          </template>
          <treecols anonid="treecols">
            <treecol id="Name" label="&treecol.name.label;" flex="1" primary="true" 
                          class="sortDirectionIndicator" 
                          persist="width hidden ordinal" 
                          sort="rdf:http://home.netscape.com/NC-rdf#Name"
                          sortActive="true" sortDirection="none"/>
            <splitter class="tree-splitter" />
            <treecol id="URL" label="&treecol.url.label;" 
                          flex="1" class="sortDirectionIndicator" 
                          sort="rdf:http://home.netscape.com/NC-rdf#URL" 
                          persist="width hidden ordinal" />
            <splitter class="tree-splitter" />
            <treecol id="ShortcutURL" label="&treecol.shortcut.label;" 
                          hidden="true" flex="1" class="sortDirectionIndicator" 
                          persist="hidden width ordinal" 
                          sort="rdf:http://home.netscape.com/NC-rdf#ShortcutURL"/>
            <splitter class="tree-splitter"/>
            <treecol id="Description" label="&treecol.description.label;" 
                          flex="1" class="sortDirectionIndicator" 
                          persist="hidden width ordinal" 
                          sort="rdf:http://home.netscape.com/NC-rdf#Description"/>
            <splitter class="tree-splitter"/>
            <treecol id="AddDate" label="&treecol.addedon.label;" 
                          hidden="true" flex="1" class="sortDirectionIndicator" 
                          sort="rdf:http://home.netscape.com/NC-rdf#BookmarkAddDate" 
                          persist="width hidden ordinal" />
            <splitter class="tree-splitter" />
            <treecol id="LastModDate" label="&treecol.lastmod.label;" 
                          hidden="true" flex="1" class="sortDirectionIndicator" 
                          sort="rdf:http://home.netscape.com/WEB-rdf#LastModifiedDate" 
                          persist="width hidden ordinal" />
            <splitter class="tree-splitter" />
            <treecol id="LastVisitDate" label="&treecol.lastvisit.label;" 
                          hidden="true" flex="1" class="sortDirectionIndicator" 
                          sort="rdf:http://home.netscape.com/WEB-rdf#LastVisitDate" 
                          persist="width hidden ordinal" />
          </treecols>
        </tree>
      </vbox>
    </xbl:content>
    <implementation>
      <constructor>
        // Adding the transaction listener
        var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
                                   .getService(Components.interfaces.nsIBookmarkTransactionManager);
        bkmkTxnSvc.transactionManager.AddListener(this.bookmarkTreeTransactionListener);
      </constructor>  
      <destructor>
        var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
                                   .getService(Components.interfaces.nsIBookmarkTransactionManager);
        bkmkTxnSvc.transactionManager.RemoveListener(this.bookmarkTreeTransactionListener);
      </destructor>  
      <field name="clickCount">2</field>
    </implementation>
  </binding>

  <!-- Single column tree -->
  <binding id="bookmarks-tree-name" extends="chrome://browser/content/bookmarks/bookmarksTree.xml#bookmarks-tree">
    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 
                 xmlns:xbl="http://www.mozilla.org/xbl" contextmenu="_child">
      <!-- context menu -->
      <menupopup xbl:inherits="onpopupshowing"
                 onpopupshowing="this.parentNode.createTreeContextMenu(event);"
                 onpopuphidden="if (content) content.focus()"
                 onclick="event.stopPropagation();"
                 onkeypress="event.stopPropagation();"/>
      <tree anonid="bookmarks-tree" flex="1" class="plain" hidecolumnpicker="true"
                datasources="rdf:bookmarks rdf:files rdf:localsearch" ref="NC:BookmarksRoot" flags="dont-build-content"
                onselect="this.parentNode.treeBoxObject.view.selectionChanged();" seltype="single">
        <template xmlns:nc="http://home.netscape.com/NC-rdf#">
          <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
            <treechildren>
              <treeitem uri="rdf:*">
                <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type separator">
                  <treecell properties="separator" label="rdf:http://home.netscape.com/NC-rdf#Name"/>
                </treerow>
              </treeitem>
            </treechildren>
          </rule>
          <rule rdf:type="http://home.netscape.com/NC-rdf#MicsumBookmark">
            <treechildren>
              <treeitem uri="rdf:*">
                <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                  <treecell src="rdf:http://home.netscape.com/NC-rdf#Icon"
                            label="rdf:http://home.netscape.com/NC-rdf#GeneratedTitle"/>
                </treerow>
              </treeitem>
            </treechildren>
          </rule>
          <rule>
            <treechildren>
              <treeitem uri="rdf:*">
                <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                  <treecell src="rdf:http://home.netscape.com/NC-rdf#Icon"
                            label="rdf:http://home.netscape.com/NC-rdf#Name"/>
                </treerow>
              </treeitem>
            </treechildren>
          </rule>
        </template>
        <treecols anonid="treecols">
          <treecol id="Name" flex="1" primary="true" hideheader="true"
                   sort="rdf:http://home.netscape.com/NC-rdf#Name"
                   sortActive="true" sortDirection="none"/>
        </treecols>
      </tree>
    </xbl:content>
    <implementation>
      <field name="clickCount">1</field>
    </implementation>
  </binding>

  <!-- Tree with folders only -->
  <binding id="bookmarks-tree-folders" extends="chrome://browser/content/bookmarks/bookmarksTree.xml#bookmarks-tree">
    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl">
      <tree anonid="bookmarks-tree" class="bookmarksTree" flex="1" hidecolumnpicker="true"
            xbl:inherits="rows,seltype"
            datasources="rdf:bookmarks rdf:files rdf:localsearch" ref="NC:BookmarksTopRoot" flags="dont-build-content"
            onselect="this.parentNode.treeBoxObject.view.selectionChanged();">
        <template>
          <!-- I don't want these things to appear at all, but that's not an option -->
          <rule rdf:type="http://home.netscape.com/NC-rdf#Livemark">
            <treechildren>
              <treeitem uri="rdf:*">
                <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                  <treecell label="rdf:http://home.netscape.com/NC-rdf#Name" />
                </treerow>
              </treeitem>
            </treechildren>
          </rule>
          <rule iscontainer="true">
            <treechildren>
              <treeitem uri="rdf:*">
                <treerow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                  <treecell label="rdf:http://home.netscape.com/NC-rdf#Name" />
                </treerow>
              </treeitem>
            </treechildren>
          </rule>
        </template>
        <treecols anonid="treecols">
          <treecol id="Name" flex="1" primary="true" hideheader="true"
                   sort="rdf:http://home.netscape.com/NC-rdf#Name"
                   sortActive="true" sortDirection="none"/>
        </treecols>
      </tree>
    </xbl:content>
    <implementation>
      <field name="clickCount">2</field>
    </implementation>
  </binding>
</bindings>