1 /******************************************************************************
2  *
3  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *
7  *******************************************************************************/
8 
9 #include "core/manager.h"
10 
11 #include "core/aggregation.h"
12 #include "core/model.h"
13 #include "core/model_p.h"
14 #include "core/storagemodelbase.h"
15 #include "core/theme.h"
16 #include "core/view.h"
17 #include "core/widgetbase.h"
18 #include "messagelistsettings.h"
19 
20 #include "utils/configureaggregationsdialog.h"
21 #include "utils/configureaggregationsdialog_p.h"
22 #include "utils/configurethemesdialog.h"
23 #include "utils/configurethemesdialog_p.h"
24 
25 #include "MessageCore/MessageCoreSettings"
26 
27 #include "messagelistutil.h"
28 #include "messagelistutil_p.h"
29 
30 #include <KMime/DateFormatter> // kdepimlibs
31 
32 #include "messagelist_debug.h"
33 #include <KConfig>
34 #include <KLocalizedString>
35 
36 using namespace MessageList::Core;
37 
38 Manager *Manager::mInstance = nullptr;
39 
Manager()40 Manager::Manager()
41     : QObject()
42 {
43     mInstance = this;
44 
45     mDateFormatter = new KMime::DateFormatter();
46 
47     mCachedLocalizedUnknownText = i18nc("Unknown date", "Unknown");
48 
49     loadConfiguration();
50     connect(MessageListSettings::self(), &MessageListSettings::configChanged, this, &Manager::reloadGlobalConfiguration);
51     connect(MessageCore::MessageCoreSettings::self(), &MessageCore::MessageCoreSettings::configChanged, this, &Manager::reloadGlobalConfiguration);
52 }
53 
~Manager()54 Manager::~Manager()
55 {
56     disconnect(MessageListSettings::self(), &MessageListSettings::configChanged, this, &Manager::reloadGlobalConfiguration);
57     disconnect(MessageCore::MessageCoreSettings::self(), &MessageCore::MessageCoreSettings::configChanged, this, &Manager::reloadGlobalConfiguration);
58 
59     saveConfiguration();
60     removeAllAggregations();
61     removeAllThemes();
62 
63     delete mDateFormatter;
64 
65     mInstance = nullptr;
66 }
67 
registerWidget(Widget * pWidget)68 void Manager::registerWidget(Widget *pWidget)
69 {
70     if (!mInstance) {
71         mInstance = new Manager();
72     }
73 
74     mInstance->mWidgetList.append(pWidget);
75 }
76 
unregisterWidget(Widget * pWidget)77 void Manager::unregisterWidget(Widget *pWidget)
78 {
79     if (!mInstance) {
80         qCWarning(MESSAGELIST_LOG) << ("ERROR: MessageList::Manager::unregisterWidget() called when Manager::mInstance is null");
81         return;
82     }
83 
84     mInstance->mWidgetList.removeAll(pWidget);
85 
86     if (mInstance->mWidgetList.isEmpty()) {
87         delete mInstance;
88         mInstance = nullptr;
89     }
90 }
91 
aggregation(const QString & id)92 const Aggregation *Manager::aggregation(const QString &id)
93 {
94     Aggregation *opt = mAggregations.value(id);
95     if (opt) {
96         return opt;
97     }
98 
99     return defaultAggregation();
100 }
101 
defaultAggregation()102 const Aggregation *Manager::defaultAggregation()
103 {
104     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelAggregationsGroup());
105 
106     const QString aggregationId = conf.readEntry(QStringLiteral("DefaultSet"), "");
107 
108     Aggregation *opt = nullptr;
109 
110     if (!aggregationId.isEmpty()) {
111         opt = mAggregations.value(aggregationId);
112     }
113 
114     if (opt) {
115         return opt;
116     }
117 
118     // try just the first one
119     QMap<QString, Aggregation *>::ConstIterator it = mAggregations.constBegin();
120     if (it != mAggregations.constEnd()) {
121         return *it;
122     }
123 
124     // aargh
125     createDefaultAggregations();
126 
127     return *(mAggregations.constBegin());
128 }
129 
saveAggregationForStorageModel(const Akonadi::Collection & col,const QString & id,bool storageUsesPrivateAggregation)130 void Manager::saveAggregationForStorageModel(const Akonadi::Collection &col, const QString &id, bool storageUsesPrivateAggregation)
131 {
132     if (!col.isValid()) {
133         return;
134     }
135     saveAggregationForStorageModel(QString::number(col.id()), id, storageUsesPrivateAggregation);
136 }
137 
saveAggregationForStorageModel(const StorageModel * storageModel,const QString & id,bool storageUsesPrivateAggregation)138 void Manager::saveAggregationForStorageModel(const StorageModel *storageModel, const QString &id, bool storageUsesPrivateAggregation)
139 {
140     saveAggregationForStorageModel(storageModel->id(), id, storageUsesPrivateAggregation);
141 }
142 
saveAggregationForStorageModel(const QString & modelId,const QString & id,bool storageUsesPrivateAggregation)143 void Manager::saveAggregationForStorageModel(const QString &modelId, const QString &id, bool storageUsesPrivateAggregation)
144 {
145     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelAggregationsGroup());
146 
147     if (storageUsesPrivateAggregation) {
148         conf.writeEntry(MessageList::Util::setForStorageModelConfigName().arg(modelId), id);
149     } else {
150         conf.deleteEntry(MessageList::Util::setForStorageModelConfigName().arg(modelId));
151     }
152 
153     if (!storageUsesPrivateAggregation) {
154         conf.writeEntry(QStringLiteral("DefaultSet"), id);
155     }
156 }
157 
aggregationForStorageModel(const Akonadi::Collection & col,bool * storageUsesPrivateAggregation)158 const Aggregation *Manager::aggregationForStorageModel(const Akonadi::Collection &col, bool *storageUsesPrivateAggregation)
159 {
160     Q_ASSERT(storageUsesPrivateAggregation);
161 
162     *storageUsesPrivateAggregation = false; // this is by default
163 
164     if (!col.isValid()) {
165         return defaultAggregation();
166     }
167     return Manager::aggregationForStorageModel(QString::number(col.id()), storageUsesPrivateAggregation);
168 }
169 
aggregationForStorageModel(const StorageModel * storageModel,bool * storageUsesPrivateAggregation)170 const Aggregation *Manager::aggregationForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateAggregation)
171 {
172     Q_ASSERT(storageUsesPrivateAggregation);
173 
174     *storageUsesPrivateAggregation = false; // this is by default
175 
176     if (!storageModel) {
177         return defaultAggregation();
178     }
179     return Manager::aggregationForStorageModel(storageModel->id(), storageUsesPrivateAggregation);
180 }
181 
aggregationForStorageModel(const QString & storageId,bool * storageUsesPrivateAggregation)182 const Aggregation *Manager::aggregationForStorageModel(const QString &storageId, bool *storageUsesPrivateAggregation)
183 {
184     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelAggregationsGroup());
185 
186     const QString aggregationId = conf.readEntry(MessageList::Util::setForStorageModelConfigName().arg(storageId), "");
187 
188     Aggregation *opt = nullptr;
189 
190     if (!aggregationId.isEmpty()) {
191         // a private aggregation was stored
192         opt = mAggregations.value(aggregationId);
193         *storageUsesPrivateAggregation = (opt != nullptr);
194     }
195 
196     if (opt) {
197         return opt;
198     }
199 
200     // FIXME: If the storageModel is a mailing list, maybe suggest a mailing-list like preset...
201     //        We could even try to guess if the storageModel is a mailing list
202 
203     return defaultAggregation();
204 }
205 
addAggregation(Aggregation * set)206 void Manager::addAggregation(Aggregation *set)
207 {
208     Aggregation *old = mAggregations.value(set->id());
209     delete old;
210     mAggregations.insert(set->id(), set);
211 }
212 
createDefaultAggregations()213 void Manager::createDefaultAggregations()
214 {
215     addAggregation(new Aggregation(i18n("Current Activity, Threaded"),
216                                    i18n("This view uses smart date range groups. "
217                                         "Messages are threaded. "
218                                         "So for example, in \"Today\" you will find all the messages arrived today "
219                                         "and all the threads that have been active today."),
220                                    Aggregation::GroupByDateRange,
221                                    Aggregation::ExpandRecentGroups,
222                                    Aggregation::PerfectReferencesAndSubject,
223                                    Aggregation::MostRecentMessage,
224                                    Aggregation::ExpandThreadsWithUnreadOrImportantMessages,
225                                    Aggregation::FavorInteractivity,
226                                    true));
227 
228     addAggregation(new Aggregation(i18n("Current Activity, Flat"),
229                                    i18n("This view uses smart date range groups. "
230                                         "Messages are not threaded. "
231                                         "So for example, in \"Today\" you will simply find all the messages arrived today."),
232                                    Aggregation::GroupByDateRange,
233                                    Aggregation::ExpandRecentGroups,
234                                    Aggregation::NoThreading,
235                                    Aggregation::MostRecentMessage,
236                                    Aggregation::NeverExpandThreads,
237                                    Aggregation::FavorInteractivity,
238                                    true));
239 
240     addAggregation(new Aggregation(i18n("Activity by Date, Threaded"),
241                                    i18n("This view uses day-by-day groups. "
242                                         "Messages are threaded. "
243                                         "So for example, in \"Today\" you will find all the messages arrived today "
244                                         "and all the threads that have been active today."),
245                                    Aggregation::GroupByDate,
246                                    Aggregation::ExpandRecentGroups,
247                                    Aggregation::PerfectReferencesAndSubject,
248                                    Aggregation::MostRecentMessage,
249                                    Aggregation::ExpandThreadsWithUnreadOrImportantMessages,
250                                    Aggregation::FavorInteractivity,
251                                    true));
252 
253     addAggregation(new Aggregation(i18n("Activity by Date, Flat"),
254                                    i18n("This view uses day-by-day groups. "
255                                         "Messages are not threaded. "
256                                         "So for example, in \"Today\" you will simply find all the messages arrived today."),
257                                    Aggregation::GroupByDate,
258                                    Aggregation::ExpandRecentGroups,
259                                    Aggregation::NoThreading,
260                                    Aggregation::MostRecentMessage,
261                                    Aggregation::NeverExpandThreads,
262                                    Aggregation::FavorInteractivity,
263                                    true));
264 
265     addAggregation(new Aggregation(i18n("Standard Mailing List"),
266                                    i18n("This is a plain and old mailing list view: no groups and heavy threading."),
267                                    Aggregation::NoGrouping,
268                                    Aggregation::NeverExpandGroups,
269                                    Aggregation::PerfectReferencesAndSubject,
270                                    Aggregation::TopmostMessage,
271                                    Aggregation::ExpandThreadsWithUnreadOrImportantMessages,
272                                    Aggregation::FavorInteractivity,
273                                    true));
274 
275     addAggregation(new Aggregation(i18n("Flat Date View"),
276                                    i18n("This is a plain and old list of messages sorted by date: no groups and no threading."),
277                                    Aggregation::NoGrouping,
278                                    Aggregation::NeverExpandGroups,
279                                    Aggregation::NoThreading,
280                                    Aggregation::TopmostMessage,
281                                    Aggregation::NeverExpandThreads,
282                                    Aggregation::FavorInteractivity,
283                                    true
284 
285                                    ));
286 
287     addAggregation(new Aggregation(i18n("Senders/Receivers, Flat"),
288                                    i18n("This view groups the messages by senders or receivers (depending on the folder "
289                                         "type). "
290                                         "Messages are not threaded."),
291                                    Aggregation::GroupBySenderOrReceiver,
292                                    Aggregation::NeverExpandGroups,
293                                    Aggregation::NoThreading,
294                                    Aggregation::TopmostMessage,
295                                    Aggregation::NeverExpandThreads,
296                                    Aggregation::FavorSpeed,
297                                    true));
298 
299     addAggregation(new Aggregation(i18n("Thread Starters"),
300                                    i18n("This view groups the messages in threads and then groups the threads by the starting user."),
301                                    Aggregation::GroupBySenderOrReceiver,
302                                    Aggregation::NeverExpandGroups,
303                                    Aggregation::PerfectReferencesAndSubject,
304                                    Aggregation::TopmostMessage,
305                                    Aggregation::NeverExpandThreads,
306                                    Aggregation::FavorSpeed,
307                                    true));
308 
309     /*
310     FIX THIS
311     addAggregation(
312       new Aggregation(
313           i18n( "Recent Thread Starters" ),
314           i18n( "This view groups the messages in threads and then groups the threads by the starting user. " \
315                 "Groups are sorted by the date of the first thread start. "
316             ),
317           Aggregation::GroupBySenderOrReceiver,
318           Aggregation::SortGroupsByDateTimeOfMostRecent,
319           Aggregation::Descending,
320           Aggregation::PerfectReferencesAndSubject,
321           Aggregation::TopmostMessage,
322           Aggregation::SortMessagesByDateTime,
323           Aggregation::Descending
324         )
325     );
326     */
327 }
328 
removeAllAggregations()329 void Manager::removeAllAggregations()
330 {
331     QMap<QString, Aggregation *>::ConstIterator end(mAggregations.constEnd());
332     for (QMap<QString, Aggregation *>::ConstIterator it = mAggregations.constBegin(); it != end; ++it) {
333         delete (*it);
334     }
335 
336     mAggregations.clear();
337 }
338 
aggregationsConfigurationCompleted()339 void Manager::aggregationsConfigurationCompleted()
340 {
341     if (mAggregations.isEmpty()) {
342         createDefaultAggregations(); // panic
343     }
344 
345     saveConfiguration(); // just to be sure :)
346 
347     // notify all the widgets that they should reload the option set combos
348     Q_EMIT aggregationsChanged();
349 }
350 
sortOrderForStorageModel(const StorageModel * storageModel,bool * storageUsesPrivateSortOrder)351 const SortOrder Manager::sortOrderForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateSortOrder)
352 {
353     Q_ASSERT(storageUsesPrivateSortOrder);
354 
355     *storageUsesPrivateSortOrder = false; // this is by default
356 
357     if (!storageModel) {
358         return SortOrder();
359     }
360 
361     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelSortOrderGroup());
362     SortOrder ret;
363     ret.readConfig(conf, storageModel->id(), storageUsesPrivateSortOrder);
364     return ret;
365 }
366 
saveSortOrderForStorageModel(const StorageModel * storageModel,SortOrder order,bool storageUsesPrivateSortOrder)367 void Manager::saveSortOrderForStorageModel(const StorageModel *storageModel, SortOrder order, bool storageUsesPrivateSortOrder)
368 {
369     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelSortOrderGroup());
370     order.writeConfig(conf, storageModel->id(), storageUsesPrivateSortOrder);
371 }
372 
theme(const QString & id)373 const Theme *Manager::theme(const QString &id)
374 {
375     Theme *opt = mThemes.value(id);
376     if (opt) {
377         return opt;
378     }
379 
380     return defaultTheme();
381 }
382 
defaultTheme()383 const Theme *Manager::defaultTheme()
384 {
385     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelThemesGroup());
386 
387     const QString themeId = conf.readEntry(QStringLiteral("DefaultSet"), "");
388 
389     Theme *opt = nullptr;
390 
391     if (!themeId.isEmpty()) {
392         opt = mThemes.value(themeId);
393     }
394 
395     if (opt) {
396         return opt;
397     }
398 
399     // try just the first one
400     QMap<QString, Theme *>::ConstIterator it = mThemes.constBegin();
401     if (it != mThemes.constEnd()) {
402         return *it;
403     }
404 
405     // aargh
406     createDefaultThemes();
407 
408     it = mThemes.constBegin();
409 
410     Q_ASSERT(it != mThemes.constEnd());
411 
412     return *it;
413 }
414 
saveThemeForStorageModel(int index,const QString & id,bool storageUsesPrivateTheme)415 void Manager::saveThemeForStorageModel(int index, const QString &id, bool storageUsesPrivateTheme)
416 {
417     saveThemeForStorageModel(QString::number(index), id, storageUsesPrivateTheme);
418 }
419 
saveThemeForStorageModel(const StorageModel * storageModel,const QString & id,bool storageUsesPrivateTheme)420 void Manager::saveThemeForStorageModel(const StorageModel *storageModel, const QString &id, bool storageUsesPrivateTheme)
421 {
422     saveThemeForStorageModel(storageModel->id(), id, storageUsesPrivateTheme);
423 }
424 
saveThemeForStorageModel(const QString & storageModelIndex,const QString & id,bool storageUsesPrivateTheme)425 void Manager::saveThemeForStorageModel(const QString &storageModelIndex, const QString &id, bool storageUsesPrivateTheme)
426 {
427     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelThemesGroup());
428 
429     if (storageUsesPrivateTheme) {
430         conf.writeEntry(MessageList::Util::setForStorageModelConfigName().arg(storageModelIndex), id);
431     } else {
432         conf.deleteEntry(MessageList::Util::setForStorageModelConfigName().arg(storageModelIndex));
433     }
434 
435     if (!storageUsesPrivateTheme) {
436         conf.writeEntry(QStringLiteral("DefaultSet"), id);
437     }
438 }
439 
themeForStorageModel(const Akonadi::Collection & col,bool * storageUsesPrivateTheme)440 const Theme *Manager::themeForStorageModel(const Akonadi::Collection &col, bool *storageUsesPrivateTheme)
441 {
442     Q_ASSERT(storageUsesPrivateTheme);
443 
444     *storageUsesPrivateTheme = false; // this is by default
445 
446     if (!col.isValid()) {
447         return defaultTheme();
448     }
449     return Manager::themeForStorageModel(QString::number(col.id()), storageUsesPrivateTheme);
450 }
451 
themeForStorageModel(const StorageModel * storageModel,bool * storageUsesPrivateTheme)452 const Theme *Manager::themeForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateTheme)
453 {
454     Q_ASSERT(storageUsesPrivateTheme);
455 
456     *storageUsesPrivateTheme = false; // this is by default
457 
458     if (!storageModel) {
459         return defaultTheme();
460     }
461     return Manager::themeForStorageModel(storageModel->id(), storageUsesPrivateTheme);
462 }
463 
themeForStorageModel(const QString & id,bool * storageUsesPrivateTheme)464 const Theme *Manager::themeForStorageModel(const QString &id, bool *storageUsesPrivateTheme)
465 {
466     KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelThemesGroup());
467     const QString themeId = conf.readEntry(MessageList::Util::setForStorageModelConfigName().arg(id), "");
468 
469     Theme *opt = nullptr;
470 
471     if (!themeId.isEmpty()) {
472         // a private theme was stored
473         opt = mThemes.value(themeId);
474         *storageUsesPrivateTheme = (opt != nullptr);
475     }
476 
477     if (opt) {
478         return opt;
479     }
480 
481     // FIXME: If the storageModel is a mailing list, maybe suggest a mailing-list like preset...
482     //        We could even try to guess if the storageModel is a mailing list
483 
484     // FIXME: Prefer right-to-left themes when application layout is RTL.
485 
486     return defaultTheme();
487 }
488 
addTheme(Theme * set)489 void Manager::addTheme(Theme *set)
490 {
491     Theme *old = mThemes.value(set->id());
492     delete old;
493     mThemes.insert(set->id(), set);
494 }
495 
add_theme_simple_text_column(Theme * s,const QString & name,Theme::ContentItem::Type type,bool visibleByDefault,SortOrder::MessageSorting messageSorting,bool alignRight,bool addGroupHeaderItem)496 static Theme::Column *add_theme_simple_text_column(Theme *s,
497                                                    const QString &name,
498                                                    Theme::ContentItem::Type type,
499                                                    bool visibleByDefault,
500                                                    SortOrder::MessageSorting messageSorting,
501                                                    bool alignRight,
502                                                    bool addGroupHeaderItem)
503 {
504     auto c = new Theme::Column();
505     c->setLabel(name);
506     c->setVisibleByDefault(visibleByDefault);
507     c->setMessageSorting(messageSorting);
508 
509     auto r = new Theme::Row();
510 
511     auto i = new Theme::ContentItem(type);
512 
513     if (alignRight) {
514         r->addRightItem(i);
515     } else {
516         r->addLeftItem(i);
517     }
518 
519     c->addMessageRow(r);
520 
521     if (addGroupHeaderItem) {
522         auto row = new Theme::Row();
523 
524         auto iRow = new Theme::ContentItem(type);
525 
526         if (alignRight) {
527             row->addRightItem(iRow);
528         } else {
529             row->addLeftItem(iRow);
530         }
531 
532         c->addGroupHeaderRow(row);
533     }
534 
535     s->addColumn(c);
536 
537     return c;
538 }
539 
add_theme_simple_icon_column(Theme * s,const QString & name,const QString & pixmapName,Theme::ContentItem::Type type,bool visibleByDefault,SortOrder::MessageSorting messageSorting)540 static Theme::Column *add_theme_simple_icon_column(Theme *s,
541                                                    const QString &name,
542                                                    const QString &pixmapName,
543                                                    Theme::ContentItem::Type type,
544                                                    bool visibleByDefault,
545                                                    SortOrder::MessageSorting messageSorting)
546 {
547     auto c = new Theme::Column();
548     c->setLabel(name);
549     c->setPixmapName(pixmapName);
550     c->setVisibleByDefault(visibleByDefault);
551     c->setMessageSorting(messageSorting);
552 
553     auto r = new Theme::Row();
554 
555     auto i = new Theme::ContentItem(type);
556     i->setSoftenByBlendingWhenDisabled(true);
557 
558     r->addLeftItem(i);
559 
560     c->addMessageRow(r);
561 
562     s->addColumn(c);
563 
564     return c;
565 }
566 
createDefaultThemes()567 void Manager::createDefaultThemes()
568 {
569     Theme *s;
570     Theme::Column *c;
571     Theme::Row *r;
572     Theme::ContentItem *i;
573 
574     // The "Classic" backward compatible theme
575 
576     s = new Theme(i18nc("Default theme name", "Classic"), i18n("A simple, backward compatible, single row theme"), true /*readOnly*/
577     );
578 
579     c = new Theme::Column();
580     c->setLabel(i18nc("@title:column Subject of messages", "Subject"));
581     c->setMessageSorting(SortOrder::SortMessagesBySubject);
582 
583     r = new Theme::Row();
584     i = new Theme::ContentItem(Theme::ContentItem::ExpandedStateIcon);
585     r->addLeftItem(i);
586     i = new Theme::ContentItem(Theme::ContentItem::GroupHeaderLabel);
587     i->setBold(true);
588     r->addLeftItem(i);
589     c->addGroupHeaderRow(r);
590 
591     r = new Theme::Row();
592     i = new Theme::ContentItem(Theme::ContentItem::CombinedReadRepliedStateIcon);
593     r->addLeftItem(i);
594     i = new Theme::ContentItem(Theme::ContentItem::AttachmentStateIcon);
595     i->setHideWhenDisabled(true);
596     r->addLeftItem(i);
597     i = new Theme::ContentItem(Theme::ContentItem::AnnotationIcon);
598     i->setHideWhenDisabled(true);
599     r->addLeftItem(i);
600     i = new Theme::ContentItem(Theme::ContentItem::InvitationIcon);
601     i->setHideWhenDisabled(true);
602     r->addLeftItem(i);
603     i = new Theme::ContentItem(Theme::ContentItem::SignatureStateIcon);
604     i->setHideWhenDisabled(true);
605     r->addLeftItem(i);
606     i = new Theme::ContentItem(Theme::ContentItem::EncryptionStateIcon);
607     i->setHideWhenDisabled(true);
608     r->addLeftItem(i);
609     i = new Theme::ContentItem(Theme::ContentItem::Subject);
610     r->addLeftItem(i);
611     c->addMessageRow(r);
612 
613     s->addColumn(c);
614 
615     c = add_theme_simple_text_column(s,
616                                      i18n("Sender/Receiver"),
617                                      Theme::ContentItem::SenderOrReceiver,
618                                      true,
619                                      SortOrder::SortMessagesBySenderOrReceiver,
620                                      false,
621                                      false);
622     c->setIsSenderOrReceiver(true);
623     add_theme_simple_text_column(s, i18nc("Sender of a message", "Sender"), Theme::ContentItem::Sender, false, SortOrder::SortMessagesBySender, false, false);
624     add_theme_simple_text_column(s,
625                                  i18nc("Receiver of a message", "Receiver"),
626                                  Theme::ContentItem::Receiver,
627                                  false,
628                                  SortOrder::SortMessagesByReceiver,
629                                  false,
630                                  false);
631     add_theme_simple_text_column(s, i18nc("Date of a message", "Date"), Theme::ContentItem::Date, true, SortOrder::SortMessagesByDateTime, false, false);
632     add_theme_simple_text_column(s,
633                                  i18n("Most Recent Date"),
634                                  Theme::ContentItem::MostRecentDate,
635                                  false,
636                                  SortOrder::SortMessagesByDateTimeOfMostRecent,
637                                  false,
638                                  true);
639     add_theme_simple_text_column(s, i18nc("Size of a message", "Size"), Theme::ContentItem::Size, false, SortOrder::SortMessagesBySize, false, false);
640     add_theme_simple_icon_column(s,
641                                  i18nc("Attachment indication", "Attachment"),
642                                  QStringLiteral("mail-attachment"),
643                                  Theme::ContentItem::AttachmentStateIcon,
644                                  false,
645                                  SortOrder::SortMessagesByAttachmentStatus);
646     add_theme_simple_icon_column(s,
647                                  i18n("Read/Unread"),
648                                  QStringLiteral("mail-mark-unread-new"),
649                                  Theme::ContentItem::ReadStateIcon,
650                                  false,
651                                  SortOrder::SortMessagesByUnreadStatus);
652     add_theme_simple_icon_column(s, i18n("Replied"), QStringLiteral("mail-replied"), Theme::ContentItem::RepliedStateIcon, false, SortOrder::NoMessageSorting);
653     add_theme_simple_icon_column(s,
654                                  i18nc("Message importance indication", "Important"),
655                                  QStringLiteral("mail-mark-important"),
656                                  Theme::ContentItem::ImportantStateIcon,
657                                  false,
658                                  SortOrder::SortMessagesByImportantStatus);
659     add_theme_simple_icon_column(s,
660                                  i18n("Action Item"),
661                                  QStringLiteral("mail-task"),
662                                  Theme::ContentItem::ActionItemStateIcon,
663                                  false,
664                                  SortOrder::SortMessagesByActionItemStatus);
665     add_theme_simple_icon_column(s,
666                                  i18n("Spam/Ham"),
667                                  QStringLiteral("mail-mark-junk"),
668                                  Theme::ContentItem::SpamHamStateIcon,
669                                  false,
670                                  SortOrder::NoMessageSorting);
671     add_theme_simple_icon_column(s,
672                                  i18n("Watched/Ignored"),
673                                  QStringLiteral("mail-thread-watch"),
674                                  Theme::ContentItem::WatchedIgnoredStateIcon,
675                                  false,
676                                  SortOrder::NoMessageSorting);
677     add_theme_simple_icon_column(s,
678                                  i18n("Encryption"),
679                                  QStringLiteral("mail-encrypted-full"),
680                                  Theme::ContentItem::EncryptionStateIcon,
681                                  false,
682                                  SortOrder::NoMessageSorting);
683     add_theme_simple_icon_column(s,
684                                  i18n("Signature"),
685                                  QStringLiteral("mail-signed-verified"),
686                                  Theme::ContentItem::SignatureStateIcon,
687                                  false,
688                                  SortOrder::NoMessageSorting);
689     add_theme_simple_icon_column(s, i18n("Tag List"), QStringLiteral("feed-subscribe"), Theme::ContentItem::TagList, false, SortOrder::NoMessageSorting);
690 
691     s->resetColumnState(); // so it's initially set from defaults
692 
693     addTheme(s);
694 
695     // The Fancy theme
696 
697     s = new Theme(i18n("Smart"), i18n("A smart multiline and multi item theme"), true /*readOnly*/
698     );
699 
700     c = new Theme::Column();
701     c->setLabel(i18n("Message"));
702 
703     r = new Theme::Row();
704     i = new Theme::ContentItem(Theme::ContentItem::ExpandedStateIcon);
705     r->addLeftItem(i);
706     i = new Theme::ContentItem(Theme::ContentItem::GroupHeaderLabel);
707     i->setBold(true);
708     r->addLeftItem(i);
709     c->addGroupHeaderRow(r);
710 
711     r = new Theme::Row();
712     i = new Theme::ContentItem(Theme::ContentItem::Subject);
713     r->addLeftItem(i);
714     i = new Theme::ContentItem(Theme::ContentItem::ReadStateIcon);
715     r->addRightItem(i);
716     i = new Theme::ContentItem(Theme::ContentItem::RepliedStateIcon);
717     i->setHideWhenDisabled(true);
718     r->addRightItem(i);
719     i = new Theme::ContentItem(Theme::ContentItem::AttachmentStateIcon);
720     i->setHideWhenDisabled(true);
721     r->addRightItem(i);
722     i = new Theme::ContentItem(Theme::ContentItem::AnnotationIcon);
723     i->setHideWhenDisabled(true);
724     r->addRightItem(i);
725     i = new Theme::ContentItem(Theme::ContentItem::InvitationIcon);
726     i->setHideWhenDisabled(true);
727     r->addRightItem(i);
728     i = new Theme::ContentItem(Theme::ContentItem::EncryptionStateIcon);
729     i->setHideWhenDisabled(true);
730     r->addRightItem(i);
731     i = new Theme::ContentItem(Theme::ContentItem::SignatureStateIcon);
732     i->setHideWhenDisabled(true);
733     r->addRightItem(i);
734     i = new Theme::ContentItem(Theme::ContentItem::TagList);
735     i->setHideWhenDisabled(true);
736     r->addRightItem(i);
737     c->addMessageRow(r);
738 
739     Theme::Row *firstFancyRow = r; // save it so we can continue adding stuff below (after cloning the theme)
740 
741     r = new Theme::Row();
742     i = new Theme::ContentItem(Theme::ContentItem::SenderOrReceiver);
743     i->setSoftenByBlending(true);
744     i->setItalic(true);
745     r->addLeftItem(i);
746     i = new Theme::ContentItem(Theme::ContentItem::Date);
747     i->setSoftenByBlending(true);
748     i->setItalic(true);
749     r->addRightItem(i);
750     c->addMessageRow(r);
751 
752     s->addColumn(c);
753 
754     // clone the "Fancy theme" here so we'll use it as starting point for the "Fancy with clickable status"
755     auto fancyWithClickableStatus = new Theme(*s);
756     fancyWithClickableStatus->detach();
757     fancyWithClickableStatus->generateUniqueId();
758 
759     // and continue the "Fancy" specific settings
760     r = firstFancyRow;
761 
762     i = new Theme::ContentItem(Theme::ContentItem::ActionItemStateIcon);
763     i->setHideWhenDisabled(true);
764     r->addRightItem(i);
765     i = new Theme::ContentItem(Theme::ContentItem::ImportantStateIcon);
766     i->setHideWhenDisabled(true);
767     r->addRightItem(i);
768     i = new Theme::ContentItem(Theme::ContentItem::SpamHamStateIcon);
769     i->setHideWhenDisabled(true);
770     r->addRightItem(i);
771     i = new Theme::ContentItem(Theme::ContentItem::WatchedIgnoredStateIcon);
772     i->setHideWhenDisabled(true);
773     r->addRightItem(i);
774 
775     s->setViewHeaderPolicy(Theme::NeverShowHeader);
776 
777     s->resetColumnState(); // so it's initially set from defaults
778 
779     addTheme(s);
780 
781     // The "Fancy with Clickable Status" theme
782 
783     s = fancyWithClickableStatus;
784 
785     s->setName(i18n("Smart with Clickable Status"));
786     s->setDescription(i18n("A smart multiline and multi item theme with a clickable status column"));
787     s->setReadOnly(true);
788 
789     c = new Theme::Column();
790     c->setLabel(i18n("Status"));
791     c->setVisibleByDefault(true);
792 
793     r = new Theme::Row();
794     i = new Theme::ContentItem(Theme::ContentItem::ActionItemStateIcon);
795     i->setSoftenByBlendingWhenDisabled(true);
796     r->addLeftItem(i);
797     i = new Theme::ContentItem(Theme::ContentItem::ImportantStateIcon);
798     i->setSoftenByBlendingWhenDisabled(true);
799     r->addLeftItem(i);
800     c->addMessageRow(r);
801 
802     r = new Theme::Row();
803     i = new Theme::ContentItem(Theme::ContentItem::SpamHamStateIcon);
804     i->setSoftenByBlendingWhenDisabled(true);
805     r->addLeftItem(i);
806     i = new Theme::ContentItem(Theme::ContentItem::WatchedIgnoredStateIcon);
807     i->setSoftenByBlendingWhenDisabled(true);
808     r->addLeftItem(i);
809     c->addMessageRow(r);
810 
811     s->addColumn(c);
812 
813     s->resetColumnState(); // so it's initially set from defaults
814 
815     addTheme(s);
816 }
817 
removeAllThemes()818 void Manager::removeAllThemes()
819 {
820     QMap<QString, Theme *>::ConstIterator end(mThemes.constEnd());
821     for (QMap<QString, Theme *>::ConstIterator it = mThemes.constBegin(); it != end; ++it) {
822         delete (*it);
823     }
824 
825     mThemes.clear();
826 }
827 
themesConfigurationCompleted()828 void Manager::themesConfigurationCompleted()
829 {
830     if (mThemes.isEmpty()) {
831         createDefaultThemes(); // panic
832     }
833 
834     saveConfiguration(); // just to be sure :)
835 
836     // notify all the widgets that they should reload the option set combos
837     Q_EMIT themesChanged();
838 }
839 
reloadAllWidgets()840 void Manager::reloadAllWidgets()
841 {
842     QList<Widget *>::ConstIterator end(mWidgetList.constEnd());
843     for (QList<Widget *>::ConstIterator it = mWidgetList.constBegin(); it != end; ++it) {
844         if ((*it)->view()) {
845             (*it)->view()->reload();
846         }
847     }
848 }
849 
reloadGlobalConfiguration()850 void Manager::reloadGlobalConfiguration()
851 {
852     // This is called when configuration changes (probably edited by the options dialog)
853     const int oldDateFormat = (int)mDateFormatter->format();
854     const QString oldDateCustomFormat = mDateFormatter->customFormat();
855 
856     loadGlobalConfiguration();
857 
858     if ((oldDateFormat != (int)mDateFormatter->format()) || (oldDateCustomFormat != mDateFormatter->customFormat())) {
859         reloadAllWidgets();
860     }
861 }
862 
loadGlobalConfiguration()863 void Manager::loadGlobalConfiguration()
864 {
865     // Load the date format
866     const auto type = static_cast<KMime::DateFormatter::FormatType>(MessageCore::MessageCoreSettings::self()->dateFormat());
867     mDateFormatter->setCustomFormat(MessageCore::MessageCoreSettings::self()->customDateFormat());
868     mDateFormatter->setFormat(type);
869 }
870 
loadConfiguration()871 void Manager::loadConfiguration()
872 {
873     loadGlobalConfiguration();
874 
875     {
876         // load Aggregations
877 
878         KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Aggregations");
879 
880         mAggregations.clear();
881 
882         const int cnt = conf.readEntry("Count", 0);
883 
884         int idx = 0;
885         while (idx < cnt) {
886             const QString data = conf.readEntry(QStringLiteral("Set%1").arg(idx), QString());
887             if (!data.isEmpty()) {
888                 auto set = new Aggregation();
889                 if (set->loadFromString(data)) {
890                     if (Aggregation *old = mAggregations.value(set->id())) {
891                         delete old;
892                     }
893                     mAggregations.insert(set->id(), set);
894                 } else {
895                     delete set; // b0rken
896                 }
897             }
898             idx++;
899         }
900 
901         if (mAggregations.isEmpty()) {
902             // don't allow zero configuration, create some presets
903             createDefaultAggregations();
904         }
905     }
906 
907     {
908         // load Themes
909 
910         KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Themes");
911 
912         mThemes.clear();
913 
914         const int cnt = conf.readEntry("Count", 0);
915 
916         int idx = 0;
917         while (idx < cnt) {
918             const QString data = conf.readEntry(QStringLiteral("Set%1").arg(idx), QString());
919             if (!data.isEmpty()) {
920                 auto set = new Theme();
921                 if (set->loadFromString(data)) {
922                     if (Theme *old = mThemes.value(set->id())) {
923                         delete old;
924                     }
925                     mThemes.insert(set->id(), set);
926                 } else {
927                     qCWarning(MESSAGELIST_LOG) << "Saved theme loading failed";
928                     delete set; // b0rken
929                 }
930             }
931             ++idx;
932         }
933 
934         if (mThemes.isEmpty()) {
935             // don't allow zero configuration, create some presets
936             createDefaultThemes();
937         }
938     }
939 }
940 
saveGlobalConfiguration()941 void Manager::saveGlobalConfiguration()
942 {
943     MessageListSettings::self()->save();
944 }
945 
saveConfiguration()946 void Manager::saveConfiguration()
947 {
948     saveGlobalConfiguration();
949 
950     {
951         // store aggregations
952 
953         KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Aggregations");
954         // conf.clear();
955 
956         conf.writeEntry("Count", mAggregations.count());
957 
958         int idx = 0;
959         QMap<QString, Aggregation *>::ConstIterator end(mAggregations.end());
960         for (QMap<QString, Aggregation *>::ConstIterator it = mAggregations.constBegin(); it != end; ++it) {
961             conf.writeEntry(QStringLiteral("Set%1").arg(idx), (*it)->saveToString());
962             ++idx;
963         }
964     }
965 
966     {
967         // store themes
968 
969         KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Themes");
970         // conf.clear();
971 
972         conf.writeEntry("Count", mThemes.count());
973 
974         int idx = 0;
975         QMap<QString, Theme *>::ConstIterator end(mThemes.constEnd());
976         for (QMap<QString, Theme *>::ConstIterator it = mThemes.constBegin(); it != end; ++it) {
977             conf.writeEntry(QStringLiteral("Set%1").arg(idx), (*it)->saveToString());
978             ++idx;
979         }
980     }
981 
982     MessageListSettings::self()->config()->sync();
983 }
984