home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / firefox-3.0.14 / chrome / browser.jar / content / browser / places / places.js < prev    next >
Encoding:
JavaScript  |  2009-04-21  |  44.6 KB  |  1,247 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  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 Places Organizer.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005-2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Ben Goodger <beng@google.com>
  23.  *   Annie Sullivan <annie.sullivan@gmail.com>
  24.  *   Asaf Romano <mano@mozilla.com>
  25.  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. var PlacesOrganizer = {
  42.   _places: null,
  43.   _content: null,
  44.  
  45.   _initFolderTree: function() {
  46.     var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
  47.     this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
  48.   },
  49.  
  50.   selectLeftPaneQuery: function PO_selectLeftPaneQuery(aQueryName) {
  51.     var itemId = PlacesUIUtils.leftPaneQueries[aQueryName];
  52.     this._places.selectItems([itemId]);
  53.     // Forcefully expand all-bookmarks
  54.     if (aQueryName == "AllBookmarks")
  55.       asContainer(this._places.selectedNode).containerOpen = true;
  56.   },
  57.  
  58.   init: function PO_init() {
  59.     this._places = document.getElementById("placesList");
  60.     this._content = document.getElementById("placeContent");
  61.     this._initFolderTree();
  62.  
  63.     var leftPaneSelection = "AllBookmarks"; // default to all-bookmarks
  64.     if ("arguments" in window && window.arguments.length > 0)
  65.       leftPaneSelection = window.arguments[0];
  66.  
  67.     this.selectLeftPaneQuery(leftPaneSelection);
  68.     // clear the back-stack
  69.     this._backHistory.splice(0);
  70.     document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
  71.  
  72.     var view = this._content.treeBoxObject.view;
  73.     if (view.rowCount > 0)
  74.       view.selection.select(0);
  75.  
  76.     this._content.focus();
  77.  
  78.     // Set up the search UI.
  79.     PlacesSearchBox.init();
  80.  
  81. //@line 85 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/components/places/content/places.js"
  82.  
  83.     window.addEventListener("AppCommand", this, true);
  84. //@line 107 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/components/places/content/places.js"
  85.  
  86.     // remove the "Properties" context-menu item, we've our own details pane
  87.     document.getElementById("placesContext")
  88.             .removeChild(document.getElementById("placesContext_show:info"));
  89.   },
  90.  
  91.   QueryInterface: function PO_QueryInterface(aIID) {
  92.     if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
  93.         aIID.equals(Components.interfaces.nsISupports))
  94.       return this;
  95.  
  96.     throw Components.results.NS_NOINTERFACE;
  97.   },
  98.  
  99.   handleEvent: function PO_handleEvent(aEvent) {
  100.     if (aEvent.type != "AppCommand")
  101.       return;
  102.  
  103.     aEvent.stopPropagation();
  104.     switch (aEvent.command) {
  105.       case "Back":
  106.         if (this._backHistory.length > 0)
  107.           this.back();
  108.         break;
  109.       case "Forward":
  110.         if (this._forwardHistory.length > 0)
  111.           this.forward();
  112.         break;
  113.       case "Search":
  114.         PlacesSearchBox.findAll();
  115.         break;
  116.     }
  117.   },
  118.  
  119.   destroy: function PO_destroy() {
  120.   },
  121.  
  122.   _location: null,
  123.   get location() {
  124.     return this._location;
  125.   },
  126.  
  127.   set location(aLocation) {
  128.     if (!aLocation || this._location == aLocation)
  129.       return aLocation;
  130.  
  131.     if (this.location) {
  132.       this._backHistory.unshift(this.location);
  133.       this._forwardHistory.splice(0);
  134.     }
  135.  
  136.     this._location = aLocation;
  137.     this._places.selectPlaceURI(aLocation);
  138.  
  139.     if (!this._places.hasSelection) {
  140.       // If no node was found for the given place: uri, just load it directly
  141.       this._content.place = aLocation;
  142.     }
  143.     this.onContentTreeSelect();
  144.  
  145.     // update navigation commands
  146.     if (this._backHistory.length == 0)
  147.       document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
  148.     else
  149.       document.getElementById("OrganizerCommand:Back").removeAttribute("disabled");
  150.     if (this._forwardHistory.length == 0)
  151.       document.getElementById("OrganizerCommand:Forward").setAttribute("disabled", true);
  152.     else
  153.       document.getElementById("OrganizerCommand:Forward").removeAttribute("disabled");
  154.  
  155.     return aLocation;
  156.   },
  157.  
  158.   _backHistory: [],
  159.   _forwardHistory: [],
  160.  
  161.   back: function PO_back() {
  162.     this._forwardHistory.unshift(this.location);
  163.     var historyEntry = this._backHistory.shift();
  164.     this._location = null;
  165.     this.location = historyEntry;
  166.   },
  167.   forward: function PO_forward() {
  168.     this._backHistory.unshift(this.location);
  169.     var historyEntry = this._forwardHistory.shift();
  170.     this._location = null;
  171.     this.location = historyEntry;
  172.   },
  173.  
  174.   /**
  175.    * Called when a place folder is selected in the left pane.
  176.    * @param   resetSearchBox
  177.    *          true if the search box should also be reset, false if it should
  178.    *          be left alone.
  179.    */
  180.   onPlaceSelected: function PO_onPlaceSelected(resetSearchBox) {
  181.     // Don't change the right-hand pane contents when there's no selection
  182.     if (!this._places.hasSelection)
  183.       return;
  184.  
  185.     var node = this._places.selectedNode;
  186.     var queries = asQuery(node).getQueries({});
  187.  
  188.     // Items are only excluded on the left pane
  189.     var options = node.queryOptions.clone();
  190.     options.excludeItems = false;
  191.     var placeURI = PlacesUtils.history.queriesToQueryString(queries, queries.length, options);
  192.  
  193.     // Update the right-pane contents.
  194.     // We must update also if the user clears the search box, in that case
  195.     // we are called with resetSearchBox == false.
  196.     if (this._content.place != placeURI || !resetSearchBox) {
  197.       this._content.place = placeURI;
  198.  
  199.       // Update the back/forward buttons.
  200.       this.location = node.uri;
  201.     }
  202.  
  203.     // Make sure the search UI is hidden.
  204.     PlacesSearchBox.hideSearchUI();
  205.     if (resetSearchBox) {
  206.       var searchFilter = document.getElementById("searchFilter");
  207.       searchFilter.reset();
  208.     }
  209.  
  210.     this._setSearchScopeForNode(node);
  211.     if (this._places.treeBoxObject.focused)
  212.       this._fillDetailsPane(node);
  213.   },
  214.  
  215.   /**
  216.    * Sets the search scope based on node's properties
  217.    * @param   aNode
  218.    *          the node to set up scope from
  219.    */
  220.   _setSearchScopeForNode: function PO__setScopeForNode(aNode) {
  221.     var scopeBarFolder = document.getElementById("scopeBarFolder");
  222.     var itemId = aNode.itemId;
  223.     if (PlacesUtils.nodeIsHistoryContainer(aNode) ||
  224.         itemId == PlacesUIUtils.leftPaneQueries["History"]) {
  225.       scopeBarFolder.disabled = true;
  226.       var folders = [];
  227.       var filterCollection = "history";
  228.       var scopeButton = "scopeBarHistory";
  229.     }
  230.     else if (PlacesUtils.nodeIsFolder(aNode) &&
  231.              itemId != PlacesUIUtils.leftPaneQueries["AllBookmarks"] &&
  232.              itemId != PlacesUIUtils.leftPaneQueries["Tags"] &&
  233.              aNode.parent.itemId != PlacesUIUtils.leftPaneQueries["Tags"]) {
  234.       // enable folder scope
  235.       scopeBarFolder.disabled = false;
  236.       var folders = [PlacesUtils.getConcreteItemId(aNode)];
  237.       var filterCollection = "collection";
  238.       var scopeButton = "scopeBarFolder";
  239.     }
  240.     else {
  241.       // default to All Bookmarks
  242.       scopeBarFolder.disabled = true;
  243.       var folders = [];
  244.       var filterCollection = "bookmarks";
  245.       var scopeButton = "scopeBarAll";
  246.     }
  247.  
  248.     // set search scope
  249.     PlacesSearchBox.folders = folders;
  250.     PlacesSearchBox.filterCollection = filterCollection;
  251.  
  252.     // update scope bar active child
  253.     var scopeBar = document.getElementById("organizerScopeBar");
  254.     var child = scopeBar.firstChild;
  255.     while (child) {
  256.       if (child.getAttribute("id") != scopeButton)
  257.         child.removeAttribute("checked");
  258.       else
  259.         child.setAttribute("checked", "true");
  260.       child = child.nextSibling;
  261.     }
  262.  
  263.     // Update the "Find in <current collection>" command
  264.     var findCommand = document.getElementById("OrganizerCommand_find:current");
  265.     var findLabel = PlacesUIUtils.getFormattedString("findInPrefix", [aNode.title]);
  266.     findCommand.setAttribute("label", findLabel);
  267.   },
  268.  
  269.   /**
  270.    * Handle clicks on the tree. If the user middle clicks on a URL, load that
  271.    * URL according to rules. Single clicks or modified clicks do not result in
  272.    * any special action, since they're related to selection.
  273.    * @param   aEvent
  274.    *          The mouse event.
  275.    */
  276.   onTreeClick: function PO_onTreeClick(aEvent) {
  277.     if (aEvent.target.localName != "treechildren")
  278.       return;
  279.  
  280.     var currentView = aEvent.currentTarget;
  281.     var selectedNode = currentView.selectedNode;
  282.     if (selectedNode && aEvent.button == 1) {
  283.       if (PlacesUtils.nodeIsURI(selectedNode))
  284.         PlacesUIUtils.openNodeWithEvent(selectedNode, aEvent);
  285.       else if (PlacesUtils.nodeIsContainer(selectedNode)) {
  286.         // The command execution function will take care of seeing the
  287.         // selection is a folder/container and loading its contents in
  288.         // tabs for us.
  289.         PlacesUIUtils.openContainerNodeInTabs(selectedNode);
  290.       }
  291.     }
  292.   },
  293.  
  294.   /**
  295.    * Handle focus changes on the trees.
  296.    * When moving focus between panes we should update the details pane contents.
  297.    * @param   aEvent
  298.    *          The mouse event.
  299.    */
  300.   onTreeFocus: function PO_onTreeFocus(aEvent) {
  301.     var currentView = aEvent.currentTarget;
  302.     var selectedNode = currentView.selectedNode;
  303.     this._fillDetailsPane(selectedNode);
  304.   },
  305.  
  306.   openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) {
  307.     if (aContainer.itemId != -1)
  308.       this._places.selectItems([aContainer.itemId]);
  309.     else if (PlacesUtils.nodeIsQuery(aContainer))
  310.       this._places.selectPlaceURI(aContainer.uri);
  311.   },
  312.  
  313.   openSelectedNode: function PU_openSelectedNode(aEvent) {
  314.     PlacesUIUtils.openNodeWithEvent(this._content.selectedNode, aEvent);
  315.   },
  316.  
  317.   /**
  318.    * Returns the options associated with the query currently loaded in the
  319.    * main places pane.
  320.    */
  321.   getCurrentOptions: function PO_getCurrentOptions() {
  322.     return asQuery(this._content.getResult().root).queryOptions;
  323.   },
  324.  
  325.   /**
  326.    * Returns the queries associated with the query currently loaded in the
  327.    * main places pane.
  328.    */
  329.   getCurrentQueries: function PO_getCurrentQueries() {
  330.     return asQuery(this._content.getResult().root).getQueries({});
  331.   },
  332.  
  333.   /**
  334.    * Show the migration wizard for importing from a file.
  335.    */
  336.   importBookmarks: function PO_import() {
  337.     // XXX: ifdef it to be non-modal (non-"sheet") on mac (see bug 259039)
  338.     var features = "modal,centerscreen,chrome,resizable=no";
  339.  
  340.     // The migrator window will set this to true when it closes, if the user
  341.     // chose to migrate from a specific file.
  342.     window.fromFile = false;
  343.     openDialog("chrome://browser/content/migration/migration.xul",
  344.                "migration", features, "bookmarks");
  345.     if (window.fromFile)
  346.       this.importFromFile();
  347.   },
  348.  
  349.   /**
  350.    * Open a file-picker and import the selected file into the bookmarks store
  351.    */
  352.   importFromFile: function PO_importFromFile() {
  353.     var fp = Cc["@mozilla.org/filepicker;1"].
  354.              createInstance(Ci.nsIFilePicker);
  355.     fp.init(window, PlacesUIUtils.getString("SelectImport"),
  356.             Ci.nsIFilePicker.modeOpen);
  357.     fp.appendFilters(Ci.nsIFilePicker.filterHTML);
  358.     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
  359.       if (fp.file) {
  360.         var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].
  361.                        getService(Ci.nsIPlacesImportExportService);
  362.         var file = fp.file.QueryInterface(Ci.nsILocalFile);
  363.         importer.importHTMLFromFile(file, false);
  364.       }
  365.     }
  366.   },
  367.  
  368.   /**
  369.    * Allows simple exporting of bookmarks.
  370.    */
  371.   exportBookmarks: function PO_exportBookmarks() {
  372.     var fp = Cc["@mozilla.org/filepicker;1"].
  373.              createInstance(Ci.nsIFilePicker);
  374.     fp.init(window, PlacesUIUtils.getString("EnterExport"),
  375.             Ci.nsIFilePicker.modeSave);
  376.     fp.appendFilters(Ci.nsIFilePicker.filterHTML);
  377.     fp.defaultString = "bookmarks.html";
  378.     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
  379.       var exporter = Cc["@mozilla.org/browser/places/import-export-service;1"].
  380.                      getService(Ci.nsIPlacesImportExportService);
  381.       exporter.exportHTMLToFile(fp.file);
  382.     }
  383.   },
  384.  
  385.   /**
  386.    * Populates the restore menu with the dates of the backups available.
  387.    */
  388.   populateRestoreMenu: function PO_populateRestoreMenu() {
  389.     var restorePopup = document.getElementById("fileRestorePopup");
  390.  
  391.     // remove existing menu items
  392.     // last item is the restoreFromFile item
  393.     while (restorePopup.childNodes.length > 1)
  394.       restorePopup.removeChild(restorePopup.firstChild);
  395.  
  396.     // get list of files
  397.     var localizedFilename = PlacesUtils.getString("bookmarksArchiveFilename");
  398.     var localizedFilenamePrefix = localizedFilename.substr(0, localizedFilename.indexOf("-"));
  399.     var fileList = [];
  400.     var files = this.bookmarksBackupDir.directoryEntries;
  401.     while (files.hasMoreElements()) {
  402.       var f = files.getNext().QueryInterface(Ci.nsIFile);
  403.       var rx = new RegExp("^(bookmarks|" + localizedFilenamePrefix + ")-.+\.json");
  404.       if (!f.isHidden() && f.leafName.match(rx))
  405.         fileList.push(f);
  406.     }
  407.  
  408.     fileList.sort(function PO_fileList_compare(a, b) {
  409.       return b.lastModifiedTime - a.lastModifiedTime;
  410.     });
  411.  
  412.     if (fileList.length == 0)
  413.       return;
  414.  
  415.     // populate menu
  416.     for (var i = 0; i < fileList.length; i++) {
  417.       var m = restorePopup.insertBefore
  418.         (document.createElement("menuitem"),
  419.          document.getElementById("restoreFromFile"));
  420.       var rx = new RegExp("^(bookmarks|" + localizedFilenamePrefix + ")-");
  421.       var dateStr = fileList[i].leafName.replace(rx, "").replace(/\.json$/, "");
  422.       if (!dateStr.length)
  423.         dateStr = fileList[i].leafName;
  424.       m.setAttribute("label", dateStr);
  425.       m.setAttribute("value", fileList[i].leafName);
  426.       m.setAttribute("oncommand",
  427.                      "PlacesOrganizer.onRestoreMenuItemClick(this);");
  428.     }
  429.     restorePopup.insertBefore(document.createElement("menuseparator"),
  430.                               document.getElementById("restoreFromFile"));
  431.   },
  432.  
  433.   /**
  434.    * Called when a menuitem is selected from the restore menu.
  435.    */
  436.   onRestoreMenuItemClick: function PO_onRestoreMenuItemClick(aMenuItem) {
  437.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  438.                  getService(Ci.nsIProperties);
  439.     var bookmarksFile = dirSvc.get("ProfD", Ci.nsIFile);
  440.     bookmarksFile.append("bookmarkbackups");
  441.     bookmarksFile.append(aMenuItem.getAttribute("value"));
  442.     if (!bookmarksFile.exists())
  443.       return;
  444.     this.restoreBookmarksFromFile(bookmarksFile);
  445.   },
  446.  
  447.   /**
  448.    * Called when 'Choose File...' is selected from the restore menu.
  449.    * Prompts for a file and restores bookmarks to those in the file.
  450.    */
  451.   onRestoreBookmarksFromFile: function PO_onRestoreBookmarksFromFile() {
  452.     var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  453.     fp.init(window, PlacesUIUtils.getString("bookmarksRestoreTitle"),
  454.             Ci.nsIFilePicker.modeOpen);
  455.     fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
  456.                     PlacesUIUtils.getString("bookmarksRestoreFilterExtension"));
  457.  
  458.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  459.                  getService(Ci.nsIProperties);
  460.     var backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
  461.     fp.displayDirectory = backupsDir;
  462.  
  463.     if (fp.show() != Ci.nsIFilePicker.returnCancel)
  464.       this.restoreBookmarksFromFile(fp.file);
  465.   },
  466.  
  467.   /**
  468.    * Restores bookmarks from a JSON file.
  469.    */
  470.   restoreBookmarksFromFile: function PO_restoreBookmarksFromFile(aFile) {
  471.     // check file extension
  472.     if (!aFile.leafName.match(/\.json$/)) {
  473.       this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreFormatError"));
  474.       return;
  475.     }
  476.  
  477.     // confirm ok to delete existing bookmarks
  478.     var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  479.                   getService(Ci.nsIPromptService);
  480.     if (!prompts.confirm(null,
  481.                          PlacesUIUtils.getString("bookmarksRestoreAlertTitle"),
  482.                          PlacesUIUtils.getString("bookmarksRestoreAlert")))
  483.       return;
  484.  
  485.     try {
  486.       PlacesUtils.restoreBookmarksFromJSONFile(aFile, [PlacesUIUtils.leftPaneFolderId]);
  487.     }
  488.     catch(ex) {
  489.       this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreParseError"));
  490.     }
  491.   },
  492.  
  493.   _showErrorAlert: function PO__showErrorAlert(aMsg) {
  494.     var brandShortName = document.getElementById("brandStrings").
  495.                                   getString("brandShortName");
  496.  
  497.     Cc["@mozilla.org/embedcomp/prompt-service;1"].
  498.       getService(Ci.nsIPromptService).
  499.       alert(window, brandShortName, aMsg);
  500.   },
  501.  
  502.   /**
  503.    * Backup bookmarks to desktop, auto-generate a filename with a date.
  504.    * The file is a JSON serialization of bookmarks, tags and any annotations
  505.    * of those items.
  506.    */
  507.   backupBookmarks: function PO_backupBookmarks() {
  508.     var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  509.     fp.init(window, PlacesUIUtils.getString("bookmarksBackupTitle"),
  510.             Ci.nsIFilePicker.modeSave);
  511.     fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
  512.                     PlacesUIUtils.getString("bookmarksRestoreFilterExtension"));
  513.  
  514.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  515.                  getService(Ci.nsIProperties);
  516.     var backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
  517.     fp.displayDirectory = backupsDir;
  518.  
  519.     // Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters
  520.     // and makes the alphabetical order of multiple backup files more useful.
  521.     var date = (new Date).toLocaleFormat("%Y-%m-%d");
  522.     fp.defaultString = PlacesUIUtils.getFormattedString("bookmarksBackupFilenameJSON",
  523.                                                         [date]);
  524.  
  525.     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
  526.       PlacesUtils.backupBookmarksToFile(fp.file, [PlacesUIUtils.leftPaneFolderId]);
  527.  
  528.       // copy new backup to /backups dir (bug 424389)
  529.       var latestBackup = PlacesUtils.getMostRecentBackup();
  530.       if (!latestBackup || latestBackup != fp.file) {
  531.         latestBackup.remove(false);
  532.         var date = new Date().toLocaleFormat("%Y-%m-%d");
  533.         var name = PlacesUtils.getFormattedString("bookmarksArchiveFilename",
  534.                                                   [date]);
  535.         fp.file.copyTo(this.bookmarksBackupDir, name);
  536.       }
  537.     }
  538.   },
  539.  
  540.   get bookmarksBackupDir() {
  541.     delete this.bookmarksBackupDir;
  542.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  543.                  getService(Ci.nsIProperties);
  544.     var bookmarksBackupDir = dirSvc.get("ProfD", Ci.nsIFile);
  545.     bookmarksBackupDir.append("bookmarkbackups");
  546.     if (!bookmarksBackupDir.exists())
  547.       bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
  548.     return this.bookmarksBackupDir = bookmarksBackupDir;
  549.   },
  550.  
  551.   _paneDisabled: false,
  552.   _setDetailsFieldsDisabledState:
  553.   function PO__setDetailsFieldsDisabledState(aDisabled) {
  554.     if (aDisabled) {
  555.       document.getElementById("paneElementsBroadcaster")
  556.               .setAttribute("disabled", "true");
  557.     }
  558.     else {
  559.       document.getElementById("paneElementsBroadcaster")
  560.               .removeAttribute("disabled");
  561.     }
  562.   },
  563.  
  564.   _detectAndSetDetailsPaneMinimalState:
  565.   function PO__detectAndSetDetailsPaneMinimalState(aNode) {
  566.     /**
  567.      * The details of simple folder-items (as opposed to livemarks) or the
  568.      * of livemark-children are not likely to fill the infoBox anyway,
  569.      * thus we remove the "More/Less" button and show all details.
  570.      *
  571.      * the wasminimal attribute here is used to persist the "more/less"
  572.      * state in a bookmark->folder->bookmark scenario.
  573.      */
  574.     var infoBox = document.getElementById("infoBox");
  575.     var infoBoxExpander = document.getElementById("infoBoxExpander");
  576.     if (aNode.itemId != -1 &&
  577.         ((PlacesUtils.nodeIsFolder(aNode) &&
  578.           !PlacesUtils.nodeIsLivemarkContainer(aNode)) ||
  579.          PlacesUtils.nodeIsLivemarkItem(aNode))) {
  580.       if (infoBox.getAttribute("minimal") == "true")
  581.         infoBox.setAttribute("wasminimal", "true");
  582.       infoBox.removeAttribute("minimal");
  583.       infoBoxExpander.hidden = true;
  584.     }
  585.     else {
  586.       if (infoBox.getAttribute("wasminimal") == "true")
  587.         infoBox.setAttribute("minimal", "true");
  588.       infoBox.removeAttribute("wasminimal");
  589.       infoBoxExpander.hidden = false;
  590.     }
  591.   },
  592.  
  593.   // NOT YET USED
  594.   updateThumbnailProportions: function PO_updateThumbnailProportions() {
  595.     var previewBox = document.getElementById("previewBox");
  596.     var canvas = document.getElementById("itemThumbnail");
  597.     var height = previewBox.boxObject.height;
  598.     var width = height * (screen.width / screen.height);
  599.     canvas.width = width;
  600.     canvas.height = height;
  601.   },
  602.  
  603.   onContentTreeSelect: function PO_onContentTreeSelect() {
  604.     if (this._content.treeBoxObject.focused)
  605.       this._fillDetailsPane(this._content.selectedNode);
  606.   },
  607.  
  608.   _fillDetailsPane: function PO__fillDetailsPane(aSelectedNode) {
  609.     var infoBox = document.getElementById("infoBox");
  610.     var detailsDeck = document.getElementById("detailsDeck");
  611.  
  612.     // If a textbox within a panel is focused, force-blur it so its contents
  613.     // are saved
  614.     if (gEditItemOverlay.itemId != -1) {
  615.       var focusedElement = document.commandDispatcher.focusedElement;
  616.       if ((focusedElement instanceof HTMLInputElement ||
  617.            focusedElement instanceof HTMLTextAreaElement) &&
  618.           /^editBMPanel.*/.test(focusedElement.parentNode.parentNode.id))
  619.         focusedElement.blur();
  620.  
  621.       // don't update the panel if we are already editing this node
  622.       if (aSelectedNode && gEditItemOverlay.itemId == aSelectedNode.itemId &&
  623.           detailsDeck.selectedIndex == 1)
  624.         return;
  625.     }
  626.  
  627.     if (aSelectedNode && !PlacesUtils.nodeIsSeparator(aSelectedNode)) {
  628.       detailsDeck.selectedIndex = 1;
  629.       // Using the concrete itemId is arguably wrong. The bookmarks API
  630.       // does allow setting properties for folder shortcuts as well, but since
  631.       // the UI does not distinct between the couple, we better just show
  632.       // the concrete item properties.
  633.       if (aSelectedNode.type ==
  634.           Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
  635.         gEditItemOverlay.initPanel(asQuery(aSelectedNode).folderItemId,
  636.                                   { hiddenRows: ["folderPicker"],
  637.                                     forceReadOnly: true });
  638.       }
  639.       else {
  640.         var itemId = PlacesUtils.getConcreteItemId(aSelectedNode);
  641.         gEditItemOverlay.initPanel(itemId != -1 ? itemId :
  642.                                    PlacesUtils._uri(aSelectedNode.uri),
  643.                                    { hiddenRows: ["folderPicker"] });
  644.       }
  645.       this._detectAndSetDetailsPaneMinimalState(aSelectedNode);
  646.     }
  647.     else {
  648.       detailsDeck.selectedIndex = 0;
  649.       var selectItemDesc = document.getElementById("selectItemDescription");
  650.       var itemsCountLabel = document.getElementById("itemsCountText");
  651.       var rowCount = this._content.treeBoxObject.view.rowCount;
  652.       if (rowCount == 0) {
  653.         selectItemDesc.hidden = true;
  654.         itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.noItems");
  655.       }
  656.       else {
  657.         selectItemDesc.hidden = false;
  658.         if (rowCount == 1)
  659.           itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.oneItem");
  660.         else {
  661.           itemsCountLabel.value =
  662.             PlacesUIUtils.getFormattedString("detailsPane.multipleItems",
  663.                                              [rowCount]);
  664.         }
  665.       }
  666.     }
  667.   },
  668.  
  669.   // NOT YET USED
  670.   _updateThumbnail: function PO__updateThumbnail() {
  671.     var bo = document.getElementById("previewBox").boxObject;
  672.     var width  = bo.width;
  673.     var height = bo.height;
  674.  
  675.     var canvas = document.getElementById("itemThumbnail");
  676.     var ctx = canvas.getContext('2d');
  677.     var notAvailableText = canvas.getAttribute("notavailabletext");
  678.     ctx.save();
  679.     ctx.fillStyle = "-moz-Dialog";
  680.     ctx.fillRect(0, 0, width, height);
  681.     ctx.translate(width/2, height/2);
  682.  
  683.     ctx.fillStyle = "GrayText";
  684.     ctx.mozTextStyle = "12pt sans serif";
  685.     var len = ctx.mozMeasureText(notAvailableText);
  686.     ctx.translate(-len/2,0);
  687.     ctx.mozDrawText(notAvailableText);
  688.     ctx.restore();
  689.   },
  690.  
  691.   toggleAdditionalInfoFields: function PO_toggleAdditionalInfoFields() {
  692.     var infoBox = document.getElementById("infoBox");
  693.     var infoBoxExpander = document.getElementById("infoBoxExpander");
  694.     if (infoBox.getAttribute("minimal") == "true") {
  695.       infoBox.removeAttribute("minimal");
  696.       infoBoxExpander.label = infoBoxExpander.getAttribute("lesslabel");
  697.       infoBoxExpander.accessKey = infoBoxExpander.getAttribute("lessaccesskey");
  698.     }
  699.     else {
  700.       infoBox.setAttribute("minimal", "true");
  701.       infoBoxExpander.label = infoBoxExpander.getAttribute("morelabel");
  702.       infoBoxExpander.accessKey = infoBoxExpander.getAttribute("moreaccesskey");
  703.     }
  704.   },
  705.  
  706.   /**
  707.    * Save the current search (or advanced query) to the bookmarks root.
  708.    */
  709.   saveSearch: function PO_saveSearch() {
  710.     // Get the place: uri for the query.
  711.     // If the advanced query builder is showing, use that.
  712.     var options = this.getCurrentOptions();
  713.  
  714. //@line 739 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/components/places/content/places.js"
  715.     var queries = this.getCurrentQueries();
  716. //@line 741 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/components/places/content/places.js"
  717.  
  718.     var placeSpec = PlacesUtils.history.queriesToQueryString(queries,
  719.                                                              queries.length,
  720.                                                              options);
  721.     var placeURI = Cc["@mozilla.org/network/io-service;1"].
  722.                    getService(Ci.nsIIOService).
  723.                    newURI(placeSpec, null, null);
  724.  
  725.     // Prompt the user for a name for the query.
  726.     // XXX - using prompt service for now; will need to make
  727.     // a real dialog and localize when we're sure this is the UI we want.
  728.     var title = PlacesUIUtils.getString("saveSearch.title");
  729.     var inputLabel = PlacesUIUtils.getString("saveSearch.inputLabel");
  730.     var defaultText = PlacesUIUtils.getString("saveSearch.defaultText");
  731.  
  732.     var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  733.                   getService(Ci.nsIPromptService);
  734.     var check = {value: false};
  735.     var input = {value: defaultText};
  736.     var save = prompts.prompt(null, title, inputLabel, input, null, check);
  737.  
  738.     // Don't add the query if the user cancels or clears the seach name.
  739.     if (!save || input.value == "")
  740.      return;
  741.  
  742.     // Add the place: uri as a bookmark under the bookmarks root.
  743.     var txn = PlacesUIUtils.ptm.createItem(placeURI,
  744.                                            PlacesUtils.bookmarksMenuFolderId,
  745.                                            PlacesUtils.bookmarks.DEFAULT_INDEX,
  746.                                            input.value);
  747.     PlacesUIUtils.ptm.doTransaction(txn);
  748.  
  749.     // select and load the new query
  750.     this._places.selectPlaceURI(placeSpec);
  751.   }
  752. };
  753.  
  754. /**
  755.  * A set of utilities relating to search within Bookmarks and History.
  756.  */
  757. var PlacesSearchBox = {
  758.  
  759.   /**
  760.    * The Search text field
  761.    */
  762.   get searchFilter() {
  763.     return document.getElementById("searchFilter");
  764.   },
  765.  
  766.   /**
  767.    * Folders to include when searching.
  768.    */
  769.   _folders: [],
  770.   get folders() {
  771.     if (this._folders.length == 0)
  772.       this._folders.push(PlacesUtils.bookmarksMenuFolderId,
  773.                          PlacesUtils.unfiledBookmarksFolderId,
  774.                          PlacesUtils.toolbarFolderId);
  775.     return this._folders;
  776.   },
  777.   set folders(aFolders) {
  778.     this._folders = aFolders;
  779.     return aFolders;
  780.   },
  781.  
  782.   /**
  783.    * Run a search for the specified text, over the collection specified by
  784.    * the dropdown arrow. The default is all bookmarks, but can be
  785.    * localized to the active collection.
  786.    * @param   filterString
  787.    *          The text to search for.
  788.    */
  789.   search: function PSB_search(filterString) {
  790.     var PO = PlacesOrganizer;
  791.     // If the user empties the search box manually, reset it and load all
  792.     // contents of the current scope.
  793.     // XXX this might be to jumpy, maybe should search for "", so results
  794.     // are ungrouped, and search box not reset
  795.     if ((filterString == "" || this.searchFilter.hasAttribute("empty"))) {
  796.       PO.onPlaceSelected(false);
  797.       return;
  798.     }
  799.  
  800.     var currentOptions = PO.getCurrentOptions();
  801.     var content = PO._content;
  802.  
  803.     switch (PlacesSearchBox.filterCollection) {
  804.     case "collection":
  805.       content.applyFilter(filterString, this.folders);
  806.       // XXX changing the button text is badness
  807.       //var scopeBtn = document.getElementById("scopeBarFolder");
  808.       //scopeBtn.label = PlacesOrganizer._places.selectedNode.title;
  809.       break;
  810.     case "bookmarks":
  811.       content.applyFilter(filterString,
  812.                           [PlacesUtils.bookmarksMenuFolderId,
  813.                            PlacesUtils.toolbarFolderId,
  814.                            PlacesUtils.unfiledBookmarksFolderId]);
  815.       break;
  816.     case "history":
  817.       if (currentOptions.queryType != Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
  818.         var query = PlacesUtils.history.getNewQuery();
  819.         query.searchTerms = filterString;
  820.         var options = currentOptions.clone();
  821.         options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
  822.         content.load([query], options);
  823.       }
  824.       else
  825.         content.applyFilter(filterString);
  826.       break;
  827.     case "all":
  828.       content.applyFilter(filterString);
  829.       break;
  830.     }
  831.  
  832.     PlacesSearchBox.showSearchUI();
  833.     this.searchFilter.setAttribute("filtered", "true");
  834.  
  835.     // Update the details panel
  836.     PlacesOrganizer.onContentTreeSelect();
  837.   },
  838.  
  839.   /**
  840.    * Finds across all bookmarks
  841.    */
  842.   findAll: function PSB_findAll() {
  843.     this.filterCollection = "all";
  844.     this.focus();
  845.   },
  846.  
  847.   /**
  848.    * Finds in the currently selected Place.
  849.    */
  850.   findCurrent: function PSB_findCurrent() {
  851.     this.filterCollection = "collection";
  852.     this.focus();
  853.   },
  854.  
  855.   /**
  856.    * Updates the display with the title of the current collection.
  857.    * @param   title
  858.    *          The title of the current collection.
  859.    */
  860.   updateCollectionTitle: function PSB_updateCollectionTitle(title) {
  861.     if (title)
  862.       this.searchFilter.emptyText =
  863.         PlacesUIUtils.getFormattedString("searchCurrentDefault", [title]);
  864.     else
  865.       this.searchFilter.emptyText = this.filterCollection == "history" ?
  866.                                     PlacesUIUtils.getString("searchHistory") :
  867.                                     PlacesUIUtils.getString("searchBookmarks");
  868.   },
  869.  
  870.   /**
  871.    * Gets/sets the active collection from the dropdown menu.
  872.    */
  873.   get filterCollection() {
  874.     return this.searchFilter.getAttribute("collection");
  875.   },
  876.   set filterCollection(collectionName) {
  877.     this.searchFilter.setAttribute("collection", collectionName);
  878.     if (this.searchFilter.value)
  879.       return; // don't overwrite pre-existing search terms
  880.     var newGrayText = null;
  881.     if (collectionName == "collection")
  882.       newGrayText = PlacesOrganizer._places.selectedNode.title;
  883.     this.updateCollectionTitle(newGrayText);
  884.     return collectionName;
  885.   },
  886.  
  887.   /**
  888.    * Focus the search box
  889.    */
  890.   focus: function PSB_focus() {
  891.     this.searchFilter.focus();
  892.   },
  893.  
  894.   /**
  895.    * Set up the gray text in the search bar as the Places View loads.
  896.    */
  897.   init: function PSB_init() {
  898.     this.updateCollectionTitle();
  899.   },
  900.  
  901.   /**
  902.    * Gets or sets the text shown in the Places Search Box
  903.    */
  904.   get value() {
  905.     return this.searchFilter.value;
  906.   },
  907.   set value(value) {
  908.     return this.searchFilter.value = value;
  909.   },
  910.  
  911.   showSearchUI: function PSB_showSearchUI() {
  912.     // Hide the advanced search controls when the user hasn't searched
  913.     var searchModifiers = document.getElementById("searchModifiers");
  914.     searchModifiers.hidden = false;
  915.  
  916. //@line 945 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/components/places/content/places.js"
  917.   },
  918.  
  919.   hideSearchUI: function PSB_hideSearchUI() {
  920.     var searchModifiers = document.getElementById("searchModifiers");
  921.     searchModifiers.hidden = true;
  922.   }
  923. };
  924.  
  925. /**
  926.  * Functions and data for advanced query builder
  927.  */
  928. var PlacesQueryBuilder = {
  929.  
  930.   queries: [],
  931.   queryOptions: null,
  932.  
  933. //@line 1424 "/build/buildd/firefox-3.0-3.0.14+build2+nobinonly/build-tree/mozilla/browser/components/places/content/places.js"
  934.  
  935.   onScopeSelected: function PQB_onScopeSelected(aButton) {
  936.     var id = aButton.getAttribute("id");
  937.     // get scope bar
  938.     var bar = document.getElementById("organizerScopeBar");
  939.     var child = bar.firstChild;
  940.     while (child) {
  941.       if (child.getAttribute("id") != id)
  942.         child.removeAttribute("checked");
  943.       else
  944.         child.setAttribute("checked", "true");
  945.       child = child.nextSibling;
  946.     }
  947.  
  948.     // update collection type and get folders
  949.     var folders = [];
  950.     switch (id) {
  951.       case "scopeBarHistory":
  952.         PlacesSearchBox.filterCollection = "history";
  953.         folders = [];
  954.         break;
  955.       case "scopeBarFolder":
  956.         var selectedFolder = PlacesOrganizer._places.selectedNode.itemId;
  957.         // note "all bookmarks" isn't the concrete parent of the top-level
  958.         // bookmark folders
  959.         if (selectedFolder != PlacesUIUtils.allBookmarksFolderId) {
  960.           PlacesSearchBox.filterCollection = "collection";
  961.           folders.push(PlacesOrganizer._places.selectedNode.itemId);
  962.           break;
  963.         }
  964.       default: // all bookmarks
  965.         PlacesSearchBox.filterCollection = "bookmarks";
  966.         folders.push(PlacesUtils.bookmarksMenuFolderId,
  967.                      PlacesUtils.toolbarFolderId,
  968.                      PlacesUtils.unfiledBookmarksFolderId);
  969.     }
  970.  
  971.     // set scope, and re-search
  972.     PlacesSearchBox.folders = folders;
  973.     PlacesSearchBox.search(PlacesSearchBox.searchFilter.value);
  974.   }
  975. };
  976.  
  977. /**
  978.  * Population and commands for the View Menu.
  979.  */
  980. var ViewMenu = {
  981.   /**
  982.    * Removes content generated previously from a menupopup.
  983.    * @param   popup
  984.    *          The popup that contains the previously generated content.
  985.    * @param   startID
  986.    *          The id attribute of an element that is the start of the
  987.    *          dynamically generated region - remove elements after this
  988.    *          item only.
  989.    *          Must be contained by popup. Can be null (in which case the
  990.    *          contents of popup are removed).
  991.    * @param   endID
  992.    *          The id attribute of an element that is the end of the
  993.    *          dynamically generated region - remove elements up to this
  994.    *          item only.
  995.    *          Must be contained by popup. Can be null (in which case all
  996.    *          items until the end of the popup will be removed). Ignored
  997.    *          if startID is null.
  998.    * @returns The element for the caller to insert new items before,
  999.    *          null if the caller should just append to the popup.
  1000.    */
  1001.   _clean: function VM__clean(popup, startID, endID) {
  1002.     if (endID)
  1003.       NS_ASSERT(startID, "meaningless to have valid endID and null startID");
  1004.     if (startID) {
  1005.       var startElement = document.getElementById(startID);
  1006.       NS_ASSERT(startElement.parentNode ==
  1007.                 popup, "startElement is not in popup");
  1008.       NS_ASSERT(startElement,
  1009.                 "startID does not correspond to an existing element");
  1010.       var endElement = null;
  1011.       if (endID) {
  1012.         endElement = document.getElementById(endID);
  1013.         NS_ASSERT(endElement.parentNode == popup,
  1014.                   "endElement is not in popup");
  1015.         NS_ASSERT(endElement,
  1016.                   "endID does not correspond to an existing element");
  1017.       }
  1018.       while (startElement.nextSibling != endElement)
  1019.         popup.removeChild(startElement.nextSibling);
  1020.       return endElement;
  1021.     }
  1022.     else {
  1023.       while(popup.hasChildNodes())
  1024.         popup.removeChild(popup.firstChild);
  1025.     }
  1026.     return null;
  1027.   },
  1028.  
  1029.   /**
  1030.    * Fills a menupopup with a list of columns
  1031.    * @param   event
  1032.    *          The popupshowing event that invoked this function.
  1033.    * @param   startID
  1034.    *          see _clean
  1035.    * @param   endID
  1036.    *          see _clean
  1037.    * @param   type
  1038.    *          the type of the menuitem, e.g. "radio" or "checkbox".
  1039.    *          Can be null (no-type).
  1040.    *          Checkboxes are checked if the column is visible.
  1041.    * @param   propertyPrefix
  1042.    *          If propertyPrefix is non-null:
  1043.    *          propertyPrefix + column ID + ".label" will be used to get the
  1044.    *          localized label string.
  1045.    *          propertyPrefix + column ID + ".accesskey" will be used to get the
  1046.    *          localized accesskey.
  1047.    *          If propertyPrefix is null, the column label is used as label and
  1048.    *          no accesskey is assigned.
  1049.    */
  1050.   fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, propertyPrefix) {
  1051.     var popup = event.target;
  1052.     var pivot = this._clean(popup, startID, endID);
  1053.  
  1054.     // If no column is "sort-active", the "Unsorted" item needs to be checked,
  1055.     // so track whether or not we find a column that is sort-active.
  1056.     var isSorted = false;
  1057.     var content = document.getElementById("placeContent");
  1058.     var columns = content.columns;
  1059.     for (var i = 0; i < columns.count; ++i) {
  1060.       var column = columns.getColumnAt(i).element;
  1061.       var menuitem = document.createElement("menuitem");
  1062.       menuitem.id = "menucol_" + column.id;
  1063.       menuitem.column = column;
  1064.       var label = column.getAttribute("label");
  1065.       if (propertyPrefix) {
  1066.         var menuitemPrefix = propertyPrefix;
  1067.         // for string properties, use "name" as the id, instead of "title"
  1068.         // see bug #386287 for details
  1069.         var columnId = column.getAttribute("anonid");
  1070.         menuitemPrefix += columnId == "title" ? "name" : columnId;
  1071.         label = PlacesUIUtils.getString(menuitemPrefix + ".label");
  1072.         var accesskey = PlacesUIUtils.getString(menuitemPrefix + ".accesskey");
  1073.         menuitem.setAttribute("accesskey", accesskey);
  1074.       }
  1075.       menuitem.setAttribute("label", label);
  1076.       if (type == "radio") {
  1077.         menuitem.setAttribute("type", "radio");
  1078.         menuitem.setAttribute("name", "columns");
  1079.         // This column is the sort key. Its item is checked.
  1080.         if (column.getAttribute("sortDirection") != "") {
  1081.           menuitem.setAttribute("checked", "true");
  1082.           isSorted = true;
  1083.         }
  1084.       }
  1085.       else if (type == "checkbox") {
  1086.         menuitem.setAttribute("type", "checkbox");
  1087.         // Cannot uncheck the primary column.
  1088.         if (column.getAttribute("primary") == "true")
  1089.           menuitem.setAttribute("disabled", "true");
  1090.         // Items for visible columns are checked.
  1091.         if (!column.hidden)
  1092.           menuitem.setAttribute("checked", "true");
  1093.       }
  1094.       if (pivot)
  1095.         popup.insertBefore(menuitem, pivot);
  1096.       else
  1097.         popup.appendChild(menuitem);
  1098.     }
  1099.     event.stopPropagation();
  1100.   },
  1101.  
  1102.   /**
  1103.    * Set up the content of the view menu.
  1104.    */
  1105.   populateSortMenu: function VM_populateSortMenu(event) {
  1106.     this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.");
  1107.  
  1108.     var sortColumn = this._getSortColumn();
  1109.     var viewSortAscending = document.getElementById("viewSortAscending");
  1110.     var viewSortDescending = document.getElementById("viewSortDescending");
  1111.     // We need to remove an existing checked attribute because the unsorted
  1112.     // menu item is not rebuilt every time we open the menu like the others.
  1113.     var viewUnsorted = document.getElementById("viewUnsorted");
  1114.     if (!sortColumn) {
  1115.       viewSortAscending.removeAttribute("checked");
  1116.       viewSortDescending.removeAttribute("checked");
  1117.       viewUnsorted.setAttribute("checked", "true");
  1118.     }
  1119.     else if (sortColumn.getAttribute("sortDirection") == "ascending") {
  1120.       viewSortAscending.setAttribute("checked", "true");
  1121.       viewSortDescending.removeAttribute("checked");
  1122.       viewUnsorted.removeAttribute("checked");
  1123.     }
  1124.     else if (sortColumn.getAttribute("sortDirection") == "descending") {
  1125.       viewSortDescending.setAttribute("checked", "true");
  1126.       viewSortAscending.removeAttribute("checked");
  1127.       viewUnsorted.removeAttribute("checked");
  1128.     }
  1129.   },
  1130.  
  1131.   /**
  1132.    * Shows/Hides a tree column.
  1133.    * @param   element
  1134.    *          The menuitem element for the column
  1135.    */
  1136.   showHideColumn: function VM_showHideColumn(element) {
  1137.     var column = element.column;
  1138.  
  1139.     var splitter = column.nextSibling;
  1140.     if (splitter && splitter.localName != "splitter")
  1141.       splitter = null;
  1142.  
  1143.     if (element.getAttribute("checked") == "true") {
  1144.       column.setAttribute("hidden", "false");
  1145.       if (splitter)
  1146.         splitter.removeAttribute("hidden");
  1147.     }
  1148.     else {
  1149.       column.setAttribute("hidden", "true");
  1150.       if (splitter)
  1151.         splitter.setAttribute("hidden", "true");
  1152.     }
  1153.   },
  1154.  
  1155.   /**
  1156.    * Gets the last column that was sorted.
  1157.    * @returns  the currently sorted column, null if there is no sorted column.
  1158.    */
  1159.   _getSortColumn: function VM__getSortColumn() {
  1160.     var content = document.getElementById("placeContent");
  1161.     var cols = content.columns;
  1162.     for (var i = 0; i < cols.count; ++i) {
  1163.       var column = cols.getColumnAt(i).element;
  1164.       var sortDirection = column.getAttribute("sortDirection");
  1165.       if (sortDirection == "ascending" || sortDirection == "descending")
  1166.         return column;
  1167.     }
  1168.     return null;
  1169.   },
  1170.  
  1171.   /**
  1172.    * Sorts the view by the specified column.
  1173.    * @param   aColumn
  1174.    *          The colum that is the sort key. Can be null - the
  1175.    *          current sort column or the title column will be used.
  1176.    * @param   aDirection
  1177.    *          The direction to sort - "ascending" or "descending".
  1178.    *          Can be null - the last direction or descending will be used.
  1179.    *
  1180.    * If both aColumnID and aDirection are null, the view will be unsorted.
  1181.    */
  1182.   setSortColumn: function VM_setSortColumn(aColumn, aDirection) {
  1183.     var result = document.getElementById("placeContent").getResult();
  1184.     if (!aColumn && !aDirection) {
  1185.       result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
  1186.       return;
  1187.     }
  1188.  
  1189.     var columnId;
  1190.     if (aColumn) {
  1191.       columnId = aColumn.getAttribute("anonid")
  1192.       if (!aDirection) {
  1193.         var sortColumn = this._getSortColumn();
  1194.         aDirection = sortColumn ?
  1195.                      sortColumn.getAttribute("sortDirection") : "descending";
  1196.       }
  1197.     }
  1198.     else {
  1199.       var sortColumn = this._getSortColumn();
  1200.       columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title";
  1201.     }
  1202.  
  1203.     var sortingMode;
  1204.     var sortingAnnotation = "";
  1205.     const NHQO = Ci.nsINavHistoryQueryOptions;
  1206.     switch (columnId) {
  1207.       case "title":
  1208.         sortingMode = aDirection == "descending" ?
  1209.           NHQO.SORT_BY_TITLE_DESCENDING : NHQO.SORT_BY_TITLE_ASCENDING;
  1210.         break;
  1211.       case "url":
  1212.         sortingMode = aDirection == "descending" ?
  1213.           NHQO.SORT_BY_URI_DESCENDING : NHQO.SORT_BY_URI_ASCENDING;
  1214.         break;
  1215.       case "date":
  1216.         sortingMode = aDirection == "descending" ?
  1217.           NHQO.SORT_BY_DATE_DESCENDING : NHQO.SORT_BY_DATE_ASCENDING;
  1218.         break;
  1219.       case "visitCount":
  1220.         sortingMode = aDirection == "descending" ?
  1221.           NHQO.SORT_BY_VISITCOUNT_DESCENDING : NHQO.SORT_BY_VISITCOUNT_ASCENDING;
  1222.         break;
  1223.       case "keyword":
  1224.         sortingMode = aDirection == "descending" ?
  1225.           NHQO.SORT_BY_KEYWORD_DESCENDING : NHQO.SORT_BY_KEYWORD_ASCENDING;
  1226.         break;
  1227.       case "description":
  1228.         sortingAnnotation = DESCRIPTION_ANNO;
  1229.         sortingMode = aDirection == "descending" ?
  1230.           NHQO.SORT_BY_ANNOTATION_DESCENDING : NHQO.SORT_BY_ANNOTATION_ASCENDING;
  1231.         break;
  1232.       case "dateAdded":
  1233.         sortingMode = aDirection == "descending" ?
  1234.           NHQO.SORT_BY_DATEADDED_DESCENDING : NHQO.SORT_BY_DATEADDED_ASCENDING;
  1235.         break;
  1236.       case "lastModified":
  1237.         sortingMode = aDirection == "descending" ?
  1238.           NHQO.SORT_BY_LASTMODIFIED_DESCENDING : NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
  1239.         break;
  1240.       default:
  1241.         throw("Invalid Column");
  1242.     }
  1243.     result.sortingAnnotation = sortingAnnotation;
  1244.     result.sortingMode = sortingMode;
  1245.   }
  1246. };
  1247.