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 #pragma once
0010 
0011 #include "core/item.h"
0012 
0013 #include "MessageCore/StringUtil"
0014 
0015 // See the MessageList::ItemPrivate::insertChildItem() function below for an explanation of this macro.
0016 #if __GNUC__ >= 3 // krazy:exclude=cpp
0017 #define GCC_DONT_INLINE_THIS __attribute__((noinline))
0018 #else
0019 #define GCC_DONT_INLINE_THIS
0020 #endif
0021 
0022 namespace MessageList
0023 {
0024 namespace Core
0025 {
0026 class ItemPrivate
0027 {
0028 public:
0029     explicit ItemPrivate(Item *owner)
0030         : q(owner)
0031         , mChildItems(nullptr)
0032         , mParent(nullptr)
0033         , mThisItemIndexGuess(0)
0034         , mInitialExpandStatus(Item::NoExpandNeeded)
0035         , mIsViewable(false)
0036         , mUseReceiver(false)
0037     {
0038     }
0039 
0040     virtual ~ItemPrivate() = default;
0041 
0042     /**
0043      * Implements "in the middle" insertions of child items.
0044      * The template argument class must export a static inline bool firstGreaterOrEqual( Item *, Item * )
0045      * function which must return true when the first parameter item is considered to be greater
0046      * or equal to the second parameter item and false otherwise.
0047      *
0048      * The insertion function *IS* our very bottleneck on flat views
0049      * (when there are items with a lot of children). This is somewhat pathological...
0050      * beside the binary search based insertion sort we actually can only do "statement level" optimization.
0051      * I've found no better algorithms so far. If someone has a clever idea, please write to pragma
0052      * at kvirc dot net :)
0053      *
0054      * GCC_DONT_INLINE_THIS is a macro defined above to __attribute__((noinline))
0055      * if the current compiler is gcc. Without this attribute gcc attempts to inline THIS
0056      * function inside the callers. The problem is that while inlining this function
0057      * it doesn't inline the INNER comparison functions (which we _WANT_ to be inlined)
0058      * because they would make the caller function too big.
0059      *
0060      * This is what gcc reports with -Winline:
0061      *
0062      * /home/pragma/kmail-soc/kmail/messagelistview/item.h:352: warning: inlining failed in call to
0063      *   'static bool MessageList::ItemSubjectComparator::firstGreaterOrEqual(MessageList::Item*, MessageList::Item*)':
0064      *    --param large-function-growth limit reached while inlining the caller
0065      * /home/pragma/kmail-soc/kmail/messagelistview/model.cpp:239: warning: called from here
0066      *
0067      * The comparison functions then appear in the symbol table:
0068      *
0069      * etherea kmail # nm /usr/kde/4.0/lib/libkmailprivate.so | grep Comparator
0070      * 00000000005d2c10 W _ZN5KMail15MessageList18ItemDateComparator19firstGreaterOrEqualEPNS0_4ItemES3_
0071      * 00000000005d2cb0 W _ZN5KMail15MessageList20ItemSenderComparator19firstGreaterOrEqualEPNS0_4ItemES3_
0072      * 00000000005d2c50 W _ZN5KMail15MessageList21ItemSubjectComparator19firstGreaterOrEqualEPNS0_4ItemES3_
0073      * ...
0074      *
0075      * With this attribute, instead, gcc doesn't complain at all and the inner comparisons
0076      * *seem* to be inlined correctly (there is no sign of them in the symbol table).
0077      */
0078     template<class ItemComparator, bool bAscending>
0079     int GCC_DONT_INLINE_THIS insertChildItem(Model *model, Item *child)
0080     {
0081         if (!mChildItems) {
0082             return q->appendChildItem(model, child);
0083         }
0084 
0085         int cnt = mChildItems->count();
0086         if (cnt < 1) {
0087             return q->appendChildItem(model, child);
0088         }
0089 
0090         int idx;
0091         Item *pivot;
0092 
0093         if (bAscending) {
0094             pivot = mChildItems->at(cnt - 1);
0095 
0096             if (ItemComparator::firstGreaterOrEqual(child, pivot)) { // gcc: <-- inline this instead, thnx
0097                 return q->appendChildItem(model, child); // this is very likely in date based comparisons (FIXME: not in other ones)
0098             }
0099 
0100             // Binary search based insertion
0101             int l = 0;
0102             int h = cnt - 1;
0103 
0104             for (;;) {
0105                 idx = (l + h) / 2;
0106                 pivot = mChildItems->at(idx);
0107                 if (ItemComparator::firstGreaterOrEqual(pivot, child)) { // gcc: <-- inline this instead, thnx
0108                     if (l < h) {
0109                         h = idx - 1;
0110                     } else {
0111                         break;
0112                     }
0113                 } else {
0114                     if (l < h) {
0115                         l = idx + 1;
0116                     } else {
0117                         idx++;
0118                         break;
0119                     }
0120                 }
0121             }
0122         } else {
0123             pivot = mChildItems->at(0);
0124             if (ItemComparator::firstGreaterOrEqual(child, pivot)) { // gcc: <-- inline this instead, thnx
0125                 idx = 0; // this is very likely in date based comparisons (FIXME: not in other ones)
0126             } else {
0127                 // Binary search based insertion
0128                 int l = 0;
0129                 int h = cnt - 1;
0130 
0131                 for (;;) {
0132                     idx = (l + h) / 2;
0133                     pivot = mChildItems->at(idx);
0134                     if (ItemComparator::firstGreaterOrEqual(child, pivot)) { // gcc: <-- inline this instead, thnx
0135                         if (l < h) {
0136                             h = idx - 1;
0137                         } else {
0138                             break;
0139                         }
0140                     } else {
0141                         if (l < h) {
0142                             l = idx + 1;
0143                         } else {
0144                             idx++;
0145                             break;
0146                         }
0147                     }
0148                 }
0149             }
0150         }
0151 
0152         Q_ASSERT(idx >= 0);
0153         Q_ASSERT(idx <= mChildItems->count());
0154 
0155         if (mIsViewable && model) {
0156             model->beginInsertRows(model->index(q, 0), idx, idx); // BLEAH :D
0157         }
0158 
0159         mChildItems->insert(idx, child);
0160         child->setIndexGuess(idx);
0161         if (mIsViewable) {
0162             if (model) {
0163                 model->endInsertRows(); // BLEAH :D
0164             }
0165             child->setViewable(model, true);
0166         }
0167 
0168         return idx;
0169     }
0170 
0171     /**
0172      * Checks if the specified child item is actually in the wrong
0173      * position in the child list and returns true in that case.
0174      * Returns false if the item is already in the right position
0175      * and no re-sorting is needed.
0176      */
0177     template<class ItemComparator, bool bAscending>
0178     bool childItemNeedsReSorting(Item *child)
0179     {
0180         if (!mChildItems) {
0181             return false; // not my child! (ugh... should assert ?)
0182         }
0183 
0184         const int idx = q->indexOfChildItem(child);
0185 
0186         if (idx > 0) {
0187             Item *prev = mChildItems->at(idx - 1);
0188             if (bAscending) {
0189                 // child must be greater or equal to the previous item
0190                 if (!ItemComparator::firstGreaterOrEqual(child, prev)) {
0191                     return true; // wrong order: needs re-sorting
0192                 }
0193             } else {
0194                 // previous must be greater or equal to the child item
0195                 if (!ItemComparator::firstGreaterOrEqual(prev, child)) {
0196                     return true; // wrong order: needs re-sorting
0197                 }
0198             }
0199         }
0200 
0201         if (idx < (mChildItems->count() - 1)) {
0202             Item *next = mChildItems->at(idx + 1);
0203             if (bAscending) {
0204                 // next must be greater or equal to child
0205                 if (!ItemComparator::firstGreaterOrEqual(next, child)) {
0206                     return true; // wrong order: needs re-sorting
0207                 }
0208             } else {
0209                 // child must be greater or equal to next
0210                 if (!ItemComparator::firstGreaterOrEqual(child, next)) {
0211                     return true; // wrong order: needs re-sorting
0212                 }
0213             }
0214         }
0215 
0216         return false;
0217     }
0218 
0219     /**
0220      * Internal handler for managing the children list.
0221      */
0222     void childItemDead(Item *child);
0223 
0224     Item *const q;
0225 
0226     QList<Item *> *mChildItems; ///< List of children, may be 0
0227     Item *mParent = nullptr; ///< The parent view item
0228     time_t mMaxDate; ///< The maximum date in the subtree
0229     time_t mDate; ///< The date of the message (or group date)
0230     size_t mSize; ///< The size of the message in bytes
0231     QString mSender; ///< The sender of the message (or group sender)
0232     QString mReceiver; ///< The receiver of the message (or group receiver)
0233     QString mSubject; ///< The subject of the message (or group subject)
0234     QString mFolder; ///< The folder of the message
0235     qint64 mItemId; ///< The Akonadi item id
0236     qint64 mParentCollectionId; ///< The Akonadi ID of collection that this particular item comes from (can be virtual collection)
0237     Akonadi::MessageStatus mStatus; ///< The status of the message (may be extended to groups in the future)
0238     int mThisItemIndexGuess; ///< The guess for the index in the parent's child list
0239     Item::Type mType : 4; ///< The type of this item
0240     Item::InitialExpandStatus mInitialExpandStatus : 4; ///< The expand status we have to honor when we attach to the viewable root
0241     bool mIsViewable : 1; ///< Is this item attached to the viewable root ?
0242     bool mUseReceiver : 1; ///< senderOrReceiver() returns receiver rather than sender
0243 };
0244 
0245 /**
0246  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0247  * MessageList::Item::insertChildItem().
0248  */
0249 class ItemSizeComparator
0250 {
0251 public:
0252     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0253     {
0254         if (first->size() < second->size()) {
0255             return false;
0256         }
0257         // When the sizes are equal compare by date too
0258         if (first->size() == second->size()) {
0259             return first->date() >= second->date();
0260         }
0261         return true;
0262     }
0263 };
0264 
0265 /**
0266  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0267  * MessageList::Item::insertChildItem().
0268  */
0269 class ItemDateComparator
0270 {
0271 public:
0272     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0273     {
0274         // When the dates are equal compare by subject too
0275         // This is useful, for example, in kernel mailing list where people
0276         // send out multiple messages with patch parts at exactly the same time.
0277         if (first->date() == second->date()) {
0278             return first->subject() >= second->subject();
0279         }
0280         if (first->date() == static_cast<uint>(-1)) { // invalid is always smaller
0281             return false;
0282         }
0283         if (second->date() == static_cast<uint>(-1)) {
0284             return true;
0285         }
0286         if (first->date() < second->date()) {
0287             return false;
0288         }
0289         return true;
0290     }
0291 };
0292 
0293 /**
0294  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0295  * MessageList::Item::insertChildItem().
0296  */
0297 class ItemMaxDateComparator
0298 {
0299 public:
0300     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0301     {
0302         if (first->maxDate() < second->maxDate()) {
0303             return false;
0304         }
0305         if (first->maxDate() == second->maxDate()) {
0306             return first->subject() >= second->subject();
0307         }
0308         return true;
0309     }
0310 };
0311 
0312 /**
0313  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0314  * MessageList::Item::insertChildItem().
0315  */
0316 class ItemSubjectComparator
0317 {
0318 public:
0319     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0320     {
0321         const int ret = MessageCore::StringUtil::stripOffPrefixes(first->subject())
0322                             .compare(MessageCore::StringUtil::stripOffPrefixes(second->subject()), Qt::CaseInsensitive);
0323         if (ret < 0) {
0324             return false;
0325         }
0326         // compare by date when subjects are equal
0327         if (ret == 0) {
0328             return first->date() >= second->date();
0329         }
0330         return true;
0331     }
0332 };
0333 
0334 /**
0335  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0336  * MessageList::Item::insertChildItem().
0337  */
0338 class ItemSenderComparator
0339 {
0340 public:
0341     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0342     {
0343         const int ret = first->displaySender().compare(second->displaySender(), Qt::CaseInsensitive);
0344         if (ret < 0) {
0345             return false;
0346         }
0347         // compare by date when senders are equal
0348         if (ret == 0) {
0349             return first->date() >= second->date();
0350         }
0351         return true;
0352     }
0353 };
0354 
0355 /**
0356  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0357  * MessageList::Item::insertChildItem().
0358  */
0359 class ItemReceiverComparator
0360 {
0361 public:
0362     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0363     {
0364         const int ret = first->displayReceiver().compare(second->displayReceiver(), Qt::CaseInsensitive);
0365         if (ret < 0) {
0366             return false;
0367         }
0368         // compare by date when receivers are equal
0369         if (ret == 0) {
0370             return first->date() >= second->date();
0371         }
0372         return true;
0373     }
0374 };
0375 
0376 /**
0377  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0378  * MessageList::Item::insertChildItem().
0379  */
0380 class ItemSenderOrReceiverComparator
0381 {
0382 public:
0383     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0384     {
0385         const int ret = first->displaySenderOrReceiver().compare(second->displaySenderOrReceiver(), Qt::CaseInsensitive);
0386         if (ret < 0) {
0387             return false;
0388         }
0389         // compare by date when sender/receiver are equal
0390         if (ret == 0) {
0391             return first->date() >= second->date();
0392         }
0393         return true;
0394     }
0395 };
0396 
0397 /**
0398  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0399  * MessageList::Item::insertChildItem().
0400  */
0401 class ItemActionItemStatusComparator
0402 {
0403 public:
0404     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0405     {
0406         if (first->status().isToAct()) {
0407             if (second->status().isToAct()) {
0408                 return first->date() >= second->date();
0409             }
0410             return true;
0411         }
0412         if (second->status().isToAct()) {
0413             return false;
0414         }
0415         return first->date() >= second->date();
0416     }
0417 };
0418 
0419 /**
0420  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0421  * MessageList::Item::insertChildItem().
0422  */
0423 class ItemUnreadStatusComparator
0424 {
0425 public:
0426     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0427     {
0428         if (!first->status().isRead()) {
0429             // fist is unread
0430             if (!second->status().isRead()) {
0431                 return first->date() >= second->date(); // both are unread
0432             }
0433             // unread comes always first with respect to non-unread
0434             return true;
0435         }
0436         if (!second->status().isRead()) {
0437             return false;
0438         }
0439         // both are read
0440         return first->date() >= second->date();
0441     }
0442 };
0443 
0444 /**
0445  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0446  * MessageList::Item::insertChildItem().
0447  */
0448 class ItemImportantStatusComparator
0449 {
0450 public:
0451     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0452     {
0453         if (!first->status().isImportant()) {
0454             // fist is unread
0455             if (!second->status().isImportant()) {
0456                 return first->date() >= second->date(); // both are unread
0457             }
0458             // unread comes always first with respect to non-unread
0459             return true;
0460         }
0461         if (!second->status().isImportant()) {
0462             return false;
0463         }
0464         // both are read
0465         return first->date() >= second->date();
0466     }
0467 };
0468 
0469 /**
0470  * A helper class used with MessageList::Item::childItemNeedsReSorting() and
0471  * MessageList::Item::insertChildItem().
0472  */
0473 class ItemAttachmentStatusComparator
0474 {
0475 public:
0476     static inline bool firstGreaterOrEqual(Item *first, Item *second)
0477     {
0478         if (!first->status().hasAttachment()) {
0479             // fist is unread
0480             if (!second->status().hasAttachment()) {
0481                 return first->date() >= second->date(); // both are unread
0482             }
0483             // unread comes always first with respect to non-unread
0484             return true;
0485         }
0486         if (!second->status().hasAttachment()) {
0487             return false;
0488         }
0489         // both are read
0490         return first->date() >= second->date();
0491     }
0492 };
0493 } // namespace Core
0494 } // namespace MessageList