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 }