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