Messagelib

messageitem.cpp
1/******************************************************************************
2 *
3 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 *
7 *******************************************************************************/
8
9#include "messageitem.h"
10#include "messageitem_p.h"
11
12#include "messagelist_debug.h"
13#include <Akonadi/Item>
14#include <Akonadi/TagAttribute>
15#include <Akonadi/TagFetchJob>
16#include <Akonadi/TagFetchScope>
17#include <KIconLoader>
18#include <KLocalizedString>
19#include <QIcon>
20#include <QPointer>
21using namespace MessageList::Core;
22
23Q_GLOBAL_STATIC(TagCache, s_tagCache)
24
25class MessageItem::Tag::TagPrivate
26{
27public:
28 TagPrivate()
29 : mPriority(0) // Initialize it
30 {
31 }
32
33 QPixmap mPixmap;
34 QString mName;
35 QString mId; ///< The unique id of this tag
36 QColor mTextColor;
37 QColor mBackgroundColor;
38 QFont mFont;
39 int mPriority;
40};
41
42MessageItem::Tag::Tag(const QPixmap &pix, const QString &tagName, const QString &tagId)
43 : d(new TagPrivate)
44{
45 d->mPixmap = pix;
46 d->mName = tagName;
47 d->mId = tagId;
48}
49
50MessageItem::Tag::~Tag() = default;
51
52const QPixmap &MessageItem::Tag::pixmap() const
53{
54 return d->mPixmap;
55}
56
57const QString &MessageItem::Tag::name() const
58{
59 return d->mName;
60}
61
62const QString &MessageItem::Tag::id() const
63{
64 return d->mId;
65}
66
67const QColor &MessageItem::Tag::textColor() const
68{
69 return d->mTextColor;
70}
71
72const QColor &MessageItem::Tag::backgroundColor() const
73{
74 return d->mBackgroundColor;
75}
76
77const QFont &MessageItem::Tag::font() const
78{
79 return d->mFont;
80}
81
82int MessageItem::Tag::priority() const
83{
84 return d->mPriority;
85}
86
87void MessageItem::Tag::setTextColor(const QColor &textColor)
88{
89 d->mTextColor = textColor;
90}
91
92void MessageItem::Tag::setBackgroundColor(const QColor &backgroundColor)
93{
94 d->mBackgroundColor = backgroundColor;
95}
96
97void MessageItem::Tag::setFont(const QFont &font)
98{
99 d->mFont = font;
100}
101
102void MessageItem::Tag::setPriority(int priority)
103{
104 d->mPriority = priority;
105}
106
107class MessageItemPrivateSettings
108{
109public:
110 QColor mColorUnreadMessage;
111 QColor mColorImportantMessage;
112 QColor mColorToDoMessage;
113 QFont mFont;
114 QFont mFontUnreadMessage;
115 QFont mFontImportantMessage;
116 QFont mFontToDoMessage;
117
118 // Keep those two invalid. They are here purely so that MessageItem can return
119 // const reference to them
120 QColor mColor;
121 QColor mBackgroundColor;
122};
123
124Q_GLOBAL_STATIC(MessageItemPrivateSettings, s_settings)
125
126MessageItemPrivate::MessageItemPrivate(MessageItem *qq)
127 : ItemPrivate(qq)
128 , mThreadingStatus(MessageItem::ParentMissing)
129 , mEncryptionState(MessageItem::NotEncrypted)
130 , mSignatureState(MessageItem::NotSigned)
131 , mAboutToBeRemoved(false)
132 , mSubjectIsPrefixed(false)
133 , mTagList(nullptr)
134{
135}
136
137MessageItemPrivate::~MessageItemPrivate()
138{
139 s_tagCache->cancelRequest(this);
140 invalidateTagCache();
141}
142
143void MessageItemPrivate::invalidateTagCache()
144{
145 if (mTagList) {
146 qDeleteAll(*mTagList);
147 delete mTagList;
148 mTagList = nullptr;
149 }
150}
151
152const MessageItem::Tag *MessageItemPrivate::bestTag() const
153{
154 const MessageItem::Tag *best = nullptr;
155 const auto tagList{getTagList()};
156 for (const MessageItem::Tag *tag : tagList) {
157 if (!best || tag->priority() < best->priority()) {
158 best = tag;
159 }
160 }
161 return best;
162}
163
164void MessageItemPrivate::fillTagList(const Akonadi::Tag::List &taglist)
165{
166 Q_ASSERT(!mTagList);
167 mTagList = new QList<MessageItem::Tag *>;
168
169 // TODO: The tag pointers here could be shared between all items, there really is no point in
170 // creating them for each item that has tags
171
172 // Priority sort this and make bestTag more efficient
173
174 for (const Akonadi::Tag &tag : taglist) {
175 QString symbol = QStringLiteral("mail-tagged");
176 const auto attr = tag.attribute<Akonadi::TagAttribute>();
177 if (attr) {
178 if (!attr->iconName().isEmpty()) {
179 symbol = attr->iconName();
180 }
181 }
182 auto messageListTag = new MessageItem::Tag(QIcon::fromTheme(symbol).pixmap(KIconLoader::SizeSmall), tag.name(), tag.url().url());
183
184 if (attr) {
185 messageListTag->setTextColor(attr->textColor());
186 messageListTag->setBackgroundColor(attr->backgroundColor());
187 if (!attr->font().isEmpty()) {
188 QFont font;
189 if (font.fromString(attr->font())) {
190 messageListTag->setFont(font);
191 }
192 }
193 if (attr->priority() != -1) {
194 messageListTag->setPriority(attr->priority());
195 } else {
196 messageListTag->setPriority(0xFFFF);
197 }
198 }
199
200 mTagList->append(messageListTag);
201 }
202}
203
204QList<MessageItem::Tag *> MessageItemPrivate::getTagList() const
205{
206 if (!mTagList) {
207 s_tagCache->retrieveTags(mAkonadiItem.tags(), const_cast<MessageItemPrivate *>(this));
208 return {};
209 }
210
211 return *mTagList;
212}
213
214bool MessageItemPrivate::tagListInitialized() const
215{
216 return mTagList != nullptr;
217}
218
219MessageItem::MessageItem()
220 : Item(Message, new MessageItemPrivate(this))
222{
223}
224
225MessageItem::MessageItem(MessageItemPrivate *dd)
226 : Item(Message, dd)
228{
229}
230
231MessageItem::~MessageItem() = default;
232
234{
235 Q_D(const MessageItem);
236 return d->getTagList();
237}
238
239const MessageItem::Tag *MessageItemPrivate::findTagInternal(const QString &szTagId) const
240{
241 const auto tagList{getTagList()};
242 for (const MessageItem::Tag *tag : tagList) {
243 if (tag->id() == szTagId) {
244 return tag;
245 }
246 }
247 return nullptr;
248}
249
250const MessageItem::Tag *MessageItem::findTag(const QString &szTagId) const
251{
252 Q_D(const MessageItem);
253 return d->findTagInternal(szTagId);
254}
255
256QString MessageItem::tagListDescription() const
257{
258 QString ret;
259
260 const auto tags{tagList()};
261 for (const Tag *tag : tags) {
262 if (!ret.isEmpty()) {
263 ret += QLatin1StringView(", ");
264 }
265 ret += tag->name();
266 }
267
268 return ret;
269}
270
272{
274 d->invalidateTagCache();
275}
276
277const QColor &MessageItem::textColor() const
278{
279 Q_D(const MessageItem);
280 const Tag *bestTag = d->bestTag();
281 if (bestTag != nullptr && bestTag->textColor().isValid()) {
282 return bestTag->textColor();
283 }
284
285 Akonadi::MessageStatus messageStatus = status();
286 if (!messageStatus.isRead()) {
287 return s_settings->mColorUnreadMessage;
288 } else if (messageStatus.isImportant()) {
289 return s_settings->mColorImportantMessage;
290 } else if (messageStatus.isToAct()) {
291 return s_settings->mColorToDoMessage;
292 } else {
293 return s_settings->mColor;
294 }
295}
296
297const QColor &MessageItem::backgroundColor() const
298{
299 Q_D(const MessageItem);
300 const Tag *bestTag = d->bestTag();
301 if (bestTag) {
302 return bestTag->backgroundColor();
303 } else {
304 return s_settings->mBackgroundColor;
305 }
306}
307
308const QFont &MessageItem::font() const
309{
310 Q_D(const MessageItem);
311 // for performance reasons we don't want font retrieval to trigger
312 // full tags loading, as the font is used for geometry calculation
313 // and thus this method called for each item
314 if (d->tagListInitialized()) {
315 const Tag *bestTag = d->bestTag();
316 if (bestTag && bestTag->font() != QFont()) {
317 return bestTag->font();
318 }
319 }
320
321 // from KDE3: "important" overrides "new" overrides "unread" overrides "todo"
322 Akonadi::MessageStatus messageStatus = status();
323 if (messageStatus.isImportant()) {
324 return s_settings->mFontImportantMessage;
325 } else if (!messageStatus.isRead()) {
326 return s_settings->mFontUnreadMessage;
327 } else if (messageStatus.isToAct()) {
328 return s_settings->mFontToDoMessage;
329 } else {
330 return s_settings->mFont;
331 }
332}
333
334MessageItem::SignatureState MessageItem::signatureState() const
335{
336 Q_D(const MessageItem);
337 return d->mSignatureState;
338}
339
340void MessageItem::setSignatureState(SignatureState state)
341{
343 d->mSignatureState = state;
344}
345
346MessageItem::EncryptionState MessageItem::encryptionState() const
347{
348 Q_D(const MessageItem);
349 return d->mEncryptionState;
350}
351
352void MessageItem::setEncryptionState(EncryptionState state)
353{
355 d->mEncryptionState = state;
356}
357
358QByteArray MessageItem::messageIdMD5() const
359{
360 Q_D(const MessageItem);
361 return d->mMessageIdMD5;
362}
363
364void MessageItem::setMessageIdMD5(const QByteArray &md5)
365{
367 d->mMessageIdMD5 = md5;
368}
369
370QByteArray MessageItem::inReplyToIdMD5() const
371{
372 Q_D(const MessageItem);
373 return d->mInReplyToIdMD5;
374}
375
376void MessageItem::setInReplyToIdMD5(const QByteArray &md5)
377{
379 d->mInReplyToIdMD5 = md5;
380}
381
382QByteArray MessageItem::referencesIdMD5() const
383{
384 Q_D(const MessageItem);
385 return d->mReferencesIdMD5;
386}
387
388void MessageItem::setReferencesIdMD5(const QByteArray &md5)
389{
391 d->mReferencesIdMD5 = md5;
392}
393
394void MessageItem::setSubjectIsPrefixed(bool subjectIsPrefixed)
395{
397 d->mSubjectIsPrefixed = subjectIsPrefixed;
398}
399
400bool MessageItem::subjectIsPrefixed() const
401{
402 Q_D(const MessageItem);
403 return d->mSubjectIsPrefixed;
404}
405
406QByteArray MessageItem::strippedSubjectMD5() const
407{
408 Q_D(const MessageItem);
409 return d->mStrippedSubjectMD5;
410}
411
412void MessageItem::setStrippedSubjectMD5(const QByteArray &md5)
413{
415 d->mStrippedSubjectMD5 = md5;
416}
417
418bool MessageItem::aboutToBeRemoved() const
419{
420 Q_D(const MessageItem);
421 return d->mAboutToBeRemoved;
422}
423
424void MessageItem::setAboutToBeRemoved(bool aboutToBeRemoved)
425{
427 d->mAboutToBeRemoved = aboutToBeRemoved;
428}
429
430MessageItem::ThreadingStatus MessageItem::threadingStatus() const
431{
432 Q_D(const MessageItem);
433 return d->mThreadingStatus;
434}
435
436void MessageItem::setThreadingStatus(ThreadingStatus threadingStatus)
437{
439 d->mThreadingStatus = threadingStatus;
440}
441
442unsigned long MessageItem::uniqueId() const
443{
444 Q_D(const MessageItem);
445 return d->mAkonadiItem.id();
446}
447
448Akonadi::Item MessageList::Core::MessageItem::akonadiItem() const
449{
450 Q_D(const MessageItem);
451 return d->mAkonadiItem;
452}
453
454void MessageList::Core::MessageItem::setAkonadiItem(const Akonadi::Item &item)
455{
457 d->mAkonadiItem = item;
458}
459
460MessageItem *MessageItem::topmostMessage()
461{
462 if (!parent()) {
463 return this;
464 }
465 if (parent()->type() == Item::Message) {
466 return static_cast<MessageItem *>(parent())->topmostMessage();
467 }
468 return this;
469}
470
471QString MessageItem::accessibleTextForField(Theme::ContentItem::Type field)
472{
473 switch (field) {
475 return d_ptr->mSubject;
477 return d_ptr->mSender;
479 return d_ptr->mReceiver;
481 return senderOrReceiver();
483 return formattedDate();
485 return formattedSize();
487 return status().isReplied() ? i18nc("Status of an item", "Replied") : QString();
489 return status().isRead() ? i18nc("Status of an item", "Read") : i18nc("Status of an item", "Unread");
491 return accessibleTextForField(Theme::ContentItem::ReadStateIcon) + accessibleTextForField(Theme::ContentItem::RepliedStateIcon);
492 default:
493 return {};
494 }
495}
496
497QString MessageItem::accessibleText(const Theme *theme, int columnIndex)
498{
499 QStringList rowsTexts;
500 const QList<Theme::Row *> rows = theme->column(columnIndex)->messageRows();
501 rowsTexts.reserve(rows.count());
502
503 for (Theme::Row *row : rows) {
504 QStringList leftStrings;
505 QStringList rightStrings;
506 const auto leftItems = row->leftItems();
507 leftStrings.reserve(leftItems.count());
508 for (Theme::ContentItem *contentItem : std::as_const(leftItems)) {
509 leftStrings.append(accessibleTextForField(contentItem->type()));
510 }
511
512 const auto rightItems = row->rightItems();
513 rightStrings.reserve(rightItems.count());
514 for (Theme::ContentItem *contentItem : rightItems) {
515 rightStrings.insert(rightStrings.begin(), accessibleTextForField(contentItem->type()));
516 }
517
518 rowsTexts.append((leftStrings + rightStrings).join(QLatin1Char(' ')));
519 }
520
521 return rowsTexts.join(QLatin1Char(' '));
522}
523
525{
526 list.append(this);
527 const auto childList = childItems();
528 if (!childList) {
529 return;
530 }
531 for (const auto child : std::as_const(*childList)) {
532 Q_ASSERT(child->type() == Item::Message);
533 static_cast<MessageItem *>(child)->subTreeToList(list);
534 }
535}
536
537void MessageItem::setUnreadMessageColor(const QColor &color)
538{
539 s_settings->mColorUnreadMessage = color;
540}
541
542void MessageItem::setImportantMessageColor(const QColor &color)
543{
544 s_settings->mColorImportantMessage = color;
545}
546
547void MessageItem::setToDoMessageColor(const QColor &color)
548{
549 s_settings->mColorToDoMessage = color;
550}
551
552void MessageItem::setGeneralFont(const QFont &font)
553{
554 s_settings->mFont = font;
555}
556
557void MessageItem::setUnreadMessageFont(const QFont &font)
558{
559 s_settings->mFontUnreadMessage = font;
560}
561
562void MessageItem::setImportantMessageFont(const QFont &font)
563{
564 s_settings->mFontImportantMessage = font;
565}
566
567void MessageItem::setToDoMessageFont(const QFont &font)
568{
569 s_settings->mFontToDoMessage = font;
570}
571
572FakeItemPrivate::FakeItemPrivate(FakeItem *qq)
573 : MessageItemPrivate(qq)
574{
575}
576
577FakeItem::FakeItem()
578 : MessageItem(new FakeItemPrivate(this))
579{
580}
581
582FakeItem::~FakeItem()
583{
584 Q_D(const FakeItem);
585 qDeleteAll(d->mFakeTags);
586}
587
589{
590 Q_D(const FakeItem);
591 return d->mFakeTags;
592}
593
595{
596 Q_D(FakeItem);
597 d->mFakeTags = tagList;
598}
599
600TagCache::TagCache()
601 : QObject()
602 , mMonitor(new Akonadi::Monitor(this))
603{
604 mCache.setMaxCost(100);
605 mMonitor->setObjectName(QLatin1StringView("MessageListTagCacheMonitor"));
606 mMonitor->setTypeMonitored(Akonadi::Monitor::Tags);
607 mMonitor->tagFetchScope().fetchAttribute<Akonadi::TagAttribute>();
608 connect(mMonitor, &Akonadi::Monitor::tagAdded, this, &TagCache::onTagAdded);
609 connect(mMonitor, &Akonadi::Monitor::tagRemoved, this, &TagCache::onTagRemoved);
610 connect(mMonitor, &Akonadi::Monitor::tagChanged, this, &TagCache::onTagChanged);
611}
612
613void TagCache::onTagAdded(const Akonadi::Tag &tag)
614{
615 mCache.insert(tag.id(), new Akonadi::Tag(tag));
616}
617
618void TagCache::onTagChanged(const Akonadi::Tag &tag)
619{
620 mCache.remove(tag.id());
621}
622
623void TagCache::onTagRemoved(const Akonadi::Tag &tag)
624{
625 mCache.remove(tag.id());
626}
627
628void TagCache::retrieveTags(const Akonadi::Tag::List &tags, MessageItemPrivate *m)
629{
630 // Retrieval is in progress
631 if (mRequests.key(m)) {
632 return;
633 }
634 Akonadi::Tag::List toFetch;
635 Akonadi::Tag::List available;
636 for (const Akonadi::Tag &tag : tags) {
637 if (mCache.contains(tag.id())) {
638 available << *mCache.object(tag.id());
639 } else {
640 toFetch << tag;
641 }
642 }
643 // Because fillTagList expects to be called once we either fetch all or none
644 if (!toFetch.isEmpty()) {
645 auto tagFetchJob = new Akonadi::TagFetchJob(tags, this);
646 tagFetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
647 connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TagCache::onTagsFetched);
648 mRequests.insert(tagFetchJob, m);
649 } else {
650 m->fillTagList(available);
651 }
652}
653
654void TagCache::cancelRequest(MessageItemPrivate *m)
655{
656 const QList<KJob *> keys = mRequests.keys(m);
657 for (KJob *job : keys) {
658 mRequests.remove(job);
659 }
660}
661
662void TagCache::onTagsFetched(KJob *job)
663{
664 if (job->error()) {
665 qCWarning(MESSAGELIST_LOG) << "Failed to fetch tags: " << job->errorString();
666 return;
667 }
668 auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
669 const auto tags{fetchJob->tags()};
670 for (const Akonadi::Tag &tag : tags) {
671 mCache.insert(tag.id(), new Akonadi::Tag(tag));
672 }
673 if (auto m = mRequests.take(fetchJob)) {
674 m->fillTagList(fetchJob->tags());
675 }
676}
677
678#include "moc_messageitem_p.cpp"
bool isImportant() const
bool isReplied() const
void tagRemoved(const Akonadi::Tag &tag)
void tagChanged(const Akonadi::Tag &tag)
void tagAdded(const Akonadi::Tag &tag)
Id id() const
virtual QString errorString() const
int error() const
void result(KJob *job)
A message item that can have a fake tag list and a fake annotation.
void setFakeTags(const QList< Tag * > &tagList)
Sets a list of fake tags for this item.
QList< Tag * > tagList() const override
Reimplemented to return the fake tag list.
const QString & senderOrReceiver() const
Returns the sender or the receiver, depending on the underlying StorageModel settings.
Definition item.cpp:516
QString formattedDate() const
A string with a text rappresentation of date() obtained via Manager.
Definition item.cpp:305
const Akonadi::MessageStatus & status() const
Returns the status associated to this Item.
Definition item.cpp:446
Type type() const
Returns the type of this item.
Definition item.cpp:342
Item * parent() const
Returns the parent Item in the tree, or 0 if this item isn't attached to the tree.
Definition item.cpp:436
@ Message
This item is a MessageItem.
Definition item.h:46
QString formattedSize() const
A string with a text rappresentation of size().
Definition item.cpp:299
QList< Item * > * childItems() const
Return the list of child items.
Definition item.cpp:58
The MessageItem class.
Definition messageitem.h:35
const Tag * findTag(const QString &szTagId) const
Returns Tag associated to this message that has the specified id or 0 if no such tag exists.
void invalidateTagCache()
Deletes all cached tags.
@ ParentMissing
this message might belong to a thread but its parent is actually missing
Definition messageitem.h:63
void subTreeToList(QList< MessageItem * > &list)
Appends the whole subtree originating at this item to the specified list.
virtual QList< Tag * > tagList() const
Returns the list of tags for this item.
An invariant index that can be ALWAYS used to reference an item inside a QAbstractItemModel.
const QList< Row * > & messageRows() const
Returns the list of rows visible in this column for a MessageItem.
Definition theme.cpp:700
The ContentItem class defines a content item inside a Row.
Definition theme.h:56
Type
The available ContentItem types.
Definition theme.h:106
@ CombinedReadRepliedStateIcon
The combined icon that displays the unread/read/replied/forwarded state (never disabled)
Definition theme.h:190
@ Date
Formatted date time of the message/group.
Definition theme.h:114
@ ReadStateIcon
The icon that displays the unread/read state (never disabled)
Definition theme.h:134
@ RepliedStateIcon
The icon that displays the replied/forwarded state (may be disabled)
Definition theme.h:142
@ SenderOrReceiver
From: or To: strip, depending on the folder settings.
Definition theme.h:118
@ Subject
Display the subject of the message item.
Definition theme.h:110
@ Size
Formatted size of the message.
Definition theme.h:130
@ Receiver
To: strip, always.
Definition theme.h:126
@ Sender
From: strip, always.
Definition theme.h:122
The Row class defines a row of items inside a Column.
Definition theme.h:408
The Theme class defines the visual appearance of the MessageList.
Definition theme.h:48
Column * column(int idx) const
Returns a pointer to the column at the specified index or 0 if there is no such column.
Definition theme.cpp:955
QString i18nc(const char *context, const char *text, const TYPE &arg...)
The implementation independent part of the MessageList library.
Definition aggregation.h:22
bool fromString(const QString &descrip)
QIcon fromTheme(const QString &name)
QString name() const const
void append(QList< T > &&value)
iterator begin()
qsizetype count() const const
iterator insert(const_iterator before, parameter_type value)
void reserve(qsizetype size)
void setObjectName(QAnyStringView name)
bool isEmpty() const const
QString join(QChar separator) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:33:26 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.