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

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/themedelegate.h"
0010 #include "core/groupheaderitem.h"
0011 #include "core/messageitem.h"
0012 #include "messagelistsettings.h"
0013 
0014 #include "MessageCore/MessageCoreSettings"
0015 #include "MessageCore/StringUtil"
0016 
0017 #include <KColorScheme>
0018 #include <QAbstractItemView>
0019 #include <QFont>
0020 #include <QFontDatabase>
0021 #include <QFontMetrics>
0022 #include <QLinearGradient>
0023 #include <QPainter>
0024 #include <QPixmap>
0025 #include <QStyle>
0026 
0027 using namespace MessageList::Core;
0028 
0029 static const int gGroupHeaderOuterVerticalMargin = 1;
0030 static const int gGroupHeaderOuterHorizontalMargin = 1;
0031 static const int gGroupHeaderInnerVerticalMargin = 1;
0032 static const int gGroupHeaderInnerHorizontalMargin = 1;
0033 static const int gMessageVerticalMargin = 2;
0034 static const int gMessageHorizontalMargin = 2;
0035 static const int gHorizontalItemSpacing = 2;
0036 
0037 ThemeDelegate::ThemeDelegate(QAbstractItemView *parent)
0038     : QStyledItemDelegate(parent)
0039     , mItemView(parent)
0040 {
0041 }
0042 
0043 ThemeDelegate::~ThemeDelegate() = default;
0044 
0045 void ThemeDelegate::setTheme(const Theme *theme)
0046 {
0047     mTheme = theme;
0048 
0049     if (!mTheme) {
0050         return; // hum
0051     }
0052 
0053     // Rebuild the group header background color cache
0054     switch (mTheme->groupHeaderBackgroundMode()) {
0055     case Theme::Transparent:
0056         mGroupHeaderBackgroundColor = QColor(); // invalid
0057         break;
0058     case Theme::CustomColor:
0059         mGroupHeaderBackgroundColor = mTheme->groupHeaderBackgroundColor();
0060         break;
0061     case Theme::AutoColor: {
0062         QPalette pal = mItemView->palette();
0063         QColor txt = pal.color(QPalette::Normal, QPalette::Text);
0064         QColor bck = pal.color(QPalette::Normal, QPalette::Base);
0065         mGroupHeaderBackgroundColor = QColor((txt.red() + (bck.red() * 3)) / 4, (txt.green() + (bck.green() * 3)) / 4, (txt.blue() + (bck.blue() * 3)) / 4);
0066         break;
0067     }
0068     }
0069 
0070     generalFontChanged();
0071 
0072     mItemView->reset();
0073 }
0074 
0075 enum FontType {
0076     Normal,
0077     Bold,
0078     Italic,
0079     BoldItalic,
0080 
0081     FontTypesCount,
0082 };
0083 
0084 static QFont sFontCache[FontTypesCount];
0085 static QFontMetrics sFontMetricsCache[FontTypesCount] = {QFontMetrics(QFont()), QFontMetrics(QFont()), QFontMetrics(QFont()), QFontMetrics(QFont())};
0086 static int sFontHeightCache = 0;
0087 
0088 static inline const QFontMetrics &cachedFontMetrics(const Theme::ContentItem *ci)
0089 {
0090     return (!ci->isBold() && !ci->isItalic()) ? sFontMetricsCache[Normal]
0091         : (ci->isBold() && !ci->isItalic())   ? sFontMetricsCache[Bold]
0092         : (!ci->isBold() && ci->isItalic())   ? sFontMetricsCache[Italic]
0093                                               : sFontMetricsCache[BoldItalic];
0094 }
0095 
0096 static inline const QFont &cachedFont(const Theme::ContentItem *ci)
0097 {
0098     return (!ci->isBold() && !ci->isItalic()) ? sFontCache[Normal]
0099         : (ci->isBold() && !ci->isItalic())   ? sFontCache[Bold]
0100         : (!ci->isBold() && ci->isItalic())   ? sFontCache[Italic]
0101                                               : sFontCache[BoldItalic];
0102 }
0103 
0104 static inline const QFont &cachedFont(const Theme::ContentItem *ci, const Item *i)
0105 {
0106     if (i->type() != Item::Message) {
0107         return cachedFont(ci);
0108     }
0109 
0110     const auto mi = static_cast<const MessageItem *>(i);
0111     const bool bold = ci->isBold() || mi->isBold();
0112     const bool italic = ci->isItalic() || mi->isItalic();
0113     return (!bold && !italic) ? sFontCache[Normal] : (bold && !italic) ? sFontCache[Bold] : (!bold && italic) ? sFontCache[Italic] : sFontCache[BoldItalic];
0114 }
0115 
0116 static inline void paint_right_aligned_elided_text(const QString &text,
0117                                                    Theme::ContentItem *ci,
0118                                                    QPainter *painter,
0119                                                    int &left,
0120                                                    int top,
0121                                                    int &right,
0122                                                    Qt::LayoutDirection layoutDir,
0123                                                    const QFont &font)
0124 {
0125     painter->setFont(font);
0126     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
0127     const int w = right - left;
0128     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideLeft : Qt::ElideRight, w);
0129     const QRect rct(left, top, w, sFontHeightCache);
0130     QRect outRct;
0131 
0132     if (ci->softenByBlending()) {
0133         qreal oldOpacity = painter->opacity();
0134         painter->setOpacity(0.6);
0135         painter->drawText(rct, Qt::AlignTop | Qt::AlignRight | Qt::TextSingleLine, elidedText, &outRct);
0136         painter->setOpacity(oldOpacity);
0137     } else {
0138         painter->drawText(rct, Qt::AlignTop | Qt::AlignRight | Qt::TextSingleLine, elidedText, &outRct);
0139     }
0140     if (layoutDir == Qt::LeftToRight) {
0141         right -= outRct.width() + gHorizontalItemSpacing;
0142     } else {
0143         left += outRct.width() + gHorizontalItemSpacing;
0144     }
0145 }
0146 
0147 static inline void compute_bounding_rect_for_right_aligned_elided_text(const QString &text,
0148                                                                        Theme::ContentItem *ci,
0149                                                                        int &left,
0150                                                                        int top,
0151                                                                        int &right,
0152                                                                        QRect &outRect,
0153                                                                        Qt::LayoutDirection layoutDir,
0154                                                                        const QFont &font)
0155 {
0156     Q_UNUSED(font)
0157     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
0158     const int w = right - left;
0159     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideLeft : Qt::ElideRight, w);
0160     const QRect rct(left, top, w, sFontHeightCache);
0161     const Qt::AlignmentFlag af = layoutDir == Qt::LeftToRight ? Qt::AlignRight : Qt::AlignLeft;
0162     outRect = fontMetrics.boundingRect(rct, Qt::AlignTop | af | Qt::TextSingleLine, elidedText);
0163     if (layoutDir == Qt::LeftToRight) {
0164         right -= outRect.width() + gHorizontalItemSpacing;
0165     } else {
0166         left += outRect.width() + gHorizontalItemSpacing;
0167     }
0168 }
0169 
0170 static inline void paint_left_aligned_elided_text(const QString &text,
0171                                                   Theme::ContentItem *ci,
0172                                                   QPainter *painter,
0173                                                   int &left,
0174                                                   int top,
0175                                                   int &right,
0176                                                   Qt::LayoutDirection layoutDir,
0177                                                   const QFont &font)
0178 {
0179     painter->setFont(font);
0180     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
0181     const int w = right - left;
0182     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideRight : Qt::ElideLeft, w);
0183     const QRect rct(left, top, w, sFontHeightCache);
0184     QRect outRct;
0185     if (ci->softenByBlending()) {
0186         qreal oldOpacity = painter->opacity();
0187         painter->setOpacity(0.6);
0188         painter->drawText(rct, Qt::AlignTop | Qt::AlignLeft | Qt::TextSingleLine, elidedText, &outRct);
0189         painter->setOpacity(oldOpacity);
0190     } else {
0191         painter->drawText(rct, Qt::AlignTop | Qt::AlignLeft | Qt::TextSingleLine, elidedText, &outRct);
0192     }
0193     if (layoutDir == Qt::LeftToRight) {
0194         left += outRct.width() + gHorizontalItemSpacing;
0195     } else {
0196         right -= outRct.width() + gHorizontalItemSpacing;
0197     }
0198 }
0199 
0200 static inline void compute_bounding_rect_for_left_aligned_elided_text(const QString &text,
0201                                                                       Theme::ContentItem *ci,
0202                                                                       int &left,
0203                                                                       int top,
0204                                                                       int &right,
0205                                                                       QRect &outRect,
0206                                                                       Qt::LayoutDirection layoutDir,
0207                                                                       const QFont &font)
0208 {
0209     Q_UNUSED(font)
0210     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
0211     const int w = right - left;
0212     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideRight : Qt::ElideLeft, w);
0213     const QRect rct(left, top, w, sFontHeightCache);
0214     const Qt::AlignmentFlag af = layoutDir == Qt::LeftToRight ? Qt::AlignLeft : Qt::AlignRight;
0215     outRect = fontMetrics.boundingRect(rct, Qt::AlignTop | af | Qt::TextSingleLine, elidedText);
0216     if (layoutDir == Qt::LeftToRight) {
0217         left += outRect.width() + gHorizontalItemSpacing;
0218     } else {
0219         right -= outRect.width() + gHorizontalItemSpacing;
0220     }
0221 }
0222 
0223 static inline const QPixmap *get_read_state_icon(const Theme *theme, Item *item)
0224 {
0225     if (item->status().isQueued()) {
0226         return theme->pixmap(Theme::IconQueued);
0227     } else if (item->status().isSent()) {
0228         return theme->pixmap(Theme::IconSent);
0229     } else if (item->status().isRead()) {
0230         return theme->pixmap(Theme::IconRead);
0231     } else if (!item->status().isRead()) {
0232         return theme->pixmap(Theme::IconUnread);
0233     } else if (item->status().isDeleted()) {
0234         return theme->pixmap(Theme::IconDeleted);
0235     }
0236 
0237     // Uhm... should never happen.. but fallback to "read"...
0238     return theme->pixmap(Theme::IconRead);
0239 }
0240 
0241 static inline const QPixmap *get_combined_read_replied_state_icon(const Theme *theme, MessageItem *messageItem)
0242 {
0243     if (messageItem->status().isReplied()) {
0244         if (messageItem->status().isForwarded()) {
0245             return theme->pixmap(Theme::IconRepliedAndForwarded);
0246         }
0247         return theme->pixmap(Theme::IconReplied);
0248     }
0249     if (messageItem->status().isForwarded()) {
0250         return theme->pixmap(Theme::IconForwarded);
0251     }
0252 
0253     return get_read_state_icon(theme, messageItem);
0254 }
0255 
0256 static inline const QPixmap *get_encryption_state_icon(const Theme *theme, MessageItem *messageItem, bool *treatAsEnabled)
0257 {
0258     switch (messageItem->encryptionState()) {
0259     case MessageItem::FullyEncrypted:
0260         *treatAsEnabled = true;
0261         return theme->pixmap(Theme::IconFullyEncrypted);
0262     case MessageItem::PartiallyEncrypted:
0263         *treatAsEnabled = true;
0264         return theme->pixmap(Theme::IconPartiallyEncrypted);
0265     case MessageItem::EncryptionStateUnknown:
0266         *treatAsEnabled = false;
0267         return theme->pixmap(Theme::IconUndefinedEncrypted);
0268     case MessageItem::NotEncrypted:
0269         *treatAsEnabled = false;
0270         return theme->pixmap(Theme::IconNotEncrypted);
0271     default:
0272         // should never happen
0273         Q_ASSERT(false);
0274         break;
0275     }
0276 
0277     *treatAsEnabled = false;
0278     return theme->pixmap(Theme::IconUndefinedEncrypted);
0279 }
0280 
0281 static inline const QPixmap *get_signature_state_icon(const Theme *theme, MessageItem *messageItem, bool *treatAsEnabled)
0282 {
0283     switch (messageItem->signatureState()) {
0284     case MessageItem::FullySigned:
0285         *treatAsEnabled = true;
0286         return theme->pixmap(Theme::IconFullySigned);
0287     case MessageItem::PartiallySigned:
0288         *treatAsEnabled = true;
0289         return theme->pixmap(Theme::IconPartiallySigned);
0290     case MessageItem::SignatureStateUnknown:
0291         *treatAsEnabled = false;
0292         return theme->pixmap(Theme::IconUndefinedSigned);
0293     case MessageItem::NotSigned:
0294         *treatAsEnabled = false;
0295         return theme->pixmap(Theme::IconNotSigned);
0296     default:
0297         // should never happen
0298         Q_ASSERT(false);
0299         break;
0300     }
0301 
0302     *treatAsEnabled = false;
0303     return theme->pixmap(Theme::IconUndefinedSigned);
0304 }
0305 
0306 static inline const QPixmap *get_replied_state_icon(const Theme *theme, MessageItem *messageItem)
0307 {
0308     if (messageItem->status().isReplied()) {
0309         if (messageItem->status().isForwarded()) {
0310             return theme->pixmap(Theme::IconRepliedAndForwarded);
0311         }
0312         return theme->pixmap(Theme::IconReplied);
0313     }
0314     if (messageItem->status().isForwarded()) {
0315         return theme->pixmap(Theme::IconForwarded);
0316     }
0317 
0318     return nullptr;
0319 }
0320 
0321 static inline const QPixmap *get_spam_ham_state_icon(const Theme *theme, MessageItem *messageItem)
0322 {
0323     if (messageItem->status().isSpam()) {
0324         return theme->pixmap(Theme::IconSpam);
0325     } else if (messageItem->status().isHam()) {
0326         return theme->pixmap(Theme::IconHam);
0327     }
0328     return nullptr;
0329 }
0330 
0331 static inline const QPixmap *get_watched_ignored_state_icon(const Theme *theme, MessageItem *messageItem)
0332 {
0333     if (messageItem->status().isIgnored()) {
0334         return theme->pixmap(Theme::IconIgnored);
0335     } else if (messageItem->status().isWatched()) {
0336         return theme->pixmap(Theme::IconWatched);
0337     }
0338     return nullptr;
0339 }
0340 
0341 static inline void paint_vertical_line(QPainter *painter, int &left, int top, int &right, int bottom, bool alignOnRight)
0342 {
0343     if (alignOnRight) {
0344         right -= 1;
0345         if (right < 0) {
0346             return;
0347         }
0348         painter->drawLine(right, top, right, bottom);
0349         right -= 2;
0350         right -= gHorizontalItemSpacing;
0351     } else {
0352         left += 1;
0353         if (left > right) {
0354             return;
0355         }
0356         painter->drawLine(left, top, left, bottom);
0357         left += 2 + gHorizontalItemSpacing;
0358     }
0359 }
0360 
0361 static inline void compute_bounding_rect_for_vertical_line(int &left, int top, int &right, int bottom, QRect &outRect, bool alignOnRight)
0362 {
0363     if (alignOnRight) {
0364         right -= 3;
0365         outRect = QRect(right, top, 3, bottom - top);
0366         right -= gHorizontalItemSpacing;
0367     } else {
0368         outRect = QRect(left, top, 3, bottom - top);
0369         left += 3 + gHorizontalItemSpacing;
0370     }
0371 }
0372 
0373 static inline void paint_horizontal_spacer(int &left, int, int &right, int, bool alignOnRight)
0374 {
0375     if (alignOnRight) {
0376         right -= 3 + gHorizontalItemSpacing;
0377     } else {
0378         left += 3 + gHorizontalItemSpacing;
0379     }
0380 }
0381 
0382 static inline void compute_bounding_rect_for_horizontal_spacer(int &left, int top, int &right, int bottom, QRect &outRect, bool alignOnRight)
0383 {
0384     if (alignOnRight) {
0385         right -= 3;
0386         outRect = QRect(right, top, 3, bottom - top);
0387         right -= gHorizontalItemSpacing;
0388     } else {
0389         outRect = QRect(left, top, 3, bottom - top);
0390         left += 3 + gHorizontalItemSpacing;
0391     }
0392 }
0393 
0394 static inline void
0395 paint_permanent_icon(const QPixmap *pix, Theme::ContentItem *, QPainter *painter, int &left, int top, int &right, bool alignOnRight, int iconSize)
0396 {
0397     if (alignOnRight) {
0398         right -= iconSize; // this icon is always present
0399         if (right < 0) {
0400             return;
0401         }
0402         painter->drawPixmap(right, top, iconSize, iconSize, *pix);
0403         right -= gHorizontalItemSpacing;
0404     } else {
0405         if (left > (right - iconSize)) {
0406             return;
0407         }
0408         painter->drawPixmap(left, top, iconSize, iconSize, *pix);
0409         left += iconSize + gHorizontalItemSpacing;
0410     }
0411 }
0412 
0413 static inline void
0414 compute_bounding_rect_for_permanent_icon(Theme::ContentItem *, int &left, int top, int &right, QRect &outRect, bool alignOnRight, int iconSize)
0415 {
0416     if (alignOnRight) {
0417         right -= iconSize; // this icon is always present
0418         outRect = QRect(right, top, iconSize, iconSize);
0419         right -= gHorizontalItemSpacing;
0420     } else {
0421         outRect = QRect(left, top, iconSize, iconSize);
0422         left += iconSize + gHorizontalItemSpacing;
0423     }
0424 }
0425 
0426 static inline void paint_boolean_state_icon(bool enabled,
0427                                             const QPixmap *pix,
0428                                             Theme::ContentItem *ci,
0429                                             QPainter *painter,
0430                                             int &left,
0431                                             int top,
0432                                             int &right,
0433                                             bool alignOnRight,
0434                                             int iconSize)
0435 {
0436     if (enabled) {
0437         paint_permanent_icon(pix, ci, painter, left, top, right, alignOnRight, iconSize);
0438         return;
0439     }
0440 
0441     // off -> icon disabled
0442     if (ci->hideWhenDisabled()) {
0443         return; // doesn't even take space
0444     }
0445 
0446     if (ci->softenByBlendingWhenDisabled()) {
0447         // still paint, but very soft
0448         qreal oldOpacity = painter->opacity();
0449         painter->setOpacity(0.1);
0450         paint_permanent_icon(pix, ci, painter, left, top, right, alignOnRight, iconSize);
0451         painter->setOpacity(oldOpacity);
0452         return;
0453     }
0454 
0455     // just takes space
0456     if (alignOnRight) {
0457         right -= iconSize + gHorizontalItemSpacing;
0458     } else {
0459         left += iconSize + gHorizontalItemSpacing;
0460     }
0461 }
0462 
0463 static inline void compute_bounding_rect_for_boolean_state_icon(bool enabled,
0464                                                                 Theme::ContentItem *ci,
0465                                                                 int &left,
0466                                                                 int top,
0467                                                                 int &right,
0468                                                                 QRect &outRect,
0469                                                                 bool alignOnRight,
0470                                                                 int iconSize)
0471 {
0472     if ((!enabled) && ci->hideWhenDisabled()) {
0473         outRect = QRect();
0474         return; // doesn't even take space
0475     }
0476 
0477     compute_bounding_rect_for_permanent_icon(ci, left, top, right, outRect, alignOnRight, iconSize);
0478 }
0479 
0480 static inline void paint_tag_list(const QList<MessageItem::Tag *> &tagList, QPainter *painter, int &left, int top, int &right, bool alignOnRight, int iconSize)
0481 {
0482     if (alignOnRight) {
0483         for (const MessageItem::Tag *tag : tagList) {
0484             right -= iconSize; // this icon is always present
0485             if (right < 0) {
0486                 return;
0487             }
0488             painter->drawPixmap(right, top, iconSize, iconSize, tag->pixmap());
0489             right -= gHorizontalItemSpacing;
0490         }
0491     } else {
0492         for (const MessageItem::Tag *tag : tagList) {
0493             if (left > right - iconSize) {
0494                 return;
0495             }
0496             painter->drawPixmap(left, top, iconSize, iconSize, tag->pixmap());
0497             left += iconSize + gHorizontalItemSpacing;
0498         }
0499     }
0500 }
0501 
0502 static inline void
0503 compute_bounding_rect_for_tag_list(const QList<MessageItem::Tag *> &tagList, int &left, int top, int &right, QRect &outRect, bool alignOnRight, int iconSize)
0504 {
0505     int width = tagList.count() * (iconSize + gHorizontalItemSpacing);
0506     if (alignOnRight) {
0507         right -= width;
0508         outRect = QRect(right, top, width, iconSize);
0509     } else {
0510         outRect = QRect(left, top, width, iconSize);
0511         left += width;
0512     }
0513 }
0514 
0515 static inline void compute_size_hint_for_item(Theme::ContentItem *ci, int &maxh, int &totalw, int iconSize, const Item *item)
0516 {
0517     Q_UNUSED(item)
0518     if (ci->displaysText()) {
0519         if (sFontHeightCache > maxh) {
0520             maxh = sFontHeightCache;
0521         }
0522         totalw += ci->displaysLongText() ? 128 : 64;
0523         return;
0524     }
0525 
0526     if (ci->isIcon()) {
0527         totalw += iconSize + gHorizontalItemSpacing;
0528         if (maxh < iconSize) {
0529             maxh = iconSize;
0530         }
0531         return;
0532     }
0533 
0534     if (ci->isSpacer()) {
0535         if (18 > maxh) {
0536             maxh = 18;
0537         }
0538         totalw += 3 + gHorizontalItemSpacing;
0539         return;
0540     }
0541 
0542     // should never be reached
0543     if (18 > maxh) {
0544         maxh = 18;
0545     }
0546     totalw += gHorizontalItemSpacing;
0547 }
0548 
0549 static inline QSize compute_size_hint_for_row(const Theme::Row *r, int iconSize, const Item *item)
0550 {
0551     int maxh = 8; // at least 8 pixels for a pixmap
0552     int totalw = 0;
0553 
0554     // right aligned stuff first
0555     auto items = r->rightItems();
0556     for (const auto it : std::as_const(items)) {
0557         compute_size_hint_for_item(const_cast<Theme::ContentItem *>(it), maxh, totalw, iconSize, item);
0558     }
0559 
0560     // then left aligned stuff
0561     items = r->leftItems();
0562     for (const auto it : std::as_const(items)) {
0563         compute_size_hint_for_item(const_cast<Theme::ContentItem *>(it), maxh, totalw, iconSize, item);
0564     }
0565 
0566     return {totalw, maxh};
0567 }
0568 
0569 void ThemeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0570 {
0571     if (!index.isValid()) {
0572         return; // bleah
0573     }
0574 
0575     Item *item = itemFromIndex(index);
0576     if (!item) {
0577         return; // hm...
0578     }
0579 
0580     QStyleOptionViewItem opt = option;
0581     initStyleOption(&opt, index);
0582 
0583     opt.text.clear(); // draw no text for me, please.. I'll do it in a while
0584 
0585     // Set background color of control if necessary
0586     if (item->type() == Item::Message) {
0587         auto msgItem = static_cast<MessageItem *>(item);
0588         if (msgItem->backgroundColor().isValid()) {
0589             opt.backgroundBrush = QBrush(msgItem->backgroundColor());
0590         }
0591     }
0592 
0593     QStyle *style = mItemView->style();
0594     style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
0595 
0596     if (!mTheme) {
0597         return; // hm hm...
0598     }
0599 
0600     const Theme::Column *skcolumn = mTheme->column(index.column());
0601     if (!skcolumn) {
0602         return; // bleah
0603     }
0604 
0605     const QList<MessageList::Core::Theme::Row *> *rows;
0606 
0607     MessageItem *messageItem = nullptr;
0608     GroupHeaderItem *groupHeaderItem = nullptr;
0609 
0610     int top = opt.rect.top();
0611     int right = opt.rect.left() + opt.rect.width(); // don't use opt.rect.right() since it's screwed
0612     int left = opt.rect.left();
0613 
0614     // Storing the changed members one by one is faster than saving the painter state
0615     QFont oldFont = painter->font();
0616     QPen oldPen = painter->pen();
0617     qreal oldOpacity = painter->opacity();
0618 
0619     QPen defaultPen;
0620     bool usingNonDefaultTextColor = false;
0621 
0622     switch (item->type()) {
0623     case Item::Message:
0624         rows = &(skcolumn->messageRows());
0625         messageItem = static_cast<MessageItem *>(item);
0626 
0627         if ((!(opt.state & QStyle::State_Enabled)) || messageItem->aboutToBeRemoved() || (!messageItem->isValid())) {
0628             painter->setOpacity(0.5);
0629             defaultPen = QPen(opt.palette.brush(QPalette::Disabled, QPalette::Text), 0);
0630         } else {
0631             QPalette::ColorGroup cg;
0632 
0633             if (opt.state & QStyle::State_Active) {
0634                 cg = QPalette::Normal;
0635             } else {
0636                 cg = QPalette::Inactive;
0637             }
0638 
0639             if (opt.state & QStyle::State_Selected) {
0640                 defaultPen = QPen(opt.palette.brush(cg, QPalette::HighlightedText), 0);
0641             } else {
0642                 if (messageItem->textColor().isValid()) {
0643                     usingNonDefaultTextColor = true;
0644                     defaultPen = QPen(messageItem->textColor(), 0);
0645                 } else {
0646                     defaultPen = QPen(opt.palette.brush(cg, QPalette::Text), 0);
0647                 }
0648             }
0649         }
0650 
0651         top += gMessageVerticalMargin;
0652         right -= gMessageHorizontalMargin;
0653         left += gMessageHorizontalMargin;
0654         break;
0655     case Item::GroupHeader: {
0656         rows = &(skcolumn->groupHeaderRows());
0657         groupHeaderItem = static_cast<GroupHeaderItem *>(item);
0658 
0659         QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0660 
0661         if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
0662             cg = QPalette::Inactive;
0663         }
0664 
0665         QPalette::ColorRole cr;
0666 
0667         top += gGroupHeaderOuterVerticalMargin;
0668         right -= gGroupHeaderOuterHorizontalMargin;
0669         left += gGroupHeaderOuterHorizontalMargin;
0670 
0671         switch (mTheme->groupHeaderBackgroundMode()) {
0672         case Theme::Transparent:
0673             cr = (opt.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text;
0674             defaultPen = QPen(opt.palette.brush(cg, cr), 0);
0675             break;
0676         case Theme::AutoColor:
0677         case Theme::CustomColor:
0678             switch (mTheme->groupHeaderBackgroundStyle()) {
0679             case Theme::PlainRect:
0680                 painter->fillRect(QRect(left, top, right - left, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
0681                                   QBrush(mGroupHeaderBackgroundColor));
0682                 break;
0683             case Theme::PlainJoinedRect: {
0684                 int rleft = (opt.viewItemPosition == QStyleOptionViewItem::Beginning) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
0685                     ? left
0686                     : opt.rect.left();
0687                 int rright = (opt.viewItemPosition == QStyleOptionViewItem::End) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
0688                     ? right
0689                     : opt.rect.left() + opt.rect.width();
0690                 painter->fillRect(QRect(rleft, top, rright - rleft, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
0691                                   QBrush(mGroupHeaderBackgroundColor));
0692                 break;
0693             }
0694             case Theme::RoundedJoinedRect:
0695                 if (opt.viewItemPosition == QStyleOptionViewItem::Middle) {
0696                     painter->fillRect(QRect(opt.rect.left(), top, opt.rect.width(), opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
0697                                       QBrush(mGroupHeaderBackgroundColor));
0698                     break; // don't fall through
0699                 }
0700                 if (opt.viewItemPosition == QStyleOptionViewItem::Beginning) {
0701                     painter->fillRect(QRect(opt.rect.left() + opt.rect.width() - 10, top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
0702                                       QBrush(mGroupHeaderBackgroundColor));
0703                 } else if (opt.viewItemPosition == QStyleOptionViewItem::End) {
0704                     painter->fillRect(QRect(opt.rect.left(), top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
0705                                       QBrush(mGroupHeaderBackgroundColor));
0706                 }
0707                 // fall through anyway
0708                 [[fallthrough]];
0709             case Theme::RoundedRect: {
0710                 painter->setPen(Qt::NoPen);
0711                 bool hadAntialiasing = painter->renderHints() & QPainter::Antialiasing;
0712                 if (!hadAntialiasing) {
0713                     painter->setRenderHint(QPainter::Antialiasing, true);
0714                 }
0715                 painter->setBrush(QBrush(mGroupHeaderBackgroundColor));
0716                 painter->setBackgroundMode(Qt::OpaqueMode);
0717                 int w = right - left;
0718                 if (w > 0) {
0719                     painter->drawRoundedRect(QRect(left, top, w, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)), 4.0, 4.0);
0720                 }
0721                 if (!hadAntialiasing) {
0722                     painter->setRenderHint(QPainter::Antialiasing, false);
0723                 }
0724                 painter->setBackgroundMode(Qt::TransparentMode);
0725                 break;
0726             }
0727             case Theme::GradientJoinedRect: {
0728                 // FIXME: Could cache this brush
0729                 QLinearGradient gradient(0, top, 0, top + opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
0730                 gradient.setColorAt(0.0, KColorScheme::shade(mGroupHeaderBackgroundColor, KColorScheme::LightShade, 0.3));
0731                 gradient.setColorAt(1.0, mGroupHeaderBackgroundColor);
0732                 if (opt.viewItemPosition == QStyleOptionViewItem::Middle) {
0733                     painter->fillRect(QRect(opt.rect.left(), top, opt.rect.width(), opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
0734                                       QBrush(gradient));
0735                     break; // don't fall through
0736                 }
0737                 if (opt.viewItemPosition == QStyleOptionViewItem::Beginning) {
0738                     painter->fillRect(QRect(opt.rect.left() + opt.rect.width() - 10, top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
0739                                       QBrush(gradient));
0740                 } else if (opt.viewItemPosition == QStyleOptionViewItem::End) {
0741                     painter->fillRect(QRect(opt.rect.left(), top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)), QBrush(gradient));
0742                 }
0743                 // fall through anyway
0744                 [[fallthrough]];
0745             }
0746             case Theme::GradientRect: {
0747                 // FIXME: Could cache this brush
0748                 QLinearGradient gradient(0, top, 0, top + opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
0749                 gradient.setColorAt(0.0, KColorScheme::shade(mGroupHeaderBackgroundColor, KColorScheme::LightShade, 0.3));
0750                 gradient.setColorAt(1.0, mGroupHeaderBackgroundColor);
0751                 painter->setPen(Qt::NoPen);
0752                 bool hadAntialiasing = painter->renderHints() & QPainter::Antialiasing;
0753                 if (!hadAntialiasing) {
0754                     painter->setRenderHint(QPainter::Antialiasing, true);
0755                 }
0756                 painter->setBrush(QBrush(gradient));
0757                 painter->setBackgroundMode(Qt::OpaqueMode);
0758                 int w = right - left;
0759                 if (w > 0) {
0760                     painter->drawRoundedRect(QRect(left, top, w, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)), 4.0, 4.0);
0761                 }
0762                 if (!hadAntialiasing) {
0763                     painter->setRenderHint(QPainter::Antialiasing, false);
0764                 }
0765                 painter->setBackgroundMode(Qt::TransparentMode);
0766                 break;
0767             }
0768             case Theme::StyledRect:
0769                 // oxygen, for instance, has a nice graphics for selected items
0770                 opt.rect = QRect(left, top, right - left, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
0771                 opt.state |= QStyle::State_Selected;
0772                 opt.viewItemPosition = QStyleOptionViewItem::OnlyOne;
0773                 opt.palette.setColor(cg, QPalette::Highlight, mGroupHeaderBackgroundColor);
0774                 style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
0775                 break;
0776             case Theme::StyledJoinedRect: {
0777                 int rleft = (opt.viewItemPosition == QStyleOptionViewItem::Beginning) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
0778                     ? left
0779                     : opt.rect.left();
0780                 int rright = (opt.viewItemPosition == QStyleOptionViewItem::End) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
0781                     ? right
0782                     : opt.rect.left() + opt.rect.width();
0783                 opt.rect = QRect(rleft, top, rright - rleft, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
0784                 opt.state |= QStyle::State_Selected;
0785                 opt.palette.setColor(cg, QPalette::Highlight, mGroupHeaderBackgroundColor);
0786                 style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
0787                 break;
0788             }
0789             }
0790 
0791             defaultPen = QPen(opt.palette.brush(cg, QPalette::Text), 0);
0792             break;
0793         }
0794         top += gGroupHeaderInnerVerticalMargin;
0795         right -= gGroupHeaderInnerHorizontalMargin;
0796         left += gGroupHeaderInnerHorizontalMargin;
0797         break;
0798     }
0799     default:
0800         Q_ASSERT(false);
0801         return; // bug
0802     }
0803 
0804     Qt::LayoutDirection layoutDir = mItemView->layoutDirection();
0805 
0806     for (const auto row : std::as_const(*rows)) {
0807         QSize rowSizeHint = compute_size_hint_for_row(row, mTheme->iconSize(), item);
0808 
0809         int bottom = top + rowSizeHint.height();
0810 
0811         // paint right aligned stuff first
0812         int r = right;
0813         int l = left;
0814         const auto rightItems = row->rightItems();
0815         for (const auto itemit : rightItems) {
0816             auto ci = const_cast<Theme::ContentItem *>(itemit);
0817 
0818             if (ci->canUseCustomColor()) {
0819                 if (ci->useCustomColor() && (!(opt.state & QStyle::State_Selected))) {
0820                     if (usingNonDefaultTextColor) {
0821                         // merge the colors
0822                         QColor nonDefault = defaultPen.color();
0823                         QColor custom = ci->customColor();
0824                         QColor merged((nonDefault.red() + custom.red()) >> 1,
0825                                       (nonDefault.green() + custom.green()) >> 1,
0826                                       (nonDefault.blue() + custom.blue()) >> 1);
0827                         painter->setPen(QPen(merged));
0828                     } else {
0829                         painter->setPen(QPen(ci->customColor()));
0830                     }
0831                 } else {
0832                     painter->setPen(defaultPen);
0833                 }
0834             } // otherwise setting a pen is useless at this time
0835 
0836             const QFont &font = cachedFont(ci, item);
0837 
0838             switch (ci->type()) {
0839             case Theme::ContentItem::Subject:
0840                 paint_right_aligned_elided_text(item->subject(), ci, painter, l, top, r, layoutDir, font);
0841                 break;
0842             case Theme::ContentItem::SenderOrReceiver:
0843                 paint_right_aligned_elided_text(item->displaySenderOrReceiver(), ci, painter, l, top, r, layoutDir, font);
0844                 break;
0845             case Theme::ContentItem::Receiver:
0846                 paint_right_aligned_elided_text(item->displayReceiver(), ci, painter, l, top, r, layoutDir, font);
0847                 break;
0848             case Theme::ContentItem::Sender:
0849                 paint_right_aligned_elided_text(item->displaySender(), ci, painter, l, top, r, layoutDir, font);
0850                 break;
0851             case Theme::ContentItem::Date:
0852                 paint_right_aligned_elided_text(item->formattedDate(), ci, painter, l, top, r, layoutDir, font);
0853                 break;
0854             case Theme::ContentItem::MostRecentDate:
0855                 paint_right_aligned_elided_text(item->formattedMaxDate(), ci, painter, l, top, r, layoutDir, font);
0856                 break;
0857             case Theme::ContentItem::Size:
0858                 paint_right_aligned_elided_text(item->formattedSize(), ci, painter, l, top, r, layoutDir, font);
0859                 break;
0860             case Theme::ContentItem::Folder:
0861                 paint_right_aligned_elided_text(item->folder(), ci, painter, l, top, r, layoutDir, font);
0862                 break;
0863             case Theme::ContentItem::GroupHeaderLabel:
0864                 if (groupHeaderItem) {
0865                     paint_right_aligned_elided_text(groupHeaderItem->label(), ci, painter, l, top, r, layoutDir, font);
0866                 }
0867                 break;
0868             case Theme::ContentItem::ReadStateIcon:
0869                 paint_permanent_icon(get_read_state_icon(mTheme, item), ci, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
0870                 break;
0871             case Theme::ContentItem::CombinedReadRepliedStateIcon:
0872                 if (messageItem) {
0873                     paint_permanent_icon(get_combined_read_replied_state_icon(mTheme, messageItem),
0874                                          ci,
0875                                          painter,
0876                                          l,
0877                                          top,
0878                                          r,
0879                                          layoutDir == Qt::LeftToRight,
0880                                          mTheme->iconSize());
0881                 }
0882                 break;
0883             case Theme::ContentItem::ExpandedStateIcon: {
0884                 const QPixmap *pix =
0885                     item->childItemCount() > 0 ? mTheme->pixmap((option.state & QStyle::State_Open) ? Theme::IconShowLess : Theme::IconShowMore) : nullptr;
0886                 paint_boolean_state_icon(pix != nullptr,
0887                                          pix ? pix : mTheme->pixmap(Theme::IconShowMore),
0888                                          ci,
0889                                          painter,
0890                                          l,
0891                                          top,
0892                                          r,
0893                                          layoutDir == Qt::LeftToRight,
0894                                          mTheme->iconSize());
0895                 break;
0896             }
0897             case Theme::ContentItem::RepliedStateIcon:
0898                 if (messageItem) {
0899                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
0900                     paint_boolean_state_icon(pix != nullptr,
0901                                              pix ? pix : mTheme->pixmap(Theme::IconReplied),
0902                                              ci,
0903                                              painter,
0904                                              l,
0905                                              top,
0906                                              r,
0907                                              layoutDir == Qt::LeftToRight,
0908                                              mTheme->iconSize());
0909                 }
0910                 break;
0911             case Theme::ContentItem::EncryptionStateIcon:
0912                 if (messageItem) {
0913                     bool enabled;
0914                     const QPixmap *pix = get_encryption_state_icon(mTheme, messageItem, &enabled);
0915                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
0916                 }
0917                 break;
0918             case Theme::ContentItem::SignatureStateIcon:
0919                 if (messageItem) {
0920                     bool enabled;
0921                     const QPixmap *pix = get_signature_state_icon(mTheme, messageItem, &enabled);
0922                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
0923                 }
0924                 break;
0925             case Theme::ContentItem::SpamHamStateIcon:
0926                 if (messageItem) {
0927                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
0928                     paint_boolean_state_icon(pix != nullptr,
0929                                              pix ? pix : mTheme->pixmap(Theme::IconSpam),
0930                                              ci,
0931                                              painter,
0932                                              l,
0933                                              top,
0934                                              r,
0935                                              layoutDir == Qt::LeftToRight,
0936                                              mTheme->iconSize());
0937                 }
0938                 break;
0939             case Theme::ContentItem::WatchedIgnoredStateIcon:
0940                 if (messageItem) {
0941                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
0942                     paint_boolean_state_icon(pix != nullptr,
0943                                              pix ? pix : mTheme->pixmap(Theme::IconWatched),
0944                                              ci,
0945                                              painter,
0946                                              l,
0947                                              top,
0948                                              r,
0949                                              layoutDir == Qt::LeftToRight,
0950                                              mTheme->iconSize());
0951                 }
0952                 break;
0953             case Theme::ContentItem::AttachmentStateIcon:
0954                 if (messageItem) {
0955                     paint_boolean_state_icon(messageItem->status().hasAttachment(),
0956                                              mTheme->pixmap(Theme::IconAttachment),
0957                                              ci,
0958                                              painter,
0959                                              l,
0960                                              top,
0961                                              r,
0962                                              layoutDir == Qt::LeftToRight,
0963                                              mTheme->iconSize());
0964                 }
0965                 break;
0966             case Theme::ContentItem::AnnotationIcon:
0967                 if (messageItem) {
0968                     paint_boolean_state_icon(messageItem->hasAnnotation(),
0969                                              mTheme->pixmap(Theme::IconAnnotation),
0970                                              ci,
0971                                              painter,
0972                                              l,
0973                                              top,
0974                                              r,
0975                                              layoutDir == Qt::LeftToRight,
0976                                              mTheme->iconSize());
0977                 }
0978                 break;
0979             case Theme::ContentItem::InvitationIcon:
0980                 if (messageItem) {
0981                     paint_boolean_state_icon(messageItem->status().hasInvitation(),
0982                                              mTheme->pixmap(Theme::IconInvitation),
0983                                              ci,
0984                                              painter,
0985                                              l,
0986                                              top,
0987                                              r,
0988                                              layoutDir == Qt::LeftToRight,
0989                                              mTheme->iconSize());
0990                 }
0991                 break;
0992             case Theme::ContentItem::ActionItemStateIcon:
0993                 if (messageItem) {
0994                     paint_boolean_state_icon(messageItem->status().isToAct(),
0995                                              mTheme->pixmap(Theme::IconActionItem),
0996                                              ci,
0997                                              painter,
0998                                              l,
0999                                              top,
1000                                              r,
1001                                              layoutDir == Qt::LeftToRight,
1002                                              mTheme->iconSize());
1003                 }
1004                 break;
1005             case Theme::ContentItem::ImportantStateIcon:
1006                 if (messageItem) {
1007                     paint_boolean_state_icon(messageItem->status().isImportant(),
1008                                              mTheme->pixmap(Theme::IconImportant),
1009                                              ci,
1010                                              painter,
1011                                              l,
1012                                              top,
1013                                              r,
1014                                              layoutDir == Qt::LeftToRight,
1015                                              mTheme->iconSize());
1016                 }
1017                 break;
1018             case Theme::ContentItem::VerticalLine:
1019                 paint_vertical_line(painter, l, top, r, bottom, layoutDir == Qt::LeftToRight);
1020                 break;
1021             case Theme::ContentItem::HorizontalSpacer:
1022                 paint_horizontal_spacer(l, top, r, bottom, layoutDir == Qt::LeftToRight);
1023                 break;
1024             case Theme::ContentItem::TagList:
1025                 if (messageItem) {
1026                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1027                     paint_tag_list(tagList, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1028                 }
1029                 break;
1030             }
1031         }
1032 
1033         // then paint left aligned stuff
1034         const auto leftItems = row->leftItems();
1035         for (const auto itemit : leftItems) {
1036             auto ci = const_cast<Theme::ContentItem *>(itemit);
1037 
1038             if (ci->canUseCustomColor()) {
1039                 if (ci->useCustomColor() && (!(opt.state & QStyle::State_Selected))) {
1040                     if (usingNonDefaultTextColor) {
1041                         // merge the colors
1042                         QColor nonDefault = defaultPen.color();
1043                         QColor custom = ci->customColor();
1044                         QColor merged((nonDefault.red() + custom.red()) >> 1,
1045                                       (nonDefault.green() + custom.green()) >> 1,
1046                                       (nonDefault.blue() + custom.blue()) >> 1);
1047                         painter->setPen(QPen(merged));
1048                     } else {
1049                         painter->setPen(QPen(ci->customColor()));
1050                     }
1051                 } else {
1052                     painter->setPen(defaultPen);
1053                 }
1054             } // otherwise setting a pen is useless at this time
1055 
1056             const QFont &font = cachedFont(ci, item);
1057 
1058             switch (ci->type()) {
1059             case Theme::ContentItem::Subject:
1060                 paint_left_aligned_elided_text(item->subject(), ci, painter, l, top, r, layoutDir, font);
1061                 break;
1062             case Theme::ContentItem::SenderOrReceiver:
1063                 paint_left_aligned_elided_text(item->displaySenderOrReceiver(), ci, painter, l, top, r, layoutDir, font);
1064                 break;
1065             case Theme::ContentItem::Receiver:
1066                 paint_left_aligned_elided_text(item->displayReceiver(), ci, painter, l, top, r, layoutDir, font);
1067                 break;
1068             case Theme::ContentItem::Sender:
1069                 paint_left_aligned_elided_text(item->displaySender(), ci, painter, l, top, r, layoutDir, font);
1070                 break;
1071             case Theme::ContentItem::Date:
1072                 paint_left_aligned_elided_text(item->formattedDate(), ci, painter, l, top, r, layoutDir, font);
1073                 break;
1074             case Theme::ContentItem::MostRecentDate:
1075                 paint_left_aligned_elided_text(item->formattedMaxDate(), ci, painter, l, top, r, layoutDir, font);
1076                 break;
1077             case Theme::ContentItem::Size:
1078                 paint_left_aligned_elided_text(item->formattedSize(), ci, painter, l, top, r, layoutDir, font);
1079                 break;
1080             case Theme::ContentItem::Folder:
1081                 paint_left_aligned_elided_text(item->folder(), ci, painter, l, top, r, layoutDir, font);
1082                 break;
1083             case Theme::ContentItem::GroupHeaderLabel:
1084                 if (groupHeaderItem) {
1085                     paint_left_aligned_elided_text(groupHeaderItem->label(), ci, painter, l, top, r, layoutDir, font);
1086                 }
1087                 break;
1088             case Theme::ContentItem::ReadStateIcon:
1089                 paint_permanent_icon(get_read_state_icon(mTheme, item), ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1090                 break;
1091             case Theme::ContentItem::CombinedReadRepliedStateIcon:
1092                 if (messageItem) {
1093                     paint_permanent_icon(get_combined_read_replied_state_icon(mTheme, messageItem),
1094                                          ci,
1095                                          painter,
1096                                          l,
1097                                          top,
1098                                          r,
1099                                          layoutDir != Qt::LeftToRight,
1100                                          mTheme->iconSize());
1101                 }
1102                 break;
1103             case Theme::ContentItem::ExpandedStateIcon: {
1104                 const QPixmap *pix =
1105                     item->childItemCount() > 0 ? mTheme->pixmap((option.state & QStyle::State_Open) ? Theme::IconShowLess : Theme::IconShowMore) : nullptr;
1106                 paint_boolean_state_icon(pix != nullptr,
1107                                          pix ? pix : mTheme->pixmap(Theme::IconShowMore),
1108                                          ci,
1109                                          painter,
1110                                          l,
1111                                          top,
1112                                          r,
1113                                          layoutDir != Qt::LeftToRight,
1114                                          mTheme->iconSize());
1115                 break;
1116             }
1117             case Theme::ContentItem::RepliedStateIcon:
1118                 if (messageItem) {
1119                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
1120                     paint_boolean_state_icon(pix != nullptr,
1121                                              pix ? pix : mTheme->pixmap(Theme::IconReplied),
1122                                              ci,
1123                                              painter,
1124                                              l,
1125                                              top,
1126                                              r,
1127                                              layoutDir != Qt::LeftToRight,
1128                                              mTheme->iconSize());
1129                 }
1130                 break;
1131             case Theme::ContentItem::EncryptionStateIcon:
1132                 if (messageItem) {
1133                     bool enabled;
1134                     const QPixmap *pix = get_encryption_state_icon(mTheme, messageItem, &enabled);
1135                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1136                 }
1137                 break;
1138             case Theme::ContentItem::SignatureStateIcon:
1139                 if (messageItem) {
1140                     bool enabled;
1141                     const QPixmap *pix = get_signature_state_icon(mTheme, messageItem, &enabled);
1142                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1143                 }
1144                 break;
1145             case Theme::ContentItem::SpamHamStateIcon:
1146                 if (messageItem) {
1147                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
1148                     paint_boolean_state_icon(pix != nullptr,
1149                                              pix ? pix : mTheme->pixmap(Theme::IconSpam),
1150                                              ci,
1151                                              painter,
1152                                              l,
1153                                              top,
1154                                              r,
1155                                              layoutDir != Qt::LeftToRight,
1156                                              mTheme->iconSize());
1157                 }
1158                 break;
1159             case Theme::ContentItem::WatchedIgnoredStateIcon:
1160                 if (messageItem) {
1161                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
1162                     paint_boolean_state_icon(pix != nullptr,
1163                                              pix ? pix : mTheme->pixmap(Theme::IconWatched),
1164                                              ci,
1165                                              painter,
1166                                              l,
1167                                              top,
1168                                              r,
1169                                              layoutDir != Qt::LeftToRight,
1170                                              mTheme->iconSize());
1171                 }
1172                 break;
1173             case Theme::ContentItem::AttachmentStateIcon:
1174                 if (messageItem) {
1175                     paint_boolean_state_icon(messageItem->status().hasAttachment(),
1176                                              mTheme->pixmap(Theme::IconAttachment),
1177                                              ci,
1178                                              painter,
1179                                              l,
1180                                              top,
1181                                              r,
1182                                              layoutDir != Qt::LeftToRight,
1183                                              mTheme->iconSize());
1184                 }
1185                 break;
1186             case Theme::ContentItem::AnnotationIcon:
1187                 if (messageItem) {
1188                     paint_boolean_state_icon(messageItem->hasAnnotation(),
1189                                              mTheme->pixmap(Theme::IconAnnotation),
1190                                              ci,
1191                                              painter,
1192                                              l,
1193                                              top,
1194                                              r,
1195                                              layoutDir != Qt::LeftToRight,
1196                                              mTheme->iconSize());
1197                 }
1198                 break;
1199             case Theme::ContentItem::InvitationIcon:
1200                 if (messageItem) {
1201                     paint_boolean_state_icon(messageItem->status().hasInvitation(),
1202                                              mTheme->pixmap(Theme::IconInvitation),
1203                                              ci,
1204                                              painter,
1205                                              l,
1206                                              top,
1207                                              r,
1208                                              layoutDir != Qt::LeftToRight,
1209                                              mTheme->iconSize());
1210                 }
1211                 break;
1212             case Theme::ContentItem::ActionItemStateIcon:
1213                 if (messageItem) {
1214                     paint_boolean_state_icon(messageItem->status().isToAct(),
1215                                              mTheme->pixmap(Theme::IconActionItem),
1216                                              ci,
1217                                              painter,
1218                                              l,
1219                                              top,
1220                                              r,
1221                                              layoutDir != Qt::LeftToRight,
1222                                              mTheme->iconSize());
1223                 }
1224                 break;
1225             case Theme::ContentItem::ImportantStateIcon:
1226                 if (messageItem) {
1227                     paint_boolean_state_icon(messageItem->status().isImportant(),
1228                                              mTheme->pixmap(Theme::IconImportant),
1229                                              ci,
1230                                              painter,
1231                                              l,
1232                                              top,
1233                                              r,
1234                                              layoutDir != Qt::LeftToRight,
1235                                              mTheme->iconSize());
1236                 }
1237                 break;
1238             case Theme::ContentItem::VerticalLine:
1239                 paint_vertical_line(painter, l, top, r, bottom, layoutDir != Qt::LeftToRight);
1240                 break;
1241             case Theme::ContentItem::HorizontalSpacer:
1242                 paint_horizontal_spacer(l, top, r, bottom, layoutDir != Qt::LeftToRight);
1243                 break;
1244             case Theme::ContentItem::TagList:
1245                 if (messageItem) {
1246                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1247                     paint_tag_list(tagList, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1248                 }
1249                 break;
1250             }
1251         }
1252 
1253         top = bottom;
1254     }
1255 
1256     painter->setFont(oldFont);
1257     painter->setPen(oldPen);
1258     painter->setOpacity(oldOpacity);
1259 }
1260 
1261 bool ThemeDelegate::hitTest(const QPoint &viewportPoint, bool exact)
1262 {
1263     mHitItem = nullptr;
1264     mHitColumn = nullptr;
1265     mHitRow = nullptr;
1266     mHitContentItem = nullptr;
1267 
1268     if (!mTheme) {
1269         return false; // hm hm...
1270     }
1271 
1272     mHitIndex = mItemView->indexAt(viewportPoint);
1273 
1274     if (!mHitIndex.isValid()) {
1275         return false; // bleah
1276     }
1277 
1278     mHitItem = itemFromIndex(mHitIndex);
1279     if (!mHitItem) {
1280         return false; // hm...
1281     }
1282 
1283     mHitItemRect = mItemView->visualRect(mHitIndex);
1284 
1285     mHitColumn = mTheme->column(mHitIndex.column());
1286     if (!mHitColumn) {
1287         return false; // bleah
1288     }
1289 
1290     const QList<Theme::Row *> *rows; // I'd like to have it as reference, but gcc complains...
1291 
1292     MessageItem *messageItem = nullptr;
1293     GroupHeaderItem *groupHeaderItem = nullptr;
1294 
1295     int top = mHitItemRect.top();
1296     int right = mHitItemRect.right();
1297     int left = mHitItemRect.left();
1298 
1299     mHitRow = nullptr;
1300     mHitRowIndex = -1;
1301     mHitContentItem = nullptr;
1302 
1303     switch (mHitItem->type()) {
1304     case Item::Message:
1305         mHitRowIsMessageRow = true;
1306         rows = &(mHitColumn->messageRows());
1307         messageItem = static_cast<MessageItem *>(mHitItem);
1308         // FIXME: paint eventual background here
1309 
1310         top += gMessageVerticalMargin;
1311         right -= gMessageHorizontalMargin;
1312         left += gMessageHorizontalMargin;
1313         break;
1314     case Item::GroupHeader:
1315         mHitRowIsMessageRow = false;
1316         rows = &(mHitColumn->groupHeaderRows());
1317         groupHeaderItem = static_cast<GroupHeaderItem *>(mHitItem);
1318 
1319         top += gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin;
1320         right -= gGroupHeaderOuterHorizontalMargin + gGroupHeaderInnerHorizontalMargin;
1321         left += gGroupHeaderOuterHorizontalMargin + gGroupHeaderInnerHorizontalMargin;
1322         break;
1323     default:
1324         return false; // bug
1325         break;
1326     }
1327 
1328     int rowIdx = 0;
1329     int bestInexactDistance = 0xffffff;
1330     bool bestInexactItemRight = false;
1331     QRect bestInexactRect;
1332     const Theme::ContentItem *bestInexactContentItem = nullptr;
1333 
1334     Qt::LayoutDirection layoutDir = mItemView->layoutDirection();
1335 
1336     for (const auto row : std::as_const(*rows)) {
1337         QSize rowSizeHint = compute_size_hint_for_row(row, mTheme->iconSize(), mHitItem);
1338 
1339         if ((viewportPoint.y() < top) && (rowIdx > 0)) {
1340             break; // not this row (tough we should have already found it... probably clicked upper margin)
1341         }
1342 
1343         int bottom = top + rowSizeHint.height();
1344 
1345         if (viewportPoint.y() > bottom) {
1346             top += rowSizeHint.height();
1347             rowIdx++;
1348             continue; // not this row
1349         }
1350 
1351         bestInexactItemRight = false;
1352         bestInexactDistance = 0xffffff;
1353         bestInexactContentItem = nullptr;
1354 
1355         // this row!
1356         mHitRow = row;
1357         mHitRowIndex = rowIdx;
1358         mHitRowRect = QRect(left, top, right - left, bottom - top);
1359 
1360         // check right aligned stuff first
1361         mHitContentItemRight = true;
1362 
1363         int r = right;
1364         int l = left;
1365         const auto rightItems = mHitRow->rightItems();
1366         for (const auto itemit : rightItems) {
1367             auto ci = const_cast<Theme::ContentItem *>(itemit);
1368 
1369             mHitContentItemRect = QRect();
1370 
1371             const QFont &font = cachedFont(ci, mHitItem);
1372 
1373             switch (ci->type()) {
1374             case Theme::ContentItem::Subject:
1375                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->subject(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1376                 break;
1377             case Theme::ContentItem::SenderOrReceiver:
1378                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displaySenderOrReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1379                 break;
1380             case Theme::ContentItem::Receiver:
1381                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displayReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1382                 break;
1383             case Theme::ContentItem::Sender:
1384                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displaySender(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1385                 break;
1386             case Theme::ContentItem::Date:
1387                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1388                 break;
1389             case Theme::ContentItem::MostRecentDate:
1390                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedMaxDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1391                 break;
1392             case Theme::ContentItem::Size:
1393                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedSize(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1394                 break;
1395             case Theme::ContentItem::Folder:
1396                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->folder(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1397                 break;
1398             case Theme::ContentItem::GroupHeaderLabel:
1399                 if (groupHeaderItem) {
1400                     compute_bounding_rect_for_right_aligned_elided_text(groupHeaderItem->label(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1401                 }
1402                 break;
1403             case Theme::ContentItem::ReadStateIcon:
1404                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1405                 break;
1406             case Theme::ContentItem::CombinedReadRepliedStateIcon:
1407                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1408                 break;
1409             case Theme::ContentItem::ExpandedStateIcon:
1410                 compute_bounding_rect_for_boolean_state_icon(mHitItem->childItemCount() > 0,
1411                                                              ci,
1412                                                              l,
1413                                                              top,
1414                                                              r,
1415                                                              mHitContentItemRect,
1416                                                              layoutDir == Qt::LeftToRight,
1417                                                              mTheme->iconSize());
1418                 break;
1419             case Theme::ContentItem::RepliedStateIcon:
1420                 if (messageItem) {
1421                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
1422                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1423                                                                  ci,
1424                                                                  l,
1425                                                                  top,
1426                                                                  r,
1427                                                                  mHitContentItemRect,
1428                                                                  layoutDir == Qt::LeftToRight,
1429                                                                  mTheme->iconSize());
1430                 }
1431                 break;
1432             case Theme::ContentItem::EncryptionStateIcon:
1433                 if (messageItem) {
1434                     bool enabled;
1435                     get_encryption_state_icon(mTheme, messageItem, &enabled);
1436                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1437                 }
1438                 break;
1439             case Theme::ContentItem::SignatureStateIcon:
1440                 if (messageItem) {
1441                     bool enabled;
1442                     get_signature_state_icon(mTheme, messageItem, &enabled);
1443                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1444                 }
1445                 break;
1446             case Theme::ContentItem::SpamHamStateIcon:
1447                 if (messageItem) {
1448                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
1449                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1450                                                                  ci,
1451                                                                  l,
1452                                                                  top,
1453                                                                  r,
1454                                                                  mHitContentItemRect,
1455                                                                  layoutDir == Qt::LeftToRight,
1456                                                                  mTheme->iconSize());
1457                 }
1458                 break;
1459             case Theme::ContentItem::WatchedIgnoredStateIcon:
1460                 if (messageItem) {
1461                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
1462                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1463                                                                  ci,
1464                                                                  l,
1465                                                                  top,
1466                                                                  r,
1467                                                                  mHitContentItemRect,
1468                                                                  layoutDir == Qt::LeftToRight,
1469                                                                  mTheme->iconSize());
1470                 }
1471                 break;
1472             case Theme::ContentItem::AttachmentStateIcon:
1473                 if (messageItem) {
1474                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasAttachment(),
1475                                                                  ci,
1476                                                                  l,
1477                                                                  top,
1478                                                                  r,
1479                                                                  mHitContentItemRect,
1480                                                                  layoutDir == Qt::LeftToRight,
1481                                                                  mTheme->iconSize());
1482                 }
1483                 break;
1484             case Theme::ContentItem::AnnotationIcon:
1485                 if (messageItem) {
1486                     compute_bounding_rect_for_boolean_state_icon(messageItem->hasAnnotation(),
1487                                                                  ci,
1488                                                                  l,
1489                                                                  top,
1490                                                                  r,
1491                                                                  mHitContentItemRect,
1492                                                                  layoutDir == Qt::LeftToRight,
1493                                                                  mTheme->iconSize());
1494                 }
1495                 break;
1496             case Theme::ContentItem::InvitationIcon:
1497                 if (messageItem) {
1498                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasInvitation(),
1499                                                                  ci,
1500                                                                  l,
1501                                                                  top,
1502                                                                  r,
1503                                                                  mHitContentItemRect,
1504                                                                  layoutDir == Qt::LeftToRight,
1505                                                                  mTheme->iconSize());
1506                 }
1507                 break;
1508             case Theme::ContentItem::ActionItemStateIcon:
1509                 if (messageItem) {
1510                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isToAct(),
1511                                                                  ci,
1512                                                                  l,
1513                                                                  top,
1514                                                                  r,
1515                                                                  mHitContentItemRect,
1516                                                                  layoutDir == Qt::LeftToRight,
1517                                                                  mTheme->iconSize());
1518                 }
1519                 break;
1520             case Theme::ContentItem::ImportantStateIcon:
1521                 if (messageItem) {
1522                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isImportant(),
1523                                                                  ci,
1524                                                                  l,
1525                                                                  top,
1526                                                                  r,
1527                                                                  mHitContentItemRect,
1528                                                                  layoutDir == Qt::LeftToRight,
1529                                                                  mTheme->iconSize());
1530                 }
1531                 break;
1532             case Theme::ContentItem::VerticalLine:
1533                 compute_bounding_rect_for_vertical_line(l, top, r, bottom, mHitContentItemRect, layoutDir == Qt::LeftToRight);
1534                 break;
1535             case Theme::ContentItem::HorizontalSpacer:
1536                 compute_bounding_rect_for_horizontal_spacer(l, top, r, bottom, mHitContentItemRect, layoutDir == Qt::LeftToRight);
1537                 break;
1538             case Theme::ContentItem::TagList:
1539                 if (messageItem) {
1540                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1541                     compute_bounding_rect_for_tag_list(tagList, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1542                 }
1543                 break;
1544             }
1545 
1546             if (mHitContentItemRect.isValid()) {
1547                 if (mHitContentItemRect.contains(viewportPoint)) {
1548                     // caught!
1549                     mHitContentItem = ci;
1550                     return true;
1551                 }
1552                 if (!exact) {
1553                     QRect inexactRect(mHitContentItemRect.left(), mHitRowRect.top(), mHitContentItemRect.width(), mHitRowRect.height());
1554                     if (inexactRect.contains(viewportPoint)) {
1555                         mHitContentItem = ci;
1556                         return true;
1557                     }
1558 
1559                     int inexactDistance =
1560                         viewportPoint.x() > inexactRect.right() ? viewportPoint.x() - inexactRect.right() : inexactRect.left() - viewportPoint.x();
1561                     if (inexactDistance < bestInexactDistance) {
1562                         bestInexactDistance = inexactDistance;
1563                         bestInexactRect = mHitContentItemRect;
1564                         bestInexactItemRight = true;
1565                         bestInexactContentItem = ci;
1566                     }
1567                 }
1568             }
1569         }
1570 
1571         // then check left aligned stuff
1572         mHitContentItemRight = false;
1573 
1574         const auto leftItems = mHitRow->leftItems();
1575         for (const auto itemit : leftItems) {
1576             auto ci = const_cast<Theme::ContentItem *>(itemit);
1577 
1578             mHitContentItemRect = QRect();
1579 
1580             const QFont &font = cachedFont(ci, mHitItem);
1581 
1582             switch (ci->type()) {
1583             case Theme::ContentItem::Subject:
1584                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->subject(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1585                 break;
1586             case Theme::ContentItem::SenderOrReceiver:
1587                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displaySenderOrReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1588                 break;
1589             case Theme::ContentItem::Receiver:
1590                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displayReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1591                 break;
1592             case Theme::ContentItem::Sender:
1593                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displaySender(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1594                 break;
1595             case Theme::ContentItem::Date:
1596                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1597                 break;
1598             case Theme::ContentItem::MostRecentDate:
1599                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedMaxDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1600                 break;
1601             case Theme::ContentItem::Size:
1602                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedSize(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1603                 break;
1604             case Theme::ContentItem::Folder:
1605                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->folder(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1606                 break;
1607             case Theme::ContentItem::GroupHeaderLabel:
1608                 if (groupHeaderItem) {
1609                     compute_bounding_rect_for_left_aligned_elided_text(groupHeaderItem->label(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1610                 }
1611                 break;
1612             case Theme::ContentItem::ReadStateIcon:
1613                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1614                 break;
1615             case Theme::ContentItem::CombinedReadRepliedStateIcon:
1616                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1617                 break;
1618             case Theme::ContentItem::ExpandedStateIcon:
1619                 compute_bounding_rect_for_boolean_state_icon(mHitItem->childItemCount() > 0,
1620                                                              ci,
1621                                                              l,
1622                                                              top,
1623                                                              r,
1624                                                              mHitContentItemRect,
1625                                                              layoutDir != Qt::LeftToRight,
1626                                                              mTheme->iconSize());
1627                 break;
1628             case Theme::ContentItem::RepliedStateIcon:
1629                 if (messageItem) {
1630                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
1631                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1632                                                                  ci,
1633                                                                  l,
1634                                                                  top,
1635                                                                  r,
1636                                                                  mHitContentItemRect,
1637                                                                  layoutDir != Qt::LeftToRight,
1638                                                                  mTheme->iconSize());
1639                 }
1640                 break;
1641             case Theme::ContentItem::EncryptionStateIcon:
1642                 if (messageItem) {
1643                     bool enabled;
1644                     get_encryption_state_icon(mTheme, messageItem, &enabled);
1645                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1646                 }
1647                 break;
1648             case Theme::ContentItem::SignatureStateIcon:
1649                 if (messageItem) {
1650                     bool enabled;
1651                     get_signature_state_icon(mTheme, messageItem, &enabled);
1652                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1653                 }
1654                 break;
1655             case Theme::ContentItem::SpamHamStateIcon:
1656                 if (messageItem) {
1657                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
1658                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1659                                                                  ci,
1660                                                                  l,
1661                                                                  top,
1662                                                                  r,
1663                                                                  mHitContentItemRect,
1664                                                                  layoutDir != Qt::LeftToRight,
1665                                                                  mTheme->iconSize());
1666                 }
1667                 break;
1668             case Theme::ContentItem::WatchedIgnoredStateIcon:
1669                 if (messageItem) {
1670                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
1671                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1672                                                                  ci,
1673                                                                  l,
1674                                                                  top,
1675                                                                  r,
1676                                                                  mHitContentItemRect,
1677                                                                  layoutDir != Qt::LeftToRight,
1678                                                                  mTheme->iconSize());
1679                 }
1680                 break;
1681             case Theme::ContentItem::AttachmentStateIcon:
1682                 if (messageItem) {
1683                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasAttachment(),
1684                                                                  ci,
1685                                                                  l,
1686                                                                  top,
1687                                                                  r,
1688                                                                  mHitContentItemRect,
1689                                                                  layoutDir != Qt::LeftToRight,
1690                                                                  mTheme->iconSize());
1691                 }
1692                 break;
1693             case Theme::ContentItem::AnnotationIcon:
1694                 if (messageItem) {
1695                     compute_bounding_rect_for_boolean_state_icon(messageItem->hasAnnotation(),
1696                                                                  ci,
1697                                                                  l,
1698                                                                  top,
1699                                                                  r,
1700                                                                  mHitContentItemRect,
1701                                                                  layoutDir != Qt::LeftToRight,
1702                                                                  mTheme->iconSize());
1703                 }
1704                 break;
1705             case Theme::ContentItem::InvitationIcon:
1706                 if (messageItem) {
1707                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasInvitation(),
1708                                                                  ci,
1709                                                                  l,
1710                                                                  top,
1711                                                                  r,
1712                                                                  mHitContentItemRect,
1713                                                                  layoutDir != Qt::LeftToRight,
1714                                                                  mTheme->iconSize());
1715                 }
1716                 break;
1717             case Theme::ContentItem::ActionItemStateIcon:
1718                 if (messageItem) {
1719                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isToAct(),
1720                                                                  ci,
1721                                                                  l,
1722                                                                  top,
1723                                                                  r,
1724                                                                  mHitContentItemRect,
1725                                                                  layoutDir != Qt::LeftToRight,
1726                                                                  mTheme->iconSize());
1727                 }
1728                 break;
1729             case Theme::ContentItem::ImportantStateIcon:
1730                 if (messageItem) {
1731                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isImportant(),
1732                                                                  ci,
1733                                                                  l,
1734                                                                  top,
1735                                                                  r,
1736                                                                  mHitContentItemRect,
1737                                                                  layoutDir != Qt::LeftToRight,
1738                                                                  mTheme->iconSize());
1739                 }
1740                 break;
1741             case Theme::ContentItem::VerticalLine:
1742                 compute_bounding_rect_for_vertical_line(l, top, r, bottom, mHitContentItemRect, layoutDir != Qt::LeftToRight);
1743                 break;
1744             case Theme::ContentItem::HorizontalSpacer:
1745                 compute_bounding_rect_for_horizontal_spacer(l, top, r, bottom, mHitContentItemRect, layoutDir != Qt::LeftToRight);
1746                 break;
1747             case Theme::ContentItem::TagList:
1748                 if (messageItem) {
1749                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1750                     compute_bounding_rect_for_tag_list(tagList, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1751                 }
1752                 break;
1753             }
1754 
1755             if (mHitContentItemRect.isValid()) {
1756                 if (mHitContentItemRect.contains(viewportPoint)) {
1757                     // caught!
1758                     mHitContentItem = ci;
1759                     return true;
1760                 }
1761                 if (!exact) {
1762                     QRect inexactRect(mHitContentItemRect.left(), mHitRowRect.top(), mHitContentItemRect.width(), mHitRowRect.height());
1763                     if (inexactRect.contains(viewportPoint)) {
1764                         mHitContentItem = ci;
1765                         return true;
1766                     }
1767 
1768                     int inexactDistance =
1769                         viewportPoint.x() > inexactRect.right() ? viewportPoint.x() - inexactRect.right() : inexactRect.left() - viewportPoint.x();
1770                     if (inexactDistance < bestInexactDistance) {
1771                         bestInexactDistance = inexactDistance;
1772                         bestInexactRect = mHitContentItemRect;
1773                         bestInexactItemRight = false;
1774                         bestInexactContentItem = ci;
1775                     }
1776                 }
1777             }
1778         }
1779 
1780         top += rowSizeHint.height();
1781         rowIdx++;
1782     }
1783 
1784     mHitContentItem = bestInexactContentItem;
1785     mHitContentItemRight = bestInexactItemRight;
1786     mHitContentItemRect = bestInexactRect;
1787     return true;
1788 }
1789 
1790 const QModelIndex &ThemeDelegate::hitIndex() const
1791 {
1792     return mHitIndex;
1793 }
1794 
1795 Item *ThemeDelegate::hitItem() const
1796 {
1797     return mHitItem;
1798 }
1799 
1800 QRect ThemeDelegate::hitItemRect() const
1801 {
1802     return mHitItemRect;
1803 }
1804 
1805 const Theme::Column *ThemeDelegate::hitColumn() const
1806 {
1807     return mHitColumn;
1808 }
1809 
1810 int ThemeDelegate::hitColumnIndex() const
1811 {
1812     return mHitIndex.column();
1813 }
1814 
1815 const Theme::Row *ThemeDelegate::hitRow() const
1816 {
1817     return mHitRow;
1818 }
1819 
1820 int ThemeDelegate::hitRowIndex() const
1821 {
1822     return mHitRowIndex;
1823 }
1824 
1825 QRect ThemeDelegate::hitRowRect() const
1826 {
1827     return mHitRowRect;
1828 }
1829 
1830 bool ThemeDelegate::hitRowIsMessageRow() const
1831 {
1832     return mHitRowIsMessageRow;
1833 }
1834 
1835 const Theme::ContentItem *ThemeDelegate::hitContentItem() const
1836 {
1837     return mHitContentItem;
1838 }
1839 
1840 bool ThemeDelegate::hitContentItemRight() const
1841 {
1842     return mHitContentItemRight;
1843 }
1844 
1845 QRect ThemeDelegate::hitContentItemRect() const
1846 {
1847     return mHitContentItemRect;
1848 }
1849 
1850 QSize ThemeDelegate::sizeHintForItemTypeAndColumn(Item::Type type, int column, const Item *item) const
1851 {
1852     if (!mTheme) {
1853         return {16, 16}; // bleah
1854     }
1855 
1856     const Theme::Column *skcolumn = mTheme->column(column);
1857     if (!skcolumn) {
1858         return {16, 16}; // bleah
1859     }
1860 
1861     const QList<Theme::Row *> *rows; // I'd like to have it as reference, but gcc complains...
1862 
1863     // The sizeHint() is layout direction independent.
1864 
1865     int marginw;
1866     int marginh;
1867 
1868     switch (type) {
1869     case Item::Message:
1870         rows = &(skcolumn->messageRows());
1871 
1872         marginh = gMessageVerticalMargin << 1;
1873         marginw = gMessageHorizontalMargin << 1;
1874         break;
1875     case Item::GroupHeader:
1876         rows = &(skcolumn->groupHeaderRows());
1877 
1878         marginh = (gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin) << 1;
1879         marginw = (gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin) << 1;
1880         break;
1881     default:
1882         return {16, 16}; // bug
1883         break;
1884     }
1885 
1886     int totalh = 0;
1887     int maxw = 0;
1888 
1889     for (QList<Theme::Row *>::ConstIterator rowit = rows->constBegin(), endRowIt = rows->constEnd(); rowit != endRowIt; ++rowit) {
1890         const QSize sh = compute_size_hint_for_row((*rowit), mTheme->iconSize(), item);
1891         totalh += sh.height();
1892         if (sh.width() > maxw) {
1893             maxw = sh.width();
1894         }
1895     }
1896 
1897     return {maxw + marginw, totalh + marginh};
1898 }
1899 
1900 QSize ThemeDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &index) const
1901 {
1902     if (!mTheme || !index.isValid()) {
1903         return {16, 16}; // hm hm...
1904     }
1905 
1906     Item *item = itemFromIndex(index);
1907     if (!item) {
1908         return {16, 16}; // hm...
1909     }
1910 
1911     const Item::Type type = item->type();
1912     if (type == Item::Message) {
1913         if (!mCachedMessageItemSizeHint.isValid()) {
1914             mCachedMessageItemSizeHint = sizeHintForItemTypeAndColumn(Item::Message, index.column(), item);
1915         }
1916         return mCachedMessageItemSizeHint;
1917     } else if (type == Item::GroupHeader) {
1918         if (!mCachedGroupHeaderItemSizeHint.isValid()) {
1919             mCachedGroupHeaderItemSizeHint = sizeHintForItemTypeAndColumn(Item::GroupHeader, index.column(), item);
1920         }
1921         return mCachedGroupHeaderItemSizeHint;
1922     } else {
1923         Q_ASSERT(false);
1924         return {};
1925     }
1926 }
1927 
1928 // Store the new fonts when the generalFont changes and flush sizeHint cache
1929 void ThemeDelegate::generalFontChanged()
1930 {
1931     mCachedMessageItemSizeHint = QSize();
1932     mCachedGroupHeaderItemSizeHint = QSize();
1933 
1934     QFont font;
1935     if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
1936         font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
1937     } else {
1938         font = MessageListSettings::self()->messageListFont();
1939     }
1940     sFontCache[Normal] = font;
1941     sFontMetricsCache[Normal] = QFontMetrics(font);
1942     font.setBold(true);
1943     sFontCache[Bold] = font;
1944     sFontMetricsCache[Bold] = QFontMetrics(font);
1945     font.setBold(false);
1946     font.setItalic(true);
1947     sFontCache[Italic] = font;
1948     sFontMetricsCache[Italic] = QFontMetrics(font);
1949     font.setBold(true);
1950     font.setItalic(true);
1951     sFontCache[BoldItalic] = font;
1952     sFontMetricsCache[BoldItalic] = QFontMetrics(font);
1953 
1954     sFontHeightCache = sFontMetricsCache[Normal].height();
1955 }
1956 
1957 const Theme *ThemeDelegate::theme() const
1958 {
1959     return mTheme;
1960 }
1961 
1962 #include "moc_themedelegate.cpp"