File indexing completed on 2024-12-15 04:54:45
0001 /****************************************************************************** 0002 * 0003 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 * 0007 *******************************************************************************/ 0008 0009 #include "utils/themeeditor.h" 0010 #include "core/groupheaderitem.h" 0011 #include "core/messageitem.h" 0012 #include "core/modelinvariantrowmapper.h" 0013 #include "utils/comboboxutils.h" 0014 0015 #include <Akonadi/MessageStatus> 0016 0017 #include <KTextEdit> 0018 0019 #include <QActionGroup> 0020 #include <QCheckBox> 0021 #include <QCursor> 0022 #include <QDrag> 0023 #include <QGridLayout> 0024 #include <QGroupBox> 0025 #include <QHeaderView> 0026 #include <QMimeData> 0027 #include <QMouseEvent> 0028 #include <QPaintEvent> 0029 #include <QPainter> 0030 #include <QPixmap> 0031 #include <QPushButton> 0032 #include <QStringList> 0033 0034 #include <KIconLoader> 0035 #include <KLocalizedString> 0036 #include <KPluralHandlingSpinBox> 0037 #include <QColorDialog> 0038 #include <QComboBox> 0039 #include <QLineEdit> 0040 #include <QMenu> 0041 0042 #include <QDialogButtonBox> 0043 #include <QVBoxLayout> 0044 #include <ctime> // for time_t 0045 0046 using namespace MessageList::Utils; 0047 using namespace MessageList::Core; 0048 0049 static const char gThemeContentItemTypeDndMimeDataFormat[] = "application/x-kmail-messagelistview-theme-contentitem-type"; 0050 0051 ThemeColumnPropertiesDialog::ThemeColumnPropertiesDialog(QWidget *parent, Theme::Column *column, const QString &title) 0052 : QDialog(parent) 0053 , mColumn(column) 0054 { 0055 auto mainLayout = new QVBoxLayout(this); 0056 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0057 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0058 okButton->setDefault(true); 0059 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0060 connect(buttonBox, &QDialogButtonBox::rejected, this, &ThemeColumnPropertiesDialog::reject); 0061 setWindowTitle(title); 0062 0063 auto base = new QWidget(this); 0064 mainLayout->addWidget(base); 0065 mainLayout->addWidget(buttonBox); 0066 0067 auto g = new QGridLayout(base); 0068 0069 auto l = new QLabel(i18nc("@label:textbox Property name", "Name:"), base); 0070 g->addWidget(l, 0, 0); 0071 0072 mNameEdit = new QLineEdit(base); 0073 mNameEdit->setToolTip(i18n("The label that will be displayed in the column header.")); 0074 g->addWidget(mNameEdit, 0, 1); 0075 0076 l = new QLabel(i18n("Header click sorts messages:"), base); 0077 g->addWidget(l, 1, 0); 0078 0079 mMessageSortingCombo = new QComboBox(base); 0080 mMessageSortingCombo->setToolTip(i18n("The sorting order that clicking on this column header will switch to.")); 0081 g->addWidget(mMessageSortingCombo, 1, 1); 0082 0083 mVisibleByDefaultCheck = new QCheckBox(i18n("Visible by default"), base); 0084 mVisibleByDefaultCheck->setToolTip(i18n("Check this if this column should be visible when the theme is selected.")); 0085 g->addWidget(mVisibleByDefaultCheck, 2, 1); 0086 0087 mIsSenderOrReceiverCheck = new QCheckBox(i18n("Contains \"Sender or Receiver\" field"), base); 0088 mIsSenderOrReceiverCheck->setToolTip(i18n("Check this if this column label should be updated depending on the folder \"inbound\"/\"outbound\" type.")); 0089 g->addWidget(mIsSenderOrReceiverCheck, 3, 1); 0090 0091 g->setColumnStretch(1, 1); 0092 g->setRowStretch(10, 1); 0093 0094 connect(okButton, &QPushButton::clicked, this, &ThemeColumnPropertiesDialog::slotOkButtonClicked); 0095 0096 // Display the current settings 0097 mNameEdit->setText(mColumn->label()); 0098 mVisibleByDefaultCheck->setChecked(mColumn->visibleByDefault()); 0099 mIsSenderOrReceiverCheck->setChecked(mColumn->isSenderOrReceiver()); 0100 ComboBoxUtils::fillIntegerOptionCombo(mMessageSortingCombo, SortOrder::enumerateMessageSortingOptions(Aggregation::PerfectReferencesAndSubject)); 0101 ComboBoxUtils::setIntegerOptionComboValue(mMessageSortingCombo, mColumn->messageSorting()); 0102 } 0103 0104 void ThemeColumnPropertiesDialog::slotOkButtonClicked() 0105 { 0106 QString text = mNameEdit->text(); 0107 if (text.isEmpty()) { 0108 text = i18n("Unnamed Column"); 0109 } 0110 mColumn->setLabel(text); 0111 mColumn->setVisibleByDefault(mVisibleByDefaultCheck->isChecked()); 0112 mColumn->setIsSenderOrReceiver(mIsSenderOrReceiverCheck->isChecked()); 0113 mColumn->setMessageSorting( 0114 static_cast<SortOrder::MessageSorting>(ComboBoxUtils::getIntegerOptionComboValue(mMessageSortingCombo, SortOrder::NoMessageSorting))); 0115 0116 accept(); 0117 } 0118 0119 ThemeContentItemSourceLabel::ThemeContentItemSourceLabel(QWidget *parent, Theme::ContentItem::Type type) 0120 : QLabel(parent) 0121 , mType(type) 0122 { 0123 setFrameStyle(QFrame::StyledPanel | QFrame::Raised); 0124 } 0125 0126 ThemeContentItemSourceLabel::~ThemeContentItemSourceLabel() = default; 0127 0128 MessageList::Core::Theme::ContentItem::Type ThemeContentItemSourceLabel::type() const 0129 { 0130 return mType; 0131 } 0132 0133 void ThemeContentItemSourceLabel::mousePressEvent(QMouseEvent *e) 0134 { 0135 if (e->button() == Qt::LeftButton) { 0136 mMousePressPoint = e->pos(); 0137 } 0138 } 0139 0140 void ThemeContentItemSourceLabel::mouseMoveEvent(QMouseEvent *e) 0141 { 0142 if (e->buttons() & Qt::LeftButton) { 0143 const QPoint diff = mMousePressPoint - e->pos(); 0144 if (diff.manhattanLength() > 4) { 0145 startDrag(); 0146 } 0147 } 0148 } 0149 0150 void ThemeContentItemSourceLabel::startDrag() 0151 { 0152 // QPixmap pix = QPixmap::grabWidget( this ); 0153 // QPixmap alpha( pix.width(), pix.height() ); 0154 // alpha.fill(0x0f0f0f0f); 0155 // pix.setAlphaChannel( alpha ); // <-- this crashes... no alpha for dragged pixmap :( 0156 auto data = new QMimeData(); 0157 QByteArray arry; 0158 arry.resize(sizeof(Theme::ContentItem::Type)); 0159 *((Theme::ContentItem::Type *)arry.data()) = mType; 0160 data->setData(QLatin1StringView(gThemeContentItemTypeDndMimeDataFormat), arry); 0161 auto drag = new QDrag(this); 0162 drag->setMimeData(data); 0163 // drag->setPixmap( pix ); 0164 // drag->setHotSpot( mapFromGlobal( QCursor::pos() ) ); 0165 drag->exec(Qt::CopyAction, Qt::CopyAction); 0166 } 0167 0168 ThemePreviewDelegate::ThemePreviewDelegate(QAbstractItemView *parent) 0169 : ThemeDelegate(parent) 0170 { 0171 mRowMapper = new ModelInvariantRowMapper(); 0172 0173 mSampleGroupHeaderItem = new GroupHeaderItem(i18n("Message Group")); 0174 0175 mSampleGroupHeaderItem->setDate(time(nullptr)); 0176 mSampleGroupHeaderItem->setMaxDate(time(nullptr) + 31337); 0177 mSampleGroupHeaderItem->setSubject(i18n("Very long subject very long subject very long subject very long subject very long subject very long")); 0178 0179 mSampleMessageItem = new FakeItem(); 0180 0181 mSampleMessageItem->setDate(time(nullptr)); 0182 mSampleMessageItem->setSize(0x31337); 0183 mSampleMessageItem->setMaxDate(time(nullptr) + 31337); 0184 mSampleMessageItem->setSender(i18n("Sender")); 0185 mSampleMessageItem->setReceiver(i18n("Receiver")); 0186 mSampleMessageItem->setSubject(i18n("Very long subject very long subject very long subject very long subject very long subject very long")); 0187 mSampleMessageItem->setFolder(i18n("Folder")); 0188 mSampleMessageItem->setSignatureState(MessageItem::FullySigned); 0189 mSampleMessageItem->setEncryptionState(MessageItem::FullyEncrypted); 0190 0191 QList<MessageItem::Tag *> list; 0192 list.append(new MessageItem::Tag(QIcon::fromTheme(QStringLiteral("feed-subscribe")).pixmap(KIconLoader::SizeSmall), i18n("Sample Tag 1"), QString())); 0193 list.append(new MessageItem::Tag(QIcon::fromTheme(QStringLiteral("feed-subscribe")).pixmap(KIconLoader::SizeSmall), i18n("Sample Tag 2"), QString())); 0194 list.append(new MessageItem::Tag(QIcon::fromTheme(QStringLiteral("feed-subscribe")).pixmap(KIconLoader::SizeSmall), i18n("Sample Tag 3"), QString())); 0195 mSampleMessageItem->setFakeTags(list); 0196 0197 mRowMapper->createModelInvariantIndex(0, mSampleMessageItem); 0198 0199 mSampleGroupHeaderItem->rawAppendChildItem(mSampleMessageItem); 0200 mSampleMessageItem->setParent(mSampleGroupHeaderItem); 0201 0202 Akonadi::MessageStatus stat; 0203 0204 stat.fromQInt32(0x7fffffff); 0205 stat.setQueued(false); 0206 stat.setSent(false); 0207 stat.setSpam(true); 0208 stat.setWatched(true); 0209 stat.setHasInvitation(); 0210 // stat.setHasAttachment( false ); 0211 0212 mSampleMessageItem->setStatus(stat); 0213 } 0214 0215 ThemePreviewDelegate::~ThemePreviewDelegate() 0216 { 0217 delete mSampleGroupHeaderItem; 0218 // delete mSampleMessageItem; (deleted by the parent) 0219 delete mRowMapper; 0220 } 0221 0222 Item *ThemePreviewDelegate::itemFromIndex(const QModelIndex &index) const 0223 { 0224 if (index.parent().isValid()) { 0225 return mSampleMessageItem; 0226 } 0227 0228 return mSampleGroupHeaderItem; 0229 } 0230 0231 ThemePreviewWidget::ThemePreviewWidget(QWidget *parent) 0232 : QTreeWidget(parent) 0233 , mTheme(nullptr) 0234 { 0235 mSelectedThemeContentItem = nullptr; 0236 mSelectedThemeColumn = nullptr; 0237 mFirstShow = true; 0238 mReadOnly = false; 0239 0240 mDelegate = new ThemePreviewDelegate(this); 0241 setItemDelegate(mDelegate); 0242 setRootIsDecorated(false); 0243 viewport()->setAcceptDrops(true); 0244 0245 header()->setContextMenuPolicy(Qt::CustomContextMenu); 0246 connect(header(), &QWidget::customContextMenuRequested, this, &ThemePreviewWidget::slotHeaderContextMenuRequested); 0247 0248 mGroupHeaderSampleItem = new QTreeWidgetItem(this); 0249 mGroupHeaderSampleItem->setText(0, QString()); 0250 mGroupHeaderSampleItem->setFlags(Qt::ItemIsEnabled); 0251 0252 auto m = new QTreeWidgetItem(mGroupHeaderSampleItem); 0253 m->setText(0, QString()); 0254 0255 mGroupHeaderSampleItem->setExpanded(true); 0256 header()->setSectionsMovable(false); 0257 } 0258 0259 void ThemePreviewWidget::changeEvent(QEvent *event) 0260 { 0261 if (event->type() == QEvent::FontChange) { 0262 mDelegate->generalFontChanged(); 0263 } 0264 QTreeWidget::changeEvent(event); 0265 } 0266 0267 ThemePreviewWidget::~ThemePreviewWidget() = default; 0268 0269 QSize ThemePreviewWidget::sizeHint() const 0270 { 0271 return {350, 180}; 0272 } 0273 0274 void ThemePreviewWidget::setReadOnly(bool readOnly) 0275 { 0276 mReadOnly = readOnly; 0277 } 0278 0279 void ThemePreviewWidget::applyThemeColumnWidths() 0280 { 0281 if (!mTheme) { 0282 return; 0283 } 0284 0285 const QList<Theme::Column *> &columns = mTheme->columns(); 0286 0287 if (columns.isEmpty()) { 0288 viewport()->update(); // trigger a repaint 0289 return; 0290 } 0291 0292 // Now we want to distribute the available width on all the columns. 0293 // The algorithm used here is very similar to the one used in View::applyThemeColumns(). 0294 // It just takes care of ALL the columns instead of the visible ones. 0295 0296 QList<Theme::Column *>::ConstIterator it; 0297 0298 // Gather size hints for all sections. 0299 int idx = 0; 0300 int totalVisibleWidthHint = 0; 0301 QList<Theme::Column *>::ConstIterator end(columns.constEnd()); 0302 0303 for (it = columns.constBegin(); it != end; ++it) { 0304 totalVisibleWidthHint += mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width(); 0305 idx++; 0306 } 0307 0308 if (totalVisibleWidthHint < 16) { 0309 totalVisibleWidthHint = 16; // be reasonable 0310 } 0311 0312 // Now we can compute proportional widths. 0313 0314 idx = 0; 0315 0316 QList<int> realWidths; 0317 realWidths.reserve(columns.count()); 0318 int totalVisibleWidth = 0; 0319 0320 end = columns.constEnd(); 0321 for (it = columns.constBegin(); it != end; ++it) { 0322 int hintWidth = mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width(); 0323 int realWidth; 0324 if ((*it)->containsTextItems()) { 0325 // the column contains text items, it should get more space 0326 realWidth = ((hintWidth * viewport()->width()) / totalVisibleWidthHint) - 2; // -2 is heuristic 0327 if (realWidth < (hintWidth + 2)) { 0328 realWidth = hintWidth + 2; // can't be less 0329 } 0330 } else { 0331 // the column contains no text items, it should get just a little bit more than its sizeHint(). 0332 realWidth = hintWidth + 2; 0333 } 0334 0335 realWidths.append(realWidth); 0336 totalVisibleWidth += realWidth; 0337 0338 idx++; 0339 } 0340 0341 idx = 0; 0342 0343 totalVisibleWidth += 4; // account for some view's border 0344 0345 if (totalVisibleWidth < viewport()->width()) { 0346 // give the additional space to the text columns 0347 // also give more space to the first ones and less space to the last ones 0348 int available = viewport()->width() - totalVisibleWidth; 0349 0350 for (it = columns.begin(); it != columns.end(); ++it) { 0351 if (((*it)->visibleByDefault() || (idx == 0)) && (*it)->containsTextItems()) { 0352 // give more space to this column 0353 available >>= 1; // eat half of the available space 0354 realWidths[idx] += available; // and give it to this column 0355 } 0356 0357 idx++; 0358 } 0359 0360 // if any space is still available, give it to the first column 0361 if (available) { 0362 realWidths[0] += available; 0363 } 0364 } 0365 0366 idx = 0; 0367 0368 // We're ready. 0369 // Assign widths. Hide the sections that are not visible by default, show the other ones. 0370 for (it = columns.begin(); it != columns.end(); ++it) { 0371 header()->resizeSection(idx, realWidths[idx]); 0372 idx++; 0373 } 0374 0375 #if 0 0376 if (mTheme->viewHeaderPolicy() == Theme::NeverShowHeader) { 0377 header()->hide(); 0378 } else { 0379 header()->show(); 0380 } 0381 #endif 0382 } 0383 0384 void ThemePreviewWidget::setTheme(Theme *theme) 0385 { 0386 bool themeChanged = theme != mTheme; 0387 0388 mSelectedThemeContentItem = nullptr; 0389 mThemeSelectedContentItemRect = QRect(); 0390 mDropIndicatorPoint1 = QPoint(); 0391 mDropIndicatorPoint2 = QPoint(); 0392 mTheme = theme; 0393 mDelegate->setTheme(theme); 0394 if (!mTheme) { 0395 return; 0396 } 0397 mGroupHeaderSampleItem->setExpanded(true); 0398 0399 const QList<Theme::Column *> &columns = mTheme->columns(); 0400 0401 setColumnCount(columns.count()); 0402 0403 QStringList headerLabels; 0404 headerLabels.reserve(columns.count()); 0405 QList<Theme::Column *>::ConstIterator end(columns.constEnd()); 0406 for (QList<Theme::Column *>::ConstIterator it = columns.constBegin(); it != end; ++it) { 0407 QString label = (*it)->label(); 0408 if ((*it)->visibleByDefault()) { 0409 label += QStringLiteral(" (%1)").arg(i18nc("Indicates whether or not a header label is visible", "Visible")); 0410 } 0411 0412 headerLabels.append(label); 0413 } 0414 0415 setHeaderLabels(headerLabels); 0416 0417 if (themeChanged) { 0418 applyThemeColumnWidths(); 0419 } 0420 0421 viewport()->update(); // trigger a repaint 0422 } 0423 0424 void ThemePreviewWidget::internalHandleDragEnterEvent(QDragEnterEvent *e) 0425 { 0426 e->ignore(); 0427 0428 if (!e->mimeData()) { 0429 return; 0430 } 0431 if (!e->mimeData()->hasFormat(QLatin1StringView(gThemeContentItemTypeDndMimeDataFormat))) { 0432 return; 0433 } 0434 0435 e->accept(); 0436 } 0437 0438 void ThemePreviewWidget::showEvent(QShowEvent *e) 0439 { 0440 QTreeWidget::showEvent(e); 0441 0442 if (mFirstShow) { 0443 // Make sure we re-apply column widths the first time we're shown. 0444 // The first "apply" call was made while the widget was still hidden and 0445 // almost surely had wrong sizes. 0446 applyThemeColumnWidths(); 0447 mFirstShow = false; 0448 } 0449 } 0450 0451 void ThemePreviewWidget::dragEnterEvent(QDragEnterEvent *e) 0452 { 0453 internalHandleDragEnterEvent(e); 0454 0455 mThemeSelectedContentItemRect = QRect(); 0456 0457 viewport()->update(); // trigger a repaint 0458 } 0459 0460 void ThemePreviewWidget::internalHandleDragMoveEvent(QDragMoveEvent *e) 0461 { 0462 e->ignore(); 0463 0464 if (mReadOnly) { 0465 return; 0466 } 0467 0468 if (!e->mimeData()) { 0469 return; 0470 } 0471 if (!e->mimeData()->hasFormat(QLatin1StringView(gThemeContentItemTypeDndMimeDataFormat))) { 0472 return; 0473 } 0474 0475 QByteArray arry = e->mimeData()->data(QLatin1StringView(gThemeContentItemTypeDndMimeDataFormat)); 0476 0477 if (arry.size() != sizeof(Theme::ContentItem::Type)) { 0478 return; // ugh 0479 } 0480 0481 Theme::ContentItem::Type type = *((Theme::ContentItem::Type *)arry.data()); 0482 if (!computeContentItemInsertPosition(e->position().toPoint(), type)) { 0483 return; 0484 } 0485 0486 e->accept(); 0487 } 0488 0489 void ThemePreviewWidget::dragMoveEvent(QDragMoveEvent *e) 0490 { 0491 if (mReadOnly) { 0492 return; 0493 } 0494 0495 internalHandleDragMoveEvent(e); 0496 0497 mThemeSelectedContentItemRect = QRect(); 0498 0499 viewport()->update(); // trigger a repaint 0500 } 0501 0502 void ThemePreviewWidget::dropEvent(QDropEvent *e) 0503 { 0504 mDropIndicatorPoint1 = mDropIndicatorPoint2; 0505 0506 e->ignore(); 0507 0508 if (mReadOnly) { 0509 return; 0510 } 0511 0512 if (!e->mimeData()) { 0513 return; 0514 } 0515 0516 if (!e->mimeData()->hasFormat(QLatin1StringView(gThemeContentItemTypeDndMimeDataFormat))) { 0517 return; 0518 } 0519 0520 QByteArray arry = e->mimeData()->data(QLatin1StringView(gThemeContentItemTypeDndMimeDataFormat)); 0521 0522 if (arry.size() != sizeof(Theme::ContentItem::Type)) { 0523 return; // ugh 0524 } 0525 0526 Theme::ContentItem::Type type = *((Theme::ContentItem::Type *)arry.data()); 0527 if (!computeContentItemInsertPosition(e->position().toPoint(), type)) { 0528 viewport()->update(); 0529 return; 0530 } 0531 0532 Theme::Row *row = nullptr; 0533 0534 switch (mRowInsertPosition) { 0535 case AboveRow: 0536 row = new Theme::Row(); 0537 if (mDelegate->hitItem()->type() == Item::Message) { 0538 const_cast<Theme::Column *>(mDelegate->hitColumn())->insertMessageRow(mDelegate->hitRowIndex(), row); 0539 } else { 0540 const_cast<Theme::Column *>(mDelegate->hitColumn())->insertGroupHeaderRow(mDelegate->hitRowIndex(), row); 0541 } 0542 break; 0543 case InsideRow: 0544 row = const_cast<Theme::Row *>(mDelegate->hitRow()); 0545 break; 0546 case BelowRow: 0547 row = new Theme::Row(); 0548 if (mDelegate->hitItem()->type() == Item::Message) { 0549 const_cast<Theme::Column *>(mDelegate->hitColumn())->insertMessageRow(mDelegate->hitRowIndex() + 1, row); 0550 } else { 0551 const_cast<Theme::Column *>(mDelegate->hitColumn())->insertGroupHeaderRow(mDelegate->hitRowIndex() + 1, row); 0552 } 0553 break; 0554 } 0555 0556 if (!row) { 0557 return; 0558 } 0559 0560 auto ci = new Theme::ContentItem(type); 0561 if (ci->canBeDisabled()) { 0562 if (ci->isClickable()) { 0563 ci->setSoftenByBlendingWhenDisabled(true); // default to softened 0564 } else { 0565 ci->setHideWhenDisabled(true); // default to hidden 0566 } 0567 } 0568 0569 int idx; 0570 0571 switch (mItemInsertPosition) { 0572 case OnLeftOfItem: 0573 if (!mDelegate->hitContentItem()) { 0574 // bleah 0575 delete ci; 0576 return; 0577 } 0578 idx = mDelegate->hitContentItemRight() ? row->rightItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem())) 0579 : row->leftItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem())); 0580 if (idx < 0) { 0581 // bleah 0582 delete ci; 0583 return; 0584 } 0585 if (mDelegate->hitContentItemRight()) { 0586 row->insertRightItem(idx + 1, ci); 0587 } else { 0588 row->insertLeftItem(idx, ci); 0589 } 0590 break; 0591 case OnRightOfItem: 0592 if (!mDelegate->hitContentItem()) { 0593 // bleah 0594 delete ci; 0595 return; 0596 } 0597 idx = mDelegate->hitContentItemRight() ? row->rightItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem())) 0598 : row->leftItems().indexOf(const_cast<Theme::ContentItem *>(mDelegate->hitContentItem())); 0599 if (idx < 0) { 0600 // bleah 0601 delete ci; 0602 return; 0603 } 0604 if (mDelegate->hitContentItemRight()) { 0605 row->insertRightItem(idx, ci); 0606 } else { 0607 row->insertLeftItem(idx + 1, ci); 0608 } 0609 break; 0610 case AsLastLeftItem: 0611 row->addLeftItem(ci); 0612 break; 0613 case AsLastRightItem: 0614 row->addRightItem(ci); 0615 break; 0616 case AsFirstLeftItem: 0617 row->insertLeftItem(0, ci); 0618 break; 0619 case AsFirstRightItem: 0620 row->insertRightItem(0, ci); 0621 break; 0622 default: // should never happen 0623 row->addRightItem(ci); 0624 break; 0625 } 0626 0627 e->acceptProposedAction(); 0628 0629 mThemeSelectedContentItemRect = QRect(); 0630 mDropIndicatorPoint1 = mDropIndicatorPoint2; 0631 mSelectedThemeContentItem = nullptr; 0632 0633 setTheme(mTheme); // this will reset theme cache and trigger a global update 0634 } 0635 0636 bool ThemePreviewWidget::computeContentItemInsertPosition(const QPoint &pos, Theme::ContentItem::Type type) 0637 { 0638 mDropIndicatorPoint1 = mDropIndicatorPoint2; // this marks the position as invalid 0639 0640 if (!mDelegate->hitTest(pos, false)) { 0641 return false; 0642 } 0643 0644 if (!mDelegate->hitRow()) { 0645 return false; 0646 } 0647 0648 if (mDelegate->hitRowIsMessageRow()) { 0649 if (!Theme::ContentItem::applicableToMessageItems(type)) { 0650 return false; 0651 } 0652 } else { 0653 if (!Theme::ContentItem::applicableToGroupHeaderItems(type)) { 0654 return false; 0655 } 0656 } 0657 0658 QRect rowRect = mDelegate->hitRowRect(); 0659 0660 if (pos.y() < rowRect.top() + 3) { 0661 // above a row 0662 mRowInsertPosition = AboveRow; 0663 if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) { 0664 mDropIndicatorPoint1 = rowRect.topLeft(); 0665 mItemInsertPosition = AsLastLeftItem; 0666 } else { 0667 mDropIndicatorPoint1 = rowRect.topRight(); 0668 mItemInsertPosition = AsLastRightItem; 0669 } 0670 mDropIndicatorPoint2 = QPoint(rowRect.left() + (rowRect.width() / 2), rowRect.top()); 0671 return true; 0672 } 0673 0674 if (pos.y() > rowRect.bottom() - 3) { 0675 // below a row 0676 mRowInsertPosition = BelowRow; 0677 if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) { 0678 mDropIndicatorPoint1 = rowRect.bottomLeft(); 0679 mItemInsertPosition = AsLastLeftItem; 0680 } else { 0681 mDropIndicatorPoint1 = rowRect.bottomRight(); 0682 mItemInsertPosition = AsLastRightItem; 0683 } 0684 mDropIndicatorPoint2 = QPoint(rowRect.left() + (rowRect.width() / 2), rowRect.bottom()); 0685 return true; 0686 } 0687 0688 mRowInsertPosition = InsideRow; 0689 0690 if (!mDelegate->hitContentItem()) { 0691 // didn't hit anything... probably no items in the row 0692 if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) { 0693 mItemInsertPosition = AsLastLeftItem; 0694 mDropIndicatorPoint1 = QPoint(rowRect.left(), rowRect.top()); 0695 mDropIndicatorPoint2 = QPoint(rowRect.left(), rowRect.bottom()); 0696 } else { 0697 mItemInsertPosition = AsLastRightItem; 0698 mDropIndicatorPoint1 = QPoint(rowRect.right(), rowRect.top()); 0699 mDropIndicatorPoint2 = QPoint(rowRect.right(), rowRect.bottom()); 0700 } 0701 return true; 0702 } 0703 0704 // hit something, maybe inexactly 0705 QRect itemRect = mDelegate->hitContentItemRect(); 0706 0707 if (!itemRect.contains(pos)) { 0708 // inexact hit: outside an item 0709 if (pos.x() > itemRect.right()) { 0710 // right side of an item 0711 if (mDelegate->hitRow()->rightItems().count() < 1) { 0712 // between the last left item and the right side 0713 if (pos.x() > (itemRect.right() + ((rowRect.right() - itemRect.right()) / 2))) { 0714 // first/last right item 0715 mItemInsertPosition = AsFirstRightItem; 0716 mDropIndicatorPoint1 = rowRect.topRight(); 0717 mDropIndicatorPoint2 = rowRect.bottomRight(); 0718 } 0719 return true; 0720 } 0721 // either there were some right items (so the theme delegate knows that the reported item is the closest) 0722 // or there were no right items but the position is closest to the left item than the right row end 0723 mItemInsertPosition = OnRightOfItem; 0724 mDropIndicatorPoint1 = itemRect.topRight(); 0725 mDropIndicatorPoint2 = itemRect.bottomRight(); 0726 return true; 0727 } 0728 0729 // left side of an item 0730 if (mDelegate->hitRow()->leftItems().count() < 1) { 0731 // between the left side and the leftmost right item 0732 if (pos.x() < (itemRect.left() - ((itemRect.left() - rowRect.left()) / 2))) { 0733 mItemInsertPosition = AsFirstLeftItem; 0734 mDropIndicatorPoint1 = rowRect.topLeft(); 0735 mDropIndicatorPoint2 = rowRect.bottomLeft(); 0736 return true; 0737 } 0738 } 0739 mItemInsertPosition = OnLeftOfItem; 0740 mDropIndicatorPoint1 = itemRect.topLeft(); 0741 mDropIndicatorPoint2 = itemRect.bottomLeft(); 0742 return true; 0743 } 0744 0745 // exact hit 0746 if (pos.x() < (itemRect.left() + (itemRect.width() / 2))) { 0747 // left side 0748 mItemInsertPosition = OnLeftOfItem; 0749 mDropIndicatorPoint1 = itemRect.topLeft(); 0750 mDropIndicatorPoint2 = itemRect.bottomLeft(); 0751 return true; 0752 } 0753 0754 // right side 0755 mItemInsertPosition = OnRightOfItem; 0756 mDropIndicatorPoint1 = itemRect.topRight(); 0757 mDropIndicatorPoint2 = itemRect.bottomRight(); 0758 return true; 0759 } 0760 0761 void ThemePreviewWidget::mouseMoveEvent(QMouseEvent *e) 0762 { 0763 if (!(mSelectedThemeContentItem && (e->buttons() & Qt::LeftButton)) || mReadOnly) { 0764 QTreeWidget::mouseMoveEvent(e); 0765 return; 0766 } 0767 0768 if (mSelectedThemeContentItem != mDelegate->hitContentItem()) { 0769 QTreeWidget::mouseMoveEvent(e); 0770 return; // ugh.. something weird happened 0771 } 0772 0773 // starting a drag ? 0774 const QPoint diff = e->pos() - mMouseDownPoint; 0775 if (diff.manhattanLength() <= 4) { 0776 QTreeWidget::mouseMoveEvent(e); 0777 return; // ugh.. something weird happened 0778 } 0779 0780 // starting a drag 0781 auto data = new QMimeData(); 0782 QByteArray arry; 0783 arry.resize(sizeof(Theme::ContentItem::Type)); 0784 *((Theme::ContentItem::Type *)arry.data()) = mSelectedThemeContentItem->type(); 0785 data->setData(QLatin1StringView(gThemeContentItemTypeDndMimeDataFormat), arry); 0786 auto drag = new QDrag(this); 0787 drag->setMimeData(data); 0788 0789 // remove the Theme::ContentItem from the Theme 0790 if (mDelegate->hitContentItemRight()) { 0791 const_cast<Theme::Row *>(mDelegate->hitRow())->removeRightItem(mSelectedThemeContentItem); 0792 } else { 0793 const_cast<Theme::Row *>(mDelegate->hitRow())->removeLeftItem(mSelectedThemeContentItem); 0794 } 0795 0796 delete mSelectedThemeContentItem; 0797 0798 if (mDelegate->hitRow()->rightItems().isEmpty() && mDelegate->hitRow()->leftItems().isEmpty()) { 0799 if (mDelegate->hitItem()->type() == Item::Message) { 0800 if (mDelegate->hitColumn()->messageRows().count() > 1) { 0801 const_cast<Theme::Column *>(mDelegate->hitColumn())->removeMessageRow(const_cast<Theme::Row *>(mDelegate->hitRow())); 0802 delete mDelegate->hitRow(); 0803 } 0804 } else { 0805 if (mDelegate->hitColumn()->groupHeaderRows().count() > 1) { 0806 const_cast<Theme::Column *>(mDelegate->hitColumn())->removeGroupHeaderRow(const_cast<Theme::Row *>(mDelegate->hitRow())); 0807 delete mDelegate->hitRow(); 0808 } 0809 } 0810 } 0811 0812 mSelectedThemeContentItem = nullptr; 0813 mThemeSelectedContentItemRect = QRect(); 0814 mDropIndicatorPoint1 = mDropIndicatorPoint2; 0815 0816 setTheme(mTheme); // this will reset theme cache and trigger a global update 0817 0818 // and do drag 0819 drag->exec(Qt::CopyAction, Qt::CopyAction); 0820 } 0821 0822 void ThemePreviewWidget::mousePressEvent(QMouseEvent *e) 0823 { 0824 if (mReadOnly) { 0825 QTreeWidget::mousePressEvent(e); 0826 return; 0827 } 0828 0829 mMouseDownPoint = e->pos(); 0830 0831 if (mDelegate->hitTest(mMouseDownPoint)) { 0832 mSelectedThemeContentItem = const_cast<Theme::ContentItem *>(mDelegate->hitContentItem()); 0833 mThemeSelectedContentItemRect = mSelectedThemeContentItem ? mDelegate->hitContentItemRect() : QRect(); 0834 } else { 0835 mSelectedThemeContentItem = nullptr; 0836 mThemeSelectedContentItemRect = QRect(); 0837 } 0838 0839 QTreeWidget::mousePressEvent(e); 0840 viewport()->update(); 0841 0842 if (e->button() == Qt::RightButton) { 0843 QMenu menu; 0844 0845 if (mSelectedThemeContentItem) { 0846 menu.addSection(Theme::ContentItem::description(mSelectedThemeContentItem->type())); 0847 0848 if (mSelectedThemeContentItem->displaysText()) { 0849 QAction *act = menu.addAction(i18nc("@action:inmenu soften the text color", "Soften")); 0850 act->setCheckable(true); 0851 act->setChecked(mSelectedThemeContentItem->softenByBlending()); 0852 connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotSoftenActionTriggered); 0853 0854 auto childmenu = new QMenu(&menu); 0855 0856 act = childmenu->addAction(i18nc("@action:inmenu Font setting", "Bold")); 0857 act->setData(QVariant(static_cast<int>(Theme::ContentItem::IsBold))); 0858 act->setCheckable(true); 0859 act->setChecked(mSelectedThemeContentItem->isBold()); 0860 act = childmenu->addAction(i18nc("@action:inmenu Font setting", "Italic")); 0861 act->setData(QVariant(static_cast<int>(Theme::ContentItem::IsItalic))); 0862 act->setCheckable(true); 0863 act->setChecked(mSelectedThemeContentItem->isItalic()); 0864 0865 connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotFontMenuTriggered); 0866 0867 menu.addMenu(childmenu)->setText(i18n("Font")); 0868 } 0869 0870 if (mSelectedThemeContentItem->canUseCustomColor()) { 0871 auto childmenu = new QMenu(&menu); 0872 0873 auto grp = new QActionGroup(childmenu); 0874 0875 QAction *act = childmenu->addAction(i18nc("@action:inmenu Foreground color setting", "Default")); 0876 act->setData(QVariant(static_cast<int>(0))); 0877 act->setCheckable(true); 0878 act->setChecked(!mSelectedThemeContentItem->useCustomColor()); 0879 grp->addAction(act); 0880 act = childmenu->addAction(i18nc("@action:inmenu Foreground color setting", "Custom...")); 0881 act->setData(QVariant(static_cast<int>(Theme::ContentItem::UseCustomColor))); 0882 act->setCheckable(true); 0883 act->setChecked(mSelectedThemeContentItem->useCustomColor()); 0884 grp->addAction(act); 0885 0886 connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotForegroundColorMenuTriggered); 0887 0888 menu.addMenu(childmenu)->setText(i18n("Foreground Color")); 0889 } 0890 0891 if (mSelectedThemeContentItem->canBeDisabled()) { 0892 auto childmenu = new QMenu(&menu); 0893 0894 auto grp = new QActionGroup(childmenu); 0895 0896 QAction *act = 0897 childmenu->addAction(i18nc("Hide a mark if the mail does not have the attribute, e.g. Important mark on a non important mail", "Hide")); 0898 act->setData(QVariant(static_cast<int>(Theme::ContentItem::HideWhenDisabled))); 0899 act->setCheckable(true); 0900 act->setChecked(mSelectedThemeContentItem->hideWhenDisabled()); 0901 grp->addAction(act); 0902 act = childmenu->addAction( 0903 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", 0904 "Keep Empty Space")); 0905 act->setData(QVariant(static_cast<int>(0))); 0906 act->setCheckable(true); 0907 act->setChecked(!(mSelectedThemeContentItem->softenByBlendingWhenDisabled() || mSelectedThemeContentItem->hideWhenDisabled())); 0908 grp->addAction(act); 0909 act = childmenu->addAction( 0910 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", 0911 "Keep Softened Icon")); 0912 act->setData(QVariant(static_cast<int>(Theme::ContentItem::SoftenByBlendingWhenDisabled))); 0913 act->setCheckable(true); 0914 act->setChecked(mSelectedThemeContentItem->softenByBlendingWhenDisabled()); 0915 grp->addAction(act); 0916 0917 connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotDisabledFlagsMenuTriggered); 0918 0919 menu.addMenu(childmenu)->setText(i18n("When Disabled")); 0920 } 0921 } 0922 0923 if (mDelegate->hitItem()) { 0924 if (mDelegate->hitItem()->type() == Item::GroupHeader) { 0925 menu.addSection(i18n("Group Header")); 0926 0927 // Background color (mode) submenu 0928 auto childmenu = new QMenu(&menu); 0929 0930 auto grp = new QActionGroup(childmenu); 0931 0932 QAction *act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "None")); 0933 act->setData(QVariant(static_cast<int>(Theme::Transparent))); 0934 act->setCheckable(true); 0935 act->setChecked(mTheme->groupHeaderBackgroundMode() == Theme::Transparent); 0936 grp->addAction(act); 0937 act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "Automatic")); 0938 act->setData(QVariant(static_cast<int>(Theme::AutoColor))); 0939 act->setCheckable(true); 0940 act->setChecked(mTheme->groupHeaderBackgroundMode() == Theme::AutoColor); 0941 grp->addAction(act); 0942 act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "Custom...")); 0943 act->setData(QVariant(static_cast<int>(Theme::CustomColor))); 0944 act->setCheckable(true); 0945 act->setChecked(mTheme->groupHeaderBackgroundMode() == Theme::CustomColor); 0946 grp->addAction(act); 0947 0948 connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotGroupHeaderBackgroundModeMenuTriggered); 0949 0950 menu.addMenu(childmenu)->setText(i18n("Background Color")); 0951 0952 // Background style submenu 0953 childmenu = new QMenu(&menu); 0954 0955 grp = new QActionGroup(childmenu); 0956 QList<QPair<QString, int>> styles = Theme::enumerateGroupHeaderBackgroundStyles(); 0957 QList<QPair<QString, int>>::ConstIterator end(styles.constEnd()); 0958 0959 for (QList<QPair<QString, int>>::ConstIterator it = styles.constBegin(); it != end; ++it) { 0960 act = childmenu->addAction((*it).first); 0961 act->setData(QVariant((*it).second)); 0962 act->setCheckable(true); 0963 act->setChecked(mTheme->groupHeaderBackgroundStyle() == static_cast<Theme::GroupHeaderBackgroundStyle>((*it).second)); 0964 grp->addAction(act); 0965 } 0966 0967 connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotGroupHeaderBackgroundStyleMenuTriggered); 0968 0969 act = menu.addMenu(childmenu); 0970 act->setText(i18n("Background Style")); 0971 if (mTheme->groupHeaderBackgroundMode() == Theme::Transparent) { 0972 act->setEnabled(false); 0973 } 0974 } 0975 } 0976 0977 if (menu.isEmpty()) { 0978 return; 0979 } 0980 0981 menu.exec(viewport()->mapToGlobal(e->pos())); 0982 } 0983 } 0984 0985 void ThemePreviewWidget::slotDisabledFlagsMenuTriggered(QAction *act) 0986 { 0987 if (!mSelectedThemeContentItem) { 0988 return; 0989 } 0990 0991 bool ok; 0992 const int flags = act->data().toInt(&ok); 0993 if (!ok) { 0994 return; 0995 } 0996 0997 mSelectedThemeContentItem->setHideWhenDisabled(flags == Theme::ContentItem::HideWhenDisabled); 0998 mSelectedThemeContentItem->setSoftenByBlendingWhenDisabled(flags == Theme::ContentItem::SoftenByBlendingWhenDisabled); 0999 1000 setTheme(mTheme); // this will reset theme cache and trigger a global update 1001 } 1002 1003 void ThemePreviewWidget::slotForegroundColorMenuTriggered(QAction *act) 1004 { 1005 if (!mSelectedThemeContentItem) { 1006 return; 1007 } 1008 1009 bool ok; 1010 const int flag = act->data().toInt(&ok); 1011 if (!ok) { 1012 return; 1013 } 1014 1015 if (flag == 0) { 1016 mSelectedThemeContentItem->setUseCustomColor(false); 1017 setTheme(mTheme); // this will reset theme cache and trigger a global update 1018 return; 1019 } 1020 1021 QColor clr; 1022 clr = QColorDialog::getColor(mSelectedThemeContentItem->customColor(), this); 1023 if (!clr.isValid()) { 1024 return; 1025 } 1026 1027 mSelectedThemeContentItem->setCustomColor(clr); 1028 mSelectedThemeContentItem->setUseCustomColor(true); 1029 1030 setTheme(mTheme); // this will reset theme cache and trigger a global update 1031 } 1032 1033 void ThemePreviewWidget::slotSoftenActionTriggered(bool) 1034 { 1035 if (!mSelectedThemeContentItem) { 1036 return; 1037 } 1038 1039 mSelectedThemeContentItem->setSoftenByBlending(!mSelectedThemeContentItem->softenByBlending()); 1040 setTheme(mTheme); // this will reset theme cache and trigger a global update 1041 } 1042 1043 void ThemePreviewWidget::slotFontMenuTriggered(QAction *act) 1044 { 1045 if (!mSelectedThemeContentItem) { 1046 return; 1047 } 1048 1049 bool ok; 1050 const int flag = act->data().toInt(&ok); 1051 if (!ok) { 1052 return; 1053 } 1054 1055 if (flag == Theme::ContentItem::IsBold && mSelectedThemeContentItem->isBold() != act->isChecked()) { 1056 mSelectedThemeContentItem->setBold(act->isChecked()); 1057 setTheme(mTheme); 1058 } else if (flag == Theme::ContentItem::IsItalic && mSelectedThemeContentItem->isItalic() != act->isChecked()) { 1059 mSelectedThemeContentItem->setItalic(act->isChecked()); 1060 setTheme(mTheme); 1061 } 1062 } 1063 1064 void ThemePreviewWidget::slotGroupHeaderBackgroundModeMenuTriggered(QAction *act) 1065 { 1066 bool ok; 1067 Theme::GroupHeaderBackgroundMode mode = static_cast<Theme::GroupHeaderBackgroundMode>(act->data().toInt(&ok)); 1068 if (!ok) { 1069 return; 1070 } 1071 1072 switch (mode) { 1073 case Theme::Transparent: 1074 mTheme->setGroupHeaderBackgroundMode(Theme::Transparent); 1075 break; 1076 case Theme::AutoColor: 1077 mTheme->setGroupHeaderBackgroundMode(Theme::AutoColor); 1078 break; 1079 case Theme::CustomColor: { 1080 QColor clr; 1081 clr = QColorDialog::getColor(mTheme->groupHeaderBackgroundColor(), this); 1082 if (!clr.isValid()) { 1083 return; 1084 } 1085 1086 mTheme->setGroupHeaderBackgroundMode(Theme::CustomColor); 1087 mTheme->setGroupHeaderBackgroundColor(clr); 1088 break; 1089 } 1090 } 1091 1092 setTheme(mTheme); // this will reset theme cache and trigger a global update 1093 } 1094 1095 void ThemePreviewWidget::slotGroupHeaderBackgroundStyleMenuTriggered(QAction *act) 1096 { 1097 bool ok; 1098 Theme::GroupHeaderBackgroundStyle mode = static_cast<Theme::GroupHeaderBackgroundStyle>(act->data().toInt(&ok)); 1099 if (!ok) { 1100 return; 1101 } 1102 1103 mTheme->setGroupHeaderBackgroundStyle(mode); 1104 1105 setTheme(mTheme); // this will reset theme cache and trigger a global update 1106 } 1107 1108 void ThemePreviewWidget::paintEvent(QPaintEvent *e) 1109 { 1110 QTreeWidget::paintEvent(e); 1111 1112 if (mThemeSelectedContentItemRect.isValid() || (mDropIndicatorPoint1 != mDropIndicatorPoint2)) { 1113 QPainter painter(viewport()); 1114 1115 if (mThemeSelectedContentItemRect.isValid()) { 1116 painter.setPen(QPen(Qt::black)); 1117 painter.drawRect(mThemeSelectedContentItemRect); 1118 } 1119 if (mDropIndicatorPoint1 != mDropIndicatorPoint2) { 1120 painter.setPen(QPen(Qt::black, 3)); 1121 painter.drawLine(mDropIndicatorPoint1, mDropIndicatorPoint2); 1122 } 1123 } 1124 } 1125 1126 void ThemePreviewWidget::slotHeaderContextMenuRequested(const QPoint &pos) 1127 { 1128 if (mReadOnly) { 1129 return; 1130 } 1131 1132 QTreeWidgetItem *hitem = headerItem(); 1133 if (!hitem) { 1134 return; // ooops 1135 } 1136 1137 int col = header()->logicalIndexAt(pos); 1138 1139 if (col < 0) { 1140 return; 1141 } 1142 1143 if (col >= mTheme->columns().count()) { 1144 return; 1145 } 1146 1147 mSelectedThemeColumn = mTheme->column(col); 1148 if (!mSelectedThemeColumn) { 1149 return; 1150 } 1151 1152 QMenu menu; 1153 1154 menu.setTitle(mSelectedThemeColumn->label()); 1155 1156 QAction *act = menu.addAction(i18n("Column Properties...")); 1157 connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotColumnProperties); 1158 1159 act = menu.addAction(i18n("Add Column...")); 1160 connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotAddColumn); 1161 1162 act = menu.addAction(i18n("Delete Column")); 1163 connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotDeleteColumn); 1164 act->setEnabled(col > 0); 1165 1166 menu.addSeparator(); 1167 1168 act = menu.addAction(i18n("Move Column to Left")); 1169 connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotMoveColumnToLeft); 1170 act->setEnabled(col > 0); 1171 1172 act = menu.addAction(i18n("Move Column to Right")); 1173 connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotMoveColumnToRight); 1174 act->setEnabled(col < mTheme->columns().count() - 1); 1175 1176 menu.exec(header()->mapToGlobal(pos)); 1177 } 1178 1179 void ThemePreviewWidget::slotMoveColumnToLeft() 1180 { 1181 if (!mSelectedThemeColumn) { 1182 return; 1183 } 1184 1185 const int columnIndex = mTheme->columns().indexOf(mSelectedThemeColumn); 1186 mTheme->moveColumn(columnIndex, columnIndex - 1); 1187 setTheme(mTheme); // this will reset theme cache and trigger a global update 1188 } 1189 1190 void ThemePreviewWidget::slotMoveColumnToRight() 1191 { 1192 if (!mSelectedThemeColumn) { 1193 return; 1194 } 1195 1196 const int columnIndex = mTheme->columns().indexOf(mSelectedThemeColumn); 1197 mTheme->moveColumn(columnIndex, columnIndex + 1); 1198 setTheme(mTheme); // this will reset theme cache and trigger a global update 1199 } 1200 1201 void ThemePreviewWidget::slotAddColumn() 1202 { 1203 int newColumnIndex = mTheme->columns().count(); 1204 1205 if (mSelectedThemeColumn) { 1206 newColumnIndex = mTheme->columns().indexOf(mSelectedThemeColumn); 1207 if (newColumnIndex < 0) { 1208 newColumnIndex = mTheme->columns().count(); 1209 } else { 1210 newColumnIndex++; 1211 } 1212 } 1213 1214 mSelectedThemeColumn = new Theme::Column(); 1215 mSelectedThemeColumn->setLabel(i18n("New Column")); 1216 mSelectedThemeColumn->setVisibleByDefault(true); 1217 1218 mSelectedThemeColumn->addMessageRow(new Theme::Row()); 1219 mSelectedThemeColumn->addGroupHeaderRow(new Theme::Row()); 1220 1221 auto dlg = new ThemeColumnPropertiesDialog(this, mSelectedThemeColumn, i18n("Add New Column")); 1222 1223 if (dlg->exec() == QDialog::Accepted) { 1224 mTheme->insertColumn(newColumnIndex, mSelectedThemeColumn); 1225 1226 mSelectedThemeContentItem = nullptr; 1227 mThemeSelectedContentItemRect = QRect(); 1228 mDropIndicatorPoint1 = mDropIndicatorPoint2; 1229 1230 setTheme(mTheme); // this will reset theme cache and trigger a global update 1231 } else { 1232 delete mSelectedThemeColumn; 1233 mSelectedThemeColumn = nullptr; 1234 } 1235 1236 delete dlg; 1237 } 1238 1239 void ThemePreviewWidget::slotColumnProperties() 1240 { 1241 if (!mSelectedThemeColumn) { 1242 return; 1243 } 1244 1245 auto dlg = new ThemeColumnPropertiesDialog(this, mSelectedThemeColumn, i18n("Column Properties")); 1246 1247 if (dlg->exec() == QDialog::Accepted) { 1248 mSelectedThemeContentItem = nullptr; 1249 mThemeSelectedContentItemRect = QRect(); 1250 mDropIndicatorPoint1 = mDropIndicatorPoint2; 1251 1252 setTheme(mTheme); // this will reset theme cache and trigger a global update 1253 } 1254 1255 delete dlg; 1256 } 1257 1258 void ThemePreviewWidget::slotDeleteColumn() 1259 { 1260 if (!mSelectedThemeColumn) { 1261 return; 1262 } 1263 1264 const int idx = mTheme->columns().indexOf(mSelectedThemeColumn); 1265 if (idx < 1) { // first column can't be deleted 1266 return; 1267 } 1268 1269 mTheme->removeColumn(mSelectedThemeColumn); 1270 delete mSelectedThemeColumn; 1271 mSelectedThemeColumn = nullptr; 1272 1273 mSelectedThemeContentItem = nullptr; 1274 mThemeSelectedContentItemRect = QRect(); 1275 mDropIndicatorPoint1 = mDropIndicatorPoint2; 1276 1277 setTheme(mTheme); // this will reset theme cache and trigger a global update 1278 } 1279 1280 ThemeEditor::ThemeEditor(QWidget *parent) 1281 : OptionSetEditor(parent) 1282 { 1283 mCurrentTheme = nullptr; 1284 1285 // Appearance tab 1286 auto tab = new QWidget(this); 1287 addTab(tab, i18n("Appearance")); 1288 1289 auto tabg = new QGridLayout(tab); 1290 1291 auto gb = new QGroupBox(i18n("Content Items"), tab); 1292 tabg->addWidget(gb, 0, 0); 1293 1294 auto gblayout = new QGridLayout(gb); 1295 1296 Theme dummyTheme; 1297 1298 auto cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Subject); 1299 cil->setText(Theme::ContentItem::description(cil->type())); 1300 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1301 gblayout->addWidget(cil, 0, 0); 1302 1303 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Date); 1304 cil->setText(Theme::ContentItem::description(cil->type())); 1305 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1306 gblayout->addWidget(cil, 1, 0); 1307 1308 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Size); 1309 cil->setText(Theme::ContentItem::description(cil->type())); 1310 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1311 gblayout->addWidget(cil, 2, 0); 1312 1313 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Sender); 1314 cil->setText(Theme::ContentItem::description(cil->type())); 1315 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1316 gblayout->addWidget(cil, 0, 1); 1317 1318 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Receiver); 1319 cil->setText(Theme::ContentItem::description(cil->type())); 1320 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1321 gblayout->addWidget(cil, 1, 1); 1322 1323 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SenderOrReceiver); 1324 cil->setText(Theme::ContentItem::description(cil->type())); 1325 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1326 gblayout->addWidget(cil, 2, 1); 1327 1328 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::MostRecentDate); 1329 cil->setText(Theme::ContentItem::description(cil->type())); 1330 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1331 gblayout->addWidget(cil, 0, 2); 1332 1333 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::TagList); 1334 cil->setText(Theme::ContentItem::description(cil->type())); 1335 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1336 gblayout->addWidget(cil, 1, 2); 1337 1338 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Folder); 1339 cil->setText(Theme::ContentItem::description(cil->type())); 1340 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1341 gblayout->addWidget(cil, 2, 2); 1342 1343 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::CombinedReadRepliedStateIcon); 1344 cil->setPixmap(*dummyTheme.pixmap(Theme::IconRepliedAndForwarded)); 1345 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1346 gblayout->addWidget(cil, 0, 3); 1347 1348 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ReadStateIcon); 1349 cil->setPixmap(*dummyTheme.pixmap(Theme::IconNew)); 1350 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1351 gblayout->addWidget(cil, 1, 3); 1352 1353 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::RepliedStateIcon); 1354 cil->setPixmap(*dummyTheme.pixmap(Theme::IconReplied)); 1355 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1356 gblayout->addWidget(cil, 2, 3); 1357 1358 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::AttachmentStateIcon); 1359 cil->setPixmap(*dummyTheme.pixmap(Theme::IconAttachment)); 1360 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1361 gblayout->addWidget(cil, 0, 4); 1362 1363 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::EncryptionStateIcon); 1364 cil->setPixmap(*dummyTheme.pixmap(Theme::IconFullyEncrypted)); 1365 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1366 gblayout->addWidget(cil, 1, 4); 1367 1368 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SignatureStateIcon); 1369 cil->setPixmap(*dummyTheme.pixmap(Theme::IconFullySigned)); 1370 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1371 gblayout->addWidget(cil, 2, 4); 1372 1373 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ActionItemStateIcon); 1374 cil->setPixmap(*dummyTheme.pixmap(Theme::IconActionItem)); 1375 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1376 gblayout->addWidget(cil, 0, 5); 1377 1378 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::AnnotationIcon); 1379 cil->setPixmap(*dummyTheme.pixmap(Theme::IconAnnotation)); 1380 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1381 gblayout->addWidget(cil, 1, 5); 1382 1383 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::InvitationIcon); 1384 cil->setPixmap(*dummyTheme.pixmap(Theme::IconInvitation)); 1385 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1386 gblayout->addWidget(cil, 2, 5); 1387 1388 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ImportantStateIcon); 1389 cil->setPixmap(*dummyTheme.pixmap(Theme::IconImportant)); 1390 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1391 gblayout->addWidget(cil, 0, 6); 1392 1393 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SpamHamStateIcon); 1394 cil->setPixmap(*dummyTheme.pixmap(Theme::IconSpam)); 1395 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1396 gblayout->addWidget(cil, 1, 6); 1397 1398 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::WatchedIgnoredStateIcon); 1399 cil->setPixmap(*dummyTheme.pixmap(Theme::IconWatched)); 1400 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1401 gblayout->addWidget(cil, 2, 6); 1402 1403 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ExpandedStateIcon); 1404 cil->setPixmap(*dummyTheme.pixmap(Theme::IconShowMore)); 1405 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1406 gblayout->addWidget(cil, 0, 7); 1407 1408 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::VerticalLine); 1409 cil->setPixmap(*dummyTheme.pixmap(Theme::IconVerticalLine)); 1410 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1411 gblayout->addWidget(cil, 1, 7); 1412 1413 cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::HorizontalSpacer); 1414 cil->setPixmap(*dummyTheme.pixmap(Theme::IconHorizontalSpacer)); 1415 cil->setToolTip(Theme::ContentItem::description(cil->type())); 1416 gblayout->addWidget(cil, 2, 7); 1417 1418 mPreviewWidget = new ThemePreviewWidget(tab); 1419 tabg->addWidget(mPreviewWidget, 1, 0); 1420 1421 auto l = new QLabel(tab); 1422 l->setText( 1423 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 " 1424 "click on the items inside the view for more options.")); 1425 l->setWordWrap(true); 1426 l->setAlignment(Qt::AlignCenter); 1427 tabg->addWidget(l, 2, 0); 1428 1429 tabg->setRowStretch(1, 1); 1430 1431 // Advanced tab 1432 tab = new QWidget(this); 1433 addTab(tab, i18nc("@title:tab Advanced theme settings", "Advanced")); 1434 1435 tabg = new QGridLayout(tab); 1436 1437 l = new QLabel(i18n("Header:"), tab); 1438 tabg->addWidget(l, 0, 0); 1439 1440 mViewHeaderPolicyCombo = new QComboBox(tab); 1441 tabg->addWidget(mViewHeaderPolicyCombo, 0, 1); 1442 1443 l = new QLabel(i18n("Icon size:"), tab); 1444 tabg->addWidget(l, 1, 0); 1445 1446 mIconSizeSpinBox = new KPluralHandlingSpinBox(tab); 1447 mIconSizeSpinBox->setMinimum(8); 1448 mIconSizeSpinBox->setMaximum(64); 1449 mIconSizeSpinBox->setSuffix(ki18ncp("suffix in a spinbox", " pixel", " pixels")); 1450 1451 QObject::connect(mIconSizeSpinBox, &KPluralHandlingSpinBox::valueChanged, this, &ThemeEditor::slotIconSizeSpinBoxValueChanged); 1452 1453 tabg->addWidget(mIconSizeSpinBox, 1, 1); 1454 1455 tabg->setColumnStretch(1, 1); 1456 tabg->setRowStretch(2, 1); 1457 fillViewHeaderPolicyCombo(); 1458 } 1459 1460 ThemeEditor::~ThemeEditor() = default; 1461 1462 void ThemeEditor::editTheme(Theme *set) 1463 { 1464 mCurrentTheme = set; 1465 mPreviewWidget->setTheme(mCurrentTheme); 1466 1467 if (!mCurrentTheme) { 1468 setEnabled(false); 1469 return; 1470 } 1471 setEnabled(true); 1472 1473 nameEdit()->setText(set->name()); 1474 descriptionEdit()->setPlainText(set->description()); 1475 1476 ComboBoxUtils::setIntegerOptionComboValue(mViewHeaderPolicyCombo, (int)mCurrentTheme->viewHeaderPolicy()); 1477 1478 mIconSizeSpinBox->setValue(set->iconSize()); 1479 setReadOnly(mCurrentTheme->readOnly()); 1480 } 1481 1482 void ThemeEditor::setReadOnly(bool readOnly) 1483 { 1484 mPreviewWidget->setReadOnly(readOnly); 1485 mViewHeaderPolicyCombo->setEnabled(!readOnly); 1486 mIconSizeSpinBox->setEnabled(!readOnly); 1487 OptionSetEditor::setReadOnly(readOnly); 1488 } 1489 1490 void ThemeEditor::commit() 1491 { 1492 if (!mCurrentTheme || mCurrentTheme->readOnly()) { 1493 return; 1494 } 1495 1496 mCurrentTheme->setName(nameEdit()->text()); 1497 mCurrentTheme->setDescription(descriptionEdit()->toPlainText()); 1498 1499 mCurrentTheme->setViewHeaderPolicy((Theme::ViewHeaderPolicy)ComboBoxUtils::getIntegerOptionComboValue(mViewHeaderPolicyCombo, 0)); 1500 mCurrentTheme->setIconSize(mIconSizeSpinBox->value()); 1501 // other settings are already committed to this theme 1502 } 1503 1504 void ThemeEditor::fillViewHeaderPolicyCombo() 1505 { 1506 ComboBoxUtils::fillIntegerOptionCombo(mViewHeaderPolicyCombo, Theme::enumerateViewHeaderPolicyOptions()); 1507 } 1508 1509 void ThemeEditor::slotNameEditTextEdited(const QString &newName) 1510 { 1511 if (!mCurrentTheme) { 1512 return; 1513 } 1514 mCurrentTheme->setName(newName); 1515 Q_EMIT themeNameChanged(); 1516 } 1517 1518 void ThemeEditor::slotIconSizeSpinBoxValueChanged(int val) 1519 { 1520 if (!mCurrentTheme) { 1521 return; 1522 } 1523 mCurrentTheme->setIconSize(val); 1524 1525 mPreviewWidget->setTheme(mCurrentTheme); // will trigger a cache reset and a view update 1526 } 1527 1528 MessageList::Core::Theme *ThemeEditor::editedTheme() const 1529 { 1530 return mCurrentTheme; 1531 } 1532 1533 #include "moc_themeeditor.cpp"