gecko/browser/components/places/content/tree.xml
2007-07-21 01:30:07 -07:00

1024 lines
39 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[
this._controller = new PlacesController(this);
this.controllers.appendController(this._controller);
// Force an initial build.
// but do it on setTimeout() for bugs #373719 and bug #373721
// caused by a change for bug #267833
// note the corresponding setTimeout() in PO_init()
if (this.place) {
var self = this;
setTimeout(function() { self.place = self.place; }, 0);
}
]]></constructor>
<destructor><![CDATA[
// Break the treeviewer->result->treeviewer cycle.
// Note: unsetting the result's viewer also unsets
// the viewer's reference to our treeBoxObject.
var result = this.getResult();
if (result)
result.viewer = null;
this.view = null;
]]></destructor>
<property name="controller"
readonly="true"
onget="return this._controller;"/>
<!-- 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;
if (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 = PlacesUtils.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.getResultNode());
var options = queryNode.queryOptions.clone();
if (optionsFilter)
options = optionsFilter.filter(queryNode.getQueries({}), options,
optionsFilter.historyHandler);
var query = PlacesUtils.history.getNewQuery();
query.searchTerms = filterString;
query.onlyBookmarked = onlyBookmarks;
// Remove "group by folder" from the options list, because
// nsNavHistory::RecursiveGroup doesn't support it.
function isFolderGrouping(grouping, index, ary) {
return grouping != Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER;
}
var groupings = options.getGroupingMode({});
var folderGroupings = groupings.filter(isFolderGrouping);
options.setGroupingMode(folderGroupings, folderGroupings.length);
if (onlyBookmarks && folderRestrict)
query.setFolders(folderRestrict, folderRestrict.length);
this.load([query], options);
]]></body>
</method>
<method name="load">
<parameter name="queries"/>
<parameter name="options"/>
<body><![CDATA[
var result = PlacesUtils.history.executeQueries(queries, queries.length,
options);
var treeView = new PlacesTreeView(this.showRoot);
result.viewer = treeView;
this.view = treeView;
]]></body>
</method>
<property name="showRoot">
<getter><![CDATA[
return this.getAttribute("showRoot") == "true";
]]></getter>
<setter><![CDATA[
if (this.showRoot != val) {
this.setAttribute("showRoot", val);
// reload with the last place set
this.place = this.place;
}
return val;
]]></setter>
</property>
<!--
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) {
if (container.uri == placeURI)
return container;
container.containerOpen = true;
for (var i = 0; i < container.childCount; ++i) {
var child = container.getChild(i);
if (child.uri == placeURI)
return child;
else if (PlacesUtils.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>
<!-- nsIPlacesView -->
<method name="getResultNode">
<body><![CDATA[
return this.getResult().root;
]]></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 = { };
PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef);
if (queryCountRef.value == 0)
queriesRef.value = [PlacesUtils.history.getNewQuery()];
if (!optionsRef.value)
optionsRef.value = PlacesUtils.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 (PlacesUtils.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 (PlacesUtils.nodeIsURI(node))
return node;
return null;
]]></getter>
</property>
<!-- nsIPlacesView -->
<property name="insertionPoint">
<getter><![CDATA[
// there is no insertion point for history queries
// so bail out now and save a lot of work when updating commands
var resultNode = this.getResultNode();
if (PlacesUtils.nodeIsQuery(resultNode)) {
var options = asQuery(resultNode).queryOptions;
if (options.queryType == options.QUERY_TYPE_HISTORY)
return null;
}
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.
//
// 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 resultView = this.getResultView();
var selection = resultView.selection;
var rc = selection.getRangeCount();
var min = { }, max = { };
selection.getRangeAt(rc - 1, min, max);
// 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 the sole selection is the bookmarks toolbar folder, we insert
// into it even if it is not opened
if (this.hasSingleSelection && resultView.isContainer(max.value) &&
(resultView.isContainerOpen(max.value) ||
resultView.nodeForTreeIndex(max.value)
.itemId == PlacesUtils.bookmarksRootId))
orientation = NHRVO.DROP_ON;
return this._getInsertionPoint(max.value, orientation);
]]></getter>
</property>
<method name="_disallowInsertion">
<parameter name="aContainer"/>
<body><![CDATA[
// Disallow insertion of items under readonly folders
// Disallow insertion of items under the places root
return (!PlacesUtils.nodeIsFolder(aContainer) ||
PlacesUtils.nodeIsReadOnly(aContainer) ||
aContainer.itemId == PlacesUtils.placesRootId);
]]></body>
</method>
<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 itemId).
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 if (!this._disallowInsertion(lastSelected) &&
lastSelected.containerOpen &&
orientation == NHRVO.DROP_AFTER) {
// If the last selected item is an open container and the user is
// trying to drag into it as a first item, really insert into it.
container = lastSelected;
orientation = NHRVO.DROP_BEFORE;
index = 0;
}
else {
// Use the last-selected node's container unless the root node
// is selected, in which case we use the root node itself as the
// insertion point.
container = lastSelected.parent || container;
// avoid the potentially expensive call to getIndexOfNode()
// if we know this container doesn't allow insertion
if (this._disallowInsertion(container))
return null;
var lsi = PlacesUtils.getIndexOfNode(lastSelected);
index = orientation == NHRVO.DROP_BEFORE ? lsi : lsi + 1;
}
}
if (this._disallowInsertion(container))
return null;
return new InsertionPoint(container.itemId, index, orientation);
]]></body>
</method>
<!-- nsIPlacesView -->
<field name="peerDropTypes">PlacesUtils.GENERIC_VIEW_DROP_TYPES</field>
<!-- nsIPlacesView -->
<field name="childDropTypes">PlacesUtils.GENERIC_VIEW_DROP_TYPES</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) {
// See if node matches an ID we wanted; add to results.
if (node.itemId in indexes) {
nodes[node.itemId] = node;
delete indexes[node.itemId];
if (!len(indexes))
return;
}
// 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 (!PlacesUtils.nodeIsFolder(childNode))
continue;
// See if child matches an ID we wanted; add to results.
if (childNode.itemId in indexes) {
nodes[childNode.itemId] = childNode;
delete indexes[childNode.itemId];
}
// If we are still searching, search down that child's subtree.
if (len(indexes))
findNodes(childNode, 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;
var nodes = this.getSelectionNodes();
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
// Disallow dragging the root node of a tree
var parent = node.parent;
if (!parent)
throw Cr.NS_OK;
// If this node is part of a readonly container (e.g. a livemark) it
// cannot be moved, only copied, so we must change the action used
// by the drag session.
if (PlacesUtils.nodeIsReadOnly(parent)) {
dragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_COPY;
break;
}
}
// 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 = this._controller.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
@returns a nsINavHistoryResultNode that is adjacent to the specified
InsertionPoint
-->
<method name="_getInsertionNode">
<parameter name="insertionPoint"/>
<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(aItemId, aContainer) {
var wasOpen = aContainer.containerOpen;
aContainer.containerOpen = true;
var cc = aContainer.childCount;
var foundNode = null;
for (var i = 0; i < cc; ++i) {
var node = aContainer.getChild(i);
if (PlacesUtils.nodeIsFolder(node)) {
if (node.itemId == aItemId) {
foundNode = node;
break;
}
foundNode = findFolder(aItemId, node);
if (foundNode)
break;
}
}
aContainer.containerOpen = wasOpen;
return foundNode;
}
var folder = null;
var root = asContainer(this.getResultNode());
if (PlacesUtils.nodeIsFolder(root) &&
root.itemId == insertionPoint.itemId)
folder = root;
else
folder = findFolder(insertionPoint.itemId, root);
// Since we find the folder manually, using findFolder instead of
// PlacesUtils.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[
var node = this._getInsertionNode(insertionPoint);
// 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;
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 || PlacesUtils.nodeIsReadOnly(node))
return false;
}
else if (node.parent && PlacesUtils.nodeIsReadOnly(node.parent))
return false;
return PlacesControllerDragHelper.canDrop(this._self, orientation);
},
/**
* @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;
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[
var allowedMaxIndex = this.view.rowCount - 1;
for (var i = 0; i < this._nextSelection.length; ++i) {
var range = this._nextSelection[i];
// make sure the index is not over the end of the view
// this fixes bug 376581
if (range.max > allowedMaxIndex)
range.max = allowedMaxIndex;
if (range.min > allowedMaxIndex)
range.min = allowedMaxIndex;
if (range.min > -1 && range.max > -1)
this.view.selection.rangedSelect(range.min, range.max, true);
}
]]></body>
</method>
<method name="buildContextMenu">
<parameter name="aPopup"/>
<body><![CDATA[
return this.controller.buildContextMenu(aPopup);
]]></body>
</method>
<method name="destroyContextMenu">
<parameter name="aPopup"/>
<body/>
</method>
</implementation>
<handlers>
<handler event="focus"><![CDATA[
// See select handler. We need the sidebar's places commandset to be
// updated as well
document.commandDispatcher.updateCommands("focus");
]]></handler>
<handler event="select"><![CDATA[
// This additional complexity is here for the sidebars
var win = window;
while (true) {
win.document.commandDispatcher.updateCommands("focus");
if (win == window.top)
break;
win = win.parent;
}
]]></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>