mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-16 12:30:13 +01:00
1014 lines
40 KiB
XML
1014 lines
40 KiB
XML
<?xml version="1.0"?>
|
|
|
|
# ***** 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 Tree View.
|
|
#
|
|
# The Initial Developer of the Original Code is Google Inc.
|
|
# Portions created by the Initial Developer are Copyright (C) 2005-2006
|
|
# the Initial Developer. All Rights Reserved.
|
|
#
|
|
# Contributor(s):
|
|
# Ben Goodger <beng@google.com>
|
|
# Annie Sullivan <annie.sullivan@gmail.com>
|
|
#
|
|
# Alternatively, the contents of this file may be used under the terms of
|
|
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
# in which case the provisions of the GPL or the LGPL are applicable instead
|
|
# of those above. If you wish to allow use of your version of this file only
|
|
# under the terms of either the GPL or the LGPL, and not to allow others to
|
|
# use your version of this file under the terms of the MPL, indicate your
|
|
# decision by deleting the provisions above and replace them with the notice
|
|
# and other provisions required by the GPL or the LGPL. If you do not delete
|
|
# the provisions above, a recipient may use your version of this file under
|
|
# the terms of any one of the MPL, the GPL or the LGPL.
|
|
#
|
|
# ***** END LICENSE BLOCK *****
|
|
|
|
<bindings id="placesTreeBindings"
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
|
|
<binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree">
|
|
<implementation implements="nsINavHistoryResultViewObserver">
|
|
<constructor><![CDATA[
|
|
// Apply meta-model rules to this view.
|
|
ViewConfigurator.configure(this);
|
|
|
|
this.history = PlacesController.history;
|
|
this.controllers.appendController(PlacesController);
|
|
|
|
// Force an initial build.
|
|
this.place = this.place;
|
|
]]></constructor>
|
|
|
|
<field name="history">null</field>
|
|
|
|
<!-- overriding -->
|
|
<property name="view">
|
|
<getter><![CDATA[
|
|
return this.treeBoxObject.view;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
// Make sure the last result doesn't hold a reference to us anymore
|
|
var resultview = this.getResultView();
|
|
if (resultview)
|
|
resultview.removeViewObserver(this._viewObserver);
|
|
this.treeBoxObject.view = val;
|
|
val.QueryInterface(Ci.nsINavHistoryResultViewer).addViewObserver(
|
|
this._viewObserver, false);
|
|
]]></setter>
|
|
</property>
|
|
|
|
<method name="getBestOptions">
|
|
<body><![CDATA[
|
|
// Get the best set of grouping options to use, either reuse the
|
|
// existing ones or create new ones.
|
|
var options = this.getResult().queryOptions;
|
|
if (!options)
|
|
options = this.history.getNewQueryOptions();
|
|
return options;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="applyFilter">
|
|
<parameter name="filterString"/>
|
|
<parameter name="onlyBookmarks"/>
|
|
<parameter name="folderRestrict"/>
|
|
<parameter name="optionsFilter"/>
|
|
<body><![CDATA[
|
|
// preserve grouping
|
|
var queryNode = asQuery(this.getResult().root);
|
|
var options = queryNode.queryOptions.clone();
|
|
if (optionsFilter)
|
|
options = optionsFilter.filter(queryNode.getQueries({}), options,
|
|
optionsFilter.historyHandler);
|
|
|
|
var query = this.history.getNewQuery();
|
|
query.searchTerms = filterString;
|
|
query.onlyBookmarked = onlyBookmarks;
|
|
|
|
// If we're searching over everything, or this is not an exclusively
|
|
// bookmarks search, we need to make sure that we remove "group by
|
|
// folder" from the options list, because otherwise we will end up
|
|
// with _no_ results if the user searches while a bookmarks folder
|
|
// is selected, since the options for the current view say group by
|
|
// folder and none of the results will match!
|
|
if (!onlyBookmarks) {
|
|
var groupings = options.getGroupingMode({});
|
|
function isFolderGrouping(grouping, index, ary) {
|
|
return grouping != Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER;
|
|
}
|
|
var folderGroupings = groupings.filter(isFolderGrouping);
|
|
options.setGroupingMode(folderGroupings, folderGroupings.length);
|
|
}
|
|
if (onlyBookmarks)
|
|
query.setFolders(folderRestrict, folderRestrict.length);
|
|
this.load([query], options);
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="load">
|
|
<parameter name="queries"/>
|
|
<parameter name="options"/>
|
|
<body><![CDATA[
|
|
// override with our local options
|
|
options.excludeItems = this.excludeItems;
|
|
options.excludeQueries = this.excludeQueries;
|
|
options.expandQueries = this.expandQueries;
|
|
var result = this.history.executeQueries(queries, queries.length,
|
|
options);
|
|
|
|
var treeviewer =
|
|
Cc["@mozilla.org/browser/nav-history/result-tree-viewer;1"].
|
|
createInstance(Ci.nsINavHistoryResultViewer);
|
|
result.viewer = treeviewer;
|
|
|
|
treeviewer.QueryInterface(Ci.nsITreeView);
|
|
this.view = treeviewer;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
Causes a particular node represented by the specified placeURI to be
|
|
selected in the tree. All containers above the node in the hierarchy
|
|
will be opened, so that the node is visible.
|
|
-->
|
|
<method name="selectPlaceURI">
|
|
<parameter name="placeURI"/>
|
|
<body><![CDATA[
|
|
function findNode(container, placeURI) {
|
|
container.containerOpen = true;
|
|
for (var i = 0; i < container.childCount; ++i) {
|
|
var child = container.getChild(i);
|
|
if (child.uri == placeURI)
|
|
return child;
|
|
else if (PlacesController.nodeIsContainer(child)) {
|
|
var nested = findNode(asContainer(child), placeURI);
|
|
if (nested)
|
|
return nested;
|
|
}
|
|
}
|
|
container.containerOpen = false;
|
|
return null;
|
|
}
|
|
|
|
var container = this._getRootNode();
|
|
NS_ASSERT(container, "No result, cannot select place URI!");
|
|
if (!container)
|
|
return;
|
|
|
|
var child = findNode(container, placeURI);
|
|
container.containerOpen = true;
|
|
if (child)
|
|
this.selectNode(child);
|
|
else {
|
|
// If the specified child could not be located, just select the first
|
|
// item in the list.
|
|
if (this.view.rowCount)
|
|
this.view.selection.select(0);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
Causes a particular node to be selected in the tree, resulting in all
|
|
containers above the node in the hierarchy to be opened, so that the
|
|
node is visible.
|
|
-->
|
|
<method name="selectNode">
|
|
<parameter name="node"/>
|
|
<body><![CDATA[
|
|
var parent = node.parent;
|
|
|
|
// Build a list of all of the nodes that are the parent of this one
|
|
// in the result.
|
|
var parents = [];
|
|
var root = this._getRootNode();
|
|
while (parent && parent != root) {
|
|
parents.push(parent);
|
|
parent = parent.parent;
|
|
}
|
|
|
|
// Walk the list backwards (opening from the root of the hierarchy)
|
|
// opening each folder as we go.
|
|
var resultview = this.getResultView();
|
|
var view = this.view;
|
|
for (var i = parents.length - 1; i >= 0; --i) {
|
|
var index = resultview.treeIndexForNode(parents[i]);
|
|
if (view.isContainer(index) && !view.isContainerOpen(index))
|
|
view.toggleOpenState(index);
|
|
}
|
|
// Select the specified node...
|
|
index = resultview.treeIndexForNode(node);
|
|
view.selection.select(index);
|
|
// ... and ensure it's visible, not scrolled off somewhere.
|
|
this.treeBoxObject.ensureRowIsVisible(index);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<method name="getResult">
|
|
<body><![CDATA[
|
|
try {
|
|
return this.view.QueryInterface(Ci.nsINavHistoryResultViewer).result;
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
<method name="getResultView">
|
|
<body><![CDATA[
|
|
try {
|
|
return this.view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
|
|
}
|
|
catch (e) {
|
|
}
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<property name="place">
|
|
<getter><![CDATA[
|
|
return this.getAttribute("place");
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.setAttribute("place", val);
|
|
|
|
var queriesRef = { };
|
|
var queryCountRef = { };
|
|
var optionsRef = { };
|
|
this.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef);
|
|
if (queryCountRef.value == 0)
|
|
queriesRef.value = [this.history.getNewQuery()];
|
|
if (!optionsRef.value)
|
|
optionsRef.value = this.history.getNewQueryOptions();
|
|
|
|
this.load(queriesRef.value, optionsRef.value);
|
|
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<property name="hasSelection">
|
|
<getter><![CDATA[
|
|
return this.view.selection.count >= 1;
|
|
]]></getter>
|
|
</property>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<property name="hasSingleSelection">
|
|
<getter><![CDATA[
|
|
return this.view.selection.count == 1;
|
|
]]></getter>
|
|
</property>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<method name="getSelectionNodes">
|
|
<body><![CDATA[
|
|
var selection = this.view.selection;
|
|
var rc = selection.getRangeCount();
|
|
var nodes = [];
|
|
var resultview = this.getResultView();
|
|
for (var i = 0; i < rc; ++i) {
|
|
var min = { }, max = { };
|
|
selection.getRangeAt(i, min, max);
|
|
|
|
for (var j = min.value; j <= max.value; ++j)
|
|
nodes.push(resultview.nodeForTreeIndex(j));
|
|
}
|
|
return nodes;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<method name="getRemovableSelectionRanges">
|
|
<body><![CDATA[
|
|
// This function exists in addition to getSelectionNodes because it
|
|
// encodes selection ranges (which only occur in list views) into
|
|
// the return value. For each removed range, the index at which items
|
|
// will be re-inserted upon the remove transaction being performed is
|
|
// the first index of the range, so that the view updates correctly.
|
|
//
|
|
// For example, if we remove rows 2,3,4 and 7,8 from a list, when we
|
|
// undo that operation, if we insert what was at row 3 at row 3 again,
|
|
// it will show up _after_ the item that was at row 5. So we need to
|
|
// insert all items at row 2, and the tree view will update correctly.
|
|
//
|
|
// Also, this function collapses the selection to remove redundant
|
|
// data, e.g. when deleting this selection:
|
|
//
|
|
// http://www.foo.com/
|
|
// (-) Some Folder
|
|
// http://www.bar.com/
|
|
//
|
|
// ... returning http://www.bar.com/ as part of the selection is
|
|
// redundant because it is implied by removing "Some Folder". We
|
|
// filter out all such redundancies since some partial amount of
|
|
// the folder's children may be selected.
|
|
//
|
|
var selection = this.view.selection;
|
|
var rc = selection.getRangeCount();
|
|
var nodes = [];
|
|
var resultview = this.getResultView();
|
|
// This list is kept independently of the range selected (i.e. OUTSIDE
|
|
// the for loop) since the row index of a container is unique for the
|
|
// entire view, and we could have some really wacky selection and we
|
|
// don't want to blow up.
|
|
var containers = { };
|
|
for (var i = 0; i < rc; ++i) {
|
|
var range = [];
|
|
var min = { }, max = { };
|
|
selection.getRangeAt(i, min, max);
|
|
|
|
for (var j = min.value; j <= max.value; ++j) {
|
|
if (this.view.isContainer(j))
|
|
containers[j] = true;
|
|
if (!(this.view.getParentIndex(j) in containers))
|
|
range.push(resultview.nodeForTreeIndex(j));
|
|
}
|
|
nodes.push(range);
|
|
}
|
|
return nodes;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<method name="getCopyableSelection">
|
|
<body><![CDATA[
|
|
// XXXben implement me!
|
|
return this.getSelectionNodes();
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<method name="getDragableSelection">
|
|
<body><![CDATA[
|
|
var nodes = this.getSelectionNodes();
|
|
for (var i = nodes.length - 1; i >= 0; i--) {
|
|
if (PlacesController.nodeIsReadOnly(nodes[i].parent))
|
|
nodes.splice(i, 1);
|
|
}
|
|
return nodes;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<property name="selectedNode">
|
|
<getter><![CDATA[
|
|
var view = this.view;
|
|
var selection = view.selection;
|
|
var rc = selection.getRangeCount();
|
|
if (rc != 1)
|
|
return null;
|
|
var min = { }, max = { };
|
|
selection.getRangeAt(0, min, max);
|
|
|
|
return this.getResultView().nodeForTreeIndex(min.value);
|
|
]]></getter>
|
|
</property>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<property name="selectedURINode">
|
|
<getter><![CDATA[
|
|
var view = this.view;
|
|
var selection = view.selection;
|
|
var rc = selection.getRangeCount();
|
|
if (rc != 1)
|
|
return null;
|
|
var min = { }, max = { };
|
|
selection.getRangeAt(0, min, max);
|
|
|
|
// only URI nodes should be returned
|
|
var node = this.getResultView().nodeForTreeIndex(min.value);
|
|
if (PlacesController.nodeIsURI(node))
|
|
return node;
|
|
return null;
|
|
]]></getter>
|
|
</property>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<property name="insertionPoint">
|
|
<getter><![CDATA[
|
|
var orientation = NHRVO.DROP_AFTER;
|
|
|
|
// If there is no selection, insert at the end of the container.
|
|
if (!this.hasSelection) {
|
|
var index = this.view.rowCount - 1;
|
|
return this._getInsertionPoint(index, orientation);
|
|
}
|
|
|
|
// This is a two-part process. The first part is determining the drop
|
|
// orientation.
|
|
// * The default orientation is to drop _after_ the selected item.
|
|
// * If the selected item is an open container, the default
|
|
// orientation is to drop _into_ that container.
|
|
// * If the selected item is in the system area, the default action
|
|
// is to insert before the first user-editable item.
|
|
//
|
|
// Warning: It may be tempting to use tree indexes in this code, but
|
|
// you must not, since the tree is nested and as your tree
|
|
// index may change when folders before you are opened and
|
|
// closed. You must convert your tree index to a node, and
|
|
// then use getIndexOfNode to find your absolute index in
|
|
// the parent container instead.
|
|
//
|
|
var selection = this.view.selection;
|
|
var rc = selection.getRangeCount();
|
|
var min = { }, max = { };
|
|
selection.getRangeAt(rc - 1, min, max);
|
|
|
|
var resultView = this.getResultView();
|
|
|
|
// If the sole selection is an open container, insert into it rather
|
|
// than adjacent to it. Note that this only applies to _single_
|
|
// selections - if the last element within a multi-selection is an
|
|
// open folder, insert _adajacent_ to the selection.
|
|
if (this.hasSingleSelection && this.view.isContainer(max.value) &&
|
|
this.view.isContainerOpen(max.value))
|
|
orientation = NHRVO.DROP_ON;
|
|
else {
|
|
// If the selection is in the static "system" area, the insertion
|
|
// point is at the start of the user-editable area, i.e. before the
|
|
// first user editable item. This index (relative to the container,
|
|
// not the tree) is defined in this.peerDropIndex.
|
|
// We walk the list of top level ("system" area only applies to the
|
|
// top level) children, looking for the last selected node. If the
|
|
// last selected node is inside the "system" area, get the node for
|
|
// the first user editable item instead, compute its tree index, and
|
|
// insert _before_ that.
|
|
node = resultView.nodeForTreeIndex(max.value);
|
|
var container = asContainer(this.getResult().root);
|
|
var cc = container.childCount;
|
|
for (var i = 0; i < cc; ++i) {
|
|
if (container.getChild(i) == node &&
|
|
i < this.peerDropIndex) {
|
|
var firstUserChild = container.getChild(this.peerDropIndex);
|
|
max.value = resultView.treeIndexForNode(firstUserChild);
|
|
orientation = NHRVO.DROP_BEFORE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return this._getInsertionPoint(max.value, orientation);
|
|
]]></getter>
|
|
</property>
|
|
|
|
<method name="_getInsertionPoint">
|
|
<parameter name="index"/>
|
|
<parameter name="orientation"/>
|
|
<body><![CDATA[
|
|
var result = this.getResult();
|
|
var resultview = this.getResultView();
|
|
var container = result.root;
|
|
NS_ASSERT(container, "null container");
|
|
// When there's no selection, assume the container is the container
|
|
// the view is populated from (i.e. the result's folderId).
|
|
if (index != -1) {
|
|
var lastSelected = resultview.nodeForTreeIndex(index);
|
|
if (resultview.isContainer(index) && orientation == NHRVO.DROP_ON) {
|
|
// If the last selected item is an open container, append _into_
|
|
// it, rather than insert adjacent to it.
|
|
container = lastSelected;
|
|
index = -1;
|
|
}
|
|
else {
|
|
// Any visible selected item will always have a parent. The parent of
|
|
// an item at the root is the result itself, which can be QI'ed to
|
|
// nsINavHistoryResult
|
|
container = lastSelected.parent;
|
|
var lsi = PlacesController.getIndexOfNode(lastSelected);
|
|
index = orientation == NHRVO.DROP_BEFORE ? lsi : lsi + 1;
|
|
}
|
|
}
|
|
if (PlacesController.nodeIsFolder(container))
|
|
return new InsertionPoint(asFolder(container).folderId, index, orientation);
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<field name="peerDropIndex">0</field>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<field name="peerDropTypes">ViewConfig.GENERIC_DROP_TYPES</field>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<field name="childDropTypes">ViewConfig.GENERIC_DROP_TYPES</field>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<field name="excludeItems">false</field>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<field name="excludeQueries">false</field>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<field name="expandQueries">false</field>
|
|
|
|
<!-- nsIPlacesView -->
|
|
<method name="selectAll">
|
|
<body><![CDATA[
|
|
this.view.selection.selectAll();
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- This method returns the root node of the query results as
|
|
a container query result node. If it is unable to do this
|
|
(for instance, if we have no query results), it will
|
|
return null.
|
|
-->
|
|
<method name="_getRootNode">
|
|
<body><![CDATA[
|
|
var result = this.getResult();
|
|
if (!result)
|
|
return null;
|
|
return asContainer(result.root);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- This method, when given a list of folder IDs, will select all
|
|
the nodes that match those IDs in the current tree.
|
|
It will open any parent nodes that it needs to in order to
|
|
show the selected folders.
|
|
-->
|
|
<method name="selectFolders">
|
|
<parameter name="ids"/>
|
|
<body><![CDATA[
|
|
/**
|
|
* Returns the number of properties on the object "obj", since
|
|
* we're using objects as sets and dictionaries in the
|
|
* code below.
|
|
*/
|
|
function len(obj) {
|
|
var count = 0;
|
|
for (property in obj)
|
|
++count;
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Recursively search through a node's children for folders
|
|
* with IDs in the set "indexes". When a matching folder
|
|
* is found, remove its ID from "indexes" and store the
|
|
* node in the dictionary "nodes".
|
|
*
|
|
* NOTE: This method will leave open any node that had
|
|
* matching folders in its subtree.
|
|
*
|
|
* @param node The root of the subtree to search for the folders.
|
|
* "node" itself will not be examined for a match.
|
|
* @param indexes A set of folder IDs, expressed as an object
|
|
* @param nodes A dictionary mapping folder IDs to folder
|
|
* result nodes, expressed as an object. This is
|
|
* empty at the initial start of the recursion and
|
|
* gets filled in as the recursion progresses.
|
|
*/
|
|
function findNodes(node, indexes, nodes) {
|
|
// Remember the beginning state so that we can re-close
|
|
// this node if we don't find any additional results here.
|
|
var previousOpenness = node.containerOpen;
|
|
var previousMatches = len(nodes);
|
|
node.containerOpen = true;
|
|
|
|
for (var child = 0;
|
|
(child < node.childCount) && (len(indexes) > 0);
|
|
child++) {
|
|
var childNode = node.getChild(child);
|
|
if (!PlacesController.nodeIsFolder(childNode))
|
|
continue;
|
|
|
|
var childFolder = asFolder(childNode);
|
|
|
|
// See if child matches an ID we wanted; add to results.
|
|
if (childFolder.folderId in indexes) {
|
|
nodes[childFolder.folderId] = childFolder;
|
|
delete indexes[childFolder.folderId];
|
|
}
|
|
|
|
// Search down that child's subtree.
|
|
findNodes(childFolder, indexes, nodes);
|
|
}
|
|
|
|
// If we didn't find any additional matches in this node's
|
|
// subtree, revert the node to its previous openness.
|
|
if (len(nodes) == previousMatches)
|
|
node.containerOpen = previousOpenness;
|
|
}
|
|
|
|
var indexes = {}; // Set of folder IDs to search for.
|
|
var nodes = {}; // Dictionary of found IDs to found nodes.
|
|
|
|
// Initialize input set
|
|
for (var i = 0; i < ids.length; i++)
|
|
indexes[ids[i]] = true;
|
|
|
|
var root = this._getRootNode();
|
|
if (!root) {
|
|
NS_ASSERT(root, "Couldn't select folders because no root node was available.");
|
|
return;
|
|
}
|
|
|
|
findNodes(root, indexes, nodes);
|
|
|
|
// For all the nodes we've found, highlight the corresponding
|
|
// index in the tree.
|
|
var resultview = this.getResultView();
|
|
var selection = this.view.selection;
|
|
selection.clearSelection();
|
|
for(idx in nodes) {
|
|
var index = resultview.treeIndexForNode(nodes[idx]);
|
|
selection.rangedSelect(index, index, true);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsDragAndDrop -->
|
|
<method name="onDragStart">
|
|
<parameter name="event"/>
|
|
<parameter name="xferData"/>
|
|
<parameter name="dragAction"/>
|
|
<body><![CDATA[
|
|
// Drag and Drop does not work while a tree view is sorted.
|
|
if (this.getAttribute("sortActive") == "true")
|
|
throw Cr.NS_OK;
|
|
|
|
// Items that are "static" - i.e. above the user-configurable area
|
|
// of the view - can not be moved.
|
|
var nodes = this.getSelectionNodes();
|
|
var bms =
|
|
Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
|
getService(Ci.nsINavBookmarksService);
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
var node = nodes[i];
|
|
|
|
// If this node is part of a readonly folder (e.g. a livemark) it
|
|
// cannot be moved, only copied, so we must change the action used
|
|
// by the drag session.
|
|
var parent = node.parent;
|
|
if (PlacesController.nodeIsFolder(parent) &&
|
|
bms.getFolderReadonly(asFolder(parent).folderId))
|
|
dragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_COPY;
|
|
|
|
// "static" top level "special" items cannot be dragged. They are
|
|
// at the root level of the view and are at a lower index in the
|
|
// root container than user-configurable items.
|
|
var treeIndex = this.getResultView().treeIndexForNode(nodes[i]);
|
|
if (this.view.getLevel(treeIndex) == 0) {
|
|
var index = PlacesController.getIndexOfNode(nodes[i]);
|
|
if (index < this.peerDropIndex)
|
|
throw Cr.NS_OK;
|
|
}
|
|
}
|
|
|
|
// XXXben - the drag wrapper should do this automatically.
|
|
if (event.ctrlKey)
|
|
dragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_COPY;
|
|
// Stuff the encoded selection into the transferable data object
|
|
xferData.data = PlacesController.getTransferData(dragAction.action);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsDragAndDrop -->
|
|
<method name="canDrop">
|
|
<parameter name="event"/>
|
|
<parameter name="session"/>
|
|
<body><![CDATA[
|
|
return this._viewObserver.canDrop(-1, -1);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsDragAndDrop -->
|
|
<method name="onDragOver">
|
|
<parameter name="event"/>
|
|
<parameter name="flavor"/>
|
|
<parameter name="session"/>
|
|
<body><![CDATA[
|
|
// When the user is dragging over an empty area of the tree, make
|
|
// sure canDrop is set to true to indicate dropping into the bucket.
|
|
var row = { }, col = { }, child = { };
|
|
this.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col,
|
|
child);
|
|
if (row.value == -1) {
|
|
var dragService =
|
|
Cc["@mozilla.org/widget/dragservice;1"].
|
|
getService(Ci.nsIDragService);
|
|
var dragSession = dragService.getCurrentSession();
|
|
dragSession.canDrop = this._viewObserver.canDrop(-1, 1);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- nsDragAndDrop -->
|
|
<method name="getSupportedFlavours">
|
|
<body><![CDATA[
|
|
var flavorSet = new FlavourSet();
|
|
for (var i = 0; i < this.peerDropTypes.length; ++i)
|
|
flavorSet.appendFlavour(this.peerDropTypes[i]);
|
|
return flavorSet;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
Gets the nsINavHistoryResultNode adjacent to the specified InsertionPoint.
|
|
@param insertionPoint
|
|
The InsertionPoint where items are being inserted
|
|
@param excludeItems
|
|
true if leaf nodes should be excluded for this view, false
|
|
otherwise.
|
|
@returns a nsINavHistoryResultNode that is adjacent to the specified
|
|
InsertionPoint
|
|
-->
|
|
<method name="_getInsertionNode">
|
|
<parameter name="insertionPoint"/>
|
|
<parameter name="excludeItems"/>
|
|
<body><![CDATA[
|
|
// The InsertionPoint defines a place within the view hierarchy where
|
|
// items may be inserted. This function returns the node adjacent to
|
|
// that point. In order to get the right node, we must enumerate the
|
|
// contents of the containing folder, but we cannot construct a new
|
|
// query for this, because if we do so the node will be a member of
|
|
// another result node (that constructed by |getFolderContents| say,
|
|
// instead of the displayed tree view). So we need to walk the
|
|
// children of the result to find the right folder.
|
|
function findFolder(folderId, container) {
|
|
var wasOpen = container.containerOpen;
|
|
container.containerOpen = true;
|
|
var cc = container.childCount;
|
|
var foundNode = null;
|
|
for (var i = 0; i < cc; ++i) {
|
|
var node = container.getChild(i);
|
|
if (PlacesController.nodeIsFolder(node)) {
|
|
var folder = asFolder(node);
|
|
if (folder.folderId == folderId) {
|
|
foundNode = node;
|
|
break;
|
|
}
|
|
foundNode = findFolder(folderId, folder);
|
|
if (foundNode)
|
|
break;
|
|
}
|
|
}
|
|
container.containerOpen = wasOpen;
|
|
return foundNode;
|
|
}
|
|
var folder = null;
|
|
var root = asContainer(this.getResult().root);
|
|
if (PlacesController.nodeIsFolder(root) &&
|
|
asFolder(root).folderId == insertionPoint.folderId)
|
|
folder = root;
|
|
else
|
|
folder = findFolder(insertionPoint.folderId, root);
|
|
|
|
// Since we find the folder manually, using findFolder instead of
|
|
// PlacesController.getFolderContents, the folder is not opened for
|
|
// us. We need to do that ourselves (and remember if it was closed,
|
|
// so that we can close it again later).
|
|
var folderWasOpen = folder.containerOpen;
|
|
folder.containerOpen = true;
|
|
|
|
var index = insertionPoint.index;
|
|
if (insertionPoint.index == -1 || insertionPoint.index >= folder.childCount)
|
|
index = folder.childCount - 1;
|
|
NS_ASSERT(index < folder.childCount,
|
|
"index out of range: " + index + " > " + folder);
|
|
var insertionNode = index > -1 ? folder.getChild(index) : null;
|
|
|
|
// Now close the folder again, if it was before.
|
|
folder.containerOpen = folderWasOpen;
|
|
|
|
return insertionNode;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
Gets the tree-index of the node adjacent to the specified InsertionPoint
|
|
@param insertionPoint
|
|
The InsertionPoint where items are being inserted
|
|
@returns the tree-index of the node adjacent to the specified InsertionPoint
|
|
-->
|
|
<method name="_getInsertionIndex">
|
|
<parameter name="insertionPoint"/>
|
|
<body><![CDATA[
|
|
// Insert index of insertion and number of rows to insert
|
|
var excludeItems = this.excludeItems;
|
|
// This is a bit of a hack. Assume any container you drop into is
|
|
// itself showing all item types, not just folders or items.
|
|
if (insertionPoint.orientation == NHRVO.DROP_ON)
|
|
excludeItems = false;
|
|
var node = this._getInsertionNode(insertionPoint, excludeItems);
|
|
// This is the insertion index of the pivot.
|
|
if (node)
|
|
return this.getResultView().treeIndexForNode(node);
|
|
else if (insertionPoint.orientation == NHRVO.DROP_ON)
|
|
return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
|
|
return -1;
|
|
]]></body>
|
|
</method>
|
|
|
|
<field name="_viewObserver"><![CDATA[({
|
|
_self: this,
|
|
|
|
canDrop: function VO_canDrop(index, orientation) {
|
|
var result = this._self.getResult();
|
|
var resultview = this._self.getResultView();
|
|
var node = index != -1 ? resultview.nodeForTreeIndex(index) : result.root;
|
|
// Cannot drop before fixed items in the list.
|
|
if (node.parent == result.root &&
|
|
PlacesController.getIndexOfNode(node) < this._self.peerDropIndex &&
|
|
orientation != NHRVO.DROP_ON)
|
|
return false;
|
|
|
|
if (orientation == NHRVO.DROP_ON) {
|
|
// The user cannot drop an item into itself or a read-only container
|
|
var droppingOnSelf =
|
|
this._getSourceView() == this._self &&
|
|
this._self.view.selection.isSelected(index);
|
|
if (droppingOnSelf || node.containerReadOnly)
|
|
return false;
|
|
}
|
|
else if (node.parent && node.parent.containerReadOnly)
|
|
return false;
|
|
|
|
return PlacesControllerDragHelper.canDrop(this._self, orientation);
|
|
},
|
|
|
|
/**
|
|
* Adjusts an InsertionPoint's insertion index using these rules:
|
|
* XXXben define rules
|
|
*/
|
|
_adjustInsertionPoint: function VO__adjustInsertionPoint(insertionPoint) {
|
|
var index = this._self._getInsertionIndex(insertionPoint);
|
|
|
|
// If the dropped items are invisible, we don't need to adjust the
|
|
// insertion point.
|
|
if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
|
|
return;
|
|
|
|
var selection = this._self.view.selection;
|
|
var rc = selection.getRangeCount();
|
|
var selectionAbove = 0;
|
|
for (var i = 0; i < rc; ++i) {
|
|
var min = { }, max = { };
|
|
selection.getRangeAt(i, min, max);
|
|
if (min.value > index)
|
|
break;
|
|
if (max.value >= index)
|
|
selectionAbove += index - min.value;
|
|
else
|
|
selectionAbove += max.value - min.value + 1;
|
|
}
|
|
insertionPoint.index -= selectionAbove;
|
|
},
|
|
|
|
/**
|
|
* @returns the view where the drag was initiated.
|
|
*/
|
|
_getSourceView: function VO__getSourceView() {
|
|
var session = this._getCurrentSession();
|
|
var sourceView = session.sourceNode;
|
|
while (sourceView && sourceView.localName != "tree")
|
|
sourceView = sourceView.parentNode;
|
|
return sourceView;
|
|
},
|
|
|
|
/**
|
|
* @returns the current drag session.
|
|
*/
|
|
_getCurrentSession: function VO__getCurrentSession() {
|
|
var dragService =
|
|
Cc["@mozilla.org/widget/dragservice;1"].
|
|
getService(Ci.nsIDragService);
|
|
return dragService.getCurrentSession();
|
|
},
|
|
|
|
/**
|
|
* Handles a drop operation on this view
|
|
* @param index
|
|
* The index at which content was dropped
|
|
* @param orientation
|
|
* The orientation relative to the drop index where content
|
|
* should be inserted.
|
|
*/
|
|
onDrop: function VO_onDrop(index, orientation) {
|
|
LOG("VO: onDrop: " + index + ", orientation: " + orientation);
|
|
if (!this.canDrop(index, orientation))
|
|
return;
|
|
|
|
var sourceView = this._getSourceView();
|
|
|
|
// We are responsible for translating the |index| and |orientation|
|
|
// parameters into a container id and index within the container,
|
|
// since this information is specific to the tree view.
|
|
var ip = this._self._getInsertionPoint(index, orientation);
|
|
if (!ip)
|
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
|
if (sourceView == this._self) {
|
|
// We are moving nodes within the same view, we need to adjust the
|
|
// insertion point to take into account the fact that rows may
|
|
// disappear above it, causing its index to be incorrect.
|
|
this._adjustInsertionPoint(ip);
|
|
}
|
|
PlacesControllerDragHelper.onDrop(sourceView, this._self, ip);
|
|
},
|
|
|
|
onToggleOpenState: function VO_onToggleOpenState(index) { },
|
|
onSelectionChanged: function VO_onSelectionChanged() { },
|
|
onCycleHeader: function VO_onCycleHeader(column) { },
|
|
onCycleCell: function VO_onCycleCell(row, column) { },
|
|
onPerformAction: function VO_onPerformAction(action) { },
|
|
onPerformActionOnRow: function VO_onPerformActionOnRow(action, row) { },
|
|
onPerformActionOnCell: function VO_onPerformActionOnCell(action, row, column) { }
|
|
})]]></field>
|
|
|
|
<field name="_nextSelection">[]</field>
|
|
<field name="SAVE_SELECTION_RELOAD">0</field>
|
|
<field name="SAVE_SELECTION_INSERT">1</field>
|
|
<field name="SAVE_SELECTION_REMOVE">2</field>
|
|
<method name="saveSelection">
|
|
<parameter name="mode"/>
|
|
<body><![CDATA[
|
|
// mode can be one of any of the SAVE_SELECTION field values,
|
|
// specifying how selection is to be saved.
|
|
var s = this.view.selection;
|
|
var rc = s.getRangeCount();
|
|
switch (mode) {
|
|
case this.SAVE_SELECTION_REMOVE:
|
|
var min = { }, max = { };
|
|
s.getRangeAt(rc - 1, min, max);
|
|
var rowCount = this.view.rowCount;
|
|
var index = -1;
|
|
if (max.value == (rowCount - 1) ||
|
|
this.view.getLevel(max.value + 1) != this.view.getLevel(max.value))
|
|
index = max.value - s.count;
|
|
else
|
|
index = max.value - s.count + 1;
|
|
this._nextSelection = [{ min: index, max: index }];
|
|
break;
|
|
case this.SAVE_SELECTION_INSERT:
|
|
var min = { }, max = { };
|
|
s.getRangeAt(rc - 1, min, max);
|
|
this._nextSelection = [{ min: max.value, max: max.value }];
|
|
break;
|
|
case this.SAVE_SELECTION_RELOAD:
|
|
this._nextSelection = [];
|
|
for (var i = 0; i < rc; ++i) {
|
|
var min = { }, max = { };
|
|
s.getRangeAt(i, range.min, range.max);
|
|
this._nextSelection.push({ min: range.min, max: range.max });
|
|
}
|
|
break;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="restoreSelection">
|
|
<body><![CDATA[
|
|
for (var i = 0; i < this._nextSelection.length; ++i) {
|
|
var range = this._nextSelection[i];
|
|
if (range.min > -1 && range.max > -1)
|
|
this.view.selection.rangedSelect(range.min, range.max, true);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
</implementation>
|
|
<handlers>
|
|
<handler event="focus"><![CDATA[
|
|
PlacesController.activeView = this;
|
|
document.commandDispatcher.updateCommands("focus");
|
|
]]></handler>
|
|
<handler event="select"><![CDATA[
|
|
document.commandDispatcher.updateCommands("select");
|
|
]]></handler>
|
|
<handler event="draggesture"><![CDATA[
|
|
// XXXben ew.
|
|
if (event.target.localName == "treechildren")
|
|
nsDragAndDrop.startDrag(event, this);
|
|
]]></handler>
|
|
<handler event="dragover"><![CDATA[
|
|
if (event.target.localName == "treechildren")
|
|
nsDragAndDrop.dragOver(event, this);
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
</bindings>
|
|
|