1 /******************************************************************************
2  *
3  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *
7  *******************************************************************************/
8 
9 #include "core/themedelegate.h"
10 #include "core/groupheaderitem.h"
11 #include "core/manager.h"
12 #include "core/messageitem.h"
13 #include "messagelistsettings.h"
14 
15 #include "MessageCore/MessageCoreSettings"
16 #include "MessageCore/StringUtil"
17 
18 #include <KColorScheme>
19 #include <QAbstractItemView>
20 #include <QFont>
21 #include <QFontDatabase>
22 #include <QFontMetrics>
23 #include <QLinearGradient>
24 #include <QPainter>
25 #include <QPixmap>
26 #include <QStyle>
27 
28 using namespace MessageList::Core;
29 
30 static const int gGroupHeaderOuterVerticalMargin = 1;
31 static const int gGroupHeaderOuterHorizontalMargin = 1;
32 static const int gGroupHeaderInnerVerticalMargin = 1;
33 static const int gGroupHeaderInnerHorizontalMargin = 1;
34 static const int gMessageVerticalMargin = 2;
35 static const int gMessageHorizontalMargin = 2;
36 static const int gHorizontalItemSpacing = 2;
37 
ThemeDelegate(QAbstractItemView * parent)38 ThemeDelegate::ThemeDelegate(QAbstractItemView *parent)
39     : QStyledItemDelegate(parent)
40     , mItemView(parent)
41 {
42 }
43 
~ThemeDelegate()44 ThemeDelegate::~ThemeDelegate()
45 {
46 }
47 
setTheme(const Theme * theme)48 void ThemeDelegate::setTheme(const Theme *theme)
49 {
50     mTheme = theme;
51 
52     if (!mTheme) {
53         return; // hum
54     }
55 
56     // Rebuild the group header background color cache
57     switch (mTheme->groupHeaderBackgroundMode()) {
58     case Theme::Transparent:
59         mGroupHeaderBackgroundColor = QColor(); // invalid
60         break;
61     case Theme::CustomColor:
62         mGroupHeaderBackgroundColor = mTheme->groupHeaderBackgroundColor();
63         break;
64     case Theme::AutoColor: {
65         QPalette pal = mItemView->palette();
66         QColor txt = pal.color(QPalette::Normal, QPalette::Text);
67         QColor bck = pal.color(QPalette::Normal, QPalette::Base);
68         mGroupHeaderBackgroundColor = QColor((txt.red() + (bck.red() * 3)) / 4, (txt.green() + (bck.green() * 3)) / 4, (txt.blue() + (bck.blue() * 3)) / 4);
69         break;
70     }
71     }
72 
73     generalFontChanged();
74 
75     mItemView->reset();
76 }
77 
78 enum FontType {
79     Normal,
80     Bold,
81     Italic,
82     BoldItalic,
83 
84     FontTypesCount
85 };
86 
87 static QFont sFontCache[FontTypesCount];
88 static QFontMetrics sFontMetricsCache[FontTypesCount] = {QFontMetrics(QFont()), QFontMetrics(QFont()), QFontMetrics(QFont()), QFontMetrics(QFont())};
89 static int sFontHeightCache = 0;
90 
cachedFontMetrics(const Theme::ContentItem * ci)91 static inline const QFontMetrics &cachedFontMetrics(const Theme::ContentItem *ci)
92 {
93     return (!ci->isBold() && !ci->isItalic()) ? sFontMetricsCache[Normal]
94         : (ci->isBold() && !ci->isItalic())   ? sFontMetricsCache[Bold]
95         : (!ci->isBold() && ci->isItalic())   ? sFontMetricsCache[Italic]
96                                               : sFontMetricsCache[BoldItalic];
97 }
98 
cachedFont(const Theme::ContentItem * ci)99 static inline const QFont &cachedFont(const Theme::ContentItem *ci)
100 {
101     return (!ci->isBold() && !ci->isItalic()) ? sFontCache[Normal]
102         : (ci->isBold() && !ci->isItalic())   ? sFontCache[Bold]
103         : (!ci->isBold() && ci->isItalic())   ? sFontCache[Italic]
104                                               : sFontCache[BoldItalic];
105 }
106 
cachedFont(const Theme::ContentItem * ci,const Item * i)107 static inline const QFont &cachedFont(const Theme::ContentItem *ci, const Item *i)
108 {
109     if (i->type() != Item::Message) {
110         return cachedFont(ci);
111     }
112 
113     const auto mi = static_cast<const MessageItem *>(i);
114     const bool bold = ci->isBold() || mi->isBold();
115     const bool italic = ci->isItalic() || mi->isItalic();
116     return (!bold && !italic) ? sFontCache[Normal] : (bold && !italic) ? sFontCache[Bold] : (!bold && italic) ? sFontCache[Italic] : sFontCache[BoldItalic];
117 }
118 
paint_right_aligned_elided_text(const QString & text,Theme::ContentItem * ci,QPainter * painter,int & left,int top,int & right,Qt::LayoutDirection layoutDir,const QFont & font)119 static inline void paint_right_aligned_elided_text(const QString &text,
120                                                    Theme::ContentItem *ci,
121                                                    QPainter *painter,
122                                                    int &left,
123                                                    int top,
124                                                    int &right,
125                                                    Qt::LayoutDirection layoutDir,
126                                                    const QFont &font)
127 {
128     painter->setFont(font);
129     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
130     const int w = right - left;
131     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideLeft : Qt::ElideRight, w);
132     const QRect rct(left, top, w, sFontHeightCache);
133     QRect outRct;
134 
135     if (ci->softenByBlending()) {
136         qreal oldOpacity = painter->opacity();
137         painter->setOpacity(0.6);
138         painter->drawText(rct, Qt::AlignTop | Qt::AlignRight | Qt::TextSingleLine, elidedText, &outRct);
139         painter->setOpacity(oldOpacity);
140     } else {
141         painter->drawText(rct, Qt::AlignTop | Qt::AlignRight | Qt::TextSingleLine, elidedText, &outRct);
142     }
143     if (layoutDir == Qt::LeftToRight) {
144         right -= outRct.width() + gHorizontalItemSpacing;
145     } else {
146         left += outRct.width() + gHorizontalItemSpacing;
147     }
148 }
149 
compute_bounding_rect_for_right_aligned_elided_text(const QString & text,Theme::ContentItem * ci,int & left,int top,int & right,QRect & outRect,Qt::LayoutDirection layoutDir,const QFont & font)150 static inline void compute_bounding_rect_for_right_aligned_elided_text(const QString &text,
151                                                                        Theme::ContentItem *ci,
152                                                                        int &left,
153                                                                        int top,
154                                                                        int &right,
155                                                                        QRect &outRect,
156                                                                        Qt::LayoutDirection layoutDir,
157                                                                        const QFont &font)
158 {
159     Q_UNUSED(font)
160     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
161     const int w = right - left;
162     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideLeft : Qt::ElideRight, w);
163     const QRect rct(left, top, w, sFontHeightCache);
164     const Qt::AlignmentFlag af = layoutDir == Qt::LeftToRight ? Qt::AlignRight : Qt::AlignLeft;
165     outRect = fontMetrics.boundingRect(rct, Qt::AlignTop | af | Qt::TextSingleLine, elidedText);
166     if (layoutDir == Qt::LeftToRight) {
167         right -= outRect.width() + gHorizontalItemSpacing;
168     } else {
169         left += outRect.width() + gHorizontalItemSpacing;
170     }
171 }
172 
paint_left_aligned_elided_text(const QString & text,Theme::ContentItem * ci,QPainter * painter,int & left,int top,int & right,Qt::LayoutDirection layoutDir,const QFont & font)173 static inline void paint_left_aligned_elided_text(const QString &text,
174                                                   Theme::ContentItem *ci,
175                                                   QPainter *painter,
176                                                   int &left,
177                                                   int top,
178                                                   int &right,
179                                                   Qt::LayoutDirection layoutDir,
180                                                   const QFont &font)
181 {
182     painter->setFont(font);
183     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
184     const int w = right - left;
185     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideRight : Qt::ElideLeft, w);
186     const QRect rct(left, top, w, sFontHeightCache);
187     QRect outRct;
188     if (ci->softenByBlending()) {
189         qreal oldOpacity = painter->opacity();
190         painter->setOpacity(0.6);
191         painter->drawText(rct, Qt::AlignTop | Qt::AlignLeft | Qt::TextSingleLine, elidedText, &outRct);
192         painter->setOpacity(oldOpacity);
193     } else {
194         painter->drawText(rct, Qt::AlignTop | Qt::AlignLeft | Qt::TextSingleLine, elidedText, &outRct);
195     }
196     if (layoutDir == Qt::LeftToRight) {
197         left += outRct.width() + gHorizontalItemSpacing;
198     } else {
199         right -= outRct.width() + gHorizontalItemSpacing;
200     }
201 }
202 
compute_bounding_rect_for_left_aligned_elided_text(const QString & text,Theme::ContentItem * ci,int & left,int top,int & right,QRect & outRect,Qt::LayoutDirection layoutDir,const QFont & font)203 static inline void compute_bounding_rect_for_left_aligned_elided_text(const QString &text,
204                                                                       Theme::ContentItem *ci,
205                                                                       int &left,
206                                                                       int top,
207                                                                       int &right,
208                                                                       QRect &outRect,
209                                                                       Qt::LayoutDirection layoutDir,
210                                                                       const QFont &font)
211 {
212     Q_UNUSED(font)
213     const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
214     const int w = right - left;
215     const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideRight : Qt::ElideLeft, w);
216     const QRect rct(left, top, w, sFontHeightCache);
217     const Qt::AlignmentFlag af = layoutDir == Qt::LeftToRight ? Qt::AlignLeft : Qt::AlignRight;
218     outRect = fontMetrics.boundingRect(rct, Qt::AlignTop | af | Qt::TextSingleLine, elidedText);
219     if (layoutDir == Qt::LeftToRight) {
220         left += outRect.width() + gHorizontalItemSpacing;
221     } else {
222         right -= outRect.width() + gHorizontalItemSpacing;
223     }
224 }
225 
get_read_state_icon(const Theme * theme,Item * item)226 static inline const QPixmap *get_read_state_icon(const Theme *theme, Item *item)
227 {
228     if (item->status().isQueued()) {
229         return theme->pixmap(Theme::IconQueued);
230     } else if (item->status().isSent()) {
231         return theme->pixmap(Theme::IconSent);
232     } else if (item->status().isRead()) {
233         return theme->pixmap(Theme::IconRead);
234     } else if (!item->status().isRead()) {
235         return theme->pixmap(Theme::IconUnread);
236     } else if (item->status().isDeleted()) {
237         return theme->pixmap(Theme::IconDeleted);
238     }
239 
240     // Uhm... should never happen.. but fallback to "read"...
241     return theme->pixmap(Theme::IconRead);
242 }
243 
get_combined_read_replied_state_icon(const Theme * theme,MessageItem * messageItem)244 static inline const QPixmap *get_combined_read_replied_state_icon(const Theme *theme, MessageItem *messageItem)
245 {
246     if (messageItem->status().isReplied()) {
247         if (messageItem->status().isForwarded()) {
248             return theme->pixmap(Theme::IconRepliedAndForwarded);
249         }
250         return theme->pixmap(Theme::IconReplied);
251     }
252     if (messageItem->status().isForwarded()) {
253         return theme->pixmap(Theme::IconForwarded);
254     }
255 
256     return get_read_state_icon(theme, messageItem);
257 }
258 
get_encryption_state_icon(const Theme * theme,MessageItem * messageItem,bool * treatAsEnabled)259 static inline const QPixmap *get_encryption_state_icon(const Theme *theme, MessageItem *messageItem, bool *treatAsEnabled)
260 {
261     switch (messageItem->encryptionState()) {
262     case MessageItem::FullyEncrypted:
263         *treatAsEnabled = true;
264         return theme->pixmap(Theme::IconFullyEncrypted);
265     case MessageItem::PartiallyEncrypted:
266         *treatAsEnabled = true;
267         return theme->pixmap(Theme::IconPartiallyEncrypted);
268     case MessageItem::EncryptionStateUnknown:
269         *treatAsEnabled = false;
270         return theme->pixmap(Theme::IconUndefinedEncrypted);
271     case MessageItem::NotEncrypted:
272         *treatAsEnabled = false;
273         return theme->pixmap(Theme::IconNotEncrypted);
274     default:
275         // should never happen
276         Q_ASSERT(false);
277         break;
278     }
279 
280     *treatAsEnabled = false;
281     return theme->pixmap(Theme::IconUndefinedEncrypted);
282 }
283 
get_signature_state_icon(const Theme * theme,MessageItem * messageItem,bool * treatAsEnabled)284 static inline const QPixmap *get_signature_state_icon(const Theme *theme, MessageItem *messageItem, bool *treatAsEnabled)
285 {
286     switch (messageItem->signatureState()) {
287     case MessageItem::FullySigned:
288         *treatAsEnabled = true;
289         return theme->pixmap(Theme::IconFullySigned);
290     case MessageItem::PartiallySigned:
291         *treatAsEnabled = true;
292         return theme->pixmap(Theme::IconPartiallySigned);
293     case MessageItem::SignatureStateUnknown:
294         *treatAsEnabled = false;
295         return theme->pixmap(Theme::IconUndefinedSigned);
296     case MessageItem::NotSigned:
297         *treatAsEnabled = false;
298         return theme->pixmap(Theme::IconNotSigned);
299     default:
300         // should never happen
301         Q_ASSERT(false);
302         break;
303     }
304 
305     *treatAsEnabled = false;
306     return theme->pixmap(Theme::IconUndefinedSigned);
307 }
308 
get_replied_state_icon(const Theme * theme,MessageItem * messageItem)309 static inline const QPixmap *get_replied_state_icon(const Theme *theme, MessageItem *messageItem)
310 {
311     if (messageItem->status().isReplied()) {
312         if (messageItem->status().isForwarded()) {
313             return theme->pixmap(Theme::IconRepliedAndForwarded);
314         }
315         return theme->pixmap(Theme::IconReplied);
316     }
317     if (messageItem->status().isForwarded()) {
318         return theme->pixmap(Theme::IconForwarded);
319     }
320 
321     return nullptr;
322 }
323 
get_spam_ham_state_icon(const Theme * theme,MessageItem * messageItem)324 static inline const QPixmap *get_spam_ham_state_icon(const Theme *theme, MessageItem *messageItem)
325 {
326     if (messageItem->status().isSpam()) {
327         return theme->pixmap(Theme::IconSpam);
328     } else if (messageItem->status().isHam()) {
329         return theme->pixmap(Theme::IconHam);
330     }
331     return nullptr;
332 }
333 
get_watched_ignored_state_icon(const Theme * theme,MessageItem * messageItem)334 static inline const QPixmap *get_watched_ignored_state_icon(const Theme *theme, MessageItem *messageItem)
335 {
336     if (messageItem->status().isIgnored()) {
337         return theme->pixmap(Theme::IconIgnored);
338     } else if (messageItem->status().isWatched()) {
339         return theme->pixmap(Theme::IconWatched);
340     }
341     return nullptr;
342 }
343 
paint_vertical_line(QPainter * painter,int & left,int top,int & right,int bottom,bool alignOnRight)344 static inline void paint_vertical_line(QPainter *painter, int &left, int top, int &right, int bottom, bool alignOnRight)
345 {
346     if (alignOnRight) {
347         right -= 1;
348         if (right < 0) {
349             return;
350         }
351         painter->drawLine(right, top, right, bottom);
352         right -= 2;
353         right -= gHorizontalItemSpacing;
354     } else {
355         left += 1;
356         if (left > right) {
357             return;
358         }
359         painter->drawLine(left, top, left, bottom);
360         left += 2 + gHorizontalItemSpacing;
361     }
362 }
363 
compute_bounding_rect_for_vertical_line(int & left,int top,int & right,int bottom,QRect & outRect,bool alignOnRight)364 static inline void compute_bounding_rect_for_vertical_line(int &left, int top, int &right, int bottom, QRect &outRect, bool alignOnRight)
365 {
366     if (alignOnRight) {
367         right -= 3;
368         outRect = QRect(right, top, 3, bottom - top);
369         right -= gHorizontalItemSpacing;
370     } else {
371         outRect = QRect(left, top, 3, bottom - top);
372         left += 3 + gHorizontalItemSpacing;
373     }
374 }
375 
paint_horizontal_spacer(int & left,int,int & right,int,bool alignOnRight)376 static inline void paint_horizontal_spacer(int &left, int, int &right, int, bool alignOnRight)
377 {
378     if (alignOnRight) {
379         right -= 3 + gHorizontalItemSpacing;
380     } else {
381         left += 3 + gHorizontalItemSpacing;
382     }
383 }
384 
compute_bounding_rect_for_horizontal_spacer(int & left,int top,int & right,int bottom,QRect & outRect,bool alignOnRight)385 static inline void compute_bounding_rect_for_horizontal_spacer(int &left, int top, int &right, int bottom, QRect &outRect, bool alignOnRight)
386 {
387     if (alignOnRight) {
388         right -= 3;
389         outRect = QRect(right, top, 3, bottom - top);
390         right -= gHorizontalItemSpacing;
391     } else {
392         outRect = QRect(left, top, 3, bottom - top);
393         left += 3 + gHorizontalItemSpacing;
394     }
395 }
396 
397 static inline void
paint_permanent_icon(const QPixmap * pix,Theme::ContentItem *,QPainter * painter,int & left,int top,int & right,bool alignOnRight,int iconSize)398 paint_permanent_icon(const QPixmap *pix, Theme::ContentItem *, QPainter *painter, int &left, int top, int &right, bool alignOnRight, int iconSize)
399 {
400     if (alignOnRight) {
401         right -= iconSize; // this icon is always present
402         if (right < 0) {
403             return;
404         }
405         painter->drawPixmap(right, top, iconSize, iconSize, *pix);
406         right -= gHorizontalItemSpacing;
407     } else {
408         if (left > (right - iconSize)) {
409             return;
410         }
411         painter->drawPixmap(left, top, iconSize, iconSize, *pix);
412         left += iconSize + gHorizontalItemSpacing;
413     }
414 }
415 
416 static inline void
compute_bounding_rect_for_permanent_icon(Theme::ContentItem *,int & left,int top,int & right,QRect & outRect,bool alignOnRight,int iconSize)417 compute_bounding_rect_for_permanent_icon(Theme::ContentItem *, int &left, int top, int &right, QRect &outRect, bool alignOnRight, int iconSize)
418 {
419     if (alignOnRight) {
420         right -= iconSize; // this icon is always present
421         outRect = QRect(right, top, iconSize, iconSize);
422         right -= gHorizontalItemSpacing;
423     } else {
424         outRect = QRect(left, top, iconSize, iconSize);
425         left += iconSize + gHorizontalItemSpacing;
426     }
427 }
428 
paint_boolean_state_icon(bool enabled,const QPixmap * pix,Theme::ContentItem * ci,QPainter * painter,int & left,int top,int & right,bool alignOnRight,int iconSize)429 static inline void paint_boolean_state_icon(bool enabled,
430                                             const QPixmap *pix,
431                                             Theme::ContentItem *ci,
432                                             QPainter *painter,
433                                             int &left,
434                                             int top,
435                                             int &right,
436                                             bool alignOnRight,
437                                             int iconSize)
438 {
439     if (enabled) {
440         paint_permanent_icon(pix, ci, painter, left, top, right, alignOnRight, iconSize);
441         return;
442     }
443 
444     // off -> icon disabled
445     if (ci->hideWhenDisabled()) {
446         return; // doesn't even take space
447     }
448 
449     if (ci->softenByBlendingWhenDisabled()) {
450         // still paint, but very soft
451         qreal oldOpacity = painter->opacity();
452         painter->setOpacity(0.1);
453         paint_permanent_icon(pix, ci, painter, left, top, right, alignOnRight, iconSize);
454         painter->setOpacity(oldOpacity);
455         return;
456     }
457 
458     // just takes space
459     if (alignOnRight) {
460         right -= iconSize + gHorizontalItemSpacing;
461     } else {
462         left += iconSize + gHorizontalItemSpacing;
463     }
464 }
465 
compute_bounding_rect_for_boolean_state_icon(bool enabled,Theme::ContentItem * ci,int & left,int top,int & right,QRect & outRect,bool alignOnRight,int iconSize)466 static inline void compute_bounding_rect_for_boolean_state_icon(bool enabled,
467                                                                 Theme::ContentItem *ci,
468                                                                 int &left,
469                                                                 int top,
470                                                                 int &right,
471                                                                 QRect &outRect,
472                                                                 bool alignOnRight,
473                                                                 int iconSize)
474 {
475     if ((!enabled) && ci->hideWhenDisabled()) {
476         outRect = QRect();
477         return; // doesn't even take space
478     }
479 
480     compute_bounding_rect_for_permanent_icon(ci, left, top, right, outRect, alignOnRight, iconSize);
481 }
482 
paint_tag_list(const QList<MessageItem::Tag * > & tagList,QPainter * painter,int & left,int top,int & right,bool alignOnRight,int iconSize)483 static inline void paint_tag_list(const QList<MessageItem::Tag *> &tagList, QPainter *painter, int &left, int top, int &right, bool alignOnRight, int iconSize)
484 {
485     if (alignOnRight) {
486         for (const MessageItem::Tag *tag : tagList) {
487             right -= iconSize; // this icon is always present
488             if (right < 0) {
489                 return;
490             }
491             painter->drawPixmap(right, top, iconSize, iconSize, tag->pixmap());
492             right -= gHorizontalItemSpacing;
493         }
494     } else {
495         for (const MessageItem::Tag *tag : tagList) {
496             if (left > right - iconSize) {
497                 return;
498             }
499             painter->drawPixmap(left, top, iconSize, iconSize, tag->pixmap());
500             left += iconSize + gHorizontalItemSpacing;
501         }
502     }
503 }
504 
505 static inline void
compute_bounding_rect_for_tag_list(const QList<MessageItem::Tag * > & tagList,int & left,int top,int & right,QRect & outRect,bool alignOnRight,int iconSize)506 compute_bounding_rect_for_tag_list(const QList<MessageItem::Tag *> &tagList, int &left, int top, int &right, QRect &outRect, bool alignOnRight, int iconSize)
507 {
508     int width = tagList.count() * (iconSize + gHorizontalItemSpacing);
509     if (alignOnRight) {
510         right -= width;
511         outRect = QRect(right, top, width, iconSize);
512     } else {
513         outRect = QRect(left, top, width, iconSize);
514         left += width;
515     }
516 }
517 
compute_size_hint_for_item(Theme::ContentItem * ci,int & maxh,int & totalw,int iconSize,const Item * item)518 static inline void compute_size_hint_for_item(Theme::ContentItem *ci, int &maxh, int &totalw, int iconSize, const Item *item)
519 {
520     Q_UNUSED(item)
521     if (ci->displaysText()) {
522         if (sFontHeightCache > maxh) {
523             maxh = sFontHeightCache;
524         }
525         totalw += ci->displaysLongText() ? 128 : 64;
526         return;
527     }
528 
529     if (ci->isIcon()) {
530         totalw += iconSize + gHorizontalItemSpacing;
531         if (maxh < iconSize) {
532             maxh = iconSize;
533         }
534         return;
535     }
536 
537     if (ci->isSpacer()) {
538         if (18 > maxh) {
539             maxh = 18;
540         }
541         totalw += 3 + gHorizontalItemSpacing;
542         return;
543     }
544 
545     // should never be reached
546     if (18 > maxh) {
547         maxh = 18;
548     }
549     totalw += gHorizontalItemSpacing;
550 }
551 
compute_size_hint_for_row(const Theme::Row * r,int iconSize,const Item * item)552 static inline QSize compute_size_hint_for_row(const Theme::Row *r, int iconSize, const Item *item)
553 {
554     int maxh = 8; // at least 8 pixels for a pixmap
555     int totalw = 0;
556 
557     // right aligned stuff first
558     auto items = r->rightItems();
559     for (const auto it : std::as_const(items)) {
560         compute_size_hint_for_item(const_cast<Theme::ContentItem *>(it), maxh, totalw, iconSize, item);
561     }
562 
563     // then left aligned stuff
564     items = r->leftItems();
565     for (const auto it : std::as_const(items)) {
566         compute_size_hint_for_item(const_cast<Theme::ContentItem *>(it), maxh, totalw, iconSize, item);
567     }
568 
569     return QSize(totalw, maxh);
570 }
571 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const572 void ThemeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
573 {
574     if (!index.isValid()) {
575         return; // bleah
576     }
577 
578     Item *item = itemFromIndex(index);
579     if (!item) {
580         return; // hm...
581     }
582 
583     QStyleOptionViewItem opt = option;
584     initStyleOption(&opt, index);
585 
586     opt.text.clear(); // draw no text for me, please.. I'll do it in a while
587 
588     // Set background color of control if necessary
589     if (item->type() == Item::Message) {
590         auto msgItem = static_cast<MessageItem *>(item);
591         if (msgItem->backgroundColor().isValid()) {
592             opt.backgroundBrush = QBrush(msgItem->backgroundColor());
593         }
594     }
595 
596     QStyle *style = mItemView->style();
597     style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
598 
599     if (!mTheme) {
600         return; // hm hm...
601     }
602 
603     const Theme::Column *skcolumn = mTheme->column(index.column());
604     if (!skcolumn) {
605         return; // bleah
606     }
607 
608     const QList<MessageList::Core::Theme::Row *> *rows;
609 
610     MessageItem *messageItem = nullptr;
611     GroupHeaderItem *groupHeaderItem = nullptr;
612 
613     int top = opt.rect.top();
614     int right = opt.rect.left() + opt.rect.width(); // don't use opt.rect.right() since it's screwed
615     int left = opt.rect.left();
616 
617     // Storing the changed members one by one is faster than saving the painter state
618     QFont oldFont = painter->font();
619     QPen oldPen = painter->pen();
620     qreal oldOpacity = painter->opacity();
621 
622     QPen defaultPen;
623     bool usingNonDefaultTextColor = false;
624 
625     switch (item->type()) {
626     case Item::Message:
627         rows = &(skcolumn->messageRows());
628         messageItem = static_cast<MessageItem *>(item);
629 
630         if ((!(opt.state & QStyle::State_Enabled)) || messageItem->aboutToBeRemoved() || (!messageItem->isValid())) {
631             painter->setOpacity(0.5);
632             defaultPen = QPen(opt.palette.brush(QPalette::Disabled, QPalette::Text), 0);
633         } else {
634             QPalette::ColorGroup cg;
635 
636             if (opt.state & QStyle::State_Active) {
637                 cg = QPalette::Normal;
638             } else {
639                 cg = QPalette::Inactive;
640             }
641 
642             if (opt.state & QStyle::State_Selected) {
643                 defaultPen = QPen(opt.palette.brush(cg, QPalette::HighlightedText), 0);
644             } else {
645                 if (messageItem->textColor().isValid()) {
646                     usingNonDefaultTextColor = true;
647                     defaultPen = QPen(messageItem->textColor(), 0);
648                 } else {
649                     defaultPen = QPen(opt.palette.brush(cg, QPalette::Text), 0);
650                 }
651             }
652         }
653 
654         top += gMessageVerticalMargin;
655         right -= gMessageHorizontalMargin;
656         left += gMessageHorizontalMargin;
657         break;
658     case Item::GroupHeader: {
659         rows = &(skcolumn->groupHeaderRows());
660         groupHeaderItem = static_cast<GroupHeaderItem *>(item);
661 
662         QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
663 
664         if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
665             cg = QPalette::Inactive;
666         }
667 
668         QPalette::ColorRole cr;
669 
670         top += gGroupHeaderOuterVerticalMargin;
671         right -= gGroupHeaderOuterHorizontalMargin;
672         left += gGroupHeaderOuterHorizontalMargin;
673 
674         switch (mTheme->groupHeaderBackgroundMode()) {
675         case Theme::Transparent:
676             cr = (opt.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text;
677             defaultPen = QPen(opt.palette.brush(cg, cr), 0);
678             break;
679         case Theme::AutoColor:
680         case Theme::CustomColor:
681             switch (mTheme->groupHeaderBackgroundStyle()) {
682             case Theme::PlainRect:
683                 painter->fillRect(QRect(left, top, right - left, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
684                                   QBrush(mGroupHeaderBackgroundColor));
685                 break;
686             case Theme::PlainJoinedRect: {
687                 int rleft = (opt.viewItemPosition == QStyleOptionViewItem::Beginning) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
688                     ? left
689                     : opt.rect.left();
690                 int rright = (opt.viewItemPosition == QStyleOptionViewItem::End) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
691                     ? right
692                     : opt.rect.left() + opt.rect.width();
693                 painter->fillRect(QRect(rleft, top, rright - rleft, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
694                                   QBrush(mGroupHeaderBackgroundColor));
695                 break;
696             }
697             case Theme::RoundedJoinedRect:
698                 if (opt.viewItemPosition == QStyleOptionViewItem::Middle) {
699                     painter->fillRect(QRect(opt.rect.left(), top, opt.rect.width(), opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
700                                       QBrush(mGroupHeaderBackgroundColor));
701                     break; // don't fall through
702                 }
703                 if (opt.viewItemPosition == QStyleOptionViewItem::Beginning) {
704                     painter->fillRect(QRect(opt.rect.left() + opt.rect.width() - 10, top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
705                                       QBrush(mGroupHeaderBackgroundColor));
706                 } else if (opt.viewItemPosition == QStyleOptionViewItem::End) {
707                     painter->fillRect(QRect(opt.rect.left(), top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
708                                       QBrush(mGroupHeaderBackgroundColor));
709                 }
710                 // fall through anyway
711                 Q_FALLTHROUGH();
712             case Theme::RoundedRect: {
713                 painter->setPen(Qt::NoPen);
714                 bool hadAntialiasing = painter->renderHints() & QPainter::Antialiasing;
715                 if (!hadAntialiasing) {
716                     painter->setRenderHint(QPainter::Antialiasing, true);
717                 }
718                 painter->setBrush(QBrush(mGroupHeaderBackgroundColor));
719                 painter->setBackgroundMode(Qt::OpaqueMode);
720                 int w = right - left;
721                 if (w > 0) {
722                     painter->drawRoundedRect(QRect(left, top, w, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)), 4.0, 4.0);
723                 }
724                 if (!hadAntialiasing) {
725                     painter->setRenderHint(QPainter::Antialiasing, false);
726                 }
727                 painter->setBackgroundMode(Qt::TransparentMode);
728                 break;
729             }
730             case Theme::GradientJoinedRect: {
731                 // FIXME: Could cache this brush
732                 QLinearGradient gradient(0, top, 0, top + opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
733                 gradient.setColorAt(0.0, KColorScheme::shade(mGroupHeaderBackgroundColor, KColorScheme::LightShade, 0.3));
734                 gradient.setColorAt(1.0, mGroupHeaderBackgroundColor);
735                 if (opt.viewItemPosition == QStyleOptionViewItem::Middle) {
736                     painter->fillRect(QRect(opt.rect.left(), top, opt.rect.width(), opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
737                                       QBrush(gradient));
738                     break; // don't fall through
739                 }
740                 if (opt.viewItemPosition == QStyleOptionViewItem::Beginning) {
741                     painter->fillRect(QRect(opt.rect.left() + opt.rect.width() - 10, top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
742                                       QBrush(gradient));
743                 } else if (opt.viewItemPosition == QStyleOptionViewItem::End) {
744                     painter->fillRect(QRect(opt.rect.left(), top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)), QBrush(gradient));
745                 }
746                 // fall through anyway
747                 Q_FALLTHROUGH();
748             }
749             case Theme::GradientRect: {
750                 // FIXME: Could cache this brush
751                 QLinearGradient gradient(0, top, 0, top + opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
752                 gradient.setColorAt(0.0, KColorScheme::shade(mGroupHeaderBackgroundColor, KColorScheme::LightShade, 0.3));
753                 gradient.setColorAt(1.0, mGroupHeaderBackgroundColor);
754                 painter->setPen(Qt::NoPen);
755                 bool hadAntialiasing = painter->renderHints() & QPainter::Antialiasing;
756                 if (!hadAntialiasing) {
757                     painter->setRenderHint(QPainter::Antialiasing, true);
758                 }
759                 painter->setBrush(QBrush(gradient));
760                 painter->setBackgroundMode(Qt::OpaqueMode);
761                 int w = right - left;
762                 if (w > 0) {
763                     painter->drawRoundedRect(QRect(left, top, w, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)), 4.0, 4.0);
764                 }
765                 if (!hadAntialiasing) {
766                     painter->setRenderHint(QPainter::Antialiasing, false);
767                 }
768                 painter->setBackgroundMode(Qt::TransparentMode);
769                 break;
770             }
771             case Theme::StyledRect:
772                 // oxygen, for instance, has a nice graphics for selected items
773                 opt.rect = QRect(left, top, right - left, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
774                 opt.state |= QStyle::State_Selected;
775                 opt.viewItemPosition = QStyleOptionViewItem::OnlyOne;
776                 opt.palette.setColor(cg, QPalette::Highlight, mGroupHeaderBackgroundColor);
777                 style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
778                 break;
779             case Theme::StyledJoinedRect: {
780                 int rleft = (opt.viewItemPosition == QStyleOptionViewItem::Beginning) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
781                     ? left
782                     : opt.rect.left();
783                 int rright = (opt.viewItemPosition == QStyleOptionViewItem::End) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne)
784                     ? right
785                     : opt.rect.left() + opt.rect.width();
786                 opt.rect = QRect(rleft, top, rright - rleft, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
787                 opt.state |= QStyle::State_Selected;
788                 opt.palette.setColor(cg, QPalette::Highlight, mGroupHeaderBackgroundColor);
789                 style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
790                 break;
791             }
792             }
793 
794             defaultPen = QPen(opt.palette.brush(cg, QPalette::Text), 0);
795             break;
796         }
797         top += gGroupHeaderInnerVerticalMargin;
798         right -= gGroupHeaderInnerHorizontalMargin;
799         left += gGroupHeaderInnerHorizontalMargin;
800         break;
801     }
802     default:
803         Q_ASSERT(false);
804         return; // bug
805     }
806 
807     Qt::LayoutDirection layoutDir = mItemView->layoutDirection();
808 
809     for (const auto row : std::as_const(*rows)) {
810         QSize rowSizeHint = compute_size_hint_for_row(row, mTheme->iconSize(), item);
811 
812         int bottom = top + rowSizeHint.height();
813 
814         // paint right aligned stuff first
815         int r = right;
816         int l = left;
817         const auto rightItems = row->rightItems();
818         for (const auto itemit : rightItems) {
819             auto ci = const_cast<Theme::ContentItem *>(itemit);
820 
821             if (ci->canUseCustomColor()) {
822                 if (ci->useCustomColor() && (!(opt.state & QStyle::State_Selected))) {
823                     if (usingNonDefaultTextColor) {
824                         // merge the colors
825                         QColor nonDefault = defaultPen.color();
826                         QColor custom = ci->customColor();
827                         QColor merged((nonDefault.red() + custom.red()) >> 1,
828                                       (nonDefault.green() + custom.green()) >> 1,
829                                       (nonDefault.blue() + custom.blue()) >> 1);
830                         painter->setPen(QPen(merged));
831                     } else {
832                         painter->setPen(QPen(ci->customColor()));
833                     }
834                 } else {
835                     painter->setPen(defaultPen);
836                 }
837             } // otherwise setting a pen is useless at this time
838 
839             const QFont &font = cachedFont(ci, item);
840 
841             switch (ci->type()) {
842             case Theme::ContentItem::Subject:
843                 paint_right_aligned_elided_text(item->subject(), ci, painter, l, top, r, layoutDir, font);
844                 break;
845             case Theme::ContentItem::SenderOrReceiver:
846                 paint_right_aligned_elided_text(item->displaySenderOrReceiver(), ci, painter, l, top, r, layoutDir, font);
847                 break;
848             case Theme::ContentItem::Receiver:
849                 paint_right_aligned_elided_text(item->displayReceiver(), ci, painter, l, top, r, layoutDir, font);
850                 break;
851             case Theme::ContentItem::Sender:
852                 paint_right_aligned_elided_text(item->displaySender(), ci, painter, l, top, r, layoutDir, font);
853                 break;
854             case Theme::ContentItem::Date:
855                 paint_right_aligned_elided_text(item->formattedDate(), ci, painter, l, top, r, layoutDir, font);
856                 break;
857             case Theme::ContentItem::MostRecentDate:
858                 paint_right_aligned_elided_text(item->formattedMaxDate(), ci, painter, l, top, r, layoutDir, font);
859                 break;
860             case Theme::ContentItem::Size:
861                 paint_right_aligned_elided_text(item->formattedSize(), ci, painter, l, top, r, layoutDir, font);
862                 break;
863             case Theme::ContentItem::Folder:
864                 paint_right_aligned_elided_text(item->folder(), ci, painter, l, top, r, layoutDir, font);
865                 break;
866             case Theme::ContentItem::GroupHeaderLabel:
867                 if (groupHeaderItem) {
868                     paint_right_aligned_elided_text(groupHeaderItem->label(), ci, painter, l, top, r, layoutDir, font);
869                 }
870                 break;
871             case Theme::ContentItem::ReadStateIcon:
872                 paint_permanent_icon(get_read_state_icon(mTheme, item), ci, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
873                 break;
874             case Theme::ContentItem::CombinedReadRepliedStateIcon:
875                 if (messageItem) {
876                     paint_permanent_icon(get_combined_read_replied_state_icon(mTheme, messageItem),
877                                          ci,
878                                          painter,
879                                          l,
880                                          top,
881                                          r,
882                                          layoutDir == Qt::LeftToRight,
883                                          mTheme->iconSize());
884                 }
885                 break;
886             case Theme::ContentItem::ExpandedStateIcon: {
887                 const QPixmap *pix =
888                     item->childItemCount() > 0 ? mTheme->pixmap((option.state & QStyle::State_Open) ? Theme::IconShowLess : Theme::IconShowMore) : nullptr;
889                 paint_boolean_state_icon(pix != nullptr,
890                                          pix ? pix : mTheme->pixmap(Theme::IconShowMore),
891                                          ci,
892                                          painter,
893                                          l,
894                                          top,
895                                          r,
896                                          layoutDir == Qt::LeftToRight,
897                                          mTheme->iconSize());
898                 break;
899             }
900             case Theme::ContentItem::RepliedStateIcon:
901                 if (messageItem) {
902                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
903                     paint_boolean_state_icon(pix != nullptr,
904                                              pix ? pix : mTheme->pixmap(Theme::IconReplied),
905                                              ci,
906                                              painter,
907                                              l,
908                                              top,
909                                              r,
910                                              layoutDir == Qt::LeftToRight,
911                                              mTheme->iconSize());
912                 }
913                 break;
914             case Theme::ContentItem::EncryptionStateIcon:
915                 if (messageItem) {
916                     bool enabled;
917                     const QPixmap *pix = get_encryption_state_icon(mTheme, messageItem, &enabled);
918                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
919                 }
920                 break;
921             case Theme::ContentItem::SignatureStateIcon:
922                 if (messageItem) {
923                     bool enabled;
924                     const QPixmap *pix = get_signature_state_icon(mTheme, messageItem, &enabled);
925                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
926                 }
927                 break;
928             case Theme::ContentItem::SpamHamStateIcon:
929                 if (messageItem) {
930                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
931                     paint_boolean_state_icon(pix != nullptr,
932                                              pix ? pix : mTheme->pixmap(Theme::IconSpam),
933                                              ci,
934                                              painter,
935                                              l,
936                                              top,
937                                              r,
938                                              layoutDir == Qt::LeftToRight,
939                                              mTheme->iconSize());
940                 }
941                 break;
942             case Theme::ContentItem::WatchedIgnoredStateIcon:
943                 if (messageItem) {
944                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
945                     paint_boolean_state_icon(pix != nullptr,
946                                              pix ? pix : mTheme->pixmap(Theme::IconWatched),
947                                              ci,
948                                              painter,
949                                              l,
950                                              top,
951                                              r,
952                                              layoutDir == Qt::LeftToRight,
953                                              mTheme->iconSize());
954                 }
955                 break;
956             case Theme::ContentItem::AttachmentStateIcon:
957                 if (messageItem) {
958                     paint_boolean_state_icon(messageItem->status().hasAttachment(),
959                                              mTheme->pixmap(Theme::IconAttachment),
960                                              ci,
961                                              painter,
962                                              l,
963                                              top,
964                                              r,
965                                              layoutDir == Qt::LeftToRight,
966                                              mTheme->iconSize());
967                 }
968                 break;
969             case Theme::ContentItem::AnnotationIcon:
970                 if (messageItem) {
971                     paint_boolean_state_icon(messageItem->hasAnnotation(),
972                                              mTheme->pixmap(Theme::IconAnnotation),
973                                              ci,
974                                              painter,
975                                              l,
976                                              top,
977                                              r,
978                                              layoutDir == Qt::LeftToRight,
979                                              mTheme->iconSize());
980                 }
981                 break;
982             case Theme::ContentItem::InvitationIcon:
983                 if (messageItem) {
984                     paint_boolean_state_icon(messageItem->status().hasInvitation(),
985                                              mTheme->pixmap(Theme::IconInvitation),
986                                              ci,
987                                              painter,
988                                              l,
989                                              top,
990                                              r,
991                                              layoutDir == Qt::LeftToRight,
992                                              mTheme->iconSize());
993                 }
994                 break;
995             case Theme::ContentItem::ActionItemStateIcon:
996                 if (messageItem) {
997                     paint_boolean_state_icon(messageItem->status().isToAct(),
998                                              mTheme->pixmap(Theme::IconActionItem),
999                                              ci,
1000                                              painter,
1001                                              l,
1002                                              top,
1003                                              r,
1004                                              layoutDir == Qt::LeftToRight,
1005                                              mTheme->iconSize());
1006                 }
1007                 break;
1008             case Theme::ContentItem::ImportantStateIcon:
1009                 if (messageItem) {
1010                     paint_boolean_state_icon(messageItem->status().isImportant(),
1011                                              mTheme->pixmap(Theme::IconImportant),
1012                                              ci,
1013                                              painter,
1014                                              l,
1015                                              top,
1016                                              r,
1017                                              layoutDir == Qt::LeftToRight,
1018                                              mTheme->iconSize());
1019                 }
1020                 break;
1021             case Theme::ContentItem::VerticalLine:
1022                 paint_vertical_line(painter, l, top, r, bottom, layoutDir == Qt::LeftToRight);
1023                 break;
1024             case Theme::ContentItem::HorizontalSpacer:
1025                 paint_horizontal_spacer(l, top, r, bottom, layoutDir == Qt::LeftToRight);
1026                 break;
1027             case Theme::ContentItem::TagList:
1028                 if (messageItem) {
1029                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1030                     paint_tag_list(tagList, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1031                 }
1032                 break;
1033             }
1034         }
1035 
1036         // then paint left aligned stuff
1037         const auto leftItems = row->leftItems();
1038         for (const auto itemit : leftItems) {
1039             auto ci = const_cast<Theme::ContentItem *>(itemit);
1040 
1041             if (ci->canUseCustomColor()) {
1042                 if (ci->useCustomColor() && (!(opt.state & QStyle::State_Selected))) {
1043                     if (usingNonDefaultTextColor) {
1044                         // merge the colors
1045                         QColor nonDefault = defaultPen.color();
1046                         QColor custom = ci->customColor();
1047                         QColor merged((nonDefault.red() + custom.red()) >> 1,
1048                                       (nonDefault.green() + custom.green()) >> 1,
1049                                       (nonDefault.blue() + custom.blue()) >> 1);
1050                         painter->setPen(QPen(merged));
1051                     } else {
1052                         painter->setPen(QPen(ci->customColor()));
1053                     }
1054                 } else {
1055                     painter->setPen(defaultPen);
1056                 }
1057             } // otherwise setting a pen is useless at this time
1058 
1059             const QFont &font = cachedFont(ci, item);
1060 
1061             switch (ci->type()) {
1062             case Theme::ContentItem::Subject:
1063                 paint_left_aligned_elided_text(item->subject(), ci, painter, l, top, r, layoutDir, font);
1064                 break;
1065             case Theme::ContentItem::SenderOrReceiver:
1066                 paint_left_aligned_elided_text(item->displaySenderOrReceiver(), ci, painter, l, top, r, layoutDir, font);
1067                 break;
1068             case Theme::ContentItem::Receiver:
1069                 paint_left_aligned_elided_text(item->displayReceiver(), ci, painter, l, top, r, layoutDir, font);
1070                 break;
1071             case Theme::ContentItem::Sender:
1072                 paint_left_aligned_elided_text(item->displaySender(), ci, painter, l, top, r, layoutDir, font);
1073                 break;
1074             case Theme::ContentItem::Date:
1075                 paint_left_aligned_elided_text(item->formattedDate(), ci, painter, l, top, r, layoutDir, font);
1076                 break;
1077             case Theme::ContentItem::MostRecentDate:
1078                 paint_left_aligned_elided_text(item->formattedMaxDate(), ci, painter, l, top, r, layoutDir, font);
1079                 break;
1080             case Theme::ContentItem::Size:
1081                 paint_left_aligned_elided_text(item->formattedSize(), ci, painter, l, top, r, layoutDir, font);
1082                 break;
1083             case Theme::ContentItem::Folder:
1084                 paint_left_aligned_elided_text(item->folder(), ci, painter, l, top, r, layoutDir, font);
1085                 break;
1086             case Theme::ContentItem::GroupHeaderLabel:
1087                 if (groupHeaderItem) {
1088                     paint_left_aligned_elided_text(groupHeaderItem->label(), ci, painter, l, top, r, layoutDir, font);
1089                 }
1090                 break;
1091             case Theme::ContentItem::ReadStateIcon:
1092                 paint_permanent_icon(get_read_state_icon(mTheme, item), ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1093                 break;
1094             case Theme::ContentItem::CombinedReadRepliedStateIcon:
1095                 if (messageItem) {
1096                     paint_permanent_icon(get_combined_read_replied_state_icon(mTheme, messageItem),
1097                                          ci,
1098                                          painter,
1099                                          l,
1100                                          top,
1101                                          r,
1102                                          layoutDir != Qt::LeftToRight,
1103                                          mTheme->iconSize());
1104                 }
1105                 break;
1106             case Theme::ContentItem::ExpandedStateIcon: {
1107                 const QPixmap *pix =
1108                     item->childItemCount() > 0 ? mTheme->pixmap((option.state & QStyle::State_Open) ? Theme::IconShowLess : Theme::IconShowMore) : nullptr;
1109                 paint_boolean_state_icon(pix != nullptr,
1110                                          pix ? pix : mTheme->pixmap(Theme::IconShowMore),
1111                                          ci,
1112                                          painter,
1113                                          l,
1114                                          top,
1115                                          r,
1116                                          layoutDir != Qt::LeftToRight,
1117                                          mTheme->iconSize());
1118                 break;
1119             }
1120             case Theme::ContentItem::RepliedStateIcon:
1121                 if (messageItem) {
1122                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
1123                     paint_boolean_state_icon(pix != nullptr,
1124                                              pix ? pix : mTheme->pixmap(Theme::IconReplied),
1125                                              ci,
1126                                              painter,
1127                                              l,
1128                                              top,
1129                                              r,
1130                                              layoutDir != Qt::LeftToRight,
1131                                              mTheme->iconSize());
1132                 }
1133                 break;
1134             case Theme::ContentItem::EncryptionStateIcon:
1135                 if (messageItem) {
1136                     bool enabled;
1137                     const QPixmap *pix = get_encryption_state_icon(mTheme, messageItem, &enabled);
1138                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1139                 }
1140                 break;
1141             case Theme::ContentItem::SignatureStateIcon:
1142                 if (messageItem) {
1143                     bool enabled;
1144                     const QPixmap *pix = get_signature_state_icon(mTheme, messageItem, &enabled);
1145                     paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1146                 }
1147                 break;
1148             case Theme::ContentItem::SpamHamStateIcon:
1149                 if (messageItem) {
1150                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
1151                     paint_boolean_state_icon(pix != nullptr,
1152                                              pix ? pix : mTheme->pixmap(Theme::IconSpam),
1153                                              ci,
1154                                              painter,
1155                                              l,
1156                                              top,
1157                                              r,
1158                                              layoutDir != Qt::LeftToRight,
1159                                              mTheme->iconSize());
1160                 }
1161                 break;
1162             case Theme::ContentItem::WatchedIgnoredStateIcon:
1163                 if (messageItem) {
1164                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
1165                     paint_boolean_state_icon(pix != nullptr,
1166                                              pix ? pix : mTheme->pixmap(Theme::IconWatched),
1167                                              ci,
1168                                              painter,
1169                                              l,
1170                                              top,
1171                                              r,
1172                                              layoutDir != Qt::LeftToRight,
1173                                              mTheme->iconSize());
1174                 }
1175                 break;
1176             case Theme::ContentItem::AttachmentStateIcon:
1177                 if (messageItem) {
1178                     paint_boolean_state_icon(messageItem->status().hasAttachment(),
1179                                              mTheme->pixmap(Theme::IconAttachment),
1180                                              ci,
1181                                              painter,
1182                                              l,
1183                                              top,
1184                                              r,
1185                                              layoutDir != Qt::LeftToRight,
1186                                              mTheme->iconSize());
1187                 }
1188                 break;
1189             case Theme::ContentItem::AnnotationIcon:
1190                 if (messageItem) {
1191                     paint_boolean_state_icon(messageItem->hasAnnotation(),
1192                                              mTheme->pixmap(Theme::IconAnnotation),
1193                                              ci,
1194                                              painter,
1195                                              l,
1196                                              top,
1197                                              r,
1198                                              layoutDir != Qt::LeftToRight,
1199                                              mTheme->iconSize());
1200                 }
1201                 break;
1202             case Theme::ContentItem::InvitationIcon:
1203                 if (messageItem) {
1204                     paint_boolean_state_icon(messageItem->status().hasInvitation(),
1205                                              mTheme->pixmap(Theme::IconInvitation),
1206                                              ci,
1207                                              painter,
1208                                              l,
1209                                              top,
1210                                              r,
1211                                              layoutDir != Qt::LeftToRight,
1212                                              mTheme->iconSize());
1213                 }
1214                 break;
1215             case Theme::ContentItem::ActionItemStateIcon:
1216                 if (messageItem) {
1217                     paint_boolean_state_icon(messageItem->status().isToAct(),
1218                                              mTheme->pixmap(Theme::IconActionItem),
1219                                              ci,
1220                                              painter,
1221                                              l,
1222                                              top,
1223                                              r,
1224                                              layoutDir != Qt::LeftToRight,
1225                                              mTheme->iconSize());
1226                 }
1227                 break;
1228             case Theme::ContentItem::ImportantStateIcon:
1229                 if (messageItem) {
1230                     paint_boolean_state_icon(messageItem->status().isImportant(),
1231                                              mTheme->pixmap(Theme::IconImportant),
1232                                              ci,
1233                                              painter,
1234                                              l,
1235                                              top,
1236                                              r,
1237                                              layoutDir != Qt::LeftToRight,
1238                                              mTheme->iconSize());
1239                 }
1240                 break;
1241             case Theme::ContentItem::VerticalLine:
1242                 paint_vertical_line(painter, l, top, r, bottom, layoutDir != Qt::LeftToRight);
1243                 break;
1244             case Theme::ContentItem::HorizontalSpacer:
1245                 paint_horizontal_spacer(l, top, r, bottom, layoutDir != Qt::LeftToRight);
1246                 break;
1247             case Theme::ContentItem::TagList:
1248                 if (messageItem) {
1249                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1250                     paint_tag_list(tagList, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1251                 }
1252                 break;
1253             }
1254         }
1255 
1256         top = bottom;
1257     }
1258 
1259     painter->setFont(oldFont);
1260     painter->setPen(oldPen);
1261     painter->setOpacity(oldOpacity);
1262 }
1263 
hitTest(const QPoint & viewportPoint,bool exact)1264 bool ThemeDelegate::hitTest(const QPoint &viewportPoint, bool exact)
1265 {
1266     mHitItem = nullptr;
1267     mHitColumn = nullptr;
1268     mHitRow = nullptr;
1269     mHitContentItem = nullptr;
1270 
1271     if (!mTheme) {
1272         return false; // hm hm...
1273     }
1274 
1275     mHitIndex = mItemView->indexAt(viewportPoint);
1276 
1277     if (!mHitIndex.isValid()) {
1278         return false; // bleah
1279     }
1280 
1281     mHitItem = itemFromIndex(mHitIndex);
1282     if (!mHitItem) {
1283         return false; // hm...
1284     }
1285 
1286     mHitItemRect = mItemView->visualRect(mHitIndex);
1287 
1288     mHitColumn = mTheme->column(mHitIndex.column());
1289     if (!mHitColumn) {
1290         return false; // bleah
1291     }
1292 
1293     const QList<Theme::Row *> *rows; // I'd like to have it as reference, but gcc complains...
1294 
1295     MessageItem *messageItem = nullptr;
1296     GroupHeaderItem *groupHeaderItem = nullptr;
1297 
1298     int top = mHitItemRect.top();
1299     int right = mHitItemRect.right();
1300     int left = mHitItemRect.left();
1301 
1302     mHitRow = nullptr;
1303     mHitRowIndex = -1;
1304     mHitContentItem = nullptr;
1305 
1306     switch (mHitItem->type()) {
1307     case Item::Message:
1308         mHitRowIsMessageRow = true;
1309         rows = &(mHitColumn->messageRows());
1310         messageItem = static_cast<MessageItem *>(mHitItem);
1311         // FIXME: paint eventual background here
1312 
1313         top += gMessageVerticalMargin;
1314         right -= gMessageHorizontalMargin;
1315         left += gMessageHorizontalMargin;
1316         break;
1317     case Item::GroupHeader:
1318         mHitRowIsMessageRow = false;
1319         rows = &(mHitColumn->groupHeaderRows());
1320         groupHeaderItem = static_cast<GroupHeaderItem *>(mHitItem);
1321 
1322         top += gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin;
1323         right -= gGroupHeaderOuterHorizontalMargin + gGroupHeaderInnerHorizontalMargin;
1324         left += gGroupHeaderOuterHorizontalMargin + gGroupHeaderInnerHorizontalMargin;
1325         break;
1326     default:
1327         return false; // bug
1328         break;
1329     }
1330 
1331     int rowIdx = 0;
1332     int bestInexactDistance = 0xffffff;
1333     bool bestInexactItemRight = false;
1334     QRect bestInexactRect;
1335     const Theme::ContentItem *bestInexactContentItem = nullptr;
1336 
1337     Qt::LayoutDirection layoutDir = mItemView->layoutDirection();
1338 
1339     for (const auto row : std::as_const(*rows)) {
1340         QSize rowSizeHint = compute_size_hint_for_row(row, mTheme->iconSize(), mHitItem);
1341 
1342         if ((viewportPoint.y() < top) && (rowIdx > 0)) {
1343             break; // not this row (tough we should have already found it... probably clicked upper margin)
1344         }
1345 
1346         int bottom = top + rowSizeHint.height();
1347 
1348         if (viewportPoint.y() > bottom) {
1349             top += rowSizeHint.height();
1350             rowIdx++;
1351             continue; // not this row
1352         }
1353 
1354         bestInexactItemRight = false;
1355         bestInexactDistance = 0xffffff;
1356         bestInexactContentItem = nullptr;
1357 
1358         // this row!
1359         mHitRow = row;
1360         mHitRowIndex = rowIdx;
1361         mHitRowRect = QRect(left, top, right - left, bottom - top);
1362 
1363         // check right aligned stuff first
1364         mHitContentItemRight = true;
1365 
1366         int r = right;
1367         int l = left;
1368         const auto rightItems = mHitRow->rightItems();
1369         for (const auto itemit : rightItems) {
1370             auto ci = const_cast<Theme::ContentItem *>(itemit);
1371 
1372             mHitContentItemRect = QRect();
1373 
1374             const QFont &font = cachedFont(ci, mHitItem);
1375 
1376             switch (ci->type()) {
1377             case Theme::ContentItem::Subject:
1378                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->subject(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1379                 break;
1380             case Theme::ContentItem::SenderOrReceiver:
1381                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displaySenderOrReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1382                 break;
1383             case Theme::ContentItem::Receiver:
1384                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displayReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1385                 break;
1386             case Theme::ContentItem::Sender:
1387                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displaySender(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1388                 break;
1389             case Theme::ContentItem::Date:
1390                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1391                 break;
1392             case Theme::ContentItem::MostRecentDate:
1393                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedMaxDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1394                 break;
1395             case Theme::ContentItem::Size:
1396                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedSize(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1397                 break;
1398             case Theme::ContentItem::Folder:
1399                 compute_bounding_rect_for_right_aligned_elided_text(mHitItem->folder(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1400                 break;
1401             case Theme::ContentItem::GroupHeaderLabel:
1402                 if (groupHeaderItem) {
1403                     compute_bounding_rect_for_right_aligned_elided_text(groupHeaderItem->label(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1404                 }
1405                 break;
1406             case Theme::ContentItem::ReadStateIcon:
1407                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1408                 break;
1409             case Theme::ContentItem::CombinedReadRepliedStateIcon:
1410                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1411                 break;
1412             case Theme::ContentItem::ExpandedStateIcon:
1413                 compute_bounding_rect_for_boolean_state_icon(mHitItem->childItemCount() > 0,
1414                                                              ci,
1415                                                              l,
1416                                                              top,
1417                                                              r,
1418                                                              mHitContentItemRect,
1419                                                              layoutDir == Qt::LeftToRight,
1420                                                              mTheme->iconSize());
1421                 break;
1422             case Theme::ContentItem::RepliedStateIcon:
1423                 if (messageItem) {
1424                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
1425                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1426                                                                  ci,
1427                                                                  l,
1428                                                                  top,
1429                                                                  r,
1430                                                                  mHitContentItemRect,
1431                                                                  layoutDir == Qt::LeftToRight,
1432                                                                  mTheme->iconSize());
1433                 }
1434                 break;
1435             case Theme::ContentItem::EncryptionStateIcon:
1436                 if (messageItem) {
1437                     bool enabled;
1438                     get_encryption_state_icon(mTheme, messageItem, &enabled);
1439                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1440                 }
1441                 break;
1442             case Theme::ContentItem::SignatureStateIcon:
1443                 if (messageItem) {
1444                     bool enabled;
1445                     get_signature_state_icon(mTheme, messageItem, &enabled);
1446                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1447                 }
1448                 break;
1449             case Theme::ContentItem::SpamHamStateIcon:
1450                 if (messageItem) {
1451                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
1452                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1453                                                                  ci,
1454                                                                  l,
1455                                                                  top,
1456                                                                  r,
1457                                                                  mHitContentItemRect,
1458                                                                  layoutDir == Qt::LeftToRight,
1459                                                                  mTheme->iconSize());
1460                 }
1461                 break;
1462             case Theme::ContentItem::WatchedIgnoredStateIcon:
1463                 if (messageItem) {
1464                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
1465                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1466                                                                  ci,
1467                                                                  l,
1468                                                                  top,
1469                                                                  r,
1470                                                                  mHitContentItemRect,
1471                                                                  layoutDir == Qt::LeftToRight,
1472                                                                  mTheme->iconSize());
1473                 }
1474                 break;
1475             case Theme::ContentItem::AttachmentStateIcon:
1476                 if (messageItem) {
1477                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasAttachment(),
1478                                                                  ci,
1479                                                                  l,
1480                                                                  top,
1481                                                                  r,
1482                                                                  mHitContentItemRect,
1483                                                                  layoutDir == Qt::LeftToRight,
1484                                                                  mTheme->iconSize());
1485                 }
1486                 break;
1487             case Theme::ContentItem::AnnotationIcon:
1488                 if (messageItem) {
1489                     compute_bounding_rect_for_boolean_state_icon(messageItem->hasAnnotation(),
1490                                                                  ci,
1491                                                                  l,
1492                                                                  top,
1493                                                                  r,
1494                                                                  mHitContentItemRect,
1495                                                                  layoutDir == Qt::LeftToRight,
1496                                                                  mTheme->iconSize());
1497                 }
1498                 break;
1499             case Theme::ContentItem::InvitationIcon:
1500                 if (messageItem) {
1501                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasInvitation(),
1502                                                                  ci,
1503                                                                  l,
1504                                                                  top,
1505                                                                  r,
1506                                                                  mHitContentItemRect,
1507                                                                  layoutDir == Qt::LeftToRight,
1508                                                                  mTheme->iconSize());
1509                 }
1510                 break;
1511             case Theme::ContentItem::ActionItemStateIcon:
1512                 if (messageItem) {
1513                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isToAct(),
1514                                                                  ci,
1515                                                                  l,
1516                                                                  top,
1517                                                                  r,
1518                                                                  mHitContentItemRect,
1519                                                                  layoutDir == Qt::LeftToRight,
1520                                                                  mTheme->iconSize());
1521                 }
1522                 break;
1523             case Theme::ContentItem::ImportantStateIcon:
1524                 if (messageItem) {
1525                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isImportant(),
1526                                                                  ci,
1527                                                                  l,
1528                                                                  top,
1529                                                                  r,
1530                                                                  mHitContentItemRect,
1531                                                                  layoutDir == Qt::LeftToRight,
1532                                                                  mTheme->iconSize());
1533                 }
1534                 break;
1535             case Theme::ContentItem::VerticalLine:
1536                 compute_bounding_rect_for_vertical_line(l, top, r, bottom, mHitContentItemRect, layoutDir == Qt::LeftToRight);
1537                 break;
1538             case Theme::ContentItem::HorizontalSpacer:
1539                 compute_bounding_rect_for_horizontal_spacer(l, top, r, bottom, mHitContentItemRect, layoutDir == Qt::LeftToRight);
1540                 break;
1541             case Theme::ContentItem::TagList:
1542                 if (messageItem) {
1543                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1544                     compute_bounding_rect_for_tag_list(tagList, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
1545                 }
1546                 break;
1547             }
1548 
1549             if (mHitContentItemRect.isValid()) {
1550                 if (mHitContentItemRect.contains(viewportPoint)) {
1551                     // caught!
1552                     mHitContentItem = ci;
1553                     return true;
1554                 }
1555                 if (!exact) {
1556                     QRect inexactRect(mHitContentItemRect.left(), mHitRowRect.top(), mHitContentItemRect.width(), mHitRowRect.height());
1557                     if (inexactRect.contains(viewportPoint)) {
1558                         mHitContentItem = ci;
1559                         return true;
1560                     }
1561 
1562                     int inexactDistance =
1563                         viewportPoint.x() > inexactRect.right() ? viewportPoint.x() - inexactRect.right() : inexactRect.left() - viewportPoint.x();
1564                     if (inexactDistance < bestInexactDistance) {
1565                         bestInexactDistance = inexactDistance;
1566                         bestInexactRect = mHitContentItemRect;
1567                         bestInexactItemRight = true;
1568                         bestInexactContentItem = ci;
1569                     }
1570                 }
1571             }
1572         }
1573 
1574         // then check left aligned stuff
1575         mHitContentItemRight = false;
1576 
1577         const auto leftItems = mHitRow->leftItems();
1578         for (const auto itemit : leftItems) {
1579             auto ci = const_cast<Theme::ContentItem *>(itemit);
1580 
1581             mHitContentItemRect = QRect();
1582 
1583             const QFont &font = cachedFont(ci, mHitItem);
1584 
1585             switch (ci->type()) {
1586             case Theme::ContentItem::Subject:
1587                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->subject(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1588                 break;
1589             case Theme::ContentItem::SenderOrReceiver:
1590                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displaySenderOrReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1591                 break;
1592             case Theme::ContentItem::Receiver:
1593                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displayReceiver(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1594                 break;
1595             case Theme::ContentItem::Sender:
1596                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displaySender(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1597                 break;
1598             case Theme::ContentItem::Date:
1599                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1600                 break;
1601             case Theme::ContentItem::MostRecentDate:
1602                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedMaxDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1603                 break;
1604             case Theme::ContentItem::Size:
1605                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedSize(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1606                 break;
1607             case Theme::ContentItem::Folder:
1608                 compute_bounding_rect_for_left_aligned_elided_text(mHitItem->folder(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1609                 break;
1610             case Theme::ContentItem::GroupHeaderLabel:
1611                 if (groupHeaderItem) {
1612                     compute_bounding_rect_for_left_aligned_elided_text(groupHeaderItem->label(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
1613                 }
1614                 break;
1615             case Theme::ContentItem::ReadStateIcon:
1616                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1617                 break;
1618             case Theme::ContentItem::CombinedReadRepliedStateIcon:
1619                 compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1620                 break;
1621             case Theme::ContentItem::ExpandedStateIcon:
1622                 compute_bounding_rect_for_boolean_state_icon(mHitItem->childItemCount() > 0,
1623                                                              ci,
1624                                                              l,
1625                                                              top,
1626                                                              r,
1627                                                              mHitContentItemRect,
1628                                                              layoutDir != Qt::LeftToRight,
1629                                                              mTheme->iconSize());
1630                 break;
1631             case Theme::ContentItem::RepliedStateIcon:
1632                 if (messageItem) {
1633                     const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
1634                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1635                                                                  ci,
1636                                                                  l,
1637                                                                  top,
1638                                                                  r,
1639                                                                  mHitContentItemRect,
1640                                                                  layoutDir != Qt::LeftToRight,
1641                                                                  mTheme->iconSize());
1642                 }
1643                 break;
1644             case Theme::ContentItem::EncryptionStateIcon:
1645                 if (messageItem) {
1646                     bool enabled;
1647                     get_encryption_state_icon(mTheme, messageItem, &enabled);
1648                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1649                 }
1650                 break;
1651             case Theme::ContentItem::SignatureStateIcon:
1652                 if (messageItem) {
1653                     bool enabled;
1654                     get_signature_state_icon(mTheme, messageItem, &enabled);
1655                     compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1656                 }
1657                 break;
1658             case Theme::ContentItem::SpamHamStateIcon:
1659                 if (messageItem) {
1660                     const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
1661                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1662                                                                  ci,
1663                                                                  l,
1664                                                                  top,
1665                                                                  r,
1666                                                                  mHitContentItemRect,
1667                                                                  layoutDir != Qt::LeftToRight,
1668                                                                  mTheme->iconSize());
1669                 }
1670                 break;
1671             case Theme::ContentItem::WatchedIgnoredStateIcon:
1672                 if (messageItem) {
1673                     const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
1674                     compute_bounding_rect_for_boolean_state_icon(pix != nullptr,
1675                                                                  ci,
1676                                                                  l,
1677                                                                  top,
1678                                                                  r,
1679                                                                  mHitContentItemRect,
1680                                                                  layoutDir != Qt::LeftToRight,
1681                                                                  mTheme->iconSize());
1682                 }
1683                 break;
1684             case Theme::ContentItem::AttachmentStateIcon:
1685                 if (messageItem) {
1686                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasAttachment(),
1687                                                                  ci,
1688                                                                  l,
1689                                                                  top,
1690                                                                  r,
1691                                                                  mHitContentItemRect,
1692                                                                  layoutDir != Qt::LeftToRight,
1693                                                                  mTheme->iconSize());
1694                 }
1695                 break;
1696             case Theme::ContentItem::AnnotationIcon:
1697                 if (messageItem) {
1698                     compute_bounding_rect_for_boolean_state_icon(messageItem->hasAnnotation(),
1699                                                                  ci,
1700                                                                  l,
1701                                                                  top,
1702                                                                  r,
1703                                                                  mHitContentItemRect,
1704                                                                  layoutDir != Qt::LeftToRight,
1705                                                                  mTheme->iconSize());
1706                 }
1707                 break;
1708             case Theme::ContentItem::InvitationIcon:
1709                 if (messageItem) {
1710                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasInvitation(),
1711                                                                  ci,
1712                                                                  l,
1713                                                                  top,
1714                                                                  r,
1715                                                                  mHitContentItemRect,
1716                                                                  layoutDir != Qt::LeftToRight,
1717                                                                  mTheme->iconSize());
1718                 }
1719                 break;
1720             case Theme::ContentItem::ActionItemStateIcon:
1721                 if (messageItem) {
1722                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isToAct(),
1723                                                                  ci,
1724                                                                  l,
1725                                                                  top,
1726                                                                  r,
1727                                                                  mHitContentItemRect,
1728                                                                  layoutDir != Qt::LeftToRight,
1729                                                                  mTheme->iconSize());
1730                 }
1731                 break;
1732             case Theme::ContentItem::ImportantStateIcon:
1733                 if (messageItem) {
1734                     compute_bounding_rect_for_boolean_state_icon(messageItem->status().isImportant(),
1735                                                                  ci,
1736                                                                  l,
1737                                                                  top,
1738                                                                  r,
1739                                                                  mHitContentItemRect,
1740                                                                  layoutDir != Qt::LeftToRight,
1741                                                                  mTheme->iconSize());
1742                 }
1743                 break;
1744             case Theme::ContentItem::VerticalLine:
1745                 compute_bounding_rect_for_vertical_line(l, top, r, bottom, mHitContentItemRect, layoutDir != Qt::LeftToRight);
1746                 break;
1747             case Theme::ContentItem::HorizontalSpacer:
1748                 compute_bounding_rect_for_horizontal_spacer(l, top, r, bottom, mHitContentItemRect, layoutDir != Qt::LeftToRight);
1749                 break;
1750             case Theme::ContentItem::TagList:
1751                 if (messageItem) {
1752                     const QList<MessageItem::Tag *> tagList = messageItem->tagList();
1753                     compute_bounding_rect_for_tag_list(tagList, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
1754                 }
1755                 break;
1756             }
1757 
1758             if (mHitContentItemRect.isValid()) {
1759                 if (mHitContentItemRect.contains(viewportPoint)) {
1760                     // caught!
1761                     mHitContentItem = ci;
1762                     return true;
1763                 }
1764                 if (!exact) {
1765                     QRect inexactRect(mHitContentItemRect.left(), mHitRowRect.top(), mHitContentItemRect.width(), mHitRowRect.height());
1766                     if (inexactRect.contains(viewportPoint)) {
1767                         mHitContentItem = ci;
1768                         return true;
1769                     }
1770 
1771                     int inexactDistance =
1772                         viewportPoint.x() > inexactRect.right() ? viewportPoint.x() - inexactRect.right() : inexactRect.left() - viewportPoint.x();
1773                     if (inexactDistance < bestInexactDistance) {
1774                         bestInexactDistance = inexactDistance;
1775                         bestInexactRect = mHitContentItemRect;
1776                         bestInexactItemRight = false;
1777                         bestInexactContentItem = ci;
1778                     }
1779                 }
1780             }
1781         }
1782 
1783         top += rowSizeHint.height();
1784         rowIdx++;
1785     }
1786 
1787     mHitContentItem = bestInexactContentItem;
1788     mHitContentItemRight = bestInexactItemRight;
1789     mHitContentItemRect = bestInexactRect;
1790     return true;
1791 }
1792 
hitIndex() const1793 const QModelIndex &ThemeDelegate::hitIndex() const
1794 {
1795     return mHitIndex;
1796 }
1797 
hitItem() const1798 Item *ThemeDelegate::hitItem() const
1799 {
1800     return mHitItem;
1801 }
1802 
hitItemRect() const1803 QRect ThemeDelegate::hitItemRect() const
1804 {
1805     return mHitItemRect;
1806 }
1807 
hitColumn() const1808 const Theme::Column *ThemeDelegate::hitColumn() const
1809 {
1810     return mHitColumn;
1811 }
1812 
hitColumnIndex() const1813 int ThemeDelegate::hitColumnIndex() const
1814 {
1815     return mHitIndex.column();
1816 }
1817 
hitRow() const1818 const Theme::Row *ThemeDelegate::hitRow() const
1819 {
1820     return mHitRow;
1821 }
1822 
hitRowIndex() const1823 int ThemeDelegate::hitRowIndex() const
1824 {
1825     return mHitRowIndex;
1826 }
1827 
hitRowRect() const1828 QRect ThemeDelegate::hitRowRect() const
1829 {
1830     return mHitRowRect;
1831 }
1832 
hitRowIsMessageRow() const1833 bool ThemeDelegate::hitRowIsMessageRow() const
1834 {
1835     return mHitRowIsMessageRow;
1836 }
1837 
hitContentItem() const1838 const Theme::ContentItem *ThemeDelegate::hitContentItem() const
1839 {
1840     return mHitContentItem;
1841 }
1842 
hitContentItemRight() const1843 bool ThemeDelegate::hitContentItemRight() const
1844 {
1845     return mHitContentItemRight;
1846 }
1847 
hitContentItemRect() const1848 QRect ThemeDelegate::hitContentItemRect() const
1849 {
1850     return mHitContentItemRect;
1851 }
1852 
sizeHintForItemTypeAndColumn(Item::Type type,int column,const Item * item) const1853 QSize ThemeDelegate::sizeHintForItemTypeAndColumn(Item::Type type, int column, const Item *item) const
1854 {
1855     if (!mTheme) {
1856         return QSize(16, 16); // bleah
1857     }
1858 
1859     const Theme::Column *skcolumn = mTheme->column(column);
1860     if (!skcolumn) {
1861         return QSize(16, 16); // bleah
1862     }
1863 
1864     const QList<Theme::Row *> *rows; // I'd like to have it as reference, but gcc complains...
1865 
1866     // The sizeHint() is layout direction independent.
1867 
1868     int marginw;
1869     int marginh;
1870 
1871     switch (type) {
1872     case Item::Message:
1873         rows = &(skcolumn->messageRows());
1874 
1875         marginh = gMessageVerticalMargin << 1;
1876         marginw = gMessageHorizontalMargin << 1;
1877         break;
1878     case Item::GroupHeader:
1879         rows = &(skcolumn->groupHeaderRows());
1880 
1881         marginh = (gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin) << 1;
1882         marginw = (gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin) << 1;
1883         break;
1884     default:
1885         return QSize(16, 16); // bug
1886         break;
1887     }
1888 
1889     int totalh = 0;
1890     int maxw = 0;
1891 
1892     for (QList<Theme::Row *>::ConstIterator rowit = rows->constBegin(), endRowIt = rows->constEnd(); rowit != endRowIt; ++rowit) {
1893         const QSize sh = compute_size_hint_for_row((*rowit), mTheme->iconSize(), item);
1894         totalh += sh.height();
1895         if (sh.width() > maxw) {
1896             maxw = sh.width();
1897         }
1898     }
1899 
1900     return QSize(maxw + marginw, totalh + marginh);
1901 }
1902 
sizeHint(const QStyleOptionViewItem &,const QModelIndex & index) const1903 QSize ThemeDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &index) const
1904 {
1905     if (!mTheme || !index.isValid()) {
1906         return QSize(16, 16); // hm hm...
1907     }
1908 
1909     Item *item = itemFromIndex(index);
1910     if (!item) {
1911         return QSize(16, 16); // hm...
1912     }
1913 
1914     const Item::Type type = item->type();
1915     if (type == Item::Message) {
1916         if (!mCachedMessageItemSizeHint.isValid()) {
1917             mCachedMessageItemSizeHint = sizeHintForItemTypeAndColumn(Item::Message, index.column(), item);
1918         }
1919         return mCachedMessageItemSizeHint;
1920     } else if (type == Item::GroupHeader) {
1921         if (!mCachedGroupHeaderItemSizeHint.isValid()) {
1922             mCachedGroupHeaderItemSizeHint = sizeHintForItemTypeAndColumn(Item::GroupHeader, index.column(), item);
1923         }
1924         return mCachedGroupHeaderItemSizeHint;
1925     } else {
1926         Q_ASSERT(false);
1927         return QSize();
1928     }
1929 }
1930 
1931 // Store the new fonts when the generalFont changes and flush sizeHint cache
generalFontChanged()1932 void ThemeDelegate::generalFontChanged()
1933 {
1934     mCachedMessageItemSizeHint = QSize();
1935     mCachedGroupHeaderItemSizeHint = QSize();
1936 
1937     QFont font;
1938     if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
1939         font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
1940     } else {
1941         font = MessageListSettings::self()->messageListFont();
1942     }
1943     sFontCache[Normal] = font;
1944     sFontMetricsCache[Normal] = QFontMetrics(font);
1945     font.setBold(true);
1946     sFontCache[Bold] = font;
1947     sFontMetricsCache[Bold] = QFontMetrics(font);
1948     font.setBold(false);
1949     font.setItalic(true);
1950     sFontCache[Italic] = font;
1951     sFontMetricsCache[Italic] = QFontMetrics(font);
1952     font.setBold(true);
1953     font.setItalic(true);
1954     sFontCache[BoldItalic] = font;
1955     sFontMetricsCache[BoldItalic] = QFontMetrics(font);
1956 
1957     sFontHeightCache = sFontMetricsCache[Normal].height();
1958 }
1959 
theme() const1960 const Theme *ThemeDelegate::theme() const
1961 {
1962     return mTheme;
1963 }
1964