1 /*
2   SPDX-FileCopyrightText: 2013-2021 Laurent Montel <montel@kde.org>
3 
4   SPDX-License-Identifier: GPL-2.0-only
5 */
6 
7 #include "configureappearancepage.h"
8 #include <PimCommon/ConfigureImmutableWidgetUtils>
9 using namespace PimCommon::ConfigureImmutableWidgetUtils;
10 #include "configuredialog/colorlistbox.h"
11 #include "kmkernel.h"
12 #include "messagelistsettings.h"
13 #include <Libkdepim/LineEditCatchReturnKey>
14 #include <MailCommon/TagWidget>
15 #include <MessageList/AggregationComboBox>
16 #include <MessageList/AggregationConfigButton>
17 #include <MessageList/ThemeComboBox>
18 #include <MessageList/ThemeConfigButton>
19 
20 #include "util.h"
21 #include <MailCommon/FolderTreeWidget>
22 
23 #include "kmmainwidget.h"
24 
25 #include "mailcommonsettings_base.h"
26 
27 #include <MessageViewer/ConfigureWidget>
28 #include <MessageViewer/MessageViewerSettings>
29 
30 #include "settings/kmailsettings.h"
31 #include <MessageCore/ColorUtil>
32 #include <MessageCore/MessageCoreSettings>
33 #include <MessageList/MessageListUtil>
34 
35 #include <MailCommon/MailUtil>
36 
37 #include <Akonadi/Tag>
38 #include <Akonadi/TagAttribute>
39 #include <Akonadi/TagCreateJob>
40 #include <Akonadi/TagDeleteJob>
41 #include <Akonadi/TagFetchJob>
42 #include <Akonadi/TagFetchScope>
43 #include <Akonadi/TagModifyJob>
44 
45 #include "kmail_debug.h"
46 #include <KColorScheme>
47 #include <KFontChooser>
48 #include <KIconButton>
49 #include <KKeySequenceWidget>
50 #include <KLocalizedString>
51 #include <KMessageBox>
52 #include <KSeparator>
53 #include <QHBoxLayout>
54 #include <QIcon>
55 #include <QLineEdit>
56 
57 #include <KMime/DateFormatter>
58 
59 #include <QButtonGroup>
60 #include <QCheckBox>
61 #include <QFontDatabase>
62 #include <QFormLayout>
63 #include <QGroupBox>
64 #include <QLabel>
65 #include <QRadioButton>
66 #include <QSpinBox>
67 #include <QVBoxLayout>
68 #include <QWhatsThis>
69 
70 using KMime::DateFormatter;
71 using namespace MailCommon;
72 
helpAnchor() const73 QString AppearancePage::helpAnchor() const
74 {
75     return QStringLiteral("configure-appearance");
76 }
77 
AppearancePage(QWidget * parent,const QVariantList & args)78 AppearancePage::AppearancePage(QWidget *parent, const QVariantList &args)
79     : ConfigModuleWithTabs(parent, args)
80 {
81     //
82     // "General" tab:
83     //
84     auto readerTab = new AppearancePageGeneralTab();
85     addTab(readerTab, i18n("General"));
86     addConfig(MessageViewer::MessageViewerSettings::self(), readerTab);
87 
88     //
89     // "Fonts" tab:
90     //
91     auto fontsTab = new AppearancePageFontsTab();
92     addTab(fontsTab, i18n("Fonts"));
93 
94     //
95     // "Colors" tab:
96     //
97     auto colorsTab = new AppearancePageColorsTab();
98     addTab(colorsTab, i18n("Colors"));
99 
100     //
101     // "Layout" tab:
102     //
103     auto layoutTab = new AppearancePageLayoutTab();
104     addTab(layoutTab, i18n("Layout"));
105 
106     //
107     // "Headers" tab:
108     //
109     auto headersTab = new AppearancePageHeadersTab();
110     addTab(headersTab, i18n("Message List"));
111 
112     //
113     // "Message Tag" tab:
114     //
115     auto messageTagTab = new AppearancePageMessageTagTab();
116     addTab(messageTagTab, i18n("Message Tags"));
117 }
118 
helpAnchor() const119 QString AppearancePageFontsTab::helpAnchor() const
120 {
121     return QStringLiteral("configure-appearance-fonts");
122 }
123 
124 static const struct {
125     const char *configName;
126     const char *displayName;
127     bool enableFamilyAndSize;
128     bool onlyFixed;
129 } fontNames[] = {
130     {"body-font", I18N_NOOP("Message Body"), true, false},
131     {"MessageListFont", I18N_NOOP("Message List"), true, false},
132     {"UnreadMessageFont", I18N_NOOP("Message List - Unread Messages"), false, false},
133     {"ImportantMessageFont", I18N_NOOP("Message List - Important Messages"), false, false},
134     {"TodoMessageFont", I18N_NOOP("Message List - Action Item Messages"), false, false},
135     {"fixed-font", I18N_NOOP("Fixed Width Font"), true, true},
136     {"composer-font", I18N_NOOP("Composer"), true, false},
137     {"print-font", I18N_NOOP("Printing Output"), true, false},
138 };
139 static const int numFontNames = sizeof fontNames / sizeof *fontNames;
140 
AppearancePageFontsTab(QWidget * parent)141 AppearancePageFontsTab::AppearancePageFontsTab(QWidget *parent)
142     : ConfigModuleTab(parent)
143 {
144     assert(numFontNames == sizeof mFont / sizeof *mFont);
145 
146     // "Use custom fonts" checkbox, followed by <hr>
147     auto vlay = new QVBoxLayout(this);
148     mCustomFontCheck = new QCheckBox(i18n("&Use custom fonts"), this);
149     vlay->addWidget(mCustomFontCheck);
150     vlay->addWidget(new KSeparator(Qt::Horizontal, this));
151     connect(mCustomFontCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
152 
153     // "font location" combo box and label:
154     auto hlay = new QHBoxLayout(); // inherites spacing
155     vlay->addLayout(hlay);
156     mFontLocationCombo = new QComboBox(this);
157     mFontLocationCombo->setEnabled(false); // !mCustomFontCheck->isChecked()
158 
159     QStringList fontDescriptions;
160     fontDescriptions.reserve(numFontNames);
161     for (int i = 0; i < numFontNames; ++i) {
162         fontDescriptions << i18n(fontNames[i].displayName);
163     }
164     mFontLocationCombo->addItems(fontDescriptions);
165 
166     auto label = new QLabel(i18n("Apply &to:"), this);
167     label->setBuddy(mFontLocationCombo);
168     label->setEnabled(false); // since !mCustomFontCheck->isChecked()
169     hlay->addWidget(label);
170 
171     hlay->addWidget(mFontLocationCombo);
172     hlay->addStretch(10);
173     mFontChooser = new KFontChooser(KFontChooser::DisplayFrame, this);
174     mFontChooser->setMinVisibleItems(4);
175     mFontChooser->setEnabled(false); // since !mCustomFontCheck->isChecked()
176     vlay->addWidget(mFontChooser);
177     connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged);
178 
179     // {en,dis}able widgets depending on the state of mCustomFontCheck:
180     connect(mCustomFontCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled);
181     connect(mCustomFontCheck, &QAbstractButton::toggled, mFontLocationCombo, &QWidget::setEnabled);
182     connect(mCustomFontCheck, &QAbstractButton::toggled, mFontChooser, &QWidget::setEnabled);
183     // load the right font settings into mFontChooser:
184     connect(mFontLocationCombo, &QComboBox::activated, this, &AppearancePageFontsTab::slotFontSelectorChanged);
185 }
186 
slotFontSelectorChanged(int index)187 void AppearancePageFontsTab::slotFontSelectorChanged(int index)
188 {
189     qCDebug(KMAIL_LOG) << "slotFontSelectorChanged() called";
190     if (index < 0 || index >= mFontLocationCombo->count()) {
191         return; // Should never happen, but it is better to check.
192     }
193 
194     // Save current fontselector setting before we install the new:
195     if (mActiveFontIndex == 0) {
196         mFont[0] = mFontChooser->font();
197         // hardcode the family and size of "message body" dependent fonts:
198         for (int i = 0; i < numFontNames; ++i) {
199             if (!fontNames[i].enableFamilyAndSize) {
200                 // ### shall we copy the font and set the save and re-set
201                 // {regular,italic,bold,bold italic} property or should we
202                 // copy only family and pointSize?
203                 mFont[i].setFamily(mFont[0].family());
204                 mFont[i].setPointSize /*Float?*/ (mFont[0].pointSize /*Float?*/ ());
205             }
206         }
207     } else if (mActiveFontIndex > 0) {
208         mFont[mActiveFontIndex] = mFontChooser->font();
209     }
210     mActiveFontIndex = index;
211 
212     // Disonnect so the "Apply" button is not activated by the change
213     disconnect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged);
214 
215     // Display the new setting:
216     mFontChooser->setFont(mFont[index], fontNames[index].onlyFixed);
217 
218     connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged);
219 
220     // Disable Family and Size list if we have selected a quote font:
221     mFontChooser->enableColumn(KFontChooser::FamilyList | KFontChooser::SizeList, fontNames[index].enableFamilyAndSize);
222 }
223 
doLoadOther()224 void AppearancePageFontsTab::doLoadOther()
225 {
226     if (KMKernel::self()) {
227         KConfigGroup fonts(KMKernel::self()->config(), "Fonts");
228 
229         mFont[0] = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
230         QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
231 
232         for (int i = 0; i < numFontNames; ++i) {
233             const QString configName = QLatin1String(fontNames[i].configName);
234             if (configName == QLatin1String("MessageListFont")) {
235                 mFont[i] = MessageList::MessageListSettings::self()->messageListFont();
236             } else if (configName == QLatin1String("UnreadMessageFont")) {
237                 mFont[i] = MessageList::MessageListSettings::self()->unreadMessageFont();
238             } else if (configName == QLatin1String("ImportantMessageFont")) {
239                 mFont[i] = MessageList::MessageListSettings::self()->importantMessageFont();
240             } else if (configName == QLatin1String("TodoMessageFont")) {
241                 mFont[i] = MessageList::MessageListSettings::self()->todoMessageFont();
242             } else {
243                 mFont[i] = fonts.readEntry(configName, (fontNames[i].onlyFixed) ? fixedFont : mFont[0]);
244             }
245         }
246         mCustomFontCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultFonts());
247         mFontLocationCombo->setCurrentIndex(0);
248         slotFontSelectorChanged(0);
249     } else {
250         setEnabled(false);
251     }
252 }
253 
save()254 void AppearancePageFontsTab::save()
255 {
256     if (KMKernel::self()) {
257         KConfigGroup fonts(KMKernel::self()->config(), "Fonts");
258 
259         // read the current font (might have been modified)
260         if (mActiveFontIndex >= 0) {
261             mFont[mActiveFontIndex] = mFontChooser->font();
262         }
263 
264         const bool customFonts = mCustomFontCheck->isChecked();
265         MessageCore::MessageCoreSettings::self()->setUseDefaultFonts(!customFonts);
266 
267         for (int i = 0; i < numFontNames; ++i) {
268             const QString configName = QLatin1String(fontNames[i].configName);
269             if (customFonts && configName == QLatin1String("MessageListFont")) {
270                 MessageList::MessageListSettings::self()->setMessageListFont(mFont[i]);
271             } else if (customFonts && configName == QLatin1String("UnreadMessageFont")) {
272                 MessageList::MessageListSettings::self()->setUnreadMessageFont(mFont[i]);
273             } else if (customFonts && configName == QLatin1String("ImportantMessageFont")) {
274                 MessageList::MessageListSettings::self()->setImportantMessageFont(mFont[i]);
275             } else if (customFonts && configName == QLatin1String("TodoMessageFont")) {
276                 MessageList::MessageListSettings::self()->setTodoMessageFont(mFont[i]);
277             } else {
278                 if (customFonts || fonts.hasKey(configName)) {
279                     // Don't write font info when we use default fonts, but write
280                     // if it's already there:
281                     fonts.writeEntry(configName, mFont[i]);
282                 }
283             }
284         }
285     }
286 }
287 
doResetToDefaultsOther()288 void AppearancePageFontsTab::doResetToDefaultsOther()
289 {
290     mCustomFontCheck->setChecked(false);
291 }
292 
helpAnchor() const293 QString AppearancePageColorsTab::helpAnchor() const
294 {
295     return QStringLiteral("configure-appearance-colors");
296 }
297 
298 static const struct {
299     const char *configName;
300     const char *displayName;
301 } colorNames[] = { // adjust doLoadOther if you change this:
302     {"QuotedText1", I18N_NOOP("Quoted Text - First Level")},
303     {"QuotedText2", I18N_NOOP("Quoted Text - Second Level")},
304     {"QuotedText3", I18N_NOOP("Quoted Text - Third Level")},
305     {"LinkColor", I18N_NOOP("Link")},
306     {"UnreadMessageColor", I18N_NOOP("Unread Message")},
307     {"ImportantMessageColor", I18N_NOOP("Important Message")},
308     {"TodoMessageColor", I18N_NOOP("Action Item Message")},
309     {"ColorbarBackgroundPlain", I18N_NOOP("HTML Status Bar Background - No HTML Message")},
310     {"ColorbarForegroundPlain", I18N_NOOP("HTML Status Bar Foreground - No HTML Message")},
311     {"ColorbarBackgroundHTML", I18N_NOOP("HTML Status Bar Background - HTML Message")},
312     {"ColorbarForegroundHTML", I18N_NOOP("HTML Status Bar Foreground - HTML Message")}};
313 static const int numColorNames = sizeof colorNames / sizeof *colorNames;
314 
AppearancePageColorsTab(QWidget * parent)315 AppearancePageColorsTab::AppearancePageColorsTab(QWidget *parent)
316     : ConfigModuleTab(parent)
317 {
318     // "use custom colors" check box
319     auto vlay = new QVBoxLayout(this);
320     mCustomColorCheck = new QCheckBox(i18n("&Use custom colors"), this);
321     vlay->addWidget(mCustomColorCheck);
322     connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
323 
324     mUseInlineStyle = new QCheckBox(i18n("&Do not change color from original HTML mail"), this);
325     vlay->addWidget(mUseInlineStyle);
326     connect(mUseInlineStyle, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
327 
328     // color list box:
329     mColorList = new ColorListBox(this);
330     mColorList->setEnabled(false); // since !mCustomColorCheck->isChecked()
331     for (int i = 0; i < numColorNames; ++i) {
332         mColorList->addColor(i18n(colorNames[i].displayName));
333     }
334     vlay->addWidget(mColorList, 1);
335 
336     // "recycle colors" check box:
337     mRecycleColorCheck = new QCheckBox(i18n("Recycle colors on deep &quoting"), this);
338     mRecycleColorCheck->setEnabled(false);
339     vlay->addWidget(mRecycleColorCheck);
340     connect(mRecycleColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
341 
342     // close to quota threshold
343     auto hbox = new QHBoxLayout();
344     vlay->addLayout(hbox);
345     auto l = new QLabel(i18n("Close to quota threshold:"), this);
346     hbox->addWidget(l);
347     mCloseToQuotaThreshold = new QSpinBox(this);
348     mCloseToQuotaThreshold->setRange(0, 100);
349     mCloseToQuotaThreshold->setSingleStep(1);
350     connect(mCloseToQuotaThreshold, &QSpinBox::valueChanged, this, &ConfigModuleTab::slotEmitChanged);
351     mCloseToQuotaThreshold->setSuffix(i18n("%"));
352 
353     hbox->addWidget(mCloseToQuotaThreshold);
354     hbox->addWidget(new QWidget(this), 2);
355 
356     // {en,dir}able widgets depending on the state of mCustomColorCheck:
357     connect(mCustomColorCheck, &QAbstractButton::toggled, mColorList, &QWidget::setEnabled);
358     connect(mCustomColorCheck, &QAbstractButton::toggled, mRecycleColorCheck, &QWidget::setEnabled);
359     connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
360     connect(mColorList, &ColorListBox::changed, this, &ConfigModuleTab::slotEmitChanged);
361 }
362 
doLoadOther()363 void AppearancePageColorsTab::doLoadOther()
364 {
365     mCustomColorCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultColors());
366     mUseInlineStyle->setChecked(MessageCore::MessageCoreSettings::self()->useRealHtmlMailColor());
367     mRecycleColorCheck->setChecked(MessageViewer::MessageViewerSettings::self()->recycleQuoteColors());
368     mCloseToQuotaThreshold->setValue(KMailSettings::self()->closeToQuotaThreshold());
369     loadColor(true);
370 }
371 
loadColor(bool loadFromConfig)372 void AppearancePageColorsTab::loadColor(bool loadFromConfig)
373 {
374     if (KMKernel::self()) {
375         KConfigGroup reader(KMKernel::self()->config(), "Reader");
376 
377         static const QColor defaultColor[numColorNames] = {
378             MessageCore::ColorUtil::self()->quoteLevel1DefaultTextColor(),
379             MessageCore::ColorUtil::self()->quoteLevel2DefaultTextColor(),
380             MessageCore::ColorUtil::self()->quoteLevel3DefaultTextColor(),
381             MessageCore::ColorUtil::self()->linkColor(), // link
382             MessageList::Util::unreadDefaultMessageColor(), // unread mgs
383             MessageList::Util::importantDefaultMessageColor(), // important msg
384             MessageList::Util::todoDefaultMessageColor(), // action item mgs
385             Qt::lightGray, // colorbar plain bg
386             Qt::black, // colorbar plain fg
387             Qt::black, // colorbar html  bg
388             Qt::white // colorbar html  fg
389         };
390 
391         for (int i = 0; i < numColorNames; ++i) {
392             if (loadFromConfig) {
393                 const QString configName = QLatin1String(colorNames[i].configName);
394                 if (configName == QLatin1String("UnreadMessageColor")) {
395                     mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->unreadMessageColor());
396                 } else if (configName == QLatin1String("ImportantMessageColor")) {
397                     mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->importantMessageColor());
398                 } else if (configName == QLatin1String("TodoMessageColor")) {
399                     mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->todoMessageColor());
400                 } else {
401                     mColorList->setColorSilently(i, reader.readEntry(configName, defaultColor[i]));
402                 }
403             } else {
404                 mColorList->setColorSilently(i, defaultColor[i]);
405             }
406         }
407     } else {
408         setEnabled(false);
409     }
410 }
411 
doResetToDefaultsOther()412 void AppearancePageColorsTab::doResetToDefaultsOther()
413 {
414     mCustomColorCheck->setChecked(false);
415     mUseInlineStyle->setChecked(false);
416     mRecycleColorCheck->setChecked(false);
417     mCloseToQuotaThreshold->setValue(80);
418     loadColor(false);
419 }
420 
save()421 void AppearancePageColorsTab::save()
422 {
423     if (!KMKernel::self()) {
424         return;
425     }
426     KConfigGroup reader(KMKernel::self()->config(), "Reader");
427     bool customColors = mCustomColorCheck->isChecked();
428     MessageCore::MessageCoreSettings::self()->setUseDefaultColors(!customColors);
429     MessageCore::MessageCoreSettings::self()->setUseRealHtmlMailColor(mUseInlineStyle->isChecked());
430 
431     for (int i = 0; i < numColorNames; ++i) {
432         const QString configName = QLatin1String(colorNames[i].configName);
433         if (customColors && configName == QLatin1String("UnreadMessageColor")) {
434             MessageList::MessageListSettings::self()->setUnreadMessageColor(mColorList->color(i));
435         } else if (customColors && configName == QLatin1String("ImportantMessageColor")) {
436             MessageList::MessageListSettings::self()->setImportantMessageColor(mColorList->color(i));
437         } else if (customColors && configName == QLatin1String("TodoMessageColor")) {
438             MessageList::MessageListSettings::self()->setTodoMessageColor(mColorList->color(i));
439         } else {
440             if (customColors || reader.hasKey(configName)) {
441                 reader.writeEntry(configName, mColorList->color(i));
442             }
443         }
444     }
445     MessageViewer::MessageViewerSettings::self()->setRecycleQuoteColors(mRecycleColorCheck->isChecked());
446     KMailSettings::self()->setCloseToQuotaThreshold(mCloseToQuotaThreshold->value());
447 }
448 
helpAnchor() const449 QString AppearancePageLayoutTab::helpAnchor() const
450 {
451     return QStringLiteral("configure-appearance-layout");
452 }
453 
AppearancePageLayoutTab(QWidget * parent)454 AppearancePageLayoutTab::AppearancePageLayoutTab(QWidget *parent)
455     : ConfigModuleTab(parent)
456 {
457     auto vlay = new QVBoxLayout(this);
458 
459     // "folder list" radio buttons:
460     populateButtonGroup(mFolderListGroupBox = new QGroupBox(this),
461                         mFolderListGroup = new QButtonGroup(this),
462                         Qt::Vertical,
463                         KMailSettings::self()->folderListItem());
464     vlay->addWidget(mFolderListGroupBox);
465     connect(mFolderListGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
466 
467     auto folderCBHLayout = new QHBoxLayout;
468     mFolderQuickSearchCB = new QCheckBox(i18n("Show folder quick search field"), this);
469     connect(mFolderQuickSearchCB, &QAbstractButton::toggled, this, &ConfigModuleTab::slotEmitChanged);
470     folderCBHLayout->addWidget(mFolderQuickSearchCB);
471     vlay->addLayout(folderCBHLayout);
472 
473     // "favorite folders view mode" radio buttons:
474     mFavoriteFoldersViewGroupBox = new QGroupBox(this);
475     mFavoriteFoldersViewGroupBox->setTitle(i18n("Show Favorite Folders View"));
476     mFavoriteFoldersViewGroupBox->setLayout(new QVBoxLayout());
477     mFavoriteFoldersViewGroup = new QButtonGroup(this);
478     connect(mFavoriteFoldersViewGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
479 
480     auto favoriteFoldersViewHiddenRadio = new QRadioButton(i18n("Never"), mFavoriteFoldersViewGroupBox);
481     mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewHiddenRadio,
482                                          static_cast<int>(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::HiddenMode));
483     mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewHiddenRadio);
484 
485     auto favoriteFoldersViewIconsRadio = new QRadioButton(i18n("As icons"), mFavoriteFoldersViewGroupBox);
486     mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewIconsRadio,
487                                          static_cast<int>(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode));
488     mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewIconsRadio);
489 
490     auto favoriteFoldersViewListRadio = new QRadioButton(i18n("As list"), mFavoriteFoldersViewGroupBox);
491     mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewListRadio,
492                                          static_cast<int>(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode));
493     mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewListRadio);
494 
495     vlay->addWidget(mFavoriteFoldersViewGroupBox);
496 
497     // "folder tooltips" radio buttons:
498     mFolderToolTipsGroupBox = new QGroupBox(this);
499     mFolderToolTipsGroupBox->setTitle(i18n("Folder Tooltips"));
500     mFolderToolTipsGroupBox->setLayout(new QVBoxLayout());
501     mFolderToolTipsGroup = new QButtonGroup(this);
502     connect(mFolderToolTipsGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
503 
504     auto folderToolTipsAlwaysRadio = new QRadioButton(i18n("Always"), mFolderToolTipsGroupBox);
505     mFolderToolTipsGroup->addButton(folderToolTipsAlwaysRadio, static_cast<int>(FolderTreeWidget::DisplayAlways));
506     mFolderToolTipsGroupBox->layout()->addWidget(folderToolTipsAlwaysRadio);
507 
508     auto folderToolTipsNeverRadio = new QRadioButton(i18n("Never"), mFolderToolTipsGroupBox);
509     mFolderToolTipsGroup->addButton(folderToolTipsNeverRadio, static_cast<int>(FolderTreeWidget::DisplayNever));
510     mFolderToolTipsGroupBox->layout()->addWidget(folderToolTipsNeverRadio);
511 
512     vlay->addWidget(mFolderToolTipsGroupBox);
513 
514     // "show reader window" radio buttons:
515     populateButtonGroup(mReaderWindowModeGroupBox = new QGroupBox(this),
516                         mReaderWindowModeGroup = new QButtonGroup(this),
517                         Qt::Vertical,
518                         KMailSettings::self()->readerWindowModeItem());
519     vlay->addWidget(mReaderWindowModeGroupBox);
520 
521     connect(mReaderWindowModeGroup, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
522 
523     vlay->addStretch(10); // spacer
524 }
525 
doLoadOther()526 void AppearancePageLayoutTab::doLoadOther()
527 {
528     loadWidget(mFolderListGroupBox, mFolderListGroup, KMailSettings::self()->folderListItem());
529     loadWidget(mReaderWindowModeGroupBox, mReaderWindowModeGroup, KMailSettings::self()->readerWindowModeItem());
530     if (KMKernel::self()) {
531         loadWidget(mFavoriteFoldersViewGroupBox, mFavoriteFoldersViewGroup, KMKernel::self()->mailCommonSettings()->favoriteCollectionViewModeItem());
532     }
533     loadWidget(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem());
534     const int checkedFolderToolTipsPolicy = KMailSettings::self()->toolTipDisplayPolicy();
535     if (checkedFolderToolTipsPolicy >= 0) {
536         mFolderToolTipsGroup->button(checkedFolderToolTipsPolicy)->setChecked(true);
537     }
538 }
539 
save()540 void AppearancePageLayoutTab::save()
541 {
542     saveButtonGroup(mFolderListGroup, KMailSettings::self()->folderListItem());
543     saveButtonGroup(mReaderWindowModeGroup, KMailSettings::self()->readerWindowModeItem());
544     if (KMKernel::self()) {
545         saveButtonGroup(mFavoriteFoldersViewGroup, KMKernel::self()->mailCommonSettings()->favoriteCollectionViewModeItem());
546     }
547     saveCheckBox(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem());
548     KMailSettings::self()->setToolTipDisplayPolicy(mFolderToolTipsGroup->checkedId());
549 }
550 
551 //
552 // Appearance Message List
553 //
554 
helpAnchor() const555 QString AppearancePageHeadersTab::helpAnchor() const
556 {
557     return QStringLiteral("configure-appearance-headers");
558 }
559 
560 static const struct {
561     const char *displayName;
562     DateFormatter::FormatType dateDisplay;
563 } dateDisplayConfig[] = {{I18N_NOOP("Sta&ndard format (%1)"), KMime::DateFormatter::CTime},
564                          {I18N_NOOP("Locali&zed format (%1)"), KMime::DateFormatter::Localized},
565                          {I18N_NOOP("Smart for&mat (%1)"), KMime::DateFormatter::Fancy},
566                          {I18N_NOOP("C&ustom format:"), KMime::DateFormatter::Custom}};
567 static const int numDateDisplayConfig = sizeof dateDisplayConfig / sizeof *dateDisplayConfig;
568 
AppearancePageHeadersTab(QWidget * parent)569 AppearancePageHeadersTab::AppearancePageHeadersTab(QWidget *parent)
570     : ConfigModuleTab(parent)
571 {
572     auto formLayout = new QFormLayout(this);
573 
574     mDisplayMessageToolTips = new QCheckBox(MessageList::MessageListSettings::self()->messageToolTipEnabledItem()->label(), this);
575     formLayout->addRow(QString(), mDisplayMessageToolTips);
576 
577     connect(mDisplayMessageToolTips, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
578 
579     // "Aggregation"
580     using MessageList::Utils::AggregationComboBox;
581     mAggregationComboBox = new AggregationComboBox(this);
582 
583     using MessageList::Utils::AggregationConfigButton;
584     auto aggregationConfigButton = new AggregationConfigButton(this, mAggregationComboBox);
585 
586     auto aggregationLayout = new QHBoxLayout();
587     aggregationLayout->addWidget(mAggregationComboBox, 1);
588     aggregationLayout->addWidget(aggregationConfigButton, 0);
589     formLayout->addRow(i18n("Default aggregation:"), aggregationLayout);
590 
591     connect(aggregationConfigButton,
592             &MessageList::Utils::AggregationConfigButton::configureDialogCompleted,
593             this,
594             &AppearancePageHeadersTab::slotSelectDefaultAggregation);
595     connect(mAggregationComboBox, &MessageList::Utils::AggregationComboBox::activated, this, &ConfigModuleTab::slotEmitChanged);
596 
597     // "Theme"
598     using MessageList::Utils::ThemeComboBox;
599     mThemeComboBox = new ThemeComboBox(this);
600 
601     using MessageList::Utils::ThemeConfigButton;
602     auto themeConfigButton = new ThemeConfigButton(this, mThemeComboBox);
603 
604     auto themeLayout = new QHBoxLayout();
605     themeLayout->addWidget(mThemeComboBox, 1);
606     themeLayout->addWidget(themeConfigButton, 0);
607     formLayout->addRow(i18n("Default theme:"), themeLayout);
608 
609     connect(themeConfigButton, &MessageList::Utils::ThemeConfigButton::configureDialogCompleted, this, &AppearancePageHeadersTab::slotSelectDefaultTheme);
610     connect(mThemeComboBox, &MessageList::Utils::ThemeComboBox::activated, this, &ConfigModuleTab::slotEmitChanged);
611 
612     // "Date Display" group:
613     mDateDisplay = new QButtonGroup(this);
614     mDateDisplay->setExclusive(true);
615 
616     for (int i = 0; i < numDateDisplayConfig; ++i) {
617         const char *label = dateDisplayConfig[i].displayName;
618         QString buttonLabel;
619         if (QString::fromLatin1(label).contains(QLatin1String("%1"))) {
620             buttonLabel = i18n(label, DateFormatter::formatCurrentDate(dateDisplayConfig[i].dateDisplay));
621         } else {
622             buttonLabel = i18n(label);
623         }
624         if (dateDisplayConfig[i].dateDisplay == DateFormatter::Custom) {
625             auto hbox = new QWidget(this);
626             auto hboxHBoxLayout = new QHBoxLayout(hbox);
627             hboxHBoxLayout->setContentsMargins({});
628             auto radio = new QRadioButton(buttonLabel, hbox);
629             hboxHBoxLayout->addWidget(radio);
630             mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay);
631 
632             mCustomDateFormatEdit = new QLineEdit(hbox);
633             new KPIM::LineEditCatchReturnKey(mCustomDateFormatEdit, this);
634             hboxHBoxLayout->addWidget(mCustomDateFormatEdit);
635             mCustomDateFormatEdit->setEnabled(false);
636             hboxHBoxLayout->setStretchFactor(mCustomDateFormatEdit, 1);
637 
638             connect(radio, &QAbstractButton::toggled, mCustomDateFormatEdit, &QWidget::setEnabled);
639             connect(mCustomDateFormatEdit, &QLineEdit::textChanged, this, &ConfigModuleTab::slotEmitChanged);
640 
641             auto formatHelp = new QLabel(i18n("<qt><a href=\"whatsthis1\">Custom format information...</a></qt>"), hbox);
642             formatHelp->setContextMenuPolicy(Qt::NoContextMenu);
643             connect(formatHelp, &QLabel::linkActivated, this, &AppearancePageHeadersTab::slotLinkClicked);
644             hboxHBoxLayout->addWidget(formatHelp);
645 
646             mCustomDateWhatsThis = i18n(
647                 "<qt><p><strong>These expressions may be used for the date:"
648                 "</strong></p>"
649                 "<ul>"
650                 "<li>d - the day as a number without a leading zero (1-31)</li>"
651                 "<li>dd - the day as a number with a leading zero (01-31)</li>"
652                 "<li>ddd - the abbreviated day name (Mon - Sun)</li>"
653                 "<li>dddd - the long day name (Monday - Sunday)</li>"
654                 "<li>M - the month as a number without a leading zero (1-12)</li>"
655                 "<li>MM - the month as a number with a leading zero (01-12)</li>"
656                 "<li>MMM - the abbreviated month name (Jan - Dec)</li>"
657                 "<li>MMMM - the long month name (January - December)</li>"
658                 "<li>yy - the year as a two digit number (00-99)</li>"
659                 "<li>yyyy - the year as a four digit number (0000-9999)</li>"
660                 "</ul>"
661                 "<p><strong>These expressions may be used for the time:"
662                 "</strong></p> "
663                 "<ul>"
664                 "<li>h - the hour without a leading zero (0-23 or 1-12 if AM/PM display)</li>"
665                 "<li>hh - the hour with a leading zero (00-23 or 01-12 if AM/PM display)</li>"
666                 "<li>m - the minutes without a leading zero (0-59)</li>"
667                 "<li>mm - the minutes with a leading zero (00-59)</li>"
668                 "<li>s - the seconds without a leading zero (0-59)</li>"
669                 "<li>ss - the seconds with a leading zero (00-59)</li>"
670                 "<li>z - the milliseconds without leading zeroes (0-999)</li>"
671                 "<li>zzz - the milliseconds with leading zeroes (000-999)</li>"
672                 "<li>AP - switch to AM/PM display. AP will be replaced by either \"AM\" or \"PM\".</li>"
673                 "<li>ap - switch to AM/PM display. ap will be replaced by either \"am\" or \"pm\".</li>"
674                 "<li>Z - time zone in numeric form (-0500)</li>"
675                 "</ul>"
676                 "<p><strong>All other input characters will be ignored."
677                 "</strong></p></qt>");
678             mCustomDateFormatEdit->setWhatsThis(mCustomDateWhatsThis);
679             radio->setWhatsThis(mCustomDateWhatsThis);
680             formLayout->addRow(QString(), hbox);
681         } else {
682             auto radio = new QRadioButton(buttonLabel, mDateDisplayBox);
683             if (i == 0) {
684                 formLayout->addRow(i18n("Date Display:"), radio);
685             } else {
686                 formLayout->addRow(QString(), radio);
687             }
688             mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay);
689         }
690     } // end for loop populating mDateDisplay
691 
692     connect(mDateDisplay, &QButtonGroup::buttonClicked, this, &ConfigModuleTab::slotEmitChanged);
693 }
694 
slotLinkClicked(const QString & link)695 void AppearancePageHeadersTab::slotLinkClicked(const QString &link)
696 {
697     if (link == QLatin1String("whatsthis1")) {
698         QWhatsThis::showText(QCursor::pos(), mCustomDateWhatsThis);
699     }
700 }
701 
slotSelectDefaultAggregation()702 void AppearancePageHeadersTab::slotSelectDefaultAggregation()
703 {
704     // Select current default aggregation.
705     mAggregationComboBox->selectDefault();
706 }
707 
slotSelectDefaultTheme()708 void AppearancePageHeadersTab::slotSelectDefaultTheme()
709 {
710     // Select current default theme.
711     mThemeComboBox->selectDefault();
712 }
713 
doLoadOther()714 void AppearancePageHeadersTab::doLoadOther()
715 {
716     // "General Options":
717     loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem());
718 
719     // "Aggregation":
720     slotSelectDefaultAggregation();
721 
722     // "Theme":
723     slotSelectDefaultTheme();
724 
725     // "Date Display":
726     setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat());
727 }
728 
doLoadFromGlobalSettings()729 void AppearancePageHeadersTab::doLoadFromGlobalSettings()
730 {
731     loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem());
732     // "Aggregation":
733     slotSelectDefaultAggregation();
734 
735     // "Theme":
736     slotSelectDefaultTheme();
737 
738     setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat());
739 }
740 
setDateDisplay(int num,const QString & format)741 void AppearancePageHeadersTab::setDateDisplay(int num, const QString &format)
742 {
743     auto dateDisplay = static_cast<DateFormatter::FormatType>(num);
744 
745     // special case: needs text for the line edit:
746     if (dateDisplay == DateFormatter::Custom) {
747         mCustomDateFormatEdit->setText(format);
748     }
749 
750     for (int i = 0; i < numDateDisplayConfig; ++i) {
751         if (dateDisplay == dateDisplayConfig[i].dateDisplay) {
752             mDateDisplay->button(dateDisplay)->setChecked(true);
753             return;
754         }
755     }
756     // fell through since none found:
757     mDateDisplay->button(numDateDisplayConfig - 2)->setChecked(true); // default
758 }
759 
save()760 void AppearancePageHeadersTab::save()
761 {
762     saveCheckBox(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem());
763 
764     if (KMKernel::self()) {
765         KMKernel::self()->savePaneSelection();
766     }
767     // "Aggregation"
768     mAggregationComboBox->writeDefaultConfig();
769 
770     // "Theme"
771     mThemeComboBox->writeDefaultConfig();
772 
773     const int dateDisplayID = mDateDisplay->checkedId();
774     MessageCore::MessageCoreSettings::self()->setDateFormat(dateDisplayID);
775     MessageCore::MessageCoreSettings::self()->setCustomDateFormat(mCustomDateFormatEdit->text());
776 }
777 
778 //
779 // Message Window
780 //
781 
helpAnchor() const782 QString AppearancePageGeneralTab::helpAnchor() const
783 {
784     return QStringLiteral("configure-appearance-reader");
785 }
786 
AppearancePageGeneralTab(QWidget * parent)787 AppearancePageGeneralTab::AppearancePageGeneralTab(QWidget *parent)
788     : ConfigModuleTab(parent)
789 {
790     auto topLayout = new QVBoxLayout(this);
791 
792     auto readerBox = new QGroupBox(i18n("Message Window"), this);
793     topLayout->addWidget(readerBox);
794 
795     auto readerBoxLayout = new QVBoxLayout(readerBox);
796 
797     // "Close message window after replying or forwarding" check box:
798     populateCheckBox(mCloseAfterReplyOrForwardCheck = new QCheckBox(this), MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem());
799     mCloseAfterReplyOrForwardCheck->setToolTip(i18n("Close the standalone message window after replying or forwarding the message"));
800     readerBoxLayout->addWidget(mCloseAfterReplyOrForwardCheck);
801     connect(mCloseAfterReplyOrForwardCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
802 
803     mViewerSettings = new MessageViewer::ConfigureWidget;
804     connect(mViewerSettings, &MessageViewer::ConfigureWidget::settingsChanged, this, &ConfigModuleTab::slotEmitChanged);
805     readerBoxLayout->addWidget(mViewerSettings);
806 
807     auto systrayBox = new QGroupBox(i18n("System Tray"), this);
808     topLayout->addWidget(systrayBox);
809 
810     auto systrayBoxlayout = new QVBoxLayout(systrayBox);
811 
812     // "Enable system tray applet" check box
813     mSystemTrayCheck = new QCheckBox(i18n("Enable system tray icon"), this);
814     systrayBoxlayout->addWidget(mSystemTrayCheck);
815 
816     // "Enable start in system tray" check box
817     mStartInTrayCheck = new QCheckBox(i18n("Start minimized to tray"));
818     systrayBoxlayout->addWidget(mStartInTrayCheck);
819 
820     // Dependencies between the two checkboxes
821     connect(mStartInTrayCheck, &QCheckBox::stateChanged, this, [this](int state) {
822         if (state == Qt::Checked) {
823             mSystemTrayCheck->setCheckState(Qt::Checked);
824         }
825         slotEmitChanged();
826     });
827     connect(mSystemTrayCheck, &QCheckBox::stateChanged, this, [this](int state) {
828         if (state == Qt::Unchecked) {
829             mStartInTrayCheck->setCheckState(Qt::Unchecked);
830         }
831         slotEmitChanged();
832     });
833 
834     // "Enable system tray applet" check box
835     mShowNumberInTaskBar = new QCheckBox(i18n("Show unread email in Taskbar"), this);
836     systrayBoxlayout->addWidget(mShowNumberInTaskBar);
837     connect(mShowNumberInTaskBar, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged);
838 
839     topLayout->addStretch(100); // spacer
840 }
841 
doResetToDefaultsOther()842 void AppearancePageGeneralTab::doResetToDefaultsOther()
843 {
844 }
845 
doLoadOther()846 void AppearancePageGeneralTab::doLoadOther()
847 {
848     loadWidget(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem());
849     loadWidget(mStartInTrayCheck, KMailSettings::self()->startInTrayItem());
850     loadWidget(mShowNumberInTaskBar, KMailSettings::self()->showUnreadInTaskbarItem());
851     loadWidget(mCloseAfterReplyOrForwardCheck, MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem());
852     mViewerSettings->readConfig();
853 }
854 
save()855 void AppearancePageGeneralTab::save()
856 {
857     saveCheckBox(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem());
858     saveCheckBox(mStartInTrayCheck, KMailSettings::self()->startInTrayItem());
859     saveCheckBox(mShowNumberInTaskBar, KMailSettings::self()->showUnreadInTaskbarItem());
860     KMailSettings::self()->save();
861     saveCheckBox(mCloseAfterReplyOrForwardCheck, MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem());
862     mViewerSettings->writeConfig();
863 }
864 
helpAnchor() const865 QString AppearancePageMessageTagTab::helpAnchor() const
866 {
867     return QStringLiteral("configure-appearance-messagetag");
868 }
869 
TagListWidgetItem(QListWidget * parent)870 TagListWidgetItem::TagListWidgetItem(QListWidget *parent)
871     : QListWidgetItem(parent)
872     , mTag(nullptr)
873 {
874 }
875 
TagListWidgetItem(const QIcon & icon,const QString & text,QListWidget * parent)876 TagListWidgetItem::TagListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent)
877     : QListWidgetItem(icon, text, parent)
878     , mTag(nullptr)
879 {
880 }
881 
882 TagListWidgetItem::~TagListWidgetItem() = default;
883 
setKMailTag(const MailCommon::Tag::Ptr & tag)884 void TagListWidgetItem::setKMailTag(const MailCommon::Tag::Ptr &tag)
885 {
886     mTag = tag;
887 }
888 
kmailTag() const889 MailCommon::Tag::Ptr TagListWidgetItem::kmailTag() const
890 {
891     return mTag;
892 }
893 
AppearancePageMessageTagTab(QWidget * parent)894 AppearancePageMessageTagTab::AppearancePageMessageTagTab(QWidget *parent)
895     : ConfigModuleTab(parent)
896 {
897     mPreviousTag = -1;
898     auto maingrid = new QHBoxLayout(this);
899 
900     // Lefthand side Listbox and friends
901 
902     // Groupbox frame
903     mTagsGroupBox = new QGroupBox(i18n("A&vailable Tags"), this);
904     maingrid->addWidget(mTagsGroupBox);
905     auto tageditgrid = new QVBoxLayout(mTagsGroupBox);
906 
907     // Listbox, add, remove row
908     auto addremovegrid = new QHBoxLayout();
909     tageditgrid->addLayout(addremovegrid);
910 
911     mTagAddLineEdit = new QLineEdit(mTagsGroupBox);
912     new KPIM::LineEditCatchReturnKey(mTagAddLineEdit, this);
913     addremovegrid->addWidget(mTagAddLineEdit);
914 
915     mTagAddButton = new QPushButton(mTagsGroupBox);
916     mTagAddButton->setToolTip(i18n("Add new tag"));
917     mTagAddButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
918     addremovegrid->addWidget(mTagAddButton);
919 
920     mTagRemoveButton = new QPushButton(mTagsGroupBox);
921     mTagRemoveButton->setToolTip(i18n("Remove selected tag"));
922     mTagRemoveButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
923     addremovegrid->addWidget(mTagRemoveButton);
924 
925     // Up and down buttons
926     auto updowngrid = new QHBoxLayout();
927     tageditgrid->addLayout(updowngrid);
928 
929     mTagUpButton = new QPushButton(mTagsGroupBox);
930     mTagUpButton->setToolTip(i18n("Increase tag priority"));
931     mTagUpButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
932     mTagUpButton->setAutoRepeat(true);
933     updowngrid->addWidget(mTagUpButton);
934 
935     mTagDownButton = new QPushButton(mTagsGroupBox);
936     mTagDownButton->setToolTip(i18n("Decrease tag priority"));
937     mTagDownButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
938     mTagDownButton->setAutoRepeat(true);
939     updowngrid->addWidget(mTagDownButton);
940 
941     // Listbox for tag names
942     auto listboxgrid = new QHBoxLayout();
943     tageditgrid->addLayout(listboxgrid);
944     mTagListBox = new QListWidget(mTagsGroupBox);
945     mTagListBox->setDragDropMode(QAbstractItemView::InternalMove);
946     connect(mTagListBox->model(), &QAbstractItemModel::rowsMoved, this, &AppearancePageMessageTagTab::slotRowsMoved);
947 
948     mTagListBox->setMinimumWidth(150);
949     listboxgrid->addWidget(mTagListBox);
950 
951     // RHS for individual tag settings
952 
953     // Extra VBoxLayout for stretchers around settings
954     auto tagsettinggrid = new QVBoxLayout();
955     maingrid->addLayout(tagsettinggrid);
956 
957     // Groupbox frame
958     mTagSettingGroupBox = new QGroupBox(i18n("Ta&g Settings"), this);
959     tagsettinggrid->addWidget(mTagSettingGroupBox);
960     QList<KActionCollection *> actionCollections;
961     if (kmkernel->getKMMainWidget()) {
962         actionCollections = kmkernel->getKMMainWidget()->actionCollections();
963     }
964 
965     auto lay = new QHBoxLayout(mTagSettingGroupBox);
966     mTagWidget = new MailCommon::TagWidget(actionCollections, this);
967     lay->addWidget(mTagWidget);
968 
969     connect(mTagWidget, &TagWidget::changed, this, &AppearancePageMessageTagTab::slotEmitChangeCheck);
970 
971     // For enabling the add button in case box is non-empty
972     connect(mTagAddLineEdit, &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotAddLineTextChanged);
973 
974     // For on-the-fly updating of tag name in editbox
975     connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
976 
977     connect(mTagWidget, &TagWidget::iconNameChanged, this, &AppearancePageMessageTagTab::slotIconNameChanged);
978 
979     connect(mTagAddLineEdit, &QLineEdit::returnPressed, this, &AppearancePageMessageTagTab::slotAddNewTag);
980 
981     connect(mTagAddButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotAddNewTag);
982 
983     connect(mTagRemoveButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotRemoveTag);
984 
985     connect(mTagUpButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagUp);
986 
987     connect(mTagDownButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagDown);
988 
989     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
990     // Adjust widths for columns
991     maingrid->setStretchFactor(mTagsGroupBox, 1);
992     maingrid->setStretchFactor(lay, 1);
993     tagsettinggrid->addStretch(10);
994 }
995 
996 AppearancePageMessageTagTab::~AppearancePageMessageTagTab() = default;
997 
slotEmitChangeCheck()998 void AppearancePageMessageTagTab::slotEmitChangeCheck()
999 {
1000     slotEmitChanged();
1001 }
1002 
slotRowsMoved(const QModelIndex &,int sourcestart,int sourceEnd,const QModelIndex &,int destinationRow)1003 void AppearancePageMessageTagTab::slotRowsMoved(const QModelIndex &, int sourcestart, int sourceEnd, const QModelIndex &, int destinationRow)
1004 {
1005     Q_UNUSED(sourceEnd)
1006     Q_UNUSED(sourcestart)
1007     Q_UNUSED(destinationRow)
1008     updateButtons();
1009     slotEmitChangeCheck();
1010 }
1011 
updateButtons()1012 void AppearancePageMessageTagTab::updateButtons()
1013 {
1014     const int currentIndex = mTagListBox->currentRow();
1015 
1016     const bool theFirst = (currentIndex == 0);
1017     const bool theLast = (currentIndex >= (int)mTagListBox->count() - 1);
1018     const bool aFilterIsSelected = (currentIndex >= 0);
1019 
1020     mTagUpButton->setEnabled(aFilterIsSelected && !theFirst);
1021     mTagDownButton->setEnabled(aFilterIsSelected && !theLast);
1022 }
1023 
slotMoveTagUp()1024 void AppearancePageMessageTagTab::slotMoveTagUp()
1025 {
1026     const int tmp_index = mTagListBox->currentRow();
1027     if (tmp_index <= 0) {
1028         return;
1029     }
1030     swapTagsInListBox(tmp_index, tmp_index - 1);
1031     updateButtons();
1032 }
1033 
slotMoveTagDown()1034 void AppearancePageMessageTagTab::slotMoveTagDown()
1035 {
1036     const int tmp_index = mTagListBox->currentRow();
1037     if ((tmp_index < 0) || (tmp_index >= int(mTagListBox->count()) - 1)) {
1038         return;
1039     }
1040     swapTagsInListBox(tmp_index, tmp_index + 1);
1041     updateButtons();
1042 }
1043 
swapTagsInListBox(const int first,const int second)1044 void AppearancePageMessageTagTab::swapTagsInListBox(const int first, const int second)
1045 {
1046     disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1047     QListWidgetItem *item = mTagListBox->takeItem(first);
1048     // now selected item is at idx(idx-1), so
1049     // insert the other item at idx, ie. above(below).
1050     mPreviousTag = second;
1051     mTagListBox->insertItem(second, item);
1052     mTagListBox->setCurrentRow(second);
1053     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1054     slotEmitChangeCheck();
1055 }
1056 
slotRecordTagSettings(int aIndex)1057 void AppearancePageMessageTagTab::slotRecordTagSettings(int aIndex)
1058 {
1059     if ((aIndex < 0) || (aIndex >= int(mTagListBox->count()))) {
1060         return;
1061     }
1062     QListWidgetItem *item = mTagListBox->item(aIndex);
1063     auto tagItem = static_cast<TagListWidgetItem *>(item);
1064 
1065     MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag();
1066 
1067     tmp_desc->tagName = tagItem->text();
1068     mTagWidget->recordTagSettings(tmp_desc);
1069 }
1070 
slotUpdateTagSettingWidgets(int aIndex)1071 void AppearancePageMessageTagTab::slotUpdateTagSettingWidgets(int aIndex)
1072 {
1073     // Check if selection is valid
1074     if ((aIndex < 0) || (mTagListBox->currentRow() < 0)) {
1075         mTagRemoveButton->setEnabled(false);
1076         mTagUpButton->setEnabled(false);
1077         mTagDownButton->setEnabled(false);
1078 
1079         mTagWidget->setEnabled(false);
1080         return;
1081     }
1082     mTagWidget->setEnabled(true);
1083 
1084     mTagRemoveButton->setEnabled(true);
1085     mTagUpButton->setEnabled((0 != aIndex));
1086     mTagDownButton->setEnabled(((int(mTagListBox->count()) - 1) != aIndex));
1087     QListWidgetItem *item = mTagListBox->currentItem();
1088     auto tagItem = static_cast<TagListWidgetItem *>(item);
1089     MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag();
1090 
1091     disconnect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1092 
1093     mTagWidget->tagNameLineEdit()->setEnabled(!tmp_desc->isImmutable);
1094     mTagWidget->tagNameLineEdit()->setText(tmp_desc->tagName);
1095     connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1096 
1097     mTagWidget->setTagTextColor(tmp_desc->textColor);
1098 
1099     mTagWidget->setTagBackgroundColor(tmp_desc->backgroundColor);
1100 
1101     mTagWidget->setTagTextFormat(tmp_desc->isBold, tmp_desc->isItalic);
1102 
1103     mTagWidget->iconButton()->setEnabled(!tmp_desc->isImmutable);
1104     mTagWidget->iconButton()->setIcon(tmp_desc->iconName);
1105 
1106     mTagWidget->keySequenceWidget()->setEnabled(true);
1107     mTagWidget->keySequenceWidget()->setKeySequence(tmp_desc->shortcut, KKeySequenceWidget::NoValidate);
1108 
1109     mTagWidget->inToolBarCheck()->setEnabled(true);
1110     mTagWidget->inToolBarCheck()->setChecked(tmp_desc->inToolbar);
1111 }
1112 
slotSelectionChanged()1113 void AppearancePageMessageTagTab::slotSelectionChanged()
1114 {
1115     mEmitChanges = false;
1116     slotRecordTagSettings(mPreviousTag);
1117     slotUpdateTagSettingWidgets(mTagListBox->currentRow());
1118     mPreviousTag = mTagListBox->currentRow();
1119     mEmitChanges = true;
1120 }
1121 
slotRemoveTag()1122 void AppearancePageMessageTagTab::slotRemoveTag()
1123 {
1124     const int tmp_index = mTagListBox->currentRow();
1125     if (tmp_index >= 0) {
1126         if (KMessageBox::Yes
1127             == KMessageBox::questionYesNo(this,
1128                                           i18n("Do you want to remove tag \'%1\'?", mTagListBox->item(mTagListBox->currentRow())->text()),
1129                                           i18nc("@title:window", "Remove Tag"),
1130                                           KStandardGuiItem::remove(),
1131                                           KStandardGuiItem::cancel())) {
1132             QListWidgetItem *item = mTagListBox->takeItem(mTagListBox->currentRow());
1133             auto tagItem = static_cast<TagListWidgetItem *>(item);
1134             MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag();
1135             if (tmp_desc->tag().isValid()) {
1136                 new Akonadi::TagDeleteJob(tmp_desc->tag());
1137             } else {
1138                 qCWarning(KMAIL_LOG) << "Can't remove tag with invalid akonadi tag";
1139             }
1140             mPreviousTag = -1;
1141 
1142             // Before deleting the current item, make sure the selectionChanged signal
1143             // is disconnected, so that the widgets will not get updated while the
1144             // deletion takes place.
1145             disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1146 
1147             delete item;
1148             connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1149 
1150             slotSelectionChanged();
1151             slotEmitChangeCheck();
1152         }
1153     }
1154 }
1155 
slotDeleteTagJob(KJob * job)1156 void AppearancePageMessageTagTab::slotDeleteTagJob(KJob *job)
1157 {
1158     if (job->error()) {
1159         qCWarning(KMAIL_LOG) << "Failed to delete tag " << job->errorString();
1160     }
1161 }
1162 
slotNameLineTextChanged(const QString & aText)1163 void AppearancePageMessageTagTab::slotNameLineTextChanged(const QString &aText)
1164 {
1165     // If deleted all, leave the first character for the sake of not having an
1166     // empty tag name
1167     if (aText.isEmpty() && !mTagListBox->currentItem()) {
1168         return;
1169     }
1170 
1171     const int count = mTagListBox->count();
1172     for (int i = 0; i < count; ++i) {
1173         if (mTagListBox->item(i)->text() == aText) {
1174             KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists."));
1175             disconnect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1176             mTagWidget->tagNameLineEdit()->setText(mTagListBox->currentItem()->text());
1177             connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged);
1178             return;
1179         }
1180     }
1181 
1182     // Disconnect so the tag information is not saved and reloaded with every
1183     // letter
1184     disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1185 
1186     mTagListBox->currentItem()->setText(aText);
1187     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1188 }
1189 
slotIconNameChanged(const QString & iconName)1190 void AppearancePageMessageTagTab::slotIconNameChanged(const QString &iconName)
1191 {
1192     mTagListBox->currentItem()->setIcon(QIcon::fromTheme(iconName));
1193 }
1194 
slotAddLineTextChanged(const QString & aText)1195 void AppearancePageMessageTagTab::slotAddLineTextChanged(const QString &aText)
1196 {
1197     mTagAddButton->setEnabled(!aText.trimmed().isEmpty());
1198 }
1199 
slotAddNewTag()1200 void AppearancePageMessageTagTab::slotAddNewTag()
1201 {
1202     const QString newTagName = mTagAddLineEdit->text().trimmed();
1203     if (newTagName.isEmpty()) {
1204         return;
1205     }
1206     const int count = mTagListBox->count();
1207     for (int i = 0; i < count; ++i) {
1208         if (mTagListBox->item(i)->text() == newTagName) {
1209             KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists."));
1210             return;
1211         }
1212     }
1213 
1214     const int tmp_priority = mTagListBox->count();
1215 
1216     MailCommon::Tag::Ptr tag(Tag::createDefaultTag(newTagName));
1217     tag->priority = tmp_priority;
1218 
1219     slotEmitChangeCheck();
1220     auto newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), newTagName, mTagListBox);
1221     newItem->setKMailTag(tag);
1222     mTagListBox->addItem(newItem);
1223     mTagListBox->setCurrentItem(newItem);
1224     mTagAddLineEdit->clear();
1225 }
1226 
doLoadFromGlobalSettings()1227 void AppearancePageMessageTagTab::doLoadFromGlobalSettings()
1228 {
1229     mTagListBox->clear();
1230 
1231     auto fetchJob = new Akonadi::TagFetchJob(this);
1232     fetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
1233     connect(fetchJob, &KJob::result, this, &AppearancePageMessageTagTab::slotTagsFetched);
1234 }
1235 
slotTagsFetched(KJob * job)1236 void AppearancePageMessageTagTab::slotTagsFetched(KJob *job)
1237 {
1238     if (job->error()) {
1239         qCWarning(KMAIL_LOG) << "Failed to load tags " << job->errorString();
1240         return;
1241     }
1242     auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
1243 
1244     QList<MailCommon::TagPtr> msgTagList;
1245     const Akonadi::Tag::List tagList = fetchJob->tags();
1246     msgTagList.reserve(tagList.count());
1247     for (const Akonadi::Tag &akonadiTag : tagList) {
1248         MailCommon::Tag::Ptr tag = MailCommon::Tag::fromAkonadi(akonadiTag);
1249         msgTagList.append(tag);
1250     }
1251 
1252     std::sort(msgTagList.begin(), msgTagList.end(), MailCommon::Tag::compare);
1253 
1254     for (const MailCommon::Tag::Ptr &tag : std::as_const(msgTagList)) {
1255         auto newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), tag->tagName, mTagListBox);
1256         newItem->setKMailTag(tag);
1257         if (tag->priority == -1) {
1258             tag->priority = mTagListBox->count() - 1;
1259         }
1260     }
1261 
1262     // Disconnect so that insertItem's do not trigger an update procedure
1263     disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1264 
1265     connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged);
1266 
1267     slotUpdateTagSettingWidgets(-1);
1268     // Needed since the previous function doesn't affect add button
1269     mTagAddButton->setEnabled(false);
1270 
1271     // Save the original list
1272     mOriginalMsgTagList.clear();
1273     for (const MailCommon::TagPtr &tag : std::as_const(msgTagList)) {
1274         mOriginalMsgTagList.append(MailCommon::TagPtr(new MailCommon::Tag(*tag)));
1275     }
1276 }
1277 
save()1278 void AppearancePageMessageTagTab::save()
1279 {
1280     const int currentRow = mTagListBox->currentRow();
1281     if (currentRow < 0) {
1282         return;
1283     }
1284 
1285     const int count = mTagListBox->count();
1286     if (!count) {
1287         return;
1288     }
1289 
1290     QListWidgetItem *item = mTagListBox->currentItem();
1291     if (!item) {
1292         return;
1293     }
1294     slotRecordTagSettings(currentRow);
1295     const int numberOfMsgTagList = count;
1296     for (int i = 0; i < numberOfMsgTagList; ++i) {
1297         auto tagItem = static_cast<TagListWidgetItem *>(mTagListBox->item(i));
1298         if ((i >= mOriginalMsgTagList.count()) || *(tagItem->kmailTag()) != *(mOriginalMsgTagList[i])) {
1299             MailCommon::Tag::Ptr tag = tagItem->kmailTag();
1300             tag->priority = i;
1301             Akonadi::Tag akonadiTag = tag->saveToAkonadi();
1302             if ((*tag).id() > 0) {
1303                 akonadiTag.setId((*tag).id());
1304             }
1305             if (akonadiTag.isValid()) {
1306                 new Akonadi::TagModifyJob(akonadiTag);
1307             } else {
1308                 new Akonadi::TagCreateJob(akonadiTag);
1309             }
1310         }
1311     }
1312 }
1313