File indexing completed on 2024-12-22 05:01:01

0001 /*
0002   SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: GPL-2.0-only
0005 */
0006 
0007 #include "configureappearancepage.h"
0008 #include <PimCommon/ConfigureImmutableWidgetUtils>
0009 using namespace PimCommon::ConfigureImmutableWidgetUtils;
0010 #include "configuredialog/colorlistbox.h"
0011 #include "kmkernel.h"
0012 #include <KLineEditEventHandler>
0013 #include <MailCommon/TagWidget>
0014 #include <MessageList/AggregationComboBox>
0015 #include <MessageList/AggregationConfigButton>
0016 #include <MessageList/ThemeComboBox>
0017 #include <MessageList/ThemeConfigButton>
0018 #include <messagelist/messagelistsettings.h>
0019 
0020 #include <MailCommon/FolderTreeWidget>
0021 
0022 #include "kmmainwidget.h"
0023 
0024 #include <mailcommon/mailcommonsettings_base.h>
0025 
0026 #include <MessageViewer/ConfigureWidget>
0027 #include <MessageViewer/MessageViewerSettings>
0028 
0029 #include "settings/kmailsettings.h"
0030 #include <MessageCore/ColorUtil>
0031 #include <MessageCore/MessageCoreSettings>
0032 #include <MessageList/MessageListUtil>
0033 
0034 #include <MailCommon/MailUtil>
0035 
0036 #include <Akonadi/Tag>
0037 #include <Akonadi/TagAttribute>
0038 #include <Akonadi/TagCreateJob>
0039 #include <Akonadi/TagDeleteJob>
0040 #include <Akonadi/TagFetchJob>
0041 #include <Akonadi/TagFetchScope>
0042 #include <Akonadi/TagModifyJob>
0043 
0044 #include "kmail_debug.h"
0045 #include <KFontChooser>
0046 #include <KIconButton>
0047 #include <KKeySequenceWidget>
0048 #include <KLocalizedString>
0049 #include <KMessageBox>
0050 #include <KSeparator>
0051 #include <QHBoxLayout>
0052 #include <QIcon>
0053 #include <QLineEdit>
0054 
0055 #include <KMime/DateFormatter>
0056 
0057 #include <KLazyLocalizedString>
0058 #include <QButtonGroup>
0059 #include <QCheckBox>
0060 #include <QFontDatabase>
0061 #include <QFormLayout>
0062 #include <QGroupBox>
0063 #include <QLabel>
0064 #include <QMenu>
0065 #include <QRadioButton>
0066 #include <QSpinBox>
0067 #include <QVBoxLayout>
0068 #include <QWhatsThis>
0069 
0070 using KMime::DateFormatter;
0071 using namespace MailCommon;
0072 
0073 QString AppearancePage::helpAnchor() const
0074 {
0075     return QStringLiteral("configure-appearance");
0076 }
0077 
0078 AppearancePage::AppearancePage(QObject *parent, const KPluginMetaData &data)
0079     : ConfigModuleWithTabs(parent, data)
0080 {
0081     //
0082     // "General" tab:
0083     //
0084     auto readerTab = new AppearancePageGeneralTab();
0085     addTab(readerTab, i18n("General"));
0086     addConfig(MessageViewer::MessageViewerSettings::self(), readerTab);
0087 
0088     //
0089     // "Fonts" tab:
0090     //
0091     auto fontsTab = new AppearancePageFontsTab();
0092     addTab(fontsTab, i18n("Fonts"));
0093 
0094     //
0095     // "Colors" tab:
0096     //
0097     auto colorsTab = new AppearancePageColorsTab();
0098     addTab(colorsTab, i18n("Colors"));
0099 
0100     //
0101     // "Layout" tab:
0102     //
0103     auto layoutTab = new AppearancePageLayoutTab();
0104     addTab(layoutTab, i18n("Layout"));
0105 
0106     //
0107     // "Headers" tab:
0108     //
0109     auto headersTab = new AppearancePageHeadersTab();
0110     addTab(headersTab, i18n("Message List"));
0111 
0112     //
0113     // "Message Tag" tab:
0114     //
0115     auto messageTagTab = new AppearancePageMessageTagTab();
0116     addTab(messageTagTab, i18n("Message Tags"));
0117 }
0118 
0119 QString AppearancePageFontsTab::helpAnchor() const
0120 {
0121     return QStringLiteral("configure-appearance-fonts");
0122 }
0123 
0124 static const struct {
0125     const char *configName;
0126     const KLazyLocalizedString displayName;
0127     bool enableFamilyAndSize;
0128     bool onlyFixed;
0129 } fontNames[] = {
0130     {"body-font", kli18n("Message Body"), true, false},
0131     {"MessageListFont", kli18n("Message List"), true, false},
0132     {"UnreadMessageFont", kli18n("Message List - Unread Messages"), false, false},
0133     {"ImportantMessageFont", kli18n("Message List - Important Messages"), false, false},
0134     {"TodoMessageFont", kli18n("Message List - Action Item Messages"), false, false},
0135     {"fixed-font", kli18n("Fixed Width Font"), true, true},
0136     {"composer-font", kli18n("Composer"), true, false},
0137     {"print-font", kli18n("Printing Output"), true, false},
0138 };
0139 static const int numFontNames = sizeof fontNames / sizeof *fontNames;
0140 
0141 AppearancePageFontsTab::AppearancePageFontsTab(QWidget *parent)
0142     : ConfigModuleTab(parent)
0143 {
0144     assert(numFontNames == sizeof mFont / sizeof *mFont);
0145 
0146     // "Use custom fonts" checkbox, followed by <hr>
0147     auto vlay = new QVBoxLayout(this);
0148     mCustomFontCheck = new QCheckBox(i18n("&Use custom fonts"), this);
0149     vlay->addWidget(mCustomFontCheck);
0150     vlay->addWidget(new KSeparator(Qt::Horizontal, this));
0151     connect(mCustomFontCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0152 
0153     // "font location" combo box and label:
0154     auto hlay = new QHBoxLayout(); // inherites spacing
0155     vlay->addLayout(hlay);
0156     mFontLocationCombo = new QComboBox(this);
0157     mFontLocationCombo->setEnabled(false); // !mCustomFontCheck->isChecked()
0158 
0159     QStringList fontDescriptions;
0160     fontDescriptions.reserve(numFontNames);
0161     for (int i = 0; i < numFontNames; ++i) {
0162         fontDescriptions << fontNames[i].displayName.toString();
0163     }
0164     mFontLocationCombo->addItems(fontDescriptions);
0165 
0166     auto label = new QLabel(i18n("Apply &to:"), this);
0167     label->setBuddy(mFontLocationCombo);
0168     label->setEnabled(false); // since !mCustomFontCheck->isChecked()
0169     hlay->addWidget(label);
0170 
0171     hlay->addWidget(mFontLocationCombo);
0172     hlay->addStretch(10);
0173     mFontChooser = new KFontChooser(KFontChooser::DisplayFrame, this);
0174     mFontChooser->setMinVisibleItems(4);
0175     mFontChooser->setEnabled(false); // since !mCustomFontCheck->isChecked()
0176     vlay->addWidget(mFontChooser);
0177     connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged);
0178 
0179     // {en,dis}able widgets depending on the state of mCustomFontCheck:
0180     connect(mCustomFontCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled);
0181     connect(mCustomFontCheck, &QAbstractButton::toggled, mFontLocationCombo, &QWidget::setEnabled);
0182     connect(mCustomFontCheck, &QAbstractButton::toggled, mFontChooser, &QWidget::setEnabled);
0183     // load the right font settings into mFontChooser:
0184     connect(mFontLocationCombo, &QComboBox::activated, this, &AppearancePageFontsTab::slotFontSelectorChanged);
0185 }
0186 
0187 void AppearancePageFontsTab::slotFontSelectorChanged(int index)
0188 {
0189     qCDebug(KMAIL_LOG) << "slotFontSelectorChanged() called";
0190     if (index < 0 || index >= mFontLocationCombo->count()) {
0191         return; // Should never happen, but it is better to check.
0192     }
0193 
0194     // Save current fontselector setting before we install the new:
0195     if (mActiveFontIndex == 0) {
0196         mFont[0] = mFontChooser->font();
0197         // hardcode the family and size of "message body" dependent fonts:
0198         for (int i = 0; i < numFontNames; ++i) {
0199             if (!fontNames[i].enableFamilyAndSize) {
0200                 // ### shall we copy the font and set the save and re-set
0201                 // {regular,italic,bold,bold italic} property or should we
0202                 // copy only family and pointSize?
0203                 mFont[i].setFamily(mFont[0].family());
0204                 mFont[i].setPointSize /*Float?*/ (mFont[0].pointSize /*Float?*/ ());
0205             }
0206         }
0207     } else if (mActiveFontIndex > 0) {
0208         mFont[mActiveFontIndex] = mFontChooser->font();
0209     }
0210     mActiveFontIndex = index;
0211 
0212     // Disonnect so the "Apply" button is not activated by the change
0213     disconnect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged);
0214 
0215     // Display the new setting:
0216     mFontChooser->setFont(mFont[index], fontNames[index].onlyFixed);
0217 
0218     connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged);
0219 
0220     // Disable Family and Size list if we have selected a quote font:
0221     mFontChooser->enableColumn(KFontChooser::FamilyList | KFontChooser::SizeList, fontNames[index].enableFamilyAndSize);
0222 }
0223 
0224 void AppearancePageFontsTab::doLoadOther()
0225 {
0226     if (KMKernel::self()) {
0227         KConfigGroup fonts(KMKernel::self()->config(), QStringLiteral("Fonts"));
0228 
0229         mFont[0] = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
0230         QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
0231 
0232         for (int i = 0; i < numFontNames; ++i) {
0233             const QString configName = QLatin1StringView(fontNames[i].configName);
0234             if (configName == QLatin1StringView("MessageListFont")) {
0235                 mFont[i] = MessageList::MessageListSettings::self()->messageListFont();
0236             } else if (configName == QLatin1StringView("UnreadMessageFont")) {
0237                 mFont[i] = MessageList::MessageListSettings::self()->unreadMessageFont();
0238             } else if (configName == QLatin1StringView("ImportantMessageFont")) {
0239                 mFont[i] = MessageList::MessageListSettings::self()->importantMessageFont();
0240             } else if (configName == QLatin1StringView("TodoMessageFont")) {
0241                 mFont[i] = MessageList::MessageListSettings::self()->todoMessageFont();
0242             } else {
0243                 mFont[i] = fonts.readEntry(configName, (fontNames[i].onlyFixed) ? fixedFont : mFont[0]);
0244             }
0245         }
0246         mCustomFontCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultFonts());
0247         mFontLocationCombo->setCurrentIndex(0);
0248         slotFontSelectorChanged(0);
0249     } else {
0250         setEnabled(false);
0251     }
0252 }
0253 
0254 void AppearancePageFontsTab::save()
0255 {
0256     if (KMKernel::self()) {
0257         KConfigGroup fonts(KMKernel::self()->config(), QStringLiteral("Fonts"));
0258 
0259         // read the current font (might have been modified)
0260         if (mActiveFontIndex >= 0) {
0261             mFont[mActiveFontIndex] = mFontChooser->font();
0262         }
0263 
0264         const bool customFonts = mCustomFontCheck->isChecked();
0265         MessageCore::MessageCoreSettings::self()->setUseDefaultFonts(!customFonts);
0266 
0267         for (int i = 0; i < numFontNames; ++i) {
0268             const QString configName = QLatin1StringView(fontNames[i].configName);
0269             if (customFonts && configName == QLatin1StringView("MessageListFont")) {
0270                 MessageList::MessageListSettings::self()->setMessageListFont(mFont[i]);
0271             } else if (customFonts && configName == QLatin1StringView("UnreadMessageFont")) {
0272                 MessageList::MessageListSettings::self()->setUnreadMessageFont(mFont[i]);
0273             } else if (customFonts && configName == QLatin1StringView("ImportantMessageFont")) {
0274                 MessageList::MessageListSettings::self()->setImportantMessageFont(mFont[i]);
0275             } else if (customFonts && configName == QLatin1StringView("TodoMessageFont")) {
0276                 MessageList::MessageListSettings::self()->setTodoMessageFont(mFont[i]);
0277             } else {
0278                 if (customFonts || fonts.hasKey(configName)) {
0279                     // Don't write font info when we use default fonts, but write
0280                     // if it's already there:
0281                     fonts.writeEntry(configName, mFont[i]);
0282                 }
0283             }
0284         }
0285     }
0286 }
0287 
0288 void AppearancePageFontsTab::doResetToDefaultsOther()
0289 {
0290     mCustomFontCheck->setChecked(false);
0291 }
0292 
0293 QString AppearancePageColorsTab::helpAnchor() const
0294 {
0295     return QStringLiteral("configure-appearance-colors");
0296 }
0297 
0298 static const struct {
0299     const char *configName;
0300     const KLazyLocalizedString displayName;
0301 } colorNames[] = { // adjust doLoadOther if you change this:
0302     {"QuotedText1", kli18n("Quoted Text - First Level")},
0303     {"QuotedText2", kli18n("Quoted Text - Second Level")},
0304     {"QuotedText3", kli18n("Quoted Text - Third Level")},
0305     {"LinkColor", kli18n("Link")},
0306     {"UnreadMessageColor", kli18n("Unread Message")},
0307     {"ImportantMessageColor", kli18n("Important Message")},
0308     {"TodoMessageColor", kli18n("Action Item Message")},
0309     {"ColorbarBackgroundPlain", kli18n("HTML Status Bar Background - No HTML Message")},
0310     {"ColorbarForegroundPlain", kli18n("HTML Status Bar Foreground - No HTML Message")},
0311     {"ColorbarBackgroundHTML", kli18n("HTML Status Bar Background - HTML Message")},
0312     {"ColorbarForegroundHTML", kli18n("HTML Status Bar Foreground - HTML Message")}};
0313 static const int numColorNames = sizeof colorNames / sizeof *colorNames;
0314 
0315 AppearancePageColorsTab::AppearancePageColorsTab(QWidget *parent)
0316     : ConfigModuleTab(parent)
0317 {
0318     // "use custom colors" check box
0319     auto vlay = new QVBoxLayout(this);
0320     mCustomColorCheck = new QCheckBox(i18n("&Use custom colors"), this);
0321     vlay->addWidget(mCustomColorCheck);
0322     connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0323 
0324     mUseInlineStyle = new QCheckBox(i18n("&Do not change color from original HTML mail"), this);
0325     vlay->addWidget(mUseInlineStyle);
0326     connect(mUseInlineStyle, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0327 
0328     // color list box:
0329     mColorList = new ColorListBox(this);
0330     mColorList->setEnabled(false); // since !mCustomColorCheck->isChecked()
0331     for (int i = 0; i < numColorNames; ++i) {
0332         mColorList->addColor(colorNames[i].displayName.toString());
0333     }
0334     vlay->addWidget(mColorList, 1);
0335 
0336     // "recycle colors" check box:
0337     mRecycleColorCheck = new QCheckBox(i18n("Recycle colors on deep &quoting"), this);
0338     mRecycleColorCheck->setEnabled(false);
0339     vlay->addWidget(mRecycleColorCheck);
0340     connect(mRecycleColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0341 
0342     // close to quota threshold
0343     auto hbox = new QHBoxLayout();
0344     vlay->addLayout(hbox);
0345     auto l = new QLabel(i18n("Close to quota threshold:"), this);
0346     hbox->addWidget(l);
0347     mCloseToQuotaThreshold = new QSpinBox(this);
0348     mCloseToQuotaThreshold->setRange(0, 100);
0349     mCloseToQuotaThreshold->setSingleStep(1);
0350     connect(mCloseToQuotaThreshold, &QSpinBox::valueChanged, this, &ConfigModuleTab::slotEmitChanged);
0351     mCloseToQuotaThreshold->setSuffix(i18n("%"));
0352 
0353     hbox->addWidget(mCloseToQuotaThreshold);
0354     hbox->addWidget(new QWidget(this), 2);
0355 
0356     // {en,dir}able widgets depending on the state of mCustomColorCheck:
0357     connect(mCustomColorCheck, &QAbstractButton::toggled, mColorList, &QWidget::setEnabled);
0358     connect(mCustomColorCheck, &QAbstractButton::toggled, mRecycleColorCheck, &QWidget::setEnabled);
0359     connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0360     connect(mColorList, &ColorListBox::changed, this, &ConfigModuleTab::slotEmitChanged);
0361 }
0362 
0363 void AppearancePageColorsTab::doLoadOther()
0364 {
0365     mCustomColorCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultColors());
0366     mUseInlineStyle->setChecked(MessageCore::MessageCoreSettings::self()->useRealHtmlMailColor());
0367     mRecycleColorCheck->setChecked(MessageViewer::MessageViewerSettings::self()->recycleQuoteColors());
0368     mCloseToQuotaThreshold->setValue(KMailSettings::self()->closeToQuotaThreshold());
0369     loadColor(true);
0370 }
0371 
0372 void AppearancePageColorsTab::loadColor(bool loadFromConfig)
0373 {
0374     if (KMKernel::self()) {
0375         KConfigGroup reader(KMKernel::self()->config(), QStringLiteral("Reader"));
0376 
0377         static const QColor defaultColor[numColorNames] = {
0378             MessageCore::ColorUtil::self()->quoteLevel1DefaultTextColor(),
0379             MessageCore::ColorUtil::self()->quoteLevel2DefaultTextColor(),
0380             MessageCore::ColorUtil::self()->quoteLevel3DefaultTextColor(),
0381             MessageCore::ColorUtil::self()->linkColor(), // link
0382             MessageList::Util::unreadDefaultMessageColor(), // unread mgs
0383             MessageList::Util::importantDefaultMessageColor(), // important msg
0384             MessageList::Util::todoDefaultMessageColor(), // action item mgs
0385             Qt::lightGray, // colorbar plain bg
0386             Qt::black, // colorbar plain fg
0387             Qt::black, // colorbar html  bg
0388             Qt::white // colorbar html  fg
0389         };
0390 
0391         for (int i = 0; i < numColorNames; ++i) {
0392             if (loadFromConfig) {
0393                 const QString configName = QLatin1StringView(colorNames[i].configName);
0394                 if (configName == QLatin1StringView("UnreadMessageColor")) {
0395                     mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->unreadMessageColor());
0396                 } else if (configName == QLatin1StringView("ImportantMessageColor")) {
0397                     mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->importantMessageColor());
0398                 } else if (configName == QLatin1StringView("TodoMessageColor")) {
0399                     mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->todoMessageColor());
0400                 } else {
0401                     mColorList->setColorSilently(i, reader.readEntry(configName, defaultColor[i]));
0402                 }
0403             } else {
0404                 mColorList->setColorSilently(i, defaultColor[i]);
0405             }
0406         }
0407     } else {
0408         setEnabled(false);
0409     }
0410 }
0411 
0412 void AppearancePageColorsTab::doResetToDefaultsOther()
0413 {
0414     mCustomColorCheck->setChecked(false);
0415     mUseInlineStyle->setChecked(false);
0416     mRecycleColorCheck->setChecked(false);
0417     mCloseToQuotaThreshold->setValue(80);
0418     loadColor(false);
0419 }
0420 
0421 void AppearancePageColorsTab::save()
0422 {
0423     if (!KMKernel::self()) {
0424         return;
0425     }
0426     KConfigGroup reader(KMKernel::self()->config(), QStringLiteral("Reader"));
0427     bool customColors = mCustomColorCheck->isChecked();
0428     MessageCore::MessageCoreSettings::self()->setUseDefaultColors(!customColors);
0429     MessageCore::MessageCoreSettings::self()->setUseRealHtmlMailColor(mUseInlineStyle->isChecked());
0430 
0431     for (int i = 0; i < numColorNames; ++i) {
0432         const QString configName = QLatin1StringView(colorNames[i].configName);
0433         if (customColors && configName == QLatin1StringView("UnreadMessageColor")) {
0434             MessageList::MessageListSettings::self()->setUnreadMessageColor(mColorList->color(i));
0435         } else if (customColors && configName == QLatin1StringView("ImportantMessageColor")) {
0436             MessageList::MessageListSettings::self()->setImportantMessageColor(mColorList->color(i));
0437         } else if (customColors && configName == QLatin1StringView("TodoMessageColor")) {
0438             MessageList::MessageListSettings::self()->setTodoMessageColor(mColorList->color(i));
0439         } else {
0440             if (customColors || reader.hasKey(configName)) {
0441                 reader.writeEntry(configName, mColorList->color(i));
0442             }
0443         }
0444     }
0445     MessageViewer::MessageViewerSettings::self()->setRecycleQuoteColors(mRecycleColorCheck->isChecked());
0446     KMailSettings::self()->setCloseToQuotaThreshold(mCloseToQuotaThreshold->value());
0447 }
0448 
0449 QString AppearancePageLayoutTab::helpAnchor() const
0450 {
0451     return QStringLiteral("configure-appearance-layout");
0452 }
0453 
0454 AppearancePageLayoutTab::AppearancePageLayoutTab(QWidget *parent)
0455     : ConfigModuleTab(parent)
0456 {
0457     auto formLayout = new QFormLayout(this);
0458 
0459     // "folder list" radio buttons:
0460     const auto folderListItem = KMailSettings::self()->folderListItem();
0461     mFolderListGroup = new QButtonGroup(this);
0462     {
0463         const int numberChoices(folderListItem->choices().size());
0464         for (int i = 0; i < numberChoices; ++i) {
0465             auto button = new QRadioButton(folderListItem->choices().at(i).label, this);
0466             mFolderListGroup->addButton(button, i);
0467             checkLockDown(button, folderListItem);
0468             if (i == 0) {
0469                 formLayout->addRow(folderListItem->label(), button);
0470             } else {
0471                 formLayout->addRow(nullptr, button);
0472             }
0473         }
0474     }
0475 
0476     connect(mFolderListGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
0477 
0478     mFolderQuickSearchCB = new QCheckBox(i18n("Show folder quick search field"), this);
0479     connect(mFolderQuickSearchCB, &QAbstractButton::toggled, this, &ConfigModuleTab::slotEmitChanged);
0480     formLayout->addRow(nullptr, mFolderQuickSearchCB);
0481 
0482     // "favorite folders view mode" radio buttons:
0483     mFavoriteFoldersViewGroup = new QButtonGroup(this);
0484     connect(mFavoriteFoldersViewGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
0485 
0486     auto favoriteFoldersViewHiddenRadio = new QRadioButton(i18n("Never"), this);
0487     mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewHiddenRadio,
0488                                          static_cast<int>(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::HiddenMode));
0489     formLayout->addRow(i18nc("@label", "Show favorite folders view:"), favoriteFoldersViewHiddenRadio);
0490 
0491     auto favoriteFoldersViewIconsRadio = new QRadioButton(i18n("As icons"), this);
0492     mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewIconsRadio,
0493                                          static_cast<int>(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode));
0494     formLayout->addRow(nullptr, favoriteFoldersViewIconsRadio);
0495 
0496     auto favoriteFoldersViewListRadio = new QRadioButton(i18n("As list"), this);
0497     mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewListRadio,
0498                                          static_cast<int>(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode));
0499     formLayout->addRow(nullptr, favoriteFoldersViewListRadio);
0500 
0501     // "folder tooltips" radio buttons:
0502     mFolderToolTipsGroup = new QButtonGroup(this);
0503     connect(mFolderToolTipsGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
0504 
0505     auto folderToolTipsAlwaysRadio = new QRadioButton(i18n("Always"), this);
0506     mFolderToolTipsGroup->addButton(folderToolTipsAlwaysRadio, static_cast<int>(FolderTreeWidget::DisplayAlways));
0507     formLayout->addRow(i18nc("@label", "Folder tooltips:"), folderToolTipsAlwaysRadio);
0508 
0509     auto folderToolTipsNeverRadio = new QRadioButton(i18n("Never"), this);
0510     mFolderToolTipsGroup->addButton(folderToolTipsNeverRadio, static_cast<int>(FolderTreeWidget::DisplayNever));
0511     formLayout->addRow(nullptr, folderToolTipsNeverRadio);
0512 
0513     // "show reader window" radio buttons:
0514     mReaderWindowModeGroup = new QButtonGroup(this);
0515     const auto readerWindowModeItem = KMailSettings::self()->readerWindowModeItem();
0516     const auto readerWindowModeLayout = new QVBoxLayout;
0517     mReaderWindowModeGroup = new QButtonGroup(this);
0518     {
0519         const int numberChoices(readerWindowModeItem->choices().size());
0520         for (int i = 0; i < numberChoices; ++i) {
0521             auto button = new QRadioButton(readerWindowModeItem->choices().at(i).label, this);
0522             mReaderWindowModeGroup->addButton(button, i);
0523             readerWindowModeLayout->addWidget(button);
0524             checkLockDown(button, readerWindowModeItem);
0525 
0526             if (i == 0) {
0527                 formLayout->addRow(readerWindowModeItem->label(), button);
0528             } else {
0529                 formLayout->addRow(nullptr, button);
0530             }
0531         }
0532     }
0533 
0534     connect(mReaderWindowModeGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
0535 }
0536 
0537 void AppearancePageLayoutTab::doLoadOther()
0538 {
0539     mFolderListGroup->buttons().at(KMailSettings::self()->folderListItem()->value())->setChecked(true);
0540     mReaderWindowModeGroup->buttons().at(KMailSettings::self()->readerWindowModeItem()->value())->setChecked(true);
0541 
0542     if (KMKernel::self()) {
0543         const auto item = KMKernel::self()->mailCommonSettings()->favoriteCollectionViewModeItem();
0544         mFavoriteFoldersViewGroup->buttons().at(item->value())->setChecked(true);
0545         if (item->isImmutable()) {
0546             for (int i = 0, count = mFavoriteFoldersViewGroup->buttons().count(); i < count; i++) {
0547                 checkLockDown(mFavoriteFoldersViewGroup->buttons().at(i), item);
0548             }
0549         }
0550     }
0551     loadWidget(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem());
0552     const int checkedFolderToolTipsPolicy = KMailSettings::self()->toolTipDisplayPolicy();
0553     if (checkedFolderToolTipsPolicy >= 0) {
0554         mFolderToolTipsGroup->button(checkedFolderToolTipsPolicy)->setChecked(true);
0555     }
0556 }
0557 
0558 void AppearancePageLayoutTab::save()
0559 {
0560     saveButtonGroup(mFolderListGroup, KMailSettings::self()->folderListItem());
0561     saveButtonGroup(mReaderWindowModeGroup, KMailSettings::self()->readerWindowModeItem());
0562     if (KMKernel::self()) {
0563         saveButtonGroup(mFavoriteFoldersViewGroup, KMKernel::self()->mailCommonSettings()->favoriteCollectionViewModeItem());
0564     }
0565     saveCheckBox(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem());
0566     KMailSettings::self()->setToolTipDisplayPolicy(mFolderToolTipsGroup->checkedId());
0567 }
0568 
0569 //
0570 // Appearance Message List
0571 //
0572 
0573 QString AppearancePageHeadersTab::helpAnchor() const
0574 {
0575     return QStringLiteral("configure-appearance-headers");
0576 }
0577 
0578 static const struct {
0579     const KLazyLocalizedString displayName;
0580     DateFormatter::FormatType dateDisplay;
0581 } dateDisplayConfig[] = {{kli18n("Sta&ndard format (%1)"), KMime::DateFormatter::CTime},
0582                          {kli18n("Locali&zed format (%1)"), KMime::DateFormatter::Localized},
0583                          {kli18n("Smart for&mat (%1)"), KMime::DateFormatter::Fancy},
0584                          {kli18n("C&ustom format:"), KMime::DateFormatter::Custom}};
0585 static const int numDateDisplayConfig = sizeof dateDisplayConfig / sizeof *dateDisplayConfig;
0586 
0587 AppearancePageHeadersTab::AppearancePageHeadersTab(QWidget *parent)
0588     : ConfigModuleTab(parent)
0589 {
0590     auto formLayout = new QFormLayout(this);
0591 
0592     mDisplayMessageToolTips = new QCheckBox(MessageList::MessageListSettings::self()->messageToolTipEnabledItem()->label(), this);
0593     formLayout->addRow(QString(), mDisplayMessageToolTips);
0594 
0595     connect(mDisplayMessageToolTips, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0596 
0597     // "Aggregation"
0598     using MessageList::Utils::AggregationComboBox;
0599     mAggregationComboBox = new AggregationComboBox(this);
0600 
0601     using MessageList::Utils::AggregationConfigButton;
0602     auto aggregationConfigButton = new AggregationConfigButton(this, mAggregationComboBox);
0603 
0604     auto aggregationLayout = new QHBoxLayout();
0605     aggregationLayout->addWidget(mAggregationComboBox, 1);
0606     aggregationLayout->addWidget(aggregationConfigButton, 0);
0607     formLayout->addRow(i18n("Default aggregation:"), aggregationLayout);
0608 
0609     connect(aggregationConfigButton,
0610             &MessageList::Utils::AggregationConfigButton::configureDialogCompleted,
0611             this,
0612             &AppearancePageHeadersTab::slotSelectDefaultAggregation);
0613     connect(mAggregationComboBox, &MessageList::Utils::AggregationComboBox::activated, this, &ConfigModuleTab::slotEmitChanged);
0614 
0615     // "Theme"
0616     using MessageList::Utils::ThemeComboBox;
0617     mThemeComboBox = new ThemeComboBox(this);
0618 
0619     using MessageList::Utils::ThemeConfigButton;
0620     auto themeConfigButton = new ThemeConfigButton(this, mThemeComboBox);
0621 
0622     auto themeLayout = new QHBoxLayout();
0623     themeLayout->addWidget(mThemeComboBox, 1);
0624     themeLayout->addWidget(themeConfigButton, 0);
0625     formLayout->addRow(i18n("Default theme:"), themeLayout);
0626 
0627     connect(themeConfigButton, &MessageList::Utils::ThemeConfigButton::configureDialogCompleted, this, &AppearancePageHeadersTab::slotSelectDefaultTheme);
0628     connect(mThemeComboBox, &MessageList::Utils::ThemeComboBox::activated, this, &ConfigModuleTab::slotEmitChanged);
0629 
0630     // "Date Display" group:
0631     mDateDisplay = new QButtonGroup(this);
0632     mDateDisplay->setExclusive(true);
0633 
0634     for (int i = 0; i < numDateDisplayConfig; ++i) {
0635         const auto label = KLocalizedString(dateDisplayConfig[i].displayName).untranslatedText();
0636 
0637         QString buttonLabel;
0638         if (label.contains(QLatin1StringView("%1"))) {
0639             buttonLabel = KLocalizedString(dateDisplayConfig[i].displayName)
0640                               .subs(DateFormatter::formatCurrentDate(dateDisplayConfig[i].dateDisplay))
0641                               .toString(); // i18n(label, DateFormatter::formatCurrentDate(dateDisplayConfig[i].dateDisplay));
0642         } else {
0643             buttonLabel = KLocalizedString(dateDisplayConfig[i].displayName).toString();
0644         }
0645         if (dateDisplayConfig[i].dateDisplay == DateFormatter::Custom) {
0646             auto hbox = new QWidget(this);
0647             auto hboxHBoxLayout = new QHBoxLayout(hbox);
0648             hboxHBoxLayout->setContentsMargins({});
0649             auto radio = new QRadioButton(buttonLabel, hbox);
0650             hboxHBoxLayout->addWidget(radio);
0651             mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay);
0652 
0653             mCustomDateFormatEdit = new QLineEdit(hbox);
0654             KLineEditEventHandler::catchReturnKey(mCustomDateFormatEdit);
0655             hboxHBoxLayout->addWidget(mCustomDateFormatEdit);
0656             mCustomDateFormatEdit->setEnabled(false);
0657             hboxHBoxLayout->setStretchFactor(mCustomDateFormatEdit, 1);
0658 
0659             connect(radio, &QAbstractButton::toggled, mCustomDateFormatEdit, &QWidget::setEnabled);
0660             connect(mCustomDateFormatEdit, &QLineEdit::textChanged, this, &ConfigModuleTab::slotEmitChanged);
0661 
0662             auto formatHelp = new QLabel(i18n("<qt><a href=\"whatsthis1\">Custom format information...</a></qt>"), hbox);
0663             formatHelp->setContextMenuPolicy(Qt::NoContextMenu);
0664             connect(formatHelp, &QLabel::linkActivated, this, &AppearancePageHeadersTab::slotLinkClicked);
0665             hboxHBoxLayout->addWidget(formatHelp);
0666 
0667             mCustomDateWhatsThis = i18n(
0668                 "<qt><p><strong>These expressions may be used for the date:"
0669                 "</strong></p>"
0670                 "<ul>"
0671                 "<li>d - the day as a number without a leading zero (1-31)</li>"
0672                 "<li>dd - the day as a number with a leading zero (01-31)</li>"
0673                 "<li>ddd - the abbreviated day name (Mon - Sun)</li>"
0674                 "<li>dddd - the long day name (Monday - Sunday)</li>"
0675                 "<li>M - the month as a number without a leading zero (1-12)</li>"
0676                 "<li>MM - the month as a number with a leading zero (01-12)</li>"
0677                 "<li>MMM - the abbreviated month name (Jan - Dec)</li>"
0678                 "<li>MMMM - the long month name (January - December)</li>"
0679                 "<li>yy - the year as a two digit number (00-99)</li>"
0680                 "<li>yyyy - the year as a four digit number (0000-9999)</li>"
0681                 "</ul>"
0682                 "<p><strong>These expressions may be used for the time:"
0683                 "</strong></p> "
0684                 "<ul>"
0685                 "<li>h - the hour without a leading zero (0-23 or 1-12 if AM/PM display)</li>"
0686                 "<li>hh - the hour with a leading zero (00-23 or 01-12 if AM/PM display)</li>"
0687                 "<li>m - the minutes without a leading zero (0-59)</li>"
0688                 "<li>mm - the minutes with a leading zero (00-59)</li>"
0689                 "<li>s - the seconds without a leading zero (0-59)</li>"
0690                 "<li>ss - the seconds with a leading zero (00-59)</li>"
0691                 "<li>z - the milliseconds without leading zeroes (0-999)</li>"
0692                 "<li>zzz - the milliseconds with leading zeroes (000-999)</li>"
0693                 "<li>AP - switch to AM/PM display. AP will be replaced by either \"AM\" or \"PM\".</li>"
0694                 "<li>ap - switch to AM/PM display. ap will be replaced by either \"am\" or \"pm\".</li>"
0695                 "<li>Z - time zone in numeric form (-0500)</li>"
0696                 "</ul>"
0697                 "<p><strong>All other input characters will be ignored."
0698                 "</strong></p></qt>");
0699             mCustomDateFormatEdit->setWhatsThis(mCustomDateWhatsThis);
0700             radio->setWhatsThis(mCustomDateWhatsThis);
0701             formLayout->addRow(QString(), hbox);
0702         } else {
0703             auto radio = new QRadioButton(buttonLabel, mDateDisplayBox);
0704             if (i == 0) {
0705                 formLayout->addRow(i18n("Date Display:"), radio);
0706             } else {
0707                 formLayout->addRow(QString(), radio);
0708             }
0709             mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay);
0710         }
0711     } // end for loop populating mDateDisplay
0712 
0713     connect(mDateDisplay, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
0714 }
0715 
0716 void AppearancePageHeadersTab::slotLinkClicked(const QString &link)
0717 {
0718     if (link == QLatin1StringView("whatsthis1")) {
0719         QWhatsThis::showText(QCursor::pos(), mCustomDateWhatsThis);
0720     }
0721 }
0722 
0723 void AppearancePageHeadersTab::slotSelectDefaultAggregation()
0724 {
0725     // Select current default aggregation.
0726     mAggregationComboBox->selectDefault();
0727 }
0728 
0729 void AppearancePageHeadersTab::slotSelectDefaultTheme()
0730 {
0731     // Select current default theme.
0732     mThemeComboBox->selectDefault();
0733 }
0734 
0735 void AppearancePageHeadersTab::doLoadOther()
0736 {
0737     // "General Options":
0738     loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem());
0739 
0740     // "Aggregation":
0741     slotSelectDefaultAggregation();
0742 
0743     // "Theme":
0744     slotSelectDefaultTheme();
0745 
0746     // "Date Display":
0747     setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat());
0748 }
0749 
0750 void AppearancePageHeadersTab::doLoadFromGlobalSettings()
0751 {
0752     loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem());
0753     // "Aggregation":
0754     slotSelectDefaultAggregation();
0755 
0756     // "Theme":
0757     slotSelectDefaultTheme();
0758 
0759     setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat());
0760 }
0761 
0762 void AppearancePageHeadersTab::setDateDisplay(int num, const QString &format)
0763 {
0764     auto dateDisplay = static_cast<DateFormatter::FormatType>(num);
0765 
0766     // special case: needs text for the line edit:
0767     if (dateDisplay == DateFormatter::Custom) {
0768         mCustomDateFormatEdit->setText(format);
0769     }
0770 
0771     for (int i = 0; i < numDateDisplayConfig; ++i) {
0772         if (dateDisplay == dateDisplayConfig[i].dateDisplay) {
0773             mDateDisplay->button(dateDisplay)->setChecked(true);
0774             return;
0775         }
0776     }
0777     // fell through since none found:
0778     mDateDisplay->button(numDateDisplayConfig - 2)->setChecked(true); // default
0779 }
0780 
0781 void AppearancePageHeadersTab::save()
0782 {
0783     saveCheckBox(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem());
0784 
0785     if (KMKernel::self()) {
0786         KMKernel::self()->savePaneSelection();
0787     }
0788     // "Aggregation"
0789     mAggregationComboBox->writeDefaultConfig();
0790 
0791     // "Theme"
0792     mThemeComboBox->writeDefaultConfig();
0793 
0794     const int dateDisplayID = mDateDisplay->checkedId();
0795     MessageCore::MessageCoreSettings::self()->setDateFormat(dateDisplayID);
0796     MessageCore::MessageCoreSettings::self()->setCustomDateFormat(mCustomDateFormatEdit->text());
0797 }
0798 
0799 //
0800 // Message Window
0801 //
0802 
0803 QString AppearancePageGeneralTab::helpAnchor() const
0804 {
0805     return QStringLiteral("configure-appearance-reader");
0806 }
0807 
0808 AppearancePageGeneralTab::AppearancePageGeneralTab(QWidget *parent)
0809     : ConfigModuleTab(parent)
0810 {
0811     auto topLayout = new QVBoxLayout(this);
0812 
0813     auto readerBox = new QGroupBox(i18n("Message Window"), this);
0814     topLayout->addWidget(readerBox);
0815 
0816     auto readerBoxLayout = new QVBoxLayout(readerBox);
0817 
0818     // "Close message window after replying or forwarding" check box:
0819     populateCheckBox(mCloseAfterReplyOrForwardCheck = new QCheckBox(this), MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem());
0820     mCloseAfterReplyOrForwardCheck->setToolTip(i18n("Close the standalone message window after replying or forwarding the message"));
0821     readerBoxLayout->addWidget(mCloseAfterReplyOrForwardCheck);
0822     connect(mCloseAfterReplyOrForwardCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0823 
0824     mViewerSettings = new MessageViewer::ConfigureWidget;
0825     connect(mViewerSettings, &MessageViewer::ConfigureWidget::settingsChanged, this, &ConfigModuleTab::slotEmitChanged);
0826     readerBoxLayout->addWidget(mViewerSettings);
0827 
0828     auto systrayBox = new QGroupBox(i18n("System Tray"), this);
0829     topLayout->addWidget(systrayBox);
0830 
0831     auto systrayBoxlayout = new QVBoxLayout(systrayBox);
0832 
0833     // "Enable system tray applet" check box
0834     mSystemTrayCheck = new QCheckBox(i18n("Enable system tray icon"), this);
0835     systrayBoxlayout->addWidget(mSystemTrayCheck);
0836 
0837     // "Enable start in system tray" check box
0838     mStartInTrayCheck = new QCheckBox(i18n("Start minimized to tray"));
0839     systrayBoxlayout->addWidget(mStartInTrayCheck);
0840 
0841     // Dependencies between the two checkboxes
0842     connect(mStartInTrayCheck, &QCheckBox::stateChanged, this, [this](int state) {
0843         if (state == Qt::Checked) {
0844             mSystemTrayCheck->setCheckState(Qt::Checked);
0845         }
0846         slotEmitChanged();
0847     });
0848     connect(mSystemTrayCheck, &QCheckBox::stateChanged, this, [this](int state) {
0849         if (state == Qt::Unchecked) {
0850             mStartInTrayCheck->setCheckState(Qt::Unchecked);
0851         }
0852         slotEmitChanged();
0853     });
0854 
0855     // "Enable system tray applet" check box
0856     mShowNumberInTaskBar = new QCheckBox(i18n("Show unread email in Taskbar"), this);
0857     systrayBoxlayout->addWidget(mShowNumberInTaskBar);
0858     connect(mShowNumberInTaskBar, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
0859 
0860     topLayout->addStretch(100); // spacer
0861 }
0862 
0863 void AppearancePageGeneralTab::doResetToDefaultsOther()
0864 {
0865 }
0866 
0867 void AppearancePageGeneralTab::doLoadOther()
0868 {
0869     loadWidget(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem());
0870     loadWidget(mStartInTrayCheck, KMailSettings::self()->startInTrayItem());
0871     loadWidget(mShowNumberInTaskBar, KMailSettings::self()->showUnreadInTaskbarItem());
0872     loadWidget(mCloseAfterReplyOrForwardCheck, MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem());
0873     mViewerSettings->readConfig();
0874 }
0875 
0876 void AppearancePageGeneralTab::save()
0877 {
0878     saveCheckBox(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem());
0879     saveCheckBox(mStartInTrayCheck, KMailSettings::self()->startInTrayItem());
0880     saveCheckBox(mShowNumberInTaskBar, KMailSettings::self()->showUnreadInTaskbarItem());
0881     KMailSettings::self()->save();
0882     saveCheckBox(mCloseAfterReplyOrForwardCheck, MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem());
0883     mViewerSettings->writeConfig();
0884 }
0885 
0886 QString AppearancePageMessageTagTab::helpAnchor() const
0887 {
0888     return QStringLiteral("configure-appearance-messagetag");
0889 }
0890 
0891 TagListWidgetItem::TagListWidgetItem(QListWidget *parent)
0892     : QListWidgetItem(parent)
0893     , mTag(nullptr)
0894 {
0895 }
0896 
0897 TagListWidgetItem::TagListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent)
0898     : QListWidgetItem(icon, text, parent)
0899     , mTag(nullptr)
0900 {
0901 }
0902 
0903 TagListWidgetItem::~TagListWidgetItem() = default;
0904 
0905 void TagListWidgetItem::setKMailTag(const MailCommon::Tag::Ptr &tag)
0906 {
0907     mTag = tag;
0908 }
0909 
0910 MailCommon::Tag::Ptr TagListWidgetItem::kmailTag() const
0911 {
0912     return mTag;
0913 }
0914 
0915 AppearancePageMessageTagTab::AppearancePageMessageTagTab(QWidget *parent)
0916     : ConfigModuleTab(parent)
0917 {
0918     mPreviousTag = -1;
0919     auto maingrid = new QHBoxLayout(this);
0920 
0921     // Lefthand side Listbox and friends
0922 
0923     // Groupbox frame
0924     mTagsGroupBox = new QGroupBox(i18n("A&vailable Tags"), this);
0925     maingrid->addWidget(mTagsGroupBox);
0926     auto tageditgrid = new QVBoxLayout(mTagsGroupBox);
0927 
0928     // Listbox, add, remove row
0929     auto addremovegrid = new QHBoxLayout();
0930     tageditgrid->addLayout(addremovegrid);
0931 
0932     mTagAddLineEdit = new QLineEdit(mTagsGroupBox);
0933     KLineEditEventHandler::catchReturnKey(mTagAddLineEdit);
0934     addremovegrid->addWidget(mTagAddLineEdit);
0935 
0936     mTagAddButton = new QPushButton(mTagsGroupBox);
0937     mTagAddButton->setToolTip(i18n("Add new tag"));
0938     mTagAddButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0939     addremovegrid->addWidget(mTagAddButton);
0940 
0941     mTagRemoveButton = new QPushButton(mTagsGroupBox);
0942     mTagRemoveButton->setToolTip(i18n("Remove selected tag"));
0943     mTagRemoveButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0944     addremovegrid->addWidget(mTagRemoveButton);
0945 
0946     // Up and down buttons
0947     auto updowngrid = new QHBoxLayout();
0948     tageditgrid->addLayout(updowngrid);
0949 
0950     mTagUpButton = new QPushButton(mTagsGroupBox);
0951     mTagUpButton->setToolTip(i18n("Increase tag priority"));
0952     mTagUpButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
0953     mTagUpButton->setAutoRepeat(true);
0954     updowngrid->addWidget(mTagUpButton);
0955 
0956     mTagDownButton = new QPushButton(mTagsGroupBox);
0957     mTagDownButton->setToolTip(i18n("Decrease tag priority"));
0958     mTagDownButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
0959     mTagDownButton->setAutoRepeat(true);
0960     updowngrid->addWidget(mTagDownButton);
0961 
0962     // Listbox for tag names
0963     auto listboxgrid = new QHBoxLayout();
0964     tageditgrid->addLayout(listboxgrid);
0965     mTagListBox = new QListWidget(mTagsGroupBox);
0966     mTagListBox->setDragDropMode(QAbstractItemView::InternalMove);
0967     connect(mTagListBox->model(), &QAbstractItemModel::rowsMoved, this, &AppearancePageMessageTagTab::slotRowsMoved);
0968     connect(mTagListBox, &QListWidget::customContextMenuRequested, this, &AppearancePageMessageTagTab::slotCustomMenuRequested);
0969 
0970     mTagListBox->setMinimumWidth(150);
0971     listboxgrid->addWidget(mTagListBox);
0972     mTagListBox->setContextMenuPolicy(Qt::CustomContextMenu);
0973 
0974     // RHS for individual tag settings
0975 
0976     // Extra VBoxLayout for stretchers around settings
0977     auto tagsettinggrid = new QVBoxLayout();
0978     maingrid->addLayout(tagsettinggrid);
0979 
0980     // Groupbox frame
0981     mTagSettingGroupBox = new QGroupBox(i18n("Ta&g Settings"), this);
0982     tagsettinggrid->addWidget(mTagSettingGroupBox);
0983     QList<KActionCollection *> actionCollections;
0984     if (kmkernel->getKMMainWidget()) {
0985         actionCollections = kmkernel->getKMMainWidget()->actionCollections();
0986     }
0987 
0988     auto lay = new QHBoxLayout(mTagSettingGroupBox);
0989     mTagWidget = new MailCommon::TagWidget(actionCollections, this);
0990     lay->addWidget(mTagWidget);
0991 
0992     connect(mTagWidget, &TagWidget::changed, this, &AppearancePageMessageTagTab::slotEmitChangeCheck);
0993 
0994     // For enabling the add button in case box is non-empty
0995     connect(mTagAddLineEdit, &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotAddLineTextChanged);
0996 
0997     // For on-the-fly updating of tag name in editbox
0998     connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
0999 
1000     connect(mTagWidget, &TagWidget::iconNameChanged, this, &AppearancePageMessageTagTab::slotIconNameChanged);
1001 
1002     connect(mTagAddLineEdit, &QLineEdit::returnPressed, this, &AppearancePageMessageTagTab::slotAddNewTag);
1003 
1004     connect(mTagAddButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotAddNewTag);
1005 
1006     connect(mTagRemoveButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotRemoveTag);
1007 
1008     connect(mTagUpButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagUp);
1009 
1010     connect(mTagDownButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagDown);
1011 
1012     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1013     // Adjust widths for columns
1014     maingrid->setStretchFactor(mTagsGroupBox, 1);
1015     maingrid->setStretchFactor(lay, 1);
1016     tagsettinggrid->addStretch(10);
1017 }
1018 
1019 AppearancePageMessageTagTab::~AppearancePageMessageTagTab() = default;
1020 
1021 void AppearancePageMessageTagTab::slotEmitChangeCheck()
1022 {
1023     slotEmitChanged();
1024 }
1025 
1026 void AppearancePageMessageTagTab::slotCustomMenuRequested(const QPoint &)
1027 {
1028     const int currentIndex = mTagListBox->currentRow();
1029     if (currentIndex >= 0) {
1030         QMenu menu(this);
1031         menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete"), this, &AppearancePageMessageTagTab::slotRemoveTag);
1032         menu.exec(QCursor::pos());
1033     }
1034 }
1035 
1036 void AppearancePageMessageTagTab::slotRowsMoved(const QModelIndex &, int sourcestart, int sourceEnd, const QModelIndex &, int destinationRow)
1037 {
1038     Q_UNUSED(sourceEnd)
1039     Q_UNUSED(sourcestart)
1040     Q_UNUSED(destinationRow)
1041     updateButtons();
1042     slotEmitChangeCheck();
1043 }
1044 
1045 void AppearancePageMessageTagTab::updateButtons()
1046 {
1047     const int currentIndex = mTagListBox->currentRow();
1048 
1049     const bool theFirst = (currentIndex == 0);
1050     const bool theLast = (currentIndex >= (int)mTagListBox->count() - 1);
1051     const bool aFilterIsSelected = (currentIndex >= 0);
1052 
1053     mTagUpButton->setEnabled(aFilterIsSelected && !theFirst);
1054     mTagDownButton->setEnabled(aFilterIsSelected && !theLast);
1055 }
1056 
1057 void AppearancePageMessageTagTab::slotMoveTagUp()
1058 {
1059     const int tmp_index = mTagListBox->currentRow();
1060     if (tmp_index <= 0) {
1061         return;
1062     }
1063     swapTagsInListBox(tmp_index, tmp_index - 1);
1064     updateButtons();
1065 }
1066 
1067 void AppearancePageMessageTagTab::slotMoveTagDown()
1068 {
1069     const int tmp_index = mTagListBox->currentRow();
1070     if ((tmp_index < 0) || (tmp_index >= int(mTagListBox->count()) - 1)) {
1071         return;
1072     }
1073     swapTagsInListBox(tmp_index, tmp_index + 1);
1074     updateButtons();
1075 }
1076 
1077 void AppearancePageMessageTagTab::swapTagsInListBox(const int first, const int second)
1078 {
1079     disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1080     QListWidgetItem *item = mTagListBox->takeItem(first);
1081     // now selected item is at idx(idx-1), so
1082     // insert the other item at idx, ie. above(below).
1083     mPreviousTag = second;
1084     mTagListBox->insertItem(second, item);
1085     mTagListBox->setCurrentRow(second);
1086     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1087     slotEmitChangeCheck();
1088 }
1089 
1090 void AppearancePageMessageTagTab::slotRecordTagSettings(int aIndex)
1091 {
1092     if ((aIndex < 0) || (aIndex >= int(mTagListBox->count()))) {
1093         return;
1094     }
1095     QListWidgetItem *item = mTagListBox->item(aIndex);
1096     auto tagItem = static_cast<TagListWidgetItem *>(item);
1097 
1098     MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag();
1099 
1100     tmp_desc->tagName = tagItem->text();
1101     mTagWidget->recordTagSettings(tmp_desc);
1102 }
1103 
1104 void AppearancePageMessageTagTab::slotUpdateTagSettingWidgets(int aIndex)
1105 {
1106     // Check if selection is valid
1107     if ((aIndex < 0) || (mTagListBox->currentRow() < 0)) {
1108         mTagRemoveButton->setEnabled(false);
1109         mTagUpButton->setEnabled(false);
1110         mTagDownButton->setEnabled(false);
1111 
1112         mTagWidget->setEnabled(false);
1113         return;
1114     }
1115     mTagWidget->setEnabled(true);
1116 
1117     mTagRemoveButton->setEnabled(true);
1118     mTagUpButton->setEnabled((0 != aIndex));
1119     mTagDownButton->setEnabled(((int(mTagListBox->count()) - 1) != aIndex));
1120     QListWidgetItem *item = mTagListBox->currentItem();
1121     auto tagItem = static_cast<TagListWidgetItem *>(item);
1122     MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag();
1123 
1124     disconnect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1125 
1126     mTagWidget->tagNameLineEdit()->setEnabled(!tmp_desc->isImmutable);
1127     mTagWidget->tagNameLineEdit()->setText(tmp_desc->tagName);
1128     connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1129 
1130     mTagWidget->setTagTextColor(tmp_desc->textColor);
1131 
1132     mTagWidget->setTagBackgroundColor(tmp_desc->backgroundColor);
1133 
1134     mTagWidget->setTagTextFormat(tmp_desc->isBold, tmp_desc->isItalic);
1135 
1136     mTagWidget->iconButton()->setEnabled(!tmp_desc->isImmutable);
1137     mTagWidget->iconButton()->setIcon(tmp_desc->iconName);
1138 
1139     mTagWidget->keySequenceWidget()->setEnabled(true);
1140     mTagWidget->keySequenceWidget()->setKeySequence(tmp_desc->shortcut, KKeySequenceWidget::NoValidate);
1141 
1142     mTagWidget->inToolBarCheck()->setEnabled(true);
1143     mTagWidget->inToolBarCheck()->setChecked(tmp_desc->inToolbar);
1144 }
1145 
1146 void AppearancePageMessageTagTab::slotSelectionChanged()
1147 {
1148     mEmitChanges = false;
1149     slotRecordTagSettings(mPreviousTag);
1150     slotUpdateTagSettingWidgets(mTagListBox->currentRow());
1151     mPreviousTag = mTagListBox->currentRow();
1152     mEmitChanges = true;
1153 }
1154 
1155 void AppearancePageMessageTagTab::slotRemoveTag()
1156 {
1157     const int tmp_index = mTagListBox->currentRow();
1158     if (tmp_index >= 0) {
1159         if (KMessageBox::ButtonCode::PrimaryAction
1160             == KMessageBox::questionTwoActions(this,
1161                                                i18n("Do you want to remove tag \'%1\'?", mTagListBox->item(mTagListBox->currentRow())->text()),
1162                                                i18nc("@title:window", "Remove Tag"),
1163                                                KStandardGuiItem::remove(),
1164                                                KStandardGuiItem::cancel())) {
1165             QListWidgetItem *item = mTagListBox->takeItem(mTagListBox->currentRow());
1166             auto tagItem = static_cast<TagListWidgetItem *>(item);
1167             MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag();
1168             if (tmp_desc->tag().isValid()) {
1169                 new Akonadi::TagDeleteJob(tmp_desc->tag());
1170             } else {
1171                 qCWarning(KMAIL_LOG) << "Can't remove tag with invalid akonadi tag";
1172             }
1173             mPreviousTag = -1;
1174 
1175             // Before deleting the current item, make sure the selectionChanged signal
1176             // is disconnected, so that the widgets will not get updated while the
1177             // deletion takes place.
1178             disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1179 
1180             delete item;
1181             connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1182 
1183             slotSelectionChanged();
1184             slotEmitChangeCheck();
1185         }
1186     }
1187 }
1188 
1189 void AppearancePageMessageTagTab::slotDeleteTagJob(KJob *job)
1190 {
1191     if (job->error()) {
1192         qCWarning(KMAIL_LOG) << "Failed to delete tag " << job->errorString();
1193     }
1194 }
1195 
1196 void AppearancePageMessageTagTab::slotNameLineTextChanged(const QString &aText)
1197 {
1198     // If deleted all, leave the first character for the sake of not having an
1199     // empty tag name
1200     if (aText.isEmpty() && !mTagListBox->currentItem()) {
1201         return;
1202     }
1203 
1204     const int count = mTagListBox->count();
1205     for (int i = 0; i < count; ++i) {
1206         if (mTagListBox->item(i)->text() == aText) {
1207             KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists."));
1208             disconnect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1209             mTagWidget->tagNameLineEdit()->setText(mTagListBox->currentItem()->text());
1210             connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1211             return;
1212         }
1213     }
1214 
1215     // Disconnect so the tag information is not saved and reloaded with every
1216     // letter
1217     disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1218 
1219     mTagListBox->currentItem()->setText(aText);
1220     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1221 }
1222 
1223 void AppearancePageMessageTagTab::slotIconNameChanged(const QString &iconName)
1224 {
1225     mTagListBox->currentItem()->setIcon(QIcon::fromTheme(iconName));
1226 }
1227 
1228 void AppearancePageMessageTagTab::slotAddLineTextChanged(const QString &aText)
1229 {
1230     mTagAddButton->setEnabled(!aText.trimmed().isEmpty());
1231 }
1232 
1233 void AppearancePageMessageTagTab::slotAddNewTag()
1234 {
1235     const QString newTagName = mTagAddLineEdit->text().trimmed();
1236     if (newTagName.isEmpty()) {
1237         return;
1238     }
1239     const int count = mTagListBox->count();
1240     for (int i = 0; i < count; ++i) {
1241         if (mTagListBox->item(i)->text() == newTagName) {
1242             KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists."));
1243             return;
1244         }
1245     }
1246 
1247     const int tmp_priority = mTagListBox->count();
1248 
1249     MailCommon::Tag::Ptr tag(Tag::createDefaultTag(newTagName));
1250     tag->priority = tmp_priority;
1251 
1252     slotEmitChangeCheck();
1253     auto newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), newTagName, mTagListBox);
1254     newItem->setKMailTag(tag);
1255     mTagListBox->addItem(newItem);
1256     mTagListBox->setCurrentItem(newItem);
1257     mTagAddLineEdit->clear();
1258 }
1259 
1260 void AppearancePageMessageTagTab::doLoadFromGlobalSettings()
1261 {
1262     mTagListBox->clear();
1263 
1264     auto fetchJob = new Akonadi::TagFetchJob(this);
1265     fetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
1266     connect(fetchJob, &KJob::result, this, &AppearancePageMessageTagTab::slotTagsFetched);
1267 }
1268 
1269 void AppearancePageMessageTagTab::slotTagsFetched(KJob *job)
1270 {
1271     if (job->error()) {
1272         qCWarning(KMAIL_LOG) << "Failed to load tags " << job->errorString();
1273         return;
1274     }
1275     auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
1276 
1277     QList<MailCommon::TagPtr> msgTagList;
1278     const Akonadi::Tag::List tagList = fetchJob->tags();
1279     msgTagList.reserve(tagList.count());
1280     for (const Akonadi::Tag &akonadiTag : tagList) {
1281         MailCommon::Tag::Ptr tag = MailCommon::Tag::fromAkonadi(akonadiTag);
1282         msgTagList.append(tag);
1283     }
1284 
1285     std::sort(msgTagList.begin(), msgTagList.end(), MailCommon::Tag::compare);
1286 
1287     for (const MailCommon::Tag::Ptr &tag : std::as_const(msgTagList)) {
1288         auto newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), tag->tagName, mTagListBox);
1289         newItem->setKMailTag(tag);
1290         if (tag->priority == -1) {
1291             tag->priority = mTagListBox->count() - 1;
1292         }
1293     }
1294 
1295     // Disconnect so that insertItem's do not trigger an update procedure
1296     disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1297 
1298     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1299 
1300     slotUpdateTagSettingWidgets(-1);
1301     // Needed since the previous function doesn't affect add button
1302     mTagAddButton->setEnabled(false);
1303 
1304     // Save the original list
1305     mOriginalMsgTagList.clear();
1306     for (const MailCommon::TagPtr &tag : std::as_const(msgTagList)) {
1307         mOriginalMsgTagList.append(MailCommon::TagPtr(new MailCommon::Tag(*tag)));
1308     }
1309 }
1310 
1311 void AppearancePageMessageTagTab::save()
1312 {
1313     const int currentRow = mTagListBox->currentRow();
1314     if (currentRow < 0) {
1315         return;
1316     }
1317 
1318     const int count = mTagListBox->count();
1319     if (!count) {
1320         return;
1321     }
1322 
1323     QListWidgetItem *item = mTagListBox->currentItem();
1324     if (!item) {
1325         return;
1326     }
1327     slotRecordTagSettings(currentRow);
1328     const int numberOfMsgTagList = count;
1329     for (int i = 0; i < numberOfMsgTagList; ++i) {
1330         auto tagItem = static_cast<TagListWidgetItem *>(mTagListBox->item(i));
1331         if ((i >= mOriginalMsgTagList.count()) || *(tagItem->kmailTag()) != *(mOriginalMsgTagList[i])) {
1332             MailCommon::Tag::Ptr tag = tagItem->kmailTag();
1333             tag->priority = i;
1334             Akonadi::Tag akonadiTag = tag->saveToAkonadi();
1335             if ((*tag).id() > 0) {
1336                 akonadiTag.setId((*tag).id());
1337             }
1338             if (akonadiTag.isValid()) {
1339                 new Akonadi::TagModifyJob(akonadiTag);
1340             } else {
1341                 new Akonadi::TagCreateJob(akonadiTag);
1342             }
1343         }
1344     }
1345 }
1346 
1347 #include "moc_configureappearancepage.cpp"