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