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

KDE's Doxygen guidelines are available online.