Messagelib

themeeditor.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 "utils/themeeditor.h"
10 #include "core/groupheaderitem.h"
11 #include "core/manager.h"
12 #include "core/messageitem.h"
13 #include "core/modelinvariantrowmapper.h"
14 #include "utils/comboboxutils.h"
15 
16 #include <Akonadi/KMime/MessageStatus>
17 
18 #include <KTextEdit>
19 
20 #include <QActionGroup>
21 #include <QCheckBox>
22 #include <QCursor>
23 #include <QDrag>
24 #include <QGridLayout>
25 #include <QGroupBox>
26 #include <QHeaderView>
27 #include <QMimeData>
28 #include <QMouseEvent>
29 #include <QPaintEvent>
30 #include <QPainter>
31 #include <QPixmap>
32 #include <QPushButton>
33 #include <QStringList>
34 
35 #include <KIconLoader>
36 #include <KLocalizedString>
37 #include <KPluralHandlingSpinBox>
38 #include <QColorDialog>
39 #include <QComboBox>
40 #include <QLineEdit>
41 #include <QMenu>
42 
43 #include <QDialogButtonBox>
44 #include <QVBoxLayout>
45 #include <ctime> // for time_t
46 
47 using namespace MessageList::Utils;
48 using namespace MessageList::Core;
49 
50 static const char gThemeContentItemTypeDndMimeDataFormat[] = "application/x-kmail-messagelistview-theme-contentitem-type";
51 
52 ThemeColumnPropertiesDialog::ThemeColumnPropertiesDialog(QWidget *parent, Theme::Column *column, const QString &title)
53  : QDialog(parent)
54  , mColumn(column)
55 {
56  auto mainLayout = new QVBoxLayout(this);
58  QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
59  okButton->setDefault(true);
60  okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
61  connect(buttonBox, &QDialogButtonBox::rejected, this, &ThemeColumnPropertiesDialog::reject);
62  setWindowTitle(title);
63 
64  auto base = new QWidget(this);
65  mainLayout->addWidget(base);
66  mainLayout->addWidget(buttonBox);
67 
68  auto g = new QGridLayout(base);
69 
70  auto l = new QLabel(i18nc("@label:textbox Property name", "Name:"), base);
71  g->addWidget(l, 0, 0);
72 
73  mNameEdit = new QLineEdit(base);
74  mNameEdit->setToolTip(i18n("The label that will be displayed in the column header."));
75  g->addWidget(mNameEdit, 0, 1);
76 
77  l = new QLabel(i18n("Header click sorts messages:"), base);
78  g->addWidget(l, 1, 0);
79 
80  mMessageSortingCombo = new QComboBox(base);
81  mMessageSortingCombo->setToolTip(i18n("The sorting order that clicking on this column header will switch to."));
82  g->addWidget(mMessageSortingCombo, 1, 1);
83 
84  mVisibleByDefaultCheck = new QCheckBox(i18n("Visible by default"), base);
85  mVisibleByDefaultCheck->setToolTip(i18n("Check this if this column should be visible when the theme is selected."));
86  g->addWidget(mVisibleByDefaultCheck, 2, 1);
87 
88  mIsSenderOrReceiverCheck = new QCheckBox(i18n("Contains \"Sender or Receiver\" field"), base);
89  mIsSenderOrReceiverCheck->setToolTip(i18n("Check this if this column label should be updated depending on the folder \"inbound\"/\"outbound\" type."));
90  g->addWidget(mIsSenderOrReceiverCheck, 3, 1);
91 
92  g->setColumnStretch(1, 1);
93  g->setRowStretch(10, 1);
94 
95  connect(okButton, &QPushButton::clicked, this, &ThemeColumnPropertiesDialog::slotOkButtonClicked);
96 
97  // Display the current settings
98  mNameEdit->setText(mColumn->label());
99  mVisibleByDefaultCheck->setChecked(mColumn->visibleByDefault());
100  mIsSenderOrReceiverCheck->setChecked(mColumn->isSenderOrReceiver());
101  ComboBoxUtils::fillIntegerOptionCombo(mMessageSortingCombo, SortOrder::enumerateMessageSortingOptions(Aggregation::PerfectReferencesAndSubject));
102  ComboBoxUtils::setIntegerOptionComboValue(mMessageSortingCombo, mColumn->messageSorting());
103 }
104 
105 void ThemeColumnPropertiesDialog::slotOkButtonClicked()
106 {
107  QString text = mNameEdit->text();
108  if (text.isEmpty()) {
109  text = i18n("Unnamed Column");
110  }
111  mColumn->setLabel(text);
112  mColumn->setVisibleByDefault(mVisibleByDefaultCheck->isChecked());
113  mColumn->setIsSenderOrReceiver(mIsSenderOrReceiverCheck->isChecked());
114  mColumn->setMessageSorting(
115  static_cast<SortOrder::MessageSorting>(ComboBoxUtils::getIntegerOptionComboValue(mMessageSortingCombo, SortOrder::NoMessageSorting)));
116 
117  accept();
118 }
119 
120 ThemeContentItemSourceLabel::ThemeContentItemSourceLabel(QWidget *parent, Theme::ContentItem::Type type)
121  : QLabel(parent)
122  , mType(type)
123 {
124  setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
125 }
126 
127 ThemeContentItemSourceLabel::~ThemeContentItemSourceLabel() = default;
128 
129 MessageList::Core::Theme::ContentItem::Type ThemeContentItemSourceLabel::type() const
130 {
131  return mType;
132 }
133 
134 void ThemeContentItemSourceLabel::mousePressEvent(QMouseEvent *e)
135 {
136  if (e->button() == Qt::LeftButton) {
137  mMousePressPoint = e->pos();
138  }
139 }
140 
141 void ThemeContentItemSourceLabel::mouseMoveEvent(QMouseEvent *e)
142 {
143  if (e->buttons() & Qt::LeftButton) {
144  const QPoint diff = mMousePressPoint - e->pos();
145  if (diff.manhattanLength() > 4) {
146  startDrag();
147  }
148  }
149 }
150 
151 void ThemeContentItemSourceLabel::startDrag()
152 {
153  // QPixmap pix = QPixmap::grabWidget( this );
154  // QPixmap alpha( pix.width(), pix.height() );
155  // alpha.fill(0x0f0f0f0f);
156  // pix.setAlphaChannel( alpha ); // <-- this crashes... no alpha for dragged pixmap :(
157  auto data = new QMimeData();
158  QByteArray arry;
159  arry.resize(sizeof(Theme::ContentItem::Type));
160  *((Theme::ContentItem::Type *)arry.data()) = mType;
161  data->setData(QLatin1String(gThemeContentItemTypeDndMimeDataFormat), arry);
162  auto drag = new QDrag(this);
163  drag->setMimeData(data);
164  // drag->setPixmap( pix );
165  // drag->setHotSpot( mapFromGlobal( QCursor::pos() ) );
166  drag->exec(Qt::CopyAction, Qt::CopyAction);
167 }
168 
169 ThemePreviewDelegate::ThemePreviewDelegate(QAbstractItemView *parent)
170  : ThemeDelegate(parent)
171 {
172  mRowMapper = new ModelInvariantRowMapper();
173 
174  mSampleGroupHeaderItem = new GroupHeaderItem(i18n("Message Group"));
175 
176  mSampleGroupHeaderItem->setDate(time(nullptr));
177  mSampleGroupHeaderItem->setMaxDate(time(nullptr) + 31337);
178  mSampleGroupHeaderItem->setSubject(i18n("Very long subject very long subject very long subject very long subject very long subject very long"));
179 
180  mSampleMessageItem = new FakeItem();
181 
182  mSampleMessageItem->setDate(time(nullptr));
183  mSampleMessageItem->setSize(0x31337);
184  mSampleMessageItem->setMaxDate(time(nullptr) + 31337);
185  mSampleMessageItem->setSender(i18n("Sender"));
186  mSampleMessageItem->setReceiver(i18n("Receiver"));
187  mSampleMessageItem->setSubject(i18n("Very long subject very long subject very long subject very long subject very long subject very long"));
188  mSampleMessageItem->setFolder(i18n("Folder"));
189  mSampleMessageItem->setSignatureState(MessageItem::FullySigned);
190  mSampleMessageItem->setEncryptionState(MessageItem::FullyEncrypted);
191 
193  list.append(new MessageItem::Tag(QIcon::fromTheme(QStringLiteral("feed-subscribe")).pixmap(KIconLoader::SizeSmall), i18n("Sample Tag 1"), QString()));
194  list.append(new MessageItem::Tag(QIcon::fromTheme(QStringLiteral("feed-subscribe")).pixmap(KIconLoader::SizeSmall), i18n("Sample Tag 2"), QString()));
195  list.append(new MessageItem::Tag(QIcon::fromTheme(QStringLiteral("feed-subscribe")).pixmap(KIconLoader::SizeSmall), i18n("Sample Tag 3"), QString()));
196  mSampleMessageItem->setFakeTags(list);
197 
198  mRowMapper->createModelInvariantIndex(0, mSampleMessageItem);
199 
200  mSampleGroupHeaderItem->rawAppendChildItem(mSampleMessageItem);
201  mSampleMessageItem->setParent(mSampleGroupHeaderItem);
202 
204 
205  stat.fromQInt32(0x7fffffff);
206  stat.setQueued(false);
207  stat.setSent(false);
208  stat.setSpam(true);
209  stat.setWatched(true);
210  stat.setHasInvitation();
211  // stat.setHasAttachment( false );
212 
213  mSampleMessageItem->setStatus(stat);
214 }
215 
216 ThemePreviewDelegate::~ThemePreviewDelegate()
217 {
218  delete mSampleGroupHeaderItem;
219  // delete mSampleMessageItem; (deleted by the parent)
220  delete mRowMapper;
221 }
222 
223 Item *ThemePreviewDelegate::itemFromIndex(const QModelIndex &index) const
224 {
225  if (index.parent().isValid()) {
226  return mSampleMessageItem;
227  }
228 
229  return mSampleGroupHeaderItem;
230 }
231 
232 ThemePreviewWidget::ThemePreviewWidget(QWidget *parent)
233  : QTreeWidget(parent)
234  , mTheme(nullptr)
235 {
236  mSelectedThemeContentItem = nullptr;
237  mSelectedThemeColumn = nullptr;
238  mFirstShow = true;
239  mReadOnly = false;
240 
241  mDelegate = new ThemePreviewDelegate(this);
242  setItemDelegate(mDelegate);
243  setRootIsDecorated(false);
244  viewport()->setAcceptDrops(true);
245 
246  header()->setContextMenuPolicy(Qt::CustomContextMenu); // make sure it's true
247  connect(header(), &QWidget::customContextMenuRequested, this, &ThemePreviewWidget::slotHeaderContextMenuRequested);
248 
249  mGroupHeaderSampleItem = new QTreeWidgetItem(this);
250  mGroupHeaderSampleItem->setText(0, QString());
251  mGroupHeaderSampleItem->setFlags(Qt::ItemIsEnabled);
252 
253  auto m = new QTreeWidgetItem(mGroupHeaderSampleItem);
254  m->setText(0, QString());
255 
256  mGroupHeaderSampleItem->setExpanded(true);
257  header()->setSectionsMovable(false);
258 }
259 
260 void ThemePreviewWidget::changeEvent(QEvent *event)
261 {
262  if (event->type() == QEvent::FontChange) {
263  mDelegate->generalFontChanged();
264  }
266 }
267 
268 ThemePreviewWidget::~ThemePreviewWidget() = default;
269 
270 QSize ThemePreviewWidget::sizeHint() const
271 {
272  return {350, 180};
273 }
274 
275 void ThemePreviewWidget::setReadOnly(bool readOnly)
276 {
277  mReadOnly = readOnly;
278 }
279 
280 void ThemePreviewWidget::applyThemeColumnWidths()
281 {
282  if (!mTheme) {
283  return;
284  }
285 
286  const QList<Theme::Column *> &columns = mTheme->columns();
287 
288  if (columns.isEmpty()) {
289  viewport()->update(); // trigger a repaint
290  return;
291  }
292 
293  // Now we want to distribute the available width on all the columns.
294  // The algorithm used here is very similar to the one used in View::applyThemeColumns().
295  // It just takes care of ALL the columns instead of the visible ones.
296 
298 
299  // Gather size hints for all sections.
300  int idx = 0;
301  int totalVisibleWidthHint = 0;
303 
304  for (it = columns.constBegin(); it != end; ++it) {
305  totalVisibleWidthHint += mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width();
306  idx++;
307  }
308 
309  if (totalVisibleWidthHint < 16) {
310  totalVisibleWidthHint = 16; // be reasonable
311  }
312 
313  // Now we can compute proportional widths.
314 
315  idx = 0;
316 
317  QList<int> realWidths;
318  realWidths.reserve(columns.count());
319  int totalVisibleWidth = 0;
320 
321  end = columns.constEnd();
322  for (it = columns.constBegin(); it != end; ++it) {
323  int hintWidth = mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width();
324  int realWidth;
325  if ((*it)->containsTextItems()) {
326  // the column contains text items, it should get more space
327  realWidth = ((hintWidth * viewport()->width()) / totalVisibleWidthHint) - 2; // -2 is heuristic
328  if (realWidth < (hintWidth + 2)) {
329  realWidth = hintWidth + 2; // can't be less
330  }
331  } else {
332  // the column contains no text items, it should get just a little bit more than its sizeHint().
333  realWidth = hintWidth + 2;
334  }
335 
336  realWidths.append(realWidth);
337  totalVisibleWidth += realWidth;
338 
339  idx++;
340  }
341 
342  idx = 0;
343 
344  totalVisibleWidth += 4; // account for some view's border
345 
346  if (totalVisibleWidth < viewport()->width()) {
347  // give the additional space to the text columns
348  // also give more space to the first ones and less space to the last ones
349  int available = viewport()->width() - totalVisibleWidth;
350 
351  for (it = columns.begin(); it != columns.end(); ++it) {
352  if (((*it)->visibleByDefault() || (idx == 0)) && (*it)->containsTextItems()) {
353  // give more space to this column
354  available >>= 1; // eat half of the available space
355  realWidths[idx] += available; // and give it to this column
356  }
357 
358  idx++;
359  }
360 
361  // if any space is still available, give it to the first column
362  if (available) {
363  realWidths[0] += available;
364  }
365  }
366 
367  idx = 0;
368 
369  // We're ready.
370  // Assign widths. Hide the sections that are not visible by default, show the other ones.
371  for (it = columns.begin(); it != columns.end(); ++it) {
372  header()->resizeSection(idx, realWidths[idx]);
373  idx++;
374  }
375 
376 #if 0
377  if (mTheme->viewHeaderPolicy() == Theme::NeverShowHeader) {
378  header()->hide();
379  } else {
380  header()->show();
381  }
382 #endif
383 }
384 
385 void ThemePreviewWidget::setTheme(Theme *theme)
386 {
387  bool themeChanged = theme != mTheme;
388 
389  mSelectedThemeContentItem = nullptr;
390  mThemeSelectedContentItemRect = QRect();
391  mDropIndicatorPoint1 = QPoint();
392  mDropIndicatorPoint2 = QPoint();
393  mTheme = theme;
394  mDelegate->setTheme(theme);
395  if (!mTheme) {
396  return;
397  }
398  mGroupHeaderSampleItem->setExpanded(true);
399 
400  const QList<Theme::Column *> &columns = mTheme->columns();
401 
402  setColumnCount(columns.count());
403 
404  QStringList headerLabels;
405  headerLabels.reserve(columns.count());
407  for (QList<Theme::Column *>::ConstIterator it = columns.constBegin(); it != end; ++it) {
408  QString label = (*it)->label();
409  if ((*it)->visibleByDefault()) {
410  label += QStringLiteral(" (%1)").arg(i18nc("Indicates whether or not a header label is visible", "Visible"));
411  }
412 
413  headerLabels.append(label);
414  }
415 
416  setHeaderLabels(headerLabels);
417 
418  if (themeChanged) {
419  applyThemeColumnWidths();
420  }
421 
422  viewport()->update(); // trigger a repaint
423 }
424 
425 void ThemePreviewWidget::internalHandleDragEnterEvent(QDragEnterEvent *e)
426 {
427  e->ignore();
428 
429  if (!e->mimeData()) {
430  return;
431  }
432  if (!e->mimeData()->hasFormat(QLatin1String(gThemeContentItemTypeDndMimeDataFormat))) {
433  return;
434  }
435 
436  e->accept();
437 }
438 
439 void ThemePreviewWidget::showEvent(QShowEvent *e)
440 {
442 
443  if (mFirstShow) {
444  // Make sure we re-apply column widths the first time we're shown.
445  // The first "apply" call was made while the widget was still hidden and
446  // almost surely had wrong sizes.
447  applyThemeColumnWidths();
448  mFirstShow = false;
449  }
450 }
451 
452 void ThemePreviewWidget::dragEnterEvent(QDragEnterEvent *e)
453 {
454  internalHandleDragEnterEvent(e);
455 
456  mThemeSelectedContentItemRect = QRect();
457 
458  viewport()->update(); // trigger a repaint
459 }
460 
461 void ThemePreviewWidget::internalHandleDragMoveEvent(QDragMoveEvent *e)
462 {
463  e->ignore();
464 
465  if (mReadOnly) {
466  return;
467  }
468 
469  if (!e->mimeData()) {
470  return;
471  }
472  if (!e->mimeData()->hasFormat(QLatin1String(gThemeContentItemTypeDndMimeDataFormat))) {
473  return;
474  }
475 
476  QByteArray arry = e->mimeData()->data(QLatin1String(gThemeContentItemTypeDndMimeDataFormat));
477 
478  if (arry.size() != sizeof(Theme::ContentItem::Type)) {
479  return; // ugh
480  }
481 
483 
484  if (!computeContentItemInsertPosition(e->pos(), type)) {
485  return;
486  }
487 
488  e->accept();
489 }
490 
491 void ThemePreviewWidget::dragMoveEvent(QDragMoveEvent *e)
492 {
493  if (mReadOnly) {
494  return;
495  }
496 
497  internalHandleDragMoveEvent(e);
498 
499  mThemeSelectedContentItemRect = QRect();
500 
501  viewport()->update(); // trigger a repaint
502 }
503 
504 void ThemePreviewWidget::dropEvent(QDropEvent *e)
505 {
506  mDropIndicatorPoint1 = mDropIndicatorPoint2;
507 
508  e->ignore();
509 
510  if (mReadOnly) {
511  return;
512  }
513 
514  if (!e->mimeData()) {
515  return;
516  }
517 
518  if (!e->mimeData()->hasFormat(QLatin1String(gThemeContentItemTypeDndMimeDataFormat))) {
519  return;
520  }
521 
522  QByteArray arry = e->mimeData()->data(QLatin1String(gThemeContentItemTypeDndMimeDataFormat));
523 
524  if (arry.size() != sizeof(Theme::ContentItem::Type)) {
525  return; // ugh
526  }
527 
529 
530  if (!computeContentItemInsertPosition(e->pos(), type)) {
531  viewport()->update();
532  return;
533  }
534 
535  Theme::Row *row = nullptr;
536 
537  switch (mRowInsertPosition) {
538  case AboveRow:
539  row = new Theme::Row();
540  if (mDelegate->hitItem()->type() == Item::Message) {
541  const_cast<Theme::Column *>(mDelegate->hitColumn())->insertMessageRow(mDelegate->hitRowIndex(), row);
542  } else {
543  const_cast<Theme::Column *>(mDelegate->hitColumn())->insertGroupHeaderRow(mDelegate->hitRowIndex(), row);
544  }
545  break;
546  case InsideRow:
547  row = const_cast<Theme::Row *>(mDelegate->hitRow());
548  break;
549  case BelowRow:
550  row = new Theme::Row();
551  if (mDelegate->hitItem()->type() == Item::Message) {
552  const_cast<Theme::Column *>(mDelegate->hitColumn())->insertMessageRow(mDelegate->hitRowIndex() + 1, row);
553  } else {
554  const_cast<Theme::Column *>(mDelegate->hitColumn())->insertGroupHeaderRow(mDelegate->hitRowIndex() + 1, row);
555  }
556  break;
557  }
558 
559  if (!row) {
560  return;
561  }
562 
563  auto ci = new Theme::ContentItem(type);
564  if (ci->canBeDisabled()) {
565  if (ci->isClickable()) {
566  ci->setSoftenByBlendingWhenDisabled(true); // default to softened
567  } else {
568  ci->setHideWhenDisabled(true); // default to hidden
569  }
570  }
571 
572  int idx;
573 
574  switch (mItemInsertPosition) {
575  case OnLeftOfItem:
576  if (!mDelegate->hitContentItem()) {
577  // bleah
578  delete ci;
579  return;
580  }
581  idx = mDelegate->hitContentItemRight() ? row->rightItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem()))
582  : row->leftItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem()));
583  if (idx < 0) {
584  // bleah
585  delete ci;
586  return;
587  }
588  if (mDelegate->hitContentItemRight()) {
589  row->insertRightItem(idx + 1, ci);
590  } else {
591  row->insertLeftItem(idx, ci);
592  }
593  break;
594  case OnRightOfItem:
595  if (!mDelegate->hitContentItem()) {
596  // bleah
597  delete ci;
598  return;
599  }
600  idx = mDelegate->hitContentItemRight() ? row->rightItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem()))
601  : row->leftItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem()));
602  if (idx < 0) {
603  // bleah
604  delete ci;
605  return;
606  }
607  if (mDelegate->hitContentItemRight()) {
608  row->insertRightItem(idx, ci);
609  } else {
610  row->insertLeftItem(idx + 1, ci);
611  }
612  break;
613  case AsLastLeftItem:
614  row->addLeftItem(ci);
615  break;
616  case AsLastRightItem:
617  row->addRightItem(ci);
618  break;
619  case AsFirstLeftItem:
620  row->insertLeftItem(0, ci);
621  break;
622  case AsFirstRightItem:
623  row->insertRightItem(0, ci);
624  break;
625  default: // should never happen
626  row->addRightItem(ci);
627  break;
628  }
629 
631 
632  mThemeSelectedContentItemRect = QRect();
633  mDropIndicatorPoint1 = mDropIndicatorPoint2;
634  mSelectedThemeContentItem = nullptr;
635 
636  setTheme(mTheme); // this will reset theme cache and trigger a global update
637 }
638 
639 bool ThemePreviewWidget::computeContentItemInsertPosition(const QPoint &pos, Theme::ContentItem::Type type)
640 {
641  mDropIndicatorPoint1 = mDropIndicatorPoint2; // this marks the position as invalid
642 
643  if (!mDelegate->hitTest(pos, false)) {
644  return false;
645  }
646 
647  if (!mDelegate->hitRow()) {
648  return false;
649  }
650 
651  if (mDelegate->hitRowIsMessageRow()) {
653  return false;
654  }
655  } else {
657  return false;
658  }
659  }
660 
661  QRect rowRect = mDelegate->hitRowRect();
662 
663  if (pos.y() < rowRect.top() + 3) {
664  // above a row
665  mRowInsertPosition = AboveRow;
666  if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) {
667  mDropIndicatorPoint1 = rowRect.topLeft();
668  mItemInsertPosition = AsLastLeftItem;
669  } else {
670  mDropIndicatorPoint1 = rowRect.topRight();
671  mItemInsertPosition = AsLastRightItem;
672  }
673  mDropIndicatorPoint2 = QPoint(rowRect.left() + (rowRect.width() / 2), rowRect.top());
674  return true;
675  }
676 
677  if (pos.y() > rowRect.bottom() - 3) {
678  // below a row
679  mRowInsertPosition = BelowRow;
680  if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) {
681  mDropIndicatorPoint1 = rowRect.bottomLeft();
682  mItemInsertPosition = AsLastLeftItem;
683  } else {
684  mDropIndicatorPoint1 = rowRect.bottomRight();
685  mItemInsertPosition = AsLastRightItem;
686  }
687  mDropIndicatorPoint2 = QPoint(rowRect.left() + (rowRect.width() / 2), rowRect.bottom());
688  return true;
689  }
690 
691  mRowInsertPosition = InsideRow;
692 
693  if (!mDelegate->hitContentItem()) {
694  // didn't hit anything... probably no items in the row
695  if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) {
696  mItemInsertPosition = AsLastLeftItem;
697  mDropIndicatorPoint1 = QPoint(rowRect.left(), rowRect.top());
698  mDropIndicatorPoint2 = QPoint(rowRect.left(), rowRect.bottom());
699  } else {
700  mItemInsertPosition = AsLastRightItem;
701  mDropIndicatorPoint1 = QPoint(rowRect.right(), rowRect.top());
702  mDropIndicatorPoint2 = QPoint(rowRect.right(), rowRect.bottom());
703  }
704  return true;
705  }
706 
707  // hit something, maybe inexactly
708  QRect itemRect = mDelegate->hitContentItemRect();
709 
710  if (!itemRect.contains(pos)) {
711  // inexact hit: outside an item
712  if (pos.x() > itemRect.right()) {
713  // right side of an item
714  if (mDelegate->hitRow()->rightItems().count() < 1) {
715  // between the last left item and the right side
716  if (pos.x() > (itemRect.right() + ((rowRect.right() - itemRect.right()) / 2))) {
717  // first/last right item
718  mItemInsertPosition = AsFirstRightItem;
719  mDropIndicatorPoint1 = rowRect.topRight();
720  mDropIndicatorPoint2 = rowRect.bottomRight();
721  }
722  return true;
723  }
724  // either there were some right items (so the theme delegate knows that the reported item is the closest)
725  // or there were no right items but the position is closest to the left item than the right row end
726  mItemInsertPosition = OnRightOfItem;
727  mDropIndicatorPoint1 = itemRect.topRight();
728  mDropIndicatorPoint2 = itemRect.bottomRight();
729  return true;
730  }
731 
732  // left side of an item
733  if (mDelegate->hitRow()->leftItems().count() < 1) {
734  // between the left side and the leftmost right item
735  if (pos.x() < (itemRect.left() - ((itemRect.left() - rowRect.left()) / 2))) {
736  mItemInsertPosition = AsFirstLeftItem;
737  mDropIndicatorPoint1 = rowRect.topLeft();
738  mDropIndicatorPoint2 = rowRect.bottomLeft();
739  return true;
740  }
741  }
742  mItemInsertPosition = OnLeftOfItem;
743  mDropIndicatorPoint1 = itemRect.topLeft();
744  mDropIndicatorPoint2 = itemRect.bottomLeft();
745  return true;
746  }
747 
748  // exact hit
749  if (pos.x() < (itemRect.left() + (itemRect.width() / 2))) {
750  // left side
751  mItemInsertPosition = OnLeftOfItem;
752  mDropIndicatorPoint1 = itemRect.topLeft();
753  mDropIndicatorPoint2 = itemRect.bottomLeft();
754  return true;
755  }
756 
757  // right side
758  mItemInsertPosition = OnRightOfItem;
759  mDropIndicatorPoint1 = itemRect.topRight();
760  mDropIndicatorPoint2 = itemRect.bottomRight();
761  return true;
762 }
763 
764 void ThemePreviewWidget::mouseMoveEvent(QMouseEvent *e)
765 {
766  if (!(mSelectedThemeContentItem && (e->buttons() & Qt::LeftButton)) || mReadOnly) {
768  return;
769  }
770 
771  if (mSelectedThemeContentItem != mDelegate->hitContentItem()) {
773  return; // ugh.. something weird happened
774  }
775 
776  // starting a drag ?
777  const QPoint diff = e->pos() - mMouseDownPoint;
778  if (diff.manhattanLength() <= 4) {
780  return; // ugh.. something weird happened
781  }
782 
783  // starting a drag
784  auto data = new QMimeData();
785  QByteArray arry;
786  arry.resize(sizeof(Theme::ContentItem::Type));
787  *((Theme::ContentItem::Type *)arry.data()) = mSelectedThemeContentItem->type();
788  data->setData(QLatin1String(gThemeContentItemTypeDndMimeDataFormat), arry);
789  auto drag = new QDrag(this);
790  drag->setMimeData(data);
791 
792  // remove the Theme::ContentItem from the Theme
793  if (mDelegate->hitContentItemRight()) {
794  const_cast<Theme::Row *>(mDelegate->hitRow())->removeRightItem(mSelectedThemeContentItem);
795  } else {
796  const_cast<Theme::Row *>(mDelegate->hitRow())->removeLeftItem(mSelectedThemeContentItem);
797  }
798 
799  delete mSelectedThemeContentItem;
800 
801  if (mDelegate->hitRow()->rightItems().isEmpty() && mDelegate->hitRow()->leftItems().isEmpty()) {
802  if (mDelegate->hitItem()->type() == Item::Message) {
803  if (mDelegate->hitColumn()->messageRows().count() > 1) {
804  const_cast<Theme::Column *>(mDelegate->hitColumn())->removeMessageRow(const_cast<Theme::Row *>(mDelegate->hitRow()));
805  delete mDelegate->hitRow();
806  }
807  } else {
808  if (mDelegate->hitColumn()->groupHeaderRows().count() > 1) {
809  const_cast<Theme::Column *>(mDelegate->hitColumn())->removeGroupHeaderRow(const_cast<Theme::Row *>(mDelegate->hitRow()));
810  delete mDelegate->hitRow();
811  }
812  }
813  }
814 
815  mSelectedThemeContentItem = nullptr;
816  mThemeSelectedContentItemRect = QRect();
817  mDropIndicatorPoint1 = mDropIndicatorPoint2;
818 
819  setTheme(mTheme); // this will reset theme cache and trigger a global update
820 
821  // and do drag
822  drag->exec(Qt::CopyAction, Qt::CopyAction);
823 }
824 
825 void ThemePreviewWidget::mousePressEvent(QMouseEvent *e)
826 {
827  if (mReadOnly) {
829  return;
830  }
831 
832  mMouseDownPoint = e->pos();
833 
834  if (mDelegate->hitTest(mMouseDownPoint)) {
835  mSelectedThemeContentItem = const_cast<Theme::ContentItem *>(mDelegate->hitContentItem());
836  mThemeSelectedContentItemRect = mSelectedThemeContentItem ? mDelegate->hitContentItemRect() : QRect();
837  } else {
838  mSelectedThemeContentItem = nullptr;
839  mThemeSelectedContentItemRect = QRect();
840  }
841 
843  viewport()->update();
844 
845  if (e->button() == Qt::RightButton) {
846  QMenu menu;
847 
848  if (mSelectedThemeContentItem) {
849  menu.addSection(Theme::ContentItem::description(mSelectedThemeContentItem->type()));
850 
851  if (mSelectedThemeContentItem->displaysText()) {
852  QAction *act = menu.addAction(i18nc("@action:inmenu soften the text color", "Soften"));
853  act->setCheckable(true);
854  act->setChecked(mSelectedThemeContentItem->softenByBlending());
855  connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotSoftenActionTriggered);
856 
857  auto childmenu = new QMenu(&menu);
858 
859  act = childmenu->addAction(i18nc("@action:inmenu Font setting", "Bold"));
860  act->setData(QVariant(static_cast<int>(Theme::ContentItem::IsBold)));
861  act->setCheckable(true);
862  act->setChecked(mSelectedThemeContentItem->isBold());
863  act = childmenu->addAction(i18nc("@action:inmenu Font setting", "Italic"));
864  act->setData(QVariant(static_cast<int>(Theme::ContentItem::IsItalic)));
865  act->setCheckable(true);
866  act->setChecked(mSelectedThemeContentItem->isItalic());
867 
868  connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotFontMenuTriggered);
869 
870  menu.addMenu(childmenu)->setText(i18n("Font"));
871  }
872 
873  if (mSelectedThemeContentItem->canUseCustomColor()) {
874  auto childmenu = new QMenu(&menu);
875 
876  auto grp = new QActionGroup(childmenu);
877 
878  QAction *act = childmenu->addAction(i18nc("@action:inmenu Foreground color setting", "Default"));
879  act->setData(QVariant(static_cast<int>(0)));
880  act->setCheckable(true);
881  act->setChecked(!mSelectedThemeContentItem->useCustomColor());
882  grp->addAction(act);
883  act = childmenu->addAction(i18nc("@action:inmenu Foreground color setting", "Custom..."));
884  act->setData(QVariant(static_cast<int>(Theme::ContentItem::UseCustomColor)));
885  act->setCheckable(true);
886  act->setChecked(mSelectedThemeContentItem->useCustomColor());
887  grp->addAction(act);
888 
889  connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotForegroundColorMenuTriggered);
890 
891  menu.addMenu(childmenu)->setText(i18n("Foreground Color"));
892  }
893 
894  if (mSelectedThemeContentItem->canBeDisabled()) {
895  auto childmenu = new QMenu(&menu);
896 
897  auto grp = new QActionGroup(childmenu);
898 
899  QAction *act =
900  childmenu->addAction(i18nc("Hide a mark if the mail does not have the attribute, e.g. Important mark on a non important mail", "Hide"));
901  act->setData(QVariant(static_cast<int>(Theme::ContentItem::HideWhenDisabled)));
902  act->setCheckable(true);
903  act->setChecked(mSelectedThemeContentItem->hideWhenDisabled());
904  grp->addAction(act);
905  act = childmenu->addAction(
906  i18nc("Keep a empty space in the list if the mail does not have the attribute, e.g. Important mark on a non important mail",
907  "Keep Empty Space"));
908  act->setData(QVariant(static_cast<int>(0)));
909  act->setCheckable(true);
910  act->setChecked(!(mSelectedThemeContentItem->softenByBlendingWhenDisabled() || mSelectedThemeContentItem->hideWhenDisabled()));
911  grp->addAction(act);
912  act = childmenu->addAction(
913  i18nc("Show the icon softened in the list if the mail does not have the attribute, e.g. Important mark on a non important mail",
914  "Keep Softened Icon"));
916  act->setCheckable(true);
917  act->setChecked(mSelectedThemeContentItem->softenByBlendingWhenDisabled());
918  grp->addAction(act);
919 
920  connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotDisabledFlagsMenuTriggered);
921 
922  menu.addMenu(childmenu)->setText(i18n("When Disabled"));
923  }
924  }
925 
926  if (mDelegate->hitItem()) {
927  if (mDelegate->hitItem()->type() == Item::GroupHeader) {
928  menu.addSection(i18n("Group Header"));
929 
930  // Background color (mode) submenu
931  auto childmenu = new QMenu(&menu);
932 
933  auto grp = new QActionGroup(childmenu);
934 
935  QAction *act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "None"));
936  act->setData(QVariant(static_cast<int>(Theme::Transparent)));
937  act->setCheckable(true);
939  grp->addAction(act);
940  act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "Automatic"));
941  act->setData(QVariant(static_cast<int>(Theme::AutoColor)));
942  act->setCheckable(true);
944  grp->addAction(act);
945  act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "Custom..."));
946  act->setData(QVariant(static_cast<int>(Theme::CustomColor)));
947  act->setCheckable(true);
949  grp->addAction(act);
950 
951  connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotGroupHeaderBackgroundModeMenuTriggered);
952 
953  menu.addMenu(childmenu)->setText(i18n("Background Color"));
954 
955  // Background style submenu
956  childmenu = new QMenu(&menu);
957 
958  grp = new QActionGroup(childmenu);
960  QVector<QPair<QString, int>>::ConstIterator end(styles.constEnd());
961 
962  for (QVector<QPair<QString, int>>::ConstIterator it = styles.constBegin(); it != end; ++it) {
963  act = childmenu->addAction((*it).first);
964  act->setData(QVariant((*it).second));
965  act->setCheckable(true);
966  act->setChecked(mTheme->groupHeaderBackgroundStyle() == static_cast<Theme::GroupHeaderBackgroundStyle>((*it).second));
967  grp->addAction(act);
968  }
969 
970  connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotGroupHeaderBackgroundStyleMenuTriggered);
971 
972  act = menu.addMenu(childmenu);
973  act->setText(i18n("Background Style"));
975  act->setEnabled(false);
976  }
977  }
978  }
979 
980  if (menu.isEmpty()) {
981  return;
982  }
983 
984  menu.exec(viewport()->mapToGlobal(e->pos()));
985  }
986 }
987 
988 void ThemePreviewWidget::slotDisabledFlagsMenuTriggered(QAction *act)
989 {
990  if (!mSelectedThemeContentItem) {
991  return;
992  }
993 
994  bool ok;
995  const int flags = act->data().toInt(&ok);
996  if (!ok) {
997  return;
998  }
999 
1000  mSelectedThemeContentItem->setHideWhenDisabled(flags == Theme::ContentItem::HideWhenDisabled);
1001  mSelectedThemeContentItem->setSoftenByBlendingWhenDisabled(flags == Theme::ContentItem::SoftenByBlendingWhenDisabled);
1002 
1003  setTheme(mTheme); // this will reset theme cache and trigger a global update
1004 }
1005 
1006 void ThemePreviewWidget::slotForegroundColorMenuTriggered(QAction *act)
1007 {
1008  if (!mSelectedThemeContentItem) {
1009  return;
1010  }
1011 
1012  bool ok;
1013  const int flag = act->data().toInt(&ok);
1014  if (!ok) {
1015  return;
1016  }
1017 
1018  if (flag == 0) {
1019  mSelectedThemeContentItem->setUseCustomColor(false);
1020  setTheme(mTheme); // this will reset theme cache and trigger a global update
1021  return;
1022  }
1023 
1024  QColor clr;
1025  clr = QColorDialog::getColor(mSelectedThemeContentItem->customColor(), this);
1026  if (!clr.isValid()) {
1027  return;
1028  }
1029 
1030  mSelectedThemeContentItem->setCustomColor(clr);
1031  mSelectedThemeContentItem->setUseCustomColor(true);
1032 
1033  setTheme(mTheme); // this will reset theme cache and trigger a global update
1034 }
1035 
1036 void ThemePreviewWidget::slotSoftenActionTriggered(bool)
1037 {
1038  if (!mSelectedThemeContentItem) {
1039  return;
1040  }
1041 
1042  mSelectedThemeContentItem->setSoftenByBlending(!mSelectedThemeContentItem->softenByBlending());
1043  setTheme(mTheme); // this will reset theme cache and trigger a global update
1044 }
1045 
1046 void ThemePreviewWidget::slotFontMenuTriggered(QAction *act)
1047 {
1048  if (!mSelectedThemeContentItem) {
1049  return;
1050  }
1051 
1052  bool ok;
1053  const int flag = act->data().toInt(&ok);
1054  if (!ok) {
1055  return;
1056  }
1057 
1058  if (flag == Theme::ContentItem::IsBold && mSelectedThemeContentItem->isBold() != act->isChecked()) {
1059  mSelectedThemeContentItem->setBold(act->isChecked());
1060  setTheme(mTheme);
1061  } else if (flag == Theme::ContentItem::IsItalic && mSelectedThemeContentItem->isItalic() != act->isChecked()) {
1062  mSelectedThemeContentItem->setItalic(act->isChecked());
1063  setTheme(mTheme);
1064  }
1065 }
1066 
1067 void ThemePreviewWidget::slotGroupHeaderBackgroundModeMenuTriggered(QAction *act)
1068 {
1069  bool ok;
1071  if (!ok) {
1072  return;
1073  }
1074 
1075  switch (mode) {
1076  case Theme::Transparent:
1078  break;
1079  case Theme::AutoColor:
1081  break;
1082  case Theme::CustomColor: {
1083  QColor clr;
1084  clr = QColorDialog::getColor(mTheme->groupHeaderBackgroundColor(), this);
1085  if (!clr.isValid()) {
1086  return;
1087  }
1088 
1090  mTheme->setGroupHeaderBackgroundColor(clr);
1091  break;
1092  }
1093  }
1094 
1095  setTheme(mTheme); // this will reset theme cache and trigger a global update
1096 }
1097 
1098 void ThemePreviewWidget::slotGroupHeaderBackgroundStyleMenuTriggered(QAction *act)
1099 {
1100  bool ok;
1102  if (!ok) {
1103  return;
1104  }
1105 
1106  mTheme->setGroupHeaderBackgroundStyle(mode);
1107 
1108  setTheme(mTheme); // this will reset theme cache and trigger a global update
1109 }
1110 
1111 void ThemePreviewWidget::paintEvent(QPaintEvent *e)
1112 {
1114 
1115  if (mThemeSelectedContentItemRect.isValid() || (mDropIndicatorPoint1 != mDropIndicatorPoint2)) {
1116  QPainter painter(viewport());
1117 
1118  if (mThemeSelectedContentItemRect.isValid()) {
1119  painter.setPen(QPen(Qt::black));
1120  painter.drawRect(mThemeSelectedContentItemRect);
1121  }
1122  if (mDropIndicatorPoint1 != mDropIndicatorPoint2) {
1123  painter.setPen(QPen(Qt::black, 3));
1124  painter.drawLine(mDropIndicatorPoint1, mDropIndicatorPoint2);
1125  }
1126  }
1127 }
1128 
1129 void ThemePreviewWidget::slotHeaderContextMenuRequested(const QPoint &pos)
1130 {
1131  if (mReadOnly) {
1132  return;
1133  }
1134 
1135  QTreeWidgetItem *hitem = headerItem();
1136  if (!hitem) {
1137  return; // ooops
1138  }
1139 
1140  int col = header()->logicalIndexAt(pos);
1141 
1142  if (col < 0) {
1143  return;
1144  }
1145 
1146  if (col >= mTheme->columns().count()) {
1147  return;
1148  }
1149 
1150  mSelectedThemeColumn = mTheme->column(col);
1151  if (!mSelectedThemeColumn) {
1152  return;
1153  }
1154 
1155  QMenu menu;
1156 
1157  menu.setTitle(mSelectedThemeColumn->label());
1158 
1159  QAction *act = menu.addAction(i18n("Column Properties..."));
1160  connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotColumnProperties);
1161 
1162  act = menu.addAction(i18n("Add Column..."));
1163  connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotAddColumn);
1164 
1165  act = menu.addAction(i18n("Delete Column"));
1166  connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotDeleteColumn);
1167  act->setEnabled(col > 0);
1168 
1169  menu.addSeparator();
1170 
1171  act = menu.addAction(i18n("Move Column to Left"));
1172  connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotMoveColumnToLeft);
1173  act->setEnabled(col > 0);
1174 
1175  act = menu.addAction(i18n("Move Column to Right"));
1176  connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotMoveColumnToRight);
1177  act->setEnabled(col < mTheme->columns().count() - 1);
1178 
1179  menu.exec(header()->mapToGlobal(pos));
1180 }
1181 
1182 void ThemePreviewWidget::slotMoveColumnToLeft()
1183 {
1184  if (!mSelectedThemeColumn) {
1185  return;
1186  }
1187 
1188  const int columnIndex = mTheme->columns().indexOf(mSelectedThemeColumn);
1189  mTheme->moveColumn(columnIndex, columnIndex - 1);
1190  setTheme(mTheme); // this will reset theme cache and trigger a global update
1191 }
1192 
1193 void ThemePreviewWidget::slotMoveColumnToRight()
1194 {
1195  if (!mSelectedThemeColumn) {
1196  return;
1197  }
1198 
1199  const int columnIndex = mTheme->columns().indexOf(mSelectedThemeColumn);
1200  mTheme->moveColumn(columnIndex, columnIndex + 1);
1201  setTheme(mTheme); // this will reset theme cache and trigger a global update
1202 }
1203 
1204 void ThemePreviewWidget::slotAddColumn()
1205 {
1206  int newColumnIndex = mTheme->columns().count();
1207 
1208  if (mSelectedThemeColumn) {
1209  newColumnIndex = mTheme->columns().indexOf(mSelectedThemeColumn);
1210  if (newColumnIndex < 0) {
1211  newColumnIndex = mTheme->columns().count();
1212  } else {
1213  newColumnIndex++;
1214  }
1215  }
1216 
1217  mSelectedThemeColumn = new Theme::Column();
1218  mSelectedThemeColumn->setLabel(i18n("New Column"));
1219  mSelectedThemeColumn->setVisibleByDefault(true);
1220 
1221  mSelectedThemeColumn->addMessageRow(new Theme::Row());
1222  mSelectedThemeColumn->addGroupHeaderRow(new Theme::Row());
1223 
1224  auto dlg = new ThemeColumnPropertiesDialog(this, mSelectedThemeColumn, i18n("Add New Column"));
1225 
1226  if (dlg->exec() == QDialog::Accepted) {
1227  mTheme->insertColumn(newColumnIndex, mSelectedThemeColumn);
1228 
1229  mSelectedThemeContentItem = nullptr;
1230  mThemeSelectedContentItemRect = QRect();
1231  mDropIndicatorPoint1 = mDropIndicatorPoint2;
1232 
1233  setTheme(mTheme); // this will reset theme cache and trigger a global update
1234  } else {
1235  delete mSelectedThemeColumn;
1236  mSelectedThemeColumn = nullptr;
1237  }
1238 
1239  delete dlg;
1240 }
1241 
1242 void ThemePreviewWidget::slotColumnProperties()
1243 {
1244  if (!mSelectedThemeColumn) {
1245  return;
1246  }
1247 
1248  auto dlg = new ThemeColumnPropertiesDialog(this, mSelectedThemeColumn, i18n("Column Properties"));
1249 
1250  if (dlg->exec() == QDialog::Accepted) {
1251  mSelectedThemeContentItem = nullptr;
1252  mThemeSelectedContentItemRect = QRect();
1253  mDropIndicatorPoint1 = mDropIndicatorPoint2;
1254 
1255  setTheme(mTheme); // this will reset theme cache and trigger a global update
1256  }
1257 
1258  delete dlg;
1259 }
1260 
1261 void ThemePreviewWidget::slotDeleteColumn()
1262 {
1263  if (!mSelectedThemeColumn) {
1264  return;
1265  }
1266 
1267  const int idx = mTheme->columns().indexOf(mSelectedThemeColumn);
1268  if (idx < 1) { // first column can't be deleted
1269  return;
1270  }
1271 
1272  mTheme->removeColumn(mSelectedThemeColumn);
1273  delete mSelectedThemeColumn;
1274  mSelectedThemeColumn = nullptr;
1275 
1276  mSelectedThemeContentItem = nullptr;
1277  mThemeSelectedContentItemRect = QRect();
1278  mDropIndicatorPoint1 = mDropIndicatorPoint2;
1279 
1280  setTheme(mTheme); // this will reset theme cache and trigger a global update
1281 }
1282 
1283 ThemeEditor::ThemeEditor(QWidget *parent)
1284  : OptionSetEditor(parent)
1285 {
1286  mCurrentTheme = nullptr;
1287 
1288  // Appearance tab
1289  auto tab = new QWidget(this);
1290  addTab(tab, i18n("Appearance"));
1291 
1292  auto tabg = new QGridLayout(tab);
1293 
1294  auto gb = new QGroupBox(i18n("Content Items"), tab);
1295  tabg->addWidget(gb, 0, 0);
1296 
1297  auto gblayout = new QGridLayout(gb);
1298 
1299  Theme dummyTheme;
1300 
1301  auto cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Subject);
1302  cil->setText(Theme::ContentItem::description(cil->type()));
1303  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1304  gblayout->addWidget(cil, 0, 0);
1305 
1306  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Date);
1307  cil->setText(Theme::ContentItem::description(cil->type()));
1308  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1309  gblayout->addWidget(cil, 1, 0);
1310 
1311  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Size);
1312  cil->setText(Theme::ContentItem::description(cil->type()));
1313  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1314  gblayout->addWidget(cil, 2, 0);
1315 
1316  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Sender);
1317  cil->setText(Theme::ContentItem::description(cil->type()));
1318  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1319  gblayout->addWidget(cil, 0, 1);
1320 
1321  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Receiver);
1322  cil->setText(Theme::ContentItem::description(cil->type()));
1323  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1324  gblayout->addWidget(cil, 1, 1);
1325 
1326  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SenderOrReceiver);
1327  cil->setText(Theme::ContentItem::description(cil->type()));
1328  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1329  gblayout->addWidget(cil, 2, 1);
1330 
1331  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::MostRecentDate);
1332  cil->setText(Theme::ContentItem::description(cil->type()));
1333  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1334  gblayout->addWidget(cil, 0, 2);
1335 
1336  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::TagList);
1337  cil->setText(Theme::ContentItem::description(cil->type()));
1338  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1339  gblayout->addWidget(cil, 1, 2);
1340 
1341  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Folder);
1342  cil->setText(Theme::ContentItem::description(cil->type()));
1343  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1344  gblayout->addWidget(cil, 2, 2);
1345 
1346  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::CombinedReadRepliedStateIcon);
1347  cil->setPixmap(*dummyTheme.pixmap(Theme::IconRepliedAndForwarded));
1348  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1349  gblayout->addWidget(cil, 0, 3);
1350 
1351  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ReadStateIcon);
1352  cil->setPixmap(*dummyTheme.pixmap(Theme::IconNew));
1353  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1354  gblayout->addWidget(cil, 1, 3);
1355 
1356  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::RepliedStateIcon);
1357  cil->setPixmap(*dummyTheme.pixmap(Theme::IconReplied));
1358  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1359  gblayout->addWidget(cil, 2, 3);
1360 
1361  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::AttachmentStateIcon);
1362  cil->setPixmap(*dummyTheme.pixmap(Theme::IconAttachment));
1363  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1364  gblayout->addWidget(cil, 0, 4);
1365 
1366  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::EncryptionStateIcon);
1367  cil->setPixmap(*dummyTheme.pixmap(Theme::IconFullyEncrypted));
1368  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1369  gblayout->addWidget(cil, 1, 4);
1370 
1371  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SignatureStateIcon);
1372  cil->setPixmap(*dummyTheme.pixmap(Theme::IconFullySigned));
1373  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1374  gblayout->addWidget(cil, 2, 4);
1375 
1376  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ActionItemStateIcon);
1377  cil->setPixmap(*dummyTheme.pixmap(Theme::IconActionItem));
1378  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1379  gblayout->addWidget(cil, 0, 5);
1380 
1381  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::AnnotationIcon);
1382  cil->setPixmap(*dummyTheme.pixmap(Theme::IconAnnotation));
1383  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1384  gblayout->addWidget(cil, 1, 5);
1385 
1386  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::InvitationIcon);
1387  cil->setPixmap(*dummyTheme.pixmap(Theme::IconInvitation));
1388  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1389  gblayout->addWidget(cil, 2, 5);
1390 
1391  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ImportantStateIcon);
1392  cil->setPixmap(*dummyTheme.pixmap(Theme::IconImportant));
1393  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1394  gblayout->addWidget(cil, 0, 6);
1395 
1396  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SpamHamStateIcon);
1397  cil->setPixmap(*dummyTheme.pixmap(Theme::IconSpam));
1398  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1399  gblayout->addWidget(cil, 1, 6);
1400 
1401  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::WatchedIgnoredStateIcon);
1402  cil->setPixmap(*dummyTheme.pixmap(Theme::IconWatched));
1403  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1404  gblayout->addWidget(cil, 2, 6);
1405 
1406  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ExpandedStateIcon);
1407  cil->setPixmap(*dummyTheme.pixmap(Theme::IconShowMore));
1408  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1409  gblayout->addWidget(cil, 0, 7);
1410 
1411  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::VerticalLine);
1412  cil->setPixmap(*dummyTheme.pixmap(Theme::IconVerticalLine));
1413  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1414  gblayout->addWidget(cil, 1, 7);
1415 
1416  cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::HorizontalSpacer);
1417  cil->setPixmap(*dummyTheme.pixmap(Theme::IconHorizontalSpacer));
1418  cil->setToolTip(Theme::ContentItem::description(cil->type()));
1419  gblayout->addWidget(cil, 2, 7);
1420 
1421  mPreviewWidget = new ThemePreviewWidget(tab);
1422  tabg->addWidget(mPreviewWidget, 1, 0);
1423 
1424  auto l = new QLabel(tab);
1425  l->setText(
1426  i18n("Right click on the header to add or modify columns. Drag the content items and drop them on the columns in order to compose your theme. Right "
1427  "click on the items inside the view for more options."));
1428  l->setWordWrap(true);
1429  l->setAlignment(Qt::AlignCenter);
1430  tabg->addWidget(l, 2, 0);
1431 
1432  tabg->setRowStretch(1, 1);
1433 
1434  // Advanced tab
1435  tab = new QWidget(this);
1436  addTab(tab, i18nc("@title:tab Advanced theme settings", "Advanced"));
1437 
1438  tabg = new QGridLayout(tab);
1439 
1440  l = new QLabel(i18n("Header:"), tab);
1441  tabg->addWidget(l, 0, 0);
1442 
1443  mViewHeaderPolicyCombo = new QComboBox(tab);
1444  tabg->addWidget(mViewHeaderPolicyCombo, 0, 1);
1445 
1446  l = new QLabel(i18n("Icon size:"), tab);
1447  tabg->addWidget(l, 1, 0);
1448 
1449  mIconSizeSpinBox = new KPluralHandlingSpinBox(tab);
1450  mIconSizeSpinBox->setMinimum(8);
1451  mIconSizeSpinBox->setMaximum(64);
1452  mIconSizeSpinBox->setSuffix(ki18ncp("suffix in a spinbox", " pixel", " pixels"));
1453 
1454  QObject::connect(mIconSizeSpinBox, qOverload<int>(&KPluralHandlingSpinBox::valueChanged), this, &ThemeEditor::slotIconSizeSpinBoxValueChanged);
1455 
1456  tabg->addWidget(mIconSizeSpinBox, 1, 1);
1457 
1458  tabg->setColumnStretch(1, 1);
1459  tabg->setRowStretch(2, 1);
1460  fillViewHeaderPolicyCombo();
1461 }
1462 
1463 ThemeEditor::~ThemeEditor() = default;
1464 
1465 void ThemeEditor::editTheme(Theme *set)
1466 {
1467  mCurrentTheme = set;
1468  mPreviewWidget->setTheme(mCurrentTheme);
1469 
1470  if (!mCurrentTheme) {
1471  setEnabled(false);
1472  return;
1473  }
1474  setEnabled(true);
1475 
1476  nameEdit()->setText(set->name());
1477  descriptionEdit()->setPlainText(set->description());
1478 
1479  ComboBoxUtils::setIntegerOptionComboValue(mViewHeaderPolicyCombo, (int)mCurrentTheme->viewHeaderPolicy());
1480 
1481  mIconSizeSpinBox->setValue(set->iconSize());
1482  setReadOnly(mCurrentTheme->readOnly());
1483 }
1484 
1485 void ThemeEditor::setReadOnly(bool readOnly)
1486 {
1487  mPreviewWidget->setReadOnly(readOnly);
1488  mViewHeaderPolicyCombo->setEnabled(!readOnly);
1489  mIconSizeSpinBox->setEnabled(!readOnly);
1490  OptionSetEditor::setReadOnly(readOnly);
1491 }
1492 
1493 void ThemeEditor::commit()
1494 {
1495  if (!mCurrentTheme || mCurrentTheme->readOnly()) {
1496  return;
1497  }
1498 
1499  mCurrentTheme->setName(nameEdit()->text());
1500  mCurrentTheme->setDescription(descriptionEdit()->toPlainText());
1501 
1502  mCurrentTheme->setViewHeaderPolicy((Theme::ViewHeaderPolicy)ComboBoxUtils::getIntegerOptionComboValue(mViewHeaderPolicyCombo, 0));
1503  mCurrentTheme->setIconSize(mIconSizeSpinBox->value());
1504  // other settings are already committed to this theme
1505 }
1506 
1507 void ThemeEditor::fillViewHeaderPolicyCombo()
1508 {
1509  ComboBoxUtils::fillIntegerOptionCombo(mViewHeaderPolicyCombo, Theme::enumerateViewHeaderPolicyOptions());
1510 }
1511 
1512 void ThemeEditor::slotNameEditTextEdited(const QString &newName)
1513 {
1514  if (!mCurrentTheme) {
1515  return;
1516  }
1517  mCurrentTheme->setName(newName);
1518  Q_EMIT themeNameChanged();
1519 }
1520 
1521 void ThemeEditor::slotIconSizeSpinBoxValueChanged(int val)
1522 {
1523  if (!mCurrentTheme) {
1524  return;
1525  }
1526  mCurrentTheme->setIconSize(val);
1527 
1528  mPreviewWidget->setTheme(mCurrentTheme); // will trigger a cache reset and a view update
1529 }
1530 
1531 MessageList::Core::Theme *ThemeEditor::editedTheme() const
1532 {
1533  return mCurrentTheme;
1534 }
void customContextMenuRequested(const QPoint &pos)
Whether the message is an invitation.
Definition: theme.h:204
void setText(const QString &text)
static bool applicableToMessageItems(Type type)
Static test that returns true if an instance of ContentItem with the specified type makes sense in a ...
Definition: theme.cpp:278
The Expanded state icon for group headers.
Definition: theme.h:167
void triggered(bool checked)
QString & append(QChar ch)
QEvent::Type type() const const
The icon that displays the unread/read state (never disabled)
Definition: theme.h:135
QByteArray data(const QString &mimeType) const const
virtual void paintEvent(QPaintEvent *event) override
QPoint topRight() const const
GroupHeaderBackgroundStyle groupHeaderBackgroundStyle() const
Returns the group header background style for this theme.
Definition: theme.cpp:1035
const QMimeData * mimeData() const const
Thread by all of the above and try to match subjects too.
Definition: aggregation.h:69
int right() const const
static QVector< QPair< QString, int > > enumerateGroupHeaderBackgroundStyles()
Enumerates the available group header background styles.
Definition: theme.cpp:1050
virtual bool hasFormat(const QString &mimeType) const const
void setWatched(bool watched=true)
The combined icon that displays the unread/read/replied/forwarded state (never disabled) ...
Definition: theme.h:191
void reserve(int alloc)
void setChecked(bool)
static QString description(Type type)
Returns a descriptive name for the specified content item type.
Definition: theme.cpp:103
void setHasInvitation(bool hasInvitation=true)
QVariant data() const const
For text items. If set then always show as bold, otherwise use the default font weight.
Definition: theme.h:220
QVector::const_iterator constEnd() const const
void insertColumn(int idx, Column *column)
Inserts a column to this theme at the specified position.
Definition: theme.cpp:990
QPoint bottomRight() const const
QPoint pos() const const
void setSpam(bool spam=true)
LeftButton
QPoint bottomLeft() const const
void fromQInt32(qint32 status)
void triggered(QAction *action)
The icon that displays the attachment state (may be disabled)
Definition: theme.h:139
GroupHeaderBackgroundMode groupHeaderBackgroundMode() const
Returns the group header background mode for this theme.
Definition: theme.cpp:1004
This class is an optimizing helper for dealing with large flat QAbstractItemModel objects...
The implementation independent part of the MessageList library.
Definition: aggregation.h:21
void setGroupHeaderBackgroundMode(GroupHeaderBackgroundMode bm)
Sets the group header background mode for this theme.
Definition: theme.cpp:1017
Qt::MouseButtons buttons() const const
void acceptProposedAction()
static QVector< QPair< QString, int > > enumerateMessageSortingOptions(Aggregation::Threading t)
Enumerates the message sorting options compatible with the specified Threading setting.
Definition: sortorder.cpp:61
static bool applicableToGroupHeaderItems(Type type)
Static test that returns true if an instance of ContentItem with the specified type makes sense in a ...
Definition: theme.cpp:283
void removeColumn(Column *col)
Removes the specified message row.
Definition: theme.cpp:999
QAction * addAction(const QString &text)
CustomContextMenu
AlignCenter
Whether the message has a annotation/note.
Definition: theme.h:199
int x() const const
int y() const const
ViewHeaderPolicy viewHeaderPolicy() const
Returns the currently set ViewHeaderPolicy.
Definition: theme.cpp:1062
QAction * addSection(const QString &text)
virtual bool event(QEvent *e)
bool isEmpty() const const
void valueChanged(int i)
void resize(int size)
void setGroupHeaderBackgroundStyle(GroupHeaderBackgroundStyle groupHeaderBackgroundStyle)
Sets the group header background style for this theme.
Definition: theme.cpp:1040
The icon that displays the replied/forwarded state (may be disabled)
Definition: theme.h:143
static QVector< QPair< QString, int > > enumerateViewHeaderPolicyOptions()
Enumerates the available view header policy options.
Definition: theme.cpp:1045
bool isValid() const const
virtual void showEvent(QShowEvent *event)
int count(const T &value) const const
No background at all: use style default.
Definition: theme.h:800
void append(const T &value)
PartitionTable::TableType type
void ignore()
int toInt(bool *ok) const const
int top() const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString label(StandardShortcut id)
Display the subject of the message item.
Definition: theme.h:111
int left() const const
Qt::MouseButton button() const const
bool isEmpty() const const
bool isEmpty() const const
void clicked(bool checked)
virtual void changeEvent(QEvent *ev) override
GroupHeaderBackgroundMode
Which color do we use to paint group header background ?
Definition: theme.h:799
void setSent(bool sent=true)
The base class for the OptionSet editors.
void setTitle(const QString &title)
void setShortcut(const QKeySequence &key)
Formatted date time of the message/group.
Definition: theme.h:115
QAction * addSeparator()
Type
The available ContentItem types.
Definition: theme.h:107
A small empty spacer usable as separator.
Definition: theme.h:183
The list of MessageItem::Tag entries.
Definition: theme.h:195
QModelIndex parent() const const
This item is a GroupHeaderItem.
Definition: item.h:45
QAction * exec()
Formatted size of the message.
Definition: theme.h:131
Use a custom color.
Definition: theme.h:802
void setData(const QVariant &userData)
This item is a MessageItem.
Definition: item.h:46
QList::iterator end()
void setQueued(bool queued=true)
int manhattanLength() const const
From: or To: strip, depending on the folder settings.
Definition: theme.h:119
A single item of the MessageList tree managed by MessageList::Model.
Definition: item.h:35
QColor getColor(const QColor &initial, QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options)
A message item that can have a fake tag list and a fake annotation.
Definition: messageitem.h:195
void setCheckable(bool)
const QList< Column * > & columns() const
Returns the list of columns available in this theme.
Definition: theme.cpp:968
QString i18n(const char *text, const TYPE &arg...)
Column * column(int idx) const
Returns a pointer to the column at the specified index or 0 if there is no such column.
Definition: theme.cpp:973
Don&#39;t sort the messages at all.
Definition: sortorder.h:58
const QList< QKeySequence > & end()
QVector::const_iterator constBegin() const const
bool contains(const QRect &rectangle, bool proper) const const
int width() const const
Fot text items. If set then always show as italic, otherwise use the default font style...
Definition: theme.h:221
KLocalizedString KI18N_EXPORT ki18ncp(const char *context, const char *singular, const char *plural)
GroupHeaderBackgroundStyle
How do we paint group header background ?
Definition: theme.h:808
const QColor & groupHeaderBackgroundColor() const
Returns the group header background color for this theme.
Definition: theme.cpp:1025
QAction * addMenu(QMenu *menu)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
For text and vertical line. If set then always use a custom color, otherwise use default text color...
Definition: theme.h:219
int bottom() const const
QPoint topLeft() const const
char * data()
void setGroupHeaderBackgroundColor(const QColor &clr)
Sets the group header background color for this theme.
Definition: theme.cpp:1030
QIcon fromTheme(const QString &name)
virtual void mousePressEvent(QMouseEvent *event) override
The Signature state icon for messages.
Definition: theme.h:175
The Theme class defines the visual appearance of the MessageList.
Definition: theme.h:48
QPoint pos() const const
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
The ThemeDelegate paints the message list view message and group items by using the supplied Theme...
Definition: themedelegate.h:30
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
The Encryption state icon for messages.
Definition: theme.h:171
The date of the most recent message in subtree.
Definition: theme.h:187
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual void mouseMoveEvent(QMouseEvent *event) override
CopyAction
Automatically determine the color (somewhere in the middle between background and text) ...
Definition: theme.h:801
In disabled state the icon should take no space (overrides SoftenByBlendingWhenDisabled) ...
Definition: theme.h:217
void setDefault(bool)
QList::iterator begin()
Key_Return
void setEnabled(bool)
Q_EMITQ_EMIT
In disabled state the icon should be still shown, but made very soft by alpha blending.
Definition: theme.h:218
bool isValid() const const
ViewHeaderPolicy
How do we manage the QHeaderView attached to our View ?
Definition: theme.h:822
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
ItemIsEnabled
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Dec 4 2021 23:12:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.