fix for bug #358784: implement Firefox 2.0 style "View | By Date" and "View | By Date and Site" for places based history sidebar (GROUP_BY_DAY)

r=g#,brettw


git-svn-id: svn://10.0.0.236/trunk@215591 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
sspitzer%mozilla.org 2006-11-21 23:02:16 +00:00
parent 63363e3091
commit d13f814777
6 changed files with 183 additions and 28 deletions

View File

@ -114,12 +114,12 @@ function SetPlace(aSearchString)
placeURI += "&sort=" + NHQO.SORT_BY_DATE_DESCENDING;
break;
case "dayandsite":
/* placeURI += "&group=" + NHQO.GROUP_BY_DAY; */
placeURI += "&group=" + NHQO.GROUP_BY_DAY;
placeURI += "&group=" + NHQO.GROUP_BY_HOST;
placeURI += "&sort=" + NHQO.SORT_BY_TITLE_ASCENDING;
break;
default: /* "day" */
/* placeURI += "&group=" + NHQO.GROUP_BY_DAY; */
placeURI += "&group=" + NHQO.GROUP_BY_DAY;
placeURI += "&sort=" + NHQO.SORT_BY_TITLE_ASCENDING;
break;
}

View File

@ -83,6 +83,7 @@ interface nsINavHistoryResultNode : nsISupports
const PRUint32 RESULT_TYPE_QUERY = 5; // nsINavHistoryQueryResultNode
const PRUint32 RESULT_TYPE_FOLDER = 6; // nsINavHistoryFolderResultNode
const PRUint32 RESULT_TYPE_SEPARATOR = 7; // nsINavHistoryResultNode
const PRUint32 RESULT_TYPE_DAY = 8; // nsINavHistoryContainerResultNode
readonly attribute PRUint32 type;
/**
@ -933,6 +934,13 @@ interface nsINavHistoryQuery : nsISupports
[scriptable, uuid(25fd4de4-33b0-475e-a63d-2bcb1d123e0d)]
interface nsINavHistoryQueryOptions : nsISupports
{
/**
* Grouping by day. The results will be an array of nsINavHistoryResults with
* type = RESULT_TYPE_DAY, one for each day where there are results. These
* will have children of corresponding to the search results of that day.
*/
const PRUint32 GROUP_BY_DAY = 0;
/**
* Groping by exact host. The results will be an array of nsINavHistoryResults
* with type = RESULT_TYPE_HOST, one for each unique host (for example,
@ -1003,8 +1011,11 @@ interface nsINavHistoryQueryOptions : nsISupports
/**
* The grouping mode to be used for this query.
* Grouping mode is an array of GROUP_BY_* values that specifies the
* structure of the tree you want. If you don't want grouping, you can
* specify an empty array.
* structure of the tree you want. For example, an array consisting of
* [GROUP_BY_DAY, GROUP_BY_DOMAIN] will give you a tree whose first level is
* a list of days, and whose second level is a list of domains, and whose
* third level is a list of pages in those domains.
* If you don't want grouping, you can specify an empty array.
*/
void getGroupingMode(out PRUint32 groupCount,
[retval,array,size_is(groupCount)] out PRUint32 groupingMode);

View File

