File indexing completed on 2024-12-15 04:54:37

0001 /******************************************************************************
0002  *
0003  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  *
0007  *******************************************************************************/
0008 
0009 #include "core/item.h"
0010 #include "core/item_p.h"
0011 #include "core/manager.h"
0012 #include "core/model.h"
0013 
0014 #include <KIO/Global> // for KIO::filesize_t and related functions
0015 #include <KMime/DateFormatter>
0016 
0017 #include <KLocalizedString>
0018 
0019 using namespace MessageList::Core;
0020 
0021 Item::Item(Type type)
0022     : d_ptr(new ItemPrivate(this))
0023 {
0024     d_ptr->mType = type;
0025 }
0026 
0027 Item::Item(Item::Type type, ItemPrivate *dd)
0028     : d_ptr(dd)
0029 {
0030     d_ptr->mType = type;
0031 }
0032 
0033 Item::~Item()
0034 {
0035     killAllChildItems();
0036 
0037     if (d_ptr->mParent) {
0038         d_ptr->mParent->d_ptr->childItemDead(this);
0039     }
0040 
0041     delete d_ptr;
0042 }
0043 
0044 void Item::childItemStats(ChildItemStats &stats) const
0045 {
0046     Q_ASSERT(d_ptr->mChildItems);
0047 
0048     stats.mTotalChildCount += d_ptr->mChildItems->count();
0049     for (const auto child : std::as_const(*d_ptr->mChildItems)) {
0050         if (!child->status().isRead()) {
0051             stats.mUnreadChildCount++;
0052         }
0053         if (child->d_ptr->mChildItems) {
0054             child->childItemStats(stats);
0055         }
0056     }
0057 }
0058 
0059 QList<Item *> *Item::childItems() const
0060 {
0061     return d_ptr->mChildItems;
0062 }
0063 
0064 Item *Item::childItem(int idx) const
0065 {
0066     if (idx < 0) {
0067         return nullptr;
0068     }
0069     if (!d_ptr->mChildItems) {
0070         return nullptr;
0071     }
0072     if (d_ptr->mChildItems->count() <= idx) {
0073         return nullptr;
0074     }
0075     return d_ptr->mChildItems->at(idx);
0076 }
0077 
0078 Item *Item::firstChildItem() const
0079 {
0080     return d_ptr->mChildItems ? (d_ptr->mChildItems->count() > 0 ? d_ptr->mChildItems->at(0) : nullptr) : nullptr;
0081 }
0082 
0083 Item *Item::itemBelowChild(Item *child)
0084 {
0085     Q_ASSERT(d_ptr->mChildItems);
0086 
0087     int idx = indexOfChildItem(child);
0088     Q_ASSERT(idx >= 0);
0089 
0090     idx++;
0091 
0092     if (idx < d_ptr->mChildItems->count()) {
0093         return d_ptr->mChildItems->at(idx);
0094     }
0095 
0096     if (!d_ptr->mParent) {
0097         return nullptr;
0098     }
0099 
0100     return d_ptr->mParent->itemBelowChild(this);
0101 }
0102 
0103 Item *Item::itemBelow()
0104 {
0105     if (d_ptr->mChildItems) {
0106         if (!d_ptr->mChildItems->isEmpty()) {
0107             return d_ptr->mChildItems->at(0);
0108         }
0109     }
0110 
0111     if (!d_ptr->mParent) {
0112         return nullptr;
0113     }
0114 
0115     return d_ptr->mParent->itemBelowChild(this);
0116 }
0117 
0118 Item *Item::deepestItem()
0119 {
0120     if (d_ptr->mChildItems) {
0121         if (!d_ptr->mChildItems->isEmpty()) {
0122             return d_ptr->mChildItems->at(d_ptr->mChildItems->count() - 1)->deepestItem();
0123         }
0124     }
0125 
0126     return this;
0127 }
0128 
0129 Item *Item::itemAboveChild(Item *child)
0130 {
0131     if (d_ptr->mChildItems) {
0132         int idx = indexOfChildItem(child);
0133         Q_ASSERT(idx >= 0);
0134         idx--;
0135 
0136         if (idx >= 0) {
0137             return d_ptr->mChildItems->at(idx);
0138         }
0139     }
0140 
0141     return this;
0142 }
0143 
0144 Item *Item::itemAbove()
0145 {
0146     if (!d_ptr->mParent) {
0147         return nullptr;
0148     }
0149 
0150     Item *siblingAbove = d_ptr->mParent->itemAboveChild(this);
0151     if (siblingAbove && siblingAbove != this && siblingAbove != d_ptr->mParent && siblingAbove->childItemCount() > 0) {
0152         return siblingAbove->deepestItem();
0153     }
0154 
0155     return d_ptr->mParent->itemAboveChild(this);
0156 }
0157 
0158 int Item::childItemCount() const
0159 {
0160     return d_ptr->mChildItems ? d_ptr->mChildItems->count() : 0;
0161 }
0162 
0163 bool Item::hasChildren() const
0164 {
0165     return childItemCount() > 0;
0166 }
0167 
0168 int Item::indexOfChildItem(Item *child) const
0169 {
0170     if (!d_ptr->mChildItems) {
0171         return -1;
0172     }
0173     int idx = child->d_ptr->mThisItemIndexGuess;
0174     if (idx < d_ptr->mChildItems->count() && d_ptr->mChildItems->at(idx) == child) {
0175         return idx; // good guess
0176     }
0177 
0178     // We had a guess but it's out-of-date. Let's use the old guess as our
0179     // starting point and search in both directions from it. It's more likely we
0180     // will find the new position by going from the old guess rather than scanning
0181     // the list from the beginning. The worst case scenario is equal to not having
0182     // any guess at all.
0183     if (idx > 0 && idx < d_ptr->mChildItems->count()) {
0184         const auto begin = d_ptr->mChildItems->cbegin();
0185         const auto end = d_ptr->mChildItems->cend();
0186         auto fwdIt = begin + idx;
0187         auto bwdIt = fwdIt;
0188 
0189         idx = -1; // invalidate idx so it's -1 in case we fail to find the item
0190         while (fwdIt != end || bwdIt != end) {
0191             if (fwdIt != end) {
0192                 if (++fwdIt != end && (*fwdIt) == child) {
0193                     idx = std::distance(begin, fwdIt);
0194                     break;
0195                 }
0196             }
0197             if (bwdIt != end) { // sic!
0198                 Q_ASSERT(bwdIt != begin);
0199                 if ((*--bwdIt) == child) {
0200                     idx = std::distance(begin, bwdIt);
0201                     break;
0202                 }
0203                 if (bwdIt == begin) {
0204                     // invalidate the iterator if we just checked the first item
0205                     bwdIt = end;
0206                 }
0207             }
0208         }
0209     } else {
0210         idx = d_ptr->mChildItems->indexOf(child);
0211     }
0212 
0213     if (idx >= 0) {
0214         Q_ASSERT(d_ptr->mChildItems->at(idx) == child); // make sure the above algorithm works
0215         child->d_ptr->mThisItemIndexGuess = idx;
0216     }
0217     return idx;
0218 }
0219 
0220 void Item::setIndexGuess(int index)
0221 {
0222     d_ptr->mThisItemIndexGuess = index;
0223 }
0224 
0225 Item *Item::topmostNonRoot()
0226 {
0227     Q_ASSERT(d_ptr->mType != InvisibleRoot);
0228 
0229     if (!d_ptr->mParent) {
0230         return this;
0231     }
0232 
0233     if (d_ptr->mParent->type() == InvisibleRoot) {
0234         return this;
0235     }
0236 
0237     return d_ptr->mParent->topmostNonRoot();
0238 }
0239 
0240 static inline void append_string(QString &buffer, const QString &append)
0241 {
0242     if (!buffer.isEmpty()) {
0243         buffer += QLatin1StringView(", ");
0244     }
0245     buffer += append;
0246 }
0247 
0248 QString Item::statusDescription() const
0249 {
0250     QString ret;
0251     if (status().isRead()) {
0252         append_string(ret, i18nc("Status of an item", "Read"));
0253     } else {
0254         append_string(ret, i18nc("Status of an item", "Unread"));
0255     }
0256 
0257     if (status().hasAttachment()) {
0258         append_string(ret, i18nc("Status of an item", "Has Attachment"));
0259     }
0260 
0261     if (status().isToAct()) {
0262         append_string(ret, i18nc("Status of an item", "Action Item"));
0263     }
0264 
0265     if (status().isReplied()) {
0266         append_string(ret, i18nc("Status of an item", "Replied"));
0267     }
0268 
0269     if (status().isForwarded()) {
0270         append_string(ret, i18nc("Status of an item", "Forwarded"));
0271     }
0272 
0273     if (status().isSent()) {
0274         append_string(ret, i18nc("Status of an item", "Sent"));
0275     }
0276 
0277     if (status().isImportant()) {
0278         append_string(ret, i18nc("Status of an item", "Important"));
0279     }
0280 
0281     if (status().isSpam()) {
0282         append_string(ret, i18nc("Status of an item", "Spam"));
0283     }
0284 
0285     if (status().isHam()) {
0286         append_string(ret, i18nc("Status of an item", "Ham"));
0287     }
0288 
0289     if (status().isWatched()) {
0290         append_string(ret, i18nc("Status of an item", "Watched"));
0291     }
0292 
0293     if (status().isIgnored()) {
0294         append_string(ret, i18nc("Status of an item", "Ignored"));
0295     }
0296 
0297     return ret;
0298 }
0299 
0300 QString Item::formattedSize() const
0301 {
0302     return KIO::convertSize((KIO::filesize_t)size());
0303 }
0304 
0305 QString Item::formattedDate() const
0306 {
0307     if (static_cast<uint>(date()) == static_cast<uint>(-1)) {
0308         return Manager::instance()->cachedLocalizedUnknownText();
0309     } else {
0310         return Manager::instance()->dateFormatter()->dateString(QDateTime::fromSecsSinceEpoch(date()));
0311     }
0312 }
0313 
0314 QString Item::formattedMaxDate() const
0315 {
0316     if (static_cast<uint>(maxDate()) == static_cast<uint>(-1)) {
0317         return Manager::instance()->cachedLocalizedUnknownText();
0318     } else {
0319         return Manager::instance()->dateFormatter()->dateString(QDateTime::fromSecsSinceEpoch(maxDate()));
0320     }
0321 }
0322 
0323 bool Item::recomputeMaxDate()
0324 {
0325     time_t newMaxDate = d_ptr->mDate;
0326 
0327     if (d_ptr->mChildItems) {
0328         for (auto child : std::as_const(*d_ptr->mChildItems)) {
0329             if (child->d_ptr->mMaxDate > newMaxDate) {
0330                 newMaxDate = child->d_ptr->mMaxDate;
0331             }
0332         }
0333     }
0334 
0335     if (newMaxDate != d_ptr->mMaxDate) {
0336         setMaxDate(newMaxDate);
0337         return true;
0338     }
0339     return false;
0340 }
0341 
0342 Item::Type Item::type() const
0343 {
0344     return d_ptr->mType;
0345 }
0346 
0347 Item::InitialExpandStatus Item::initialExpandStatus() const
0348 {
0349     return d_ptr->mInitialExpandStatus;
0350 }
0351 
0352 void Item::setInitialExpandStatus(InitialExpandStatus initialExpandStatus)
0353 {
0354     d_ptr->mInitialExpandStatus = initialExpandStatus;
0355 }
0356 
0357 bool Item::isViewable() const
0358 {
0359     return d_ptr->mIsViewable;
0360 }
0361 
0362 bool Item::hasAncestor(const Item *it) const
0363 {
0364     return d_ptr->mParent ? (d_ptr->mParent == it ? true : d_ptr->mParent->hasAncestor(it)) : false;
0365 }
0366 
0367 void Item::setViewable(Model *model, bool bViewable)
0368 {
0369     if (d_ptr->mIsViewable == bViewable) {
0370         return;
0371     }
0372 
0373     if (!d_ptr->mChildItems) {
0374         d_ptr->mIsViewable = bViewable;
0375         return;
0376     }
0377 
0378     if (d_ptr->mChildItems->isEmpty()) {
0379         d_ptr->mIsViewable = bViewable;
0380         return;
0381     }
0382 
0383     if (bViewable) {
0384         if (model) {
0385             // fake having no children, for a second
0386             QList<Item *> *tmp = d_ptr->mChildItems;
0387             d_ptr->mChildItems = nullptr;
0388             // qDebug("BEGIN INSERT ROWS FOR PARENT %x: from %d to %d, (will) have %d children",this,0,tmp->count()-1,tmp->count());
0389             model->beginInsertRows(model->index(this, 0), 0, tmp->count() - 1);
0390             d_ptr->mChildItems = tmp;
0391             d_ptr->mIsViewable = true;
0392             model->endInsertRows();
0393         } else {
0394             d_ptr->mIsViewable = true;
0395         }
0396 
0397         for (const auto child : std::as_const(*d_ptr->mChildItems)) {
0398             child->setViewable(model, bViewable);
0399         }
0400     } else {
0401         for (const auto child : std::as_const(*d_ptr->mChildItems)) {
0402             child->setViewable(model, bViewable);
0403         }
0404 
0405         // It seems that we can avoid removing child items here since the parent has been removed: this is a hack tough
0406         // and should check if Qt4 still supports it in the next (hopefully largely fixed) release
0407 
0408         if (model) {
0409             // fake having no children, for a second
0410             model->beginRemoveRows(model->index(this, 0), 0, d_ptr->mChildItems->count() - 1);
0411             QList<Item *> *tmp = d_ptr->mChildItems;
0412             d_ptr->mChildItems = nullptr;
0413             d_ptr->mIsViewable = false;
0414             model->endRemoveRows();
0415             d_ptr->mChildItems = tmp;
0416         } else {
0417             d_ptr->mIsViewable = false;
0418         }
0419     }
0420 }
0421 
0422 void Item::killAllChildItems()
0423 {
0424     if (!d_ptr->mChildItems) {
0425         return;
0426     }
0427 
0428     while (!d_ptr->mChildItems->isEmpty()) {
0429         delete d_ptr->mChildItems->first(); // this will call childDead() which will remove the child from the list
0430     }
0431 
0432     delete d_ptr->mChildItems;
0433     d_ptr->mChildItems = nullptr;
0434 }
0435 
0436 Item *Item::parent() const
0437 {
0438     return d_ptr->mParent;
0439 }
0440 
0441 void Item::setParent(Item *pParent)
0442 {
0443     d_ptr->mParent = pParent;
0444 }
0445 
0446 const Akonadi::MessageStatus &Item::status() const
0447 {
0448     return d_ptr->mStatus;
0449 }
0450 
0451 void Item::setStatus(Akonadi::MessageStatus status)
0452 {
0453     d_ptr->mStatus = status;
0454 }
0455 
0456 size_t Item::size() const
0457 {
0458     return d_ptr->mSize;
0459 }
0460 
0461 void Item::setSize(size_t size)
0462 {
0463     d_ptr->mSize = size;
0464 }
0465 
0466 time_t Item::date() const
0467 {
0468     return d_ptr->mDate;
0469 }
0470 
0471 void Item::setDate(time_t date)
0472 {
0473     d_ptr->mDate = date;
0474 }
0475 
0476 time_t Item::maxDate() const
0477 {
0478     return d_ptr->mMaxDate;
0479 }
0480 
0481 void Item::setMaxDate(time_t date)
0482 {
0483     d_ptr->mMaxDate = date;
0484 }
0485 
0486 const QString &Item::sender() const
0487 {
0488     return d_ptr->mSender;
0489 }
0490 
0491 void Item::setSender(const QString &sender)
0492 {
0493     d_ptr->mSender = sender;
0494 }
0495 
0496 QString Item::displaySender() const
0497 {
0498     return sender();
0499 }
0500 
0501 const QString &Item::receiver() const
0502 {
0503     return d_ptr->mReceiver;
0504 }
0505 
0506 void Item::setReceiver(const QString &receiver)
0507 {
0508     d_ptr->mReceiver = receiver;
0509 }
0510 
0511 QString Item::displayReceiver() const
0512 {
0513     return receiver();
0514 }
0515 
0516 const QString &Item::senderOrReceiver() const
0517 {
0518     return d_ptr->mUseReceiver ? d_ptr->mReceiver : d_ptr->mSender;
0519 }
0520 
0521 QString Item::displaySenderOrReceiver() const
0522 {
0523     return senderOrReceiver();
0524 }
0525 
0526 bool Item::useReceiver() const
0527 {
0528     return d_ptr->mUseReceiver;
0529 }
0530 
0531 const QString &Item::subject() const
0532 {
0533     return d_ptr->mSubject;
0534 }
0535 
0536 void Item::setSubject(const QString &subject)
0537 {
0538     d_ptr->mSubject = subject;
0539 }
0540 
0541 const QString &Item::folder() const
0542 {
0543     return d_ptr->mFolder;
0544 }
0545 
0546 void Item::setFolder(const QString &folder)
0547 {
0548     d_ptr->mFolder = folder;
0549 }
0550 
0551 void MessageList::Core::Item::initialSetup(time_t date, size_t size, const QString &sender, const QString &receiver, bool useReceiver)
0552 {
0553     d_ptr->mDate = date;
0554     d_ptr->mMaxDate = date;
0555     d_ptr->mSize = size;
0556     d_ptr->mSender = sender;
0557     d_ptr->mReceiver = receiver;
0558     d_ptr->mUseReceiver = useReceiver;
0559 }
0560 
0561 void MessageList::Core::Item::setItemId(qint64 id)
0562 {
0563     d_ptr->mItemId = id;
0564 }
0565 
0566 qint64 MessageList::Core::Item::itemId() const
0567 {
0568     return d_ptr->mItemId;
0569 }
0570 
0571 void Item::setParentCollectionId(qint64 id)
0572 {
0573     d_ptr->mParentCollectionId = id;
0574 }
0575 
0576 qint64 Item::parentCollectionId() const
0577 {
0578     return d_ptr->mParentCollectionId;
0579 }
0580 
0581 void MessageList::Core::Item::setSubjectAndStatus(const QString &subject, Akonadi::MessageStatus status)
0582 {
0583     d_ptr->mSubject = subject;
0584     d_ptr->mStatus = status;
0585 }
0586 
0587 // FIXME: Try to "cache item insertions" and call beginInsertRows() and endInsertRows() in a chunked fashion...
0588 
0589 void Item::rawAppendChildItem(Item *child)
0590 {
0591     if (!d_ptr->mChildItems) {
0592         d_ptr->mChildItems = new QList<Item *>();
0593     }
0594     d_ptr->mChildItems->append(child);
0595 }
0596 
0597 int Item::appendChildItem(Model *model, Item *child)
0598 {
0599     if (!d_ptr->mChildItems) {
0600         d_ptr->mChildItems = new QList<Item *>();
0601     }
0602     const int idx = d_ptr->mChildItems->count();
0603     if (d_ptr->mIsViewable) {
0604         if (model) {
0605             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
0606         }
0607         d_ptr->mChildItems->append(child);
0608         child->setIndexGuess(idx);
0609         if (model) {
0610             model->endInsertRows(); // THIS IS EXTREMELY UGLY, BUT IT'S THE ONLY POSSIBLE WAY WITH QT4 AT THE TIME OF WRITING
0611         }
0612         child->setViewable(model, true);
0613     } else {
0614         d_ptr->mChildItems->append(child);
0615         child->setIndexGuess(idx);
0616     }
0617     return idx;
0618 }
0619 
0620 void Item::dump(const QString &prefix)
0621 {
0622     QString out = QStringLiteral("%1 %x VIEWABLE:%2").arg(prefix, d_ptr->mIsViewable ? QStringLiteral("yes") : QStringLiteral("no"));
0623     qDebug(out.toUtf8().data(), this);
0624 
0625     QString nPrefix(prefix);
0626     nPrefix += QLatin1StringView("  ");
0627 
0628     if (!d_ptr->mChildItems) {
0629         return;
0630     }
0631 
0632     for (const auto child : std::as_const(*d_ptr->mChildItems)) {
0633         child->dump(nPrefix);
0634     }
0635 }
0636 
0637 void Item::takeChildItem(Model *model, Item *child)
0638 {
0639     if (!d_ptr->mChildItems) {
0640         return; // Ugh... not our child ?
0641     }
0642 
0643     if (!d_ptr->mIsViewable) {
0644         // qDebug("TAKING NON VIEWABLE CHILD ITEM %x",child);
0645         // We can highly optimize this case
0646         d_ptr->mChildItems->removeOne(child);
0647 #if 0
0648         // This *could* be done, but we optimize and avoid it.
0649         if (d->mChildItems->isEmpty()) {
0650             delete d->mChildItems;
0651             d->mChildItems = 0;
0652         }
0653 #endif
0654         child->setParent(nullptr);
0655         return;
0656     }
0657 
0658     const int idx = indexOfChildItem(child);
0659     if (idx < 0) {
0660         return; // Aaargh... not our child ?
0661     }
0662 
0663     child->setViewable(model, false);
0664     if (model) {
0665         model->beginRemoveRows(model->index(this, 0), idx, idx);
0666     }
0667     child->setParent(nullptr);
0668     d_ptr->mChildItems->removeAt(idx);
0669     if (model) {
0670         model->endRemoveRows();
0671     }
0672 
0673 #if 0
0674     // This *could* be done, but we optimize and avoid it.
0675     if (d->mChildItems->isEmpty()) {
0676         delete d->mChildItems;
0677         d->mChildItems = 0;
0678     }
0679 #endif
0680 }
0681 
0682 void ItemPrivate::childItemDead(Item *child)
0683 {
0684     // mChildItems MUST be non zero here, if it's not then it's a bug in THIS FILE
0685     mChildItems->removeOne(child); // since we always have ONE (if we not, it's a bug)
0686 }