1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "msgCore.h"
7 #include "nsMsgUtils.h"
8 #include "nsMsgGroupView.h"
9 #include "nsIMsgHdr.h"
10 #include "nsIMsgThread.h"
11 #include "nsIDBFolderInfo.h"
12 #include "nsIMsgSearchSession.h"
13 #include "nsMsgGroupThread.h"
14 #include "nsTreeColumns.h"
15 #include "nsMsgMessageFlags.h"
16 #include <plhash.h>
17 #include "mozilla/Attributes.h"
18
19 // Allocate this more to avoid reallocation on new mail.
20 #define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25
21 // Max msghdr cache entries.
22 #define MSGHDR_CACHE_MAX_SIZE 8192
23 #define MSGHDR_CACHE_DEFAULT_SIZE 100
24
nsMsgGroupView()25 nsMsgGroupView::nsMsgGroupView() {
26 m_dayChanged = false;
27 m_lastCurExplodedTime.tm_mday = 0;
28 }
29
~nsMsgGroupView()30 nsMsgGroupView::~nsMsgGroupView() {}
31
32 NS_IMETHODIMP
Open(nsIMsgFolder * aFolder,nsMsgViewSortTypeValue aSortType,nsMsgViewSortOrderValue aSortOrder,nsMsgViewFlagsTypeValue aViewFlags,int32_t * aCount)33 nsMsgGroupView::Open(nsIMsgFolder* aFolder, nsMsgViewSortTypeValue aSortType,
34 nsMsgViewSortOrderValue aSortOrder,
35 nsMsgViewFlagsTypeValue aViewFlags, int32_t* aCount) {
36 nsresult rv =
37 nsMsgDBView::Open(aFolder, aSortType, aSortOrder, aViewFlags, aCount);
38 NS_ENSURE_SUCCESS(rv, rv);
39
40 nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
41 PersistFolderInfo(getter_AddRefs(dbFolderInfo));
42
43 nsCOMPtr<nsIMsgEnumerator> headers;
44 rv = m_db->EnumerateMessages(getter_AddRefs(headers));
45 NS_ENSURE_SUCCESS(rv, rv);
46
47 return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount);
48 }
49
InternalClose()50 void nsMsgGroupView::InternalClose() {
51 m_groupsTable.Clear();
52 // Nothing to do if we're not grouped.
53 if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) return;
54
55 bool rcvDate = false;
56
57 if (m_sortType == nsMsgViewSortType::byReceived) rcvDate = true;
58
59 if (m_db && ((m_sortType == nsMsgViewSortType::byDate) ||
60 (m_sortType == nsMsgViewSortType::byReceived))) {
61 nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
62 m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
63 if (dbFolderInfo) {
64 uint32_t expandFlags = 0;
65 uint32_t num = GetSize();
66
67 for (uint32_t i = 0; i < num; i++) {
68 if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD &&
69 !(m_flags[i] & nsMsgMessageFlags::Elided)) {
70 nsCOMPtr<nsIMsgDBHdr> msgHdr;
71 GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
72 if (msgHdr) {
73 uint32_t ageBucket;
74 nsresult rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
75 if (NS_SUCCEEDED(rv)) expandFlags |= 1 << ageBucket;
76 }
77 }
78 }
79 dbFolderInfo->SetUint32Property("dateGroupFlags", expandFlags);
80 }
81 }
82 }
83
84 NS_IMETHODIMP
Close()85 nsMsgGroupView::Close() {
86 InternalClose();
87 return nsMsgDBView::Close();
88 }
89
90 // Set rcvDate to true to get the Received: date instead of the Date: date.
GetAgeBucketValue(nsIMsgDBHdr * aMsgHdr,uint32_t * aAgeBucket,bool rcvDate)91 nsresult nsMsgGroupView::GetAgeBucketValue(nsIMsgDBHdr* aMsgHdr,
92 uint32_t* aAgeBucket, bool rcvDate) {
93 NS_ENSURE_ARG_POINTER(aMsgHdr);
94 NS_ENSURE_ARG_POINTER(aAgeBucket);
95
96 PRTime dateOfMsg;
97 nsresult rv;
98 if (!rcvDate)
99 rv = aMsgHdr->GetDate(&dateOfMsg);
100 else {
101 uint32_t rcvDateSecs;
102 rv = aMsgHdr->GetUint32Property("dateReceived", &rcvDateSecs);
103 Seconds2PRTime(rcvDateSecs, &dateOfMsg);
104 }
105 NS_ENSURE_SUCCESS(rv, rv);
106
107 PRTime currentTime = PR_Now();
108 PRExplodedTime currentExplodedTime;
109 PR_ExplodeTime(currentTime, PR_LocalTimeParameters, ¤tExplodedTime);
110 PRExplodedTime explodedMsgTime;
111 PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
112
113 if (m_lastCurExplodedTime.tm_mday &&
114 m_lastCurExplodedTime.tm_mday != currentExplodedTime.tm_mday)
115 // This will cause us to rebuild the view.
116 m_dayChanged = true;
117
118 m_lastCurExplodedTime = currentExplodedTime;
119 if (currentExplodedTime.tm_year == explodedMsgTime.tm_year &&
120 currentExplodedTime.tm_month == explodedMsgTime.tm_month &&
121 currentExplodedTime.tm_mday == explodedMsgTime.tm_mday) {
122 // Same day.
123 *aAgeBucket = 1;
124 }
125 // Figure out how many days ago this msg arrived.
126 else if (currentTime > dateOfMsg) {
127 // Setting the time variables to local time.
128 int64_t GMTLocalTimeShift = currentExplodedTime.tm_params.tp_gmt_offset +
129 currentExplodedTime.tm_params.tp_dst_offset;
130 GMTLocalTimeShift *= PR_USEC_PER_SEC;
131 currentTime += GMTLocalTimeShift;
132 dateOfMsg += GMTLocalTimeShift;
133
134 // The most recent midnight, counting from current time.
135 int64_t mostRecentMidnight = currentTime - currentTime % PR_USEC_PER_DAY;
136 int64_t yesterday = mostRecentMidnight - PR_USEC_PER_DAY;
137 // Most recent midnight minus 6 days.
138 int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6);
139
140 // Was the message sent yesterday?
141 if (dateOfMsg >= yesterday)
142 *aAgeBucket = 2;
143 else if (dateOfMsg >= mostRecentWeek)
144 *aAgeBucket = 3;
145 else {
146 int64_t lastTwoWeeks = mostRecentMidnight - PR_USEC_PER_DAY * 13;
147 *aAgeBucket = (dateOfMsg >= lastTwoWeeks) ? 4 : 5;
148 }
149 } else {
150 // All that remains is a future date.
151 *aAgeBucket = 6;
152 }
153 return NS_OK;
154 }
155
HashHdr(nsIMsgDBHdr * msgHdr,nsString & aHashKey)156 nsresult nsMsgGroupView::HashHdr(nsIMsgDBHdr* msgHdr, nsString& aHashKey) {
157 nsCString cStringKey;
158 aHashKey.Truncate();
159 nsresult rv = NS_OK;
160 bool rcvDate = false;
161
162 switch (m_sortType) {
163 case nsMsgViewSortType::bySubject:
164 (void)msgHdr->GetSubject(getter_Copies(cStringKey));
165 CopyASCIItoUTF16(cStringKey, aHashKey);
166 break;
167 case nsMsgViewSortType::byAuthor:
168 rv = nsMsgDBView::FetchAuthor(msgHdr, aHashKey);
169 break;
170 case nsMsgViewSortType::byRecipient:
171 (void)msgHdr->GetRecipients(getter_Copies(cStringKey));
172 CopyASCIItoUTF16(cStringKey, aHashKey);
173 break;
174 case nsMsgViewSortType::byAccount:
175 case nsMsgViewSortType::byTags: {
176 nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
177 if (!dbToUse)
178 // Probably a search view.
179 GetDBForViewIndex(0, getter_AddRefs(dbToUse));
180
181 rv = (m_sortType == nsMsgViewSortType::byAccount)
182 ? FetchAccount(msgHdr, aHashKey)
183 : FetchTags(msgHdr, aHashKey);
184 } break;
185 case nsMsgViewSortType::byAttachments: {
186 uint32_t flags;
187 msgHdr->GetFlags(&flags);
188 aHashKey.Assign(flags & nsMsgMessageFlags::Attachment ? '1' : '0');
189 break;
190 }
191 case nsMsgViewSortType::byFlagged: {
192 uint32_t flags;
193 msgHdr->GetFlags(&flags);
194 aHashKey.Assign(flags & nsMsgMessageFlags::Marked ? '1' : '0');
195 break;
196 }
197 case nsMsgViewSortType::byPriority: {
198 nsMsgPriorityValue priority;
199 msgHdr->GetPriority(&priority);
200 aHashKey.AppendInt(priority);
201 } break;
202 case nsMsgViewSortType::byStatus: {
203 uint32_t status = 0;
204 GetStatusSortValue(msgHdr, &status);
205 aHashKey.AppendInt(status);
206 } break;
207 case nsMsgViewSortType::byReceived:
208 rcvDate = true;
209 [[fallthrough]];
210 case nsMsgViewSortType::byDate: {
211 uint32_t ageBucket;
212 rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
213 if (NS_SUCCEEDED(rv)) aHashKey.AppendInt(ageBucket);
214
215 break;
216 }
217 case nsMsgViewSortType::byCustom: {
218 nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
219 if (colHandler) {
220 bool isString;
221 colHandler->IsString(&isString);
222 if (isString) {
223 rv = colHandler->GetSortStringForRow(msgHdr, aHashKey);
224 } else {
225 uint32_t intKey;
226 rv = colHandler->GetSortLongForRow(msgHdr, &intKey);
227 aHashKey.AppendInt(intKey);
228 }
229 }
230 break;
231 }
232 case nsMsgViewSortType::byCorrespondent:
233 if (IsOutgoingMsg(msgHdr))
234 rv = FetchRecipients(msgHdr, aHashKey);
235 else
236 rv = FetchAuthor(msgHdr, aHashKey);
237
238 break;
239 default:
240 NS_ASSERTION(false, "no hash key for this type");
241 rv = NS_ERROR_FAILURE;
242 }
243 return rv;
244 }
245
CreateGroupThread(nsIMsgDatabase * db)246 nsMsgGroupThread* nsMsgGroupView::CreateGroupThread(nsIMsgDatabase* db) {
247 return new nsMsgGroupThread(db);
248 }
249
AddHdrToThread(nsIMsgDBHdr * msgHdr,bool * pNewThread)250 nsMsgGroupThread* nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr* msgHdr,
251 bool* pNewThread) {
252 nsMsgKey msgKey;
253 uint32_t msgFlags;
254 msgHdr->GetMessageKey(&msgKey);
255 msgHdr->GetFlags(&msgFlags);
256 nsString hashKey;
257 nsresult rv = HashHdr(msgHdr, hashKey);
258 if (NS_FAILED(rv)) return nullptr;
259
260 // if (m_sortType == nsMsgViewSortType::byDate)
261 // msgKey = ((nsPRUint32Key *)hashKey)->GetValue();
262 nsCOMPtr<nsIMsgThread> msgThread;
263 m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
264 bool newThread = !msgThread;
265 *pNewThread = newThread;
266 // Index of first message in thread in view.
267 nsMsgViewIndex viewIndexOfThread;
268 // Index of newly added header in thread.
269 nsMsgViewIndex threadInsertIndex;
270
271 nsMsgGroupThread* foundThread =
272 static_cast<nsMsgGroupThread*>(msgThread.get());
273 if (foundThread) {
274 // Find the view index of the root node of the thread in the view.
275 viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread, true);
276 if (viewIndexOfThread == nsMsgViewIndex_None) {
277 // Something is wrong with the group table. Remove the old group and
278 // insert a new one.
279 m_groupsTable.Remove(hashKey);
280 foundThread = nullptr;
281 *pNewThread = newThread = true;
282 }
283 }
284
285 // If the thread does not already exist, create one
286 if (!foundThread) {
287 foundThread = CreateGroupThread(m_db);
288 msgThread = foundThread;
289 m_groupsTable.InsertOrUpdate(hashKey, msgThread);
290 if (GroupViewUsesDummyRow()) {
291 foundThread->m_dummy = true;
292 msgFlags |= MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN;
293 }
294
295 viewIndexOfThread = GetInsertIndex(msgHdr);
296 if (viewIndexOfThread == nsMsgViewIndex_None)
297 viewIndexOfThread = m_keys.Length();
298
299 // Add the thread root node to the view.
300 InsertMsgHdrAt(
301 viewIndexOfThread, msgHdr, msgKey,
302 msgFlags | MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided, 0);
303
304 // For dummy rows, Have the header serve as the dummy node (it will be
305 // added again for its actual content later).
306 if (GroupViewUsesDummyRow()) foundThread->InsertMsgHdrAt(0, msgHdr);
307
308 // Calculate the (integer thread key); this really only needs to be done for
309 // the byDate case where the expanded state of the groups can be easily
310 // persisted and restored because of the bounded, consecutive value space
311 // occupied. We calculate an integer value in all cases mainly because
312 // it's the sanest choice available...
313 // (The thread key needs to be an integer, so parse hash keys that are
314 // stringified integers to real integers, and hash actual strings into
315 // integers.)
316 if ((m_sortType == nsMsgViewSortType::byAttachments) ||
317 (m_sortType == nsMsgViewSortType::byFlagged) ||
318 (m_sortType == nsMsgViewSortType::byPriority) ||
319 (m_sortType == nsMsgViewSortType::byStatus) ||
320 (m_sortType == nsMsgViewSortType::byReceived) ||
321 (m_sortType == nsMsgViewSortType::byDate))
322 foundThread->m_threadKey =
323 atoi(NS_LossyConvertUTF16toASCII(hashKey).get());
324 else
325 foundThread->m_threadKey =
326 (nsMsgKey)PL_HashString(NS_LossyConvertUTF16toASCII(hashKey).get());
327 }
328
329 // Add the message to the thread as an actual content-bearing header.
330 // (If we use dummy rows, it was already added to the thread during creation.)
331 threadInsertIndex = foundThread->AddChildFromGroupView(msgHdr, this);
332 // Check if new hdr became thread root.
333 if (!newThread && threadInsertIndex == 0) {
334 // Update the root node's header (in the view) to be the same as the root
335 // node in the thread.
336 SetMsgHdrAt(msgHdr, viewIndexOfThread, msgKey,
337 (msgFlags & ~(nsMsgMessageFlags::Elided)) |
338 // Maintain elided flag and dummy flag.
339 (m_flags[viewIndexOfThread] &
340 (nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_DUMMY)) |
341 // Ensure thread and has-children flags are set.
342 MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN,
343 0);
344 // Update the content-bearing copy in the thread to match. (the root and
345 // first nodes in the thread should always be the same header.)
346 // Note: the guy who used to be the root will still exist. If our list of
347 // nodes was [A A], a new node B is introduced which sorts to be the first
348 // node, giving us [B A A], our copy makes that [B B A], and things are
349 // right in the world (since we want the first two headers to be the same
350 // since one is our dummy and one is real.)
351 if (GroupViewUsesDummyRow()) {
352 // Replace the old duplicate dummy header.
353 // We do not update the content-bearing copy in the view to match; we
354 // leave that up to OnNewHeader, which is the piece of code who gets to
355 // care about whether the thread's children are shown or not (elided).
356 foundThread->SetMsgHdrAt(1, msgHdr);
357 }
358 }
359
360 return foundThread;
361 }
362
363 NS_IMETHODIMP
OpenWithHdrs(nsIMsgEnumerator * aHeaders,nsMsgViewSortTypeValue aSortType,nsMsgViewSortOrderValue aSortOrder,nsMsgViewFlagsTypeValue aViewFlags,int32_t * aCount)364 nsMsgGroupView::OpenWithHdrs(nsIMsgEnumerator* aHeaders,
365 nsMsgViewSortTypeValue aSortType,
366 nsMsgViewSortOrderValue aSortOrder,
367 nsMsgViewFlagsTypeValue aViewFlags,
368 int32_t* aCount) {
369 nsresult rv = NS_OK;
370
371 m_groupsTable.Clear();
372 if (aSortType == nsMsgViewSortType::byThread ||
373 aSortType == nsMsgViewSortType::byId ||
374 aSortType == nsMsgViewSortType::byNone ||
375 aSortType == nsMsgViewSortType::bySize)
376 return NS_ERROR_INVALID_ARG;
377
378 m_sortType = aSortType;
379 m_sortOrder = aSortOrder;
380 m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay |
381 nsMsgViewFlagsType::kGroupBySort;
382 SaveSortInfo(m_sortType, m_sortOrder);
383
384 if (m_sortType == nsMsgViewSortType::byCustom) {
385 // If the desired sort is a custom column and there is no handler found,
386 // it hasn't been registered yet; after the custom column observer is
387 // notified with MsgCreateDBView and registers the handler, it will come
388 // back and build the view.
389 nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
390 if (!colHandler) return rv;
391 }
392
393 bool hasMore;
394 nsCOMPtr<nsISupports> supports;
395 nsCOMPtr<nsIMsgDBHdr> msgHdr;
396 while (NS_SUCCEEDED(rv) &&
397 NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) {
398 rv = aHeaders->GetNext(getter_AddRefs(msgHdr));
399 if (NS_SUCCEEDED(rv) && msgHdr) {
400 bool notUsed;
401 AddHdrToThread(msgHdr, ¬Used);
402 }
403 }
404 uint32_t expandFlags = 0;
405 bool expandAll = m_viewFlags & nsMsgViewFlagsType::kExpandAll;
406 uint32_t viewFlag =
407 (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0;
408 if (viewFlag && m_db) {
409 nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
410 nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
411 NS_ENSURE_SUCCESS(rv, rv);
412 if (dbFolderInfo)
413 dbFolderInfo->GetUint32Property("dateGroupFlags", 0, &expandFlags);
414 }
415 // Go through the view updating the flags for threads with more than one
416 // message, and if grouped by date, expanding threads that were expanded
417 // before.
418 for (uint32_t viewIndex = 0; viewIndex < m_keys.Length(); viewIndex++) {
419 nsCOMPtr<nsIMsgThread> thread;
420 GetThreadContainingIndex(viewIndex, getter_AddRefs(thread));
421 if (thread) {
422 uint32_t numChildren;
423 thread->GetNumChildren(&numChildren);
424 if (numChildren > 1 || viewFlag)
425 OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN);
426 if (expandAll || expandFlags) {
427 nsMsgGroupThread* groupThread =
428 static_cast<nsMsgGroupThread*>((nsIMsgThread*)thread);
429 if (expandAll || expandFlags & (1 << groupThread->m_threadKey)) {
430 uint32_t numExpanded;
431 ExpandByIndex(viewIndex, &numExpanded);
432 viewIndex += numExpanded;
433 }
434 }
435 }
436 }
437 *aCount = m_keys.Length();
438 return rv;
439 }
440
441 // We wouldn't need this if we never instantiated this directly,
442 // but instead used nsMsgThreadedDBView with the grouping flag set.
443 // Or, we could get rid of the nsMsgThreadedDBView impl of this method.
444 NS_IMETHODIMP
GetViewType(nsMsgViewTypeValue * aViewType)445 nsMsgGroupView::GetViewType(nsMsgViewTypeValue* aViewType) {
446 NS_ENSURE_ARG_POINTER(aViewType);
447 *aViewType = nsMsgViewType::eShowAllThreads;
448 return NS_OK;
449 }
450
451 NS_IMETHODIMP
CopyDBView(nsMsgDBView * aNewMsgDBView,nsIMessenger * aMessengerInstance,nsIMsgWindow * aMsgWindow,nsIMsgDBViewCommandUpdater * aCmdUpdater)452 nsMsgGroupView::CopyDBView(nsMsgDBView* aNewMsgDBView,
453 nsIMessenger* aMessengerInstance,
454 nsIMsgWindow* aMsgWindow,
455 nsIMsgDBViewCommandUpdater* aCmdUpdater) {
456 nsMsgDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow,
457 aCmdUpdater);
458 nsMsgGroupView* newMsgDBView = (nsMsgGroupView*)aNewMsgDBView;
459
460 // If grouped, we need to clone the group thread hash table.
461 if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) {
462 for (auto iter = m_groupsTable.Iter(); !iter.Done(); iter.Next()) {
463 newMsgDBView->m_groupsTable.InsertOrUpdate(iter.Key(), iter.UserData());
464 }
465 }
466 return NS_OK;
467 }
468
469 // E.g., if the day has changed, we need to close and re-open the view.
470 // Or, if we're switching between grouping and threading in a cross-folder
471 // saved search. In that case, we needed to build an enumerator based on the
472 // old view type, and internally close the view based on its old type, but
473 // rebuild the new view based on the new view type. So we pass the new
474 // view flags to OpenWithHdrs.
RebuildView(nsMsgViewFlagsTypeValue newFlags)475 nsresult nsMsgGroupView::RebuildView(nsMsgViewFlagsTypeValue newFlags) {
476 nsCOMPtr<nsIMsgEnumerator> headers;
477 if (NS_SUCCEEDED(GetMessageEnumerator(getter_AddRefs(headers)))) {
478 int32_t count;
479 m_dayChanged = false;
480 AutoTArray<nsMsgKey, 1> preservedSelection;
481 nsMsgKey curSelectedKey;
482 SaveAndClearSelection(&curSelectedKey, preservedSelection);
483 InternalClose();
484 int32_t oldSize = GetSize();
485 // This is important, because the tree will ask us for our row count,
486 // which gets determined from the number of keys.
487 m_keys.Clear();
488 // Be consistent.
489 m_flags.Clear();
490 m_levels.Clear();
491
492 // This needs to happen after we remove all the keys, since
493 // RowCountChanged() will call our GetRowCount().
494 if (mTree) mTree->RowCountChanged(0, -oldSize);
495
496 SetSuppressChangeNotifications(true);
497 nsresult rv =
498 OpenWithHdrs(headers, m_sortType, m_sortOrder, newFlags, &count);
499 SetSuppressChangeNotifications(false);
500 if (mTree) mTree->RowCountChanged(0, GetSize());
501
502 NS_ENSURE_SUCCESS(rv, rv);
503
504 // Now, restore our desired selection.
505 AutoTArray<nsMsgKey, 1> keyArray;
506 keyArray.AppendElement(curSelectedKey);
507
508 return RestoreSelection(curSelectedKey, keyArray);
509 }
510 return NS_OK;
511 }
512
OnNewHeader(nsIMsgDBHdr * newHdr,nsMsgKey aParentKey,bool ensureListed)513 nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey,
514 bool ensureListed) {
515 if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
516 return nsMsgDBView::OnNewHeader(newHdr, aParentKey, ensureListed);
517
518 // Check if we're adding a header, and the current day has changed.
519 // If it has, we're just going to close and re-open the view so things
520 // will be correctly categorized.
521 if (m_dayChanged) return RebuildView(m_viewFlags);
522
523 bool newThread;
524 nsMsgGroupThread* thread = AddHdrToThread(newHdr, &newThread);
525 if (thread) {
526 // Find the view index of (the root node of) the thread.
527 nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(newHdr);
528 // May need to fix thread counts.
529 if (threadIndex != nsMsgViewIndex_None) {
530 if (newThread) {
531 // AddHdrToThread creates the header elided, so we need to un-elide it
532 // if we want it expanded.
533 if (m_viewFlags & nsMsgViewFlagsType::kExpandAll)
534 m_flags[threadIndex] &= ~nsMsgMessageFlags::Elided;
535 } else {
536 m_flags[threadIndex] |=
537 MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
538 }
539
540 int32_t numRowsToInvalidate = 1;
541 // If the thread is expanded (not elided), we should add the header to
542 // the view.
543 if (!(m_flags[threadIndex] & nsMsgMessageFlags::Elided)) {
544 uint32_t msgIndexInThread = thread->FindMsgHdr(newHdr);
545 bool insertedAtThreadRoot = !msgIndexInThread;
546 // Add any new display node and potentially fix-up changes in the root.
547 // (If this is a new thread and we are not using a dummy row, the only
548 // node to display is the root node which has already been added by
549 // AddHdrToThread. And since there is just the one, no change in root
550 // could have occurred, so we have nothing to do.)
551 if (!newThread || GroupViewUsesDummyRow()) {
552 // We never want to insert/update the root node, because
553 // AddHdrToThread has already done that for us (in all cases).
554 if (insertedAtThreadRoot) msgIndexInThread++;
555 // If this header is the new parent of the thread... AND
556 // If we are not using a dummy row, this means we need to append our
557 // old node as the first child of the new root.
558 // (If we are using a dummy row, the old node's "content" node already
559 // exists (at position threadIndex + 1) and we need to insert the
560 // "content" copy of the new root node there, pushing our old
561 // "content" node down.)
562 // Example mini-diagrams, wrapping the to-add thing with ()
563 // No dummy row; we had: [A], now we have [B], we want [B (A)].
564 // Dummy row; we had: [A A], now we have [B A], we want [B (B) A].
565 // (Coming into this we're adding 'B')
566 if (!newThread && insertedAtThreadRoot && !GroupViewUsesDummyRow()) {
567 // Grab a copy of the old root node ('A') from the thread so we can
568 // insert it. (offset msgIndexInThread=1 is the right thing; we are
569 // non-dummy.)
570 thread->GetChildHdrAt(msgIndexInThread, &newHdr);
571 }
572 // Nothing to do for dummy case, we're already inserting 'B'.
573
574 nsMsgKey msgKey;
575 uint32_t msgFlags;
576 newHdr->GetMessageKey(&msgKey);
577 newHdr->GetFlags(&msgFlags);
578 InsertMsgHdrAt(threadIndex + msgIndexInThread, newHdr, msgKey,
579 msgFlags, 1);
580 }
581 // The call to NoteChange() has to happen after we add the key
582 // as NoteChange() will call RowCountChanged() which will call our
583 // GetRowCount().
584 // (msgIndexInThread states - new thread: 0, old thread at root: 1).
585 if (newThread && GroupViewUsesDummyRow())
586 NoteChange(threadIndex, 2, nsMsgViewNotificationCode::insertOrDelete);
587 else
588 NoteChange(threadIndex + msgIndexInThread, 1,
589 nsMsgViewNotificationCode::insertOrDelete);
590
591 numRowsToInvalidate = msgIndexInThread;
592 } else if (newThread) {
593 // We still need the addition notification for new threads when elided.
594 NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
595 }
596
597 NoteChange(threadIndex, numRowsToInvalidate,
598 nsMsgViewNotificationCode::changed);
599 }
600 }
601
602 // If thread is expanded, we need to add hdr to view...
603 return NS_OK;
604 }
605
606 NS_IMETHODIMP
OnHdrFlagsChanged(nsIMsgDBHdr * aHdrChanged,uint32_t aOldFlags,uint32_t aNewFlags,nsIDBChangeListener * aInstigator)607 nsMsgGroupView::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags,
608 uint32_t aNewFlags,
609 nsIDBChangeListener* aInstigator) {
610 if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
611 return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
612 aInstigator);
613
614 nsCOMPtr<nsIMsgThread> thread;
615
616 // Check if we're adding a header, and the current day has changed.
617 // If it has, we're just going to close and re-open the view so things
618 // will be correctly categorized.
619 if (m_dayChanged) return RebuildView(m_viewFlags);
620
621 nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread));
622 NS_ENSURE_SUCCESS(rv, rv);
623 uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
624 if (deltaFlags & nsMsgMessageFlags::Read)
625 thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read);
626
627 return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
628 aInstigator);
629 }
630
631 NS_IMETHODIMP
OnHdrDeleted(nsIMsgDBHdr * aHdrDeleted,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)632 nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey,
633 int32_t aFlags, nsIDBChangeListener* aInstigator) {
634 if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
635 return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags,
636 aInstigator);
637
638 // Check if we're adding a header, and the current day has changed.
639 // If it has, we're just going to close and re-open the view so things
640 // will be correctly categorized.
641 if (m_dayChanged) return RebuildView(m_viewFlags);
642
643 nsCOMPtr<nsIMsgThread> thread;
644 nsMsgKey keyDeleted;
645 aHdrDeleted->GetMessageKey(&keyDeleted);
646
647 nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread));
648 NS_ENSURE_SUCCESS(rv, rv);
649 nsMsgViewIndex viewIndexOfThread =
650 GetIndexOfFirstDisplayedKeyInThread(thread, true); // Yes to dummy node.
651
652 thread->RemoveChildHdr(aHdrDeleted, nullptr);
653
654 nsMsgGroupThread* groupThread =
655 static_cast<nsMsgGroupThread*>((nsIMsgThread*)thread);
656
657 bool rootDeleted = viewIndexOfThread != nsMsgKey_None &&
658 m_keys[viewIndexOfThread] == keyDeleted;
659 rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
660 if (groupThread->m_dummy) {
661 if (!groupThread->NumRealChildren()) {
662 // Get rid of dummy.
663 thread->RemoveChildAt(0);
664 if (viewIndexOfThread != nsMsgKey_None) {
665 RemoveByIndex(viewIndexOfThread);
666 if (m_deletingRows)
667 mIndicesToNoteChange.AppendElement(viewIndexOfThread);
668 }
669 } else if (rootDeleted) {
670 // Reflect new thread root into view.dummy row.
671 nsCOMPtr<nsIMsgDBHdr> hdr;
672 thread->GetChildHdrAt(0, getter_AddRefs(hdr));
673 if (hdr) {
674 nsMsgKey msgKey;
675 hdr->GetMessageKey(&msgKey);
676 SetMsgHdrAt(hdr, viewIndexOfThread, msgKey, m_flags[viewIndexOfThread],
677 0);
678 }
679 }
680 }
681 if (!groupThread->m_keys.Length()) {
682 nsString hashKey;
683 rv = HashHdr(aHdrDeleted, hashKey);
684 if (NS_SUCCEEDED(rv)) m_groupsTable.Remove(hashKey);
685 }
686 return rv;
687 }
688
689 NS_IMETHODIMP
GetRowProperties(int32_t aRow,nsAString & aProperties)690 nsMsgGroupView::GetRowProperties(int32_t aRow, nsAString& aProperties) {
691 if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
692
693 if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) {
694 aProperties.AssignLiteral("dummy");
695 return NS_OK;
696 }
697
698 return nsMsgDBView::GetRowProperties(aRow, aProperties);
699 }
700
701 NS_IMETHODIMP
GetCellProperties(int32_t aRow,nsTreeColumn * aCol,nsAString & aProperties)702 nsMsgGroupView::GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
703 nsAString& aProperties) {
704 if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
705
706 if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) {
707 aProperties.AssignLiteral("dummy read");
708
709 if (!(m_flags[aRow] & nsMsgMessageFlags::Elided)) return NS_OK;
710
711 // Set unread property if a collapsed group thread has unread.
712 nsCOMPtr<nsIMsgDBHdr> msgHdr;
713 nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
714 NS_ENSURE_SUCCESS(rv, rv);
715 nsString hashKey;
716 rv = HashHdr(msgHdr, hashKey);
717 if (NS_FAILED(rv)) return NS_OK;
718
719 nsCOMPtr<nsIMsgThread> msgThread;
720 m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
721 nsMsgGroupThread* groupThread =
722 static_cast<nsMsgGroupThread*>(msgThread.get());
723 if (!groupThread) return NS_OK;
724
725 uint32_t numUnrMsg = 0;
726 groupThread->GetNumUnreadChildren(&numUnrMsg);
727 if (numUnrMsg > 0) aProperties.AppendLiteral(" hasUnread");
728
729 return NS_OK;
730 }
731
732 return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties);
733 }
734
735 NS_IMETHODIMP
CellTextForColumn(int32_t aRow,const nsAString & aColumnName,nsAString & aValue)736 nsMsgGroupView::CellTextForColumn(int32_t aRow, const nsAString& aColumnName,
737 nsAString& aValue) {
738 if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
739
740 if (!(m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) ||
741 aColumnName.EqualsLiteral("unreadCol"))
742 return nsMsgDBView::CellTextForColumn(aRow, aColumnName, aValue);
743
744 // We only treat "subject" and "total" here.
745 bool isSubject;
746 if (!(isSubject = aColumnName.EqualsLiteral("subjectCol")) &&
747 !aColumnName.EqualsLiteral("totalCol"))
748 return NS_OK;
749
750 nsCOMPtr<nsIMsgDBHdr> msgHdr;
751 nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
752 NS_ENSURE_SUCCESS(rv, rv);
753 nsString hashKey;
754 rv = HashHdr(msgHdr, hashKey);
755 if (NS_FAILED(rv)) return NS_OK;
756 nsCOMPtr<nsIMsgThread> msgThread;
757 m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
758 nsMsgGroupThread* groupThread =
759 static_cast<nsMsgGroupThread*>(msgThread.get());
760 if (isSubject) {
761 uint32_t flags;
762 bool rcvDate = false;
763 msgHdr->GetFlags(&flags);
764 aValue.Truncate();
765 nsString tmp_str;
766 switch (m_sortType) {
767 case nsMsgViewSortType::byReceived:
768 rcvDate = true;
769 [[fallthrough]];
770 case nsMsgViewSortType::byDate: {
771 uint32_t ageBucket = 0;
772 GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
773 switch (ageBucket) {
774 case 1:
775 if (m_kTodayString.IsEmpty())
776 m_kTodayString.Adopt(GetString(u"today"));
777
778 aValue.Assign(m_kTodayString);
779 break;
780 case 2:
781 if (m_kYesterdayString.IsEmpty())
782 m_kYesterdayString.Adopt(GetString(u"yesterday"));
783
784 aValue.Assign(m_kYesterdayString);
785 break;
786 case 3:
787 if (m_kLastWeekString.IsEmpty())
788 m_kLastWeekString.Adopt(GetString(u"last7Days"));
789
790 aValue.Assign(m_kLastWeekString);
791 break;
792 case 4:
793 if (m_kTwoWeeksAgoString.IsEmpty())
794 m_kTwoWeeksAgoString.Adopt(GetString(u"last14Days"));
795
796 aValue.Assign(m_kTwoWeeksAgoString);
797 break;
798 case 5:
799 if (m_kOldMailString.IsEmpty())
800 m_kOldMailString.Adopt(GetString(u"older"));
801
802 aValue.Assign(m_kOldMailString);
803 break;
804 default:
805 // Future date, error/spoofed.
806 if (m_kFutureDateString.IsEmpty())
807 m_kFutureDateString.Adopt(GetString(u"futureDate"));
808
809 aValue.Assign(m_kFutureDateString);
810 break;
811 }
812 break;
813 }
814 case nsMsgViewSortType::bySubject:
815 FetchSubject(msgHdr, m_flags[aRow], aValue);
816 break;
817 case nsMsgViewSortType::byAuthor:
818 FetchAuthor(msgHdr, aValue);
819 break;
820 case nsMsgViewSortType::byStatus:
821 rv = FetchStatus(m_flags[aRow], aValue);
822 if (aValue.IsEmpty()) {
823 tmp_str.Adopt(GetString(u"messagesWithNoStatus"));
824 aValue.Assign(tmp_str);
825 }
826 break;
827 case nsMsgViewSortType::byTags:
828 rv = FetchTags(msgHdr, aValue);
829 if (aValue.IsEmpty()) {
830 tmp_str.Adopt(GetString(u"untaggedMessages"));
831 aValue.Assign(tmp_str);
832 }
833 break;
834 case nsMsgViewSortType::byPriority:
835 FetchPriority(msgHdr, aValue);
836 if (aValue.IsEmpty()) {
837 tmp_str.Adopt(GetString(u"noPriority"));
838 aValue.Assign(tmp_str);
839 }
840 break;
841 case nsMsgViewSortType::byAccount:
842 FetchAccount(msgHdr, aValue);
843 break;
844 case nsMsgViewSortType::byRecipient:
845 FetchRecipients(msgHdr, aValue);
846 break;
847 case nsMsgViewSortType::byAttachments:
848 tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Attachment
849 ? u"attachments"
850 : u"noAttachments"));
851 aValue.Assign(tmp_str);
852 break;
853 case nsMsgViewSortType::byFlagged:
854 tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Marked
855 ? u"groupFlagged"
856 : u"notFlagged"));
857 aValue.Assign(tmp_str);
858 break;
859 // byLocation is a special case; we don't want to have duplicate
860 // all this logic in nsMsgSearchDBView, and its hash key is what we
861 // want anyways, so just copy it across.
862 case nsMsgViewSortType::byLocation:
863 case nsMsgViewSortType::byCorrespondent:
864 aValue = hashKey;
865 break;
866 case nsMsgViewSortType::byCustom: {
867 nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
868 if (colHandler) {
869 bool isString;
870 colHandler->IsString(&isString);
871 if (isString) {
872 rv = colHandler->GetSortStringForRow(msgHdr.get(), aValue);
873 } else {
874 uint32_t intKey;
875 rv = colHandler->GetSortLongForRow(msgHdr.get(), &intKey);
876 aValue.AppendInt(intKey);
877 }
878 }
879 if (aValue.IsEmpty()) aValue.Assign('*');
880 break;
881 }
882
883 default:
884 NS_ASSERTION(false, "we don't sort by group for this type");
885 break;
886 }
887
888 if (groupThread) {
889 // Get number of messages in group.
890 nsAutoString formattedCountMsg;
891 uint32_t numMsg = groupThread->NumRealChildren();
892 formattedCountMsg.AppendInt(numMsg);
893
894 // Get number of unread messages.
895 nsAutoString formattedCountUnrMsg;
896 uint32_t numUnrMsg = 0;
897 groupThread->GetNumUnreadChildren(&numUnrMsg);
898 formattedCountUnrMsg.AppendInt(numUnrMsg);
899
900 // Add text to header.
901 aValue.AppendLiteral(u" (");
902 if (numUnrMsg) {
903 aValue.Append(formattedCountUnrMsg);
904 aValue.Append(u'/');
905 }
906
907 aValue.Append(formattedCountMsg);
908 aValue.Append(u')');
909 }
910 } else {
911 nsAutoString formattedCountString;
912 uint32_t numChildren = (groupThread) ? groupThread->NumRealChildren() : 0;
913 formattedCountString.AppendInt(numChildren);
914 aValue.Assign(formattedCountString);
915 }
916 return NS_OK;
917 }
918
919 NS_IMETHODIMP
LoadMessageByViewIndex(nsMsgViewIndex aViewIndex)920 nsMsgGroupView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex) {
921 if (!IsValidIndex(aViewIndex)) return NS_MSG_INVALID_DBVIEW_INDEX;
922
923 if (m_flags[aViewIndex] & MSG_VIEW_FLAG_DUMMY) {
924 // If we used to have one item selected, and now we have more than one,
925 // we should clear the message pane.
926 nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
927 nsCOMPtr<nsIMsgWindowCommands> windowCommands;
928 if (msgWindow &&
929 NS_SUCCEEDED(
930 msgWindow->GetWindowCommands(getter_AddRefs(windowCommands))) &&
931 windowCommands) {
932 windowCommands->ClearMsgPane();
933 }
934
935 // Since we are selecting a dummy row, we should also clear out
936 // m_currentlyDisplayedMsgUri.
937 m_currentlyDisplayedMsgUri.Truncate();
938 return NS_OK;
939 } else {
940 return nsMsgDBView::LoadMessageByViewIndex(aViewIndex);
941 }
942 }
943
944 NS_IMETHODIMP
GetThreadContainingMsgHdr(nsIMsgDBHdr * msgHdr,nsIMsgThread ** pThread)945 nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
946 nsIMsgThread** pThread) {
947 if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
948 return nsMsgDBView::GetThreadContainingMsgHdr(msgHdr, pThread);
949
950 nsString hashKey;
951 nsresult rv = HashHdr(msgHdr, hashKey);
952 *pThread = nullptr;
953 if (NS_SUCCEEDED(rv)) {
954 nsCOMPtr<nsIMsgThread> thread;
955 m_groupsTable.Get(hashKey, getter_AddRefs(thread));
956 thread.forget(pThread);
957 }
958
959 return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
960 }
961
FindLevelInThread(nsIMsgDBHdr * msgHdr,nsMsgViewIndex startOfThread,nsMsgViewIndex viewIndex)962 int32_t nsMsgGroupView::FindLevelInThread(nsIMsgDBHdr* msgHdr,
963 nsMsgViewIndex startOfThread,
964 nsMsgViewIndex viewIndex) {
965 if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
966 return nsMsgDBView::FindLevelInThread(msgHdr, startOfThread, viewIndex);
967
968 return (startOfThread == viewIndex) ? 0 : 1;
969 }
970
GroupViewUsesDummyRow()971 bool nsMsgGroupView::GroupViewUsesDummyRow() {
972 // Return true to always use a header row as root grouped parent row.
973 return true;
974 }
975
976 NS_IMETHODIMP
AddColumnHandler(const nsAString & column,nsIMsgCustomColumnHandler * handler)977 nsMsgGroupView::AddColumnHandler(const nsAString& column,
978 nsIMsgCustomColumnHandler* handler) {
979 nsMsgDBView::AddColumnHandler(column, handler);
980
981 // If the sortType is byCustom and the desired custom column is the one just
982 // registered, build the view.
983 if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort &&
984 m_sortType == nsMsgViewSortType::byCustom) {
985 nsAutoString curCustomColumn;
986 GetCurCustomColumn(curCustomColumn);
987 if (curCustomColumn == column) RebuildView(m_viewFlags);
988 }
989
990 return NS_OK;
991 }
992