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 }