more work on selection saving across view reloading, also as part of 317633 store txmgr on history service for global txn stack. r=brettw for that NPOB

This commit is contained in:
beng%bengoodger.com 2005-12-01 00:09:29 +00:00
parent ca282183d0
commit 86283b16ab
9 changed files with 260 additions and 123 deletions

View File

@ -62,6 +62,10 @@ const TYPE_HTML = "text/html";
// Place entries as raw URL text
const TYPE_UNICODE = "text/unicode";
const RELOAD_ACTION_NOTHING = 0;
const RELOAD_ACTION_INSERT = 1;
const RELOAD_ACTION_REMOVE = 2;
function STACK(args) {
var temp = arguments.callee.caller;
while (temp) {
@ -95,20 +99,6 @@ var PlacesController = {
return ios.newURI(spec, null, null);
},
/**
* The Global Transaction Manager
* XXXben - this needs to move into a service, because it ain't global!
*/
__txmgr: null,
get _txmgr() {
if (!this.__txmgr) {
this.__txmgr =
Cc["@mozilla.org/transactionmanager;1"].
createInstance(Ci.nsITransactionManager);
}
return this.__txmgr;
},
__bms: null,
get _bms() {
if (!this.__bms) {
@ -215,9 +205,6 @@ var PlacesController = {
_canPaste: function PC__canPaste() {
// XXXben: check selection to see if insertion point would suggest pasting
// into an immutable container.
// XXXben: check clipboard for leaf data when pasting into views that can
// only take non-leaf data. This is going to be hard because the
// clipboard apis are teh suck.
return this._hasClipboardData();
},
@ -256,6 +243,10 @@ var PlacesController = {
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
},
/**
* 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.
@ -343,6 +334,16 @@ var PlacesController = {
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)
@ -351,6 +352,13 @@ var PlacesController = {
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");
@ -516,9 +524,12 @@ var PlacesController = {
var value = { value: bundle.getString("newFolderDefault") };
if (ps.prompt(window, title, text, value, null, { })) {
var ip = view.insertionPoint;
this.willReloadView(RELOAD_ACTION_INSERT, this._activeView, ip, 1);
var txn = new PlacesCreateFolderTransaction(value.value, ip.folderId,
ip.index);
this._txmgr.doTransaction(txn);
this._hist.transactionManager.doTransaction(txn);
this.didReloadView(this._activeView);
this._activeView.focus();
}
},
@ -545,8 +556,10 @@ var PlacesController = {
index));
}
}
this.willReloadView(RELOAD_ACTION_REMOVE, this._activeView, null, 0);
var txn = new PlacesAggregateTransaction(txnName || "RemoveItems", txns);
this._txmgr.doTransaction(txn);
this._hist.transactionManager.doTransaction(txn);
this.didReloadView(this._activeView);
},
/**
@ -642,6 +655,51 @@ var PlacesController = {
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 = [];
function createTransactions(folderId, container, index) {
var folderTitle = this._bms.getFolderTitle(folderId);
var createTxn =
new PlacesCreateFolderTransaction(folderTitle, container, index);
transactions.push(createTxn);
// set up a query for the folder's children
var query = hist.getNewQuery();
query.setFolders([folderId], 1);
var queryOptions = hist.getNewQueryOptions();
queryOptions.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER], 1);
// queryOptions.setExpandPlaces(); ?
var kids = hist.executeQuery(query, options);
var cc = kids.childCount;
for (var i = 0; i < cc; ++i) {
var node = kids.getChild(i);
if (this.nodeIsFolder(node))
createTransactions(node.folderId, folderId, i);
else {
var uri = this._uri(node.url);
transactions.push(this._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.
@ -666,7 +724,7 @@ var PlacesController = {
if (data.folderId > 0) {
// Place is a folder.
if (copy)
return PlacesControllerDragHelper._getFolderCopyTransaction(data, container, index);
return this._getFolderCopyTransaction(data, container, index);
return new PlacesMoveFolderTransaction(data.folderId, data.parent,
data.index, container,
index);
@ -831,11 +889,43 @@ var PlacesController = {
transactions.push(this.makeTransaction(data[i], type.value,
ip.folderId, ip.index, true));
var txn = new PlacesAggregateTransaction("Paste", transactions);
this._txmgr.doTransaction(txn);
this._hist.transactionManager.doTransaction(txn);
},
_viewObservers: [],
addViewObserver: function PC_addTransactionObserver(observer) {
for (var i = 0; i < this._viewObservers.length; ++i) {
if (this._viewObservers[i] == observer)
return;
}
this._viewObservers.push(observer);
},
removeViewObserver: function PC_removeTransactionObserver(observer) {
for (var i = 0; i < this._viewObservers.length; ++i) {
if (this._viewObservers[i] == observer)
this._viewObservers.splice(i, 1);
}
},
willReloadView: function PC_willReloadView(action, view, insertionPoint, insertCount) {
for (var i = 0; i < this._viewObservers.length; ++i)
this._viewObservers[i].willReloadView(action, this._activeView,
insertionPoint, insertCount);
},
didReloadView: function PC_didReloadView(view) {
for (var i = 0; i < this._viewObservers.length; ++i)
this._viewObservers[i].didReloadView(view);
},
};
/**
* 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.
@ -859,7 +949,7 @@ var PlacesControllerDragHelper = {
*/
canDrop: function PCDH_canDrop(view, orientation) {
var result = view.getResult();
if (result.readOnly)
if (result.readOnly || !PlacesController.nodeIsFolder(result))
return false;
var session = this._getSession();
@ -876,53 +966,6 @@ var PlacesControllerDragHelper = {
return false;
},
/**
* 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 = [];
function createTransactions(folderId, container, index) {
var bms = PlacesController._bms;
var hist = PlacesController._hist;
var folderTitle = bms.getFolderTitle(folderId);
var createTxn =
new PlacesCreateFolderTransaction(folderTitle, container, index);
transactions.push(createTxn);
// set up a query for the folder's children
var query = hist.getNewQuery();
query.setFolders([folderId], 1);
var queryOptions = hist.getNewQueryOptions();
queryOptions.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER], 1);
// queryOptions.setExpandPlaces(); ?
var kids = hist.executeQuery(query, options);
var cc = kids.childCount;
for (var i = 0; i < cc; ++i) {
var node = kids.getChild(i);
if (this.nodeIsFolder(node))
createTransactions(node.folderId, folderId, i);
else {
var uri = PlacesController._uri(node.url);
transactions.push(this._getItemCopyTransaction(uri, container,
index));
}
}
}
createTransactions(data.folderId, container, index);
return new PlacesAggregateTransaction("FolderCopy", transactions);
},
/**
* Creates a Transeferable object that can be filled with data of types
* supported by a view.
@ -951,14 +994,12 @@ var PlacesControllerDragHelper = {
* Handles the drop of one or more items onto a view.
* @param view
* The AVI-implementing object that received the drop.
* @param container
* The container the drop was into
* @param index
* The index within the container the item was dropped at
* @param insertionPoint
* The insertion point where the items should be dropped
* @param orientation
* The orientation of the drop
*/
onDrop: function PCDH_onDrop(view, container, index, orientation) {
onDrop: function PCDH_onDrop(view, insertionPoint, orientation) {
var session = this._getSession();
if (!session)
return;
@ -977,10 +1018,14 @@ var PlacesControllerDragHelper = {
var unwrapped = PlacesController.unwrapNodes(data.value.data,
flavor.value)[0];
transactions.push(PlacesController.makeTransaction(unwrapped,
flavor.value, container, index, copy));
flavor.value, insertionPoint.folderId,
insertionPoint.index, copy));
}
PlacesController.willReloadView(RELOAD_ACTION_INSERT, view,
insertionPoint, session.numDropItems);
var txn = new PlacesAggregateTransaction("DropItems", transactions);
PlacesController._txmgr.doTransaction(txn);
PlacesController._hist.transactionManager.doTransaction(txn);
PlacesController.didReloadView(view);
}
};

View File

@ -111,6 +111,9 @@ var PlacesPage = {
this._content.supportedDropTypes = [TYPE_X_MOZ_PLACE_CONTAINER,
TYPE_X_MOZ_PLACE, TYPE_X_MOZ_URL];
this._content.supportedDropOnTypes = this._content.supportedDropTypes;
this._places.filterOptions = [Ci.nsINavHistoryQuery.INCLUDE_QUERIES];
this._content.filterOptions = [Ci.nsINavHistoryQuery.INCLUDE_ITEMS +
Ci.nsINavHistoryQuery.INCLUDE_QUERIES];
this._places.firstDropIndex = 2;
this._content.firstDropIndex = 0;
@ -123,8 +126,7 @@ var PlacesPage = {
// Attach the Places model to the Place View
// XXXben - move this to an attribute/property on the tree view
var bms = PlacesController._bms;
this._places.loadFolder(bms.placesRoot,
Ci.nsINavHistoryQuery.INCLUDE_QUERIES);
this._places.loadFolder(bms.placesRoot);
},
uninit: function PP_uninit() {
@ -165,6 +167,8 @@ var PlacesPage = {
*/
placeSelected: function PP_placeSelected(event) {
var node = this._places.selectedNode;
if (!node || this._places.suppressSelection)
return;
var queries = node.getQueries({});
if (PlacesController.nodeIsFolder(node))
this._content.loadFolder(node.folderId);

View File

@ -28,10 +28,14 @@
Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
this._bms.addObserver(this._bookmarkObserver);
PlacesController.addViewObserver(this);
]]></constructor>
<destructor><![CDATA[
this._bms.removeObserver(this._bookmarkObserver);
PlacesController.removeViewObserver(this);
]]></destructor>
<method name="_fireEvent">
@ -123,7 +127,7 @@
this.load([query], this.getBestOptions());
]]></body>
</method>
<property name="queryString">
<getter><![CDATA[
var result = this.getResult();
@ -146,20 +150,17 @@
<method name="loadFolder">
<parameter name="folderId"/>
<parameter name="filterOptions"/>
<body><![CDATA[
var query = this._places.getNewQuery();
query.setFolders([folderId], 1);
if (filterOptions) {
query.itemTypes = filterOptions;
}
query.itemTypes = this.filterOptions;
var options = this._places.getNewQueryOptions();
options.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER], 1);
// options.setExpandPlaces(); ???
var result = this._places.executeQuery(query, options);
result.QueryInterface(Ci.nsITreeView);
this.view = result;
this._lastFilterOptions = filterOptions;
this._fireEvent("reloaded");
]]></body>
</method>
@ -362,8 +363,7 @@
// 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);
PlacesControllerDragHelper.onDrop(this._self, ip.folderId, ip.index,
orientation);
PlacesControllerDragHelper.onDrop(this._self, ip, orientation);
},
_states: { },
@ -382,39 +382,13 @@
onPerformActionOnCell: function VO_onPerformActionOnCell(action, row, column) { },
})]]></field>
<field name="_selection">null</field>
<method name="_saveSelection">
<body><![CDATA[
this._selection = [];
var selection = this.view.selection;
var rc = selection.getRangeCount();
for (var i = 0; i < rc; ++i) {
var min = { }, max = { };
selection.getRangeAt(i, min, max);
this._selection.push({ min: min.value, max: max.value });
}
]]></body>
</method>
<method name="_restoreSelection">
<body><![CDATA[
var selection = this.view.selection;
for (var i = 0; i < this._selection.length; ++i) {
var range = this._selection[i];
selection.rangedSelect(range.min, range.max, true);
}
]]></body>
</method>
<method name="_reloadView">
<body><![CDATA[
var result = this.getResult();
this._saveSelection();
if (PlacesController.nodeIsFolder(result))
this.loadFolder(result.folderId, this._lastFilterOptions);
this.loadFolder(result.folderId);
else
this.load(result.getQueries({ }), result.queryOptions);
this._restoreSelection();
]]></body>
</method>
@ -433,31 +407,112 @@
return false;
},
onItemAdded: function PT_O_onItemAdded(bookmark, folder, index) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
onItemRemoved: function PT_O_onItemRemoved(bookmark, folder, index) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
onItemMoved: function PT_O_onItemMoved(bookmark, folder, oldIndex, newIndex) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
onItemChanged: function PT_O_onItemChanged(bookmark, property) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
onFolderAdded: function PT_O_onFolderAdded(folder, parent, index) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
onFolderRemoved: function PT_O_onFolderRemoved(folder, parent, index) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
onFolderMoved: function PT_O_onFolderMoved(folder, oldParent, oldIndex, newParent, newIndex) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
onFolderChanged: function PT_O_onFolderChanged(folder, property) {
this._self._reloadView();
if (!this._numBatches)
this._self._reloadView();
},
})]]></field>
<field name="suppressSelection">false</field>
<field name="_selection">null</field>
<method name="willReloadView">
<parameter name="action"/>
<parameter name="view"/>
<parameter name="insertionPoint"/>
<parameter name="insertCount"/>
<body><![CDATA[
this._selection = [];
var selection = this.view.selection;
var rc = selection.getRangeCount();
if (view != this)
action = RELOAD_ACTION_NOTHING;
LOG("VIEW = " + view.id + ", THIS = " + this.id + " ACTION: " + action);
switch (action) {
case RELOAD_ACTION_NOTHING:
for (var i = 0; i < rc; ++i) {
var min = { }, max = { };
selection.getRangeAt(i, min, max);
this._selection.push({ min: min.value, max: max.value });
}
break;
case RELOAD_ACTION_INSERT:
// Insert index of insertion and number of rows to insert
var query = this._places.getNewQuery();
query.setFolders([insertionPoint.folderId], 1);
query.itemTypes = this.filterOptions;
var options = this._places.getNewQueryOptions();
options.setGroupingMode([Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER], 1);
var result = this._places.executeQuery(query, options);
if (insertionPoint.index > -1) {
LOG("IP: " + insertionPoint.toSource())
LOG("CC: " + result.childCount + ", result.itemTypes = " + this.filterOptions + " ID : " + this.id);
var node = result.getChild(insertionPoint.index - 1);
var index = this.getResult().treeIndexForNode(node);
if (insertionPoint.index == result.childCount)
++index;
}
else
index = this.getResult().childCount;
var max = index + insertCount - 1;
this._selection = [{ min: index, max: max }];
break;
case RELOAD_ACTION_REMOVE:
min = { }, max = { };
selection.getRangeAt(0, min, max);
this._selection = [{ min: min.value, max: min.value }];
break;
}
//if (this._selection.length)
// LOG("SET UP RANGE: " + this._selection[0].toSource() + " for " + this.id + " action = " + action);
]]></body>
</method>
<method name="didReloadView">
<parameter name="view"/>
<body><![CDATA[
var selection = this.view.selection;
if (view != this.view)
this.suppressSelection = true;
selection.clearSelection();
for (var i = 0; i < this._selection.length; ++i) {
var range = this._selection[i];
//LOG("RESELECTING RANGE: " + range.min + "," + range.max + " in: " + this.id);
selection.rangedSelect(range.min, range.max, true);
}
if (view != this.view)
this.suppressSelection = false;
]]></body>
</method>
</implementation>
<handlers>
<handler event="focus"><![CDATA[

View File

@ -105,7 +105,7 @@
<!ENTITY col.url.label
"Location">
<!ENTITY col.lastvisit.label
"Last Visited">
"Last Visit Date">
<!ENTITY col.visitcount.label
"Visit Count">
<!ENTITY advancedSearch.label

View File

@ -42,6 +42,7 @@
interface nsINavHistoryQuery;
interface nsINavHistoryQueryOptions;
interface nsITransactionManager;
interface nsITreeColumn;
[scriptable, uuid(acae2b2d-5fcd-4419-b1bc-b7dc92a1836c)]
@ -634,4 +635,10 @@ interface nsINavHistory : nsISupports
* done changing. Should match beginUpdateBatch or bad things will happen.
*/
void endUpdateBatch();
/**
* The Transaction Manager for edit operations performed on History and
* Bookmark items.
*/
readonly attribute nsITransactionManager transactionManager;
};

View File

@ -42,6 +42,7 @@
interface nsINavHistoryQuery;
interface nsINavHistoryQueryOptions;
interface nsITransactionManager;
interface nsITreeColumn;
[scriptable, uuid(acae2b2d-5fcd-4419-b1bc-b7dc92a1836c)]
@ -634,4 +635,10 @@ interface nsINavHistory : nsISupports
* done changing. Should match beginUpdateBatch or bad things will happen.
*/
void endUpdateBatch();
/**
* The Transaction Manager for edit operations performed on History and
* Bookmark items.
*/
readonly attribute nsITransactionManager transactionManager;
};

View File

@ -64,6 +64,7 @@ REQUIRES = xpcom \
autocomplete \
storage \
uconv \
txmgr \
$(NULL)
LOCAL_INCLUDES = -I$(srcdir)/../../build

View File

@ -1341,6 +1341,20 @@ nsNavHistory::EndUpdateBatch()
return NS_OK;
}
// nsNavHistory::GetTransactionManager
NS_IMETHODIMP
nsNavHistory::GetTransactionManager(nsITransactionManager** result)
{
if (!mTransactionManager) {
nsresult rv;
mTransactionManager =
do_CreateInstance(NS_TRANSACTIONMANAGER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ADDREF(*result = mTransactionManager);
return NS_OK;
}
// Browser history *************************************************************

View File

@ -63,6 +63,7 @@
#include "nsServiceManagerUtils.h"
#include "nsIStringBundle.h"
#include "nsITimer.h"
#include "nsITransactionManager.h"
#include "nsITreeSelection.h"
#include "nsITreeView.h"
#include "nsString.h"
@ -596,6 +597,9 @@ protected:
nsNavHistoryQueryOptions* aOptions);
nsresult ImportFromMork();
// Transaction Manager
nsCOMPtr<nsITransactionManager> mTransactionManager;
};
/**