@ -1154,6 +1154,21 @@ void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure)
history->mExpireNowTimer = nsnull;
}
static PRTime
NormalizeTimeRelativeToday(PRTime aTime)
{
// round to midnight this morning
PRExplodedTime explodedTime;
PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime);
// set to midnight (0:00)
explodedTime.tm_min =
explodedTime.tm_hour =
explodedTime.tm_sec =
explodedTime.tm_usec = 0;
return PR_ImplodeTime(&explodedTime);
}
// nsNavHistory::NormalizeTime
//
@ -1173,17 +1188,9 @@ nsNavHistory::NormalizeTime(PRUint32 aRelative, PRTime aOffset)
{
case nsINavHistoryQuery::TIME_RELATIVE_EPOCH:
return aOffset;
case nsINavHistoryQuery::TIME_RELATIVE_TODAY: {
// round to midnight this morning
PRExplodedTime explodedTime;
PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &explodedTime);
explodedTime.tm_min =
explodedTime.tm_hour =
explodedTime.tm_sec =
explodedTime.tm_usec = 0;
ref = PR_ImplodeTime(&explodedTime);
case nsINavHistoryQuery::TIME_RELATIVE_TODAY:
ref = NormalizeTimeRelativeToday(PR_Now());
break;
}
case nsINavHistoryQuery::TIME_RELATIVE_NOW:
ref = PR_Now();
break;
@ -1194,7 +1201,6 @@ nsNavHistory::NormalizeTime(PRUint32 aRelative, PRTime aOffset)
return ref + aOffset;
}
// nsNavHistory::CanLiveUpdateQuery
//
// Returns true if this set of queries/options can be live-updated. That is,
@ -3254,6 +3260,9 @@ nsNavHistory::RecursiveGroup(const nsCOMArray<nsNavHistoryResultNode>& aSource,
nsresult rv;
switch (aGroupingMode[0]) {
case nsINavHistoryQueryOptions::GROUP_BY_DAY:
rv = GroupByDay(aSource, aDest);
break;
case nsINavHistoryQueryOptions::GROUP_BY_HOST:
rv = GroupByHost(aSource, aDest, PR_FALSE);
break;
@ -3284,6 +3293,92 @@ nsNavHistory::RecursiveGroup(const nsCOMArray<nsNavHistoryResultNode>& aSource,
return NS_OK;
}
// code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp
// pass in a pre-normalized now and a date, and we'll find
// the difference since midnight on each of the days.
//
// USECS_PER_DAY == PR_USEC_PER_SEC * 60 * 60 * 24;
static const PRInt64 USECS_PER_DAY = LL_INIT(20, 500654080);
static PRInt64
GetAgeInDays(PRTime aNormalizedNow, PRTime aDate)
{
PRTime dateMidnight = NormalizeTimeRelativeToday(aDate);
return ((aNormalizedNow - dateMidnight) / USECS_PER_DAY);
}
// XXX todo
// we should make "group by date" more flexible and extensible, in order
// to allow extension developers to write better history sidebars and viewers
// see bug #359346
// nsNavHistory::GroupByDay
nsresult
nsNavHistory::GroupByDay(const nsCOMArray<nsNavHistoryResultNode>& aSource,
nsCOMArray<nsNavHistoryResultNode>* aDest)
{
// 8 == today, yesterday, 2 ago, 3 ago, 4 ago, 5 ago, 6 ago, older than 6
const PRInt32 numDays = 8;
nsNavHistoryContainerResultNode *dates[numDays];
for (PRInt32 i = 0; i < numDays; i++)
dates[i] = nsnull;
nsCAutoString dateNames[numDays];
// special case: Today
GetStringFromName(NS_LITERAL_STRING("finduri-AgeInDays-is-0").get(),
dateNames[0]);
// special case: Yesterday
GetStringFromName(NS_LITERAL_STRING("finduri-AgeInDays-is-1").get(),
dateNames[1]);
for (PRInt32 curDay = 2; curDay <= numDays-2; curDay++) {
// common case: "<curDay> days ago"
GetAgeInDaysString(curDay, NS_LITERAL_STRING("finduri-AgeInDays-is").get(),
dateNames[curDay]);
}
// special case: "Older than <numDays-2> days"
GetAgeInDaysString(numDays-2,
NS_LITERAL_STRING("finduri-AgeInDays-isgreater").get(),
dateNames[numDays-1]);
PRTime normalizedNow = NormalizeTimeRelativeToday(PR_Now());
for (PRInt32 i = 0; i < aSource.Count(); i ++) {
if (!aSource[i]->IsURI()) {
// what do we do with non-URLs? I'll just dump them into the top level
aDest->AppendObject(aSource[i]);
continue;
}
// get the date from aSource[i]
nsCAutoString curDateName;
PRInt64 ageInDays = GetAgeInDays(normalizedNow, aSource[i]->mTime);
if (ageInDays > (numDays - 1))
ageInDays = numDays - 1;
curDateName = dateNames[ageInDays];
if (!dates[ageInDays]) {
// need to create an entry for this date
dates[ageInDays] = new nsNavHistoryContainerResultNode(EmptyCString(),
curDateName,
EmptyCString(),
nsNavHistoryResultNode::RESULT_TYPE_DAY,
PR_TRUE,
EmptyCString());
if (!dates[ageInDays])
return NS_ERROR_OUT_OF_MEMORY;
}
if (!dates[ageInDays]->mChildren.AppendObject(aSource[i]))
return NS_ERROR_OUT_OF_MEMORY;
}
for (PRInt32 i = 0; i < numDays; i++) {
if (dates[i]) {
nsresult rv = aDest->AppendObject(dates[i]);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
// nsNavHistory::GroupByHost
//
@ -3768,15 +3863,34 @@ nsNavHistory::TitleForDomain(const nsCString& domain, nsACString& aTitle)
}
// use the localized one instead
nsXPIDLString value;
nsresult rv = mBundle->GetStringFromName(
NS_LITERAL_STRING("localhost").get(), getter_Copies(value));
if (NS_SUCCEEDED(rv))
CopyUTF16toUTF8(value, aTitle);
else
aTitle.Truncate(0);
GetStringFromName(NS_LITERAL_STRING("localhost").get(), aTitle);
}
void
nsNavHistory::GetAgeInDaysString(PRInt32 aInt, const PRUnichar *aName, nsACString& aResult)
{
nsAutoString intString;
intString.AppendInt(aInt);
const PRUnichar* strings[1] = { intString.get() };
nsXPIDLString value;
nsresult rv = mBundle->FormatStringFromName(aName, strings,
1, getter_Copies(value));
if (NS_SUCCEEDED(rv))
CopyUTF16toUTF8(value, aResult);
else
aResult.Truncate(0);
}
void
nsNavHistory::GetStringFromName(const PRUnichar *aName, nsACString& aResult)
{
nsXPIDLString value;
nsresult rv = mBundle->GetStringFromName(aName, getter_Copies(value));
if (NS_SUCCEEDED(rv))
CopyUTF16toUTF8(value, aResult);
else
aResult.Truncate(0);
}
// nsNavHistory::SetPageTitleInternal
//

View File

@ -477,10 +477,19 @@ protected:
nsNavHistoryQueryOptions* aOptions,
nsCOMArray<nsNavHistoryResultNode>* aResults);
void GetAgeInDaysString(PRInt32 aInt, const PRUnichar *aName,
nsACString& aResult);
void GetStringFromName(const PRUnichar *aName, nsACString& aResult);
void TitleForDomain(const nsCString& domain, nsACString& aTitle);
nsresult SetPageTitleInternal(nsIURI* aURI, PRBool aIsUserTitle,
const nsAString& aTitle);
nsresult GroupByDay(const nsCOMArray<nsNavHistoryResultNode>& aSource,
nsCOMArray<nsNavHistoryResultNode>* aDest);
nsresult GroupByHost(const nsCOMArray<nsNavHistoryResultNode>& aSource,
nsCOMArray<nsNavHistoryResultNode>* aDest,
PRBool aIsDomain);

View File

@ -445,6 +445,9 @@ nsNavHistoryContainerResultNode::FillStats()
container->FillStats();
}
mAccessCount += node->mAccessCount;
// this is how container nodes get sorted by date
// (of type nsINavHistoryResultNode::RESULT_TYPE_DAY, for example)
// The container gets the most recent time of the child nodes.
if (node->mTime > mTime)
mTime = node->mTime;
}
@ -698,6 +701,18 @@ PRInt32 PR_CALLBACK nsNavHistoryContainerResultNode::SortComparison_TitleLess(
return bType - aType;
}
if (aType == nsINavHistoryResultNode::RESULT_TYPE_DAY) {
// for the history sidebar, when we do "View | By Date" or
// "View | By Date and Site" we sort by SORT_BY_TITLE_ASCENDING.
//
// so to make the day container show up in the desired order
// we need to compare by time, instead of by title.
//
// hard coding this isn't ideal, but we can't currently have
// one sort per grouping. see bug #359332 on that issue.
return -ComparePRTime(a->mTime, b->mTime);
}
nsICollation* collation = NS_STATIC_CAST(nsICollation*, closure);
PRInt32 value = -1; // default to returning "true" on failure
collation->CompareString(
@ -1983,11 +1998,11 @@ nsNavHistoryQueryResultNode::GetSortType()
} else if (mResult) {
return mResult->mSortingMode;
}
NS_NOTREACHED("We should always have a result");
return nsINavHistoryQueryOptions::SORT_BY_NONE;
}
// nsNavHistoryResultNode::OnBeginUpdateBatch
NS_IMETHODIMP
@ -3864,10 +3879,14 @@ nsNavHistoryResultTreeViewer::ComputeShowSessions()
mResult->mSortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
return; // not date sorting
// showing sessions only makes sense if we are grouping by date
// any other grouping (or recursive grouping) doesn't make sense
PRUint32 groupCount;
options->GroupingMode(&groupCount);
if (groupCount > 0)
return;
const PRUint32* groupings = options->GroupingMode(&groupCount);
for (PRUint32 i = 0; i < groupCount; i ++) {
if (groupings[i] != nsINavHistoryQueryOptions::GROUP_BY_DAY)
return; // non-time-based grouping
}
mShowSessions = PR_TRUE;
}

View File

@ -275,7 +275,8 @@ public:
return (type == nsINavHistoryResultNode::RESULT_TYPE_HOST ||
type == nsINavHistoryResultNode::RESULT_TYPE_REMOTE_CONTAINER ||
type == nsINavHistoryResultNode::RESULT_TYPE_QUERY ||
type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER);
type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER ||
type == nsINavHistoryResultNode::RESULT_TYPE_DAY);
}
PRBool IsContainer() {
PRUint32 type;
@ -287,7 +288,8 @@ public:
// itself, and is used when recursively updating a query. This currently
// includes only host containers, but may be extended to support things
// like days or other criteria. It doesn't include other queries and folders.
return (type == nsINavHistoryResultNode::RESULT_TYPE_HOST);
return (type == nsINavHistoryResultNode::RESULT_TYPE_HOST ||
type == nsINavHistoryResultNode::RESULT_TYPE_DAY);
}
PRBool IsQuerySubcontainer() {
PRUint32 type;