1 /******************************************************************************
2 *
3 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 *
7 *******************************************************************************/
8
9 #include "core/item.h"
10 #include "core/item_p.h"
11 #include "core/manager.h"
12 #include "core/model.h"
13
14 #include <KIO/Global> // for KIO::filesize_t and related functions
15 #include <KMime/DateFormatter>
16
17 #include <KLocalizedString>
18
19 using namespace MessageList::Core;
20
Item(Type type)21 Item::Item(Type type)
22 : d_ptr(new ItemPrivate(this))
23 {
24 d_ptr->mType = type;
25 }
26
Item(Item::Type type,ItemPrivate * dd)27 Item::Item(Item::Type type, ItemPrivate *dd)
28 : d_ptr(dd)
29 {
30 d_ptr->mType = type;
31 }
32
~Item()33 Item::~Item()
34 {
35 killAllChildItems();
36
37 if (d_ptr->mParent) {
38 d_ptr->mParent->d_ptr->childItemDead(this);
39 }
40
41 delete d_ptr;
42 }
43
childItemStats(ChildItemStats & stats) const44 void Item::childItemStats(ChildItemStats &stats) const
45 {
46 Q_ASSERT(d_ptr->mChildItems);
47
48 stats.mTotalChildCount += d_ptr->mChildItems->count();
49 for (const auto child : std::as_const(*d_ptr->mChildItems)) {
50 if (!child->status().isRead()) {
51 stats.mUnreadChildCount++;
52 }
53 if (child->d_ptr->mChildItems) {
54 child->childItemStats(stats);
55 }
56 }
57 }
58
childItems() const59 QList<Item *> *Item::childItems() const
60 {
61 return d_ptr->mChildItems;
62 }
63
childItem(int idx) const64 Item *Item::childItem(int idx) const
65 {
66 if (idx < 0) {
67 return nullptr;
68 }
69 if (!d_ptr->mChildItems) {
70 return nullptr;
71 }
72 if (d_ptr->mChildItems->count() <= idx) {
73 return nullptr;
74 }
75 return d_ptr->mChildItems->at(idx);
76 }
77
firstChildItem() const78 Item *Item::firstChildItem() const
79 {
80 return d_ptr->mChildItems ? (d_ptr->mChildItems->count() > 0 ? d_ptr->mChildItems->at(0) : nullptr) : nullptr;
81 }
82
itemBelowChild(Item * child)83 Item *Item::itemBelowChild(Item *child)
84 {
85 Q_ASSERT(d_ptr->mChildItems);
86
87 int idx = indexOfChildItem(child);
88 Q_ASSERT(idx >= 0);
89
90 idx++;
91
92 if (idx < d_ptr->mChildItems->count()) {
93 return d_ptr->mChildItems->at(idx);
94 }
95
96 if (!d_ptr->mParent) {
97 return nullptr;
98 }
99
100 return d_ptr->mParent->itemBelowChild(this);
101 }
102
itemBelow()103 Item *Item::itemBelow()
104 {
105 if (d_ptr->mChildItems) {
106 if (!d_ptr->mChildItems->isEmpty()) {
107 return d_ptr->mChildItems->at(0);
108 }
109 }
110
111 if (!d_ptr->mParent) {
112 return nullptr;
113 }
114
115 return d_ptr->mParent->itemBelowChild(this);
116 }
117
deepestItem()118 Item *Item::deepestItem()
119 {
120 if (d_ptr->mChildItems) {
121 if (!d_ptr->mChildItems->isEmpty()) {
122 return d_ptr->mChildItems->at(d_ptr->mChildItems->count() - 1)->deepestItem();
123 }
124 }
125
126 return this;
127 }
128
itemAboveChild(Item * child)129 Item *Item::itemAboveChild(Item *child)
130 {
131 if (d_ptr->mChildItems) {
132 int idx = indexOfChildItem(child);
133 Q_ASSERT(idx >= 0);
134 idx--;
135
136 if (idx >= 0) {
137 return d_ptr->mChildItems->at(idx);
138 }
139 }
140
141 return this;
142 }
143
itemAbove()144 Item *Item::itemAbove()
145 {
146 if (!d_ptr->mParent) {
147 return nullptr;
148 }
149
150 Item *siblingAbove = d_ptr->mParent->itemAboveChild(this);
151 if (siblingAbove && siblingAbove != this && siblingAbove != d_ptr->mParent && siblingAbove->childItemCount() > 0) {
152 return siblingAbove->deepestItem();
153 }
154
155 return d_ptr->mParent->itemAboveChild(this);
156 }
157
childItemCount() const158 int Item::childItemCount() const
159 {
160 return d_ptr->mChildItems ? d_ptr->mChildItems->count() : 0;
161 }
162
hasChildren() const163 bool Item::hasChildren() const
164 {
165 return childItemCount() > 0;
166 }
167
indexOfChildItem(Item * child) const168 int Item::indexOfChildItem(Item *child) const
169 {
170 if (!d_ptr->mChildItems) {
171 return -1;
172 }
173 int idx = child->d_ptr->mThisItemIndexGuess;
174 if (idx < d_ptr->mChildItems->count() && d_ptr->mChildItems->at(idx) == child) {
175 return idx; // good guess
176 }
177
178 // We had a guess but it's out-of-date. Let's use the old guess as our
179 // starting point and search in both directions from it. It's more likely we
180 // will find the new position by going from the old guess rather than scanning
181 // the list from the beginning. The worst case scenario is equal to not having
182 // any guess at all.
183 if (idx > 0 && idx < d_ptr->mChildItems->count()) {
184 const auto begin = d_ptr->mChildItems->cbegin();
185 const auto end = d_ptr->mChildItems->cend();
186 auto fwdIt = begin + idx;
187 auto bwdIt = fwdIt;
188
189 idx = -1; // invalidate idx so it's -1 in case we fail to find the item
190 while (fwdIt != end || bwdIt != end) {
191 if (fwdIt != end) {
192 if (++fwdIt != end && (*fwdIt) == child) {
193 idx = std::distance(begin, fwdIt);
194 break;
195 }
196 }
197 if (bwdIt != end) { // sic!
198 Q_ASSERT(bwdIt != begin);
199 if ((*--bwdIt) == child) {
200 idx = std::distance(begin, bwdIt);
201 break;
202 }
203 if (bwdIt == begin) {
204 // invalidate the iterator if we just checked the first item
205 bwdIt = end;
206 }
207 }
208 }
209 } else {
210 idx = d_ptr->mChildItems->indexOf(child);
211 }
212
213 if (idx >= 0) {
214 Q_ASSERT(d_ptr->mChildItems->at(idx) == child); // make sure the above algorithm works
215 child->d_ptr->mThisItemIndexGuess = idx;
216 }
217 return idx;
218 }
219
setIndexGuess(int index)220 void Item::setIndexGuess(int index)
221 {
222 d_ptr->mThisItemIndexGuess = index;
223 }
224
topmostNonRoot()225 Item *Item::topmostNonRoot()
226 {
227 Q_ASSERT(d_ptr->mType != InvisibleRoot);
228
229 if (!d_ptr->mParent) {
230 return this;
231 }
232
233 if (d_ptr->mParent->type() == InvisibleRoot) {
234 return this;
235 }
236
237 return d_ptr->mParent->topmostNonRoot();
238 }
239
append_string(QString & buffer,const QString & append)240 static inline void append_string(QString &buffer, const QString &append)
241 {
242 if (!buffer.isEmpty()) {
243 buffer += QLatin1String(", ");
244 }
245 buffer += append;
246 }
247
statusDescription() const248 QString Item::statusDescription() const
249 {
250 QString ret;
251 if (status().isRead()) {
252 append_string(ret, i18nc("Status of an item", "Read"));
253 } else {
254 append_string(ret, i18nc("Status of an item", "Unread"));
255 }
256
257 if (status().hasAttachment()) {
258 append_string(ret, i18nc("Status of an item", "Has Attachment"));
259 }
260
261 if (status().isToAct()) {
262 append_string(ret, i18nc("Status of an item", "Action Item"));
263 }
264
265 if (status().isReplied()) {
266 append_string(ret, i18nc("Status of an item", "Replied"));
267 }
268
269 if (status().isForwarded()) {
270 append_string(ret, i18nc("Status of an item", "Forwarded"));
271 }
272
273 if (status().isSent()) {
274 append_string(ret, i18nc("Status of an item", "Sent"));
275 }
276
277 if (status().isImportant()) {
278 append_string(ret, i18nc("Status of an item", "Important"));
279 }
280
281 if (status().isSpam()) {
282 append_string(ret, i18nc("Status of an item", "Spam"));
283 }
284
285 if (status().isHam()) {
286 append_string(ret, i18nc("Status of an item", "Ham"));
287 }
288
289 if (status().isWatched()) {
290 append_string(ret, i18nc("Status of an item", "Watched"));
291 }
292
293 if (status().isIgnored()) {
294 append_string(ret, i18nc("Status of an item", "Ignored"));
295 }
296
297 return ret;
298 }
299
formattedSize() const300 QString Item::formattedSize() const
301 {
302 return KIO::convertSize((KIO::filesize_t)size());
303 }
304
formattedDate() const305 QString Item::formattedDate() const
306 {
307 if (static_cast<uint>(date()) == static_cast<uint>(-1)) {
308 return Manager::instance()->cachedLocalizedUnknownText();
309 } else {
310 return Manager::instance()->dateFormatter()->dateString(date());
311 }
312 }
313
formattedMaxDate() const314 QString Item::formattedMaxDate() const
315 {
316 if (static_cast<uint>(maxDate()) == static_cast<uint>(-1)) {
317 return Manager::instance()->cachedLocalizedUnknownText();
318 } else {
319 return Manager::instance()->dateFormatter()->dateString(maxDate());
320 }
321 }
322
recomputeMaxDate()323 bool Item::recomputeMaxDate()
324 {
325 time_t newMaxDate = d_ptr->mDate;
326
327 if (d_ptr->mChildItems) {
328 for (auto child : std::as_const(*d_ptr->mChildItems)) {
329 if (child->d_ptr->mMaxDate > newMaxDate) {
330 newMaxDate = child->d_ptr->mMaxDate;
331 }
332 }
333 }
334
335 if (newMaxDate != d_ptr->mMaxDate) {
336 setMaxDate(newMaxDate);
337 return true;
338 }
339 return false;
340 }
341
type() const342 Item::Type Item::type() const
343 {
344 return d_ptr->mType;
345 }
346
initialExpandStatus() const347 Item::InitialExpandStatus Item::initialExpandStatus() const
348 {
349 return d_ptr->mInitialExpandStatus;
350 }
351
setInitialExpandStatus(InitialExpandStatus initialExpandStatus)352 void Item::setInitialExpandStatus(InitialExpandStatus initialExpandStatus)
353 {
354 d_ptr->mInitialExpandStatus = initialExpandStatus;
355 }
356
isViewable() const357 bool Item::isViewable() const
358 {
359 return d_ptr->mIsViewable;
360 }
361
hasAncestor(const Item * it) const362 bool Item::hasAncestor(const Item *it) const
363 {
364 return d_ptr->mParent ? (d_ptr->mParent == it ? true : d_ptr->mParent->hasAncestor(it)) : false;
365 }
366
setViewable(Model * model,bool bViewable)367 void Item::setViewable(Model *model, bool bViewable)
368 {
369 if (d_ptr->mIsViewable == bViewable) {
370 return;
371 }
372
373 if (!d_ptr->mChildItems) {
374 d_ptr->mIsViewable = bViewable;
375 return;
376 }
377
378 if (d_ptr->mChildItems->isEmpty()) {
379 d_ptr->mIsViewable = bViewable;
380 return;
381 }
382
383 if (bViewable) {
384 if (model) {
385 // fake having no children, for a second
386 QList<Item *> *tmp = d_ptr->mChildItems;
387 d_ptr->mChildItems = nullptr;
388 // qDebug("BEGIN INSERT ROWS FOR PARENT %x: from %d to %d, (will) have %d children",this,0,tmp->count()-1,tmp->count());
389 model->beginInsertRows(model->index(this, 0), 0, tmp->count() - 1);
390 d_ptr->mChildItems = tmp;
391 d_ptr->mIsViewable = true;
392 model->endInsertRows();
393 } else {
394 d_ptr->mIsViewable = true;
395 }
396
397 for (const auto child : std::as_const(*d_ptr->mChildItems)) {
398 child->setViewable(model, bViewable);
399 }
400 } else {
401 for (const auto child : std::as_const(*d_ptr->mChildItems)) {
402 child->setViewable(model, bViewable);
403 }
404
405 // It seems that we can avoid removing child items here since the parent has been removed: this is a hack tough
406 // and should check if Qt4 still supports it in the next (hopefully largely fixed) release
407
408 if (model) {
409 // fake having no children, for a second
410 model->beginRemoveRows(model->index(this, 0), 0, d_ptr->mChildItems->count() - 1);
411 QList<Item *> *tmp = d_ptr->mChildItems;
412 d_ptr->mChildItems = nullptr;
413 d_ptr->mIsViewable = false;
414 model->endRemoveRows();
415 d_ptr->mChildItems = tmp;
416 } else {
417 d_ptr->mIsViewable = false;
418 }
419 }
420 }
421
killAllChildItems()422 void Item::killAllChildItems()
423 {
424 if (!d_ptr->mChildItems) {
425 return;
426 }
427
428 while (!d_ptr->mChildItems->isEmpty()) {
429 delete d_ptr->mChildItems->first(); // this will call childDead() which will remove the child from the list
430 }
431
432 delete d_ptr->mChildItems;
433 d_ptr->mChildItems = nullptr;
434 }
435
parent() const436 Item *Item::parent() const
437 {
438 return d_ptr->mParent;
439 }
440
setParent(Item * pParent)441 void Item::setParent(Item *pParent)
442 {
443 d_ptr->mParent = pParent;
444 }
445
status() const446 const Akonadi::MessageStatus &Item::status() const
447 {
448 return d_ptr->mStatus;
449 }
450
setStatus(Akonadi::MessageStatus status)451 void Item::setStatus(Akonadi::MessageStatus status)
452 {
453 d_ptr->mStatus = status;
454 }
455
size() const456 size_t Item::size() const
457 {
458 return d_ptr->mSize;
459 }
460
setSize(size_t size)461 void Item::setSize(size_t size)
462 {
463 d_ptr->mSize = size;
464 }
465
date() const466 time_t Item::date() const
467 {
468 return d_ptr->mDate;
469 }
470
setDate(time_t date)471 void Item::setDate(time_t date)
472 {
473 d_ptr->mDate = date;
474 }
475
maxDate() const476 time_t Item::maxDate() const
477 {
478 return d_ptr->mMaxDate;
479 }
480
setMaxDate(time_t date)481 void Item::setMaxDate(time_t date)
482 {
483 d_ptr->mMaxDate = date;
484 }
485
sender() const486 const QString &Item::sender() const
487 {
488 return d_ptr->mSender;
489 }
490
setSender(const QString & sender)491 void Item::setSender(const QString &sender)
492 {
493 d_ptr->mSender = sender;
494 }
495
displaySender() const496 QString Item::displaySender() const
497 {
498 return sender();
499 }
500
receiver() const501 const QString &Item::receiver() const
502 {
503 return d_ptr->mReceiver;
504 }
505
setReceiver(const QString & receiver)506 void Item::setReceiver(const QString &receiver)
507 {
508 d_ptr->mReceiver = receiver;
509 }
510
displayReceiver() const511 QString Item::displayReceiver() const
512 {
513 return receiver();
514 }
515
senderOrReceiver() const516 const QString &Item::senderOrReceiver() const
517 {
518 return d_ptr->mUseReceiver ? d_ptr->mReceiver : d_ptr->mSender;
519 }
520
displaySenderOrReceiver() const521 QString Item::displaySenderOrReceiver() const
522 {
523 return senderOrReceiver();
524 }
525
useReceiver() const526 bool Item::useReceiver() const
527 {
528 return d_ptr->mUseReceiver;
529 }
530
subject() const531 const QString &Item::subject() const
532 {
533 return d_ptr->mSubject;
534 }
535
setSubject(const QString & subject)536 void Item::setSubject(const QString &subject)
537 {
538 d_ptr->mSubject = subject;
539 }
540
folder() const541 const QString &Item::folder() const
542 {
543 return d_ptr->mFolder;
544 }
545
setFolder(const QString & folder)546 void Item::setFolder(const QString &folder)
547 {
548 d_ptr->mFolder = folder;
549 }
550
initialSetup(time_t date,size_t size,const QString & sender,const QString & receiver,bool useReceiver)551 void MessageList::Core::Item::initialSetup(time_t date, size_t size, const QString &sender, const QString &receiver, bool useReceiver)
552 {
553 d_ptr->mDate = date;
554 d_ptr->mMaxDate = date;
555 d_ptr->mSize = size;
556 d_ptr->mSender = sender;
557 d_ptr->mReceiver = receiver;
558 d_ptr->mUseReceiver = useReceiver;
559 }
560
setItemId(qint64 id)561 void MessageList::Core::Item::setItemId(qint64 id)
562 {
563 d_ptr->mItemId = id;
564 }
565
itemId() const566 qint64 MessageList::Core::Item::itemId() const
567 {
568 return d_ptr->mItemId;
569 }
570
setParentCollectionId(qint64 id)571 void Item::setParentCollectionId(qint64 id)
572 {
573 d_ptr->mParentCollectionId = id;
574 }
575
parentCollectionId() const576 qint64 Item::parentCollectionId() const
577 {
578 return d_ptr->mParentCollectionId;
579 }
580
setSubjectAndStatus(const QString & subject,Akonadi::MessageStatus status)581 void MessageList::Core::Item::setSubjectAndStatus(const QString &subject, Akonadi::MessageStatus status)
582 {
583 d_ptr->mSubject = subject;
584 d_ptr->mStatus = status;
585 }
586
587 // FIXME: Try to "cache item insertions" and call beginInsertRows() and endInsertRows() in a chunked fashion...
588
rawAppendChildItem(Item * child)589 void Item::rawAppendChildItem(Item *child)
590 {
591 if (!d_ptr->mChildItems) {
592 d_ptr->mChildItems = new QList<Item *>();
593 }
594 d_ptr->mChildItems->append(child);
595 }
596
appendChildItem(Model * model,Item * child)597 int Item::appendChildItem(Model *model, Item *child)
598 {
599 if (!d_ptr->mChildItems) {
600 d_ptr->mChildItems = new QList<Item *>();
601 }
602 const int idx = d_ptr->mChildItems->count();
603 if (d_ptr->mIsViewable) {
604 if (model) {
605 model->beginInsertRows(model->index(this, 0), idx, idx); // THIS IS EXTREMELY UGLY, BUT IT'S THE ONLY POSSIBLE WAY WITH QT4 AT THE TIME OF WRITING
606 }
607 d_ptr->mChildItems->append(child);
608 child->setIndexGuess(idx);
609 if (model) {
610 model->endInsertRows(); // THIS IS EXTREMELY UGLY, BUT IT'S THE ONLY POSSIBLE WAY WITH QT4 AT THE TIME OF WRITING
611 }
612 child->setViewable(model, true);
613 } else {
614 d_ptr->mChildItems->append(child);
615 child->setIndexGuess(idx);
616 }
617 return idx;
618 }
619
dump(const QString & prefix)620 void Item::dump(const QString &prefix)
621 {
622 QString out = QStringLiteral("%1 %x VIEWABLE:%2").arg(prefix, d_ptr->mIsViewable ? QStringLiteral("yes") : QStringLiteral("no"));
623 qDebug(out.toUtf8().data(), this);
624
625 QString nPrefix(prefix);
626 nPrefix += QLatin1String(" ");
627
628 if (!d_ptr->mChildItems) {
629 return;
630 }
631
632 for (const auto child : std::as_const(*d_ptr->mChildItems)) {
633 child->dump(nPrefix);
634 }
635 }
636
takeChildItem(Model * model,Item * child)637 void Item::takeChildItem(Model *model, Item *child)
638 {
639 if (!d_ptr->mChildItems) {
640 return; // Ugh... not our child ?
641 }
642
643 if (!d_ptr->mIsViewable) {
644 // qDebug("TAKING NON VIEWABLE CHILD ITEM %x",child);
645 // We can highly optimize this case
646 d_ptr->mChildItems->removeOne(child);
647 #if 0
648 // This *could* be done, but we optimize and avoid it.
649 if (d->mChildItems->isEmpty()) {
650 delete d->mChildItems;
651 d->mChildItems = 0;
652 }
653 #endif
654 child->setParent(nullptr);
655 return;
656 }
657
658 const int idx = indexOfChildItem(child);
659 if (idx < 0) {
660 return; // Aaargh... not our child ?
661 }
662
663 child->setViewable(model, false);
664 if (model) {
665 model->beginRemoveRows(model->index(this, 0), idx, idx);
666 }
667 child->setParent(nullptr);
668 d_ptr->mChildItems->removeAt(idx);
669 if (model) {
670 model->endRemoveRows();
671 }
672
673 #if 0
674 // This *could* be done, but we optimize and avoid it.
675 if (d->mChildItems->isEmpty()) {
676 delete d->mChildItems;
677 d->mChildItems = 0;
678 }
679 #endif
680 }
681
childItemDead(Item * child)682 void ItemPrivate::childItemDead(Item *child)
683 {
684 // mChildItems MUST be non zero here, if it's not then it's a bug in THIS FILE
685 mChildItems->removeOne(child); // since we always have ONE (if we not, it's a bug)
686 }
687