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

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 "messageitem.h"
0010 #include "messageitem_p.h"
0011 
0012 #include "messagelist_debug.h"
0013 #include <Akonadi/EntityAnnotationsAttribute>
0014 #include <Akonadi/Item>
0015 #include <Akonadi/TagAttribute>
0016 #include <Akonadi/TagFetchJob>
0017 #include <Akonadi/TagFetchScope>
0018 #include <KIconLoader>
0019 #include <KLocalizedString>
0020 #include <QIcon>
0021 #include <QPointer>
0022 using namespace MessageList::Core;
0023 
0024 Q_GLOBAL_STATIC(TagCache, s_tagCache)
0025 
0026 class MessageItem::Tag::TagPrivate
0027 {
0028 public:
0029     TagPrivate()
0030         : mPriority(0) // Initialize it
0031     {
0032     }
0033 
0034     QPixmap mPixmap;
0035     QString mName;
0036     QString mId; ///< The unique id of this tag
0037     QColor mTextColor;
0038     QColor mBackgroundColor;
0039     QFont mFont;
0040     int mPriority;
0041 };
0042 
0043 MessageItem::Tag::Tag(const QPixmap &pix, const QString &tagName, const QString &tagId)
0044     : d(new TagPrivate)
0045 {
0046     d->mPixmap = pix;
0047     d->mName = tagName;
0048     d->mId = tagId;
0049 }
0050 
0051 MessageItem::Tag::~Tag() = default;
0052 
0053 const QPixmap &MessageItem::Tag::pixmap() const
0054 {
0055     return d->mPixmap;
0056 }
0057 
0058 const QString &MessageItem::Tag::name() const
0059 {
0060     return d->mName;
0061 }
0062 
0063 const QString &MessageItem::Tag::id() const
0064 {
0065     return d->mId;
0066 }
0067 
0068 const QColor &MessageItem::Tag::textColor() const
0069 {
0070     return d->mTextColor;
0071 }
0072 
0073 const QColor &MessageItem::Tag::backgroundColor() const
0074 {
0075     return d->mBackgroundColor;
0076 }
0077 
0078 const QFont &MessageItem::Tag::font() const
0079 {
0080     return d->mFont;
0081 }
0082 
0083 int MessageItem::Tag::priority() const
0084 {
0085     return d->mPriority;
0086 }
0087 
0088 void MessageItem::Tag::setTextColor(const QColor &textColor)
0089 {
0090     d->mTextColor = textColor;
0091 }
0092 
0093 void MessageItem::Tag::setBackgroundColor(const QColor &backgroundColor)
0094 {
0095     d->mBackgroundColor = backgroundColor;
0096 }
0097 
0098 void MessageItem::Tag::setFont(const QFont &font)
0099 {
0100     d->mFont = font;
0101 }
0102 
0103 void MessageItem::Tag::setPriority(int priority)
0104 {
0105     d->mPriority = priority;
0106 }
0107 
0108 class MessageItemPrivateSettings
0109 {
0110 public:
0111     QColor mColorUnreadMessage;
0112     QColor mColorImportantMessage;
0113     QColor mColorToDoMessage;
0114     QFont mFont;
0115     QFont mFontUnreadMessage;
0116     QFont mFontImportantMessage;
0117     QFont mFontToDoMessage;
0118 
0119     // Keep those two invalid. They are here purely so that MessageItem can return
0120     // const reference to them
0121     QColor mColor;
0122     QColor mBackgroundColor;
0123 };
0124 
0125 Q_GLOBAL_STATIC(MessageItemPrivateSettings, s_settings)
0126 
0127 MessageItemPrivate::MessageItemPrivate(MessageItem *qq)
0128     : ItemPrivate(qq)
0129     , mThreadingStatus(MessageItem::ParentMissing)
0130     , mEncryptionState(MessageItem::NotEncrypted)
0131     , mSignatureState(MessageItem::NotSigned)
0132     , mAboutToBeRemoved(false)
0133     , mSubjectIsPrefixed(false)
0134     , mTagList(nullptr)
0135 {
0136 }
0137 
0138 MessageItemPrivate::~MessageItemPrivate()
0139 {
0140     s_tagCache->cancelRequest(this);
0141     invalidateTagCache();
0142 }
0143 
0144 void MessageItemPrivate::invalidateTagCache()
0145 {
0146     if (mTagList) {
0147         qDeleteAll(*mTagList);
0148         delete mTagList;
0149         mTagList = nullptr;
0150     }
0151 }
0152 
0153 void MessageItemPrivate::invalidateAnnotationCache()
0154 {
0155 }
0156 
0157 const MessageItem::Tag *MessageItemPrivate::bestTag() const
0158 {
0159     const MessageItem::Tag *best = nullptr;
0160     const auto tagList{getTagList()};
0161     for (const MessageItem::Tag *tag : tagList) {
0162         if (!best || tag->priority() < best->priority()) {
0163             best = tag;
0164         }
0165     }
0166     return best;
0167 }
0168 
0169 void MessageItemPrivate::fillTagList(const Akonadi::Tag::List &taglist)
0170 {
0171     Q_ASSERT(!mTagList);
0172     mTagList = new QList<MessageItem::Tag *>;
0173 
0174     // TODO: The tag pointers here could be shared between all items, there really is no point in
0175     //       creating them for each item that has tags
0176 
0177     // Priority sort this and make bestTag more efficient
0178 
0179     for (const Akonadi::Tag &tag : taglist) {
0180         QString symbol = QStringLiteral("mail-tagged");
0181         const auto attr = tag.attribute<Akonadi::TagAttribute>();
0182         if (attr) {
0183             if (!attr->iconName().isEmpty()) {
0184                 symbol = attr->iconName();
0185             }
0186         }
0187         auto messageListTag = new MessageItem::Tag(QIcon::fromTheme(symbol).pixmap(KIconLoader::SizeSmall), tag.name(), tag.url().url());
0188 
0189         if (attr) {
0190             messageListTag->setTextColor(attr->textColor());
0191             messageListTag->setBackgroundColor(attr->backgroundColor());
0192             if (!attr->font().isEmpty()) {
0193                 QFont font;
0194                 if (font.fromString(attr->font())) {
0195                     messageListTag->setFont(font);
0196                 }
0197             }
0198             if (attr->priority() != -1) {
0199                 messageListTag->setPriority(attr->priority());
0200             } else {
0201                 messageListTag->setPriority(0xFFFF);
0202             }
0203         }
0204 
0205         mTagList->append(messageListTag);
0206     }
0207 }
0208 
0209 QList<MessageItem::Tag *> MessageItemPrivate::getTagList() const
0210 {
0211     if (!mTagList) {
0212         s_tagCache->retrieveTags(mAkonadiItem.tags(), const_cast<MessageItemPrivate *>(this));
0213         return {};
0214     }
0215 
0216     return *mTagList;
0217 }
0218 
0219 bool MessageItemPrivate::tagListInitialized() const
0220 {
0221     return mTagList != nullptr;
0222 }
0223 
0224 MessageItem::MessageItem()
0225     : Item(Message, new MessageItemPrivate(this))
0226     , ModelInvariantIndex()
0227 {
0228 }
0229 
0230 MessageItem::MessageItem(MessageItemPrivate *dd)
0231     : Item(Message, dd)
0232     , ModelInvariantIndex()
0233 {
0234 }
0235 
0236 MessageItem::~MessageItem() = default;
0237 
0238 QList<MessageItem::Tag *> MessageItem::tagList() const
0239 {
0240     Q_D(const MessageItem);
0241     return d->getTagList();
0242 }
0243 
0244 bool MessageItem::hasAnnotation() const
0245 {
0246     Q_D(const MessageItem);
0247     // TODO check for note entry?
0248     return d->mAkonadiItem.hasAttribute<Akonadi::EntityAnnotationsAttribute>();
0249 }
0250 
0251 QString MessageItem::annotation() const
0252 {
0253     Q_D(const MessageItem);
0254     if (d->mAkonadiItem.hasAttribute<Akonadi::EntityAnnotationsAttribute>()) {
0255         auto attr = d->mAkonadiItem.attribute<Akonadi::EntityAnnotationsAttribute>();
0256         const auto annotations = attr->annotations();
0257         QByteArray annot = annotations.value("/private/comment");
0258         if (!annot.isEmpty()) {
0259             return QString::fromLatin1(annot);
0260         }
0261         annot = annotations.value("/shared/comment");
0262         if (!annot.isEmpty()) {
0263             return QString::fromLatin1(annot);
0264         }
0265     }
0266     return {};
0267 }
0268 
0269 void MessageItem::editAnnotation(QWidget *parent)
0270 {
0271     Q_D(MessageItem);
0272     QPointer<PimCommon::AnnotationEditDialog> mAnnotationDialog = new PimCommon::AnnotationEditDialog(d->mAkonadiItem, parent);
0273     // FIXME make async
0274     if (mAnnotationDialog->exec()) {
0275         // invalidate the cached mHasAnnotation value
0276     }
0277     delete mAnnotationDialog;
0278 }
0279 
0280 const MessageItem::Tag *MessageItemPrivate::findTagInternal(const QString &szTagId) const
0281 {
0282     const auto tagList{getTagList()};
0283     for (const MessageItem::Tag *tag : tagList) {
0284         if (tag->id() == szTagId) {
0285             return tag;
0286         }
0287     }
0288     return nullptr;
0289 }
0290 
0291 const MessageItem::Tag *MessageItem::findTag(const QString &szTagId) const
0292 {
0293     Q_D(const MessageItem);
0294     return d->findTagInternal(szTagId);
0295 }
0296 
0297 QString MessageItem::tagListDescription() const
0298 {
0299     QString ret;
0300 
0301     const auto tags{tagList()};
0302     for (const Tag *tag : tags) {
0303         if (!ret.isEmpty()) {
0304             ret += QLatin1StringView(", ");
0305         }
0306         ret += tag->name();
0307     }
0308 
0309     return ret;
0310 }
0311 
0312 void MessageItem::invalidateTagCache()
0313 {
0314     Q_D(MessageItem);
0315     d->invalidateTagCache();
0316 }
0317 
0318 void MessageItem::invalidateAnnotationCache()
0319 {
0320     Q_D(MessageItem);
0321     d->invalidateAnnotationCache();
0322 }
0323 
0324 const QColor &MessageItem::textColor() const
0325 {
0326     Q_D(const MessageItem);
0327     const Tag *bestTag = d->bestTag();
0328     if (bestTag != nullptr && bestTag->textColor().isValid()) {
0329         return bestTag->textColor();
0330     }
0331 
0332     Akonadi::MessageStatus messageStatus = status();
0333     if (!messageStatus.isRead()) {
0334         return s_settings->mColorUnreadMessage;
0335     } else if (messageStatus.isImportant()) {
0336         return s_settings->mColorImportantMessage;
0337     } else if (messageStatus.isToAct()) {
0338         return s_settings->mColorToDoMessage;
0339     } else {
0340         return s_settings->mColor;
0341     }
0342 }
0343 
0344 const QColor &MessageItem::backgroundColor() const
0345 {
0346     Q_D(const MessageItem);
0347     const Tag *bestTag = d->bestTag();
0348     if (bestTag) {
0349         return bestTag->backgroundColor();
0350     } else {
0351         return s_settings->mBackgroundColor;
0352     }
0353 }
0354 
0355 const QFont &MessageItem::font() const
0356 {
0357     Q_D(const MessageItem);
0358     // for performance reasons we don't want font retrieval to trigger
0359     // full tags loading, as the font is used for geometry calculation
0360     // and thus this method called for each item
0361     if (d->tagListInitialized()) {
0362         const Tag *bestTag = d->bestTag();
0363         if (bestTag && bestTag->font() != QFont()) {
0364             return bestTag->font();
0365         }
0366     }
0367 
0368     // from KDE3: "important" overrides "new" overrides "unread" overrides "todo"
0369     Akonadi::MessageStatus messageStatus = status();
0370     if (messageStatus.isImportant()) {
0371         return s_settings->mFontImportantMessage;
0372     } else if (!messageStatus.isRead()) {
0373         return s_settings->mFontUnreadMessage;
0374     } else if (messageStatus.isToAct()) {
0375         return s_settings->mFontToDoMessage;
0376     } else {
0377         return s_settings->mFont;
0378     }
0379 }
0380 
0381 MessageItem::SignatureState MessageItem::signatureState() const
0382 {
0383     Q_D(const MessageItem);
0384     return d->mSignatureState;
0385 }
0386 
0387 void MessageItem::setSignatureState(SignatureState state)
0388 {
0389     Q_D(MessageItem);
0390     d->mSignatureState = state;
0391 }
0392 
0393 MessageItem::EncryptionState MessageItem::encryptionState() const
0394 {
0395     Q_D(const MessageItem);
0396     return d->mEncryptionState;
0397 }
0398 
0399 void MessageItem::setEncryptionState(EncryptionState state)
0400 {
0401     Q_D(MessageItem);
0402     d->mEncryptionState = state;
0403 }
0404 
0405 QByteArray MessageItem::messageIdMD5() const
0406 {
0407     Q_D(const MessageItem);
0408     return d->mMessageIdMD5;
0409 }
0410 
0411 void MessageItem::setMessageIdMD5(const QByteArray &md5)
0412 {
0413     Q_D(MessageItem);
0414     d->mMessageIdMD5 = md5;
0415 }
0416 
0417 QByteArray MessageItem::inReplyToIdMD5() const
0418 {
0419     Q_D(const MessageItem);
0420     return d->mInReplyToIdMD5;
0421 }
0422 
0423 void MessageItem::setInReplyToIdMD5(const QByteArray &md5)
0424 {
0425     Q_D(MessageItem);
0426     d->mInReplyToIdMD5 = md5;
0427 }
0428 
0429 QByteArray MessageItem::referencesIdMD5() const
0430 {
0431     Q_D(const MessageItem);
0432     return d->mReferencesIdMD5;
0433 }
0434 
0435 void MessageItem::setReferencesIdMD5(const QByteArray &md5)
0436 {
0437     Q_D(MessageItem);
0438     d->mReferencesIdMD5 = md5;
0439 }
0440 
0441 void MessageItem::setSubjectIsPrefixed(bool subjectIsPrefixed)
0442 {
0443     Q_D(MessageItem);
0444     d->mSubjectIsPrefixed = subjectIsPrefixed;
0445 }
0446 
0447 bool MessageItem::subjectIsPrefixed() const
0448 {
0449     Q_D(const MessageItem);
0450     return d->mSubjectIsPrefixed;
0451 }
0452 
0453 QByteArray MessageItem::strippedSubjectMD5() const
0454 {
0455     Q_D(const MessageItem);
0456     return d->mStrippedSubjectMD5;
0457 }
0458 
0459 void MessageItem::setStrippedSubjectMD5(const QByteArray &md5)
0460 {
0461     Q_D(MessageItem);
0462     d->mStrippedSubjectMD5 = md5;
0463 }
0464 
0465 bool MessageItem::aboutToBeRemoved() const
0466 {
0467     Q_D(const MessageItem);
0468     return d->mAboutToBeRemoved;
0469 }
0470 
0471 void MessageItem::setAboutToBeRemoved(bool aboutToBeRemoved)
0472 {
0473     Q_D(MessageItem);
0474     d->mAboutToBeRemoved = aboutToBeRemoved;
0475 }
0476 
0477 MessageItem::ThreadingStatus MessageItem::threadingStatus() const
0478 {
0479     Q_D(const MessageItem);
0480     return d->mThreadingStatus;
0481 }
0482 
0483 void MessageItem::setThreadingStatus(ThreadingStatus threadingStatus)
0484 {
0485     Q_D(MessageItem);
0486     d->mThreadingStatus = threadingStatus;
0487 }
0488 
0489 unsigned long MessageItem::uniqueId() const
0490 {
0491     Q_D(const MessageItem);
0492     return d->mAkonadiItem.id();
0493 }
0494 
0495 Akonadi::Item MessageList::Core::MessageItem::akonadiItem() const
0496 {
0497     Q_D(const MessageItem);
0498     return d->mAkonadiItem;
0499 }
0500 
0501 void MessageList::Core::MessageItem::setAkonadiItem(const Akonadi::Item &item)
0502 {
0503     Q_D(MessageItem);
0504     d->mAkonadiItem = item;
0505 }
0506 
0507 MessageItem *MessageItem::topmostMessage()
0508 {
0509     if (!parent()) {
0510         return this;
0511     }
0512     if (parent()->type() == Item::Message) {
0513         return static_cast<MessageItem *>(parent())->topmostMessage();
0514     }
0515     return this;
0516 }
0517 
0518 QString MessageItem::accessibleTextForField(Theme::ContentItem::Type field)
0519 {
0520     switch (field) {
0521     case Theme::ContentItem::Subject:
0522         return d_ptr->mSubject;
0523     case Theme::ContentItem::Sender:
0524         return d_ptr->mSender;
0525     case Theme::ContentItem::Receiver:
0526         return d_ptr->mReceiver;
0527     case Theme::ContentItem::SenderOrReceiver:
0528         return senderOrReceiver();
0529     case Theme::ContentItem::Date:
0530         return formattedDate();
0531     case Theme::ContentItem::Size:
0532         return formattedSize();
0533     case Theme::ContentItem::RepliedStateIcon:
0534         return status().isReplied() ? i18nc("Status of an item", "Replied") : QString();
0535     case Theme::ContentItem::ReadStateIcon:
0536         return status().isRead() ? i18nc("Status of an item", "Read") : i18nc("Status of an item", "Unread");
0537     case Theme::ContentItem::CombinedReadRepliedStateIcon:
0538         return accessibleTextForField(Theme::ContentItem::ReadStateIcon) + accessibleTextForField(Theme::ContentItem::RepliedStateIcon);
0539     default:
0540         return {};
0541     }
0542 }
0543 
0544 QString MessageItem::accessibleText(const Theme *theme, int columnIndex)
0545 {
0546     QStringList rowsTexts;
0547     const QList<Theme::Row *> rows = theme->column(columnIndex)->messageRows();
0548     rowsTexts.reserve(rows.count());
0549 
0550     for (Theme::Row *row : rows) {
0551         QStringList leftStrings;
0552         QStringList rightStrings;
0553         const auto leftItems = row->leftItems();
0554         leftStrings.reserve(leftItems.count());
0555         for (Theme::ContentItem *contentItem : std::as_const(leftItems)) {
0556             leftStrings.append(accessibleTextForField(contentItem->type()));
0557         }
0558 
0559         const auto rightItems = row->rightItems();
0560         rightStrings.reserve(rightItems.count());
0561         for (Theme::ContentItem *contentItem : rightItems) {
0562             rightStrings.insert(rightStrings.begin(), accessibleTextForField(contentItem->type()));
0563         }
0564 
0565         rowsTexts.append((leftStrings + rightStrings).join(QLatin1Char(' ')));
0566     }
0567 
0568     return rowsTexts.join(QLatin1Char(' '));
0569 }
0570 
0571 void MessageItem::subTreeToList(QList<MessageItem *> &list)
0572 {
0573     list.append(this);
0574     const auto childList = childItems();
0575     if (!childList) {
0576         return;
0577     }
0578     for (const auto child : std::as_const(*childList)) {
0579         Q_ASSERT(child->type() == Item::Message);
0580         static_cast<MessageItem *>(child)->subTreeToList(list);
0581     }
0582 }
0583 
0584 void MessageItem::setUnreadMessageColor(const QColor &color)
0585 {
0586     s_settings->mColorUnreadMessage = color;
0587 }
0588 
0589 void MessageItem::setImportantMessageColor(const QColor &color)
0590 {
0591     s_settings->mColorImportantMessage = color;
0592 }
0593 
0594 void MessageItem::setToDoMessageColor(const QColor &color)
0595 {
0596     s_settings->mColorToDoMessage = color;
0597 }
0598 
0599 void MessageItem::setGeneralFont(const QFont &font)
0600 {
0601     s_settings->mFont = font;
0602 }
0603 
0604 void MessageItem::setUnreadMessageFont(const QFont &font)
0605 {
0606     s_settings->mFontUnreadMessage = font;
0607 }
0608 
0609 void MessageItem::setImportantMessageFont(const QFont &font)
0610 {
0611     s_settings->mFontImportantMessage = font;
0612 }
0613 
0614 void MessageItem::setToDoMessageFont(const QFont &font)
0615 {
0616     s_settings->mFontToDoMessage = font;
0617 }
0618 
0619 FakeItemPrivate::FakeItemPrivate(FakeItem *qq)
0620     : MessageItemPrivate(qq)
0621 {
0622 }
0623 
0624 FakeItem::FakeItem()
0625     : MessageItem(new FakeItemPrivate(this))
0626 {
0627 }
0628 
0629 FakeItem::~FakeItem()
0630 {
0631     Q_D(const FakeItem);
0632     qDeleteAll(d->mFakeTags);
0633 }
0634 
0635 QList<MessageItem::Tag *> FakeItem::tagList() const
0636 {
0637     Q_D(const FakeItem);
0638     return d->mFakeTags;
0639 }
0640 
0641 void FakeItem::setFakeTags(const QList<MessageItem::Tag *> &tagList)
0642 {
0643     Q_D(FakeItem);
0644     d->mFakeTags = tagList;
0645 }
0646 
0647 bool FakeItem::hasAnnotation() const
0648 {
0649     return true;
0650 }
0651 
0652 TagCache::TagCache()
0653     : QObject()
0654     , mMonitor(new Akonadi::Monitor(this))
0655 {
0656     mCache.setMaxCost(100);
0657     mMonitor->setObjectName(QLatin1StringView("MessageListTagCacheMonitor"));
0658     mMonitor->setTypeMonitored(Akonadi::Monitor::Tags);
0659     mMonitor->tagFetchScope().fetchAttribute<Akonadi::TagAttribute>();
0660     connect(mMonitor, &Akonadi::Monitor::tagAdded, this, &TagCache::onTagAdded);
0661     connect(mMonitor, &Akonadi::Monitor::tagRemoved, this, &TagCache::onTagRemoved);
0662     connect(mMonitor, &Akonadi::Monitor::tagChanged, this, &TagCache::onTagChanged);
0663 }
0664 
0665 void TagCache::onTagAdded(const Akonadi::Tag &tag)
0666 {
0667     mCache.insert(tag.id(), new Akonadi::Tag(tag));
0668 }
0669 
0670 void TagCache::onTagChanged(const Akonadi::Tag &tag)
0671 {
0672     mCache.remove(tag.id());
0673 }
0674 
0675 void TagCache::onTagRemoved(const Akonadi::Tag &tag)
0676 {
0677     mCache.remove(tag.id());
0678 }
0679 
0680 void TagCache::retrieveTags(const Akonadi::Tag::List &tags, MessageItemPrivate *m)
0681 {
0682     // Retrieval is in progress
0683     if (mRequests.key(m)) {
0684         return;
0685     }
0686     Akonadi::Tag::List toFetch;
0687     Akonadi::Tag::List available;
0688     for (const Akonadi::Tag &tag : tags) {
0689         if (mCache.contains(tag.id())) {
0690             available << *mCache.object(tag.id());
0691         } else {
0692             toFetch << tag;
0693         }
0694     }
0695     // Because fillTagList expects to be called once we either fetch all or none
0696     if (!toFetch.isEmpty()) {
0697         auto tagFetchJob = new Akonadi::TagFetchJob(tags, this);
0698         tagFetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
0699         connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TagCache::onTagsFetched);
0700         mRequests.insert(tagFetchJob, m);
0701     } else {
0702         m->fillTagList(available);
0703     }
0704 }
0705 
0706 void TagCache::cancelRequest(MessageItemPrivate *m)
0707 {
0708     const QList<KJob *> keys = mRequests.keys(m);
0709     for (KJob *job : keys) {
0710         mRequests.remove(job);
0711     }
0712 }
0713 
0714 void TagCache::onTagsFetched(KJob *job)
0715 {
0716     if (job->error()) {
0717         qCWarning(MESSAGELIST_LOG) << "Failed to fetch tags: " << job->errorString();
0718         return;
0719     }
0720     auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
0721     const auto tags{fetchJob->tags()};
0722     for (const Akonadi::Tag &tag : tags) {
0723         mCache.insert(tag.id(), new Akonadi::Tag(tag));
0724     }
0725     if (auto m = mRequests.take(fetchJob)) {
0726         m->fillTagList(fetchJob->tags());
0727     }
0728 }
0729 
0730 #include "moc_messageitem_p.cpp"