Original committer: bryner%brianryner.com Original revision: 1.30 Original date: 2006/01/06 01:38:44 git-svn-id: svn://10.0.0.236/trunk@202955 18797224-902f-48f8-a5cc-f745e15eee43
2474 lines
74 KiB
C++
2474 lines
74 KiB
C++
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla History System
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Google Inc.
|
|
* Portions created by the Initial Developer are Copyright (C) 2005
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Brett Wilson <brettw@gmail.com> (original author)
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
#include <stdio.h>
|
|
#include "nsNavHistory.h"
|
|
#include "nsNavBookmarks.h"
|
|
|
|
#include "nsArray.h"
|
|
#include "nsCollationCID.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsDateTimeFormatCID.h"
|
|
#include "nsDebug.h"
|
|
#include "nsFaviconService.h"
|
|
#include "nsIComponentManager.h"
|
|
#include "nsIDateTimeFormat.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsILocale.h"
|
|
#include "nsILocaleService.h"
|
|
#include "nsILocalFile.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsITreeColumns.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIURL.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsPromiseFlatString.h"
|
|
#include "nsString.h"
|
|
#include "nsUnicharUtils.h"
|
|
#include "prtime.h"
|
|
#include "prprf.h"
|
|
#include "mozStorageHelper.h"
|
|
|
|
// emulate string comparison (used for sorting) for PRTime and int
|
|
inline PRInt32 ComparePRTime(PRTime a, PRTime b)
|
|
{
|
|
if (LL_CMP(a, <, b))
|
|
return -1;
|
|
else if (LL_CMP(a, >, b))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
inline PRInt32 CompareIntegers(PRUint32 a, PRUint32 b)
|
|
{
|
|
return a - b;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResultNode ******************************************************
|
|
|
|
NS_IMPL_ISUPPORTS2(nsNavHistoryResultNode,
|
|
nsNavHistoryResultNode, nsINavHistoryResultNode)
|
|
|
|
nsNavHistoryResultNode::nsNavHistoryResultNode() :
|
|
mParent(nsnull),
|
|
mID(0),
|
|
mAccessCount(0),
|
|
mTime(0),
|
|
mSessionID(0),
|
|
mExpanded(PR_FALSE)
|
|
{
|
|
}
|
|
|
|
/* attribute nsINavHistoryResultNode parent */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetParent(nsINavHistoryResultNode** parent)
|
|
{
|
|
NS_IF_ADDREF(*parent = mParent);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute PRInt32 type; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetType(PRUint32 *aType)
|
|
{
|
|
*aType = mType;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute string url; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetUrl(nsACString& aUrl)
|
|
{
|
|
aUrl = mUrl;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute PRInt64 folderId; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetFolderId(PRInt64 *aID)
|
|
{
|
|
*aID = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void getQueries(out unsigned long queryCount,
|
|
[retval,array,size_is(queryCount)] out nsINavHistoryQuery queries); */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetQueries(PRUint32 *aQueryCount,
|
|
nsINavHistoryQuery ***aQueries)
|
|
{
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
/* readonly attribute nsINavHistoryQueryOptions options */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetQueryOptions(nsINavHistoryQueryOptions **aOptions)
|
|
{
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
/* attribute string title; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetTitle(nsAString& aTitle)
|
|
{
|
|
aTitle = mTitle;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute string folderType; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetFolderType(nsAString& aFolderType)
|
|
{
|
|
aFolderType.Truncate(0);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute PRInt32 accessCount; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetAccessCount(PRInt32 *aAccessCount)
|
|
{
|
|
*aAccessCount = mAccessCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute PRTime time; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetTime(PRTime *aTime)
|
|
{
|
|
*aTime = mTime;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute nsIURI con; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetIcon(nsIURI** aURI)
|
|
{
|
|
nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
|
|
NS_ENSURE_TRUE(faviconService, NS_ERROR_NO_INTERFACE);
|
|
return faviconService->GetFaviconLinkForIconString(mFaviconURL, aURI);
|
|
}
|
|
|
|
/* attribute pRInt32 indentLevel; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetIndentLevel(PRInt32* aIndentLevel)
|
|
{
|
|
*aIndentLevel = mIndentLevel;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* attribute PRInt32 childCount; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetChildCount(PRInt32* aChildCount)
|
|
{
|
|
*aChildCount = mChildren.Count();
|
|
return NS_OK;
|
|
}
|
|
|
|
/* nsINavHistoryResultNode getChild(in PRInt32 index); */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetChild(PRInt32 aIndex,
|
|
nsINavHistoryResultNode** _retval)
|
|
{
|
|
if (aIndex < 0 || aIndex >= mChildren.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
NS_ADDREF(*_retval = mChildren[aIndex]);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* readonly attribute boolean childrenReadOnly; */
|
|
NS_IMETHODIMP nsNavHistoryResultNode::GetChildrenReadOnly(PRBool *aResult)
|
|
{
|
|
*aResult = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsINavBookmarkObserver implementation
|
|
|
|
/* void onBeginUpdateBatch(); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnBeginUpdateBatch()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onEndUpdateBatch(); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnEndUpdateBatch()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemAdded(in nsIURI bookmark, in PRInt64 folder, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnItemAdded(nsIURI *aBookmark,
|
|
PRInt64 aFolder,
|
|
PRInt32 aIndex)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemRemoved(in nsIURI bookmark, in PRInt64 folder, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnItemRemoved(nsIURI *aBookmark,
|
|
PRInt64 aFolder, PRInt32 aIndex)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemMoved(in nsIURI bookmark, in PRInt64 folder,
|
|
in PRInt32 oldIndex, in PRInt32 newIndex); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnItemMoved(nsIURI *aBookmark, PRInt64 aFolder,
|
|
PRInt32 aOldIndex, PRInt32 aNewIndex)
|
|
{
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
/* void onItemChanged(in nsIURI bookmark, in ACString property, in AString value); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnItemChanged(nsIURI *aBookmark,
|
|
const nsACString &aProperty,
|
|
const nsAString &aValue)
|
|
{
|
|
// We let OnPageChanged handle this case
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemVisited(in nsIURI bookmark, in PRTime time); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnItemVisited(nsIURI* aBookmark, PRTime aVisitTime)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemReplaced(in PRInt64 folder, in nsIURI item, in nsIURI newItem); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnItemReplaced(PRInt64 aFolder,
|
|
nsIURI *aItem, nsIURI *aNewItem)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderAdded(in PRInt64 folder, in PRInt64 parent, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnFolderAdded(PRInt64 aFolder,
|
|
PRInt64 aParent, PRInt32 aIndex)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderRemoved(in PRInt64 folder, in PRInt64 parent, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnFolderRemoved(PRInt64 aFolder,
|
|
PRInt64 aParent, PRInt32 aIndex)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderMoved(in PRInt64 folder,
|
|
in PRInt64 oldParent, in PRInt32 oldIndex,
|
|
in PRInt64 newParent, in PRInt32 newIndex); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnFolderMoved(PRInt64 aFolder,
|
|
PRInt64 aOldParent, PRInt32 aOldIndex,
|
|
PRInt64 aNewParent, PRInt32 aNewIndex)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderChanged(in PRInt64 folder, in ACString property); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnFolderChanged(PRInt64 aFolder,
|
|
const nsACString &aProperty)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsINavHistoryObserver implementation
|
|
|
|
/* void onVisit(in nsIURI aURI, in PRInt64 aVisitID, in PRTime aTime,
|
|
in PRInt64 aSessionID, in PRInt64 aReferringID,
|
|
in PRUint32 aTransitionType); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnVisit(nsIURI* aURI, PRInt64 aVisitID, PRTime aTime,
|
|
PRInt64 aSessionID, PRInt64 aReferringID,
|
|
PRUint32 aTransitionType)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onTitleChanged(in nsIURI aURI, in AString aPageTitle,
|
|
in AString aUserTitle, in PRBool aUserTitleChanged); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnTitleChanged(nsIURI* aURI, const nsAString& aPageTitle,
|
|
const nsAString& aUserTitle,
|
|
PRBool aIsUserTitleChanged)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onDeleteURI(in nsIURI aURI); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnDeleteURI(nsIURI *aURI)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onClearHistory(); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnClearHistory()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onPageChanged(in nsIURI aURI, in PRUint32 aWhat, in AString aValue); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResultNode::OnPageChanged(nsIURI *aURI,
|
|
PRUint32 aWhat, const nsAString &aValue)
|
|
{
|
|
// We're an item node in a bookmark folder. Check if the given URL
|
|
// matches ours, and rebuild the row if so.
|
|
nsCAutoString spec;
|
|
aURI->GetSpec(spec);
|
|
if (! spec.Equals(mUrl))
|
|
return NS_OK; // not ours
|
|
|
|
// TODO(bryner): only rebuild if aProperty is being shown
|
|
|
|
if (aWhat == nsINavHistoryObserver::ATTRIBUTE_FAVICON) {
|
|
mFaviconURL = NS_ConvertUTF16toUTF8(aValue);
|
|
return NS_OK;
|
|
}
|
|
|
|
Rebuild();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsNavHistoryResult*
|
|
nsNavHistoryResultNode::GetResult()
|
|
{
|
|
nsNavHistoryResultNode *parent, *node = this;
|
|
while ((parent = node->mParent)) {
|
|
node = parent;
|
|
}
|
|
|
|
nsCOMPtr<nsINavHistoryResult> result = do_QueryInterface(node);
|
|
NS_ASSERTION(result, "toplevel node must be a result");
|
|
return NS_STATIC_CAST(nsNavHistoryResult*,
|
|
NS_STATIC_CAST(nsINavHistoryResult*, result));
|
|
}
|
|
|
|
nsresult
|
|
nsNavHistoryResultNode::Rebuild()
|
|
{
|
|
if (mID == 0) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsNavHistory *history = nsNavHistory::GetHistoryService();
|
|
mozIStorageStatement *statement = history->DBGetIdPageInfoFull();
|
|
mozStorageStatementScoper scope(statement);
|
|
|
|
nsresult rv = statement->BindInt64Parameter(0, mID);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
PRBool results;
|
|
rv = statement->ExecuteStep(&results);
|
|
NS_ASSERTION(results, "node must be in history!");
|
|
|
|
// get the concrete options class that generated this node
|
|
nsCOMPtr<nsINavHistoryQueryOptions> optionsInterface;
|
|
rv = GetResult()->GetQueryOptions(getter_AddRefs(optionsInterface));
|
|
nsCOMPtr<nsNavHistoryQueryOptions> options =
|
|
do_QueryInterface(optionsInterface, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return history->FillURLResult(statement, options, this);
|
|
}
|
|
|
|
// nsNavHistoryQueryNode ******************************************************
|
|
|
|
nsNavHistoryQueryNode::~nsNavHistoryQueryNode()
|
|
{
|
|
for (PRUint32 i = 0; i < mQueryCount; ++i) {
|
|
NS_RELEASE(mQueries[i]);
|
|
}
|
|
nsMemory::Free(mQueries);
|
|
}
|
|
|
|
nsresult
|
|
nsNavHistoryQueryNode::ParseQueries()
|
|
{
|
|
nsNavHistory *history = nsNavHistory::GetHistoryService();
|
|
nsCOMPtr<nsINavHistoryQueryOptions> options;
|
|
nsresult rv = history->QueryStringToQueries(QueryURIToQuery(mUrl),
|
|
&mQueries, &mQueryCount,
|
|
getter_AddRefs(options));
|
|
mOptions = do_QueryInterface(options);
|
|
return rv;
|
|
}
|
|
|
|
PRInt64
|
|
nsNavHistoryQueryNode::FolderId() const
|
|
{
|
|
PRInt64 id;
|
|
if (mQueryCount > 0) {
|
|
const nsTArray<PRInt64>& folders =
|
|
NS_STATIC_CAST(nsNavHistoryQuery*, mQueries[0])->Folders();
|
|
id = (folders.Length() == 1) ? folders[0] : 0;
|
|
} else {
|
|
id = 0;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
/* attribute string folderType; */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::GetFolderType(nsAString& aFolderType)
|
|
{
|
|
aFolderType = mFolderType;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::GetQueries(PRUint32 *aQueryCount,
|
|
nsINavHistoryQuery ***aQueries)
|
|
{
|
|
if (!mOptions) {
|
|
nsresult rv = ParseQueries();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsINavHistoryQuery **queries = nsnull;
|
|
if (mQueryCount > 0) {
|
|
queries = NS_STATIC_CAST(nsINavHistoryQuery**,
|
|
nsMemory::Alloc(mQueryCount * sizeof(nsINavHistoryQuery*)));
|
|
NS_ENSURE_TRUE(queries, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
for (PRUint32 i = 0; i < mQueryCount; ++i) {
|
|
NS_ADDREF(queries[i] = mQueries[i]);
|
|
}
|
|
}
|
|
|
|
*aQueryCount = mQueryCount;
|
|
*aQueries = queries;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::GetQueryOptions(nsINavHistoryQueryOptions **aOptions)
|
|
{
|
|
if (!mOptions) {
|
|
nsresult rv = ParseQueries();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
NS_ADDREF(*aOptions = mOptions);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNavHistoryQueryNode::BuildChildren(PRBool *aBuilt)
|
|
{
|
|
if (mBuiltChildren) {
|
|
*aBuilt = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
*aBuilt = PR_TRUE;
|
|
nsresult rv;
|
|
if (!mOptions) {
|
|
rv = ParseQueries();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<nsINavHistoryResult> iResult;
|
|
nsNavHistory *history = nsNavHistory::GetHistoryService();
|
|
rv = history->ExecuteQueries(mQueries, mQueryCount, mOptions,
|
|
getter_AddRefs(iResult));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsNavHistoryResult *result =
|
|
NS_STATIC_CAST(nsNavHistoryResult*, NS_STATIC_CAST(nsINavHistoryResult*,
|
|
iResult));
|
|
|
|
mChildren.Clear();
|
|
NS_ENSURE_TRUE(mChildren.AppendObjects(*(result->GetTopLevel())),
|
|
NS_ERROR_OUT_OF_MEMORY);
|
|
mBuiltChildren = PR_TRUE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNavHistoryQueryNode::UpdateQuery()
|
|
{
|
|
mBuiltChildren = PR_FALSE;
|
|
if (mExpanded) {
|
|
nsNavHistoryResult *result = GetResult();
|
|
nsresult rv = result->BuildChildrenFor(this);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
result->Invalidate();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsINavBookmarkObserver implementation
|
|
|
|
/* void onBeginUpdateBatch(); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnBeginUpdateBatch()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onEndUpdateBatch(); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnEndUpdateBatch()
|
|
{
|
|
// TODO(bryner): Batch updates
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::GetChildrenReadOnly(PRBool *aResult)
|
|
{
|
|
PRInt64 folderId = FolderId();
|
|
if (folderId == 0) {
|
|
*aResult = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
return nsNavBookmarks::GetBookmarksService()->GetFolderReadonly(folderId,
|
|
aResult);
|
|
}
|
|
|
|
nsresult
|
|
nsNavHistoryQueryNode::CreateNode(nsIURI *aBookmark,
|
|
nsNavHistoryResultNode **aNode)
|
|
{
|
|
nsNavHistory *history = nsNavHistory::GetHistoryService();
|
|
mozIStorageStatement *statement = history->DBGetURLPageInfoFull();
|
|
mozStorageStatementScoper scope(statement);
|
|
|
|
nsresult rv = BindStatementURI(statement, 0, aBookmark);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
PRBool results;
|
|
rv = statement->ExecuteStep(&results);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
NS_ASSERTION(results, "item must be in history!");
|
|
|
|
return history->RowToResult(statement, mOptions, aNode);
|
|
}
|
|
|
|
PRBool
|
|
nsNavHistoryQueryNode::HasFilteredChildren() const
|
|
{
|
|
if (mQueryCount != 1) {
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRUint32 itemTypes;
|
|
mQueries[0]->GetItemTypes(&itemTypes);
|
|
return itemTypes != (nsINavHistoryQuery::INCLUDE_ITEMS |
|
|
nsINavHistoryQuery::INCLUDE_QUERIES);
|
|
}
|
|
|
|
/* void onItemAdded(in nsIURI bookmark, in PRInt64 folder, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnItemAdded(nsIURI *aBookmark,
|
|
PRInt64 aFolder, PRInt32 aIndex)
|
|
{
|
|
nsresult rv;
|
|
if (FolderId() == aFolder) {
|
|
// If we're not expanded, we can just invalidate our child list
|
|
// and rebuild it the next time we're opened.
|
|
if (!mExpanded) {
|
|
mBuiltChildren = PR_FALSE; // causes a rebuild on next open
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we're a special query type, just requery.
|
|
if (HasFilteredChildren()) {
|
|
rv = UpdateQuery();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
// This node is expanded and is the target of this add, so
|
|
// create a new result node and insert it.
|
|
nsRefPtr<nsNavHistoryResultNode> node;
|
|
rv = CreateNode(aBookmark, getter_AddRefs(node));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
NS_ENSURE_TRUE(mChildren.InsertObjectAt(node, aIndex),
|
|
NS_ERROR_OUT_OF_MEMORY);
|
|
GetResult()->RowAdded(this, aIndex);
|
|
}
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnItemAdded(aBookmark, aFolder, aIndex);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemRemoved(in nsIURI bookmark, in PRInt64 folder, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnItemRemoved(nsIURI *aBookmark,
|
|
PRInt64 aFolder, PRInt32 aIndex)
|
|
{
|
|
if (FolderId() == aFolder) {
|
|
if (aIndex < 0 || aIndex >= mChildren.Count()) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// If we're not expanded, we can just invalidate our child list
|
|
// and rebuild it the next time we're opened.
|
|
if (!mExpanded) {
|
|
mBuiltChildren = PR_FALSE; // causes a rebuild on next open
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we're a special query type, just requery.
|
|
if (HasFilteredChildren()) {
|
|
return UpdateQuery();
|
|
} else {
|
|
// Remove the ResultNode for the URI
|
|
PRInt32 index = mChildren[aIndex]->VisibleIndex();
|
|
mChildren.RemoveObjectAt(aIndex);
|
|
GetResult()->RowRemoved(index);
|
|
}
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
nsresult rv = mChildren[i]->OnItemRemoved(aBookmark, aFolder, aIndex);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemMoved(in nsIURI bookmark, in PRInt64 folder,
|
|
in PRInt32 oldIndex, in PRInt32 newIndex); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnItemMoved(nsIURI *aBookmark, PRInt64 aFolder,
|
|
PRInt32 aOldIndex, PRInt32 aNewIndex)
|
|
{
|
|
if (FolderId() == aFolder) {
|
|
// If we're not expanded, we can just invalidate our child list
|
|
// and rebuild it the next time we're opened.
|
|
if (!mExpanded) {
|
|
mBuiltChildren = PR_FALSE; // causes a rebuild on next open
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we're a special query type, just requery.
|
|
if (HasFilteredChildren()) {
|
|
return UpdateQuery();
|
|
} else {
|
|
nsRefPtr<nsNavHistoryResultNode> node = mChildren[aOldIndex];
|
|
PRInt32 delta = (aNewIndex > aOldIndex) ? 1 : -1;
|
|
for (PRInt32 i = aOldIndex; i != aNewIndex; i += delta) {
|
|
mChildren.ReplaceObjectAt(mChildren[i + delta], i);
|
|
}
|
|
mChildren.ReplaceObjectAt(node, aNewIndex);
|
|
// TODO(bryner): we should only invalidate the affected rows
|
|
GetResult()->Invalidate();
|
|
}
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
nsresult rv = mChildren[i]->OnItemMoved(aBookmark, aFolder,
|
|
aOldIndex, aNewIndex);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemChanged(in nsIURI bookmark, in ACString property, in AStirng value); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnItemChanged(nsIURI *aBookmark,
|
|
const nsACString &aProperty,
|
|
const nsAString &aValue)
|
|
{
|
|
// We let OnPageChanged handle this case. FIXME: This should be able to do
|
|
// all the work necessary from bookmark callbacks, so this needs to handle
|
|
// all bookmark cases.
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemVisited(in nsIURI bookmark, in PRTime time); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnItemVisited(nsIURI* aBookmark, PRTime aVisitTime)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onItemReplaced(in PRInt64 folder, in nsIURI item, in nsIURI newItem); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnItemReplaced(PRInt64 aFolder,
|
|
nsIURI *aItem, nsIURI *aNewItem)
|
|
{
|
|
nsresult rv;
|
|
if (FolderId() == aFolder) {
|
|
// If we're not expanded, we can just invalidate our child list
|
|
// and rebuild it the next time we're opened.
|
|
if (!mExpanded) {
|
|
mBuiltChildren = PR_FALSE; // causes a rebuild on next open
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we're a special query type, just requery.
|
|
if (HasFilteredChildren()) {
|
|
return UpdateQuery();
|
|
} else {
|
|
// Replace the old node (aItem) with a new node for aNewItem.
|
|
// We copy the indices from aItem since it is at the same location.
|
|
nsRefPtr<nsNavHistoryResultNode> node;
|
|
rv = CreateNode(aNewItem, getter_AddRefs(node));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
|
|
PRInt32 index;
|
|
bookmarks->IndexOfItem(aFolder, aNewItem, &index);
|
|
|
|
PRInt32 visibleIndex = mChildren[index]->VisibleIndex();
|
|
node->SetParent(this);
|
|
node->SetVisibleIndex(visibleIndex);
|
|
node->SetIndentLevel(mChildren[index]->IndentLevel());
|
|
mChildren.ReplaceObjectAt(node, index);
|
|
GetResult()->RowChanged(node->VisibleIndex());
|
|
}
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnItemReplaced(aFolder, aItem, aNewItem);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderAdded(in PRInt64 folder, in PRInt64 parent, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnFolderAdded(PRInt64 aFolder,
|
|
PRInt64 aParent, PRInt32 aIndex)
|
|
{
|
|
nsresult rv;
|
|
if (FolderId() == aParent) {
|
|
// If we're not expanded, we can just invalidate our child list
|
|
// and rebuild it the next time we're opened.
|
|
if (!mExpanded) {
|
|
mBuiltChildren = PR_FALSE; // causes a rebuild on next open
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we're a special query type, just requery.
|
|
if (HasFilteredChildren()) {
|
|
return UpdateQuery();
|
|
} else {
|
|
// This node is expanded and is the target of this add, so
|
|
// create a new result node and insert it.
|
|
nsRefPtr<nsNavHistoryResultNode> node;
|
|
rv = nsNavBookmarks::GetBookmarksService()->
|
|
ResultNodeForFolder(aFolder, mQueries[0], mOptions,
|
|
getter_AddRefs(node));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
NS_ENSURE_TRUE(mChildren.InsertObjectAt(node, aIndex),
|
|
NS_ERROR_OUT_OF_MEMORY);
|
|
GetResult()->RowAdded(this, aIndex);
|
|
}
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnFolderAdded(aFolder, aParent, aIndex);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderRemoved(in PRInt64 folder, in PRInt64 parent, in PRInt32 index); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnFolderRemoved(PRInt64 aFolder,
|
|
PRInt64 aParent, PRInt32 aIndex)
|
|
{
|
|
if (FolderId() == aParent) {
|
|
if (aIndex < 0 || aIndex >= mChildren.Count()) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// If we're not expanded, we can just invalidate our child list
|
|
// and rebuild it the next time we're opened.
|
|
if (!mExpanded) {
|
|
mBuiltChildren = PR_FALSE; // causes a rebuild on next open
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we're a special query type, just requery.
|
|
if (HasFilteredChildren()) {
|
|
return UpdateQuery();
|
|
} else {
|
|
// Remove the folder node from our child list.
|
|
PRInt32 index = mChildren[aIndex]->VisibleIndex();
|
|
mChildren.RemoveObjectAt(aIndex);
|
|
GetResult()->RowRemoved(index);
|
|
}
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
nsresult rv = mChildren[i]->OnFolderRemoved(aFolder, aParent, aIndex);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderMoved(in PRInt64 folder,
|
|
in PRInt64 oldParent, in PRInt32 oldIndex,
|
|
in PRInt64 newParent, in PRInt32 newIndex); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnFolderMoved(PRInt64 aFolder,
|
|
PRInt64 aOldParent, PRInt32 aOldIndex,
|
|
PRInt64 aNewParent, PRInt32 aNewIndex)
|
|
{
|
|
nsresult rv;
|
|
PRInt64 nodeFolder = FolderId();
|
|
|
|
if (aOldParent == aNewParent && aOldParent == nodeFolder) {
|
|
// If we're not expanded, we can just invalidate our child list
|
|
// and rebuild it the next time we're opened.
|
|
if (!mExpanded) {
|
|
mBuiltChildren = PR_FALSE; // causes a rebuild on next open
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we're a special query type, just requery.
|
|
if (HasFilteredChildren()) {
|
|
return UpdateQuery();
|
|
} else {
|
|
nsRefPtr<nsNavHistoryResultNode> node = mChildren[aOldIndex];
|
|
PRInt32 delta = (aNewIndex > aOldIndex) ? 1 : -1;
|
|
for (PRInt32 i = aOldIndex; i != aNewIndex; i += delta) {
|
|
mChildren.ReplaceObjectAt(mChildren[i + delta], i);
|
|
}
|
|
mChildren.ReplaceObjectAt(node, aNewIndex);
|
|
// TODO(bryner): we should only invalidate the affected rows
|
|
GetResult()->Invalidate();
|
|
}
|
|
} else if (aOldParent == nodeFolder) {
|
|
OnFolderRemoved(aFolder, aOldParent, aOldIndex);
|
|
} else if (aNewParent == nodeFolder) {
|
|
OnFolderAdded(aFolder, aNewParent, aNewIndex);
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnFolderMoved(aFolder,
|
|
aOldParent, aOldIndex,
|
|
aNewParent, aNewIndex);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onFolderChanged(in PRInt64 folder, in ACString property); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnFolderChanged(PRInt64 aFolder,
|
|
const nsACString &aProperty)
|
|
{
|
|
if (FolderId() == aFolder) {
|
|
// TODO(bryner): only rebuild if aProperty is being shown
|
|
Rebuild();
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
nsresult rv = mChildren[i]->OnFolderChanged(aFolder, aProperty);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsINavHistoryObserver implementation
|
|
|
|
/* void onVisit(in nsIURI aURI, in PRInt64 aVisitID, in PRTime aTime,
|
|
in PRInt64 aSessionID, in PRInt64 aReferringID,
|
|
in PRUint32 aTransitionType); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnVisit(nsIURI* aURI, PRInt64 aVisitID, PRTime aTime,
|
|
PRInt64 aSessionID, PRInt64 aReferringID,
|
|
PRUint32 aTransitionType)
|
|
{
|
|
nsresult rv;
|
|
|
|
// embedded transitions are not visible in queries unless you want to include
|
|
// hidden ones, so we can ignore these notifications (which comprise the bulk
|
|
// of history)
|
|
if (aTransitionType == nsINavHistoryService::TRANSITION_EMBED &&
|
|
! mOptions->IncludeHidden())
|
|
return NS_OK;
|
|
|
|
if (FolderId() == 0) {
|
|
// We're a non-folder query, so we need to requery.
|
|
return UpdateQuery();
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnVisit(aURI, aVisitID, aTime, aSessionID,
|
|
aReferringID, aTransitionType);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onTitleChange */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnTitleChanged(nsIURI* aURI, const nsAString& aPageTitle,
|
|
const nsAString& aUserTitle,
|
|
PRBool aIsUserTitleChanged)
|
|
{
|
|
nsresult rv;
|
|
PRInt64 folder = nsNavHistoryQueryNode::FolderId();
|
|
if (folder == 0) {
|
|
// If we're a query node (other than a folder), we need to re-execute
|
|
// our queries in case aBookmark should be added/removed from the
|
|
// results.
|
|
return UpdateQuery();
|
|
} else {
|
|
// We're a bookmark folder. Run through our children and notify them.
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnTitleChanged(aURI, aPageTitle, aUserTitle, aIsUserTitleChanged);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/* void onDeleteURI(in nsIURI aURI); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnDeleteURI(nsIURI *aURI)
|
|
{
|
|
nsresult rv;
|
|
if (FolderId() == 0) {
|
|
// We're a non-folder query, so we need to requery.
|
|
return UpdateQuery();
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnDeleteURI(aURI);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onClearHistory(); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnClearHistory()
|
|
{
|
|
nsresult rv;
|
|
if (FolderId() == 0) {
|
|
// We're a non-folder query, so we need to requery.
|
|
return UpdateQuery();
|
|
} else {
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnClearHistory();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void onPageChanged(in nsIURI aURI, in PRUint32 aWhat, in AString aValue); */
|
|
NS_IMETHODIMP
|
|
nsNavHistoryQueryNode::OnPageChanged(nsIURI *aURI,
|
|
PRUint32 aWhat, const nsAString &aValue)
|
|
{
|
|
nsresult rv;
|
|
PRInt64 folder = nsNavHistoryQueryNode::FolderId();
|
|
if (folder == 0) {
|
|
// If we're a query node (other than a folder), we need to re-execute
|
|
// our queries in case aBookmark should be added/removed from the
|
|
// results.
|
|
return UpdateQuery();
|
|
} else {
|
|
// We're a bookmark folder. Run through our children and notify them.
|
|
for (PRInt32 i = 0; i < mChildren.Count(); ++i) {
|
|
rv = mChildren[i]->OnPageChanged(aURI, aWhat, aValue);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNavHistoryQueryNode::Rebuild()
|
|
{
|
|
PRInt64 folderId = FolderId();
|
|
if (folderId != 0) {
|
|
nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
|
|
return bookmarks->FillFolderNode(folderId, this);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsNavHistoryResult **********************************************************
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED5(nsNavHistoryResult, nsNavHistoryQueryNode,
|
|
nsINavHistoryResult,
|
|
nsITreeView,
|
|
nsINavBookmarkObserver,
|
|
nsINavHistoryObserver,
|
|
nsISupportsWeakReference)
|
|
|
|
|
|
// nsNavHistoryResult::nsNavHistoryResult
|
|
|
|
nsNavHistoryResult::nsNavHistoryResult(nsNavHistory* aHistoryService,
|
|
nsIStringBundle* aHistoryBundle,
|
|
nsINavHistoryQuery** aQueries,
|
|
PRUint32 aQueryCount,
|
|
nsNavHistoryQueryOptions* aOptions)
|
|
: mBundle(aHistoryBundle), mHistoryService(aHistoryService),
|
|
mCollapseDuplicates(PR_TRUE),
|
|
mShowSessions(PR_FALSE)
|
|
{
|
|
NS_ASSERTION(aOptions, "must have options!");
|
|
// Fill saved source queries with copies of the original (the caller might
|
|
// change their original objects, and we always want to reflect the source
|
|
// parameters).
|
|
// XXX this should be in Init() since it could fail to alloc
|
|
if (aQueryCount > 0) {
|
|
mQueries = NS_STATIC_CAST(nsINavHistoryQuery**,
|
|
nsMemory::Alloc(aQueryCount * sizeof(nsINavHistoryQuery*)));
|
|
|
|
for (PRUint32 i = 0; i < aQueryCount; i ++) {
|
|
nsCOMPtr<nsINavHistoryQuery> queryClone;
|
|
if (NS_SUCCEEDED(aQueries[i]->Clone(getter_AddRefs(queryClone)))) {
|
|
mQueries[i] = nsnull;
|
|
queryClone.swap(mQueries[i]);
|
|
}
|
|
}
|
|
mQueryCount = aQueryCount;
|
|
}
|
|
aOptions->Clone(getter_AddRefs(mOptions));
|
|
|
|
PRInt64 folderId = 0;
|
|
GetFolderId(&folderId);
|
|
if (folderId != 0) {
|
|
nsNavBookmarks::GetBookmarksService()->FillFolderNode(folderId, this);
|
|
}
|
|
|
|
mType = nsINavHistoryResult::RESULT_TYPE_QUERY;
|
|
mVisibleIndex = -1;
|
|
mExpanded = PR_TRUE; // the result itself can never be "collapsed"
|
|
}
|
|
|
|
// nsNavHistoryResult::~nsNavHistoryResult
|
|
|
|
nsNavHistoryResult::~nsNavHistoryResult()
|
|
{
|
|
nsNavBookmarks::GetBookmarksService()->RemoveObserver(this);
|
|
nsNavHistory::GetHistoryService()->RemoveObserver(this);
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::Init
|
|
|
|
nsresult
|
|
nsNavHistoryResult::Init()
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = ls->GetApplicationLocale(getter_AddRefs(mLocale));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// collation service for sorting
|
|
static NS_DEFINE_CID(kCollationFactoryCID, NS_COLLATIONFACTORY_CID);
|
|
nsCOMPtr<nsICollationFactory> cfact = do_CreateInstance(
|
|
kCollationFactoryCID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
cfact->CreateCollation(mLocale, getter_AddRefs(mCollation));
|
|
|
|
// date time formatting
|
|
static NS_DEFINE_CID(kDateTimeFormatCID, NS_DATETIMEFORMAT_CID);
|
|
mDateFormatter = do_CreateInstance(kDateTimeFormatCID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Hook up as an observer
|
|
nsNavBookmarks::GetBookmarksService()->AddObserver(this, PR_TRUE);
|
|
nsNavHistory::GetHistoryService()->AddObserver(this, PR_TRUE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::FilledAllResults
|
|
//
|
|
// Note that the toplevel node is not actually displayed in the tree.
|
|
// This is why we use a starting level of -1. The immediate children
|
|
// of this result will be at level 0, along the left side of the tree.
|
|
|
|
void
|
|
nsNavHistoryResult::FilledAllResults()
|
|
{
|
|
FillTreeStats(this, -1),
|
|
RebuildAllListRecurse(mChildren);
|
|
InitializeVisibleList();
|
|
ComputeShowSessions();
|
|
}
|
|
|
|
// nsNavHistoryResult::BuildChildrenFor
|
|
|
|
nsresult
|
|
nsNavHistoryResult::BuildChildrenFor(nsNavHistoryResultNode *aNode)
|
|
{
|
|
PRBool built;
|
|
nsresult rv = aNode->BuildChildren(&built);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!built) {
|
|
// No children needed to be built.
|
|
return NS_OK;
|
|
}
|
|
|
|
PRInt32 flatIndex = mAllElements.IndexOf(aNode) + 1;
|
|
for (PRInt32 i = 0; i < aNode->mChildren.Count(); ++i) {
|
|
nsNavHistoryResultNode *child = aNode->mChildren[i];
|
|
|
|
// XXX inefficient, need to be able to insert multiple items at once
|
|
mAllElements.InsertElementAt(child, flatIndex++);
|
|
for (PRInt32 j = 0; j < child->mChildren.Count(); ++j) {
|
|
mAllElements.InsertElementAt(child->mChildren[j], flatIndex++);
|
|
}
|
|
}
|
|
|
|
FillTreeStats(aNode, aNode->mIndentLevel);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::RecursiveSort
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResult::RecursiveSort(PRUint32 aSortingMode)
|
|
{
|
|
if (aSortingMode > nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
NS_ASSERTION(mOptions, "Options should always be present for a root query");
|
|
mOptions->SetSortingMode(aSortingMode);
|
|
|
|
RecursiveSortArray(mChildren, aSortingMode);
|
|
|
|
// This sorting function is called from two contexts. First, when everything
|
|
// is being built, executeQueries will do a sort and then call
|
|
// FilledAllResults to build up all the bookkeeping. In this case, we don't
|
|
// need to do anything more since everything will be built later.
|
|
//
|
|
// Second, anybody can call this function to cause a resort. In this case,
|
|
// we need to rebuild the visible element lists and notify the tree box object
|
|
// of the change. The way to disambiguate these cases is whether the flatted
|
|
// mAllElements list has been generated yet.
|
|
if (mAllElements.Count() > 0)
|
|
RebuildList();
|
|
|
|
// update the UI on the tree columns
|
|
if (mTree)
|
|
SetTreeSortingIndicator();
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::Get/SetCollapseDuplicates
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::SetCollapseDuplicates(PRBool aValue)
|
|
{
|
|
PRBool oldValue = mCollapseDuplicates;
|
|
mCollapseDuplicates = aValue;
|
|
if (oldValue != mCollapseDuplicates && mAllElements.Count() > 0)
|
|
RebuildList();
|
|
return NS_OK;
|
|
}
|
|
NS_IMETHODIMP nsNavHistoryResult::GetCollapseDuplicates(PRBool* aValue)
|
|
{
|
|
*aValue = mCollapseDuplicates;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::RecursiveSortArray
|
|
|
|
void
|
|
nsNavHistoryResult::RecursiveSortArray(
|
|
nsCOMArray<nsNavHistoryResultNode>& aSources, PRUint32 aSortingMode)
|
|
{
|
|
switch (aSortingMode)
|
|
{
|
|
case nsINavHistoryQueryOptions::SORT_BY_NONE:
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
|
|
aSources.Sort(SortComparison_TitleLess, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
|
|
aSources.Sort(SortComparison_TitleGreater, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
|
|
aSources.Sort(SortComparison_DateLess, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
|
|
aSources.Sort(SortComparison_DateGreater, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_URL_ASCENDING:
|
|
aSources.Sort(SortComparison_URLLess, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_URL_DESCENDING:
|
|
aSources.Sort(SortComparison_URLGreater, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
|
|
aSources.Sort(SortComparison_VisitCountLess, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
|
|
aSources.Sort(SortComparison_VisitCountGreater, NS_STATIC_CAST(void*, this));
|
|
break;
|
|
default:
|
|
NS_NOTREACHED("Bad sorting type");
|
|
break;
|
|
}
|
|
|
|
// sort any children
|
|
for (PRInt32 i = 0; i < aSources.Count(); i ++) {
|
|
if (aSources[i]->mChildren.Count() > 0)
|
|
RecursiveSortArray(aSources[i]->mChildren, aSortingMode);
|
|
}
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::ApplyTreeState
|
|
//
|
|
|
|
void nsNavHistoryResult::ApplyTreeState(
|
|
const nsDataHashtable<nsStringHashKey, int>& aExpanded)
|
|
{
|
|
RecursiveApplyTreeState(mChildren, aExpanded);
|
|
|
|
// If the list has been build yet, we need to redo the visible list.
|
|
// Normally, this function is called during object creation, and we don't
|
|
// need to worry about the list of visible nodes.
|
|
if (mAllElements.Count() > 0)
|
|
RebuildList();
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::RecursiveApplyTreeState
|
|
//
|
|
// See ApplyTreeState
|
|
|
|
void
|
|
nsNavHistoryResult::RecursiveApplyTreeState(
|
|
nsCOMArray<nsNavHistoryResultNode>& aList,
|
|
const nsDataHashtable<nsStringHashKey, int>& aExpanded)
|
|
{
|
|
for (PRInt32 i = 0; i < aList.Count(); i ++) {
|
|
if (aList[i]->mChildren.Count()) {
|
|
PRInt32 beenExpanded = 0;
|
|
if (aExpanded.Get(aList[i]->mTitle, &beenExpanded) && beenExpanded)
|
|
aList[i]->mExpanded = PR_TRUE;
|
|
else
|
|
aList[i]->mExpanded = PR_FALSE;
|
|
RecursiveApplyTreeState(aList[i]->mChildren, aExpanded);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::ExpandAll
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResult::ExpandAll()
|
|
{
|
|
RecursiveExpandCollapse(mChildren, PR_TRUE);
|
|
RebuildList();
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::CollapseAll
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResult::CollapseAll()
|
|
{
|
|
RecursiveExpandCollapse(mChildren, PR_FALSE);
|
|
RebuildList();
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::RecursiveExpandCollapse
|
|
//
|
|
// aExpand = true -> expand all
|
|
// aExpand = false -> collapse all
|
|
|
|
void
|
|
nsNavHistoryResult::RecursiveExpandCollapse(
|
|
nsCOMArray<nsNavHistoryResultNode>& aList, PRBool aExpand)
|
|
{
|
|
for (PRInt32 i = 0; i < aList.Count(); i ++) {
|
|
if (aList[i]->mChildren.Count()) {
|
|
aList[i]->mExpanded = aExpand;
|
|
RecursiveExpandCollapse(aList[i]->mChildren, aExpand);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::NodeForTreeIndex
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResult::NodeForTreeIndex(PRUint32 index,
|
|
nsINavHistoryResultNode** aResult)
|
|
{
|
|
if (index >= (PRUint32)mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
NS_ADDREF(*aResult = VisibleElementAt(index));
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::TreeIndexForNode
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResult::TreeIndexForNode(nsINavHistoryResultNode* aNode,
|
|
PRUint32* aResult)
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsNavHistoryResultNode> node = do_QueryInterface(aNode, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (node->mVisibleIndex < 0)
|
|
*aResult = nsINavHistoryResult::INDEX_INVISIBLE;
|
|
else
|
|
*aResult = node->mVisibleIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResult::AddObserver(nsINavHistoryResultViewObserver* aObserver,
|
|
PRBool aOwnsWeak)
|
|
{
|
|
return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNavHistoryResult::RemoveObserver(nsINavHistoryResultViewObserver* aObserver)
|
|
{
|
|
return mObservers.RemoveWeakElement(aObserver);
|
|
}
|
|
|
|
|
|
// 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<nsITreeColumns> 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<nsITreeColumn> column;
|
|
rv = columns->GetSortedColumn(getter_AddRefs(column));
|
|
if (NS_FAILED(rv)) return;
|
|
if (column) {
|
|
nsCOMPtr<nsIDOMElement> element;
|
|
rv = column->GetElement(getter_AddRefs(element));
|
|
if (NS_FAILED(rv)) return;
|
|
element->SetAttribute(sortDirectionKey, NS_LITERAL_STRING(""));
|
|
}
|
|
|
|
// set new sorting indicator by looking through all columns for ours
|
|
NS_ASSERTION(mOptions, "Options should always be present for a root query");
|
|
if (mOptions->SortingMode() == nsINavHistoryQueryOptions::SORT_BY_NONE)
|
|
return;
|
|
PRBool desiredIsDescending;
|
|
ColumnType desiredColumn = SortTypeToColumnType(mOptions->SortingMode(),
|
|
&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<nsIDOMElement> 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::SortComparison_Title*
|
|
//
|
|
// These are a little more complicated because they do a localization
|
|
// conversion. If this is too slow, we can compute the sort keys once in
|
|
// advance, sort that array, and then reorder the real array accordingly.
|
|
// This would save some key generations.
|
|
//
|
|
// The collation object must be allocated before sorting on title!
|
|
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_TitleLess(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
nsNavHistoryResult* result = NS_STATIC_CAST(nsNavHistoryResult*, closure);
|
|
PRInt32 value = -1; // default to returning "true" on failure
|
|
result->mCollation->CompareString(
|
|
nsICollation::kCollationCaseInSensitive, a->mTitle, b->mTitle, &value);
|
|
if (value == 0) {
|
|
// resolve by URL
|
|
value = a->mUrl.Compare(PromiseFlatCString(b->mUrl).get());
|
|
if (value == 0) {
|
|
// resolve by date
|
|
return ComparePRTime(a->mTime, b->mTime);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_TitleGreater(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
return -SortComparison_TitleLess(a, b, closure);
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::SortComparison_Date*
|
|
//
|
|
// Equal times will be very unusual, but it is important that there is some
|
|
// deterministic ordering of the results so they don't move around. Use URLs
|
|
// for conflict resolution. If those are the same, we probably don't care
|
|
// about the relative ordering.
|
|
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_DateLess(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
PRInt32 value = ComparePRTime(a->mTime, b->mTime);
|
|
if (value == 0)
|
|
return a->mUrl.Compare(b->mUrl.get());
|
|
return value;
|
|
}
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_DateGreater(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
PRInt32 value = -ComparePRTime(a->mTime, b->mTime);
|
|
if (value == 0)
|
|
return -a->mUrl.Compare(b->mUrl.get());
|
|
return value;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::SortComparison_URL*
|
|
//
|
|
// Certain types of parent nodes are treated specially because URLs are not
|
|
// meaningful.
|
|
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_URLLess(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
if (a->mType != b->mType) {
|
|
// we really shouldn't be comparing different row types
|
|
NS_NOTREACHED("Comparing different row types!");
|
|
return 0;
|
|
}
|
|
|
|
PRInt32 value;
|
|
if (a->mType == nsINavHistoryResult::RESULT_TYPE_HOST) {
|
|
// for host nodes, use title (= host name)
|
|
nsNavHistoryResult* result = NS_STATIC_CAST(nsNavHistoryResult*, closure);
|
|
result->mCollation->CompareString(
|
|
nsICollation::kCollationCaseInSensitive, a->mTitle, b->mTitle, &value);
|
|
} else if (a->mType == nsINavHistoryResult::RESULT_TYPE_DAY) {
|
|
// date nodes use date (skip conflict resolution becuase it uses date too)
|
|
return ComparePRTime(a->mTime, b->mTime);
|
|
} else {
|
|
// normal URL or visit
|
|
value = a->mUrl.Compare(PromiseFlatCString(b->mUrl).get());
|
|
}
|
|
|
|
// resolve conflicts using date
|
|
if (value == 0) {
|
|
return ComparePRTime(a->mTime, b->mTime);
|
|
}
|
|
return value;
|
|
}
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_URLGreater(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
return -SortComparison_URLLess(a, b, closure);
|
|
}
|
|
|
|
|
|
// nsNavHistory::SortComparison_VisitCount*
|
|
//
|
|
// Fall back on dates for conflict resolution
|
|
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_VisitCountLess(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
PRInt32 value = CompareIntegers(a->mAccessCount, b->mAccessCount);
|
|
if (value == 0)
|
|
return ComparePRTime(a->mTime, b->mTime);
|
|
return value;
|
|
}
|
|
PRInt32 PR_CALLBACK nsNavHistoryResult::SortComparison_VisitCountGreater(
|
|
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
|
|
{
|
|
PRInt32 value = -CompareIntegers(a->mAccessCount, b->mAccessCount);
|
|
if (value == 0)
|
|
return -ComparePRTime(a->mTime, b->mTime);
|
|
return value;
|
|
}
|
|
|
|
|
|
// 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;
|
|
mDateFormatter->FormatPRTime(mLocale, dateFormat,
|
|
nsIScriptableDateFormat::timeFormatNoSeconds,
|
|
aTime, resultString);
|
|
aResult = resultString;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// 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::RESULT_TYPE_VISIT)
|
|
return; // not visits
|
|
if (mOptions->SortingMode() != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
|
|
mOptions->SortingMode() != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
|
|
return; // not date sorting
|
|
|
|
PRUint32 groupCount;
|
|
const PRUint32* groups = mOptions->GroupingMode(&groupCount);
|
|
for (PRUint32 i = 0; i < groupCount; i ++) {
|
|
if (groups[i] != nsINavHistoryQueryOptions::GROUP_BY_DAY)
|
|
return; // non-time-based grouping
|
|
}
|
|
|
|
mShowSessions = PR_TRUE;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::FillTreeStats
|
|
//
|
|
// This basically does a recursive depth-first traversal of the tree to fill
|
|
// in bookeeping information and statistics for parent nodes.
|
|
|
|
void
|
|
nsNavHistoryResult::FillTreeStats(nsNavHistoryResultNode* aResult, PRInt32 aLevel)
|
|
{
|
|
aResult->mIndentLevel = aLevel;
|
|
if (aResult->mChildren.Count() > 0) {
|
|
PRInt32 totalAccessCount = 0;
|
|
PRTime mostRecentTime = 0;
|
|
for (PRInt32 i = 0; i < aResult->mChildren.Count(); i ++ ) {
|
|
nsNavHistoryResultNode* child = NS_STATIC_CAST(
|
|
nsNavHistoryResultNode*, aResult->mChildren[i]);
|
|
|
|
child->mParent = aResult;
|
|
FillTreeStats(aResult->mChildren[i], aLevel + 1);
|
|
|
|
totalAccessCount += child->mAccessCount;
|
|
if (LL_CMP(child->mTime, >, mostRecentTime))
|
|
mostRecentTime = child->mTime;
|
|
}
|
|
|
|
// when there are children, the parent is the aggregate of its children
|
|
aResult->mAccessCount = totalAccessCount;
|
|
aResult->mTime = mostRecentTime;
|
|
}
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::InitializeVisibleList
|
|
//
|
|
// The root keeps its visible index of -1, since it is never visible
|
|
|
|
void
|
|
nsNavHistoryResult::InitializeVisibleList()
|
|
{
|
|
NS_ASSERTION(mVisibleElements.Count() == 0, "Initializing visible list twice");
|
|
|
|
// Fill the visible element list and notify those elements of their new
|
|
// positions. We fill directly into the result list, so we need to manually
|
|
// set the visible indices on those elements (normally this is done by
|
|
// InsertVisibleSection)
|
|
BuildVisibleSection(mChildren, &mVisibleElements);
|
|
for (PRInt32 i = 0; i < mVisibleElements.Count(); i ++)
|
|
VisibleElementAt(i)->mVisibleIndex = i;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::RebuildList
|
|
//
|
|
// Called when something big changes, like sorting. This rebuilds the
|
|
// flat and visible element lists using the current toplevel array.
|
|
|
|
void
|
|
nsNavHistoryResult::RebuildList()
|
|
{
|
|
PRInt32 oldVisibleCount = mVisibleElements.Count();
|
|
|
|
// something changed, so we better update the show sessions value
|
|
ComputeShowSessions();
|
|
|
|
mAllElements.Clear();
|
|
mVisibleElements.Clear();
|
|
RebuildAllListRecurse(mChildren);
|
|
InitializeVisibleList();
|
|
|
|
// We need to update the tree to tell it about the new list
|
|
if (mTree) {
|
|
if (mVisibleElements.Count() != oldVisibleCount)
|
|
mTree->RowCountChanged(0, mVisibleElements.Count() - oldVisibleCount);
|
|
mTree->InvalidateRange(0, mVisibleElements.Count());
|
|
}
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::RebuildAllListRecurse
|
|
//
|
|
// Does the work for RebuildList in generating the new mAllElements list
|
|
|
|
void
|
|
nsNavHistoryResult::RebuildAllListRecurse(
|
|
const nsCOMArray<nsNavHistoryResultNode>& aSource)
|
|
{
|
|
for (PRInt32 i = 0; i < aSource.Count(); i ++) {
|
|
PRUint32 allCount = mAllElements.Count();
|
|
if (mCollapseDuplicates && allCount > 0 && aSource[i]->mID != 0 &&
|
|
AllElementAt(allCount - 1)->mID == aSource[i]->mID) {
|
|
// ignore duplicate, that elements' flat index is the index of its dup
|
|
// note, we don't collapse ID=0 elements since that is all parent nodes
|
|
} else {
|
|
mAllElements.AppendElement(aSource[i]);
|
|
if (aSource[i]->mChildren.Count() > 0)
|
|
RebuildAllListRecurse(aSource[i]->mChildren);
|
|
}
|
|
}
|
|
}
|
|
|
|
// nsNavHistoryResult::BuildVisibleSection
|
|
//
|
|
// This takes all nodes in the given source array and recursively (when
|
|
// containers are open) appends them to the flattened visible result array.
|
|
// This is used when expanding folders and in initially building the
|
|
// visible array list.
|
|
//
|
|
// This will also collapse duplicate elements. As long as this is called for
|
|
// an entire subtree, it is OK, since duplicate elements should not
|
|
// occur next to each other at different levels of the tree.
|
|
|
|
void
|
|
nsNavHistoryResult::BuildVisibleSection(
|
|
const nsCOMArray<nsNavHistoryResultNode>& aSources, nsVoidArray* aVisible)
|
|
{
|
|
for (PRInt32 i = 0; i < aSources.Count(); i ++) {
|
|
nsNavHistoryResultNode* cur = aSources[i];
|
|
if (mCollapseDuplicates && aVisible->Count() > 0 && aSources[i]->mID != 0) {
|
|
nsNavHistoryResultNode* prev =
|
|
(nsNavHistoryResultNode*)(*aVisible)[aVisible->Count() - 1];
|
|
if (prev->mID == cur->mID)
|
|
continue; // collapse duplicate
|
|
}
|
|
|
|
aVisible->AppendElement(cur);
|
|
if (cur->mExpanded) {
|
|
BuildChildrenFor(cur);
|
|
if (cur->mChildren.Count() > 0) {
|
|
BuildVisibleSection(cur->mChildren, aVisible);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::InsertVisibleSection
|
|
//
|
|
// This takes a list of items (probably generated by BuildVisibleSection)
|
|
// and inserts it into the visible element list, updating those elements as
|
|
// needed. This is used when expanding the tree.
|
|
|
|
void
|
|
nsNavHistoryResult::InsertVisibleSection(const nsVoidArray& aAddition,
|
|
PRInt32 aInsertHere)
|
|
{
|
|
NS_ASSERTION(aInsertHere >= 0 && aInsertHere <= mVisibleElements.Count(),
|
|
"Invalid insertion point");
|
|
mVisibleElements.InsertElementsAt(aAddition, aInsertHere);
|
|
|
|
// we need to update all the elements from the insertion point to the end
|
|
// of the list of their new indices
|
|
for (PRInt32 i = aInsertHere; i < mVisibleElements.Count(); i ++)
|
|
VisibleElementAt(i)->mVisibleIndex = i;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::DeleteVisibleChildrenOf
|
|
//
|
|
// This is called when an element is collapsed. We search for all elements
|
|
// that are at a greater level of indent and remove them from the visible
|
|
// element list. Returns the number of rows deleted
|
|
|
|
int
|
|
nsNavHistoryResult::DeleteVisibleChildrenOf(PRInt32 aIndex)
|
|
{
|
|
NS_ASSERTION(aIndex >= 0 && aIndex < mVisibleElements.Count(),
|
|
"Index out of bounds");
|
|
|
|
const nsNavHistoryResultNode* parentNode = VisibleElementAt(aIndex);
|
|
NS_ASSERTION(parentNode->mChildren.Count() > 0 && parentNode->mExpanded,
|
|
"Trying to collapse an improper node");
|
|
|
|
// compute the index of the element just after the end of the deleted region
|
|
PRInt32 outerLevel = parentNode->mIndentLevel;
|
|
PRInt32 nextOuterIndex = mVisibleElements.Count();
|
|
PRInt32 i;
|
|
for (i = aIndex + 1; i < mVisibleElements.Count(); i ++) {
|
|
if (VisibleElementAt(i)->mIndentLevel <= outerLevel) {
|
|
nextOuterIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Mark those elements as invisible and remove them.
|
|
for (i = aIndex + 1; i < nextOuterIndex; i ++)
|
|
VisibleElementAt(i)->mVisibleIndex = -1;
|
|
PRInt32 deleteCount = nextOuterIndex - aIndex - 1;
|
|
mVisibleElements.RemoveElementsAt(aIndex + 1, deleteCount);
|
|
|
|
// re-number the moved elements
|
|
for (i = aIndex + 1; i < mVisibleElements.Count(); i ++)
|
|
VisibleElementAt(i)->mVisibleIndex = i;
|
|
|
|
return deleteCount;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::GetRowCount (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::GetRowCount(PRInt32 *aRowCount)
|
|
{
|
|
*aRowCount = mVisibleElements.Count();
|
|
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;
|
|
}
|
|
|
|
/* void getRowProperties (in long index, in nsISupportsArray properties); */
|
|
NS_IMETHODIMP nsNavHistoryResult::GetRowProperties(PRInt32 index, nsISupportsArray *properties)
|
|
{
|
|
if (! mShowSessions)
|
|
return NS_OK; // don't need to bother to compute session boundaries
|
|
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
nsNavHistoryResultNode *node = VisibleElementAt(index);
|
|
|
|
if (node->mSessionID != 0) {
|
|
if (index == 0 ||
|
|
node->mSessionID != VisibleElementAt(index - 1)->mSessionID) {
|
|
properties->AppendElement(nsNavHistory::sSessionStartAtom);
|
|
} else {
|
|
properties->AppendElement(nsNavHistory::sSessionContinueAtom);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void getCellProperties (in long row, in nsITreeColumn col, in nsISupportsArray properties); */
|
|
NS_IMETHODIMP nsNavHistoryResult::GetCellProperties(PRInt32 row, nsITreeColumn *col, nsISupportsArray *properties)
|
|
{
|
|
if (row < 0 || row >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
nsNavHistoryResultNode *node = VisibleElementAt(row);
|
|
PRInt64 folderId, bookmarksRootId, toolbarRootId;
|
|
node->GetFolderId(&folderId);
|
|
|
|
nsCOMPtr<nsINavBookmarksService> 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;
|
|
}
|
|
|
|
/* void getColumnProperties (in nsITreeColumn col, in nsISupportsArray properties); */
|
|
NS_IMETHODIMP nsNavHistoryResult::GetColumnProperties(nsITreeColumn *col, nsISupportsArray *properties)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::IsContainer (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::IsContainer(PRInt32 index, PRBool *_retval)
|
|
{
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
nsNavHistoryResultNode *node = VisibleElementAt(index);
|
|
*_retval = (node->mChildren.Count() > 0 ||
|
|
(node->mType == nsINavHistoryResult::RESULT_TYPE_QUERY &&
|
|
(node->FolderId() > 0 ||
|
|
(mOptions && mOptions->ExpandPlaces()))));
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::IsContainerOpen (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::IsContainerOpen(PRInt32 index, PRBool *_retval)
|
|
{
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
*_retval = VisibleElementAt(index)->mExpanded;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::IsContainerEmpty (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::IsContainerEmpty(PRInt32 index, PRBool *_retval)
|
|
{
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
*_retval = (VisibleElementAt(index)->mChildren.Count() == 0);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::IsSeparator (nsITreeView)
|
|
//
|
|
// We don't support separators
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::IsSeparator(PRInt32 index, PRBool *_retval)
|
|
{
|
|
*_retval = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* boolean isSorted (); */
|
|
NS_IMETHODIMP nsNavHistoryResult::IsSorted(PRBool *_retval)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::CanDrop (nsITreeView)
|
|
//
|
|
// No drag-and-drop to history.
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::CanDrop(PRInt32 index, PRInt32 orientation,
|
|
PRBool *_retval)
|
|
{
|
|
PRUint32 count = mObservers.Length();
|
|
for (PRUint32 i = 0; i < count; ++i) {
|
|
const nsCOMPtr<nsINavHistoryResultViewObserver> &obs = mObservers[i];
|
|
if (obs) {
|
|
obs->CanDrop(index, orientation, _retval);
|
|
if (*_retval) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::Drop (nsITreeView)
|
|
//
|
|
// No drag-and-drop to history.
|
|
|
|
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 index, PRInt32 *_retval)
|
|
{
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
nsNavHistoryResultNode* parent = VisibleElementAt(index)->mParent;
|
|
if (!parent || parent->mVisibleIndex < 0)
|
|
*_retval = -1;
|
|
else
|
|
*_retval = parent->mVisibleIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::HasNextSibling (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::HasNextSibling(PRInt32 index,
|
|
PRInt32 afterIndex,
|
|
PRBool *_retval)
|
|
{
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
if (index == mVisibleElements.Count() - 1) {
|
|
// this is the last thing in the list -> no next sibling
|
|
*_retval = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
*_retval = (VisibleElementAt(index)->mIndentLevel ==
|
|
VisibleElementAt(index + 1)->mIndentLevel);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::GetLevel (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::GetLevel(PRInt32 index, PRInt32 *_retval)
|
|
{
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
*_retval = VisibleElementAt(index)->mIndentLevel;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::GetImageSrc (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::GetImageSrc(PRInt32 row, nsITreeColumn *col,
|
|
nsAString& _retval)
|
|
{
|
|
if (row < 0 || row >= mVisibleElements.Count())
|
|
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;
|
|
}
|
|
|
|
// Don't set the favicon for containers, we will use the default folder icon
|
|
// The special icons for the bookmarks menu and toolbar are set in CSS from
|
|
// attributes defined by GetCellProperties.
|
|
PRBool isContainer;
|
|
IsContainer(row, &isContainer);
|
|
if (isContainer) {
|
|
_retval.Truncate(0);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
|
|
NS_ENSURE_TRUE(faviconService, NS_ERROR_NO_INTERFACE);
|
|
|
|
nsCAutoString spec;
|
|
faviconService->GetFaviconSpecForIconString(VisibleElementAt(row)->mFaviconURL, spec);
|
|
// _retval = NS_ConvertUTF8toUTF16(spec);
|
|
CopyUTF8toUTF16(spec, _retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* long getProgressMode (in long row, in nsITreeColumn col); */
|
|
NS_IMETHODIMP nsNavHistoryResult::GetProgressMode(PRInt32 row, nsITreeColumn *col, PRInt32 *_retval)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
/* AString getCellValue (in long row, in nsITreeColumn col); */
|
|
NS_IMETHODIMP nsNavHistoryResult::GetCellValue(PRInt32 row, nsITreeColumn *col, nsAString & _retval)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::GetCellText (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::GetCellText(PRInt32 rowIndex,
|
|
nsITreeColumn *col,
|
|
nsAString& _retval)
|
|
{
|
|
if (rowIndex < 0 || rowIndex >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
PRInt32 colIndex;
|
|
col->GetIndex(&colIndex);
|
|
|
|
nsNavHistoryResultNode* elt = VisibleElementAt(rowIndex);
|
|
|
|
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 (! elt->mTitle.IsEmpty()) {
|
|
_retval = elt->mTitle;
|
|
} else {
|
|
nsXPIDLString value;
|
|
nsresult rv = mBundle->GetStringFromName(
|
|
NS_LITERAL_STRING("noTitle").get(), getter_Copies(value));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
_retval = value;
|
|
}
|
|
break;
|
|
}
|
|
case Column_URL:
|
|
{
|
|
_retval = NS_ConvertUTF8toUTF16(elt->mUrl);
|
|
break;
|
|
}
|
|
case Column_Date:
|
|
{
|
|
if (elt->mTime == 0 ||
|
|
(elt->mType != nsINavHistoryResult::RESULT_TYPE_URL &&
|
|
elt->mType != nsINavHistoryResult::RESULT_TYPE_VISIT)) {
|
|
// 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 URLs and visits
|
|
_retval.Truncate(0);
|
|
} else {
|
|
return FormatFriendlyTime(elt->mTime, _retval);
|
|
}
|
|
break;
|
|
}
|
|
case Column_VisitCount:
|
|
{
|
|
_retval = NS_ConvertUTF8toUTF16(nsPrintfCString("%d", elt->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) // might be a NULL tree
|
|
SetTreeSortingIndicator();
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::ToggleOpenState (nsITreeView)
|
|
|
|
NS_IMETHODIMP nsNavHistoryResult::ToggleOpenState(PRInt32 index)
|
|
{
|
|
if (index < 0 || index >= mVisibleElements.Count())
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver,
|
|
OnToggleOpenState(index))
|
|
|
|
nsNavHistoryResultNode* curNode = VisibleElementAt(index);
|
|
if (curNode->mExpanded) {
|
|
// collapse
|
|
PRInt32 deleteCount = DeleteVisibleChildrenOf(index);
|
|
curNode->mExpanded = PR_FALSE;
|
|
if (mTree)
|
|
mTree->RowCountChanged(index + 1, -deleteCount);
|
|
|
|
mHistoryService->SaveCollapseItem(curNode->mTitle);
|
|
} else {
|
|
// expand
|
|
BuildChildrenFor(curNode);
|
|
|
|
nsVoidArray addition;
|
|
BuildVisibleSection(curNode->mChildren, &addition);
|
|
InsertVisibleSection(addition, index + 1);
|
|
|
|
curNode->mExpanded = PR_TRUE;
|
|
if (mTree)
|
|
mTree->RowCountChanged(index + 1, addition.Count());
|
|
|
|
mHistoryService->SaveExpandItem(curNode->mTitle);
|
|
}
|
|
/* Maybe we don't need this (test me)
|
|
if (mTree)
|
|
mTree->InvalidateRow(index);
|
|
*/
|
|
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 = mOptions->SortingMode();
|
|
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_URL:
|
|
if (oldSort == nsINavHistoryQueryOptions::SORT_BY_URL_ASCENDING)
|
|
newSort = nsINavHistoryQueryOptions::SORT_BY_URL_DESCENDING;
|
|
else
|
|
newSort = nsINavHistoryQueryOptions::SORT_BY_URL_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 RecursiveSort(newSort);
|
|
}
|
|
|
|
/* void selectionChanged (); */
|
|
NS_IMETHODIMP nsNavHistoryResult::SelectionChanged()
|
|
{
|
|
ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver,
|
|
OnSelectionChanged())
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void cycleCell (in long row, in nsITreeColumn col); */
|
|
NS_IMETHODIMP nsNavHistoryResult::CycleCell(PRInt32 row, nsITreeColumn *col)
|
|
{
|
|
ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver,
|
|
OnCycleCell(row, col))
|
|
return NS_OK;
|
|
}
|
|
|
|
/* boolean isEditable (in long row, in nsITreeColumn col); */
|
|
NS_IMETHODIMP nsNavHistoryResult::IsEditable(PRInt32 row, nsITreeColumn *col, PRBool *_retval)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
/* void setCellValue (in long row, in nsITreeColumn col, in AString value); */
|
|
NS_IMETHODIMP nsNavHistoryResult::SetCellValue(PRInt32 row, nsITreeColumn *col, const nsAString & value)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
/* void setCellText (in long row, in nsITreeColumn col, in AString value); */
|
|
NS_IMETHODIMP nsNavHistoryResult::SetCellText(PRInt32 row, nsITreeColumn *col, const nsAString & value)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
/* void performAction (in wstring action); */
|
|
NS_IMETHODIMP nsNavHistoryResult::PerformAction(const PRUnichar *action)
|
|
{
|
|
ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver,
|
|
OnPerformAction(action))
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void performActionOnRow (in wstring action, in long row); */
|
|
NS_IMETHODIMP nsNavHistoryResult::PerformActionOnRow(const PRUnichar *action, PRInt32 row)
|
|
{
|
|
ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver,
|
|
OnPerformActionOnRow(action, row))
|
|
return NS_OK;
|
|
}
|
|
|
|
/* void performActionOnCell (in wstring action, in long row, in nsITreeColumn col); */
|
|
NS_IMETHODIMP nsNavHistoryResult::PerformActionOnCell(const PRUnichar *action, PRInt32 row, nsITreeColumn *col)
|
|
{
|
|
ENUMERATE_WEAKARRAY(mObservers, nsINavHistoryResultViewObserver,
|
|
OnPerformActionOnCell(action, row, col))
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsNavHistoryResult::GetColumnType
|
|
//
|
|
|
|
nsNavHistoryResult::ColumnType
|
|
nsNavHistoryResult::GetColumnType(nsITreeColumn* col)
|
|
{
|
|
const PRUnichar* idConst;
|
|
col->GetIdConst(&idConst);
|
|
switch(idConst[0]) {
|
|
case 't':
|
|
return Column_Title;
|
|
case 'u':
|
|
return Column_URL;
|
|
case 'd':
|
|
return Column_Date;
|
|
case 'v':
|
|
return Column_VisitCount;
|
|
default:
|
|
return Column_Unknown;
|
|
}
|
|
}
|
|
|
|
|
|
// nsNavHistoryResult::SortTypeToColumnType
|
|
//
|
|
// Places whether its 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_URL_ASCENDING:
|
|
*aDescending = PR_FALSE;
|
|
return Column_URL;
|
|
case nsINavHistoryQueryOptions::SORT_BY_URL_DESCENDING:
|
|
*aDescending = PR_TRUE;
|
|
return Column_URL;
|
|
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:
|
|
return Column_Unknown;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsNavHistoryResult::RowAdded(nsNavHistoryResultNode *aParent, PRInt32 aIndex)
|
|
{
|
|
mAllElements.Clear();
|
|
mVisibleElements.Clear();
|
|
FilledAllResults();
|
|
if (mTree) {
|
|
mTree->RowCountChanged(aParent->ChildAt(aIndex)->mVisibleIndex, 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsNavHistoryResult::RowRemoved(PRInt32 aVisibleIndex)
|
|
{
|
|
mAllElements.Clear();
|
|
mVisibleElements.Clear();
|
|
FilledAllResults();
|
|
if (mTree) {
|
|
mTree->RowCountChanged(aVisibleIndex, -1);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsNavHistoryResult::RowChanged(PRInt32 aVisibleIndex)
|
|
{
|
|
if (mTree) {
|
|
mTree->InvalidateRow(aVisibleIndex);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsNavHistoryResult::Invalidate()
|
|
{
|
|
FillTreeStats(this, -1);
|
|
RebuildList();
|
|
}
|