File indexing completed on 2024-12-15 04:54:41

0001 /******************************************************************************
0002  *
0003  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  *
0007  *******************************************************************************/
0008 
0009 #include "core/theme.h"
0010 
0011 #include <QApplication>
0012 #include <QDataStream>
0013 #include <QIcon>
0014 #include <QPixmap>
0015 #include <QStandardPaths>
0016 
0017 #include "messagelist_debug.h"
0018 #include <KLocalizedString>
0019 
0020 using namespace MessageList::Core;
0021 
0022 //
0023 // Theme versioning
0024 //
0025 // The themes simply have a DWORD version number attached.
0026 // The earliest version we're able to load is 0x1013.
0027 //
0028 // Theme revision history:
0029 //
0030 //  Version Date introduced Description
0031 // --------------------------------------------------------------------------------------------------------------
0032 //  0x1013  08.11.2008      Initial theme version, introduced when this piece of code has been moved into trunk.
0033 //  0x1014  12.11.2008      Added runtime column data: width and column visibility
0034 //  0x1015  03.03.2009      Added icon size
0035 //  0x1016  08.03.2009      Added support for sorting by New/Unread status
0036 //  0x1017  16.08.2009      Added support for column icon
0037 //  0x1018  17.01.2010      Added support for annotation icon
0038 //  0x1019  13.07.2010      Added support for invitation icon
0039 //
0040 static const int gThemeCurrentVersion = 0x1019; // increase if you add new fields or change the meaning of some
0041 // you don't need to change the values below, but you might want to add new ones
0042 static const int gThemeMinimumSupportedVersion = 0x1013;
0043 static const int gThemeMinimumVersionWithColumnRuntimeData = 0x1014;
0044 static const int gThemeMinimumVersionWithIconSizeField = 0x1015;
0045 static const int gThemeMinimumVersionWithSortingByUnreadStatusAllowed = 0x1016;
0046 static const int gThemeMinimumVersionWithColumnIcon = 0x1017;
0047 static const int gThemeMinimumVersionWithAnnotationIcon = 0x1018;
0048 static const int gThemeMinimumVersionWithInvitationIcon = 0x1019;
0049 
0050 // the default icon size
0051 static const int gThemeDefaultIconSize = 16;
0052 
0053 Theme::ContentItem::ContentItem(Type type)
0054     : mType(type)
0055     , mFlags(0)
0056 {
0057 }
0058 
0059 Theme::ContentItem::ContentItem(const ContentItem &src)
0060 
0061     = default;
0062 
0063 Theme::ContentItem::Type Theme::ContentItem::type() const
0064 {
0065     return mType;
0066 }
0067 
0068 bool Theme::ContentItem::canBeDisabled() const
0069 {
0070     return (static_cast<int>(mType) & CanBeDisabled) != 0;
0071 }
0072 
0073 bool Theme::ContentItem::canUseCustomColor() const
0074 {
0075     return (static_cast<int>(mType) & CanUseCustomColor) != 0;
0076 }
0077 
0078 bool Theme::ContentItem::displaysText() const
0079 {
0080     return (static_cast<int>(mType) & DisplaysText) != 0;
0081 }
0082 
0083 bool Theme::ContentItem::displaysLongText() const
0084 {
0085     return (static_cast<int>(mType) & LongText) != 0;
0086 }
0087 
0088 bool Theme::ContentItem::isIcon() const
0089 {
0090     return (static_cast<int>(mType) & IsIcon) != 0;
0091 }
0092 
0093 bool Theme::ContentItem::isClickable() const
0094 {
0095     return (static_cast<int>(mType) & IsClickable) != 0;
0096 }
0097 
0098 bool Theme::ContentItem::isSpacer() const
0099 {
0100     return (static_cast<int>(mType) & IsSpacer) != 0;
0101 }
0102 
0103 QString Theme::ContentItem::description(Type type)
0104 {
0105     switch (type) {
0106     case Subject:
0107         return i18nc("Description of Type Subject", "Subject");
0108         break;
0109     case Date:
0110         return i18nc("Description of Type Date", "Date");
0111         break;
0112     case SenderOrReceiver:
0113         return i18n("Sender/Receiver");
0114         break;
0115     case Sender:
0116         return i18nc("Description of Type Sender", "Sender");
0117         break;
0118     case Receiver:
0119         return i18nc("Description of Type Receiver", "Receiver");
0120         break;
0121     case Size:
0122         return i18nc("Description of Type Size", "Size");
0123         break;
0124     case ReadStateIcon:
0125         return i18n("Unread/Read Icon");
0126         break;
0127     case AttachmentStateIcon:
0128         return i18n("Attachment Icon");
0129         break;
0130     case RepliedStateIcon:
0131         return i18n("Replied/Forwarded Icon");
0132         break;
0133     case CombinedReadRepliedStateIcon:
0134         return i18n("Combined New/Unread/Read/Replied/Forwarded Icon");
0135         break;
0136     case ActionItemStateIcon:
0137         return i18n("Action Item Icon");
0138         break;
0139     case ImportantStateIcon:
0140         return i18n("Important Icon");
0141         break;
0142     case GroupHeaderLabel:
0143         return i18n("Group Header Label");
0144         break;
0145     case SpamHamStateIcon:
0146         return i18n("Spam/Ham Icon");
0147         break;
0148     case WatchedIgnoredStateIcon:
0149         return i18n("Watched/Ignored Icon");
0150         break;
0151     case ExpandedStateIcon:
0152         return i18n("Group Header Expand/Collapse Icon");
0153         break;
0154     case EncryptionStateIcon:
0155         return i18n("Encryption State Icon");
0156         break;
0157     case SignatureStateIcon:
0158         return i18n("Signature State Icon");
0159         break;
0160     case VerticalLine:
0161         return i18n("Vertical Separation Line");
0162         break;
0163     case HorizontalSpacer:
0164         return i18n("Horizontal Spacer");
0165         break;
0166     case MostRecentDate:
0167         return i18n("Max Date");
0168         break;
0169     case TagList:
0170         return i18n("Message Tags");
0171         break;
0172     case AnnotationIcon:
0173         return i18n("Note Icon");
0174     case InvitationIcon:
0175         return i18n("Invitation Icon");
0176     case Folder:
0177         return i18nc("Description of Type Folder", "Folder");
0178     default:
0179         return i18nc("Description for an Unknown Type", "Unknown");
0180         break;
0181     }
0182 }
0183 
0184 bool Theme::ContentItem::useCustomColor() const
0185 {
0186     return mFlags & UseCustomColor;
0187 }
0188 
0189 void Theme::ContentItem::setUseCustomColor(bool useCustomColor)
0190 {
0191     if (useCustomColor) {
0192         mFlags |= UseCustomColor;
0193     } else {
0194         mFlags &= ~UseCustomColor;
0195     }
0196 }
0197 
0198 bool Theme::ContentItem::isBold() const
0199 {
0200     return mFlags & IsBold;
0201 }
0202 
0203 void Theme::ContentItem::setBold(bool isBold)
0204 {
0205     if (isBold) {
0206         mFlags |= IsBold;
0207     } else {
0208         mFlags &= ~IsBold;
0209     }
0210 }
0211 
0212 bool Theme::ContentItem::isItalic() const
0213 {
0214     return mFlags & IsItalic;
0215 }
0216 
0217 void Theme::ContentItem::setItalic(bool isItalic)
0218 {
0219     if (isItalic) {
0220         mFlags |= IsItalic;
0221     } else {
0222         mFlags &= ~IsItalic;
0223     }
0224 }
0225 
0226 bool Theme::ContentItem::hideWhenDisabled() const
0227 {
0228     return mFlags & HideWhenDisabled;
0229 }
0230 
0231 void Theme::ContentItem::setHideWhenDisabled(bool hideWhenDisabled)
0232 {
0233     if (hideWhenDisabled) {
0234         mFlags |= HideWhenDisabled;
0235     } else {
0236         mFlags &= ~HideWhenDisabled;
0237     }
0238 }
0239 
0240 bool Theme::ContentItem::softenByBlendingWhenDisabled() const
0241 {
0242     return mFlags & SoftenByBlendingWhenDisabled;
0243 }
0244 
0245 void Theme::ContentItem::setSoftenByBlendingWhenDisabled(bool softenByBlendingWhenDisabled)
0246 {
0247     if (softenByBlendingWhenDisabled) {
0248         mFlags |= SoftenByBlendingWhenDisabled;
0249     } else {
0250         mFlags &= ~SoftenByBlendingWhenDisabled;
0251     }
0252 }
0253 
0254 bool Theme::ContentItem::softenByBlending() const
0255 {
0256     return mFlags & SoftenByBlending;
0257 }
0258 
0259 void Theme::ContentItem::setSoftenByBlending(bool softenByBlending)
0260 {
0261     if (softenByBlending) {
0262         mFlags |= SoftenByBlending;
0263     } else {
0264         mFlags &= ~SoftenByBlending;
0265     }
0266 }
0267 
0268 const QColor &Theme::ContentItem::customColor() const
0269 {
0270     return mCustomColor;
0271 }
0272 
0273 void Theme::ContentItem::setCustomColor(const QColor &clr)
0274 {
0275     mCustomColor = clr;
0276 }
0277 
0278 bool Theme::ContentItem::applicableToMessageItems(Type type)
0279 {
0280     return static_cast<int>(type) & ApplicableToMessageItems;
0281 }
0282 
0283 bool Theme::ContentItem::applicableToGroupHeaderItems(Type type)
0284 {
0285     return static_cast<int>(type) & ApplicableToGroupHeaderItems;
0286 }
0287 
0288 void Theme::ContentItem::save(QDataStream &stream) const
0289 {
0290     stream << (int)mType;
0291     stream << mFlags;
0292     stream << mCustomColor;
0293 }
0294 
0295 bool Theme::ContentItem::load(QDataStream &stream, int /*themeVersion*/)
0296 {
0297     int val;
0298 
0299     stream >> val;
0300     mType = static_cast<Type>(val);
0301     switch (mType) {
0302     case Subject:
0303     case Date:
0304     case SenderOrReceiver:
0305     case Sender:
0306     case Receiver:
0307     case Size:
0308     case ReadStateIcon:
0309     case AttachmentStateIcon:
0310     case RepliedStateIcon:
0311     case GroupHeaderLabel:
0312     case ActionItemStateIcon:
0313     case ImportantStateIcon:
0314     case SpamHamStateIcon:
0315     case WatchedIgnoredStateIcon:
0316     case ExpandedStateIcon:
0317     case EncryptionStateIcon:
0318     case SignatureStateIcon:
0319     case VerticalLine:
0320     case HorizontalSpacer:
0321     case MostRecentDate:
0322     case CombinedReadRepliedStateIcon:
0323     case TagList:
0324     case AnnotationIcon:
0325     case InvitationIcon:
0326     case Folder:
0327         // ok
0328         break;
0329     default:
0330         qCDebug(MESSAGELIST_LOG) << "Invalid content item type";
0331         return false; // b0rken
0332         break;
0333     }
0334 
0335     stream >> mFlags;
0336     stream >> mCustomColor;
0337     if (mFlags & UseCustomColor) {
0338         if (!mCustomColor.isValid()) {
0339             mFlags &= ~UseCustomColor;
0340         }
0341     }
0342     return true;
0343 }
0344 
0345 Theme::Row::Row() = default;
0346 
0347 Theme::Row::Row(const Row &src)
0348 {
0349     for (const auto ci : std::as_const(src.mLeftItems)) {
0350         addLeftItem(new ContentItem(*ci));
0351     }
0352 
0353     for (const auto ci : std::as_const(src.mRightItems)) {
0354         addRightItem(new ContentItem(*ci));
0355     }
0356 }
0357 
0358 Theme::Row::~Row()
0359 {
0360     removeAllLeftItems();
0361     removeAllRightItems();
0362 }
0363 
0364 void Theme::Row::removeAllLeftItems()
0365 {
0366     while (!mLeftItems.isEmpty()) {
0367         delete mLeftItems.takeFirst();
0368     }
0369 }
0370 
0371 void Theme::Row::addLeftItem(Theme::ContentItem *item)
0372 {
0373     mLeftItems.append(item);
0374 }
0375 
0376 void Theme::Row::removeAllRightItems()
0377 {
0378     while (!mRightItems.isEmpty()) {
0379         delete mRightItems.takeFirst();
0380     }
0381 }
0382 
0383 void Theme::Row::addRightItem(Theme::ContentItem *item)
0384 {
0385     mRightItems.append(item);
0386 }
0387 
0388 void Theme::Row::insertLeftItem(int idx, ContentItem *item)
0389 {
0390     if (idx >= mLeftItems.count()) {
0391         mLeftItems.append(item);
0392         return;
0393     }
0394     mLeftItems.insert(idx, item);
0395 }
0396 
0397 void Theme::Row::removeLeftItem(Theme::ContentItem *item)
0398 {
0399     mLeftItems.removeAll(item);
0400 }
0401 
0402 const QList<Theme::ContentItem *> &Theme::Row::rightItems() const
0403 {
0404     return mRightItems;
0405 }
0406 
0407 void Theme::Row::insertRightItem(int idx, ContentItem *item)
0408 {
0409     if (idx >= mRightItems.count()) {
0410         mRightItems.append(item);
0411         return;
0412     }
0413     mRightItems.insert(idx, item);
0414 }
0415 
0416 void Theme::Row::removeRightItem(Theme::ContentItem *item)
0417 {
0418     mRightItems.removeAll(item);
0419 }
0420 
0421 bool Theme::Row::containsTextItems() const
0422 {
0423     for (const auto ci : std::as_const(mLeftItems)) {
0424         if (ci->displaysText()) {
0425             return true;
0426         }
0427     }
0428     for (const auto ci : std::as_const(mRightItems)) {
0429         if (ci->displaysText()) {
0430             return true;
0431         }
0432     }
0433     return false;
0434 }
0435 
0436 void Theme::Row::save(QDataStream &stream) const
0437 {
0438     stream << (int)mLeftItems.count();
0439 
0440     int cnt = mLeftItems.count();
0441 
0442     for (int i = 0; i < cnt; ++i) {
0443         ContentItem *ci = mLeftItems.at(i);
0444         ci->save(stream);
0445     }
0446 
0447     stream << (int)mRightItems.count();
0448 
0449     cnt = mRightItems.count();
0450 
0451     for (int i = 0; i < cnt; ++i) {
0452         ContentItem *ci = mRightItems.at(i);
0453         ci->save(stream);
0454     }
0455 }
0456 
0457 bool Theme::Row::LoadContentItem(int val, QDataStream &stream, int themeVersion, bool leftItem)
0458 {
0459     if ((val < 0) || (val > 50)) {
0460         return false; // senseless
0461     }
0462 
0463     // FIXME: Remove code duplication here
0464 
0465     for (int i = 0; i < val; ++i) {
0466         auto ci = new ContentItem(ContentItem::Subject); // dummy type
0467         if (!ci->load(stream, themeVersion)) {
0468             qCDebug(MESSAGELIST_LOG) << "Left content item loading failed";
0469             delete ci;
0470             return false;
0471         }
0472         if (leftItem) {
0473             addLeftItem(ci);
0474         } else {
0475             addRightItem(ci);
0476         }
0477 
0478         // Add the annotation item next to the attachment icon, so that users upgrading from old
0479         // versions don't manually need to set this.
0480         // Don't do this for the stand-alone attachment column.
0481         if (ci->type() == ContentItem::AttachmentStateIcon && themeVersion < gThemeMinimumVersionWithAnnotationIcon && val > 1) {
0482             qCDebug(MESSAGELIST_LOG) << "Old theme version detected, adding annotation item next to attachment icon.";
0483             auto annotationItem = new ContentItem(ContentItem::AnnotationIcon);
0484             annotationItem->setHideWhenDisabled(true);
0485             if (leftItem) {
0486                 addLeftItem(annotationItem);
0487             } else {
0488                 addRightItem(annotationItem);
0489             }
0490         }
0491 
0492         // Same as above, for the invitation icon
0493         if (ci->type() == ContentItem::AttachmentStateIcon && themeVersion < gThemeMinimumVersionWithInvitationIcon && val > 1) {
0494             qCDebug(MESSAGELIST_LOG) << "Old theme version detected, adding invitation item next to attachment icon.";
0495             auto invitationItem = new ContentItem(ContentItem::InvitationIcon);
0496             invitationItem->setHideWhenDisabled(true);
0497             if (leftItem) {
0498                 addLeftItem(invitationItem);
0499             } else {
0500                 addRightItem(invitationItem);
0501             }
0502         }
0503     }
0504     return true;
0505 }
0506 
0507 const QList<Theme::ContentItem *> &Theme::Row::leftItems() const
0508 {
0509     return mLeftItems;
0510 }
0511 
0512 bool Theme::Row::load(QDataStream &stream, int themeVersion)
0513 {
0514     removeAllLeftItems();
0515     removeAllRightItems();
0516 
0517     int val;
0518 
0519     // left item count
0520 
0521     stream >> val;
0522     if (!LoadContentItem(val, stream, themeVersion, true)) {
0523         return false;
0524     }
0525 
0526     // right item count
0527 
0528     stream >> val;
0529 
0530     if (!LoadContentItem(val, stream, themeVersion, false)) {
0531         return false;
0532     }
0533 
0534     return true;
0535 }
0536 
0537 Theme::Column::SharedRuntimeData::SharedRuntimeData(bool currentlyVisible, double currentWidth)
0538     : mReferences(0)
0539     , mCurrentlyVisible(currentlyVisible)
0540     , mCurrentWidth(currentWidth)
0541 {
0542 }
0543 
0544 Theme::Column::SharedRuntimeData::~SharedRuntimeData() = default;
0545 
0546 void Theme::Column::SharedRuntimeData::addReference()
0547 {
0548     mReferences++;
0549 }
0550 
0551 bool Theme::Column::SharedRuntimeData::deleteReference()
0552 {
0553     mReferences--;
0554     Q_ASSERT(mReferences >= 0);
0555     return mReferences > 0;
0556 }
0557 
0558 int Theme::Column::SharedRuntimeData::referenceCount() const
0559 {
0560     return mReferences;
0561 }
0562 
0563 bool Theme::Column::SharedRuntimeData::currentlyVisible() const
0564 {
0565     return mCurrentlyVisible;
0566 }
0567 
0568 void Theme::Column::SharedRuntimeData::setCurrentlyVisible(bool visible)
0569 {
0570     mCurrentlyVisible = visible;
0571 }
0572 
0573 double Theme::Column::SharedRuntimeData::currentWidth() const
0574 {
0575     return mCurrentWidth;
0576 }
0577 
0578 void Theme::Column::SharedRuntimeData::setCurrentWidth(double currentWidth)
0579 {
0580     mCurrentWidth = currentWidth;
0581 }
0582 
0583 void Theme::Column::SharedRuntimeData::save(QDataStream &stream) const
0584 {
0585     stream << mCurrentlyVisible;
0586     stream << mCurrentWidth;
0587 }
0588 
0589 bool Theme::Column::SharedRuntimeData::load(QDataStream &stream, int /* themeVersion */)
0590 {
0591     stream >> mCurrentlyVisible;
0592     stream >> mCurrentWidth;
0593     if (mCurrentWidth > 10000) {
0594         qCDebug(MESSAGELIST_LOG) << "Theme has insane column width " << mCurrentWidth << " chopping to 100";
0595         mCurrentWidth = 100; // avoid really insane values
0596     }
0597     return mCurrentWidth >= -1;
0598 }
0599 
0600 Theme::Column::Column()
0601     : mVisibleByDefault(true)
0602     , mIsSenderOrReceiver(false)
0603     , mMessageSorting(SortOrder::NoMessageSorting)
0604 {
0605     mSharedRuntimeData = new SharedRuntimeData(true, -1);
0606     mSharedRuntimeData->addReference();
0607 }
0608 
0609 Theme::Column::Column(const Column &src)
0610 {
0611     mLabel = src.mLabel;
0612     mPixmapName = src.mPixmapName;
0613     mVisibleByDefault = src.mVisibleByDefault;
0614     mIsSenderOrReceiver = src.mIsSenderOrReceiver;
0615     mMessageSorting = src.mMessageSorting;
0616 
0617     mSharedRuntimeData = src.mSharedRuntimeData;
0618     mSharedRuntimeData->addReference();
0619     for (const auto row : std::as_const(src.mMessageRows)) {
0620         addMessageRow(new Row(*row));
0621     }
0622 
0623     for (const auto row : std::as_const(src.mGroupHeaderRows)) {
0624         addGroupHeaderRow(new Row(*row));
0625     }
0626 }
0627 
0628 Theme::Column::~Column()
0629 {
0630     removeAllMessageRows();
0631     removeAllGroupHeaderRows();
0632     if (!(mSharedRuntimeData->deleteReference())) {
0633         delete mSharedRuntimeData;
0634     }
0635 }
0636 
0637 const QString &Theme::Column::label() const
0638 {
0639     return mLabel;
0640 }
0641 
0642 void Theme::Column::setLabel(const QString &label)
0643 {
0644     mLabel = label;
0645 }
0646 
0647 const QString &Theme::Column::pixmapName() const
0648 {
0649     return mPixmapName;
0650 }
0651 
0652 void Theme::Column::setPixmapName(const QString &pixmapName)
0653 {
0654     mPixmapName = pixmapName;
0655 }
0656 
0657 bool Theme::Column::isSenderOrReceiver() const
0658 {
0659     return mIsSenderOrReceiver;
0660 }
0661 
0662 void Theme::Column::setIsSenderOrReceiver(bool sor)
0663 {
0664     mIsSenderOrReceiver = sor;
0665 }
0666 
0667 bool Theme::Column::visibleByDefault() const
0668 {
0669     return mVisibleByDefault;
0670 }
0671 
0672 void Theme::Column::setVisibleByDefault(bool vbd)
0673 {
0674     mVisibleByDefault = vbd;
0675 }
0676 
0677 void Theme::Column::detach()
0678 {
0679     if (mSharedRuntimeData->referenceCount() < 2) {
0680         return; // nothing to detach
0681     }
0682     mSharedRuntimeData->deleteReference();
0683 
0684     mSharedRuntimeData = new SharedRuntimeData(mVisibleByDefault, -1);
0685     mSharedRuntimeData->addReference();
0686 }
0687 
0688 SortOrder::MessageSorting Theme::Column::messageSorting() const
0689 {
0690     return mMessageSorting;
0691 }
0692 
0693 void Theme::Column::setMessageSorting(SortOrder::MessageSorting ms)
0694 {
0695     mMessageSorting = ms;
0696 }
0697 
0698 bool Theme::Column::currentlyVisible() const
0699 {
0700     return mSharedRuntimeData->currentlyVisible();
0701 }
0702 
0703 void Theme::Column::setCurrentlyVisible(bool currentlyVisible)
0704 {
0705     mSharedRuntimeData->setCurrentlyVisible(currentlyVisible);
0706 }
0707 
0708 double Theme::Column::currentWidth() const
0709 {
0710     return mSharedRuntimeData->currentWidth();
0711 }
0712 
0713 void Theme::Column::setCurrentWidth(double currentWidth)
0714 {
0715     mSharedRuntimeData->setCurrentWidth(currentWidth);
0716 }
0717 
0718 const QList<Theme::Row *> &Theme::Column::messageRows() const
0719 {
0720     return mMessageRows;
0721 }
0722 
0723 void Theme::Column::removeAllMessageRows()
0724 {
0725     while (!mMessageRows.isEmpty()) {
0726         delete mMessageRows.takeFirst();
0727     }
0728 }
0729 
0730 void Theme::Column::addMessageRow(Theme::Row *row)
0731 {
0732     mMessageRows.append(row);
0733 }
0734 
0735 void Theme::Column::removeAllGroupHeaderRows()
0736 {
0737     while (!mGroupHeaderRows.isEmpty()) {
0738         delete mGroupHeaderRows.takeFirst();
0739     }
0740 }
0741 
0742 void Theme::Column::addGroupHeaderRow(Theme::Row *row)
0743 {
0744     mGroupHeaderRows.append(row);
0745 }
0746 
0747 void Theme::Column::insertMessageRow(int idx, Row *row)
0748 {
0749     if (idx >= mMessageRows.count()) {
0750         mMessageRows.append(row);
0751         return;
0752     }
0753     mMessageRows.insert(idx, row);
0754 }
0755 
0756 void Theme::Column::removeMessageRow(Theme::Row *row)
0757 {
0758     mMessageRows.removeAll(row);
0759 }
0760 
0761 const QList<Theme::Row *> &Theme::Column::groupHeaderRows() const
0762 {
0763     return mGroupHeaderRows;
0764 }
0765 
0766 void Theme::Column::insertGroupHeaderRow(int idx, Row *row)
0767 {
0768     if (idx >= mGroupHeaderRows.count()) {
0769         mGroupHeaderRows.append(row);
0770         return;
0771     }
0772     mGroupHeaderRows.insert(idx, row);
0773 }
0774 
0775 void Theme::Column::removeGroupHeaderRow(Theme::Row *row)
0776 {
0777     mGroupHeaderRows.removeAll(row);
0778 }
0779 
0780 bool Theme::Column::containsTextItems() const
0781 {
0782     for (const auto row : std::as_const(mMessageRows)) {
0783         if (row->containsTextItems()) {
0784             return true;
0785         }
0786     }
0787     for (const auto row : std::as_const(mGroupHeaderRows)) {
0788         if (row->containsTextItems()) {
0789             return true;
0790         }
0791     }
0792     return false;
0793 }
0794 
0795 void Theme::Column::save(QDataStream &stream) const
0796 {
0797     stream << mLabel;
0798     stream << mPixmapName;
0799     stream << mVisibleByDefault;
0800     stream << mIsSenderOrReceiver;
0801     stream << static_cast<int>(mMessageSorting);
0802 
0803     stream << static_cast<int>(mGroupHeaderRows.count());
0804 
0805     int cnt = mGroupHeaderRows.count();
0806 
0807     for (int i = 0; i < cnt; ++i) {
0808         Row *row = mGroupHeaderRows.at(i);
0809         row->save(stream);
0810     }
0811 
0812     cnt = mMessageRows.count();
0813     stream << static_cast<int>(cnt);
0814 
0815     for (int i = 0; i < cnt; ++i) {
0816         Row *row = mMessageRows.at(i);
0817         row->save(stream);
0818     }
0819 
0820     // added in version 0x1014
0821     mSharedRuntimeData->save(stream);
0822 }
0823 
0824 bool Theme::Column::load(QDataStream &stream, int themeVersion)
0825 {
0826     removeAllGroupHeaderRows();
0827     removeAllMessageRows();
0828 
0829     stream >> mLabel;
0830 
0831     if (themeVersion >= gThemeMinimumVersionWithColumnIcon) {
0832         stream >> mPixmapName;
0833     }
0834 
0835     stream >> mVisibleByDefault;
0836     stream >> mIsSenderOrReceiver;
0837 
0838     int val;
0839 
0840     stream >> val;
0841     mMessageSorting = static_cast<SortOrder::MessageSorting>(val);
0842     if (!SortOrder::isValidMessageSorting(mMessageSorting)) {
0843         qCDebug(MESSAGELIST_LOG) << "Invalid message sorting";
0844         return false;
0845     }
0846 
0847     if (themeVersion < gThemeMinimumVersionWithSortingByUnreadStatusAllowed) {
0848         // The default "Classic" theme "Unread" column had sorting disabled here.
0849         // We want to be nice to the existing users and automatically set
0850         // the new sorting method for this column (so they don't have to make the
0851         // complex steps to set it by themselves).
0852         // This piece of code isn't strictly required: it's just a niceness :)
0853         if ((mMessageSorting == SortOrder::NoMessageSorting) && (mLabel == i18n("Unread"))) {
0854             mMessageSorting = SortOrder::SortMessagesByUnreadStatus;
0855         }
0856     }
0857 
0858     // group header row count
0859     stream >> val;
0860 
0861     if ((val < 0) || (val > 50)) {
0862         qCDebug(MESSAGELIST_LOG) << "Invalid group header row count";
0863         return false; // senseless
0864     }
0865 
0866     for (int i = 0; i < val; ++i) {
0867         Row *row = new Row();
0868         if (!row->load(stream, themeVersion)) {
0869             qCDebug(MESSAGELIST_LOG) << "Group header row loading failed";
0870             delete row;
0871             return false;
0872         }
0873         addGroupHeaderRow(row);
0874     }
0875 
0876     // message row count
0877     stream >> val;
0878 
0879     if ((val < 0) || (val > 50)) {
0880         qCDebug(MESSAGELIST_LOG) << "Invalid message row count";
0881         return false; // senseless
0882     }
0883 
0884     for (int i = 0; i < val; ++i) {
0885         Row *row = new Row();
0886         if (!row->load(stream, themeVersion)) {
0887             qCDebug(MESSAGELIST_LOG) << "Message row loading failed";
0888             delete row;
0889             return false;
0890         }
0891         addMessageRow(row);
0892     }
0893 
0894     if (themeVersion >= gThemeMinimumVersionWithColumnRuntimeData) {
0895         // starting with version 0x1014 we have runtime data too
0896         if (!mSharedRuntimeData->load(stream, themeVersion)) {
0897             qCDebug(MESSAGELIST_LOG) << "Shared runtime data loading failed";
0898             return false;
0899         }
0900     } else {
0901         // assume default shared data
0902         mSharedRuntimeData->setCurrentlyVisible(mVisibleByDefault);
0903         mSharedRuntimeData->setCurrentWidth(-1);
0904     }
0905 
0906     return true;
0907 }
0908 
0909 Theme::Theme()
0910     : OptionSet()
0911 {
0912     mGroupHeaderBackgroundMode = AutoColor;
0913     mViewHeaderPolicy = ShowHeaderAlways;
0914     mIconSize = gThemeDefaultIconSize;
0915     mGroupHeaderBackgroundStyle = StyledJoinedRect;
0916 }
0917 
0918 Theme::Theme(const QString &name, const QString &description, bool readOnly)
0919     : OptionSet(name, description, readOnly)
0920 {
0921     mGroupHeaderBackgroundMode = AutoColor;
0922     mGroupHeaderBackgroundStyle = StyledJoinedRect;
0923     mViewHeaderPolicy = ShowHeaderAlways;
0924     mIconSize = gThemeDefaultIconSize;
0925 }
0926 
0927 Theme::Theme(const Theme &src)
0928     : OptionSet(src)
0929 {
0930     mGroupHeaderBackgroundMode = src.mGroupHeaderBackgroundMode;
0931     mGroupHeaderBackgroundColor = src.mGroupHeaderBackgroundColor;
0932     mGroupHeaderBackgroundStyle = src.mGroupHeaderBackgroundStyle;
0933     mViewHeaderPolicy = src.mViewHeaderPolicy;
0934     mIconSize = src.mIconSize;
0935     for (const auto col : std::as_const(src.mColumns)) {
0936         addColumn(new Column(*col));
0937     }
0938 }
0939 
0940 Theme::~Theme()
0941 {
0942     clearPixmapCache();
0943     removeAllColumns();
0944 }
0945 
0946 void Theme::detach()
0947 {
0948     for (const auto col : std::as_const(mColumns)) {
0949         col->detach();
0950     }
0951 }
0952 
0953 void Theme::resetColumnState()
0954 {
0955     for (const auto col : std::as_const(mColumns)) {
0956         col->setCurrentlyVisible(col->visibleByDefault());
0957         col->setCurrentWidth(-1);
0958     }
0959 }
0960 
0961 void Theme::resetColumnSizes()
0962 {
0963     for (const auto col : std::as_const(mColumns)) {
0964         col->setCurrentWidth(-1);
0965     }
0966 }
0967 
0968 const QList<Theme::Column *> &Theme::columns() const
0969 {
0970     return mColumns;
0971 }
0972 
0973 Theme::Column *Theme::column(int idx) const
0974 {
0975     return mColumns.count() > idx ? mColumns.at(idx) : nullptr;
0976 }
0977 
0978 void Theme::removeAllColumns()
0979 {
0980     while (!mColumns.isEmpty()) {
0981         delete mColumns.takeFirst();
0982     }
0983 }
0984 
0985 void Theme::addColumn(Theme::Column *column)
0986 {
0987     mColumns.append(column);
0988 }
0989 
0990 void Theme::insertColumn(int idx, Column *column)
0991 {
0992     if (idx >= mColumns.count()) {
0993         mColumns.append(column);
0994         return;
0995     }
0996     mColumns.insert(idx, column);
0997 }
0998 
0999 void Theme::removeColumn(Theme::Column *col)
1000 {
1001     mColumns.removeAll(col);
1002 }
1003 
1004 Theme::GroupHeaderBackgroundMode Theme::groupHeaderBackgroundMode() const
1005 {
1006     return mGroupHeaderBackgroundMode;
1007 }
1008 
1009 void Theme::moveColumn(int idx, int newPosition)
1010 {
1011     if ((newPosition >= mColumns.count()) || newPosition < 0) {
1012         return;
1013     }
1014     mColumns.move(idx, newPosition);
1015 }
1016 
1017 void Theme::setGroupHeaderBackgroundMode(GroupHeaderBackgroundMode bm)
1018 {
1019     mGroupHeaderBackgroundMode = bm;
1020     if ((bm == CustomColor) && !mGroupHeaderBackgroundColor.isValid()) {
1021         mGroupHeaderBackgroundColor = QColor(127, 127, 127); // something neutral
1022     }
1023 }
1024 
1025 const QColor &Theme::groupHeaderBackgroundColor() const
1026 {
1027     return mGroupHeaderBackgroundColor;
1028 }
1029 
1030 void Theme::setGroupHeaderBackgroundColor(const QColor &clr)
1031 {
1032     mGroupHeaderBackgroundColor = clr;
1033 }
1034 
1035 Theme::GroupHeaderBackgroundStyle Theme::groupHeaderBackgroundStyle() const
1036 {
1037     return mGroupHeaderBackgroundStyle;
1038 }
1039 
1040 void Theme::setGroupHeaderBackgroundStyle(Theme::GroupHeaderBackgroundStyle groupHeaderBackgroundStyle)
1041 {
1042     mGroupHeaderBackgroundStyle = groupHeaderBackgroundStyle;
1043 }
1044 
1045 QList<QPair<QString, int>> Theme::enumerateViewHeaderPolicyOptions()
1046 {
1047     return {{i18n("Never Show"), NeverShowHeader}, {i18n("Always Show"), ShowHeaderAlways}};
1048 }
1049 
1050 QList<QPair<QString, int>> Theme::enumerateGroupHeaderBackgroundStyles()
1051 {
1052     return {{i18n("Plain Rectangles"), PlainRect},
1053             {i18n("Plain Joined Rectangle"), PlainJoinedRect},
1054             {i18n("Rounded Rectangles"), RoundedRect},
1055             {i18n("Rounded Joined Rectangle"), RoundedJoinedRect},
1056             {i18n("Gradient Rectangles"), GradientRect},
1057             {i18n("Gradient Joined Rectangle"), GradientJoinedRect},
1058             {i18n("Styled Rectangles"), StyledRect},
1059             {i18n("Styled Joined Rectangles"), StyledJoinedRect}};
1060 }
1061 
1062 Theme::ViewHeaderPolicy Theme::viewHeaderPolicy() const
1063 {
1064     return mViewHeaderPolicy;
1065 }
1066 
1067 void Theme::setViewHeaderPolicy(Theme::ViewHeaderPolicy vhp)
1068 {
1069     mViewHeaderPolicy = vhp;
1070 }
1071 
1072 int Theme::iconSize() const
1073 {
1074     return mIconSize;
1075 }
1076 
1077 void Theme::setIconSize(int iconSize)
1078 {
1079     if (mIconSize != iconSize) {
1080         clearPixmapCache();
1081 
1082         mIconSize = iconSize;
1083         if ((mIconSize < 8) || (mIconSize > 64)) {
1084             mIconSize = gThemeDefaultIconSize;
1085         }
1086     }
1087 }
1088 
1089 bool Theme::load(QDataStream &stream)
1090 {
1091     removeAllColumns();
1092 
1093     int themeVersion;
1094 
1095     stream >> themeVersion;
1096 
1097     // We support themes starting at version gThemeMinimumSupportedVersion (0x1013 actually)
1098 
1099     if ((themeVersion > gThemeCurrentVersion) || (themeVersion < gThemeMinimumSupportedVersion)) {
1100         qCDebug(MESSAGELIST_LOG) << "Invalid theme version";
1101         return false; // b0rken (invalid version)
1102     }
1103 
1104     int val;
1105 
1106     stream >> val;
1107     mGroupHeaderBackgroundMode = static_cast<GroupHeaderBackgroundMode>(val);
1108     switch (mGroupHeaderBackgroundMode) {
1109     case Transparent:
1110     case AutoColor:
1111     case CustomColor:
1112         // ok
1113         break;
1114     default:
1115         qCDebug(MESSAGELIST_LOG) << "Invalid theme group header background mode";
1116         return false; // b0rken
1117     }
1118 
1119     stream >> mGroupHeaderBackgroundColor;
1120 
1121     stream >> val;
1122     mGroupHeaderBackgroundStyle = static_cast<GroupHeaderBackgroundStyle>(val);
1123     switch (mGroupHeaderBackgroundStyle) {
1124     case PlainRect:
1125     case PlainJoinedRect:
1126     case RoundedRect:
1127     case RoundedJoinedRect:
1128     case GradientRect:
1129     case GradientJoinedRect:
1130     case StyledRect:
1131     case StyledJoinedRect:
1132         // ok
1133         break;
1134     default:
1135         qCDebug(MESSAGELIST_LOG) << "Invalid theme group header background style";
1136         return false; // b0rken
1137     }
1138 
1139     stream >> val;
1140     mViewHeaderPolicy = (ViewHeaderPolicy)val;
1141     switch (mViewHeaderPolicy) {
1142     case ShowHeaderAlways:
1143     case NeverShowHeader:
1144         // ok
1145         break;
1146     default:
1147         qCDebug(MESSAGELIST_LOG) << "Invalid theme view header policy";
1148         return false; // b0rken
1149     }
1150 
1151     if (themeVersion >= gThemeMinimumVersionWithIconSizeField) {
1152         // icon size parameter
1153         stream >> mIconSize;
1154         if ((mIconSize < 8) || (mIconSize > 64)) {
1155             mIconSize = gThemeDefaultIconSize; // limit insane values
1156         }
1157     } else {
1158         mIconSize = gThemeDefaultIconSize;
1159     }
1160 
1161     // column count
1162     stream >> val;
1163     if (val < 1 || val > 50) {
1164         return false; // plain b0rken ( negative, zero or more than 50 columns )
1165     }
1166 
1167     for (int i = 0; i < val; ++i) {
1168         auto col = new Column();
1169         if (!col->load(stream, themeVersion)) {
1170             qCDebug(MESSAGELIST_LOG) << "Column loading failed";
1171             delete col;
1172             return false;
1173         }
1174         addColumn(col);
1175     }
1176 
1177     return true;
1178 }
1179 
1180 void Theme::save(QDataStream &stream) const
1181 {
1182     stream << (int)gThemeCurrentVersion;
1183 
1184     stream << (int)mGroupHeaderBackgroundMode;
1185     stream << mGroupHeaderBackgroundColor;
1186     stream << (int)mGroupHeaderBackgroundStyle;
1187     stream << (int)mViewHeaderPolicy;
1188     stream << mIconSize;
1189 
1190     const int cnt = mColumns.count();
1191     stream << (int)cnt;
1192 
1193     for (int i = 0; i < cnt; ++i) {
1194         Column *col = mColumns.at(i);
1195         col->save(stream);
1196     }
1197 }
1198 
1199 void Theme::clearPixmapCache() const
1200 {
1201     qDeleteAll(mPixmaps);
1202     mPixmaps.clear();
1203 }
1204 
1205 void Theme::populatePixmapCache() const
1206 {
1207     clearPixmapCache();
1208 
1209     mPixmaps.reserve(_IconCount);
1210     // WARNING: The order of those icons must be in sync with order of the
1211     // corresponding enum values in ThemeIcon!
1212     mPixmaps << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-unread-new")).pixmap(mIconSize, mIconSize))
1213              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-unread")).pixmap(mIconSize, mIconSize))
1214              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-read")).pixmap(mIconSize, mIconSize))
1215              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-deleted")).pixmap(mIconSize, mIconSize))
1216              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-replied")).pixmap(mIconSize, mIconSize))
1217              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-forwarded-replied")).pixmap(mIconSize, mIconSize))
1218              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-queued")).pixmap(mIconSize, mIconSize)) // mail-queue ?
1219              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-task")).pixmap(mIconSize, mIconSize))
1220              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-sent")).pixmap(mIconSize, mIconSize))
1221              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-forwarded")).pixmap(mIconSize, mIconSize))
1222              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-important")).pixmap(mIconSize, mIconSize)) // "flag"
1223              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-thread-watch")).pixmap(mIconSize, mIconSize))
1224              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-thread-ignored")).pixmap(mIconSize, mIconSize))
1225              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-junk")).pixmap(mIconSize, mIconSize))
1226              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-notjunk")).pixmap(mIconSize, mIconSize))
1227              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-signed-verified")).pixmap(mIconSize, mIconSize))
1228              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-signed-part")).pixmap(mIconSize, mIconSize))
1229              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-signed")).pixmap(mIconSize, mIconSize))
1230              << new QPixmap(QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(mIconSize, mIconSize))
1231              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-encrypted-full")).pixmap(mIconSize, mIconSize))
1232              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-encrypted-part")).pixmap(mIconSize, mIconSize))
1233              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-encrypted")).pixmap(mIconSize, mIconSize))
1234              << new QPixmap(QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(mIconSize, mIconSize))
1235              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-attachment")).pixmap(mIconSize, mIconSize))
1236              << new QPixmap(QIcon::fromTheme(QStringLiteral("view-pim-notes")).pixmap(mIconSize, mIconSize))
1237              << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-invitation")).pixmap(mIconSize, mIconSize))
1238              << ((QApplication::isRightToLeft()) ? new QPixmap(QIcon::fromTheme(QStringLiteral("arrow-left")).pixmap(mIconSize, mIconSize))
1239                                                  : new QPixmap(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(mIconSize, mIconSize)))
1240              << new QPixmap(QIcon::fromTheme(QStringLiteral("arrow-down")).pixmap(mIconSize, mIconSize))
1241              << new QPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("messagelist/pics/mail-vertical-separator-line.png")))
1242              << new QPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("messagelist/pics/mail-horizontal-space.png")));
1243 }