From 3780cba74a95f0809a16a6468b52edfe3f13ffbb Mon Sep 17 00:00:00 2001 From: "brettw%gmail.com" Date: Thu, 23 Mar 2006 00:32:09 +0000 Subject: [PATCH] Bug 329546 r=ben@mozilla.org, annie.sullivan Separate out view from history result. git-svn-id: svn://10.0.0.236/trunk@192823 18797224-902f-48f8-a5cc-f745e15eee43 --- .../components/build/nsBrowserCompsCID.h | 6 + mozilla/browser/components/build/nsModule.cpp | 6 + .../components/places/content/tree.xml | 69 +- .../places/public/nsINavHistoryService.idl | 212 +- .../places/src/nsNavHistoryResult.cpp | 3037 +++++++++-------- .../places/src/nsNavHistoryResult.h | 160 +- 6 files changed, 1967 insertions(+), 1523 deletions(-) diff --git a/mozilla/browser/components/build/nsBrowserCompsCID.h b/mozilla/browser/components/build/nsBrowserCompsCID.h index 5e73bda3f99..ddfd0d8364e 100644 --- a/mozilla/browser/components/build/nsBrowserCompsCID.h +++ b/mozilla/browser/components/build/nsBrowserCompsCID.h @@ -88,6 +88,12 @@ #define NS_NAVHISTORYSERVICE_CONTRACTID \ "@mozilla.org/browser/nav-history-service;1" +#define NS_NAVHISTORYRESULTTREEVIEWER_CID \ +{ 0x2ea8966f, 0x0671, 0x4c02, { 0x9c, 0x70, 0x94, 0x59, 0x56, 0xd4, 0x54, 0x34 } } + +#define NS_NAVHISTORYRESULTTREEVIEWER_CONTRACTID \ + "@mozilla.org/browser/nav-history/result-tree-viewer;1" + #define NS_ANNOTATIONSERVICE_CID \ { 0x5e8d4751, 0x1852, 0x434b, { 0xa9, 0x92, 0x2c, 0x6d, 0x2a, 0x25, 0xfa, 0x46 } } diff --git a/mozilla/browser/components/build/nsModule.cpp b/mozilla/browser/components/build/nsModule.cpp index 5ea2896b410..dd33dd35746 100644 --- a/mozilla/browser/components/build/nsModule.cpp +++ b/mozilla/browser/components/build/nsModule.cpp @@ -82,6 +82,7 @@ #ifdef MOZ_PLACES NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNavHistory, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsNavHistoryResultTreeViewer) NS_GENERIC_FACTORY_CONSTRUCTOR(nsAnnoProtocolHandler) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAnnotationService, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNavBookmarks, Init) @@ -152,6 +153,11 @@ static const nsModuleComponentInfo components[] = "@mozilla.org/autocomplete/search;1?name=history", nsNavHistoryConstructor }, + { "History tree view", + NS_NAVHISTORYRESULTTREEVIEWER_CID, + NS_NAVHISTORYRESULTTREEVIEWER_CONTRACTID, + nsNavHistoryResultTreeViewerConstructor }, + { "Page Annotation Service", NS_ANNOTATIONSERVICE_CID, NS_ANNOTATIONSERVICE_CONTRACTID, diff --git a/mozilla/browser/components/places/content/tree.xml b/mozilla/browser/components/places/content/tree.xml index 21799829ffc..3fafd204665 100644 --- a/mozilla/browser/components/places/content/tree.xml +++ b/mozilla/browser/components/places/content/tree.xml @@ -28,12 +28,13 @@ ]]> + val.QueryInterface(Ci.nsINavHistoryResultViewer).addViewObserver( + this._viewObserver, false); + ]]> @@ -92,7 +93,13 @@ options.expandQueries = this.expandQueries; var result = this.history.executeQueries(queries, queries.length, options); - this.view = result.QueryInterface(Ci.nsITreeView); + + 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; ]]> @@ -159,15 +166,15 @@ // Walk the list backwards (opening from the root of the hierarchy) // opening each folder as we go. - var result = this.getResult(); + var resultview = this.getResultView(); var view = this.view; for (var i = parents.length - 1; i >= 0; --i) { - var index = result.treeIndexForNode(parents[i]); + var index = resultview.treeIndexForNode(parents[i]); if (view.isContainer(index) && !view.isContainerOpen(index)) view.toggleOpenState(index); } // Select the specified node... - index = result.treeIndexForNode(node); + index = resultview.treeIndexForNode(node); view.selection.select(index); // ... and ensure it's visible, not scrolled off somewhere. this.treeBoxObject.ensureRowIsVisible(index); @@ -178,7 +185,17 @@ + + + @@ -270,7 +287,7 @@ var selection = this.view.selection; var rc = selection.getRangeCount(); var nodes = []; - var result = this.getResult(); + 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 @@ -285,7 +302,7 @@ if (this.view.isContainer(j)) containers[j] = true; if (!(this.view.getParentIndex(j) in containers)) - range.push(result.nodeForTreeIndex(j)); + range.push(resultview.nodeForTreeIndex(j)); } nodes.push(range); } @@ -324,7 +341,7 @@ var min = { }, max = { }; selection.getRangeAt(0, min, max); - return this.getResult().nodeForTreeIndex(min.value); + return this.getResultView().nodeForTreeIndex(min.value); ]]> @@ -340,7 +357,7 @@ selection.getRangeAt(0, min, max); // only URI nodes should be returned - var node = this.getResult().nodeForTreeIndex(min.value); + var node = this.getResultView().nodeForTreeIndex(min.value); if (PlacesController.nodeIsURI(node)) return node; return null; @@ -364,7 +381,7 @@ // If an item in the static region is selected, insert the new item // before the first drop index. - var node = this.getResult().nodeForTreeIndex(max.value); + var node = this.getResultView().nodeForTreeIndex(max.value); var container = asContainer(this.getResult().root); var cc = container.childCount; for (var i = 0; i < cc; ++i) { @@ -384,14 +401,15 @@ @@ -572,7 +590,7 @@ // "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.getResult().treeIndexForNode(nodes[i]); + var treeIndex = this.getResultView().treeIndexForNode(nodes[i]); if (this.view.getLevel(treeIndex) == 0) { var index = PlacesController.getIndexOfNode(nodes[i]); if (index < this.peerDropIndex) @@ -674,7 +692,7 @@ var node = this._getInsertionNode(insertionPoint, excludeItems); // This is the insertion index of the pivot. if (node) - return this.getResult().treeIndexForNode(node); + return this.getResultView().treeIndexForNode(node); else if (insertionPoint.orientation == NHRVO.DROP_ON) return Ci.nsINavHistoryResult.INDEX_INVISIBLE; return -1; @@ -686,7 +704,8 @@ canDrop: function VO_canDrop(index, orientation) { var result = this._self.getResult(); - var node = index != -1 ? result.nodeForTreeIndex(index) : result.root; + 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 && diff --git a/mozilla/browser/components/places/public/nsINavHistoryService.idl b/mozilla/browser/components/places/public/nsINavHistoryService.idl index c7870b4ba0d..b0c901c251c 100644 --- a/mozilla/browser/components/places/public/nsINavHistoryService.idl +++ b/mozilla/browser/components/places/public/nsINavHistoryService.idl @@ -46,6 +46,7 @@ interface nsINavHistoryQueryResultNode; interface nsINavHistoryFolderResultNode; interface nsINavHistoryQuery; interface nsINavHistoryQueryOptions; +interface nsINavHistoryResult; interface nsITreeColumn; interface nsIWritablePropertyBag; @@ -133,6 +134,13 @@ interface nsINavHistoryResultNode : nsISupports */ readonly attribute PRUint32 indentLevel; + /** + * Value with undefined meaning for use by the view. Its initial value will + * be -1. The tree view uses this value to indicate the row in the tree that + * this node is at. Other views may use this value however they choose. + */ + attribute PRInt32 viewIndex; + /** * You can use this to associate temporary information with the result node. * This property bag is associated with the result node and is not persisted @@ -374,6 +382,8 @@ interface nsINavHistoryFolderResultNode : nsINavHistoryQueryResultNode /** * Allows clients of the HistoryResult to define domain specific handling * of specific nsITreeView methods that the HistoryResult does not implement. + * + * @see nsINavHistoryResult for where this fits in */ [scriptable, uuid(b34d5ca4-ce8e-49a6-9201-a59ae2128d2c)] interface nsINavHistoryResultViewObserver : nsISupports @@ -438,22 +448,127 @@ interface nsINavHistoryResultViewObserver : nsISupports /** - * The result of a history/bookmark query. + * Allows clients to observe what is happening to a result as it updates itself + * according to history and bookmark system events. Register this observer on a + * result using registerView * - * Use the "root" element to access the children of this query. + * @see nsINavHistoryResult for where this fits in */ - -[scriptable, uuid(25b45a94-3323-4c7b-910a-315f2c59bfb4)] -interface nsINavHistoryResult : nsISupports +[scriptable, uuid(11032c23-a9fd-4918-bc8f-004726a748da)] +interface nsINavHistoryResultViewer : nsISupports { /** - * Sorts all nodes recursively by the given parameter, one of - * nsINavHistory.SORT_BY_* This will update the corresponding options for - * this result, so that re-using the current options/queries will always give - * you the current view. + * Called when 'item' is inserted into 'parent' at index 'newIndex'. The item + * previously at index (if any) and everything below it will have been + * shifted down by one. The item may be a container or a leaf. */ - void sortAll(in PRUint32 aSortingMode); + void itemInserted(in nsINavHistoryContainerResultNode parent, + in nsINavHistoryResultNode item, + in PRUint32 newIndex); + /** + * Called whan 'item' is removed from 'parent' at 'oldIndex'. The item may be + * a container or a leaf. This function will be called after the item has + * been removed from its parent list, but before anything else (including + * NULLing out the item's parent) has happened. + */ + void itemRemoved(in nsINavHistoryContainerResultNode parent, + in nsINavHistoryResultNode item, + in PRUint32 oldIndex); + + /** + * Called when an item has been changed and should be repainted. This only + * refers to the specific item. If it is a container, getting this message + * does not imply anything happened to the children. You'll get separate + * messages for those. Also, this may be called for container nodes at times + * when the result thinks it's possible that a twisty mey need to bw redrawn. + */ + void itemChanged(in nsINavHistoryResultNode item); + + /** + * Called when an item is being replaced with another item at the exact + * same position. + */ + void itemReplaced(in nsINavHistoryContainerResultNode parent, + in nsINavHistoryResultNode oldItem, + in nsINavHistoryResultNode newItem, + in PRUint32 index); + + /** + * Called after a container node went from closed to opened. + */ + void containerOpened(in nsINavHistoryContainerResultNode item); + + /** + * Called after a container node went from opened to closed. This will be + * called for the topmost container that is closing, and implies that any + * child containers have closed as well. + */ + void containerClosed(in nsINavHistoryContainerResultNode item); + + /** + * Called when something significant has happened within the container. The + * contents of the container should be re-built. + */ + void invalidateContainer(in nsINavHistoryContainerResultNode item); + + /** + * Called when something significant is changing that requires everything + * to be recomputed. For example, changing sorting or changing collapse + * duplicates can affect every row. + */ + void invalidateAll(); + + /** + * This is called to indicate to the UI that the sort has changed to the + * given mode. For trees, for example, this would update the column headers + * to reflect the sorting. For many other types of views, this won't be + * applicable. + * + * @param sortingMode One of nsINavHistoryQueryOptions.SORT_BY_* that + * indicates the new sorting mode. + * + * This only is expected to update the sorting UI. invalidateAll() will also + * get called if the sorting changes to update everything. + */ + void sortingChanged(in PRUint32 sortingMode); + + /** + * Called by the result when this object is set using + * nsINavHistoryResult.viewer. This will be set to NULL when the result + * is being deallocated. This should not be set by other code. + */ + attribute nsINavHistoryResult result; + + /** + * Adds the specified view observer to the view. In ownsWeak is false, then + * the result will hold a strong reference to the observer. If ownsWeak is + * true, then the observer must implement nsISupportsWeakReference, and the + * result will hold a weak reference to the observer. + */ + void addViewObserver(in nsINavHistoryResultViewObserver observer, + in boolean ownsWeak); + + /** + * Removes the specified view observer from this view. This observer must + * have been previously registered using addViewObserver. + */ + void removeViewObserver(in nsINavHistoryResultViewObserver observer); +}; + + +/** + * A predefined view adaptor for interfacing results with an nsITree. This + * object will remove itself from its associated result when the tree has been + * detached. This prevents circular references. Users should be aware of this, + * if you want to re-use the same viewer, you will need to keep your own + * reference to it and re-initialize it when the tree changes. If you use this + * object, attach it to a result, never attach it to a tree, and forget about + * it, it will leak! + */ +[scriptable, uuid(ecd6ad22-8eb0-4824-8a57-5edad9a08ae6)] +interface nsINavHistoryResultTreeViewer : nsINavHistoryResultViewer +{ /** * Controls whether duplicate adjacent elements are collapsed into a single * item in the tree. This prevents you from seeing multiple entries for @@ -463,36 +578,82 @@ interface nsINavHistoryResult : nsISupports * ever want one entry per site, you should ask for URIs back instead of * visits so it will be more efficient. * Default = true + * + * Changing this value is somewhat heavyweight since it will force a tree + * refresh. */ attribute boolean collapseDuplicates; /** - * This allows you to get at the real node for a given row index in the tree. + * This tells you how many items are in the flattened list of results, i.e. + * how many rows are in this tree right now. The tree adaptor will also + * QI to nsITreeView, and this will be the same as nsITreeView.rowCount. + * This is only valid when a tree is attached, the result will be 0 otherwise. + */ + readonly attribute PRUint32 flatItemCount; + + /** + * This allows you to get at the real node for a given row index. This is + * only valid when a tree is attached. */ nsINavHistoryResultNode nodeForTreeIndex(in PRUint32 aIndex); /** - * Reverse of nodeForTreeIndex, returns the row index for a given result node. + * Reverse of nodeForFlatIndex, returns the row index for a given result node. * Returns INDEX_INVISIBLE if the item is not visible (for example, its - * parent is collapsed). + * parent is collapsed). This is only valid when a tree is attached. The + * the result will always be INDEX_INVISIBLE if not. */ const PRUint32 INDEX_INVISIBLE = 0xffffffff; PRUint32 treeIndexForNode(in nsINavHistoryResultNode aNode); +}; + + +/** + * The result of a history/bookmark query. + * + * Use the "root" element to access the children of this query. + * + * The basic design of the system is a model-view-controller. This result object + * represents the model where the data is stored. External components + * provide the view and controller which define how the data looks and how + * interaction happens. + * +-- nsINavHistoryResultViewObserver + * | + * [RESULT]----->[viewer]----->[controller] + * | + * +-- nsINavHistoryResultViewer + * + * The result indicates to the view when something changes through the + * nsINavHistoryResultViewer interface. Viewers register themselves through + * the nsINavHistoryResult.RegisterViewer. The viewer interface would then tell + * the actual view object what to do. A nsINavHistoryResultTreeViewer is + * provided which provides pre-packaged interaction with a nsITreeBoxObject. + * Other views will need to provide their own viewer interfaces. + * + * The viewer provides notifications to the controller when view events occur + * through the nsINavHistoryResultViewObserver interface. + */ + +[scriptable, uuid(25b45a94-3323-4c7b-910a-315f2c59bfb4)] +interface nsINavHistoryResult : nsISupports +{ + /** + * Sorts all nodes recursively by the given parameter, one of + * nsINavHistoryQueryOptions.SORT_BY_* This will update the corresponding + * options for this result, so that re-using the current options/queries will + * always give you the current view. + */ + void sortAll(in PRUint32 aSortingMode); /** - * Add a Tree Builder Observer to handle Tree View methods that the - * HistoryResult object does not implement. If ownsWeak is false, then - * the result will hold a strong reference to the observer. If ownsWeak - * is true, then the observer must implement nsISupportsWeakReference, and - * the result will hold a weak reference to the observer. + * The viewer for this result (see comment for the class for how these + * objects are related). This may be null, in which case you can still + * manually walk the tree using the root node. When this is non-null, you + * can access the flattened list of items (flatItemCount, nodeForFlatIndex, + * flatIndexForNode). */ - void addObserver(in nsINavHistoryResultViewObserver observer, - in boolean ownsWeak); - - /** - * Remove a tree builder observer. - */ - void removeObserver(in nsINavHistoryResultViewObserver observer); + attribute nsINavHistoryResultViewer viewer; /** * This is the root of the results. It will either be a @@ -504,6 +665,7 @@ interface nsINavHistoryResult : nsISupports readonly attribute nsINavHistoryQueryResultNode root; }; + /** * Similar to nsIRDFObserver for history. Note that we don't pass the data * source since that is always the global history. diff --git a/mozilla/browser/components/places/src/nsNavHistoryResult.cpp b/mozilla/browser/components/places/src/nsNavHistoryResult.cpp index 89d59869189..b18538f79fd 100755 --- a/mozilla/browser/components/places/src/nsNavHistoryResult.cpp +++ b/mozilla/browser/components/places/src/nsNavHistoryResult.cpp @@ -70,6 +70,18 @@ #define ICONURI_QUERY "chrome://browser/skin/places/query.png" +// What we want is: NS_INTERFACE_MAP_ENTRY(self) for static IID accessors, +// but some of our classes (like nsNavHistoryResult) have an ambiguous base +// class of nsISupports which prevents this from working (the default macro +// converts it to nsISupports, then addrefs it, then returns it). Therefore, we +// expand the macro here and change it so that it works. Yuck. +#define NS_INTERFACE_MAP_STATIC_AMBIGUOUS(_class) \ + if (aIID.Equals(NS_GET_IID(_class))) { \ + NS_ADDREF(this); \ + *aInstancePtr = this; \ + return NS_OK; \ + } else + // emulate string comparison (used for sorting) for PRTime and int inline PRInt32 ComparePRTime(PRTime a, PRTime b) { @@ -100,7 +112,7 @@ nsNavHistoryResultNode::nsNavHistoryResultNode( mTime(aTime), mFaviconURI(aIconURI), mIndentLevel(-1), - mVisibleIndex(-1) + mViewIndex(-1) { } @@ -140,7 +152,7 @@ void nsNavHistoryResultNode::OnRemoving() { mParent = nsnull; - mVisibleIndex = -1; + mViewIndex = -1; } @@ -242,9 +254,13 @@ nsNavHistoryFullVisitResultNode::nsNavHistoryFullVisitResultNode( // nsNavHistoryContainerResultNode ********************************************* -NS_IMPL_ISUPPORTS_INHERITED1(nsNavHistoryContainerResultNode, - nsNavHistoryResultNode, - nsINavHistoryContainerResultNode) +NS_IMPL_ADDREF_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode) +NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode) + +NS_INTERFACE_MAP_BEGIN(nsNavHistoryContainerResultNode) + NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode) + NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode) +NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode) nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode( const nsACString& aURI, const nsACString& aTitle, @@ -277,7 +293,7 @@ nsNavHistoryContainerResultNode::OnRemoving() // nsNavHistoryContainerResultNode::AreChildrenVisible // -// Folders can't always depend on their mVisibleIndex value to determine if +// Folders can't always depend on their mViewIndex value to determine if // their children are visible because they can be root nodes. Root nodes // are visible if a tree is attached to the result. @@ -289,7 +305,7 @@ nsNavHistoryContainerResultNode::AreChildrenVisible() return PR_FALSE; // easy case, the node itself is visible - if (mVisibleIndex >= 0) + if (mViewIndex >= 0) return PR_TRUE; nsNavHistoryResult* result = GetResult(); @@ -297,7 +313,7 @@ nsNavHistoryContainerResultNode::AreChildrenVisible() NS_NOTREACHED("Invalid result"); return PR_FALSE; } - if (result->mRootNode == this && result->mTree) + if (result->mRootNode == this && result->mView) return PR_TRUE; return PR_FALSE; } @@ -354,8 +370,8 @@ nsNavHistoryContainerResultNode::OpenContainer() nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - rv = result->RefreshVisibleSection(this); - NS_ENSURE_SUCCESS(rv, rv); + if (result->GetView()) + result->GetView()->ContainerOpened(this); return NS_OK; } @@ -368,7 +384,7 @@ nsNavHistoryContainerResultNode::OpenContainer() // for its entire subtree. nsresult -nsNavHistoryContainerResultNode::CloseContainer(PRBool aUpdateVisible) +nsNavHistoryContainerResultNode::CloseContainer(PRBool aUpdateView) { NS_ASSERTION(mExpanded, "Container must be expanded to close it"); @@ -388,11 +404,11 @@ nsNavHistoryContainerResultNode::CloseContainer(PRBool aUpdateVisible) remote->OnContainerClosed(this); } - if (aUpdateVisible) { + if (aUpdateView) { nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - rv = result->RefreshVisibleSection(this); - NS_ENSURE_SUCCESS(rv, rv); + if (result->GetView()) + result->GetView()->ContainerClosed(this); } return NS_OK; } @@ -488,8 +504,11 @@ nsNavHistoryContainerResultNode::ReverseUpdateStats(PRInt32 aAccessCountChange) } if (! resorted) { // repaint visible rows - if (mParent->mVisibleIndex >= 0 && mResult->mTree) - mResult->RowChanged(mParent->mVisibleIndex); + nsNavHistoryResult* result = GetResult(); + if (result && result->GetView()) { + result->GetView()->ItemChanged(NS_STATIC_CAST( + nsINavHistoryContainerResultNode*, mParent)); + } } mParent->ReverseUpdateStats(aAccessCountChange); @@ -869,7 +888,7 @@ nsNavHistoryContainerResultNode::InsertChildAt(nsNavHistoryResultNode* aNode, nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - aNode->mVisibleIndex = -1; + aNode->mViewIndex = -1; aNode->mParent = this; aNode->mIndentLevel = mIndentLevel + 1; if (! aIsTemporary && aNode->IsContainer()) { @@ -887,53 +906,17 @@ nsNavHistoryContainerResultNode::InsertChildAt(nsNavHistoryResultNode* aNode, mAccessCount += aNode->mAccessCount; if (mTime < aNode->mTime) mTime = aNode->mTime; - if (mVisibleIndex >= 0) - result->RowChanged(mVisibleIndex); + if (result->GetView()) + result->GetView()->ItemChanged( + NS_STATIC_CAST(nsINavHistoryContainerResultNode*, this)); ReverseUpdateStats(aNode->mAccessCount); } // Update tree if we are visible. Note that we could be here and not expanded, // like when there is a bookmark folder being updated because its parent is // visible. - if (AreChildrenVisible()) { - // this node will take the visible index of the element that used to be at - // its location - PRInt32 nodeVisibleIndex = -1; - - if (aIndex == 0) { - // item is the first thing in our child list, it takes our index +1. Note - // that this computation still works if we are an invisible root node, - // because our index + 1 = -1 + 1 = 0 - nodeVisibleIndex = mVisibleIndex + 1; - } else { - // Here, we try to find the next visible element in the child list so we - // can set the new visible index to be right before that. Note that we - // have to search DOWN instead of up, because some siblings could have - // children themselves that would be in the way. - for (PRInt32 i = aIndex + 1; i < mChildren.Count(); i ++) { - if (mChildren[i]->mVisibleIndex >= 0) { - // the visible indices of subsequent children have not been shiftedn - // so the next item will have what should be our index - nodeVisibleIndex = mChildren[i]->mVisibleIndex; - break; - } - } - if (nodeVisibleIndex < 0) { - // At the end of our child list without finding a visible sibling: This - // is a little harder because we don't know how many rows the last item - // in our list takes up (it could be a container with many children). - PRUint32 lastRowCount = - result->CountVisibleRowsForItem(mChildren[aIndex - 1]); - nodeVisibleIndex = mChildren[aIndex - 1]->mVisibleIndex + lastRowCount; - } - } - - // this will update the node's visible index - result->RowAdded(nodeVisibleIndex, aNode); - if (aNode->IsContainer() && aNode->GetAsContainer()->mExpanded) - result->RefreshVisibleSection(aNode->GetAsContainer()); - } - + if (mExpanded && result->GetView()) + result->GetView()->ItemInserted(this, aNode, aIndex); return NS_OK; } @@ -1058,17 +1041,21 @@ nsNavHistoryContainerResultNode::ReplaceChildURIAt(PRUint32 aIndex, ReverseUpdateStats(accessCountChange); } - // clear out old child and replace - PRInt32 visibleIndex = mChildren[aIndex]->mVisibleIndex; - mChildren[aIndex]->OnRemoving(); + // Hold a reference so it doesn't go away as soon as we remove it from the + // array. This needs to be passed to the view. + nsCOMPtr oldItem = mChildren[aIndex]; + + // actually replace if (! mChildren.ReplaceObjectAt(aNode, aIndex)) return NS_ERROR_FAILURE; - // update screen + // update view nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - if (mVisibleIndex >= 0) - result->RowReplaced(visibleIndex, aNode); + if (result->GetView()) + result->GetView()->ItemReplaced(this, oldItem, aNode, aIndex); + + mChildren[aIndex]->OnRemoving(); return NS_OK; } @@ -1093,23 +1080,8 @@ nsNavHistoryContainerResultNode::RemoveChildAt(PRInt32 aIndex, nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - PRBool isContainer = mChildren[aIndex]->IsContainer(); - PRBool isContainerOpen = PR_FALSE; - PRInt32 visibleRows; - if (AreChildrenVisible()) { - if (isContainer) { - isContainerOpen = mChildren[aIndex]->GetAsContainer()->mExpanded; - if (isContainerOpen) - visibleRows = result->CountVisibleRowsForItem(mChildren[aIndex]); - else // visible but closed containers take one row (their own) - visibleRows = 1; - } else { - visibleRows = 1; - } - } else { - visibleRows = 0; - } - PRInt32 childVisibleIndex = mChildren[aIndex]->mVisibleIndex; + // hold an owning reference to keep from expiring while we work with it + nsCOMPtr oldNode = mChildren[aIndex]; // stats PRUint32 oldAccessCount = 0; @@ -1117,21 +1089,16 @@ nsNavHistoryContainerResultNode::RemoveChildAt(PRInt32 aIndex, oldAccessCount = mAccessCount; mAccessCount -= mChildren[aIndex]->mAccessCount; NS_ASSERTION(mAccessCount >= 0, "Invalid access count while updating!"); - - mChildren[aIndex]->OnRemoving(); } // remove from our list and notify the tree mChildren.RemoveObjectAt(aIndex); - if (visibleRows && childVisibleIndex >= 0) - result->RowsRemoved(childVisibleIndex, visibleRows); + if (result->GetView()) + result->GetView()->ItemRemoved(this, oldNode, aIndex); if (! aIsTemporary) { ReverseUpdateStats(mAccessCount - oldAccessCount); - - // update our row (the twisty might have changed) - if (mVisibleIndex >= 0) - result->RowChanged(mVisibleIndex); + oldNode->OnRemoving(); } return NS_OK; } @@ -1255,23 +1222,24 @@ nsNavHistoryContainerResultNode::UpdateURIs(PRBool aRecursive, PRBool aOnlyOne, parent->mAccessCount += node->mAccessCount - oldAccessCount; if (node->mTime > parent->mTime) parent->mTime = node->mTime; - if (parent->mVisibleIndex >= 0) - result->RowChanged(parent->mVisibleIndex); + if (result->GetView()) + result->GetView()->ItemChanged( + NS_STATIC_CAST(nsINavHistoryContainerResultNode*, parent)); parent->ReverseUpdateStats(node->mAccessCount - oldAccessCount); } if (aUpdateSort) { PRInt32 childIndex = parent->FindChild(node); - if (childIndex >= 0 && DoesChildNeedResorting(childIndex, comparator)) { + if (childIndex >= 0 && parent->DoesChildNeedResorting(childIndex, comparator)) { // child position changed parent->RemoveChildAt(childIndex, PR_TRUE); parent->InsertChildAt(node, parent->FindInsertionPoint(node, comparator), PR_TRUE); - } else if (node->mVisibleIndex >= 0) { - result->RowChanged(node->mVisibleIndex); + } else if (result->GetView()) { + result->GetView()->ItemChanged(node); } - } else if (node->mVisibleIndex >= 0) { - result->RowChanged(node->mVisibleIndex); + } else if (result->GetView()) { + result->GetView()->ItemChanged(node); } } } @@ -1693,7 +1661,10 @@ nsNavHistoryQueryResultNode::OpenContainer() nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - return result->RefreshVisibleSection(this); + if (result->GetView()) + result->GetView()->ContainerOpened( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, this)); + return NS_OK; } @@ -1918,7 +1889,10 @@ nsNavHistoryQueryResultNode::Refresh() nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - return result->RefreshVisibleSection(this); + if (result->GetView()) + return result->GetView()->InvalidateContainer( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, this)); + return NS_OK; } @@ -2452,7 +2426,10 @@ nsNavHistoryFolderResultNode::OpenContainer() mExpanded = PR_TRUE; nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - return result->RefreshVisibleSection(this); + if (result->GetView()) + result->GetView()->ContainerOpened( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, this)); + return NS_OK; } @@ -2647,7 +2624,10 @@ nsNavHistoryFolderResultNode::Refresh() nsNavHistoryResult* result = GetResult(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - return result->RefreshVisibleSection(this); + if (result->GetView()) + return result->GetView()->InvalidateContainer( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, this)); + return NS_OK; } @@ -2674,7 +2654,7 @@ nsNavHistoryFolderResultNode::StartIncrementalUpdate() // when a tree is attached also do incremental updates if our parent is // visible so that twisties are drawn correctly. - if (result->mTree && mParent && mParent->mVisibleIndex >= 0) + if (mParent && result->GetView()) return PR_TRUE; } @@ -2786,12 +2766,11 @@ nsNavHistoryFolderResultNode::OnItemMoved(nsIURI* aBookmark, PRInt64 aFolder, // Just refresh the entire container: a lot of stuff (possibly even open // containers) may have moved, and we don't want to handle all the cases // here in this uncommon and non-time-critical update. - if (mVisibleIndex >= 0) { - nsNavHistoryResult* result = GetResult(); - NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - nsresult rv = result->RefreshVisibleSection(this); - NS_ENSURE_SUCCESS(rv, rv); - } + nsNavHistoryResult* result = GetResult(); + NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); + if (result->GetView()) + result->GetView()->InvalidateContainer( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, this)); return NS_OK; } @@ -2833,11 +2812,10 @@ nsNavHistoryFolderResultNode::OnItemChanged(nsIURI* aBookmark, NS_NOTREACHED("Unknown bookmark property changing."); } - if (mVisibleIndex >= 0) { - nsNavHistoryResult* result = GetResult(); - NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - result->RowChanged(node->mVisibleIndex); - } + nsNavHistoryResult* result = GetResult(); + NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); + if (result->GetView()) + result->GetView()->ItemChanged(node); return NS_OK; } @@ -2888,9 +2866,9 @@ nsNavHistoryFolderResultNode::OnItemVisited(nsIURI* aBookmark, PRInt64 aVisitId, RemoveChildAt(childIndex, PR_TRUE); InsertChildAt(node, FindInsertionPoint(node, comparator), PR_TRUE); } - } else if (mVisibleIndex >= 0) { + } else if (result->GetView()) { // no sorting changed, just redraw the row if visible - result->RowChanged(node->mVisibleIndex); + result->GetView()->ItemChanged(node); } return NS_OK; } @@ -3003,6 +2981,10 @@ nsNavHistoryFolderResultNode::OnFolderMoved(PRInt64 aFolder, PRInt64 aOldParent, // nsNavHistoryFolderResultNode::OnFolderChanged (nsINavBookmarkObserver) +// +// Unlike some of the other notifications that are sent to the parent of +// the item that's changing, this one is sent to the folder that's +// changing. NS_IMETHODIMP nsNavHistoryFolderResultNode::OnFolderChanged(PRInt64 aFolder, @@ -3011,27 +2993,34 @@ nsNavHistoryFolderResultNode::OnFolderChanged(PRInt64 aFolder, if (! StartIncrementalUpdate()) return NS_OK; - // find this folder in our list - PRUint32 folderIndex; - nsNavHistoryFolderResultNode* folderNode = FindChildFolder(aFolder, &folderIndex); - if (! folderNode) { - NS_NOTREACHED("Folder change message for a folder we don't have"); - return NS_ERROR_FAILURE; - } - if (property.EqualsLiteral("title")) { - folderNode->mTitle = property; + mTitle = property; + PRInt32 sortType = GetSortType(); + if ((sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING || + sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING) && + mParent) { + PRInt32 ourIndex = mParent->FindChild(this); + SortComparator comparator = GetSortingComparator(sortType); + if (mParent->DoesChildNeedResorting(ourIndex, comparator)) { + // needs resorting, this will cause everything to be redrawn, so we + // don't need to do that explicitly later. + mParent->RemoveChildAt(ourIndex, PR_TRUE); + mParent->InsertChildAt(this, mParent->FindInsertionPoint(this, comparator), + PR_TRUE); + return NS_OK; + } + } } else { NS_NOTREACHED("Unknown folder change event"); return NS_ERROR_FAILURE; } // update folder if visible - if (folderNode->mVisibleIndex >= 0) { - nsNavHistoryResult* result = GetResult(); - NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); - result->RowChanged(folderNode->mVisibleIndex); - } + nsNavHistoryResult* result = GetResult(); + NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); + if (result->GetView()) + result->GetView()->ItemChanged( + NS_STATIC_CAST(nsNavHistoryResultNode*, this)); return NS_OK; } @@ -3124,17 +3113,20 @@ nsNavHistorySeparatorResultNode::nsNavHistorySeparatorResultNode() // nsNavHistoryResult ********************************************************** -NS_IMPL_ISUPPORTS5(nsNavHistoryResult, - nsINavHistoryResult, - nsITreeView, - nsINavBookmarkObserver, - nsINavHistoryObserver, - nsISupportsWeakReference) +NS_IMPL_ADDREF(nsNavHistoryResult) +NS_IMPL_RELEASE(nsNavHistoryResult) + +NS_INTERFACE_MAP_BEGIN(nsNavHistoryResult) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResult) + NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryResult) + NS_INTERFACE_MAP_ENTRY(nsINavHistoryResult) + NS_INTERFACE_MAP_ENTRY(nsINavBookmarkObserver) + NS_INTERFACE_MAP_ENTRY(nsINavHistoryObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END nsNavHistoryResult::nsNavHistoryResult(nsNavHistoryContainerResultNode* aRoot) : mRootNode(aRoot), - mCollapseDuplicates(PR_TRUE), - mShowSessions(PR_FALSE), mIsHistoryObserver(PR_FALSE), mIsBookmarksObserver(PR_FALSE) { @@ -3191,7 +3183,6 @@ nsNavHistoryResult::Init(nsINavHistoryQuery** aQueries, NS_ASSERTION(mRootNode->mIndentLevel == -1, "Root node's indent level initialized wrong"); mRootNode->FillStats(); - ComputeShowSessions(); return NS_OK; } @@ -3222,34 +3213,6 @@ nsNavHistoryResult::NewHistoryResult(nsINavHistoryQuery** aQueries, } -// nsNavHistoryResult::ComputeShowSessions -// -// Computes the value of mShowSessions, which is used to see if we should -// try to set styles for session groupings. We only want to do session -// grouping if we're in an appropriate view, which is view by visit and -// sorted by date. - -void -nsNavHistoryResult::ComputeShowSessions() -{ - mShowSessions = PR_FALSE; - NS_ASSERTION(mOptions, "navHistoryResults must have valid options"); - if (mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_VISIT && - mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_FULL_VISIT) - return; // not visits - if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING && - mSortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) - return; // not date sorting - - PRUint32 groupCount; - mOptions->GroupingMode(&groupCount); - if (groupCount > 0) - return; - - mShowSessions = PR_TRUE; -} - - // nsNavHistoryResult::PropertyBagFor // // Given a pointer to a result node, this will give you the property bag @@ -3284,184 +3247,6 @@ nsNavHistoryResult::PropertyBagFor(nsISupports* aObject, } -// nsNavHistoryResult::BuildVisibleList -// -// Call to completely rebuild the list of visible items. Note if there is no -// tree or root this will just clear out the list, so you can also call this -// when a tree is detached to clear the list. -// -// This does NOT update the screen. - -nsresult -nsNavHistoryResult::BuildVisibleList() -{ - // any current visible elements need to be marked as invisible - for (PRUint32 i = 0; i < mVisibleElements.Length(); i ++) - mVisibleElements[i]->mVisibleIndex = -1; - mVisibleElements.Clear(); - - if (! mRootNode || ! mTree) - return NS_OK; - - return BuildVisibleSection(mRootNode, &mVisibleElements, 0); -} - - -// nsNavHistoryResult::BuildVisibleSection -// -// This takes a container and recursively appends visible elements to the -// given array. This is used to build the visible element list (with -// mVisibleElements passed as the array), or portions thereof (with a -// separate array that is merged with the main list later. -// -// aVisibleStartIndex is the visible index of the beginning of the 'aVisible' -// array. When aVisible is mVisibleElements, this is 0. This is non-zero -// when we are building up a sub-region for insertion. Then, this is the -// index where the new array will be inserted into mVisibleElements. It is -// used to compute each node's mVisibleIndex. - -nsresult -nsNavHistoryResult::BuildVisibleSection( - nsNavHistoryContainerResultNode* aContainer, - VisibleList* aVisible, - PRUint32 aVisibleStartIndex) -{ - if (! aContainer->mExpanded) - return NS_OK; // nothing to do - for (PRInt32 i = 0; i < aContainer->mChildren.Count(); i ++) { - nsNavHistoryResultNode* cur = aContainer->mChildren[i]; - - // collapse all duplicates starting from here - if (mCollapseDuplicates) { - PRUint32 showThis; - while (i < aContainer->mChildren.Count() - 1 && - CanCollapseDuplicates(cur, aContainer->mChildren[i+1], &showThis)) { - if (showThis) { - // collapse the first and use the second - cur->mVisibleIndex = -1; - cur = aContainer->mChildren[i + 1]; - } else { - // collapse the second and use the first - aContainer->mChildren[i + 1]->mVisibleIndex = -1; - } - i ++; - } - } - - // add item - cur->mVisibleIndex = aVisibleStartIndex + aVisible->Length(); - if (! aVisible->AppendElement(cur)) - return NS_ERROR_OUT_OF_MEMORY; - - // recursively do containers - if (cur->IsContainer()) { - nsNavHistoryContainerResultNode* curContainer = cur->GetAsContainer(); - if (curContainer->mExpanded && curContainer->mChildren.Count() > 0) { - nsresult rv = BuildVisibleSection(curContainer, aVisible, - aVisibleStartIndex); - NS_ENSURE_SUCCESS(rv, rv); - } - } - } - return NS_OK; -} - - -// nsNavHistoryResult::CountVisibleRowsForItem -// -// This counts how many rows an item takes in the tree, that is, the item -// itself plus any nodes following it with an increased indent. This allows -// you to figure out how many rows an item (=1) or a container with all of -// its children takes. - -PRUint32 -nsNavHistoryResult::CountVisibleRowsForItem(nsNavHistoryResultNode* aNode) -{ - NS_ASSERTION(aNode->mVisibleIndex >= 0, "Item is not visible, no rows to count"); - PRInt32 outerLevel = aNode->mIndentLevel; - for (PRUint32 i = aNode->mVisibleIndex + 1; i < mVisibleElements.Length(); i ++) { - if (mVisibleElements[i]->mIndentLevel <= outerLevel) - return i - aNode->mVisibleIndex; - } - // this node plus its children occupy the bottom of the list - return mVisibleElements.Length() - aNode->mVisibleIndex; -} - - -// nsNavHistoryResult::RefreshVisibleSection -// -// This is called by containers when they change and we need to update -// everything about the container. We build a new visible section with the -// container as a separate object so we first know how the list changes. This -// way we only have to do one realloc/memcpy to update the list. -// -// We also try to be smart here about redrawing the screen. - -nsresult -nsNavHistoryResult::RefreshVisibleSection( - nsNavHistoryContainerResultNode* aContainer) -{ - // don't update if there is no tree - if (! mTree) - return NS_OK; - - // The root node is invisible, so we can't check its index and indent level. - // Therefore, special case it. This is easy because we just rebuild everything - if (aContainer == mRootNode) - return BuildVisibleList(); - - // When the container is not the root, it better be visible if we are updating - NS_ASSERTION(aContainer->mVisibleIndex >= 0 && - aContainer->mVisibleIndex < PRInt32(mVisibleElements.Length()), - "Trying to expand a node that is not visible"); - NS_ASSERTION(mVisibleElements[aContainer->mVisibleIndex] == aContainer, - "Visible index is out of sync!"); - - PRUint32 startReplacement = aContainer->mVisibleIndex + 1; - PRUint32 replaceCount = CountVisibleRowsForItem(aContainer) - 1; - - // Mark the removees as invisible - PRUint32 i; - for (i = startReplacement; i < replaceCount; i ++) - mVisibleElements[startReplacement + i]->mVisibleIndex = -1; - - // Building the new list will set the new elements' visible indices. - VisibleList newElements; - nsresult rv = BuildVisibleSection(aContainer, &newElements, startReplacement); - NS_ENSURE_SUCCESS(rv, rv); - - // actually update the visible list - if (! mVisibleElements.ReplaceElementsAt(aContainer->mVisibleIndex + 1, - replaceCount, newElements.Elements(), newElements.Length())) - return NS_ERROR_OUT_OF_MEMORY; - - if (replaceCount == newElements.Length()) { - // length is the same, just repaint the changed area - if (replaceCount > 0) - mTree->InvalidateRange(startReplacement, startReplacement + replaceCount - 1); - } else { - // If the new area is a different size, we'll have to renumber the elements - // following the area. - for (i = startReplacement + newElements.Length(); - i < mVisibleElements.Length(); i ++) - mVisibleElements[i]->mVisibleIndex = i; - - // repaint the region that stayed the same, also including the container - // element itself since its twisty could have changed. - PRUint32 minLength; - if (replaceCount > newElements.Length()) - minLength = newElements.Length(); - else - minLength = replaceCount; - mTree->InvalidateRange(startReplacement - 1, - startReplacement + minLength - 1); - - // now update the number of elements - mTree->RowCountChanged(startReplacement + minLength, - newElements.Length() - replaceCount); - } - return NS_OK; -} // nsNavHistoryResult::AddEverythingObserver @@ -3541,245 +3326,6 @@ nsNavHistoryResult::BookmarkObserversForId(PRInt64 aFolderId, PRBool aCreate) } -// nsNavHistoryResultNode::CanCollapseDuplicates -// -// This returns true if the two results can be collapsed as duplicates. -// *aShowThisOne will be either 0 or 1, indicating which of the duplicates -// should be shown. - -PRBool -nsNavHistoryResult::CanCollapseDuplicates(nsNavHistoryResultNode* aTop, - nsNavHistoryResultNode* aNext, - PRUint32* aShowThisOne) -{ - if (! mCollapseDuplicates) - return PR_FALSE; - if (! aTop->IsVisit() || ! aNext->IsVisit()) - return PR_FALSE; // only collapse two visits - - nsNavHistoryVisitResultNode* topVisit = aTop->GetAsVisit(); - nsNavHistoryVisitResultNode* nextVisit = aNext->GetAsVisit(); - - if (! topVisit->mURI.Equals(nextVisit->mURI)) - return PR_FALSE; // don't collapse nonmatching URIs - - // now we know we want to collapse, show the one with the more recent time - *aShowThisOne = topVisit->mTime < nextVisit->mTime; - return PR_TRUE; -} - - -// nsNavHistoryResult::GetColumnType - -nsNavHistoryResult::ColumnType -nsNavHistoryResult::GetColumnType(nsITreeColumn* col) -{ - const PRUnichar* idConst; - col->GetIdConst(&idConst); - switch(idConst[0]) { - case PRUnichar('t'): - return Column_Title; - case PRUnichar('u'): - return Column_URI; - case PRUnichar('d'): - return Column_Date; - case PRUnichar('v'): - return Column_VisitCount; - default: - return Column_Unknown; - } -} - - -// nsNavHistoryResult::SortTypeToColumnType -// -// Places whether it's ascending or descending into the given boolean buffer - -nsNavHistoryResult::ColumnType -nsNavHistoryResult::SortTypeToColumnType(PRUint32 aSortType, - PRBool* aDescending) -{ - switch(aSortType) { - case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING: - *aDescending = PR_FALSE; - return Column_Title; - case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING: - *aDescending = PR_TRUE; - return Column_Title; - case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING: - *aDescending = PR_FALSE; - return Column_Date; - case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING: - *aDescending = PR_TRUE; - return Column_Date; - case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING: - *aDescending = PR_FALSE; - return Column_URI; - case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING: - *aDescending = PR_TRUE; - return Column_URI; - case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING: - *aDescending = PR_FALSE; - return Column_VisitCount; - case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING: - *aDescending = PR_TRUE; - return Column_VisitCount; - default: - *aDescending = PR_FALSE; - return Column_Unknown; - } -} - - -// nsNavHistoryResult::SetTreeSortingIndicator -// -// This is called to assign the correct arrow to the column header. It is -// called both when you resort, and when a tree is first attached (to -// initialize its headers). - -void -nsNavHistoryResult::SetTreeSortingIndicator() -{ - NS_ASSERTION(mTree, "should only be called if we have a tree"); - - nsCOMPtr columns; - nsresult rv = mTree->GetColumns(getter_AddRefs(columns)); - if (NS_FAILED(rv)) return; - - NS_NAMED_LITERAL_STRING(sortDirectionKey, "sortDirection"); - - // clear old sorting indicator. Watch out: GetSortedColumn returns NS_OK - // but null element if there is no sorted element - nsCOMPtr column; - rv = columns->GetSortedColumn(getter_AddRefs(column)); - if (NS_FAILED(rv)) return; - if (column) { - nsCOMPtr element; - rv = column->GetElement(getter_AddRefs(element)); - if (NS_FAILED(rv)) return; - element->SetAttribute(sortDirectionKey, EmptyString()); - } - - // set new sorting indicator by looking through all columns for ours - NS_ASSERTION(mOptions, "Options should always be present for a root query"); - if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE) - return; - PRBool desiredIsDescending; - ColumnType desiredColumn = SortTypeToColumnType(mSortingMode, - &desiredIsDescending); - PRInt32 colCount; - rv = columns->GetCount(&colCount); - if (NS_FAILED(rv)) return; - for (PRInt32 i = 0; i < colCount; i ++) { - columns->GetColumnAt(i, getter_AddRefs(column)); - if (NS_FAILED(rv)) return; - if (GetColumnType(column) == desiredColumn) { - // found our desired one, set - nsCOMPtr element; - rv = column->GetElement(getter_AddRefs(element)); - if (NS_FAILED(rv)) return; - if (desiredIsDescending) - element->SetAttribute(sortDirectionKey, NS_LITERAL_STRING("descending")); - else - element->SetAttribute(sortDirectionKey, NS_LITERAL_STRING("ascending")); - break; - } - } -} - - -// nsNavHistoryResult::InvalidateTree -// -// Call to invalidate all rows on the tree - -void -nsNavHistoryResult::InvalidateTree() -{ - if (! mTree) - return; - mTree->Invalidate(); -} - - -// nsNavHistoryResult::FormatFriendlyTime - -nsresult -nsNavHistoryResult::FormatFriendlyTime(PRTime aTime, nsAString& aResult) -{ - // use navHistory's GetNow function for performance - nsNavHistory* history = nsNavHistory::GetHistoryService(); - NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); - PRTime now = history->GetNow(); - - const PRInt64 ago = now - aTime; - - /* - * This code generates strings like "X minutes ago" when the time is very - * recent. This looks much nicer, but we will have to redraw the list - * periodically. I also found it a little strange watching the numbers in - * the list change as I browse. For now, I'll leave this commented out, - * perhaps we can revisit whether it is a good idea to do this and put in - * the proper auto-refresh code so the numbers stay correct. - * - * To enable, you'll need to put these in places.properties: - * 0MinutesAgo=<1 minute ago - * 1MinutesAgo=1 minute ago - * XMinutesAgo=%s minutes ago - - static const PRInt64 minuteThreshold = (PRInt64)1000000 * 60 * 60; - if (ago > -10000000 && ago < minuteThreshold) { - // show "X minutes ago" - PRInt64 minutesAgo = NSToIntFloor( - NS_STATIC_CAST(float, (ago / 1000000)) / 60.0); - - nsXPIDLString resultString; - if (minutesAgo == 0) { - nsresult rv = mBundle->GetStringFromName( - NS_LITERAL_STRING("0MinutesAgo").get(), getter_Copies(resultString)); - NS_ENSURE_SUCCESS(rv, rv); - } else if (minutesAgo == 1) { - nsresult rv = mBundle->GetStringFromName( - NS_LITERAL_STRING("1MinutesAgo").get(), getter_Copies(resultString)); - NS_ENSURE_SUCCESS(rv, rv); - } else { - nsAutoString minutesAgoString; - minutesAgoString.AppendInt(minutesAgo); - const PRUnichar* stringPtr = minutesAgoString.get(); - nsresult rv = mBundle->FormatStringFromName( - NS_LITERAL_STRING("XMinutesAgo").get(), - &stringPtr, 1, getter_Copies(resultString)); - NS_ENSURE_SUCCESS(rv, rv); - } - aResult = resultString; - return NS_OK; - } */ - - // Check if it is today and only display the time. Only bother checking for - // today if it's within the last 24 hours, since computing midnight is not - // really cheap. Sometimes we may get dates in the future, so always show - // those. Note the 10s slop in case the cached GetNow value in NavHistory is - // out-of-date. - nsDateFormatSelector dateFormat = nsIScriptableDateFormat::dateFormatShort; - if (ago > -10000000 && ago < (PRInt64)1000000 * 24 * 60 * 60) { - PRExplodedTime explodedTime; - PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime); - explodedTime.tm_min = - explodedTime.tm_hour = - explodedTime.tm_sec = - explodedTime.tm_usec = 0; - PRTime midnight = PR_ImplodeTime(&explodedTime); - if (aTime > midnight) - dateFormat = nsIScriptableDateFormat::dateFormatNone; - } - nsAutoString resultString; - history->GetDateFormatter()->FormatPRTime(history->GetLocale(), dateFormat, - nsIScriptableDateFormat::timeFormatNoSeconds, - aTime, resultString); - aResult = resultString; - return NS_OK; -} - - // nsNavHistoryResult::SortAll (nsINavHistoryResult) NS_IMETHODIMP @@ -3790,12 +3336,9 @@ nsNavHistoryResult::SortAll(PRUint32 aSortingMode) if (! mRootNode) return NS_ERROR_FAILURE; - PRUint32 oldVisibleCount = mVisibleElements.Length(); - // keep everything in sync NS_ASSERTION(mOptions, "Options should always be present for a root query"); mSortingMode = aSortingMode; - ComputeShowSessions(); // actually do sorting nsNavHistoryContainerResultNode::SortComparator comparator = @@ -3806,96 +3349,33 @@ nsNavHistoryResult::SortAll(PRUint32 aSortingMode) mRootNode->RecursiveSort(history->GetCollation(), comparator); } - if (mTree) { - BuildVisibleList(); - SetTreeSortingIndicator(); - - // sorting may have caused the row count to change from duplicate collapsing - if (mVisibleElements.Length() != oldVisibleCount) - mTree->RowCountChanged(0, mVisibleElements.Length() - oldVisibleCount); - mTree->InvalidateRange(0, mVisibleElements.Length()); + if (mView) { + mView->SortingChanged(aSortingMode); + mView->InvalidateAll(); } return NS_OK; } -// nsNavHistoryResult::Get/SetCollapseDuplicates (nsINavHistoryResult) +// nsNavHistoryResult::Viewer (nsINavHistoryResult) NS_IMETHODIMP -nsNavHistoryResult::GetCollapseDuplicates(PRBool* aCollapseDuplicates) +nsNavHistoryResult::GetViewer(nsINavHistoryResultViewer** aViewer) { - *aCollapseDuplicates = mCollapseDuplicates; + *aViewer = mView; + NS_IF_ADDREF(*aViewer); return NS_OK; } NS_IMETHODIMP -nsNavHistoryResult::SetCollapseDuplicates(PRBool aCollapseDuplicates) +nsNavHistoryResult::SetViewer(nsINavHistoryResultViewer* aViewer) { - if (aCollapseDuplicates == mCollapseDuplicates) - return NS_OK; - mCollapseDuplicates = aCollapseDuplicates; - if (mTree) { - // all we need to do here is rebuild the visible list with the new - // collapse flag value - BuildVisibleList(); - InvalidateTree(); - } + mView = aViewer; + if (aViewer) + aViewer->SetResult(this); return NS_OK; } -// nsNavHistoryResult::NodeForTreeIndex (nsINavHistoryResult) - -NS_IMETHODIMP -nsNavHistoryResult::NodeForTreeIndex(PRUint32 index, - nsINavHistoryResultNode** aResult) -{ - if (index >= PRUint32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - NS_ADDREF(*aResult = mVisibleElements[index]); - return NS_OK; -} - - -// nsNavHistoryResult::TreeIndexForNode (nsINavHistoryResult) - -NS_IMETHODIMP -nsNavHistoryResult::TreeIndexForNode(nsINavHistoryResultNode* aNode, - PRUint32* aResult) -{ - nsresult rv; - nsCOMPtr node = do_QueryInterface(aNode, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - if (node->mVisibleIndex < 0) { - *aResult = nsINavHistoryResult::INDEX_INVISIBLE; - } else { - *aResult = node->mVisibleIndex; - NS_ASSERTION(mVisibleElements[*aResult] == node, - "Node's visible index and array out of sync"); - } - return NS_OK; -} - - -// nsNavHistoryResult::AddObserver (nsINavHistoryResult) - -NS_IMETHODIMP -nsNavHistoryResult::AddObserver(nsINavHistoryResultViewObserver* aObserver, - PRBool aOwnsWeak) -{ - return mObservers.AppendWeakElement(aObserver, aOwnsWeak); -} - - -// nsNavHistoryResult::RemoveObserver (nsINavHistoryResult) - -NS_IMETHODIMP -nsNavHistoryResult::RemoveObserver(nsINavHistoryResultViewObserver* aObserver) -{ - return mObservers.RemoveWeakElement(aObserver); -} - - // nsNavHistoryResult::GetRoot (nsINavHistoryResult) // // We have a pointer to a container, but it will either be a folder or @@ -3915,757 +3395,6 @@ nsNavHistoryResult::GetRoot(nsINavHistoryQueryResultNode** aRoot) } -// nsNavHistoryResult::GetRowCount (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetRowCount(PRInt32 *aRowCount) -{ - *aRowCount = mVisibleElements.Length(); - return NS_OK; -} - - -// nsNavHistoryResult::Get/SetSelection (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetSelection(nsITreeSelection** aSelection) -{ - if (! mSelection) { - *aSelection = nsnull; - return NS_ERROR_FAILURE; - } - NS_ADDREF(*aSelection = mSelection); - return NS_OK; -} -NS_IMETHODIMP nsNavHistoryResult::SetSelection(nsITreeSelection* aSelection) -{ - mSelection = aSelection; - return NS_OK; -} - - -// nsNavHistoryResult::GetRowProperties (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetRowProperties(PRInt32 row, - nsISupportsArray *properties) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - - nsNavHistoryResultNode *node = mVisibleElements[row]; - - // Add the container property if it's applicable for this row. - if (node->IsContainer()) { - properties->AppendElement(nsNavHistory::sContainerAtom); - } - - // Next handle properties for session information. - if (! mShowSessions) - return NS_OK; // don't need to bother to compute session boundaries - if (! node->IsVisit()) - return NS_OK; // not a visit, so there are no sessions - - nsNavHistoryVisitResultNode* visit = node->GetAsVisit(); - if (visit->mSessionId != 0) { - if (row == 0) { - properties->AppendElement(nsNavHistory::sSessionStartAtom); - } else { - nsNavHistoryResultNode* previousNode = mVisibleElements[row - 1]; - if (previousNode->IsVisit() && - visit->mSessionId != previousNode->GetAsVisit()->mSessionId) { - properties->AppendElement(nsNavHistory::sSessionStartAtom); - } else { - properties->AppendElement(nsNavHistory::sSessionContinueAtom); - } - } - } - return NS_OK; -} - - -// nsNavHistoryResult::GetCellProperties (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::GetCellProperties(PRInt32 row, nsITreeColumn *col, - nsISupportsArray *properties) -{ - /* - if (row < 0 || row >= mVisibleElements.Length()) - return NS_ERROR_INVALID_ARG; - - nsNavHistoryResultNode *node = mVisibleElements[row]; - PRInt64 folderId, bookmarksRootId, toolbarRootId; - node->GetFolderId(&folderId); - - nsCOMPtr bms(do_GetService( - NS_NAVBOOKMARKSSERVICE_CONTRACTID)); - bms->GetBookmarksRoot(&bookmarksRootId); - bms->GetToolbarRoot(&toolbarRootId); - if (bookmarksRootId == folderId) - properties->AppendElement(nsNavHistory::sMenuRootAtom); - else if (toolbarRootId == folderId) - properties->AppendElement(nsNavHistory::sToolbarRootAtom); - - if (mShowSessions && node->mSessionID != 0) { - if (row == 0 || - node->mSessionID != VisibleElementAt(row - 1)->mSessionID) { - properties->AppendElement(nsNavHistory::sSessionStartAtom); - } else { - properties->AppendElement(nsNavHistory::sSessionContinueAtom); - } - } - */ - return NS_OK; -} - - -// nsNavHistoryResult::GetColumnProperties (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::GetColumnProperties(nsITreeColumn *col, - nsISupportsArray *properties) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - - -// nsNavHistoryResult::IsContainer (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::IsContainer(PRInt32 row, PRBool *_retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - *_retval = mVisibleElements[row]->IsContainer(); - if (*_retval) { - // treat non-expandable queries as non-containers - if (mVisibleElements[row]->IsQuery() && - ! mVisibleElements[row]->GetAsQuery()->CanExpand()) - *_retval = PR_FALSE; - } - return NS_OK; -} - - -// nsNavHistoryResult::IsContainerOpen (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::IsContainerOpen(PRInt32 row, PRBool *_retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - if (! mVisibleElements[row]->IsContainer()) - return NS_ERROR_INVALID_ARG; - - nsINavHistoryContainerResultNode* container = - mVisibleElements[row]->GetAsContainer(); - return container->GetContainerOpen(_retval); -} - - -// nsNavHistoryResult::IsContainerEmpty (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::IsContainerEmpty(PRInt32 row, PRBool *_retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - if (! mVisibleElements[row]->IsContainer()) - return NS_ERROR_INVALID_ARG; - - nsINavHistoryContainerResultNode* container = - mVisibleElements[row]->GetAsContainer(); - PRBool hasChildren = PR_FALSE; - nsresult rv = container->GetHasChildren(&hasChildren); - *_retval = ! hasChildren; - return rv; -} - - -// nsNavHistoryResult::IsSeparator (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::IsSeparator(PRInt32 row, PRBool *_retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - - *_retval = mVisibleElements[row]->IsSeparator(); - return NS_OK; -} - - -// nsNavHistoryResult::IsSorted (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::IsSorted(PRBool *_retval) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - - -// nsNavHistoryResult::CanDrop (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::CanDrop(PRInt32 row, PRInt32 orientation, - PRBool *_retval) -{ - PRUint32 count = mObservers.Length(); - for (PRUint32 i = 0; i < count; ++i) { - const nsCOMPtr &obs = mObservers[i]; - if (obs) { - obs->CanDrop(row, orientation, _retval); - if (*_retval) { - break; - } - } - } - return NS_OK; -} - - -// nsNavHistoryResult::Drop (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::Drop(PRInt32 row, PRInt32 orientation) -{ - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnDrop(row, orientation)) - return NS_OK; -} - - -// nsNavHistoryResult::GetParentIndex (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetParentIndex(PRInt32 row, PRInt32* _retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - nsNavHistoryResultNode* parent = mVisibleElements[row]->mParent; - if (! parent || parent->mVisibleIndex < 0) - *_retval = -1; - else - *_retval = parent->mVisibleIndex; - return NS_OK; -} - - -// nsNavHistoryResult::HasNextSibling (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::HasNextSibling(PRInt32 row, - PRInt32 afterIndex, - PRBool* _retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - if (row == PRInt32(mVisibleElements.Length()) - 1) { - // this is the last thing in the list -> no next sibling - *_retval = PR_FALSE; - return NS_OK; - } - *_retval = (mVisibleElements[row]->mIndentLevel == - mVisibleElements[row + 1]->mIndentLevel); - return NS_OK; -} - - -// nsNavHistoryResult::GetLevel (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetLevel(PRInt32 row, PRInt32* _retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - *_retval = mVisibleElements[row]->mIndentLevel; - return NS_OK; -} - - -// nsNavHistoryResult::GetImageSrc (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetImageSrc(PRInt32 row, nsITreeColumn *col, - nsAString& _retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - - // only the first column has an image - PRInt32 colIndex; - col->GetIndex(&colIndex); - if (colIndex != 0) { - _retval.Truncate(0); - return NS_OK; - } - - nsNavHistoryResultNode* node = mVisibleElements[row]; - - // Containers may or may not have favicons. If not, we will return nothing - // as the image, and the style rule should pick up the default. - // Separator rows never have icons. - if (node->IsSeparator() || - (node->IsContainer() && node->mFaviconURI.IsEmpty())) { - _retval.Truncate(0); - return NS_OK; - } - - // For consistency, we always return a favicon for non-containers, even if - // it is just the default one. - nsFaviconService* faviconService = nsFaviconService::GetFaviconService(); - NS_ENSURE_TRUE(faviconService, NS_ERROR_NO_INTERFACE); - - nsCAutoString spec; - faviconService->GetFaviconSpecForIconString( - mVisibleElements[row]->mFaviconURI, spec); - CopyUTF8toUTF16(spec, _retval); - return NS_OK; -} - - -// nsNavHistoryResult::GetProgressMode (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetProgressMode(PRInt32 row, - nsITreeColumn* col, - PRInt32* _retval) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - - -// nsNavHistoryResult::GetCellValue (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetCellValue(PRInt32 row, nsITreeColumn *col, - nsAString& _retval) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - - -// nsNavHistoryResult::GetCellText (nsITreeView) - -NS_IMETHODIMP nsNavHistoryResult::GetCellText(PRInt32 row, - nsITreeColumn *col, - nsAString& _retval) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - PRInt32 colIndex; - col->GetIndex(&colIndex); - - nsNavHistoryResultNode* node = mVisibleElements[row]; - - switch (GetColumnType(col)) { - case Column_Title: - { - // title: normally, this is just the title, but we don't want empty - // items in the tree view so return a special string if the title is - // empty. Do it here so that callers can still get at the 0 length title - // if they go through the "result" API. - if (node->IsSeparator()) { - _retval.Truncate(0); - } else if (! node->mTitle.IsEmpty()) { - _retval = NS_ConvertUTF8toUTF16(node->mTitle); - } else { - nsXPIDLString value; - nsresult rv = - nsNavHistory::GetHistoryService()->GetBundle()->GetStringFromName( - NS_LITERAL_STRING("noTitle").get(), getter_Copies(value)); - NS_ENSURE_SUCCESS(rv, rv); - _retval = value; - } - break; - } - case Column_URI: - { - if (node->IsURI()) - _retval = NS_ConvertUTF8toUTF16(node->mURI); - else - _retval.Truncate(0); - break; - } - case Column_Date: - { - if (node->mTime == 0 || ! node->IsURI()) { - // hosts and days shouldn't have a value for the date column. Actually, - // you could argue this point, but looking at the results, seeing the - // most recently visited date is not what I expect, and gives me no - // information I know how to use. Only show this for URI-based items. - _retval.Truncate(0); - } else { - return FormatFriendlyTime(node->mTime, _retval); - } - break; - } - case Column_VisitCount: - { - if (node->IsSeparator()) { - _retval.Truncate(0); - } else { - _retval = NS_ConvertUTF8toUTF16(nsPrintfCString("%d", node->mAccessCount)); - } - break; - } - default: - return NS_ERROR_INVALID_ARG; - } - return NS_OK; -} - - -// nsNavHistoryResult::SetTree (nsITreeView) -// -// This will get called when this view is attached to the tree to tell us -// the box object, AND when the tree is being destroyed, with NULL. - -NS_IMETHODIMP nsNavHistoryResult::SetTree(nsITreeBoxObject* tree) -{ - mTree = tree; - if (mTree) { - mRootNode->SetContainerOpen(PR_TRUE); - SetTreeSortingIndicator(); - } - // if there is no tree, BuildVisibleList will clear everything for us - return BuildVisibleList(); -} - - -// nsNavHistoryResult::ToggleOpenState (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::ToggleOpenState(PRInt32 row) -{ - if (row < 0 || row >= PRInt32(mVisibleElements.Length())) - return NS_ERROR_INVALID_ARG; - - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnToggleOpenState(row)) - - nsNavHistoryResultNode* node = mVisibleElements[row]; - if (! node->IsContainer()) - return NS_OK; // not a container, nothing to do - nsNavHistoryContainerResultNode* container = node->GetAsContainer(); - - nsresult rv; - if (container->mExpanded) - rv = container->CloseContainer(); - else - rv = container->OpenContainer(); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - - -// nsNavHistoryResult::CycleHeader (nsITreeView) -// -// If we already sorted by that column, the sorting is reversed, otherwise -// we use the default sorting direction for that data type. - -NS_IMETHODIMP -nsNavHistoryResult::CycleHeader(nsITreeColumn *col) -{ - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnCycleHeader(col)) - - PRInt32 colIndex; - col->GetIndex(&colIndex); - - NS_ASSERTION(mOptions, "Options should always be present for a root query"); - PRInt32 oldSort = mSortingMode; - PRInt32 newSort; - switch (GetColumnType(col)) { - case Column_Title: - if (oldSort == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING) - newSort = nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING; - else - newSort = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING; - break; - case Column_URI: - if (oldSort == nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING) - newSort = nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING; - else - newSort = nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING; - break; - case Column_Date: - if (oldSort == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING) - newSort = nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING; - else - newSort = nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING; - break; - case Column_VisitCount: - // visit count default is unusual because it is descending - if (oldSort == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING) - newSort = nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING; - else - newSort = nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING; - break; - default: - return NS_ERROR_INVALID_ARG; - } - return SortAll(newSort); -} - - -// nsNavHistoryResult::SelectionChanged (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::SelectionChanged() -{ - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnSelectionChanged()) - return NS_OK; -} - - -// nsNavHistoryResult::CycleCell (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::CycleCell(PRInt32 row, nsITreeColumn *col) -{ - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnCycleCell(row, col)) - return NS_OK; -} - - -// nsNavHistoryResult::IsEditable (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::IsEditable(PRInt32 row, nsITreeColumn *col, PRBool *_retval) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - - -// nsNavHistoryResult::SetCellValue (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::SetCellValue(PRInt32 row, nsITreeColumn *col, const nsAString & value) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - - -// nsNavHistoryResult::SetCellText (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::SetCellText(PRInt32 row, nsITreeColumn *col, const nsAString & value) -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - - -// nsNavHistoryResult::PerformAction (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::PerformAction(const PRUnichar *action) -{ - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnPerformAction(action)) - return NS_OK; -} - - -// nsNavHistoryResult::PerformActionOnRow (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::PerformActionOnRow(const PRUnichar *action, PRInt32 row) -{ - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnPerformActionOnRow(action, row)) - return NS_OK; -} - - -// nsNavHistoryResult::PerformActionOnCell (nsITreeView) - -NS_IMETHODIMP -nsNavHistoryResult::PerformActionOnCell(const PRUnichar *action, PRInt32 row, - nsITreeColumn *col) -{ - ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, - OnPerformActionOnCell(action, row, col)) - return NS_OK; -} - - -// nsNavHistoryResult::RowAdded -// -// Called by nodes to perform dynamic updates of the visible list. This -// will add the item to the visible list and notify the tree that we -// inserted something. -// -// Should only be called when there is an attached tree. -// -// This may not actually insert a row due to duplicate collapsing. - -void -nsNavHistoryResult::RowAdded(PRInt32 aVisibleIndex, - nsNavHistoryResultNode* aNode) -{ - if (! mTree) { - NS_NOTREACHED("RowAdded should only be called when there is a tree"); - return; - } - if (aVisibleIndex < 0 || aVisibleIndex > PRInt32(mVisibleElements.Length())) { - NS_NOTREACHED("Trying to insert invalid row"); - return; - } - - // Try collapsing with the previous node. Note that we do not have to try - // to redraw the surrounding rows (which we normally would because session - // boundaries may have changed) because when an item is merged, it will - // always be in the same session. - PRUint32 showThis; - if (aVisibleIndex > 0 && - CanCollapseDuplicates(mVisibleElements[aVisibleIndex - 1], - aNode, &showThis)) { - if (showThis == 0) { - // new node is invisible, collapsed with previous one - aNode->mVisibleIndex = -1; - return; - } else { - // new node replaces the previous (RowReplaced won't update replacee) - mVisibleElements[aVisibleIndex - 1]->mVisibleIndex = -1; - RowReplaced(aVisibleIndex - 1, aNode); - return; - } - } - - // try collapsing with the next node (which is currently at the same index - // we are trying to insert at) - if (aVisibleIndex < PRInt32(mVisibleElements.Length()) && - CanCollapseDuplicates(aNode, mVisibleElements[aVisibleIndex], - &showThis)) { - if (showThis == 0) { - // new node replaces the next node (RowReplaced won't update replacee) - mVisibleElements[aVisibleIndex]->mVisibleIndex = -1; - RowReplaced(aVisibleIndex, aNode); - return; - } else { - // new node is invisible, replaced by next one - aNode->mVisibleIndex = -1; - return; - } - } - - // no collapsing - aNode->mVisibleIndex = aVisibleIndex; - mVisibleElements.InsertElementAt(aVisibleIndex, aNode); - for (PRUint32 i = aVisibleIndex + 1; i < mVisibleElements.Length(); i ++) - mVisibleElements[i]->mVisibleIndex = i; - mTree->RowCountChanged(aVisibleIndex, 1); - - // Need to redraw the rows around this one because session boundaries may - // have changed. For example, if we add a page to a session, the previous - // page will need to be redrawn because it's session border will disappear. - if (mShowSessions) { - if (aVisibleIndex > 0) - mTree->InvalidateRow(aVisibleIndex - 1); - if (aVisibleIndex < PRInt32(mVisibleElements.Length()) - 1) - mTree->InvalidateRow(aVisibleIndex + 1); - } -} - - -// nsNavHistoryResult::RowsRemoved -// -// Called by the container nodes to perform incremental updates. Only call -// when there is an attached tree and the new item was visible. This will -// update the visible list and notify the tree. -// -// THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being -// removed but the node it is collapsed with is not being removed (this -// then just swap out the removee with it's collapsing partner). The only -// time when we really remove things is when deleting URIs, which will -// apply to all collapsees. This function is called sometimes when -// resorting items. However, we won't do this when sorted by date because -// dates will never change for visits, and date sorting is the only time -// things are collapsed. -// -// THIS FUNCTION SHOULD NOT TOUCH THE EXISTING NODE. This is called when -// an item is being removed, and it probably was already removed from -// its container which owns the reference. The visible element list does -// NOT own references, so our pointer may be invalid. - -void -nsNavHistoryResult::RowsRemoved(PRInt32 aVisibleIndex, PRUint32 aCount) -{ - if (! mTree) { - NS_NOTREACHED("RowsRemoved should only be called when there is a tree"); - return; - } - - // We really want tail recursion here, since we sometimes do another remove - // after this when duplicates are being collapsed. This loop emulates that. - while (PR_TRUE) { - if (aVisibleIndex < 0 || aVisibleIndex + aCount > - mVisibleElements.Length()) { - NS_NOTREACHED("Trying to remove invalid row"); - return; - } - mVisibleElements.RemoveElementsAt(aVisibleIndex, aCount); - for (PRUint32 i = aVisibleIndex; i < mVisibleElements.Length(); i ++) - mVisibleElements[i]->mVisibleIndex = i; - mTree->RowCountChanged(aVisibleIndex, -PRInt32(aCount)); - - // the removal might have put two things together that should be collapsed - if (aVisibleIndex > 0 && - aVisibleIndex < NS_STATIC_CAST(PRInt32, mVisibleElements.Length())) { - PRUint32 showThisOne; - if (CanCollapseDuplicates(mVisibleElements[aVisibleIndex - 1], - mVisibleElements[aVisibleIndex], &showThisOne)) - { - // Fake-tail-recurse to the beginning of this function to remove the - // collapsed row. Note that we need to set the visible index to -1 - // before looping because we can never touch the row we're removing - // (callers may have already destroyed it). - aVisibleIndex = aVisibleIndex - 1 + showThisOne; - mVisibleElements[aVisibleIndex]->mVisibleIndex = -1; - aCount = 1; - continue; - } - } - break; // normal path: just remove once - } -} - - -// nsNavHistoryResult::RowChanged - -void -nsNavHistoryResult::RowChanged(PRInt32 aVisibleIndex) -{ - if (! mTree) { - NS_NOTREACHED("RowChanged should only be called when there is a tree"); - return; - } - if (aVisibleIndex < 0 || aVisibleIndex >= PRInt32(mVisibleElements.Length())) { - NS_NOTREACHED("Trying to update invalid row"); - return; - } - mTree->InvalidateRow(aVisibleIndex); -} - - -// nsNavHistoryResult::RowReplaced -// -// Same as RowChanged but this will replace the visible element index -// with the new node pointer. We assume that the replacee will not be -// duplicate collapsed and go away (this is only used by bookmarks code -// which has no duplicate collapsing, and by InsertRow to handle the case of -// duplicate collapsing when a node is being replaced). -// -// THIS FUNCTION SHOULD NOT TOUCH THE EXISTING NODE. This is called when -// an item is being replaced, and the old one was probably already removed -// from its container which owns the reference. The visible element list -// does NOT own references, so our pointer may be invalid. - -void -nsNavHistoryResult::RowReplaced(PRInt32 aVisibleIndex, - nsNavHistoryResultNode* aNode) -{ - if (! mTree) { - NS_NOTREACHED("RowReplaced should only be called when there is a tree"); - return; - } - if (aVisibleIndex < 0 || aVisibleIndex >= PRInt32(mVisibleElements.Length())) { - NS_NOTREACHED("Trying to replace invalid row"); - return; - } - mVisibleElements[aVisibleIndex] = aNode; - aNode->mVisibleIndex = aVisibleIndex; - mTree->InvalidateRow(aVisibleIndex); -} - // nsINavBookmarkObserver implementation // Here, it is important that we create a COPY of the observer array. Some @@ -4962,3 +3691,1491 @@ nsNavHistoryResult::OnPageChanged(nsIURI *aURI, ENUMERATE_HISTORY_OBSERVERS(OnPageChanged(aURI, aWhat, aValue)); return NS_OK; } + +// nsNavHistoryResultTreeViewer ************************************************ + +NS_IMPL_ADDREF(nsNavHistoryResultTreeViewer) +NS_IMPL_RELEASE(nsNavHistoryResultTreeViewer) + +NS_INTERFACE_MAP_BEGIN(nsNavHistoryResultTreeViewer) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResultTreeViewer) + NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultViewer) + NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultTreeViewer) + NS_INTERFACE_MAP_ENTRY(nsITreeView) +NS_INTERFACE_MAP_END + +nsNavHistoryResultTreeViewer::nsNavHistoryResultTreeViewer() : + mCollapseDuplicates(PR_TRUE), + mShowSessions(PR_FALSE) +{ + +} + + +// nsNavHistoryResultTreeViewer::FinishInit +// +// This is called when the result or tree may have changed. It reinitializes +// everything. Result and/or tree can be null when calling. + +nsresult +nsNavHistoryResultTreeViewer::FinishInit() +{ + if (mTree && mResult) { + mResult->mRootNode->SetContainerOpen(PR_TRUE); + SortingChanged(mResult->mSortingMode); + } + // if there is no tree, BuildVisibleList will clear everything for us + return BuildVisibleList(); +} + + +// nsNavHistoryResultTreeViewer::ComputeShowSessions +// +// Computes the value of mShowSessions, which is used to see if we should +// try to set styles for session groupings. We only want to do session +// grouping if we're in an appropriate view, which is view by visit and +// sorted by date. + +void +nsNavHistoryResultTreeViewer::ComputeShowSessions() +{ + NS_ASSERTION(mResult, "Must have a result to show sessions!"); + mShowSessions = PR_FALSE; + + nsNavHistoryQueryOptions* options = mResult->mOptions; + NS_ASSERTION(options, "navHistoryResults must have valid options"); + if (options->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_VISIT && + options->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_FULL_VISIT) + return; // not visits + + if (mResult->mSortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING && + mResult->mSortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) + return; // not date sorting + + PRUint32 groupCount; + options->GroupingMode(&groupCount); + if (groupCount > 0) + return; + + mShowSessions = PR_TRUE; +} + + +// nsNavHistoryResultTreeViewer::BuildVisibleList +// +// Call to completely rebuild the list of visible items. Note if there is no +// tree or root this will just clear out the list, so you can also call this +// when a tree is detached to clear the list. +// +// This does NOT update the screen. + +nsresult +nsNavHistoryResultTreeViewer::BuildVisibleList() +{ + if (mResult) { + // Any current visible elements need to be marked as invisible. However, + // be careful to do this only when there is no result. We don't have owning + // references to these objects, and if the result was detached and freed, + // our pointers will be stale. + for (PRUint32 i = 0; i < mVisibleElements.Length(); i ++) + mVisibleElements[i]->mViewIndex = -1; + } + mVisibleElements.Clear(); + + if (mResult->mRootNode && mTree) { + ComputeShowSessions(); + return BuildVisibleSection(mResult->mRootNode, &mVisibleElements, 0); + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::BuildVisibleSection +// +// This takes a container and recursively appends visible elements to the +// given array. This is used to build the visible element list (with +// mVisibleElements passed as the array), or portions thereof (with a +// separate array that is merged with the main list later. +// +// aVisibleStartIndex is the visible index of the beginning of the 'aVisible' +// array. When aVisible is mVisibleElements, this is 0. This is non-zero +// when we are building up a sub-region for insertion. Then, this is the +// index where the new array will be inserted into mVisibleElements. It is +// used to compute each node's mViewIndex. + +nsresult +nsNavHistoryResultTreeViewer::BuildVisibleSection( + nsNavHistoryContainerResultNode* aContainer, + VisibleList* aVisible, + PRUint32 aVisibleStartIndex) +{ + if (! aContainer->mExpanded) + return NS_OK; // nothing to do + for (PRInt32 i = 0; i < aContainer->mChildren.Count(); i ++) { + nsNavHistoryResultNode* cur = aContainer->mChildren[i]; + + // collapse all duplicates starting from here + if (mCollapseDuplicates) { + PRUint32 showThis; + while (i < aContainer->mChildren.Count() - 1 && + CanCollapseDuplicates(cur, aContainer->mChildren[i+1], &showThis)) { + if (showThis) { + // collapse the first and use the second + cur->mViewIndex = -1; + cur = aContainer->mChildren[i + 1]; + } else { + // collapse the second and use the first + aContainer->mChildren[i + 1]->mViewIndex = -1; + } + i ++; + } + } + + // add item + cur->mViewIndex = aVisibleStartIndex + aVisible->Length(); + if (! aVisible->AppendElement(cur)) + return NS_ERROR_OUT_OF_MEMORY; + + // recursively do containers + if (cur->IsContainer()) { + nsNavHistoryContainerResultNode* curContainer = cur->GetAsContainer(); + if (curContainer->mExpanded && curContainer->mChildren.Count() > 0) { + nsresult rv = BuildVisibleSection(curContainer, aVisible, + aVisibleStartIndex); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::CountVisibleRowsForItem +// +// This counts how many rows an item takes in the tree, that is, the item +// itself plus any nodes following it with an increased indent. This allows +// you to figure out how many rows an item (=1) or a container with all of +// its children takes. + +PRUint32 +nsNavHistoryResultTreeViewer::CountVisibleRowsForItem( + nsNavHistoryResultNode* aNode) +{ + NS_ASSERTION(aNode->mViewIndex >= 0, "Item is not visible, no rows to count"); + PRInt32 outerLevel = aNode->mIndentLevel; + for (PRUint32 i = aNode->mViewIndex + 1; i < mVisibleElements.Length(); i ++) { + if (mVisibleElements[i]->mIndentLevel <= outerLevel) + return i - aNode->mViewIndex; + } + // this node plus its children occupy the bottom of the list + return mVisibleElements.Length() - aNode->mViewIndex; +} + + +// nsNavHistoryResultTreeViewer::RefreshVisibleSection +// +// This is called by containers when they change and we need to update +// everything about the container. We build a new visible section with the +// container as a separate object so we first know how the list changes. This +// way we only have to do one realloc/memcpy to update the list. +// +// We also try to be smart here about redrawing the screen. + +nsresult +nsNavHistoryResultTreeViewer::RefreshVisibleSection( + nsNavHistoryContainerResultNode* aContainer) +{ + NS_ASSERTION(mResult, "Need to have a result to update"); + if (! mTree) + return NS_OK; + + // The root node is invisible, so we can't check its index and indent level. + // Therefore, special case it. This is easy because we just rebuild everything + if (aContainer == mResult->mRootNode) + return BuildVisibleList(); + + // When the container is not the root, it better be visible if we are updating + NS_ASSERTION(aContainer->mViewIndex >= 0 && + aContainer->mViewIndex < PRInt32(mVisibleElements.Length()), + "Trying to expand a node that is not visible"); + NS_ASSERTION(mVisibleElements[aContainer->mViewIndex] == aContainer, + "Visible index is out of sync!"); + + PRUint32 startReplacement = aContainer->mViewIndex + 1; + PRUint32 replaceCount = CountVisibleRowsForItem(aContainer) - 1; + + // Mark the removees as invisible + PRUint32 i; + for (i = startReplacement; i < replaceCount; i ++) + mVisibleElements[startReplacement + i]->mViewIndex = -1; + + // Building the new list will set the new elements' visible indices. + VisibleList newElements; + nsresult rv = BuildVisibleSection(aContainer, &newElements, startReplacement); + NS_ENSURE_SUCCESS(rv, rv); + + // actually update the visible list + if (! mVisibleElements.ReplaceElementsAt(aContainer->mViewIndex + 1, + replaceCount, newElements.Elements(), newElements.Length())) + return NS_ERROR_OUT_OF_MEMORY; + + if (replaceCount == newElements.Length()) { + // length is the same, just repaint the changed area + if (replaceCount > 0) + mTree->InvalidateRange(startReplacement, startReplacement + replaceCount - 1); + } else { + // If the new area is a different size, we'll have to renumber the elements + // following the area. + for (i = startReplacement + newElements.Length(); + i < mVisibleElements.Length(); i ++) + mVisibleElements[i]->mViewIndex = i; + + // repaint the region that stayed the same, also including the container + // element itself since its twisty could have changed. + PRUint32 minLength; + if (replaceCount > newElements.Length()) + minLength = newElements.Length(); + else + minLength = replaceCount; + mTree->InvalidateRange(startReplacement - 1, startReplacement + minLength - 1); + + // now update the number of elements + mTree->RowCountChanged(startReplacement + minLength, + newElements.Length() - replaceCount); + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::CanCollapseDuplicates +// +// This returns true if the two results can be collapsed as duplicates. +// *aShowThisOne will be either 0 or 1, indicating which of the duplicates +// should be shown. + +PRBool +nsNavHistoryResultTreeViewer::CanCollapseDuplicates( + nsNavHistoryResultNode* aTop, nsNavHistoryResultNode* aNext, + PRUint32* aShowThisOne) +{ + if (! mCollapseDuplicates) + return PR_FALSE; + if (! aTop->IsVisit() || ! aNext->IsVisit()) + return PR_FALSE; // only collapse two visits + + nsNavHistoryVisitResultNode* topVisit = aTop->GetAsVisit(); + nsNavHistoryVisitResultNode* nextVisit = aNext->GetAsVisit(); + + if (! topVisit->mURI.Equals(nextVisit->mURI)) + return PR_FALSE; // don't collapse nonmatching URIs + + // now we know we want to collapse, show the one with the more recent time + *aShowThisOne = topVisit->mTime < nextVisit->mTime; + return PR_TRUE; +} + + +// nsNavHistoryResultTreeViewer::GetColumnType + +nsNavHistoryResultTreeViewer::ColumnType +nsNavHistoryResultTreeViewer::GetColumnType(nsITreeColumn* col) +{ + const PRUnichar* idConst; + col->GetIdConst(&idConst); + switch(idConst[0]) { + case PRUnichar('t'): + return Column_Title; + case PRUnichar('u'): + return Column_URI; + case PRUnichar('d'): + return Column_Date; + case PRUnichar('v'): + return Column_VisitCount; + default: + return Column_Unknown; + } +} + + +// nsNavHistoryResultTreeViewer::SortTypeToColumnType +// +// Places whether it's ascending or descending into the given boolean buffer + +nsNavHistoryResultTreeViewer::ColumnType +nsNavHistoryResultTreeViewer::SortTypeToColumnType(PRUint32 aSortType, + PRBool* aDescending) +{ + switch(aSortType) { + case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING: + *aDescending = PR_FALSE; + return Column_Title; + case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING: + *aDescending = PR_TRUE; + return Column_Title; + case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING: + *aDescending = PR_FALSE; + return Column_Date; + case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING: + *aDescending = PR_TRUE; + return Column_Date; + case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING: + *aDescending = PR_FALSE; + return Column_URI; + case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING: + *aDescending = PR_TRUE; + return Column_URI; + case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING: + *aDescending = PR_FALSE; + return Column_VisitCount; + case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING: + *aDescending = PR_TRUE; + return Column_VisitCount; + default: + *aDescending = PR_FALSE; + return Column_Unknown; + } +} + + +// nsNavHistoryResultTreeViewer::ItemInserted (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::ItemInserted( + nsINavHistoryContainerResultNode *aParent, nsINavHistoryResultNode *aItem, + PRUint32 aNewIndex) +{ + if (! mTree) + return NS_OK; // nothing to do + + nsRefPtr parent; + aParent->QueryInterface(NS_GET_IID(nsNavHistoryContainerResultNode), + getter_AddRefs(parent)); + if (! parent) { + NS_NOTREACHED("Parent is not a concrete node"); + return NS_ERROR_UNEXPECTED; + } + nsCOMPtr item = do_QueryInterface(aItem); + if (! aItem) { + NS_NOTREACHED("Item is not a concrete node"); + return NS_ERROR_UNEXPECTED; + } + + // update parent when inserting the first item because twisty may have changed + if (parent->mChildren.Count() == 1) + ItemChanged(aParent); + + // compute the new view index of the item + PRInt32 newViewIndex = -1; + if (aNewIndex == 0) { + // item is the first thing in our child list, it takes our index +1. Note + // that this computation still works if the parent is an invisible root + // node, because root_index + 1 = -1 + 1 = 0 + newViewIndex = parent->mViewIndex + 1; + } else { + // Here, we try to find the next visible element in the child list so we + // can set the new visible index to be right before that. Note that we + // have to search DOWN instead of up, because some siblings could have + // children themselves that would be in the way. + for (PRInt32 i = aNewIndex + 1; i < parent->mChildren.Count(); i ++) { + if (parent->mChildren[i]->mViewIndex >= 0) { + // the view indices of subsequent children have not been shifted + // so the next item will have what should be our index + newViewIndex = parent->mChildren[i]->mViewIndex; + break; + } + } + if (newViewIndex < 0) { + // At the end of the child list without finding a visible sibling: This + // is a little harder because we don't know how many rows the last item + // in our list takes up (it could be a container with many children). + PRUint32 lastRowCount = + CountVisibleRowsForItem(parent->mChildren[aNewIndex - 1]); + newViewIndex = parent->mChildren[aNewIndex - 1]->mViewIndex + + lastRowCount; + } + } + + // Try collapsing with the previous node. Note that we do not have to try + // to redraw the surrounding rows (which we normally would because session + // boundaries may have changed) because when an item is merged, it will + // always be in the same session. + PRUint32 showThis; + if (newViewIndex > 0 && + CanCollapseDuplicates(mVisibleElements[newViewIndex - 1], + item, &showThis)) { + if (showThis == 0) { + // new node is invisible, collapsed with previous one + item->mViewIndex = -1; + return NS_OK; + } else { + // new node replaces the previous + ItemReplaced(parent, mVisibleElements[newViewIndex - 1], item, 0); + return NS_OK; + } + } + + // try collapsing with the next node (which is currently at the same index + // we are trying to insert at) + if (newViewIndex < PRInt32(mVisibleElements.Length()) && + CanCollapseDuplicates(item, mVisibleElements[newViewIndex], + &showThis)) { + if (showThis == 0) { + // new node replaces the next node + ItemReplaced(parent, mVisibleElements[newViewIndex], item, 0); + return NS_OK; + } else { + // new node is invisible, replaced by next one + item->mViewIndex = -1; + return NS_OK; + } + } + + // no collapsing, insert new item + item->mViewIndex = newViewIndex; + mVisibleElements.InsertElementAt(newViewIndex, item); + for (PRUint32 i = newViewIndex + 1; i < mVisibleElements.Length(); i ++) + mVisibleElements[i]->mViewIndex = i; + mTree->RowCountChanged(newViewIndex, 1); + + // Need to redraw the rows around this one because session boundaries may + // have changed. For example, if we add a page to a session, the previous + // page will need to be redrawn because it's session border will disappear. + if (mShowSessions) { + if (newViewIndex > 0) + mTree->InvalidateRange(newViewIndex - 1, newViewIndex - 1); + if (newViewIndex < PRInt32(mVisibleElements.Length()) - 1) + mTree->InvalidateRange(newViewIndex + 1, newViewIndex + 1); + } + + if (item->IsContainer() && item->GetAsContainer()->mExpanded) + RefreshVisibleSection(item->GetAsContainer()); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::ItemRemoved (nsINavHistoryResultViewer) +// +// THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being +// removed but the node it is collapsed with is not being removed (this +// then just swap out the removee with it's collapsing partner). The only +// time when we really remove things is when deleting URIs, which will +// apply to all collapsees. This function is called sometimes when +// resorting items. However, we won't do this when sorted by date because +// dates will never change for visits, and date sorting is the only time +// things are collapsed. + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::ItemRemoved( + nsINavHistoryContainerResultNode *aParent, nsINavHistoryResultNode *aItem, + PRUint32 aOldIndex) +{ + NS_ASSERTION(mResult, "Got a notification but have no result!"); + if (! mTree) + return NS_OK; // nothing to do + + nsCOMPtr item = do_QueryInterface(aItem); + NS_ENSURE_TRUE(item, NS_ERROR_INVALID_ARG); + + PRInt32 oldViewIndex = item->mViewIndex; + if (oldViewIndex < 0) + return NS_OK; // item was already invisible, nothing to do + + // this may have been a container, in which case it has a lot of rows + PRInt32 count = CountVisibleRowsForItem(item); + + // We really want tail recursion here, since we sometimes do another remove + // after this when duplicates are being collapsed. This loop emulates that. + while (PR_TRUE) { + if (oldViewIndex > NS_STATIC_CAST(PRInt32, mVisibleElements.Length())) { + NS_NOTREACHED("Trying to remove invalid row"); + return NS_OK; + } + mVisibleElements.RemoveElementsAt(oldViewIndex, count); + for (PRUint32 i = oldViewIndex; i < mVisibleElements.Length(); i ++) + mVisibleElements[i]->mViewIndex = i; + + mTree->RowCountChanged(oldViewIndex, -count); + + // the removal might have put two things together that should be collapsed + if (oldViewIndex > 0 && + oldViewIndex < NS_STATIC_CAST(PRInt32, mVisibleElements.Length())) { + PRUint32 showThisOne; + if (CanCollapseDuplicates(mVisibleElements[oldViewIndex - 1], + mVisibleElements[oldViewIndex], &showThisOne)) + { + // Fake-tail-recurse to the beginning of this function to remove the + // collapsed row. Note that we need to set the visible index to -1 + // before looping because we can never touch the row we're removing + // (callers may have already destroyed it). + oldViewIndex = oldViewIndex - 1 + showThisOne; + mVisibleElements[oldViewIndex]->mViewIndex = -1; + count = 1; // next time remove one row + continue; + } + } + break; // normal path: just remove once + } + + // redraw parent because twisty may have changed + PRBool hasChildren; + aParent->GetHasChildren(&hasChildren); + if (! hasChildren) + ItemChanged(aParent); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::ItemReplaced (nsINavHistoryResultViewer) +// +// Be careful, the parameter 'aIndex' here specifies the index in the +// parent node of the item, not the visible index. +// +// This is called from the result when the item is replaced, but this object +// calls this function internally also when duplicate collapsing changes. +// In this case, aIndex will be 0, so we should be careful not to use the +// value. + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::ItemReplaced( + nsINavHistoryContainerResultNode* parent, nsINavHistoryResultNode* aOldItem, + nsINavHistoryResultNode* aNewItem, PRUint32 aIndexDoNotUse) +{ + if (! mTree) + return NS_OK; + + PRInt32 viewIndex; + aOldItem->GetViewIndex(&viewIndex); + aNewItem->SetViewIndex(viewIndex); + if (viewIndex >= 0 && + viewIndex < NS_STATIC_CAST(PRInt32, mVisibleElements.Length())) { + nsCOMPtr newItem = do_QueryInterface(aNewItem); + NS_ASSERTION(newItem, "Node is not one of our concrete ones"); + mVisibleElements[viewIndex] = newItem; + } + aOldItem->SetViewIndex(-1); + mTree->InvalidateRow(viewIndex); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::ItemChanged (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::ItemChanged(nsINavHistoryResultNode *item) +{ + NS_ASSERTION(mResult, "Got a notification but have no result!"); + PRInt32 viewIndex; + item->GetViewIndex(&viewIndex); + if (mTree && viewIndex >= 0) + return mTree->InvalidateRow(viewIndex); + printf("Invalidated row %d\n", viewIndex); + return NS_OK; // nothing to refresh +} + + +// nsNavHistoryResultTreeViewer::ContainerOpened (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::ContainerOpened( + nsINavHistoryContainerResultNode *item) +{ + NS_ASSERTION(mResult, "Got a notification but have no result!"); + if (! mTree || item < 0) + return NS_OK; // nothing to do, container is not visible + PRInt32 viewIndex; + item->GetViewIndex(&viewIndex); + if (viewIndex >= NS_STATIC_CAST(PRInt32, mVisibleElements.Length())) { + // be paranoid about visible indices since others can change it + NS_NOTREACHED("Invalid visible index"); + return NS_ERROR_UNEXPECTED; + } + return RefreshVisibleSection( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, item)); +} + + +// nsNavHistoryResultTreeViewer::ContainerClosed (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::ContainerClosed( + nsINavHistoryContainerResultNode *item) +{ + NS_ASSERTION(mResult, "Got a notification but have no result!"); + if (! mTree || item < 0) + return NS_OK; // nothing to do, container is not visible + + PRInt32 viewIndex; + item->GetViewIndex(&viewIndex); + if (viewIndex >= NS_STATIC_CAST(PRInt32, mVisibleElements.Length())) { + // be paranoid about visible indices since others can change it + NS_NOTREACHED("Invalid visible index"); + return NS_ERROR_UNEXPECTED; + } + return RefreshVisibleSection( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, item)); +} + + +// nsNavHistoryResultTreeViewer::InvalidateContainer (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::InvalidateContainer( + nsINavHistoryContainerResultNode* aItem) +{ + NS_ASSERTION(mResult, "Got message but don't have a result!"); + if (! mTree) + return NS_OK; + + return RefreshVisibleSection( + NS_STATIC_CAST(nsNavHistoryContainerResultNode*, aItem)); +} + + +// nsNavHistoryResultTreeViewer::InvalidateAll (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::InvalidateAll() +{ + NS_ASSERTION(mResult, "Got message but don't have a result!"); + if (! mTree) + return NS_OK; + + PRInt32 oldRowCount = mVisibleElements.Length(); + + // update flat list to new contents + nsresult rv = BuildVisibleList(); + NS_ENSURE_SUCCESS(rv, rv); + + // redraw the tree, inserting new items + rv = mTree->RowCountChanged(0, mVisibleElements.Length() - oldRowCount); + NS_ENSURE_SUCCESS(rv, rv); + return mTree->Invalidate(); +} + + +// nsNavHistoryResultTreeViewer::SortingChanged (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::SortingChanged(PRUint32 aSortingMode) +{ + if (! mTree || ! mResult) + return NS_OK; + + nsCOMPtr columns; + nsresult rv = mTree->GetColumns(getter_AddRefs(columns)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_NAMED_LITERAL_STRING(sortDirectionKey, "sortDirection"); + + // clear old sorting indicator. Watch out: GetSortedColumn returns NS_OK + // but null element if there is no sorted element + nsCOMPtr column; + rv = columns->GetSortedColumn(getter_AddRefs(column)); + NS_ENSURE_SUCCESS(rv, rv); + if (column) { + nsCOMPtr element; + rv = column->GetElement(getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, rv); + element->SetAttribute(sortDirectionKey, EmptyString()); + } + + // set new sorting indicator by looking through all columns for ours + if (aSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE) + return NS_OK; + PRBool desiredIsDescending; + ColumnType desiredColumn = SortTypeToColumnType(aSortingMode, + &desiredIsDescending); + PRInt32 colCount; + rv = columns->GetCount(&colCount); + NS_ENSURE_SUCCESS(rv, rv); + for (PRInt32 i = 0; i < colCount; i ++) { + columns->GetColumnAt(i, getter_AddRefs(column)); + NS_ENSURE_SUCCESS(rv, rv); + if (GetColumnType(column) == desiredColumn) { + // found our desired one, set + nsCOMPtr element; + rv = column->GetElement(getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, rv); + if (desiredIsDescending) + element->SetAttribute(sortDirectionKey, NS_LITERAL_STRING("descending")); + else + element->SetAttribute(sortDirectionKey, NS_LITERAL_STRING("ascending")); + break; + } + } + + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::Result (nsINavHistoryResultViewer) +// +// We store a pointer to the concrete object, so need to QI. We fail if the +// given result object doesn't match our concrete one. +// +// @see SetTree + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::GetResult(nsINavHistoryResult** aResult) +{ + *aResult = mResult; + NS_IF_ADDREF(*aResult); + return NS_OK; +} +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::SetResult(nsINavHistoryResult* aResult) +{ + if (aResult) { + nsresult rv = aResult->QueryInterface(NS_GET_IID(nsNavHistoryResult), + getter_AddRefs(mResult)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mResult = nsnull; + } + return FinishInit(); +} + + +// nsNavHistoryResultTreeViewer::AddViewObserver (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::AddViewObserver( + nsINavHistoryResultViewObserver* aObserver, PRBool aOwnsWeak) +{ + return mObservers.AppendWeakElement(aObserver, aOwnsWeak); +} + + +// nsNavHistoryResultTreeViewer::RemoveViewObserver (nsINavHistoryResultViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::RemoveViewObserver( + nsINavHistoryResultViewObserver* aObserver) +{ + return mObservers.RemoveWeakElement(aObserver); +} + + +// nsNavHistoryResultTreeViewer::CollapseDuplicates (nsINavHistoryResultTreeViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::GetCollapseDuplicates(PRBool* aCollapseDuplicates) +{ + *aCollapseDuplicates = mCollapseDuplicates; + return NS_OK; +} +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::SetCollapseDuplicates(PRBool aCollapseDuplicates) +{ + if ((aCollapseDuplicates && mCollapseDuplicates) || + (! aCollapseDuplicates && ! mCollapseDuplicates)) + return NS_OK; // no change + + mCollapseDuplicates = aCollapseDuplicates; + + if (mResult && mTree) + InvalidateAll(); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetFlatItemCount (nsINavHistoryResultTreeViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::GetFlatItemCount(PRUint32 *aItemCount) +{ + *aItemCount = mVisibleElements.Length(); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::NodeForTreeIndex (nsINavHistoryResultTreeViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::NodeForTreeIndex( + PRUint32 index, nsINavHistoryResultNode** aResult) +{ + if (index >= PRUint32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + NS_ADDREF(*aResult = mVisibleElements[index]); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::TreeIndexForNode (nsINavHistoryResultTreeViewer) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::TreeIndexForNode(nsINavHistoryResultNode* aNode, + PRUint32* aResult) +{ + nsresult rv; + nsCOMPtr node = do_QueryInterface(aNode, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (node->mViewIndex < 0) { + *aResult = nsINavHistoryResultTreeViewer::INDEX_INVISIBLE; + } else { + *aResult = node->mViewIndex; + NS_ASSERTION(mVisibleElements[*aResult] == node, + "Node's visible index and array out of sync"); + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetRowCount (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetRowCount(PRInt32 *aRowCount) +{ + *aRowCount = mVisibleElements.Length(); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::Get/SetSelection (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetSelection(nsITreeSelection** aSelection) +{ + if (! mSelection) { + *aSelection = nsnull; + return NS_ERROR_FAILURE; + } + NS_ADDREF(*aSelection = mSelection); + return NS_OK; +} +NS_IMETHODIMP nsNavHistoryResultTreeViewer::SetSelection(nsITreeSelection* aSelection) +{ + mSelection = aSelection; + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetRowProperties (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetRowProperties(PRInt32 row, + nsISupportsArray *properties) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + + nsNavHistoryResultNode *node = mVisibleElements[row]; + + // Add the container property if it's applicable for this row. + if (node->IsContainer()) { + properties->AppendElement(nsNavHistory::sContainerAtom); + } + + // Next handle properties for session information. + if (! mShowSessions) + return NS_OK; // don't need to bother to compute session boundaries + if (! node->IsVisit()) + return NS_OK; // not a visit, so there are no sessions + + nsNavHistoryVisitResultNode* visit = node->GetAsVisit(); + if (visit->mSessionId != 0) { + if (row == 0) { + properties->AppendElement(nsNavHistory::sSessionStartAtom); + } else { + nsNavHistoryResultNode* previousNode = mVisibleElements[row - 1]; + if (previousNode->IsVisit() && + visit->mSessionId != previousNode->GetAsVisit()->mSessionId) { + properties->AppendElement(nsNavHistory::sSessionStartAtom); + } else { + properties->AppendElement(nsNavHistory::sSessionContinueAtom); + } + } + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetCellProperties (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::GetCellProperties(PRInt32 row, nsITreeColumn *col, + nsISupportsArray *properties) +{ + /* + if (row < 0 || row >= mVisibleElements.Length()) + return NS_ERROR_INVALID_ARG; + + nsNavHistoryResultNode *node = mVisibleElements[row]; + PRInt64 folderId, bookmarksRootId, toolbarRootId; + node->GetFolderId(&folderId); + + nsCOMPtr bms(do_GetService( + NS_NAVBOOKMARKSSERVICE_CONTRACTID)); + bms->GetBookmarksRoot(&bookmarksRootId); + bms->GetToolbarRoot(&toolbarRootId); + if (bookmarksRootId == folderId) + properties->AppendElement(nsNavHistory::sMenuRootAtom); + else if (toolbarRootId == folderId) + properties->AppendElement(nsNavHistory::sToolbarRootAtom); + + if (mShowSessions && node->mSessionID != 0) { + if (row == 0 || + node->mSessionID != VisibleElementAt(row - 1)->mSessionID) { + properties->AppendElement(nsNavHistory::sSessionStartAtom); + } else { + properties->AppendElement(nsNavHistory::sSessionContinueAtom); + } + } + */ + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetColumnProperties (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::GetColumnProperties(nsITreeColumn *col, + nsISupportsArray *properties) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +// nsNavHistoryResultTreeViewer::IsContainer (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::IsContainer(PRInt32 row, PRBool *_retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + *_retval = mVisibleElements[row]->IsContainer(); + if (*_retval) { + // treat non-expandable queries as non-containers + if (mVisibleElements[row]->IsQuery() && + ! mVisibleElements[row]->GetAsQuery()->CanExpand()) + *_retval = PR_FALSE; + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::IsContainerOpen (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::IsContainerOpen(PRInt32 row, PRBool *_retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + if (! mVisibleElements[row]->IsContainer()) + return NS_ERROR_INVALID_ARG; + + nsINavHistoryContainerResultNode* container = + mVisibleElements[row]->GetAsContainer(); + return container->GetContainerOpen(_retval); +} + + +// nsNavHistoryResultTreeViewer::IsContainerEmpty (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::IsContainerEmpty(PRInt32 row, PRBool *_retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + if (! mVisibleElements[row]->IsContainer()) + return NS_ERROR_INVALID_ARG; + + nsINavHistoryContainerResultNode* container = + mVisibleElements[row]->GetAsContainer(); + PRBool hasChildren = PR_FALSE; + nsresult rv = container->GetHasChildren(&hasChildren); + *_retval = ! hasChildren; + return rv; +} + + +// nsNavHistoryResultTreeViewer::IsSeparator (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::IsSeparator(PRInt32 row, PRBool *_retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + + *_retval = mVisibleElements[row]->IsSeparator(); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::IsSorted (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::IsSorted(PRBool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +// nsNavHistoryResultTreeViewer::CanDrop (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::CanDrop(PRInt32 row, PRInt32 orientation, + PRBool *_retval) +{ + PRUint32 count = mObservers.Length(); + for (PRUint32 i = 0; i < count; ++i) { + const nsCOMPtr &obs = mObservers[i]; + if (obs) { + obs->CanDrop(row, orientation, _retval); + if (*_retval) { + break; + } + } + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::Drop (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::Drop(PRInt32 row, PRInt32 orientation) +{ + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnDrop(row, orientation)) + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetParentIndex (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetParentIndex(PRInt32 row, PRInt32* _retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + nsNavHistoryResultNode* parent = mVisibleElements[row]->mParent; + if (! parent || parent->mViewIndex < 0) + *_retval = -1; + else + *_retval = parent->mViewIndex; + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::HasNextSibling (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::HasNextSibling(PRInt32 row, + PRInt32 afterIndex, + PRBool* _retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + if (row == PRInt32(mVisibleElements.Length()) - 1) { + // this is the last thing in the list -> no next sibling + *_retval = PR_FALSE; + return NS_OK; + } + *_retval = (mVisibleElements[row]->mIndentLevel == + mVisibleElements[row + 1]->mIndentLevel); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetLevel (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetLevel(PRInt32 row, PRInt32* _retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + *_retval = mVisibleElements[row]->mIndentLevel; + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetImageSrc (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetImageSrc(PRInt32 row, nsITreeColumn *col, + nsAString& _retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + + // only the first column has an image + PRInt32 colIndex; + col->GetIndex(&colIndex); + if (colIndex != 0) { + _retval.Truncate(0); + return NS_OK; + } + + nsNavHistoryResultNode* node = mVisibleElements[row]; + + // Containers may or may not have favicons. If not, we will return nothing + // as the image, and the style rule should pick up the default. + // Separator rows never have icons. + if (node->IsSeparator() || + (node->IsContainer() && node->mFaviconURI.IsEmpty())) { + _retval.Truncate(0); + return NS_OK; + } + + // For consistency, we always return a favicon for non-containers, even if + // it is just the default one. + nsFaviconService* faviconService = nsFaviconService::GetFaviconService(); + NS_ENSURE_TRUE(faviconService, NS_ERROR_NO_INTERFACE); + + nsCAutoString spec; + faviconService->GetFaviconSpecForIconString( + mVisibleElements[row]->mFaviconURI, spec); + CopyUTF8toUTF16(spec, _retval); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::GetProgressMode (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetProgressMode(PRInt32 row, + nsITreeColumn* col, + PRInt32* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +// nsNavHistoryResultTreeViewer::GetCellValue (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetCellValue( + PRInt32 row, nsITreeColumn *col, nsAString& _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +// nsNavHistoryResultTreeViewer::GetCellText (nsITreeView) + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::GetCellText(PRInt32 row, + nsITreeColumn *col, + nsAString& _retval) +{ + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + PRInt32 colIndex; + col->GetIndex(&colIndex); + + nsNavHistoryResultNode* node = mVisibleElements[row]; + + switch (GetColumnType(col)) { + case Column_Title: + { + // title: normally, this is just the title, but we don't want empty + // items in the tree view so return a special string if the title is + // empty. Do it here so that callers can still get at the 0 length title + // if they go through the "result" API. + if (node->IsSeparator()) { + _retval.Truncate(0); + } else if (! node->mTitle.IsEmpty()) { + _retval = NS_ConvertUTF8toUTF16(node->mTitle); + } else { + nsXPIDLString value; + nsresult rv = + nsNavHistory::GetHistoryService()->GetBundle()->GetStringFromName( + NS_LITERAL_STRING("noTitle").get(), getter_Copies(value)); + NS_ENSURE_SUCCESS(rv, rv); + _retval = value; + } + break; + } + case Column_URI: + { + if (node->IsURI()) + _retval = NS_ConvertUTF8toUTF16(node->mURI); + else + _retval.Truncate(0); + break; + } + case Column_Date: + { + if (node->mTime == 0 || ! node->IsURI()) { + // hosts and days shouldn't have a value for the date column. Actually, + // you could argue this point, but looking at the results, seeing the + // most recently visited date is not what I expect, and gives me no + // information I know how to use. Only show this for URI-based items. + _retval.Truncate(0); + } else { + return FormatFriendlyTime(node->mTime, _retval); + } + break; + } + case Column_VisitCount: + { + if (node->IsSeparator()) { + _retval.Truncate(0); + } else { + _retval = NS_ConvertUTF8toUTF16(nsPrintfCString("%d", node->mAccessCount)); + } + break; + } + default: + return NS_ERROR_INVALID_ARG; + } + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::SetTree (nsITreeView) +// +// This will get called when this view is attached to the tree to tell us +// the box object, AND when the tree is being destroyed, with NULL. +// +// @see SetResult + +NS_IMETHODIMP nsNavHistoryResultTreeViewer::SetTree(nsITreeBoxObject* tree) +{ + PRBool hasOldTree = (mTree != nsnull); + mTree = tree; + + // do this before detaching from result when there is no tree. This ensures + // that the visible indices of the elements in the result have been set to -1 + FinishInit(); + + if (! tree && hasOldTree && mResult) { + // detach from result when we are detaching from the tree. This breaks the + // reference cycle between us and the result. + mResult->SetViewer(nsnull); + } +} + + +// nsNavHistoryResultTreeViewer::ToggleOpenState (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::ToggleOpenState(PRInt32 row) +{ + if (! mResult) + return NS_ERROR_UNEXPECTED; + if (row < 0 || row >= PRInt32(mVisibleElements.Length())) + return NS_ERROR_INVALID_ARG; + + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnToggleOpenState(row)) + + nsNavHistoryResultNode* node = mVisibleElements[row]; + if (! node->IsContainer()) + return NS_OK; // not a container, nothing to do + nsNavHistoryContainerResultNode* container = node->GetAsContainer(); + + nsresult rv; + if (container->mExpanded) + rv = container->CloseContainer(); + else + rv = container->OpenContainer(); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::CycleHeader (nsITreeView) +// +// If we already sorted by that column, the sorting is reversed, otherwise +// we use the default sorting direction for that data type. + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::CycleHeader(nsITreeColumn* col) +{ + if (! mResult) + return NS_ERROR_UNEXPECTED; + + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnCycleHeader(col)) + + PRInt32 colIndex; + col->GetIndex(&colIndex); + + PRInt32 oldSort = mResult->mSortingMode; + PRInt32 newSort; + switch (GetColumnType(col)) { + case Column_Title: + if (oldSort == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING) + newSort = nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING; + else + newSort = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING; + break; + case Column_URI: + if (oldSort == nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING) + newSort = nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING; + else + newSort = nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING; + break; + case Column_Date: + if (oldSort == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING) + newSort = nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING; + else + newSort = nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING; + break; + case Column_VisitCount: + // visit count default is unusual because it is descending + if (oldSort == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING) + newSort = nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING; + else + newSort = nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING; + break; + default: + return NS_ERROR_INVALID_ARG; + } + return mResult->SortAll(newSort); +} + + +// nsNavHistoryResultTreeViewer::SelectionChanged (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::SelectionChanged() +{ + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnSelectionChanged()) + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::CycleCell (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::CycleCell(PRInt32 row, nsITreeColumn* col) +{ + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnCycleCell(row, col)) + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::IsEditable (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::IsEditable(PRInt32 row, nsITreeColumn* col, + PRBool* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +// nsNavHistoryResultTreeViewer::SetCellValue (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::SetCellValue(PRInt32 row, nsITreeColumn* col, + const nsAString & value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +// nsNavHistoryResultTreeViewer::SetCellText (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::SetCellText(PRInt32 row, nsITreeColumn* col, + const nsAString & value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +// nsNavHistoryResultTreeViewer::PerformAction (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::PerformAction(const PRUnichar* action) +{ + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnPerformAction(action)) + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::PerformActionOnRow (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::PerformActionOnRow(const PRUnichar* action, + PRInt32 row) +{ + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnPerformActionOnRow(action, row)) + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::PerformActionOnCell (nsITreeView) + +NS_IMETHODIMP +nsNavHistoryResultTreeViewer::PerformActionOnCell(const PRUnichar* action, + PRInt32 row, + nsITreeColumn* col) +{ + ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver, + OnPerformActionOnCell(action, row, col)) + return NS_OK; +} + + +// nsNavHistoryResultTreeViewer::FormatFriendlyTime + +nsresult +nsNavHistoryResultTreeViewer::FormatFriendlyTime(PRTime aTime, + nsAString& aResult) +{ + // use navHistory's GetNow function for performance + nsNavHistory* history = nsNavHistory::GetHistoryService(); + NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); + PRTime now = history->GetNow(); + + const PRInt64 ago = now - aTime; + + /* + * This code generates strings like "X minutes ago" when the time is very + * recent. This looks much nicer, but we will have to redraw the list + * periodically. I also found it a little strange watching the numbers in + * the list change as I browse. For now, I'll leave this commented out, + * perhaps we can revisit whether it is a good idea to do this and put in + * the proper auto-refresh code so the numbers stay correct. + * + * To enable, you'll need to put these in places.properties: + * 0MinutesAgo=<1 minute ago + * 1MinutesAgo=1 minute ago + * XMinutesAgo=%s minutes ago + + static const PRInt64 minuteThreshold = (PRInt64)1000000 * 60 * 60; + if (ago > -10000000 && ago < minuteThreshold) { + // show "X minutes ago" + PRInt64 minutesAgo = NSToIntFloor( + NS_STATIC_CAST(float, (ago / 1000000)) / 60.0); + + nsXPIDLString resultString; + if (minutesAgo == 0) { + nsresult rv = mBundle->GetStringFromName( + NS_LITERAL_STRING("0MinutesAgo").get(), getter_Copies(resultString)); + NS_ENSURE_SUCCESS(rv, rv); + } else if (minutesAgo == 1) { + nsresult rv = mBundle->GetStringFromName( + NS_LITERAL_STRING("1MinutesAgo").get(), getter_Copies(resultString)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsAutoString minutesAgoString; + minutesAgoString.AppendInt(minutesAgo); + const PRUnichar* stringPtr = minutesAgoString.get(); + nsresult rv = mBundle->FormatStringFromName( + NS_LITERAL_STRING("XMinutesAgo").get(), + &stringPtr, 1, getter_Copies(resultString)); + NS_ENSURE_SUCCESS(rv, rv); + } + aResult = resultString; + return NS_OK; + } */ + + // Check if it is today and only display the time. Only bother checking for + // today if it's within the last 24 hours, since computing midnight is not + // really cheap. Sometimes we may get dates in the future, so always show + // those. Note the 10s slop in case the cached GetNow value in NavHistory is + // out-of-date. + nsDateFormatSelector dateFormat = nsIScriptableDateFormat::dateFormatShort; + if (ago > -10000000 && ago < (PRInt64)1000000 * 24 * 60 * 60) { + PRExplodedTime explodedTime; + PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime); + explodedTime.tm_min = + explodedTime.tm_hour = + explodedTime.tm_sec = + explodedTime.tm_usec = 0; + PRTime midnight = PR_ImplodeTime(&explodedTime); + if (aTime > midnight) + dateFormat = nsIScriptableDateFormat::dateFormatNone; + } + nsAutoString resultString; + history->GetDateFormatter()->FormatPRTime(history->GetLocale(), dateFormat, + nsIScriptableDateFormat::timeFormatNoSeconds, + aTime, resultString); + aResult = resultString; + return NS_OK; +} diff --git a/mozilla/browser/components/places/src/nsNavHistoryResult.h b/mozilla/browser/components/places/src/nsNavHistoryResult.h index 792951a1222..f26b32622ab 100644 --- a/mozilla/browser/components/places/src/nsNavHistoryResult.h +++ b/mozilla/browser/components/places/src/nsNavHistoryResult.h @@ -128,7 +128,11 @@ private: NS_IMETHOD GetTime(PRTime* aTime) \ { *aTime = mTime; return NS_OK; } \ NS_IMETHOD GetIndentLevel(PRUint32* aIndentLevel) \ - { *aIndentLevel = mIndentLevel; return NS_OK; } + { *aIndentLevel = mIndentLevel; return NS_OK; } \ + NS_IMETHOD GetViewIndex(PRInt32* aViewIndex) \ + { *aViewIndex = mViewIndex; return NS_OK; } \ + NS_IMETHOD SetViewIndex(PRInt32 aViewIndex) \ + { mViewIndex = aViewIndex; return NS_OK; } // This is used by the base classes instead of // NS_FORWARD_NSINAVHISTORYRESULTNODE(nsNavHistoryResultNode) because they @@ -273,11 +277,13 @@ public: // root's children will have a value of 0, and so on. PRInt32 mIndentLevel; - // The index into the result's mVisibleElements list of this element. This is - // -1 if it is invalid. For items, >= 0 can be used to determine if the node - // is visible in the list or not. For folders, call IsVisible, since they - // can be the root node which is not itself visible, but its children are. - PRInt32 mVisibleIndex; + // Value used by the view for whatever it wants. For the built-in tree view, + // this is the index into the result's mVisibleElements list of this element. + // This is -1 if it is invalid. For items, >= 0 can be used to determine if + // the node is visible in the list or not. For folders, call IsVisible, since + // they can be the root node which is not itself visible, but its children + // are. + PRInt32 mViewIndex; }; #ifndef MOZILLA_1_8_BRANCH @@ -379,6 +385,9 @@ public: NS_IMETHOD ClearContents() \ { return nsNavHistoryContainerResultNode::ClearContents(); } +#define NS_NAVHISTORYCONTAINERRESULTNODE_IID \ + { 0x6e3bf8d3, 0x22aa, 0x4065, { 0x86, 0xbc, 0x37, 0x46, 0xb5, 0xb3, 0x2c, 0xe8 } } + class nsNavHistoryContainerResultNode : public nsNavHistoryResultNode, public nsINavHistoryContainerResultNode { @@ -388,6 +397,12 @@ public: const nsACString& aIconURI, PRUint32 aContainerType, PRBool aReadOnly, const nsACString& aRemoteContainerType); +#ifdef MOZILLA_1_8_BRANCH + NS_DEFINE_STATIC_IID_ACCESSOR(NS_NAVHISTORYCONTAINERRESULTNODE_IID) +#else + NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYCONTAINERRESULTNODE_IID) +#endif + NS_DECL_ISUPPORTS_INHERITED NS_FORWARD_COMMON_RESULTNODE_TO_BASE NS_IMETHOD GetType(PRUint32* type) @@ -404,7 +419,7 @@ public: // overridded by descendents to populate virtual nsresult OpenContainer(); - nsresult CloseContainer(PRBool aUpdateVisible = PR_TRUE); + nsresult CloseContainer(PRBool aUpdateView = PR_TRUE); // this points to the result that owns this container. All containers have // their result pointer set so we can quickly get to the result without having @@ -631,6 +646,65 @@ public: { *type = nsNavHistoryResultNode::RESULT_TYPE_SEPARATOR; return NS_OK; } }; + +// nsNavHistoryResultTreeViewer +// + +class nsNavHistoryResultTreeViewer : public nsINavHistoryResultTreeViewer, + public nsITreeView +{ +public: + nsNavHistoryResultTreeViewer(); + virtual ~nsNavHistoryResultTreeViewer() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSINAVHISTORYRESULTVIEWER + NS_DECL_NSINAVHISTORYRESULTTREEVIEWER + NS_DECL_NSITREEVIEW + +protected: + nsRefPtr mResult; + nsCOMPtr mTree; // will be null when no tree attached + nsCOMPtr mSelection; // may be null + + PRBool mCollapseDuplicates; + + // This value indicates whether we should try to compute session boundaries. + // It is cached so we don't have to compute it every time we want to get a + // row style. + PRBool mShowSessions; + void ComputeShowSessions(); + + // This list is used to map rows to nodes. + typedef nsTArray< nsCOMPtr > VisibleList; + VisibleList mVisibleElements; + nsresult BuildVisibleList(); + nsresult BuildVisibleSection(nsNavHistoryContainerResultNode* aContainer, + VisibleList* aVisible, + PRUint32 aVisibleStartIndex); + PRUint32 CountVisibleRowsForItem(nsNavHistoryResultNode* aNode); + nsresult RefreshVisibleSection(nsNavHistoryContainerResultNode* aContainer); + + PRBool CanCollapseDuplicates(nsNavHistoryResultNode* aTop, + nsNavHistoryResultNode* aNext, + PRUint32* aShowThisOne); + + // external observers + nsMaybeWeakPtrArray mObservers; + + nsresult FinishInit(); + + // columns + enum ColumnType { Column_Unknown = -1, Column_Title, Column_URI, Column_Date, + Column_VisitCount }; + ColumnType GetColumnType(nsITreeColumn* col); + ColumnType SortTypeToColumnType(PRUint32 aSortType, + PRBool* aDescending = nsnull); + + nsresult FormatFriendlyTime(PRTime aTime, nsAString& aResult); +}; + + // nsNavHistoryResult // // nsNavHistory creates this object and fills in mChildren (by getting @@ -640,11 +714,12 @@ public: // This object implements nsITreeView so you can just set it to a tree // view and it will work. This object also observes the necessary history // and bookmark events to keep itself up-to-date. -// + +#define NS_NAVHISTORYRESULT_IID \ + { 0x455d1d40, 0x1b9b, 0x40e6, { 0xa6, 0x41, 0x8b, 0xb7, 0xe8, 0x82, 0x23, 0x87 } } class nsNavHistoryResult : public nsSupportsWeakReference, public nsINavHistoryResult, - public nsITreeView, public nsINavBookmarkObserver, public nsINavHistoryObserver { @@ -655,25 +730,30 @@ public: nsNavHistoryContainerResultNode* aRoot, nsNavHistoryResult** result); + // the tree viewer can go faster if it can bypass XPCOM + friend class nsNavHistoryResultTreeViewer; + +#ifdef MOZILLA_1_8_BRANCH + NS_DEFINE_STATIC_IID_ACCESSOR(NS_NAVHISTORYRESULT_IID) +#else + NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYRESULT_IID) +#endif + nsresult PropertyBagFor(nsISupports* aObject, nsIWritablePropertyBag** aBag); NS_DECL_ISUPPORTS NS_DECL_NSINAVHISTORYRESULT - NS_DECL_NSITREEVIEW NS_DECL_BOOKMARK_HISTORY_OBSERVER - // called by container nodes to update the tree when things change - nsresult RefreshVisibleSection(nsNavHistoryContainerResultNode* aContainer); - void AddEverythingObserver(nsNavHistoryQueryResultNode* aNode); void AddBookmarkObserver(nsNavHistoryFolderResultNode* aNode, PRInt64 aFolder); void RemoveEverythingObserver(nsNavHistoryQueryResultNode* aNode); void RemoveBookmarkObserver(nsNavHistoryFolderResultNode* aNode, PRInt64 aFolder); - PRBool CanCollapseDuplicates(nsNavHistoryResultNode* aTop, - nsNavHistoryResultNode* aNext, - PRUint32* aShowThisOne); + // returns the view. NOT-ADDREFED. May be NULL if there is no view + nsINavHistoryResultViewer* GetView() const + { return mView; } public: // two-stage init, use NewHistoryResult to construct @@ -692,30 +772,11 @@ public: // but may be overridden if the user clicks on one of the columns. PRUint32 mSortingMode; - PRBool mCollapseDuplicates; - - // This value indicates whether we should try to compute session boundaries. - // It is cached so we don't have to compute it every time we want to get a - // row style. - PRBool mShowSessions; - void ComputeShowSessions(); + nsCOMPtr mView; // property bags for all result nodes, see PropertyBagFor nsInterfaceHashtable mPropertyBags; - nsCOMPtr mTree; // will be null if no tree bound - nsCOMPtr mSelection; // may be null - - // This list is maintained only when a tree is attached (mTree != null) to - // the result. It is used to map rows to nodes. - typedef nsTArray< nsCOMPtr > VisibleList; - VisibleList mVisibleElements; - nsresult BuildVisibleList(); - nsresult BuildVisibleSection(nsNavHistoryContainerResultNode* aContainer, - VisibleList* aVisible, - PRUint32 aVisibleStartIndex); - PRUint32 CountVisibleRowsForItem(nsNavHistoryResultNode* aNode); - // node observers PRBool mIsHistoryObserver; PRBool mIsBookmarksObserver; @@ -724,37 +785,10 @@ public: nsDataHashtable mBookmarkObservers; FolderObserverList* BookmarkObserversForId(PRInt64 aFolderId, PRBool aCreate); - // external observers - nsMaybeWeakPtrArray mObservers; - - // columns - enum ColumnType { Column_Unknown = -1, Column_Title, Column_URI, Column_Date, - Column_VisitCount }; - ColumnType GetColumnType(nsITreeColumn* col); - ColumnType SortTypeToColumnType(PRUint32 aSortType, - PRBool* aDescending = nsnull); - - void SetTreeSortingIndicator(); void RecursiveExpandCollapse(nsNavHistoryContainerResultNode* aContainer, PRBool aExpand); void InvalidateTree(); - - nsresult FormatFriendlyTime(PRTime aTime, nsAString& aResult); - - // Notify the result that a row has been added at index aIndex relative - // to aParent. - void RowAdded(PRInt32 aVisibleIndex, nsNavHistoryResultNode* aNode); - - // Notify the result that the row with visible index aVisibleIndex has been - // removed from the tree. - void RowsRemoved(PRInt32 aVisibleIndex, PRUint32 aCount = 1); - - // Notify the result that the contents of the row at visible index - // aVisibleIndex has been modified. - void RowChanged(PRInt32 aVisibleIndex); - - void RowReplaced(PRInt32 aVisibleIndex, nsNavHistoryResultNode* aNode); }; #endif // nsNavHistoryResult_h_