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 / bookmarksOverlay.js < prev    next >
Text File  |  2003-06-08  |  39KB  |  1,030 lines

  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  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 NC_NS  = "http://home.netscape.com/NC-rdf#";
  40. var RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  41. const NC_NS_CMD = NC_NS + "command?cmd=";
  42.  
  43. /**
  44.  * XXX - 04/16/01
  45.  *  ACK! massive command name collision problems are causing big issues
  46.  *  in getting this stuff to work in the Navigator window. For sanity's 
  47.  *  sake, we need to rename all the commands to be of the form cmd_bm_*
  48.  *  otherwise there'll continue to be problems. For now, we're just 
  49.  *  renaming those that affect the personal toolbar (edit operations,
  50.  *  which were clashing with the textfield controller)
  51.  *
  52.  * There are also several places that need to be updated if you need
  53.  * to change a command name. 
  54.  *   1) the controller in ALL clients (bookmarksTree.js, personalToolbar.js)
  55.  *   2) the command nodes in the overlay
  56.  *   3) the command human-readable name key in bookmark.properties
  57.  *   4) the function 'getAllCmds' in bookmarksOverlay.js
  58.  *   5) the function 'execCommand' in bookmarksOverlay.js
  59.  * Yes, this blows crusty dead goats through straws, and I should probably
  60.  * create some constants somewhere to bring this number down to 3. 
  61.  * However, if you fail to do one of these, you WILL break something
  62.  * and I WILL come after you with a knife. 
  63.  */
  64.  
  65. function LITERAL (aDB, aElement, aPropertyID)
  66. {
  67.   var RDF = BookmarksUIElement.prototype.RDF;
  68.   var rSource = RDF.GetResource(aElement.id);
  69.   var rProperty = RDF.GetResource(aPropertyID);
  70.   var node = aDB.GetTarget(rSource, rProperty, true);
  71.   return node ? node.QueryInterface(Components.interfaces.nsIRDFLiteral).Value : "";
  72. }
  73.  
  74. function BookmarksUIElement () { }
  75. BookmarksUIElement.prototype = {
  76.   _rdf: null,
  77.   get RDF ()
  78.   {
  79.     if (!this._rdf) {
  80.       const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
  81.       const kRDFIID = Components.interfaces.nsIRDFService;
  82.       this._rdf = Components.classes[kRDFContractID].getService(kRDFIID);
  83.     }
  84.     return this._rdf;
  85.   },
  86.  
  87.   propertySet: function (sourceID, propertyID, newValue)
  88.   {
  89.     if (!newValue) return;
  90.     const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
  91.     const kRDFIID = Components.interfaces.nsIRDFService;
  92.     const kRDF = Components.classes[kRDFContractID].getService(kRDFIID);
  93.     // need to shuffle this into an API. 
  94.     const kBMDS = kRDF.GetDataSource("rdf:bookmarks");
  95.     const krProperty = kRDF.GetResource(propertyID);
  96.     const krItem = kRDF.GetResource(sourceID);
  97.     var rCurrValue = kBMDS.GetTarget(krItem, krProperty, true);
  98.     const krNewValue = kRDF.GetLiteral(newValue);
  99.     if (!rCurrValue)
  100.       kBMDS.Assert(krItem, krProperty, krNewValue, true);
  101.     else {
  102.       rCurrValue = rCurrValue.QueryInterface(Components.interfaces.nsIRDFLiteral);
  103.       if (rCurrValue.Value != newValue) 
  104.         kBMDS.Change(krItem, krProperty, rCurrValue, krNewValue);
  105.     }
  106.   },
  107.  
  108.   /////////////////////////////////////////////////////////////////////////////
  109.   // Fill a context menu popup with menuitems that are appropriate for the current
  110.   // selection.
  111.   createContextMenu: function (aEvent)
  112.   {
  113.     var popup = aEvent.target;
  114.     // clear out the old context menu contents (if any)
  115.     while (popup.hasChildNodes()) 
  116.       popup.removeChild(popup.firstChild);
  117.     
  118.     var popupNode = document.popupNode;
  119.     
  120.     if (!("findRDFNode" in this))
  121.       throw "Clients must implement findRDFNode!";
  122.     var itemNode = this.findRDFNode(popupNode, true);
  123.     if (!itemNode || !itemNode.getAttributeNS(RDF_NS, "type") || itemNode.getAttribute("mode") == "edit") {
  124.       aEvent.preventDefault();
  125.       return;
  126.     }
  127.     if (!("getContextSelection" in this)) 
  128.       throw "Clients must implement getContextSelection!";
  129.     var selection = this.getContextSelection (itemNode);
  130.     var commonCommands = [];
  131.     for (var i = 0; i < selection.length; ++i) {
  132.       var commands = this.getAllCmds(selection[i].id);
  133.       if (!commands) {
  134.         aEvent.preventDefault();
  135.         return;
  136.       }
  137.       commands = this.flattenEnumerator(commands);
  138.       if (!commonCommands.length) commonCommands = commands;
  139.       commonCommands = this.findCommonNodes(commands, commonCommands);
  140.     }
  141.  
  142.     if (!commonCommands.length) {
  143.       aEvent.preventDefault();
  144.       return;
  145.     }
  146.     
  147.     // Now that we should have generated a list of commands that is valid
  148.     // for the entire selection, build a context menu.
  149.     for (i = 0; i < commonCommands.length; ++i) {
  150.       const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  151.       var currCommand = commonCommands[i].QueryInterface(Components.interfaces.nsIRDFResource).Value;
  152.       var element = null;
  153.       if (currCommand != NC_NS_CMD + "bm_separator") {
  154.         var commandName = this.getCommandName(currCommand);
  155.         element = this.createMenuItem(commandName, currCommand, itemNode);
  156.       }
  157.       else if (i != 0 && i < commonCommands.length-1) {
  158.         // Never append a separator as the first or last element in a context
  159.         // menu.
  160.         element = document.createElementNS(kXULNS, "menuseparator");
  161.       }
  162.       
  163.       if (element) 
  164.         popup.appendChild(element);
  165.     }
  166.     return;
  167.   },
  168.   
  169.   /////////////////////////////////////////////////////////////////////////////
  170.   // Given two unique arrays, return an array that contains only the elements
  171.   // common to both. 
  172.   findCommonNodes: function (aNewArray, aOldArray)
  173.   {
  174.     var common = [];
  175.     for (var i = 0; i < aNewArray.length; ++i) {
  176.       for (var j = 0; j < aOldArray.length; ++j) {
  177.         if (common.length > 0 && common[common.length-1] == aNewArray[i])
  178.           continue;
  179.         if (aNewArray[i] == aOldArray[j])
  180.           common.push(aNewArray[i]);
  181.       }
  182.     }
  183.     return common;
  184.   },
  185.  
  186.   flattenEnumerator: function (aEnumerator)
  187.   {
  188.     if ("_index" in aEnumerator)
  189.       return aEnumerator._inner;
  190.     
  191.     var temp = [];
  192.     while (aEnumerator.hasMoreElements()) 
  193.       temp.push(aEnumerator.getNext());
  194.     return temp;
  195.   },
  196.   
  197.   /////////////////////////////////////////////////////////////////////////////
  198.   // For a given URI (a unique identifier of a resource in the graph) return 
  199.   // an enumeration of applicable commands for that URI. 
  200.   getAllCmds: function (aNodeID)
  201.   {
  202.     var type = BookmarksUtils.resolveType(aNodeID);
  203.     if (!type) {
  204.       if (aNodeID == "NC:PersonalToolbarFolder" || aNodeID == "NC:BookmarksRoot")
  205.         type = "http://home.netscape.com/NC-rdf#Folder";
  206.       else
  207.         return null;
  208.     }
  209.     var commands = [];
  210.     // menu order:
  211.     // 
  212.     // bm_open
  213.     // bm_openfolder
  214.     // bm_openinnewwindow
  215.     // bm_openinnewtab 
  216.     // ---------------------
  217.     // /* bm_find removed */
  218.     // bm_newfolder
  219.     // ---------------------
  220.     // bm_cut
  221.     // bm_copy
  222.     // bm_paste
  223.     // bm_fileBookmark
  224.     // ---------------------
  225.     // bm_delete
  226.     // bm_rename
  227.     // ---------------------
  228.     // bm_properties
  229.     switch (type) {
  230.     case "http://home.netscape.com/NC-rdf#BookmarkSeparator":
  231.       commands = ["bm_newfolder", "bm_separator", 
  232.                   "bm_cut", "bm_copy", "bm_paste", "bm_separator",
  233.                   "bm_delete"];
  234.       break;
  235.     case "http://home.netscape.com/NC-rdf#Bookmark":
  236.       commands = ["bm_open", "bm_openinnewwindow", "bm_openinnewtab", "bm_separator",
  237.                   "bm_newfolder", "bm_separator",
  238.                   "bm_cut", "bm_copy", "bm_paste", "bm_fileBookmark", "bm_separator",
  239.                   "bm_delete", "bm_rename", "bm_separator",
  240.                   "bm_properties"];
  241.       break;
  242.     case "http://home.netscape.com/NC-rdf#Folder":
  243.       commands = ["bm_openfolder", "bm_openinnewwindow", "bm_separator", 
  244.                   "bm_newfolder", "bm_separator",
  245.                   "bm_cut", "bm_copy", "bm_paste", "bm_fileBookmark", "bm_separator",
  246.                   "bm_delete", "bm_rename", "bm_separator",
  247.                   "bm_properties"];
  248.       break;
  249.     case "http://home.netscape.com/NC-rdf#IEFavoriteFolder":
  250.       commands = ["bm_openfolder", "bm_separator",
  251.                   "bm_delete"];
  252.       break;
  253.     case "http://home.netscape.com/NC-rdf#IEFavorite":
  254.       commands = ["bm_open", "bm_openinnewwindow", /* "bm_openinnewtab", */ "bm_separator",
  255.                   "bm_copy"];
  256.       break;
  257.     case "http://home.netscape.com/NC-rdf#FileSystemObject":
  258.       commands = ["bm_open", "bm_openinnewwindow", /* "bm_openinnewtab", */ "bm_separator",
  259.                   "bm_copy"];
  260.       break;
  261.     default: 
  262.       var source = this.RDF.GetResource(aNodeID);
  263.       return this.db.GetAllCmds(source);
  264.     }
  265.     return new CommandArrayEnumerator(commands);
  266.   },
  267.   
  268.   /////////////////////////////////////////////////////////////////////////////
  269.   // Retrieve the human-readable name for a particular command. Used when 
  270.   // manufacturing a UI to invoke commands.
  271.   getCommandName: function (aCommand) 
  272.   {
  273.     var cmdName = aCommand.substring(NC_NS_CMD.length);
  274.     try {
  275.       // Note: this will succeed only if there's a string in the bookmarks
  276.       //       string bundle for this command name. Otherwise, <xul:stringbundle/>
  277.       //       will throw, we'll catch & stifle the error, and look up the command
  278.       //       name in the datasource. 
  279.       return BookmarksUtils.getLocaleString ("cmd_" + cmdName);
  280.     }
  281.     catch (e) {
  282.     }   
  283.     // XXX - WORK TO DO HERE! (rjc will cry if we don't fix this) 
  284.     // need to ask the ds for the commands for this node, however we don't
  285.     // have the right params. This is kind of a problem. 
  286.     dump("*** BAD! EVIL! WICKED! NO! ACK! ARGH! ORGH!\n");
  287.     const rName = this.RDF.GetResource(NC_NS + "Name");
  288.     const rSource = this.RDF.GetResource(aNodeID);
  289.     return this.db.GetTarget(rSource, rName, true).Value;
  290.   },
  291.   
  292.   /////////////////////////////////////////////////////////////////////////////
  293.   // Perform a command based on a UI event. XXX - work to do here. 
  294.   preExecCommand: function (aEvent)
  295.   {
  296.     var commandID = aEvent.target.getAttribute("cmd");
  297.     if (!commandID) return;
  298.     goDoCommand("cmd_" + commandID.substring(NC_NS_CMD.length));
  299.   },
  300.   
  301.   execCommand: function (aCommandID) 
  302.   {
  303.     var args = [];
  304.     var selection = this.getSelection ();
  305.     if (selection.length >= 1) 
  306.       var selectedItem = selection[0];
  307.     switch (aCommandID) {
  308.     case "bm_open":
  309.       this.open(null, selectedItem, "current_window");
  310.       break;
  311.     case "bm_openfolder":
  312.       this.commands.openFolder(selectedItem);
  313.       break;
  314.     case "bm_openinnewwindow":
  315.       if (BookmarksUtils.resolveType(selectedItem.id) == NC_NS + "Folder")
  316.         this.openFolderInNewWindow(selectedItem);
  317.       else
  318.         this.open(null, selectedItem, "new_window");
  319.       break;
  320.     case "bm_openinnewtab":
  321.       this.open(null, selectedItem, "new_tab");
  322.       break;
  323.     case "bm_rename":
  324.       // XXX - this is SO going to break if we ever do column re-ordering.
  325.       this.commands.editCell(selectedItem, 0);
  326.       break;
  327.     case "bm_editurl":
  328.       this.commands.editCell(selectedItem, 1);
  329.       break;
  330.     case "bm_setnewbookmarkfolder":
  331.     case "bm_setpersonaltoolbarfolder":
  332.     case "bm_setnewsearchfolder":
  333.       BookmarksUtils.doBookmarksCommand(selectedItem.id, NC_NS_CMD + aCommandID, args);
  334.       // XXX - The containing node seems to be closed here and the 
  335.       //       focus/selection is destroyed.
  336.       this.selectElement(selectedItem);
  337.       break;
  338.     case "bm_properties":
  339.       this.showPropertiesForNode(selectedItem);
  340.       break;
  341.     case "bm_find":
  342.       this.findInBookmarks();
  343.       break;
  344.     case "bm_cut":
  345.       this.copySelection(selection);
  346.       this.deleteSelection(selection);
  347.       break;
  348.     case "bm_copy":
  349.       this.copySelection(selection);
  350.       break;
  351.     case "bm_paste":
  352.       this.paste(selection);
  353.       break;
  354.     case "bm_delete":
  355.       this.deleteSelection(selection);
  356.       break;
  357.     case "bm_fileBookmark":
  358.       var rv = { selectedFolder: null };      
  359.       openDialog("chrome://communicator/content/bookmarks/addBookmark.xul", "", 
  360.                  "centerscreen,chrome,modal=yes,dialog=yes,resizable=yes", null, null, folder, null, "selectFolder", rv);
  361.       if (rv.selectedFolder) {
  362.         for (var k = 0; k < selection.length; ++k) {
  363.           if (selection[k].id == rv.selectedFolder)
  364.             return; // Selection contains the target folder. Just fail silently.
  365.         }
  366.         var additiveFlag = false;
  367.         var selectedItems = [].concat(this.getSelection())
  368.         for (var i = 0; i < selectedItems.length; ++i) {
  369.           var currItem = selectedItems[i];
  370.           var currURI = currItem.id;
  371.           var parent = gBookmarksShell.findRDFNode(currItem, false);
  372.           gBookmarksShell.moveBookmark(currURI, parent.id, rv.selectedFolder);
  373.           gBookmarksShell.selectFolderItem(rv.selectedFolder, currURI, additiveFlag);
  374.           if (!additiveFlag) additiveFlag = true;
  375.         }
  376.         gBookmarksShell.flushDataSource();
  377.       }
  378.       break;
  379.     case "bm_newfolder":
  380.       var nfseln = document.popupNode;
  381.       this.commands.createBookmarkItem("folder", nfseln);
  382.       break;
  383.     case "bm_newbookmark":
  384.       var folder = this.getSelectedFolder();
  385.       openDialog("chrome://communicator/content/bookmarks/addBookmark.xul", "", 
  386.                  "centerscreen,chrome,modal=yes,dialog=yes,resizable=no", null, null, folder, null, "newBookmark");
  387.       break;
  388.     case "bm_newseparator":
  389.       nfseln = this.getBestItem();
  390.       var parentNode = this.findRDFNode(nfseln, false);
  391.       args = [{ property: NC_NS + "parent", 
  392.                 resource: parentNode.id }];
  393.       BookmarksUtils.doBookmarksCommand(nfseln.id, NC_NS_CMD + "newseparator", args);
  394.       break;
  395.     case "bm_import":
  396.     case "bm_export":
  397.       const isImport = aCommandID == "bm_import";
  398.       try {
  399.         const kFilePickerContractID = "@mozilla.org/filepicker;1";
  400.         const kFilePickerIID = Components.interfaces.nsIFilePicker;
  401.         const kFilePicker = Components.classes[kFilePickerContractID].createInstance(kFilePickerIID);
  402.         
  403.         const kTitle = BookmarksUtils.getLocaleString(isImport ? "SelectImport": "EnterExport");
  404.         kFilePicker.init(window, kTitle, kFilePickerIID[isImport ? "modeOpen" : "modeSave"]);
  405.         kFilePicker.appendFilters(kFilePickerIID.filterHTML | kFilePickerIID.filterAll);
  406.         if (!isImport) kFilePicker.defaultString = "bookmarks.html";
  407.         if (kFilePicker.show() != kFilePickerIID.returnCancel) {
  408.           var fileName = kFilePicker.fileURL.spec;
  409.           if (!fileName) break;
  410.         }
  411.         else break;
  412.       }
  413.       catch (e) {
  414.         break;
  415.       }
  416.       var seln = this.getBestItem();
  417.       args = [{ property: NC_NS + "URL", literal: fileName}];
  418.       BookmarksUtils.doBookmarksCommand(seln.id, NC_NS_CMD + aCommandID, args);
  419.       break;
  420.     }
  421.   },
  422.   
  423.   openFolderInNewWindow: function (aSelectedItem)
  424.   {
  425.     openDialog("chrome://communicator/content/bookmarks/bookmarks.xul", 
  426.                "", "chrome,all,dialog=no", aSelectedItem.id);
  427.   },
  428.   
  429.   copySelection: function (aSelection)
  430.   {
  431.     const kSuppArrayContractID = "@mozilla.org/supports-array;1";
  432.     const kSuppArrayIID = Components.interfaces.nsISupportsArray;
  433.     var itemArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  434.  
  435.     const kSuppWStringContractID = "@mozilla.org/supports-string;1";
  436.     const kSuppWStringIID = Components.interfaces.nsISupportsString;
  437.     var bmstring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
  438.     var unicodestring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
  439.     var htmlstring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
  440.   
  441.     var sBookmarkItem = ""; var sTextUnicode = ""; var sTextHTML = "";
  442.     for (var i = 0; i < aSelection.length; ++i) {
  443.       var url = LITERAL(this.db, aSelection[i], NC_NS + "URL");
  444.       var name = LITERAL(this.db, aSelection[i], NC_NS + "Name");
  445.       sBookmarkItem += aSelection[i].id + "\n";
  446.       sTextUnicode += url + "\n";
  447.       sTextHTML += "<A HREF=\"" + url + "\">" + name + "</A>";
  448.     }    
  449.     
  450.     const kXferableContractID = "@mozilla.org/widget/transferable;1";
  451.     const kXferableIID = Components.interfaces.nsITransferable;
  452.     var xferable = Components.classes[kXferableContractID].createInstance(kXferableIID);
  453.  
  454.     xferable.addDataFlavor("moz/bookmarkclipboarditem");
  455.     bmstring.data = sBookmarkItem;
  456.     xferable.setTransferData("moz/bookmarkclipboarditem", bmstring, sBookmarkItem.length*2)
  457.     
  458.     xferable.addDataFlavor("text/html");
  459.     htmlstring.data = sTextHTML;
  460.     xferable.setTransferData("text/html", htmlstring, sTextHTML.length*2)
  461.     
  462.     xferable.addDataFlavor("text/unicode");
  463.     unicodestring.data = sTextUnicode;
  464.     xferable.setTransferData("text/unicode", unicodestring, sTextUnicode.length*2)
  465.     
  466.     const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
  467.     const kClipboardIID = Components.interfaces.nsIClipboard;
  468.     var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
  469.     clipboard.setData(xferable, null, kClipboardIID.kGlobalClipboard);
  470.   },
  471.  
  472.   paste: function (aSelection)
  473.   {
  474.     const kXferableContractID = "@mozilla.org/widget/transferable;1";
  475.     const kXferableIID = Components.interfaces.nsITransferable;
  476.     var xferable = Components.classes[kXferableContractID].createInstance(kXferableIID);
  477.     xferable.addDataFlavor("moz/bookmarkclipboarditem");
  478.     xferable.addDataFlavor("text/x-moz-url");
  479.     xferable.addDataFlavor("text/unicode");
  480.  
  481.     const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
  482.     const kClipboardIID = Components.interfaces.nsIClipboard;
  483.     var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
  484.     clipboard.getData(xferable, kClipboardIID.kGlobalClipboard);
  485.     
  486.     var flavour = { };
  487.     var data = { };
  488.     var length = { };
  489.     xferable.getAnyTransferData(flavour, data, length);
  490.     var nodes = []; var names = [];
  491.     data = data.value.QueryInterface(Components.interfaces.nsISupportsString).data;
  492.     switch (flavour.value) {
  493.     case "moz/bookmarkclipboarditem":
  494.       nodes = data.split("\n");
  495.       break;
  496.     case "text/x-moz-url":
  497.       var ix = data.indexOf("\n");
  498.       nodes.push(data.substring(0, ix != -1 ? ix : data.length));
  499.       names.push(data.substring(ix));
  500.       break;
  501.     default: 
  502.       return;
  503.     }
  504.     
  505.     const lastSelected = aSelection[aSelection.length-1];  
  506.     const kParentNode = this.resolvePasteFolder(aSelection);
  507.     const krParent = this.RDF.GetResource(kParentNode.id);
  508.     const krSource = this.RDF.GetResource(lastSelected.id);
  509.     
  510.     const kRDFCContractID = "@mozilla.org/rdf/container;1";
  511.     const kRDFCIID = Components.interfaces.nsIRDFContainer;
  512.     const ksRDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
  513.     const kBMDS = this.RDF.GetDataSource("rdf:bookmarks");
  514.  
  515.     var additiveFlag = false;
  516.     for (var i = 0; i < nodes.length; ++i) {
  517.       if (!nodes[i]) continue;
  518.       var rCurrent = this.RDF.GetResource(nodes[i]);
  519.       const krTypeProperty = this.RDF.GetResource(RDF_NS + "type");
  520.       var rType = this.db.GetTarget(rCurrent, krTypeProperty, true);
  521.       try {
  522.         rType = rType.QueryInterface(Components.interfaces.nsIRDFResource);
  523.       }
  524.       catch (e) {
  525.         try {
  526.           rType = rType.QueryInterface(Components.interfaces.nsIRDFLiteral);
  527.         }
  528.         catch (e) {
  529.           // OK, no type exists, so node does not exist in the graph. 
  530.           // (e.g. user pastes url as text)
  531.           // Do some housekeeping. 
  532.           const krName = this.RDF.GetResource(names[i]);
  533.           const krNameProperty = this.RDF.GetResource(NC_NS + "Name");
  534.           const krBookmark = this.RDF.GetResource(NC_NS + "Bookmark");
  535.           kBMDS.Assert(rCurrent, krNameProperty, krName, true);
  536.           kBMDS.Assert(rCurrent, krTypeProperty, krBookmark, true);
  537.         }
  538.       }
  539.  
  540.       // If the node is a folder, then we need to create a new anonymous 
  541.       // resource and copy all the arcs over.
  542.       if (rType && rType.Value == NC_NS + "Folder")
  543.         rCurrent = BookmarksUtils.cloneFolder(rCurrent, krParent, krSource);
  544.  
  545.       // If this item already exists in this container, don't paste, as 
  546.       // this will result in the creation of multiple copies in the datasource
  547.       // but will not result in an update of the UI. (In Short: we don't
  548.       // handle multiple bookmarks well)
  549.       ksRDFC.Init(kBMDS, krParent);
  550.       ix = ksRDFC.IndexOf(rCurrent);
  551.       if (ix != -1)
  552.         continue;
  553.  
  554.       ix = ksRDFC.IndexOf(krSource);
  555.       if (ix != -1) 
  556.         ksRDFC.InsertElementAt(rCurrent, ix+1, true);
  557.       else
  558.         ksRDFC.AppendElement(rCurrent);
  559.       this.selectFolderItem(krSource.Value, rCurrent.Value, additiveFlag);
  560.       if (!additiveFlag) additiveFlag = true;
  561.  
  562.       var rds = kBMDS.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  563.       rds.Flush();
  564.     }
  565.   },
  566.   
  567.   /////////////////////////////////////////////////////////////////////////////
  568.   // For the given selection, determines the element that should form the 
  569.   // container to paste items into.
  570.   resolvePasteFolder: function (aSelection)
  571.   {
  572.     const lastSelected = aSelection[aSelection.length-1];  
  573.     if (lastSelected.getAttribute("container") == "true" &&
  574.         aSelection.length == 1)
  575.       return lastSelected;
  576.     return this.findRDFNode(lastSelected, false);
  577.   },
  578.   
  579.   canPaste: function ()
  580.   {
  581.     const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
  582.     const kClipboardIID = Components.interfaces.nsIClipboard;
  583.     var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
  584.     const kSuppArrayContractID = "@mozilla.org/supports-array;1";
  585.     const kSuppArrayIID = Components.interfaces.nsISupportsArray;
  586.     var flavourArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  587.     const kSuppStringContractID = "@mozilla.org/supports-cstring;1";
  588.     const kSuppStringIID = Components.interfaces.nsISupportsCString;
  589.     
  590.     var flavours = ["moz/bookmarkclipboarditem", "text/x-moz-url"];
  591.     for (var i = 0; i < flavours.length; ++i) {
  592.       const kSuppString = Components.classes[kSuppStringContractID].createInstance(kSuppStringIID);
  593.       kSuppString.data = flavours[i];
  594.       flavourArray.AppendElement(kSuppString);
  595.     }
  596.     var hasFlavours = clipboard.hasDataMatchingFlavors(flavourArray, kClipboardIID.kGlobalClipboard);
  597.     return hasFlavours;
  598.   },
  599.   
  600.   /////////////////////////////////////////////////////////////////////////////
  601.   // aSelection is a mutable array, not a NodeList. 
  602.   deleteSelection: function (aSelection)
  603.   {
  604.     const kRDFCContractID = "@mozilla.org/rdf/container;1";
  605.     const kRDFCIID = Components.interfaces.nsIRDFContainer;
  606.     const ksRDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
  607.  
  608.     var nextElement;
  609.     var count = 0;
  610.  
  611.     var selectionLength = aSelection.length;
  612.     while (aSelection.length && aSelection[count]) {
  613.       const currParent = this.findRDFNode(aSelection[count], false);
  614.       const kSelectionURI = aSelection[count].id;
  615.  
  616.       // Disallow the removal of certain 'special' nodes
  617.       if (kSelectionURI == "NC:BookmarksRoot") {
  618.         aSelection.splice(count++,1);
  619.         continue;
  620.       }
  621.  
  622.       // If the current bookmark is the IE Favorites folder, we have a little
  623.       // extra work to do - set the pref |browser.bookmarks.import_system_favorites|
  624.       // to ensure that we don't re-import next time. 
  625.       if (aSelection[count].getAttribute("type") == (NC_NS + "IEFavoriteFolder")) {
  626.         const kPrefSvcContractID = "@mozilla.org/preferences-service;1";
  627.         const kPrefSvcIID = Components.interfaces.nsIPrefBranch;
  628.         const kPrefSvc = Components.classes[kPrefSvcContractID].getService(kPrefSvcIID);
  629.         kPrefSvc.setBoolPref("browser.bookmarks.import_system_favorites", false);
  630.       }
  631.         
  632.       const krParent = this.RDF.GetResource(currParent.id);
  633.       const krBookmark = this.RDF.GetResource(kSelectionURI);
  634.       const kBMDS = this.RDF.GetDataSource("rdf:bookmarks");
  635.  
  636.       ksRDFC.Init(kBMDS, krParent);
  637.       nextElement = this.getNextElement(aSelection[count]);
  638.       ksRDFC.RemoveElement(krBookmark, true);
  639.  
  640.       try {
  641.         // XXX - UGH. Template builder is NOT removing the element from the
  642.         //       tree, and so selection remains non-zero in length and we go into
  643.         //       an infinite loop here. Tear the node out of the document. 
  644.         var parent = aSelection[count].parentNode;
  645.         parent.removeChild(aSelection[count]);
  646.       }
  647.       catch (e) {
  648.       }
  649.       // Manipulate the selection array ourselves. 
  650.       aSelection.splice(count,1);
  651.     }
  652.     this.selectElement(nextElement);
  653.   },
  654.  
  655.   moveBookmark: function (aBookmarkURI, aFromFolderURI, aToFolderURI)
  656.   {
  657.     const kBMDS = this.RDF.GetDataSource("rdf:bookmarks");
  658.     const kRDFCContractID = "@mozilla.org/rdf/container;1";
  659.     const kRDFCIID = Components.interfaces.nsIRDFContainer;
  660.     const kRDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
  661.     const krSrc = this.RDF.GetResource(aBookmarkURI);
  662.     const krOldParent = this.RDF.GetResource(aFromFolderURI);
  663.     const krNewParent = this.RDF.GetResource(aToFolderURI);
  664.     kRDFC.Init(kBMDS, krNewParent);
  665.     kRDFC.AppendElement(krSrc);
  666.     kRDFC.Init(kBMDS, krOldParent);
  667.     kRDFC.RemoveElement(krSrc, true);
  668.   },
  669.   
  670.   open: function (aEvent, aRDFNode, aTarget) 
  671.   { 
  672.     var urlValue = LITERAL(this.db, aRDFNode, NC_NS + "URL");
  673.     
  674.     // Ignore "NC:" and empty urls.
  675.     if (urlValue.substring(0,3) == "NC:" || !urlValue) return;
  676.     
  677.     if (aEvent && aEvent.altKey)   
  678.       this.showPropertiesForNode (aRDFNode);
  679.     else if (aTarget == "new_window")
  680.       openDialog (getBrowserURL(), "_blank", "chrome,all,dialog=no", urlValue);
  681.     else if (aTarget == "new_tab") {
  682.       var browser = null;
  683.       if ("getBrowser" in window)
  684.         browser = getBrowser();
  685.       if (browser && "localName" in browser && browser.localName == "tabbrowser") {
  686.         var theTab, loadInBackground;
  687.         theTab = browser.addTab(urlValue); // open link in a new tab
  688.         try {
  689.           if (pref)
  690.             loadInBackground = pref.getBoolPref("browser.tabs.loadInBackground");
  691.           if (!loadInBackground)
  692.             browser.selectedTab = theTab;
  693.         }
  694.         catch (e) {
  695.         }
  696.       }
  697.     }  
  698.     else
  699.       openTopWin (urlValue);
  700.     if (aEvent) 
  701.       aEvent.preventBubble();
  702.   },
  703.  
  704.   showPropertiesForNode: function (aBookmarkItem) 
  705.   {
  706.     if (aBookmarkItem.getAttribute("type") != NC_NS + "BookmarkSeparator") 
  707.       openDialog("chrome://communicator/content/bookmarks/bm-props.xul",
  708.                  "", "centerscreen,chrome,resizable=no", aBookmarkItem.id);
  709.   },
  710.  
  711.   findInBookmarks: function ()
  712.   {
  713.     openDialog("chrome://communicator/content/bookmarks/findBookmark.xul",
  714.                "FindBookmarksWindow",
  715.                "centerscreen,resizable=no,chrome,dependent");
  716.   },
  717.  
  718.   flushDataSource: function ()
  719.   {
  720.     const kBMDS = this.RDF.GetDataSource("rdf:bookmarks");
  721.     var remoteDS = kBMDS.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  722.     remoteDS.Flush();
  723.   },
  724.  
  725.   /////////////////////////////////////////////////////////////////////////////
  726.   // takes a node and adds the appropriate adornments for a bookmark container. 
  727.   createBookmarkFolderDecorations: function (aNode)
  728.   {
  729.     aNode.setAttribute("type", "http://home.netscape.com/NC-rdf#Folder");
  730.     aNode.setAttribute("container", "true");
  731.     return aNode;
  732.   }
  733. };
  734.  
  735. function CommandArrayEnumerator (aCommandArray)
  736. {
  737.   this._inner = [];
  738.   const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
  739.   const kRDFIID = Components.interfaces.nsIRDFService;
  740.   const RDF = Components.classes[kRDFContractID].getService(kRDFIID);
  741.   for (var i = 0; i < aCommandArray.length; ++i)
  742.     this._inner.push(RDF.GetResource(NC_NS_CMD + aCommandArray[i]));
  743.     
  744.   this._index = 0;
  745. }
  746.  
  747. CommandArrayEnumerator.prototype = {
  748.   getNext: function () 
  749.   {
  750.     return this._inner[this._index];
  751.   },
  752.   
  753.   hasMoreElements: function ()
  754.   {
  755.     return this._index < this._inner.length;
  756.   }
  757. };
  758.  
  759. var BookmarksUtils = {
  760.  
  761.   _rdf: null,
  762.   get RDF ()
  763.   {
  764.     if (!this._rdf) {
  765.       const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
  766.       const kRDFIID = Components.interfaces.nsIRDFService;
  767.       this._rdf = Components.classes[kRDFContractID].getService(kRDFIID);
  768.     }
  769.     return this._rdf;
  770.   },
  771.  
  772.   _bmds: null,
  773.   get BMDS ()
  774.   {
  775.     if (!this._bmds) {
  776.       this._bmds = this.RDF.GetDataSource("rdf:bookmarks");
  777.     }
  778.     return this._bmds;
  779.   },
  780.  
  781.   _bundle        : null,
  782.   _brandShortName: null,
  783.  
  784.   /////////////////////////////////////////////////////////////////////////////////////
  785.   // returns a property from chrome://communicator/locale/bookmarks/bookmark.properties
  786.   getLocaleString: function (aStringKey)
  787.   {
  788.     if (!this._bundle) {
  789.       var LOCALESVC = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
  790.                                 .getService(Components.interfaces.nsILocaleService);
  791.       var BUNDLESVC = Components.classes["@mozilla.org/intl/stringbundle;1"]
  792.                                 .getService(Components.interfaces.nsIStringBundleService);
  793.       var bookmarksBundle  = "chrome://communicator/locale/bookmarks/bookmark.properties";
  794.       this._bundle         = BUNDLESVC.createBundle(bookmarksBundle, LOCALESVC.GetApplicationLocale());
  795.       var brandBundle      = "chrome://global/locale/brand.properties";
  796.       this._brandShortName = BUNDLESVC.createBundle(brandBundle,     LOCALESVC.GetApplicationLocale())
  797.                                       .GetStringFromName("brandShortName");
  798.     }
  799.    
  800.     var bundle = this._bundle.GetStringFromName(aStringKey);
  801.     bundle = bundle.replace(/%brandShortName%/, this._brandShortName);
  802.     return bundle;
  803.   },
  804.     
  805.   /////////////////////////////////////////////////////////////////////////////
  806.   // Determine the rdf:type property for the given resource.
  807.   resolveType: function (aResource)
  808.   {
  809.     var rElement;
  810.     if (typeof(aResource) == "string")
  811.       rElement = this.RDF.GetResource(aResource);
  812.     else
  813.       rElement = aResource;
  814.  
  815.     const typeArc = this.RDF.GetResource(RDF_NS + "type");
  816.     const type    = this.BMDS.GetTarget(rElement, typeArc, true);
  817.     try {
  818.       return type.QueryInterface(Components.interfaces.nsIRDFResource).Value;
  819.     }
  820.     catch (e) {
  821.       try { 
  822.         return type.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
  823.       }
  824.       catch (e) {
  825.         return null;
  826.       }
  827.     }    
  828.   },
  829.  
  830.   ///////////////////////////////////////////////////////////////////////////
  831.   // Execute a command with the given source and arguments
  832.   doBookmarksCommand: function (aSourceURI, aCommand, aArgumentsArray)
  833.   {
  834.     var rCommand = this.RDF.GetResource(aCommand);
  835.   
  836.     var kSuppArrayContractID = "@mozilla.org/supports-array;1";
  837.     var kSuppArrayIID = Components.interfaces.nsISupportsArray;
  838.     var sourcesArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  839.     if (aSourceURI) {
  840.       var rSource = this.RDF.GetResource(aSourceURI);
  841.       sourcesArray.AppendElement (rSource);
  842.     }
  843.   
  844.     var argsArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  845.     for (var i = 0; i < aArgumentsArray.length; ++i) {
  846.       var rArc = this.RDF.GetResource(aArgumentsArray[i].property);
  847.       argsArray.AppendElement(rArc);
  848.       var rValue = null;
  849.       if ("resource" in aArgumentsArray[i]) { 
  850.         rValue = this.RDF.GetResource(aArgumentsArray[i].resource);
  851.       }
  852.       else
  853.         rValue = this.RDF.GetLiteral(aArgumentsArray[i].literal);
  854.       argsArray.AppendElement(rValue);
  855.     }
  856.  
  857.     // Exec the command in the Bookmarks datasource. 
  858.     const kBMDS = this.RDF.GetDataSource("rdf:bookmarks");
  859.     kBMDS.DoCommand(sourcesArray, rCommand, argsArray);
  860.   },
  861.  
  862.   cloneFolder: function (aFolder, aParent, aRelativeItem) 
  863.   {
  864.     var BMDS = this.RDF.GetDataSource("rdf:bookmarks");
  865.     
  866.     var nameArc = this.RDF.GetResource(NC_NS + "Name");
  867.     var rName = BMDS.GetTarget(aFolder, nameArc, true);
  868.     rName = rName.QueryInterface(Components.interfaces.nsIRDFLiteral);
  869.     
  870.     var newFolder = this.createFolderWithID(rName.Value, aRelativeItem, aParent);
  871.     
  872.     // Now need to append kiddies. 
  873.     try {
  874.       const kRDFCContractID = "@mozilla.org/rdf/container;1";
  875.       const kRDFCIID = Components.interfaces.nsIRDFContainer;
  876.       var RDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
  877.       const kRDFCUContractID = "@mozilla.org/rdf/container-utils;1";
  878.       const kRDFCUIID = Components.interfaces.nsIRDFContainerUtils;
  879.       var RDFCU = Components.classes[kRDFCUContractID].getService(kRDFCUIID);
  880.  
  881.       RDFC.Init(BMDS, aFolder);
  882.       var elts = RDFC.GetElements();
  883.       RDFC.Init(BMDS, newFolder);
  884.  
  885.       while (elts.hasMoreElements()) {
  886.         var curr = elts.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  887.         if (RDFCU.IsContainer(BMDS, curr))
  888.           BookmarksUtils.cloneFolder(curr, newFolder);
  889.         else
  890.           RDFC.AppendElement(curr);
  891.       }
  892.     }
  893.     catch (e) {
  894.     }
  895.     return newFolder;
  896.   },
  897.   
  898.   createFolderWithID: function (aTitle, aRelativeItem, aParentFolder)
  899.   {
  900.     const kRDFCContractID = "@mozilla.org/rdf/container;1";
  901.     const kRDFCIID = Components.interfaces.nsIRDFContainer;
  902.     var RDFC = Components.classes[kRDFCContractID].createInstance(kRDFCIID);
  903.     var BMDS = this.RDF.GetDataSource("rdf:bookmarks");
  904.     try {
  905.       RDFC.Init(BMDS, aParentFolder);
  906.     }
  907.     catch (e) {
  908.       return null;
  909.     }
  910.  
  911.     var ix = RDFC.IndexOf(aRelativeItem);
  912.     var BMSvc = BMDS.QueryInterface(Components.interfaces.nsIBookmarksService);
  913.     return BMSvc.createFolderInContainer(aTitle, aParentFolder, ix);
  914.   },
  915.  
  916.   addBookmarkForTabBrowser: function( aTabBrowser, aSelect )
  917.   {
  918.     var tabsInfo = [];
  919.     var currentTabInfo = { name: "", url: "", charset: null };
  920.  
  921.     const activeBrowser = aTabBrowser.selectedBrowser;
  922.     const browsers = aTabBrowser.browsers;
  923.     for (var i = 0; i < browsers.length; ++i) {
  924.       var webNav = browsers[i].webNavigation;
  925.       var url = webNav.currentURI.spec;
  926.       var name = "";
  927.       var charset;
  928.       try {
  929.         var doc = webNav.document;
  930.         name = doc.title || url;
  931.         charset = doc.characterSet;
  932.       } catch (e) {
  933.         name = url;
  934.       }
  935.  
  936.       tabsInfo[i] = { name: name, url: url, charset: charset };
  937.  
  938.       if (browsers[i] == activeBrowser)
  939.         currentTabInfo = tabsInfo[i];
  940.     }
  941.  
  942.     openDialog("chrome://communicator/content/bookmarks/addBookmark.xul", "",
  943.                "centerscreen,chrome,dialog=yes,resizable,dependent",
  944.                currentTabInfo.name, currentTabInfo.url, null,
  945.                currentTabInfo.charset, "addGroup" + (aSelect ? ",group" : ""), tabsInfo);
  946.   },
  947.  
  948.   addBookmarkForBrowser: function (aDocShell, aShowDialog)
  949.   {
  950.     // Bug 52536: We obtain the URL and title from the nsIWebNavigation 
  951.     //            associated with a <browser/> rather than from a DOMWindow.
  952.     //            This is because when a full page plugin is loaded, there is
  953.     //            no DOMWindow (?) but information about the loaded document
  954.     //            may still be obtained from the webNavigation. 
  955.     var url = aDocShell.currentURI.spec;
  956.     var title, docCharset = null;
  957.     try {
  958.       title = aDocShell.document.title || url;
  959.       docCharset = aDocShell.document.characterSet;
  960.     }
  961.     catch (e) {
  962.       title = url;
  963.     }
  964.  
  965.     this.addBookmark(url, title, docCharset, aShowDialog);
  966.   },
  967.   
  968.   addBookmark: function (aURL, aTitle, aCharset, aShowDialog)
  969.   {
  970.     if (aCharset === undefined) {
  971.       var fw = document.commandDispatcher.focusedWindow;
  972.       aCharset = fw.document.characterSet;
  973.     }
  974.  
  975.     if (aShowDialog)
  976.       openDialog("chrome://communicator/content/bookmarks/addBookmark.xul", "", 
  977.                  "centerscreen,chrome,dialog=yes,resizable,dependent", aTitle, aURL, null, aCharset);
  978.     else {
  979.       // User has elected to override the file dialog and always file bookmarks
  980.       // into the default bookmark folder. 
  981.       const kBMSvcContractID = "@mozilla.org/browser/bookmarks-service;1";
  982.       const kBMSvcIID = Components.interfaces.nsIBookmarksService;
  983.       const kBMSvc = Components.classes[kBMSvcContractID].getService(kBMSvcIID);
  984.       kBMSvc.addBookmarkImmediately(aURL, aTitle, kBMSvcIID.BOOKMARK_DEFAULT_TYPE, aCharset);
  985.     }
  986.   },
  987.  
  988.   getSpecialFolder: function (aID)
  989.   {
  990.     var sources = this.BMDS.GetSources(this.RDF.GetResource("http://home.netscape.com/NC-rdf#FolderType"),
  991.                                        this.RDF.GetResource(aID), true);
  992.     var folder = null;
  993.     if (sources.hasMoreElements())
  994.       folder = sources.getNext();
  995.     else 
  996.       folder = this.RDF.GetResource("NC:BookmarksRoot");
  997.     return folder;
  998.   },
  999.  
  1000.   getNewBookmarkFolder: function()
  1001.   {
  1002.     return this.getSpecialFolder("NC:NewBookmarkFolder");
  1003.   },
  1004.  
  1005.   getNewSearchFolder: function()
  1006.   {
  1007.     return this.getSpecialFolder("NC:NewSearchFolder");
  1008.   }
  1009.  
  1010. }
  1011.  
  1012. var ContentUtils = {
  1013.   childByLocalName: function (aSelectedItem, aLocalName)
  1014.   {
  1015.     var temp = aSelectedItem.firstChild;
  1016.     while (temp) {
  1017.       if (temp.localName == aLocalName)
  1018.         return temp;
  1019.       temp = temp.nextSibling;
  1020.     }
  1021.     return null;
  1022.   }
  1023. };
  1024.  
  1025.  
  1026.  
  1027.  
  1028.  
  1029.  
  1030.