Messagelib

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

KDE's Doxygen guidelines are available online.