mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-11 13:48:23 +02:00
1509 lines
50 KiB
JavaScript
Executable file
1509 lines
50 KiB
JavaScript
Executable file
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is the Places Command Controller.
|
|
*
|
|
* The Initial Developer of the Original Code is Google Inc.
|
|
* Portions created by the Initial Developer are Copyright (C) 2005
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Ben Goodger <beng@google.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
function LOG(str) {
|
|
dump("*** " + str + "\n");
|
|
}
|
|
|
|
const Ci = Components.interfaces;
|
|
const Cc = Components.classes;
|
|
const Cr = Components.results;
|
|
|
|
const SELECTION_CONTAINS_URL = 0x01;
|
|
const SELECTION_CONTAINS_CONTAINER = 0x02;
|
|
const SELECTION_IS_OPEN_CONTAINER = 0x04;
|
|
const SELECTION_IS_CLOSED_CONTAINER = 0x08;
|
|
const SELECTION_IS_CHANGEABLE = 0x10;
|
|
const SELECTION_IS_REMOVABLE = 0x20;
|
|
const SELECTION_IS_MOVABLE = 0x40;
|
|
|
|
// Place entries that are containers, e.g. bookmark folders or queries.
|
|
const TYPE_X_MOZ_PLACE_CONTAINER = "text/x-moz-place-container";
|
|
// Place entries that are not containers
|
|
const TYPE_X_MOZ_PLACE = "text/x-moz-place";
|
|
// Place entries in shortcut url format (url\ntitle)
|
|
const TYPE_X_MOZ_URL = "text/x-moz-url";
|
|
// Place entries formatted as HTML anchors
|
|
const TYPE_HTML = "text/html";
|
|
// Place entries as raw URL text
|
|
const TYPE_UNICODE = "text/unicode";
|
|
|
|
// No change to the view, preserve current selection
|
|
const RELOAD_ACTION_NOTHING = 0;
|
|
// Inserting items new to the view, select the inserted rows
|
|
const RELOAD_ACTION_INSERT = 1;
|
|
// Removing items from the view, select the first item after the last selected
|
|
const RELOAD_ACTION_REMOVE = 2;
|
|
// Moving items within a view, don't treat the dropped items as additional
|
|
// rows.
|
|
const RELOAD_ACTION_MOVE = 3;
|
|
|
|
function STACK(args) {
|
|
var temp = arguments.callee.caller;
|
|
while (temp) {
|
|
LOG("NAME: " + temp.name);
|
|
temp = temp.arguments.callee.caller;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents an insertion point within a container where we can insert
|
|
* items.
|
|
* @param folderId
|
|
* The folderId of the parent container
|
|
* @param index
|
|
* The index within the container where we should insert
|
|
* @param orientation
|
|
* The orientation of the insertion. NOTE: the adjustments to the
|
|
* insertion point to accommodate the orientation should be done by
|
|
* the person who constructs the IP, not the user. The orientation
|
|
* is provided for informational purposes only!
|
|
* @constructor
|
|
*/
|
|
function InsertionPoint(folderId, index, orientation) {
|
|
this.folderId = folderId;
|
|
this.index = index;
|
|
this.orientation = orientation;
|
|
}
|
|
|
|
/**
|
|
* Initialization Configuration for a View
|
|
* @constructor
|
|
*/
|
|
function ViewConfig(dropTypes, dropOnTypes, filterOptions, firstDropIndex) {
|
|
this.dropTypes = dropTypes;
|
|
this.dropOnTypes = dropOnTypes;
|
|
this.filterOptions = filterOptions;
|
|
this.firstDropIndex = firstDropIndex;
|
|
}
|
|
ViewConfig.GENERIC_DROP_TYPES = [TYPE_X_MOZ_PLACE_CONTAINER, TYPE_X_MOZ_PLACE,
|
|
TYPE_X_MOZ_URL];
|
|
ViewConfig.GENERIC_FILTER_OPTIONS = Ci.nsINavHistoryQuery.INCLUDE_ITEMS +
|
|
Ci.nsINavHistoryQuery.INCLUDE_QUERIES;
|
|
|
|
/**
|
|
* Manages grouping options for a particular view type.
|
|
* @param pref
|
|
* The preference that stores these grouping options.
|
|
* @param defaultGroupings
|
|
* The default groupings to be used for views of this type.
|
|
* @param serializable
|
|
* An object bearing a serialize and deserialize method that
|
|
* read and write the object's string representation from/to
|
|
* preferences.
|
|
* @constructor
|
|
*/
|
|
function PrefHandler(pref, defaultValue, serializable) {
|
|
this._pref = pref;
|
|
this._defaultValue = defaultValue;
|
|
this._serializable = serializable;
|
|
|
|
this._pb =
|
|
Cc["@mozilla.org/preferences-service;1"].
|
|
getService(Components.interfaces.nsIPrefBranch2);
|
|
this._pb.addObserver(this._pref, this, false);
|
|
}
|
|
PrefHandler.prototype = {
|
|
/**
|
|
* Clean up when the window is going away to avoid leaks.
|
|
*/
|
|
destroy: function PC_PH_destroy() {
|
|
this._pb.removeObserver(this._pref, this);
|
|
},
|
|
|
|
/**
|
|
* Observes changes to the preferences.
|
|
* @param subject
|
|
* @param topic
|
|
* The preference changed notification
|
|
* @param data
|
|
* The preference that changed
|
|
*/
|
|
observe: function PC_PH_observe(subject, topic, data) {
|
|
if (topic == "nsPref:changed" && data == this._pref)
|
|
this._value = null;
|
|
},
|
|
|
|
/**
|
|
* The cached value, null if it needs to be rebuilt from preferences.
|
|
*/
|
|
_value: null,
|
|
|
|
/**
|
|
* Get the preference value, reading from preferences if necessary.
|
|
*/
|
|
get value() {
|
|
if (!this._value) {
|
|
if (this._pb.prefHasUserValue(this._pref)) {
|
|
var valueString = this._pb.getCharPref(this._pref);
|
|
this._value = this._serializable.deserialize(valueString);
|
|
}
|
|
else
|
|
this._value = this._defaultValue;
|
|
}
|
|
return this._value;
|
|
},
|
|
|
|
/**
|
|
* Stores a value in preferences.
|
|
* @param value
|
|
* The data to be stored.
|
|
*/
|
|
set value(value) {
|
|
if (value != this._value)
|
|
this._pb.setCharPref(this._pref, this._serializable.serialize(value));
|
|
return value;
|
|
},
|
|
};
|
|
|
|
|
|
/**
|
|
* The Master Places Controller
|
|
*/
|
|
var PlacesController = {
|
|
/**
|
|
* Makes a URI from a spec.
|
|
* @param spec
|
|
* The string spec of the URI
|
|
* @returns A URI object for the spec.
|
|
*/
|
|
_uri: function PC__uri(spec) {
|
|
var ios =
|
|
Cc["@mozilla.org/network/io-service;1"].
|
|
getService(Ci.nsIIOService);
|
|
return ios.newURI(spec, null, null);
|
|
},
|
|
|
|
/**
|
|
* The Bookmarks Service.
|
|
*/
|
|
__bms: null,
|
|
get _bms() {
|
|
if (!this.__bms) {
|
|
this.__bms =
|
|
Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
|
getService(Ci.nsINavBookmarksService);
|
|
}
|
|
return this.__bms;
|
|
},
|
|
|
|
/**
|
|
* The Nav History Service.
|
|
*/
|
|
__hist: null,
|
|
get _hist() {
|
|
if (!this.__hist) {
|
|
this.__hist =
|
|
Cc["@mozilla.org/browser/nav-history-service;1"].
|
|
getService(Ci.nsINavHistoryService);
|
|
}
|
|
return this.__hist;
|
|
},
|
|
|
|
/**
|
|
* Generates a HistoryResult for the contents of a folder.
|
|
* @param folderId
|
|
* The folder to open
|
|
* @param filterOptions
|
|
* Options regarding the type of items to be returned. See
|
|
* documentation in nsINavHistoryQuery for the |itemTypes| property.
|
|
* @returns A HistoryResult containing the contents of the folder.
|
|
*/
|
|
getFolderContents: function PC_getFolderContents(folderId, filterOptions) {
|
|
var query = this._hist.getNewQuery();
|
|
query.setFolders([folderId], 1);
|
|
query.itemTypes = filterOptions;
|
|
var options = this._hist.getNewQueryOptions();
|
|
options.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER], 1);
|
|
return this._hist.executeQuery(query, options);
|
|
},
|
|
|
|
/**
|
|
* Gets a place: URI for the given queries and options.
|
|
* @param queries
|
|
* An array of NavHistoryQueries
|
|
* @param options
|
|
* A NavHistoryQueryOptions object
|
|
* @returns A place: URI encoding the parameters.
|
|
*/
|
|
getPlaceURI: function PC_getPlaceURI(queries, options) {
|
|
var queryString = this._hist.queriesToQueryString(queries, queries.length,
|
|
options);
|
|
return this._uri(queryString);
|
|
},
|
|
|
|
/**
|
|
* The currently active Places view.
|
|
*/
|
|
_activeView: null,
|
|
get activeView() {
|
|
return this._activeView;
|
|
},
|
|
set activeView(activeView) {
|
|
this._activeView = activeView;
|
|
return this._activeView;
|
|
},
|
|
|
|
/**
|
|
* The current groupable Places view.
|
|
*/
|
|
_groupableView: null,
|
|
get groupableView() {
|
|
return this._groupableView;
|
|
},
|
|
set groupableView(groupableView) {
|
|
this._groupableView = groupableView;
|
|
return this._groupableView;
|
|
},
|
|
|
|
isCommandEnabled: function PC_isCommandEnabled(command) {
|
|
//LOG("isCommandEnabled: " + command);
|
|
return document.getElementById(command).getAttribute("disabled") == "true";
|
|
},
|
|
|
|
supportsCommand: function PC_supportsCommand(command) {
|
|
//LOG("supportsCommand: " + command);
|
|
return document.getElementById(command) != null;
|
|
},
|
|
|
|
doCommand: function PC_doCommand(command) {
|
|
LOG("doCommand: " + command);
|
|
},
|
|
|
|
onEvent: function PC_onEvent(eventName) {
|
|
LOG("onEvent: " + eventName);
|
|
},
|
|
|
|
/**
|
|
* Updates the enabled state of a command element.
|
|
* @param command
|
|
* The id of the command element to update
|
|
* @param enabled
|
|
* Whether or not the command element should be enabled.
|
|
*/
|
|
_setEnabled: function PC__setEnabled(command, enabled) {
|
|
var command = document.getElementById(command);
|
|
// Prevents excessive setAttributes
|
|
var disabled = command.hasAttribute("disabled");
|
|
if (enabled && disabled)
|
|
command.removeAttribute("disabled");
|
|
else if (!enabled && !disabled)
|
|
command.setAttribute("disabled", "true");
|
|
},
|
|
|
|
/**
|
|
* Determine whether or not the selection can be removed, either by the
|
|
* delete or cut operations based on whether or not any of its contents
|
|
* are non-removable. We don't need to worry about recursion here since it
|
|
* is a policy decision that a removable item not be placed inside a non-
|
|
* removable item.
|
|
* @returns true if the selection contains no nodes that cannot be removed,
|
|
* false otherwise.
|
|
*/
|
|
_hasRemovableSelection: function PC__hasRemovableSelection() {
|
|
var nodes = this._activeView.getSelectionNodes();
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
if (nodes[i].readonly)
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not the clipboard contains data that the active
|
|
* view can support in a paste operation.
|
|
* @returns true if the clipboard contains data compatible with the active
|
|
* view, false otherwise.
|
|
*/
|
|
_hasClipboardData: function PC__hasClipboardData() {
|
|
var types = this._activeView.supportedDropTypes;
|
|
var flavors =
|
|
Cc["@mozilla.org/supports-array;1"].
|
|
createInstance(Ci.nsISupportsArray);
|
|
for (var i = 0; i < types.length; ++i) {
|
|
var cstring =
|
|
Cc["@mozilla.org/supports-cstring;1"].
|
|
createInstance(Ci.nsISupportsCString);
|
|
cstring.data = types[i];
|
|
flavors.AppendElement(cstring);
|
|
}
|
|
|
|
var clipboard =
|
|
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
|
return clipboard.hasDataMatchingFlavors(flavors,
|
|
Ci.nsIClipboard.kGlobalClipboard);
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not the paste command is enabled, based on the
|
|
* content of the clipboard and the selection within the active view.
|
|
*/
|
|
_canPaste: function PC__canPaste() {
|
|
// XXXben: check selection to see if insertion point would suggest pasting
|
|
// into an immutable container.
|
|
return this._hasClipboardData();
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not a ResultNode is a Bookmark folder or not.
|
|
* @param node
|
|
* A NavHistoryResultNode
|
|
* @returns true if the node is a Bookmark folder, false otherwise
|
|
*/
|
|
nodeIsFolder: function PC_nodeIsFolder(node) {
|
|
return (node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
|
|
node.folderId > 0);
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not a ResultNode is a URL item or not
|
|
* @param node
|
|
* A NavHistoryResultNode
|
|
* @returns true if the node is a URL item, false otherwise
|
|
*/
|
|
nodeIsURL: function PC_nodeIsURL(node) {
|
|
const NHRN = Ci.nsINavHistoryResultNode;
|
|
return node.type == NHRN.RESULT_TYPE_URL ||
|
|
node.type == NHRN.RESULT_TYPE_VISIT;
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not a ResultNode is a Query item or not
|
|
* @param node
|
|
* A NavHistoryResultNode
|
|
* @returns true if the node is a Query item, false otherwise
|
|
*/
|
|
nodeIsQuery: function PC_nodeIsQuery(node) {
|
|
return node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not a ResultNode is a host folder or not
|
|
* @param node
|
|
* A NavHistoryResultNode
|
|
* @returns true if the node is a host item, false otherwise
|
|
*/
|
|
nodeIsHost: function PC_nodeIsHost(node) {
|
|
return node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_HOST;
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not a ResultNode is a container item or not
|
|
* @param node
|
|
* A NavHistoryResultNode
|
|
* @returns true if the node is a container item, false otherwise
|
|
*/
|
|
nodeIsContainer: function PC_nodeIsContainer(node) {
|
|
return node.folderType != "";
|
|
},
|
|
|
|
/**
|
|
* Updates commands on focus/selection change to reflect the enabled/
|
|
* disabledness of commands in relation to the state of the selection.
|
|
*/
|
|
onCommandUpdate: function PC_onCommandUpdate() {
|
|
if (!this._activeView) {
|
|
// Initial command update, no view yet.
|
|
return;
|
|
}
|
|
|
|
// Select All
|
|
this._setEnabled("placesCmd_select:all",
|
|
this._activeView.getAttribute("seltype") != "single");
|
|
// Show Info
|
|
var hasSingleSelection = this._activeView.hasSingleSelection;
|
|
this._setEnabled("placesCmd_show:info", hasSingleSelection);
|
|
// Cut
|
|
var removableSelection = this._hasRemovableSelection();
|
|
this._setEnabled("placesCmd_edit:cut", removableSelection);
|
|
this._setEnabled("placesCmd_edit:delete", removableSelection);
|
|
// Copy
|
|
this._setEnabled("placesCmd_edit:copy", this._activeView.hasSelection);
|
|
// Paste
|
|
this._setEnabled("placesCmd_edit:paste", this._canPaste());
|
|
// Open
|
|
var hasSelectedURL = this._activeView.selectedURLNode != null;
|
|
this._setEnabled("placesCmd_open", hasSelectedURL);
|
|
this._setEnabled("placesCmd_open:window", hasSelectedURL);
|
|
this._setEnabled("placesCmd_open:tab", hasSelectedURL);
|
|
|
|
// We can open multiple links in tabs if there is either:
|
|
// a) a single folder selected
|
|
// b) many links or folders selected
|
|
var singleFolderSelected = hasSingleSelection &&
|
|
this.nodeIsFolder(this._activeView.selectedNode);
|
|
this._setEnabled("placesCmd_open:tabs",
|
|
singleFolderSelected || !hasSingleSelection);
|
|
|
|
var viewIsFolder = this.nodeIsFolder(this._activeView.getResult());
|
|
// Persistent Sort
|
|
this._setEnabled("placesCmd_sortby:name", viewIsFolder);
|
|
// New Folder
|
|
this._setEnabled("placesCmd_new:folder", viewIsFolder);
|
|
// New Separator
|
|
// ...
|
|
this._setEnabled("placesCmd_new:separator", false);
|
|
// Feed Reload
|
|
this._setEnabled("placesCmd_reload", false);
|
|
},
|
|
|
|
/**
|
|
* Gather information about the selection according to the following
|
|
* rules:
|
|
* Selection Grammar:
|
|
* is-link "link"
|
|
* is-links "links"
|
|
* is-folder "folder"
|
|
* is-mutable "mutable"
|
|
* is-removable "removable"
|
|
* is-multiselect"multiselect"
|
|
* is-container "container"
|
|
* @returns an object with each of the properties above set if the selection
|
|
* matches that rule.
|
|
*/
|
|
_buildSelectionMetadata: function PC__buildSelectionMetadata() {
|
|
var metadata = { mixed: true };
|
|
|
|
var hasSingleSelection = this._activeView.hasSingleSelection;
|
|
if (this._activeView.selectedURLNode && hasSingleSelection)
|
|
metadata["link"] = true;
|
|
var selectedNode = this._activeView.selectedNode;
|
|
if (this.nodeIsFolder(selectedNode) && hasSingleSelection)
|
|
metadata["folder"] = true;
|
|
if (this.nodeIsContainer(selectedNode) && hasSingleSelection)
|
|
metadata["container"] = true;
|
|
|
|
var foundNonLeaf = false;
|
|
var nodes = this._activeView.getSelectionNodes();
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
var node = nodes[i];
|
|
if (node.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_URL)
|
|
foundNonLeaf = true;
|
|
if (!node.readonly && node.folderType == "")
|
|
metadata["mutable"] = true;
|
|
}
|
|
if (this._activeView.getAttribute("seltype") != "single")
|
|
metadata["multiselect"] = true;
|
|
if (!foundNonLeaf && nodes.length > 1)
|
|
metadata["links"] = true;
|
|
return metadata;
|
|
},
|
|
|
|
/**
|
|
* Determines if a menuitem should be shown or not by comparing the rules
|
|
* that govern the item's display with the state of the selection.
|
|
* @param metadata
|
|
* metadata about the selection.
|
|
* @param rules
|
|
* rules that govern the item's display
|
|
* @returns true if the conditions are satisfied and the item can be
|
|
* displayed, false otherwise.
|
|
*/
|
|
_shouldShowMenuItem: function(metadata, rules) {
|
|
for (var i = 0; i < rules.length; ++i) {
|
|
if (rules[i] in metadata)
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Build a context menu for the selection, ensuring that the content of the
|
|
* selection is correct and enabling/disabling items according to the state
|
|
* of the commands.
|
|
* @param popup
|
|
* The menupopup to build children into.
|
|
*/
|
|
buildContextMenu: function PC_buildContextMenu(popup) {
|
|
if (document.popupNode.hasAttribute("view")) {
|
|
var view = document.popupNode.getAttribute("view");
|
|
this.activeView = document.getElementById(view);
|
|
}
|
|
|
|
// Determine availability/enabled state of commands
|
|
var metadata = this._buildSelectionMetadata();
|
|
var lastVisible = null;
|
|
for (var i = 0; i < popup.childNodes.length; ++i) {
|
|
var item = popup.childNodes[i];
|
|
var rules = item.getAttribute("selection")
|
|
item.hidden = !this._shouldShowMenuItem(metadata, rules.split("|"));
|
|
if (!item.hidden)
|
|
lastVisible = item;
|
|
if (item.hasAttribute("command")) {
|
|
var disabled = !this.isCommandEnabled(item.getAttribute("command"));
|
|
item.setAttribute("disabled", disabled);
|
|
}
|
|
}
|
|
if (lastVisible.localName == "menuseparator")
|
|
lastVisible.hidden = true;
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Select all links in the current view.
|
|
*/
|
|
selectAll: function() {
|
|
this._activeView.selectAll();
|
|
},
|
|
|
|
/**
|
|
* Given a Mouse event, determine which function should be used to load
|
|
* the selected link. Modifiers may override the default settings and cause
|
|
* a link to be opened in a new tab or window.
|
|
* @param event
|
|
* The DOM Mouse Event triggering a URL load
|
|
* @returns A function which should be used to load the selected URL.
|
|
*/
|
|
_getLoadFunctionForEvent: function PP__getLoadFunctionForEvent(event) {
|
|
if (event.button != 0)
|
|
return null;
|
|
|
|
if (event.ctrlKey)
|
|
return this.openLinkInNewTab;
|
|
else if (event.shiftKey)
|
|
return this.openLinkInNewWindow;
|
|
return this.openLinkInCurrentWindow;
|
|
},
|
|
|
|
/**
|
|
* Loads a URL in the appropriate tab or window, given the user's preference
|
|
* specified by modifier keys tracked by a DOM event
|
|
* @param event
|
|
* The DOM Mouse event with modifier keys set that track the user's
|
|
* preferred destination window or tab.
|
|
*/
|
|
mouseLoadURI: function PC_mouseLoadURI(event) {
|
|
var fn = this._getLoadFunctionForEvent(event);
|
|
if (fn)
|
|
this._getLoadFunctionForEvent(event)();
|
|
},
|
|
|
|
/**
|
|
* Loads the selected URL in a new tab.
|
|
*/
|
|
openLinkInNewTab: function PC_openLinkInNewTab() {
|
|
var node = this._activeView.selectedURLNode;
|
|
if (node)
|
|
this._activeView.browserWindow.openNewTabWith(node.url, null, null);
|
|
},
|
|
|
|
/**
|
|
* Loads the selected URL in a new window.
|
|
*/
|
|
openLinkInNewWindow: function PC_openLinkInNewWindow() {
|
|
var node = this._activeView.selectedURLNode;
|
|
if (node)
|
|
this._activeView.browserWindow.openNewWindowWith(node.url, null, null);
|
|
},
|
|
|
|
/**
|
|
* Loads the selected URL in the current window, replacing the Places page.
|
|
*/
|
|
openLinkInCurrentWindow: function PC_openLinkInCurrentWindow() {
|
|
var node = this._activeView.selectedURLNode;
|
|
if (node)
|
|
this._activeView.browserWindow.loadURI(node.url, null, null);
|
|
},
|
|
|
|
/**
|
|
* Opens the links in the selected folder, or the selected links in new tabs.
|
|
*/
|
|
openLinksInTabs: function PC_openLinksInTabs() {
|
|
var node = this._activeView.selectedNode;
|
|
if (this._activeView.hasSingleSelection && this.nodeIsFolder(node)) {
|
|
var queries = node.getQueries({});
|
|
var kids = this._hist.executeQueries(queries, queries.length,
|
|
node.queryOptions);
|
|
var cc = kids.childCount;
|
|
for (var i = 0; i < cc; ++i) {
|
|
var node = kids.getChild(i);
|
|
if (this.nodeIsURL(node))
|
|
this._activeView.browserWindow.openNewTabWith(node.url,
|
|
null, null);
|
|
}
|
|
}
|
|
else {
|
|
var nodes = this._activeView.getSelectionNodes();
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
if (this.nodeIsURL(nodes[i]))
|
|
this._activeView.browserWindow.openNewTabWith(nodes[i].url, null, null);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A hash of groupers that supply grouping options for queries of a given
|
|
* type. This is an override of grouping options that might be encoded in
|
|
* a saved place: URI
|
|
*/
|
|
groupers: { },
|
|
|
|
/**
|
|
* Rebuilds the view using a new set of grouping options.
|
|
* @param groupings
|
|
* An array of grouping options, see nsINavHistoryQueryOptions
|
|
* for details.
|
|
*/
|
|
setGroupingMode: function PC_setGroupingOptions(groupings) {
|
|
if (!this._groupableView)
|
|
return;
|
|
var result = this._groupableView.getResult();
|
|
var queries = result.getQueries({ });
|
|
var newOptions = result.queryOptions.clone();
|
|
|
|
// Update the grouping mode only after persisting, so that the URI is not
|
|
// changed.
|
|
newOptions.setGroupingMode(groupings, groupings.length);
|
|
|
|
// Persist this selection
|
|
if (this._groupableView.isBookmarks && "bookmark" in this.groupers)
|
|
this.groupers.bookmark.value = groupings;
|
|
else if ("generic" in this.groupers)
|
|
this.groupers.generic.value = groupings;
|
|
|
|
// Reload the view
|
|
this._groupableView.load(queries, newOptions);
|
|
},
|
|
|
|
/**
|
|
* Group the current content view by domain
|
|
*/
|
|
groupBySite: function PC_groupBySite() {
|
|
this.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_DOMAIN]);
|
|
},
|
|
|
|
/**
|
|
* Group the current content view by folder
|
|
*/
|
|
groupByFolder: function PC_groupByFolder() {
|
|
this.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER]);
|
|
},
|
|
|
|
/**
|
|
* Ungroup the current content view (i.e. show individual pages)
|
|
*/
|
|
groupByPage: function PC_groupByPage() {
|
|
this.setGroupingMode([]);
|
|
},
|
|
|
|
/**
|
|
* Create a new Bookmark folder somewhere. Prompts the user for the name
|
|
* of the folder.
|
|
*/
|
|
newFolder: function PC_newFolder() {
|
|
var view = this._activeView;
|
|
|
|
var ps =
|
|
Cc["@mozilla.org/embedcomp/prompt-service;1"].
|
|
getService(Ci.nsIPromptService);
|
|
var bundle = document.getElementById("placeBundle");
|
|
var title = bundle.getString("newFolderTitle");
|
|
var text = bundle.getString("newFolderMessage");
|
|
var value = { value: bundle.getString("newFolderDefault") };
|
|
if (ps.prompt(window, title, text, value, null, { })) {
|
|
var ip = view.insertionPoint;
|
|
var txn = new PlacesCreateFolderTransaction(value.value, ip.folderId,
|
|
ip.index);
|
|
this._hist.transactionManager.doTransaction(txn);
|
|
this._activeView.focus();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Removes the selection
|
|
* @param txnName
|
|
* An optional name for the transaction if this is being performed
|
|
* as part of another operation.
|
|
*/
|
|
remove: function PC_remove(txnName) {
|
|
var nodes = this._activeView.getSelectionNodes();
|
|
this._activeView.saveSelection();
|
|
if (this._activeView.isBookmarks) {
|
|
// delete bookmarks
|
|
var txns = [];
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
var node = nodes[i];
|
|
var index = this.getIndexOfNode(node);
|
|
if (this.nodeIsFolder(node)) {
|
|
txns.push(new PlacesRemoveFolderTransaction(node.folderId,
|
|
node.parent.folderId,
|
|
index));
|
|
}
|
|
else {
|
|
txns.push(new PlacesRemoveItemTransaction(this._uri(node.url),
|
|
node.parent.folderId,
|
|
index));
|
|
}
|
|
}
|
|
var txn = new PlacesAggregateTransaction(txnName || "RemoveItems", txns);
|
|
this._hist.transactionManager.doTransaction(txn);
|
|
} else {
|
|
// delete history items: these are unfortunately not undoable.
|
|
var hist = Cc["@mozilla.org/browser/nav-history-service;1"].
|
|
getService(Ci.nsIBrowserHistory);
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
var node = nodes[i];
|
|
if (this.nodeIsHost(node)) {
|
|
hist.removePagesFromHost(node.title, true);
|
|
} else {
|
|
hist.removePage(this._uri(node.url));
|
|
}
|
|
}
|
|
}
|
|
this._activeView.restoreSelection();
|
|
},
|
|
|
|
/**
|
|
* Gets the index of a node within its parent container
|
|
* @param node
|
|
* The node to look up
|
|
* @returns The index of the node within its parent container, or -1 if the
|
|
* node was not found or the node specified has no parent.
|
|
*/
|
|
getIndexOfNode: function PC_getIndexOfNode(node) {
|
|
var parent = node.parent;
|
|
if (!parent)
|
|
return -1;
|
|
var cc = parent.childCount;
|
|
for (var i = 0; i < cc && parent.getChild(i) != node; ++i);
|
|
return i < cc ? i : -1;
|
|
},
|
|
|
|
/**
|
|
* String-wraps a NavHistoryResultNode according to the rules of the specified
|
|
* content type.
|
|
* @param node
|
|
* The Result node to wrap (serialize)
|
|
* @param type
|
|
* The content type to serialize as
|
|
* @returns A string serialization of the node
|
|
*/
|
|
wrapNode: function PC_wrapNode(node, type) {
|
|
switch (type) {
|
|
case TYPE_X_MOZ_PLACE_CONTAINER:
|
|
case TYPE_X_MOZ_PLACE:
|
|
return node.folderId + "\n" + node.url + "\n" + node.parent.folderId + "\n" + this.getIndexOfNode(node);
|
|
case TYPE_X_MOZ_URL:
|
|
return node.url + "\n" + node.title;
|
|
case TYPE_HTML:
|
|
return "<A HREF=\"" + node.url + "\">" + node.title + "</A>";
|
|
}
|
|
// case TYPE_UNICODE:
|
|
return node.url;
|
|
},
|
|
|
|
/**
|
|
* Unwraps data from the Clipboard or the current Drag Session.
|
|
* @param blob
|
|
* A blob (string) of data, in some format we potentially know how
|
|
* to parse.
|
|
* @param type
|
|
* The content type of the blob.
|
|
* @returns An array of objects representing each item contained by the source.
|
|
*/
|
|
unwrapNodes: function PC_unwrapNodes(blob, type) {
|
|
var parts = blob.split("\n");
|
|
var nodes = [];
|
|
for (var i = 0; i < parts.length; ++i) {
|
|
var data = { };
|
|
switch (type) {
|
|
case TYPE_X_MOZ_PLACE_CONTAINER:
|
|
case TYPE_X_MOZ_PLACE:
|
|
nodes.push({ folderId: parseInt(parts[i++]),
|
|
uri: parts[i] ? this._uri(parts[i++]) : null,
|
|
parent: parseInt(parts[i++]),
|
|
index: parseInt(parts[i]) });
|
|
break;
|
|
case TYPE_X_MOZ_URL:
|
|
nodes.push({ uri: this._uri(parts[i++]),
|
|
title: parts[i] });
|
|
break;
|
|
case TYPE_UNICODE:
|
|
nodes.push({ uri: this._uri(parts[i]) });
|
|
break;
|
|
default:
|
|
LOG("Cannot unwrap data of type " + type);
|
|
throw Cr.NS_ERROR_INVALID_ARG;
|
|
}
|
|
}
|
|
return nodes;
|
|
},
|
|
|
|
/**
|
|
* Get a transaction for copying a leaf item from one container to another.
|
|
* @param uri
|
|
* The URI of the item being copied
|
|
* @param container
|
|
* The container being copied into
|
|
* @param index
|
|
* The index within the container the item is copied to
|
|
* @returns A nsITransaction object that performs the copy.
|
|
*/
|
|
_getItemCopyTransaction: function (uri, container, index) {
|
|
var itemTitle = this._bms.getItemTitle(uri);
|
|
var createTxn = new PlacesCreateItemTransaction(uri, container, index);
|
|
var editTxn = new PlacesEditItemTransaction(uri, { title: itemTitle });
|
|
return new PlacesAggregateTransaction("ItemCopy", [createTxn, editTxn]);
|
|
},
|
|
|
|
/**
|
|
* Gets a transaction for copying (recursively nesting to include children)
|
|
* a folder and its contents from one folder to another.
|
|
* @param data
|
|
* Unwrapped dropped folder data
|
|
* @param container
|
|
* The container we are copying into
|
|
* @param index
|
|
* The index in the destination container to insert the new items
|
|
* @returns A nsITransaction object that will perform the copy.
|
|
*/
|
|
_getFolderCopyTransaction:
|
|
function PC__getFolderCopyTransaction(data, container, index) {
|
|
var transactions = [];
|
|
var self = this;
|
|
function createTransactions(folderId, container, index) {
|
|
var folderTitle = self._bms.getFolderTitle(folderId);
|
|
|
|
var createTxn =
|
|
new PlacesCreateFolderTransaction(folderTitle, container, index);
|
|
transactions.push(createTxn);
|
|
|
|
// Get the folder's children
|
|
var kids = self.getFolderContents(folderId,
|
|
ViewConfig.GENERIC_FILTER_OPTIONS);
|
|
var cc = kids.childCount;
|
|
for (var i = 0; i < cc; ++i) {
|
|
var node = kids.getChild(i);
|
|
if (self.nodeIsFolder(node))
|
|
createTransactions(node.folderId, folderId, i);
|
|
else {
|
|
var uri = self._uri(node.url);
|
|
transactions.push(self._getItemCopyTransaction(uri, container,
|
|
index));
|
|
}
|
|
}
|
|
}
|
|
createTransactions(data.folderId, container, index);
|
|
return new PlacesAggregateTransaction("FolderCopy", transactions);
|
|
},
|
|
|
|
/**
|
|
* Constructs a Transaction for the drop or paste of a blob of data into
|
|
* a container.
|
|
* @param data
|
|
* The unwrapped data blob of dropped or pasted data.
|
|
* @param type
|
|
* The content type of the data
|
|
* @param container
|
|
* The container the data was dropped or pasted into
|
|
* @param index
|
|
* The index within the container the item was dropped or pasted at
|
|
* @param copy
|
|
* The drag action was copy, so don't move folders or links.
|
|
* @returns An object implementing nsITransaction that can perform
|
|
* the move/insert.
|
|
*/
|
|
makeTransaction: function PC_makeTransaction(data, type, container,
|
|
index, copy) {
|
|
switch (type) {
|
|
case TYPE_X_MOZ_PLACE_CONTAINER:
|
|
case TYPE_X_MOZ_PLACE:
|
|
if (data.folderId > 0) {
|
|
// Place is a folder.
|
|
if (copy)
|
|
return this._getFolderCopyTransaction(data, container, index);
|
|
return new PlacesMoveFolderTransaction(data.folderId, data.parent,
|
|
data.index, container,
|
|
index);
|
|
}
|
|
if (copy)
|
|
return this._getItemCopyTransaction(data.uri, container, index);
|
|
return new PlacesMoveItemTransaction(data.uri, data.parent,
|
|
data.index, container,
|
|
index);
|
|
case TYPE_X_MOZ_URL:
|
|
// Creating and Setting the title is a two step process, so create
|
|
// a transaction for each then aggregate them.
|
|
var createTxn =
|
|
new PlacesCreateItemTransaction(data.uri, container, index);
|
|
var editTxn =
|
|
new PlacesEditItemTransaction(data.uri, { title: data.title });
|
|
return new PlacesAggregateTransaction("DropMozURLItem", [createTxn, editTxn]);
|
|
case TYPE_UNICODE:
|
|
// Creating and Setting the title is a two step process, so create
|
|
// a transaction for each then aggregate them.
|
|
var createTxn =
|
|
new PlacesCreateItemTransaction(data.uri, container, index);
|
|
var editTxn =
|
|
new PlacesEditItemTransaction(data.uri, { title: data.uri });
|
|
return new PlacesAggregateTransaction("DropItem", [createTxn, editTxn]);
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Wraps a string in a nsISupportsString wrapper
|
|
* @param str
|
|
* The string to wrap
|
|
* @returns A nsISupportsString object containing a string.
|
|
*/
|
|
_wrapString: function PC__wrapString(str) {
|
|
var s =
|
|
Cc["@mozilla.org/supports-string;1"].
|
|
createInstance(Ci.nsISupportsString);
|
|
s.data = str;
|
|
return s;
|
|
},
|
|
|
|
/**
|
|
* Get a TransferDataSet containing the content of the selection that can be
|
|
* dropped elsewhere.
|
|
* @returns A TransferDataSet object that can be dragged and dropped
|
|
* elsewhere.
|
|
*/
|
|
getTransferData: function PC_getTransferData() {
|
|
var nodes = this._activeView.getCopyableSelection();
|
|
var dataSet = new TransferDataSet();
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
var node = nodes[i];
|
|
|
|
var data = new TransferData();
|
|
var self = this;
|
|
function addData(type) {
|
|
data.addDataForFlavour(type, self._wrapString(self.wrapNode(node, type)));
|
|
}
|
|
if (this.nodeIsFolder(node) || this.nodeIsQuery(node))
|
|
addData(TYPE_X_MOZ_PLACE_CONTAINER);
|
|
else {
|
|
// This order is _important_! It controls how this and other
|
|
// applications select data to be inserted based on type.
|
|
addData(TYPE_X_MOZ_PLACE);
|
|
addData(TYPE_UNICODE);
|
|
addData(TYPE_HTML);
|
|
addData(TYPE_X_MOZ_URL);
|
|
}
|
|
dataSet.push(data);
|
|
}
|
|
return dataSet;
|
|
},
|
|
|
|
/**
|
|
* Copy Bookmarks and Folders to the clipboard
|
|
*/
|
|
copy: function() {
|
|
var nodes = this._activeView.getCopyableSelection();
|
|
|
|
var xferable =
|
|
Cc["@mozilla.org/widget/transferable;1"].
|
|
createInstance(Ci.nsITransferable);
|
|
var foundFolder = false, foundLink = false;
|
|
var pcString = placeString = mozURLString = htmlString = unicodeString = "";
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
var node = nodes[i];
|
|
var self = this;
|
|
function generateChunk(type) {
|
|
var suffix = i < (nodes.length - 1) ? "\n" : "";
|
|
return self.wrapNode(node, type) + suffix;
|
|
}
|
|
if (this.nodeIsFolder(node) || this.nodeIsQuery(node))
|
|
pcString += generateChunk(TYPE_X_MOZ_PLACE_CONTAINER);
|
|
else {
|
|
placeString += generateChunk(TYPE_X_MOZ_PLACE);
|
|
mozURLString += generateChunk(TYPE_X_MOZ_URL);
|
|
htmlString += generateChunk(TYPE_HTML);
|
|
unicodeString += generateChunk(TYPE_UNICODE);
|
|
}
|
|
}
|
|
|
|
var self = this;
|
|
function addData(type, data) {
|
|
xferable.addDataFlavor(type);
|
|
xferable.setTransferData(type, self._wrapString(data), data.length * 2);
|
|
}
|
|
// This order is _important_! It controls how this and other applications
|
|
// select data to be inserted based on type.
|
|
if (pcString)
|
|
addData(TYPE_X_MOZ_PLACE_CONTAINER, pcString);
|
|
if (placeString)
|
|
addData(TYPE_X_MOZ_PLACE, placeString);
|
|
if (unicodeString)
|
|
addData(TYPE_UNICODE, unicodeString);
|
|
if (htmlString)
|
|
addData(TYPE_HTML, htmlString);
|
|
if (mozURLString)
|
|
addData(TYPE_X_MOZ_URL, mozURLString);
|
|
|
|
if (pcString || placeString || unicodeString || htmlString ||
|
|
mozURLString) {
|
|
var clipboard =
|
|
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
|
clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Cut Bookmarks and Folders to the clipboard
|
|
*/
|
|
cut: function() {
|
|
this.copy();
|
|
this.remove("Cut");
|
|
},
|
|
|
|
/**
|
|
* Paste Bookmarks and Folders from the clipboard
|
|
*/
|
|
paste: function() {
|
|
var xferable =
|
|
Cc["@mozilla.org/widget/transferable;1"].
|
|
createInstance(Ci.nsITransferable);
|
|
xferable.addDataFlavor(TYPE_X_MOZ_PLACE_CONTAINER);
|
|
xferable.addDataFlavor(TYPE_X_MOZ_PLACE);
|
|
xferable.addDataFlavor(TYPE_X_MOZ_URL);
|
|
xferable.addDataFlavor(TYPE_UNICODE);
|
|
|
|
var clipboard =
|
|
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
|
clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
|
|
|
|
var data = { }, type = { };
|
|
xferable.getAnyTransferData(type, data, { });
|
|
data = data.value.QueryInterface(Ci.nsISupportsString).data;
|
|
data = this.unwrapNodes(data, type.value);
|
|
|
|
var ip = this._activeView.insertionPoint;
|
|
var transactions = [];
|
|
for (var i = 0; i < data.length; ++i)
|
|
transactions.push(this.makeTransaction(data[i], type.value,
|
|
ip.folderId, ip.index, true));
|
|
|
|
var txn = new PlacesAggregateTransaction("Paste", transactions);
|
|
this._hist.transactionManager.doTransaction(txn);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Handles drag and drop operations for views. Note that this is view agnostic!
|
|
* You should not use PlacesController.activeView within these methods, since
|
|
* the view that the item(s) have been dropped on was not necessarily active.
|
|
* Drop functions are passed the view that is being dropped on.
|
|
*/
|
|
var PlacesControllerDragHelper = {
|
|
/**
|
|
* @returns The current active drag session. Returns null if there is none.
|
|
*/
|
|
_getSession: function VO__getSession() {
|
|
var dragService =
|
|
Cc["@mozilla.org/widget/dragservice;1"].
|
|
getService(Ci.nsIDragService);
|
|
return dragService.getCurrentSession();
|
|
},
|
|
|
|
/**
|
|
* Determines whether or not the data currently being dragged can be dropped
|
|
* on the specified view.
|
|
* @param view
|
|
* An object implementing the AVI
|
|
* @param orientation
|
|
* The orientation of the drop
|
|
* @returns true if the data being dragged is of a type supported by the view
|
|
* it is being dragged over, false otherwise.
|
|
*/
|
|
canDrop: function PCDH_canDrop(view, orientation) {
|
|
var result = view.getResult();
|
|
if (result.readOnly || !PlacesController.nodeIsFolder(result))
|
|
return false;
|
|
|
|
var session = this._getSession();
|
|
if (session) {
|
|
if (orientation != Ci.nsINavHistoryResultViewObserver.DROP_ON)
|
|
var types = view.supportedDropTypes;
|
|
else
|
|
types = view.supportedDropOnTypes;
|
|
for (var i = 0; i < types.length; ++i) {
|
|
if (session.isDataFlavorSupported(types[i]))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Creates a Transeferable object that can be filled with data of types
|
|
* supported by a view.
|
|
* @param view
|
|
* An object implementing the AVI that supplies a list of
|
|
* supported droppable content types
|
|
* @param orientation
|
|
* The orientation of the drop
|
|
* @returns An object implementing nsITransferable that can receive data
|
|
* dropped onto a view.
|
|
*/
|
|
_initTransferable: function PCDH__initTransferable(view, orientation) {
|
|
var xferable =
|
|
Cc["@mozilla.org/widget/transferable;1"].
|
|
createInstance(Ci.nsITransferable);
|
|
if (orientation != Ci.nsINavHistoryResultViewObserver.DROP_ON)
|
|
var types = view.supportedDropTypes;
|
|
else
|
|
types = view.supportedDropOnTypes;
|
|
for (var j = 0; j < types.length; ++j)
|
|
xferable.addDataFlavor(types[j]);
|
|
return xferable;
|
|
},
|
|
|
|
/**
|
|
* Handles the drop of one or more items onto a view.
|
|
* @param sourceView
|
|
* The AVI-implementing object that started the drop.
|
|
* @param targetView
|
|
* The AVI-implementing object that received the drop.
|
|
* @param insertionPoint
|
|
* The insertion point where the items should be dropped
|
|
* @param visibleInsertCount
|
|
* The number of visible items to be inserted. This can be zero
|
|
* even when items are dropped because this is how many items will
|
|
* be _visible_ in the resulting tree.
|
|
*/
|
|
onDrop: function PCDH_onDrop(sourceView, targetView, insertionPoint,
|
|
visibleInsertCount) {
|
|
var session = this._getSession();
|
|
var copy = session.dragAction & Ci.nsIDragService.DRAGDROP_ACTION_COPY;
|
|
var transactions = [];
|
|
var xferable = this._initTransferable(targetView,
|
|
insertionPoint.orientation);
|
|
var dropCount = session.numDropItems;
|
|
for (var i = dropCount - 1; i >= 0; --i) {
|
|
session.getData(xferable, i);
|
|
|
|
var data = { }, flavor = { };
|
|
xferable.getAnyTransferData(flavor, data, { });
|
|
data.value.QueryInterface(Ci.nsISupportsString);
|
|
|
|
// There's only ever one in the D&D case.
|
|
var unwrapped = PlacesController.unwrapNodes(data.value.data,
|
|
flavor.value)[0];
|
|
transactions.push(PlacesController.makeTransaction(unwrapped,
|
|
flavor.value, insertionPoint.folderId,
|
|
insertionPoint.index, copy));
|
|
}
|
|
|
|
var txn = new PlacesAggregateTransaction("DropItems", transactions);
|
|
PlacesController._hist.transactionManager.doTransaction(txn);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Method and utility stubs for Place Edit Transactions
|
|
*/
|
|
function PlacesBaseTransaction() {
|
|
}
|
|
PlacesBaseTransaction.prototype = {
|
|
_bms: Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
|
getService(Ci.nsINavBookmarksService),
|
|
|
|
redoTransaction: function PIT_redoTransaction() {
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
},
|
|
|
|
get isTransient() {
|
|
return false;
|
|
},
|
|
|
|
merge: function PIT_merge(transaction) {
|
|
return false;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Performs several Places Transactions in a single batch.
|
|
*/
|
|
function PlacesAggregateTransaction(name, transactions) {
|
|
this._transactions = transactions;
|
|
this._name = name;
|
|
}
|
|
PlacesAggregateTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function() {
|
|
LOG("== " + this._name + " (Aggregate) ==============");
|
|
this._bms.beginUpdateBatch();
|
|
for (var i = 0; i < this._transactions.length; ++i)
|
|
this._transactions[i].doTransaction();
|
|
this._bms.endUpdateBatch();
|
|
LOG("== " + this._name + " (Aggregate Ends) =========");
|
|
},
|
|
|
|
undoTransaction: function() {
|
|
LOG("== UN" + this._name + " (UNAggregate) ============");
|
|
this._bms.beginUpdateBatch();
|
|
for (var i = 0; i < this._transactions.length; ++i)
|
|
this._transactions[i].undoTransaction();
|
|
this._bms.endUpdateBatch();
|
|
LOG("== UN" + this._name + " (UNAggregate Ends) =======");
|
|
},
|
|
};
|
|
|
|
|
|
/**
|
|
* Create a new Folder
|
|
*/
|
|
function PlacesCreateFolderTransaction(name, container, index) {
|
|
this._name = name;
|
|
this._container = container;
|
|
this._index = index;
|
|
this._id = null;
|
|
}
|
|
PlacesCreateFolderTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PCFT_doTransaction() {
|
|
LOG("Create Folder: " + this._name + " in: " + this._container + "," + this._index);
|
|
this._id = this._bms.createFolder(this._container, this._name, this._index);
|
|
},
|
|
|
|
undoTransaction: function PCFT_undoTransaction() {
|
|
LOG("UNCreate Folder: " + this._name + " from: " + this._container + "," + this._index);
|
|
this._bms.removeFolder(this._id);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Create a new Item
|
|
*/
|
|
function PlacesCreateItemTransaction(uri, container, index) {
|
|
this._uri = uri;
|
|
this._container = container;
|
|
this._index = index;
|
|
}
|
|
PlacesCreateItemTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PCIT_doTransaction() {
|
|
LOG("Create Item: " + this._uri.spec + " in: " + this._container + "," + this._index);
|
|
this._bms.insertItem(this._container, this._uri, this._index);
|
|
},
|
|
|
|
undoTransaction: function PCIT_undoTransaction() {
|
|
LOG("UNCreate Item: " + this._uri.spec + " from: " + this._container + "," + this._index);
|
|
this._bms.removeItem(this._container, this._uri);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Move a Folder
|
|
*/
|
|
function PlacesMoveFolderTransaction(id, oldContainer, oldIndex, newContainer, newIndex) {
|
|
this._id = id;
|
|
this._oldContainer = oldContainer;
|
|
this._oldIndex = oldIndex;
|
|
this._newContainer = newContainer;
|
|
this._newIndex = newIndex;
|
|
}
|
|
PlacesMoveFolderTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PMFT_doTransaction() {
|
|
LOG("Move Folder: " + this._id + " from: " + this._oldContainer + "," + this._oldIndex + " to: " + this._newContainer + "," + this._newIndex);
|
|
this._bms.moveFolder(this._id, this._newContainer, this._newIndex);
|
|
},
|
|
|
|
undoTransaction: function PMFT_undoTransaction() {
|
|
LOG("UNMove Folder: " + this._id + " from: " + this._oldContainer + "," + this._oldIndex + " to: " + this._newContainer + "," + this._newIndex);
|
|
this._bms.moveFolder(this._id, this._oldContainer, this._oldIndex);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Move an Item
|
|
*/
|
|
function PlacesMoveItemTransaction(uri, oldContainer, oldIndex, newContainer, newIndex) {
|
|
this._uri = uri;
|
|
this._oldContainer = oldContainer;
|
|
this._oldIndex = oldIndex;
|
|
this._newContainer = newContainer;
|
|
this._newIndex = newIndex;
|
|
}
|
|
PlacesMoveItemTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PMIT_doTransaction() {
|
|
LOG("Move Item: " + this._uri.spec + " from: " + this._oldContainer + "," + this._oldIndex + " to: " + this._newContainer + "," + this._newIndex);
|
|
this._bms.removeItem(this._oldContainer, this._uri);
|
|
this._bms.insertItem(this._newContainer, this._uri, this._newIndex);
|
|
},
|
|
|
|
undoTransaction: function PMIT_undoTransaction() {
|
|
LOG("UNMove Item: " + this._uri.spec + " from: " + this._oldContainer + "," + this._oldIndex + " to: " + this._newContainer + "," + this._newIndex);
|
|
this._bms.removeItem(this._newContainer, this._uri);
|
|
this._bms.insertItem(this._oldContainer, this._uri, this._oldIndex);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Remove a Folder
|
|
*/
|
|
function PlacesRemoveFolderTransaction(id, oldContainer, oldIndex) {
|
|
this._id = id;
|
|
this._oldContainer = oldContainer;
|
|
this._oldIndex = oldIndex;
|
|
this._oldFolderTitle = null;
|
|
}
|
|
PlacesRemoveFolderTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PRFT_doTransaction() {
|
|
LOG("Remove Folder: " + this._id + " from: " + this._oldContainer + "," + this._oldIndex);
|
|
this._oldFolderTitle = this._bms.getFolderTitle(this._id);
|
|
this._bms.removeFolder(this._id);
|
|
},
|
|
|
|
undoTransaction: function PRFT_undoTransaction() {
|
|
LOG("UNRemove Folder: " + this._id + " from: " + this._oldContainer + "," + this._oldIndex);
|
|
this._id = this._bms.createFolder(this._oldContainer, this._oldFolderTitle, this._oldIndex);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Remove an Item
|
|
*/
|
|
function PlacesRemoveItemTransaction(uri, oldContainer, oldIndex) {
|
|
this._uri = uri;
|
|
this._oldContainer = oldContainer;
|
|
this._oldIndex = oldIndex;
|
|
}
|
|
PlacesRemoveItemTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PRIT_doTransaction() {
|
|
LOG("Remove Item: " + this._uri.spec + " from: " + this._oldContainer + "," + this._oldIndex);
|
|
this._bms.removeItem(this._oldContainer, this._uri);
|
|
},
|
|
|
|
undoTransaction: function PRIT_undoTransaction() {
|
|
LOG("UNRemove Item: " + this._uri.spec + " from: " + this._oldContainer + "," + this._oldIndex);
|
|
this._bms.insertItem(this._oldContainer, this._uri, this._oldIndex);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Edit a Folder
|
|
*/
|
|
function PlacesEditFolderTransaction(id, oldAttributes, newAttributes) {
|
|
this._id = id;
|
|
this._oldAttributes = oldAttributes;
|
|
this._newAttributes = newAttributes;
|
|
}
|
|
PlacesEditFolderTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PEFT_doTransaction() {
|
|
LOG("Edit Folder: " + this._id + " oldAttrs: " + this._oldAttributes.toSource() + " newAttrs: " + this._newAttributes.toSource());
|
|
// Use Bookmarks and Annotation Services to perform these operations.
|
|
},
|
|
|
|
undoTransaction: function PEFT_undoTransaction() {
|
|
LOG("UNEdit Folder: " + this._id + " oldAttrs: " + this._oldAttributes.toSource() + " newAttrs: " + this._newAttributes.toSource());
|
|
// Use Bookmarks and Annotation Services to perform these operations.
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Edit an Item
|
|
*/
|
|
function PlacesEditItemTransaction(uri, newAttributes) {
|
|
this._uri = uri;
|
|
this._newAttributes = newAttributes;
|
|
this._oldAttributes = { };
|
|
}
|
|
PlacesEditItemTransaction.prototype = {
|
|
__proto__: PlacesBaseTransaction.prototype,
|
|
|
|
doTransaction: function PEIT_doTransaction() {
|
|
LOG("Edit Item: " + this._uri.spec + " oldAttrs: " + this._oldAttributes.toSource() + " newAttrs: " + this._newAttributes.toSource());
|
|
for (var p in this._newAttributes) {
|
|
if (p == "title") {
|
|
this._oldAttributes[p] = this._bms.getItemTitle(this._uri);
|
|
this._bms.setItemTitle(this._uri, this._newAttributes[p]);
|
|
}
|
|
else {
|
|
// Use Annotation Service
|
|
}
|
|
}
|
|
},
|
|
|
|
undoTransaction: function PEIT_undoTransaction() {
|
|
LOG("UNEdit Item: " + this._uri.spec + " oldAttrs: " + this._oldAttributes.toSource() + " newAttrs: " + this._newAttributes.toSource());
|
|
for (var p in this._newAttributes) {
|
|
if (p == "title")
|
|
this._bms.setItemTitle(this._uri, this._oldAttributes[p]);
|
|
else {
|
|
// Use Annotation Service
|
|
}
|
|
}
|
|
},
|
|
};
|
|
/*
|
|
|
|
AVI rules:
|
|
|
|
readonly attribute boolean hasSelection;
|
|
readonly attribute boolean hasSingleSelection;
|
|
readonly attribute boolean selectionIsContainer;
|
|
readonly attribute boolean containerIsOpen;
|
|
void getSelectedNodes([retval, array, size_is(nodeCount)] out nodes, out nodeCount);
|
|
|
|
selection flags
|
|
|
|
flags:
|
|
SELECTION_CONTAINS_URL
|
|
SELECTION_CONTAINS_CONTAINER_OPEN
|
|
SELECTION_CONTAINS_CONTAINER_CLOSED
|
|
SELECTION_CONTAINS_CHANGEABLE
|
|
SELECTION_CONTAINS_REMOVABLE
|
|
SELECTION_CONTAINS_MOVABLE
|
|
|
|
Given a:
|
|
- view, via AVI
|
|
- query
|
|
- query options
|
|
|
|
Determine the state of commands!
|
|
|
|
*/
|
|
|
|
|