1 // For license of this file, see <project-root-folder>/LICENSE.md.
2
3 #include "core/messagesmodel.h"
4
5 #include "core/messagesmodelcache.h"
6 #include "database/databasefactory.h"
7 #include "database/databasequeries.h"
8 #include "definitions/definitions.h"
9 #include "miscellaneous/application.h"
10 #include "miscellaneous/iconfactory.h"
11 #include "miscellaneous/skinfactory.h"
12 #include "miscellaneous/textfactory.h"
13 #include "services/abstract/recyclebin.h"
14 #include "services/abstract/serviceroot.h"
15
16 #include <QPainter>
17 #include <QPainterPath>
18 #include <QSqlError>
19 #include <QSqlField>
20
21 #include <cmath>
22
MessagesModel(QObject * parent)23 MessagesModel::MessagesModel(QObject* parent)
24 : QSqlQueryModel(parent), m_cache(new MessagesModelCache(this)), m_messageHighlighter(MessageHighlighter::NoHighlighting),
25 m_customDateFormat(QString()), m_selectedItem(nullptr), m_itemHeight(-1), m_displayFeedIcons(false) {
26 setupFonts();
27 setupIcons();
28 setupHeaderData();
29 updateDateFormat();
30 updateFeedIconsDisplay();
31 loadMessages(nullptr);
32 }
33
~MessagesModel()34 MessagesModel::~MessagesModel() {
35 qDebugNN << LOGSEC_MESSAGEMODEL << "Destroying MessagesModel instance.";
36 }
37
setupIcons()38 void MessagesModel::setupIcons() {
39 m_favoriteIcon = qApp->icons()->fromTheme(QSL("mail-mark-important"));
40 m_readIcon = qApp->icons()->fromTheme(QSL("mail-mark-read"));
41 m_unreadIcon = qApp->icons()->fromTheme(QSL("mail-mark-unread"));
42 m_enclosuresIcon = qApp->icons()->fromTheme(QSL("mail-attachment"));
43
44 for (int i = int(MSG_SCORE_MIN); i <= int(MSG_SCORE_MAX); i += 10) {
45 m_scoreIcons.append(generateIconForScore(double(i)));
46 }
47 }
48
generateIconForScore(double score)49 QIcon MessagesModel::generateIconForScore(double score) {
50 QPixmap pix(64, 64);
51 QPainter paint(&pix);
52
53 paint.setRenderHint(QPainter::RenderHint::Antialiasing);
54
55 int level = std::min(MSG_SCORE_MAX, std::max(MSG_SCORE_MIN, std::floor(score / 10.0)));
56 QPainterPath path;
57
58 path.addRoundedRect(QRectF(2, 2, 60, 60), 5, 5);
59
60 QPen pen(Qt::GlobalColor::black, 2);
61
62 paint.setPen(pen);
63 paint.fillPath(path, Qt::GlobalColor::white);
64 paint.drawPath(path);
65
66 #if QT_VERSION >= 0x050D00 // Qt >= 5.13.0
67 path.clear();
68 #else
69 path = QPainterPath();
70 #endif
71
72 paint.setPen(Qt::GlobalColor::transparent);
73
74 int bar_height = 6 * level;
75
76 path.addRoundedRect(QRectF(2, 64 - bar_height - 2, 60, bar_height), 5, 5);
77 paint.fillPath(path, QColor::fromHsv(int(score), 200, 230));
78
79 return pix;
80 }
81
cache() const82 MessagesModelCache* MessagesModel::cache() const {
83 return m_cache;
84 }
85
repopulate()86 void MessagesModel::repopulate() {
87 m_cache->clear();
88 setQuery(selectStatement(), m_db);
89
90 if (lastError().isValid()) {
91 qCriticalNN << LOGSEC_MESSAGEMODEL << "Error when setting new msg view query: '" << lastError().text() << "'.";
92 qCriticalNN << LOGSEC_MESSAGEMODEL << "Used SQL select statement: '" << selectStatement() << "'.";
93 }
94
95 while (canFetchMore()) {
96 fetchMore();
97 }
98
99 qDebugNN << LOGSEC_MESSAGEMODEL
100 << "Repopulated model, SQL statement is now:\n"
101 << QUOTE_W_SPACE_DOT(selectStatement());
102 }
103
setData(const QModelIndex & index,const QVariant & value,int role)104 bool MessagesModel::setData(const QModelIndex& index, const QVariant& value, int role) {
105 Q_UNUSED(role)
106 m_cache->setData(index, value, record(index.row()));
107 return true;
108 }
109
setupFonts()110 void MessagesModel::setupFonts() {
111 QFont fon;
112
113 fon.fromString(qApp->settings()->value(GROUP(Messages), Messages::ListFont, Application::font("MessagesView").toString()).toString());
114
115 m_normalFont = fon;
116 m_boldFont = m_normalFont;
117 m_boldFont.setBold(true);
118 m_normalStrikedFont = m_normalFont;
119 m_boldStrikedFont = m_boldFont;
120 m_normalStrikedFont.setStrikeOut(true);
121 m_boldStrikedFont.setStrikeOut(true);
122
123 m_itemHeight = qApp->settings()->value(GROUP(GUI), SETTING(GUI::HeightRowMessages)).toInt();
124
125 if (m_itemHeight > 0) {
126 m_boldFont.setPixelSize(int(m_itemHeight * 0.6));
127 m_normalFont.setPixelSize(int(m_itemHeight * 0.6));
128 m_boldStrikedFont.setPixelSize(int(m_itemHeight * 0.6));
129 m_normalStrikedFont.setPixelSize(int(m_itemHeight * 0.6));
130 }
131 }
132
loadMessages(RootItem * item)133 void MessagesModel::loadMessages(RootItem* item) {
134 m_selectedItem = item;
135
136 if (item == nullptr) {
137 setFilter(QSL(DEFAULT_SQL_MESSAGES_FILTER));
138 }
139 else {
140 if (!item->getParentServiceRoot()->loadMessagesForItem(item, this)) {
141 setFilter(QSL(DEFAULT_SQL_MESSAGES_FILTER));
142 qCriticalNN << LOGSEC_MESSAGEMODEL
143 << "Loading of messages from item '"
144 << item->title() << "' failed.";
145 qApp->showGuiMessage(Notification::Event::GeneralEvent,
146 tr("Loading of articles from item '%1' failed.").arg(item->title()),
147 tr("Loading of articles failed, maybe messages could not be downloaded."),
148 QSystemTrayIcon::MessageIcon::Critical,
149 true);
150 }
151 }
152
153 repopulate();
154 }
155
setMessageImportantById(int id,RootItem::Importance important)156 bool MessagesModel::setMessageImportantById(int id, RootItem::Importance important) {
157 for (int i = 0; i < rowCount(); i++) {
158 int found_id = data(i, MSG_DB_ID_INDEX, Qt::EditRole).toInt();
159
160 if (found_id == id) {
161 bool set = setData(index(i, MSG_DB_IMPORTANT_INDEX), int(important));
162
163 if (set) {
164 emit dataChanged(index(i, 0), index(i, MSG_DB_CUSTOM_HASH_INDEX));
165 }
166
167 return set;
168 }
169 }
170
171 return false;
172 }
173
highlightMessages(MessagesModel::MessageHighlighter highlight)174 void MessagesModel::highlightMessages(MessagesModel::MessageHighlighter highlight) {
175 m_messageHighlighter = highlight;
176 emit layoutAboutToBeChanged();
177 emit layoutChanged();
178 }
179
messageId(int row_index) const180 int MessagesModel::messageId(int row_index) const {
181 return data(row_index, MSG_DB_ID_INDEX, Qt::EditRole).toInt();
182 }
183
messageImportance(int row_index) const184 RootItem::Importance MessagesModel::messageImportance(int row_index) const {
185 return RootItem::Importance(data(row_index, MSG_DB_IMPORTANT_INDEX, Qt::EditRole).toInt());
186 }
187
loadedItem() const188 RootItem* MessagesModel::loadedItem() const {
189 return m_selectedItem;
190 }
191
updateDateFormat()192 void MessagesModel::updateDateFormat() {
193 if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::UseCustomDate)).toBool()) {
194 m_customDateFormat = qApp->settings()->value(GROUP(Messages), SETTING(Messages::CustomDateFormat)).toString();
195 }
196 else {
197 m_customDateFormat = QString();
198 }
199 }
200
updateFeedIconsDisplay()201 void MessagesModel::updateFeedIconsDisplay() {
202 m_displayFeedIcons = qApp->settings()->value(GROUP(Messages), SETTING(Messages::DisplayFeedIconsInList)).toBool();
203 }
204
reloadWholeLayout()205 void MessagesModel::reloadWholeLayout() {
206 emit layoutAboutToBeChanged();
207 emit layoutChanged();
208 }
209
messageAt(int row_index) const210 Message MessagesModel::messageAt(int row_index) const {
211 return Message::fromSqlRecord(m_cache->containsData(row_index) ? m_cache->record(row_index) : record(row_index));
212 }
213
setupHeaderData()214 void MessagesModel::setupHeaderData() {
215 m_headerData <<
216
217 /*: Tooltip for ID of message.*/ tr("Id") <<
218
219 /*: Tooltip for "read" column in msg list.*/ tr("Read") <<
220
221 /*: Tooltip for "important" column in msg list.*/ tr("Important") <<
222
223 /*: Tooltip for "deleted" column in msg list.*/ tr("Deleted") <<
224
225 /*: Tooltip for "pdeleted" column in msg list.*/ tr("Permanently deleted") <<
226
227 /*: Tooltip for custom ID of feed of message.*/ tr("Feed ID") <<
228
229 /*: Tooltip for title of message.*/ tr("Title") <<
230
231 /*: Tooltip for url of message.*/ tr("Url") <<
232
233 /*: Tooltip for author of message.*/ tr("Author") <<
234
235 /*: Tooltip for creation date of message.*/ tr("Date") <<
236
237 /*: Tooltip for contents of message.*/ tr("Contents") <<
238
239 /*: Tooltip for attachments of message.*/ tr("Attachments") <<
240
241 /*: Tooltip for score of message.*/ tr("Score") <<
242
243 /*: Tooltip for account ID of message.*/ tr("Account ID") <<
244
245 /*: Tooltip for custom ID of message.*/ tr("Custom ID") <<
246
247 /*: Tooltip for custom hash string of message.*/ tr("Custom hash") <<
248
249 /*: Tooltip for name of feed for message.*/ tr("Feed") <<
250
251 /*: Tooltip for indication of presence of enclosures.*/ tr("Has enclosures");
252
253 m_tooltipData
254 << tr("ID of the article.")
255 << tr("Is article read?")
256 << tr("Is article important?")
257 << tr("Is article deleted?")
258 << tr("Is article permanently deleted from recycle bin?")
259 << tr("ID of feed which this article belongs to.")
260 << tr("Title of the article.")
261 << tr("Url of the article.")
262 << tr("Author of the article.")
263 << tr("Creation date of the article.")
264 << tr("Contents of the article.")
265 << tr("List of attachments.")
266 << tr("Score of the article.")
267 << tr("Account ID of the article.")
268 << tr("Custom ID of the article")
269 << tr("Custom hash of the article.")
270 << tr("Custom ID of feed of the article.")
271 << tr("Indication of enclosures presence within the article.");
272 }
273
flags(const QModelIndex & index) const274 Qt::ItemFlags MessagesModel::flags(const QModelIndex& index) const {
275 Q_UNUSED(index)
276 return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren;
277 }
278
messagesAt(const QList<int> & row_indices) const279 QList<Message> MessagesModel::messagesAt(const QList<int>& row_indices) const {
280 QList<Message> msgs; msgs.reserve(row_indices.size());
281
282 for (int idx : row_indices) {
283 msgs << messageAt(idx);
284 }
285
286 return msgs;
287 }
288
data(int row,int column,int role) const289 QVariant MessagesModel::data(int row, int column, int role) const {
290 return data(index(row, column), role);
291 }
292
data(const QModelIndex & idx,int role) const293 QVariant MessagesModel::data(const QModelIndex& idx, int role) const {
294 // This message is not in cache, return real data from live query.
295 switch (role) {
296 // Human readable data for viewing.
297 case Qt::ItemDataRole::DisplayRole: {
298 int index_column = idx.column();
299
300 if (index_column == MSG_DB_DCREATED_INDEX) {
301 QDateTime dt = TextFactory::parseDateTime(QSqlQueryModel::data(idx, role).value<qint64>()).toLocalTime();
302
303 if (m_customDateFormat.isEmpty()) {
304 return QLocale().toString(dt, QLocale::FormatType::ShortFormat);
305 }
306 else {
307 return dt.toString(m_customDateFormat);
308 }
309 }
310 else if (index_column == MSG_DB_CONTENTS_INDEX) {
311 // Do not display full contents here.
312 QString contents = data(idx, Qt::EditRole).toString().mid(0, 64).simplified() + QL1S("...");
313
314 return contents;
315 }
316 else if (index_column == MSG_DB_AUTHOR_INDEX) {
317 const QString author_name = QSqlQueryModel::data(idx, role).toString();
318
319 return author_name.isEmpty() ? QSL("-") : author_name;
320 }
321 else if (index_column != MSG_DB_IMPORTANT_INDEX &&
322 index_column != MSG_DB_READ_INDEX &&
323 index_column != MSG_DB_HAS_ENCLOSURES &&
324 index_column != MSG_DB_SCORE_INDEX) {
325 return QSqlQueryModel::data(idx, role);
326 }
327 else {
328 return QVariant();
329 }
330 }
331
332 case LOWER_TITLE_ROLE:
333 return m_cache->containsData(idx.row())
334 ? m_cache->data(idx).toString().toLower()
335 : QSqlQueryModel::data(idx, Qt::ItemDataRole::EditRole).toString().toLower();
336
337 case Qt::ItemDataRole::EditRole:
338 return m_cache->containsData(idx.row())
339 ? m_cache->data(idx)
340 : QSqlQueryModel::data(idx, role);
341
342 case Qt::ItemDataRole::ToolTipRole: {
343 if (!qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::EnableTooltipsFeedsMessages)).toBool()) {
344 return QVariant();
345 }
346 else {
347 if (idx.column() == MSG_DB_SCORE_INDEX) {
348 return data(idx, Qt::ItemDataRole::EditRole);
349 }
350 else {
351 return data(idx, Qt::ItemDataRole::DisplayRole);
352 }
353 }
354 }
355
356 case Qt::ItemDataRole::FontRole: {
357 QModelIndex idx_read = index(idx.row(), MSG_DB_READ_INDEX);
358 QVariant data_read = data(idx_read, Qt::ItemDataRole::EditRole);
359 const bool is_bin = qobject_cast<RecycleBin*>(loadedItem()) != nullptr;
360 bool is_deleted;
361
362 if (is_bin) {
363 QModelIndex idx_del = index(idx.row(), MSG_DB_PDELETED_INDEX);
364
365 is_deleted = data(idx_del, Qt::ItemDataRole::EditRole).toBool();
366 }
367 else {
368 QModelIndex idx_del = index(idx.row(), MSG_DB_DELETED_INDEX);
369
370 is_deleted = data(idx_del, Qt::ItemDataRole::EditRole).toBool();
371 }
372
373 const bool striked = is_deleted;
374
375 if (data_read.toBool()) {
376 return striked ? m_normalStrikedFont : m_normalFont;
377 }
378 else {
379 return striked ? m_boldStrikedFont : m_boldFont;
380 }
381 }
382
383 case Qt::ItemDataRole::ForegroundRole:
384 switch (m_messageHighlighter) {
385 case MessageHighlighter::HighlightImportant: {
386 QModelIndex idx_important = index(idx.row(), MSG_DB_IMPORTANT_INDEX);
387 QVariant dta = m_cache->containsData(idx_important.row()) ? m_cache->data(idx_important) : QSqlQueryModel::data(idx_important);
388
389 return dta.toInt() == 1 ? qApp->skins()->currentSkin().m_colorPalette[Skin::PaletteColors::Highlight] : QVariant();
390 }
391
392 case MessageHighlighter::HighlightUnread: {
393 QModelIndex idx_read = index(idx.row(), MSG_DB_READ_INDEX);
394 QVariant dta = m_cache->containsData(idx_read.row()) ? m_cache->data(idx_read) : QSqlQueryModel::data(idx_read);
395
396 return dta.toInt() == 0 ? qApp->skins()->currentSkin().m_colorPalette[Skin::PaletteColors::Highlight] : QVariant();
397 }
398
399 case MessageHighlighter::NoHighlighting:
400 default:
401 return QVariant();
402 }
403
404 case Qt::ItemDataRole::DecorationRole: {
405 const int index_column = idx.column();
406
407 if (index_column == MSG_DB_READ_INDEX) {
408 if (m_displayFeedIcons && m_selectedItem != nullptr) {
409 QModelIndex idx_feedid = index(idx.row(), MSG_DB_FEED_CUSTOM_ID_INDEX);
410 QVariant dta = m_cache->containsData(idx_feedid.row())
411 ? m_cache->data(idx_feedid)
412 : QSqlQueryModel::data(idx_feedid);
413 QString feed_custom_id = dta.toString();
414 auto acc = m_selectedItem->getParentServiceRoot()->feedIconForMessage(feed_custom_id);
415
416 if (acc.isNull()) {
417 return qApp->icons()->fromTheme(QSL("application-rss+xml"));
418 }
419 else {
420 return acc;
421 }
422 }
423 else {
424 QModelIndex idx_read = index(idx.row(), MSG_DB_READ_INDEX);
425 QVariant dta = m_cache->containsData(idx_read.row()) ? m_cache->data(idx_read) : QSqlQueryModel::data(idx_read);
426
427 return dta.toInt() == 1 ? m_readIcon : m_unreadIcon;
428 }
429 }
430 else if (index_column == MSG_DB_IMPORTANT_INDEX) {
431 QModelIndex idx_important = index(idx.row(), MSG_DB_IMPORTANT_INDEX);
432 QVariant dta = m_cache->containsData(idx_important.row()) ? m_cache->data(idx_important) : QSqlQueryModel::data(idx_important);
433
434 return dta.toInt() == 1 ? m_favoriteIcon : QVariant();
435 }
436 else if (index_column == MSG_DB_HAS_ENCLOSURES) {
437 QModelIndex idx_important = index(idx.row(), MSG_DB_HAS_ENCLOSURES);
438 QVariant dta = QSqlQueryModel::data(idx_important);
439
440 return dta.toBool() ? m_enclosuresIcon : QVariant();
441 }
442 else if (index_column == MSG_DB_SCORE_INDEX) {
443 QVariant dta = QSqlQueryModel::data(idx);
444 int level = std::min(MSG_SCORE_MAX, std::max(MSG_SCORE_MIN, std::floor(dta.toDouble() / 10.0)));
445
446 return m_scoreIcons.at(level);
447 }
448 else {
449 return QVariant();
450 }
451 }
452
453 default:
454 return QVariant();
455 }
456 }
457
setMessageRead(int row_index,RootItem::ReadStatus read)458 bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) {
459 if (data(row_index, MSG_DB_READ_INDEX, Qt::EditRole).toInt() == int(read)) {
460 // Read status is the same is the one currently set.
461 // In that case, no extra work is needed.
462 return true;
463 }
464
465 Message message = messageAt(row_index);
466
467 if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList<Message>() << message, read)) {
468 // Cannot change read status of the item. Abort.
469 return false;
470 }
471
472 // Rewrite "visible" data in the model.
473 bool working_change = setData(index(row_index, MSG_DB_READ_INDEX), int(read));
474
475 if (!working_change) {
476 // If rewriting in the model failed, then cancel all actions.
477 qDebug("Setting of new data to the model failed for message read change.");
478 return false;
479 }
480
481 if (DatabaseQueries::markMessagesReadUnread(m_db, QStringList() << QString::number(message.m_id), read)) {
482 return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList<Message>() << message, read);
483 }
484 else {
485 return false;
486 }
487 }
488
setMessageReadById(int id,RootItem::ReadStatus read)489 bool MessagesModel::setMessageReadById(int id, RootItem::ReadStatus read) {
490 for (int i = 0; i < rowCount(); i++) {
491 int found_id = data(i, MSG_DB_ID_INDEX, Qt::EditRole).toInt();
492
493 if (found_id == id) {
494 bool set = setData(index(i, MSG_DB_READ_INDEX), int(read));
495
496 if (set) {
497 emit dataChanged(index(i, 0), index(i, MSG_DB_CUSTOM_HASH_INDEX));
498 }
499
500 return set;
501 }
502 }
503
504 return false;
505 }
506
switchMessageImportance(int row_index)507 bool MessagesModel::switchMessageImportance(int row_index) {
508 const QModelIndex target_index = index(row_index, MSG_DB_IMPORTANT_INDEX);
509 const RootItem::Importance current_importance = (RootItem::Importance) data(target_index, Qt::EditRole).toInt();
510 const RootItem::Importance next_importance = current_importance == RootItem::Importance::Important
511 ? RootItem::Importance::NotImportant
512 : RootItem::Importance::Important;
513 const Message message = messageAt(row_index);
514 const QPair<Message, RootItem::Importance> pair(message, next_importance);
515
516 if (!m_selectedItem->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_selectedItem,
517 QList<QPair<Message, RootItem::Importance>>() << pair)) {
518 return false;
519 }
520
521 // Rewrite "visible" data in the model.
522 const bool working_change = setData(target_index, int(next_importance));
523
524 if (!working_change) {
525 // If rewriting in the model failed, then cancel all actions.
526 qDebugNN << LOGSEC_MESSAGEMODEL << "Setting of new data to the model failed for message importance change.";
527 return false;
528 }
529
530 // Commit changes.
531 if (DatabaseQueries::markMessageImportant(m_db, message.m_id, next_importance)) {
532 emit dataChanged(index(row_index, 0), index(row_index, MSG_DB_FEED_CUSTOM_ID_INDEX), QVector<int>() << Qt::FontRole);
533
534 return m_selectedItem->getParentServiceRoot()->onAfterSwitchMessageImportance(m_selectedItem,
535 QList<QPair<Message, RootItem::Importance>>() << pair);
536 }
537 else {
538 return false;
539 }
540 }
541
switchBatchMessageImportance(const QModelIndexList & messages)542 bool MessagesModel::switchBatchMessageImportance(const QModelIndexList& messages) {
543 QStringList message_ids; message_ids.reserve(messages.size());
544 QList<QPair<Message, RootItem::Importance>> message_states; message_states.reserve(messages.size());
545
546 // Obtain IDs of all desired messages.
547 for (const QModelIndex& message : messages) {
548 const Message msg = messageAt(message.row());
549
550 RootItem::Importance message_importance = messageImportance((message.row()));
551
552 message_states.append(QPair<Message, RootItem::Importance>(msg, message_importance == RootItem::Importance::Important
553 ? RootItem::Importance::NotImportant
554 : RootItem::Importance::Important));
555 message_ids.append(QString::number(msg.m_id));
556 QModelIndex idx_msg_imp = index(message.row(), MSG_DB_IMPORTANT_INDEX);
557
558 setData(idx_msg_imp, message_importance == RootItem::Importance::Important
559 ? int(RootItem::Importance::NotImportant)
560 : int(RootItem::Importance::Important));
561 }
562
563 reloadWholeLayout();
564
565 if (!m_selectedItem->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_selectedItem, message_states)) {
566 return false;
567 }
568
569 if (DatabaseQueries::switchMessagesImportance(m_db, message_ids)) {
570 return m_selectedItem->getParentServiceRoot()->onAfterSwitchMessageImportance(m_selectedItem, message_states);
571 }
572 else {
573 return false;
574 }
575 }
576
setBatchMessagesDeleted(const QModelIndexList & messages)577 bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList& messages) {
578 QStringList message_ids; message_ids.reserve(messages.size());
579 QList<Message> msgs; msgs.reserve(messages.size());
580
581 // Obtain IDs of all desired messages.
582 for (const QModelIndex& message : messages) {
583 const Message msg = messageAt(message.row());
584
585 msgs.append(msg);
586 message_ids.append(QString::number(msg.m_id));
587
588 if (qobject_cast<RecycleBin*>(m_selectedItem) != nullptr) {
589 setData(index(message.row(), MSG_DB_PDELETED_INDEX), 1);
590 }
591 else {
592 setData(index(message.row(), MSG_DB_DELETED_INDEX), 1);
593 }
594 }
595
596 reloadWholeLayout();
597
598 if (!m_selectedItem->getParentServiceRoot()->onBeforeMessagesDelete(m_selectedItem, msgs)) {
599 return false;
600 }
601
602 bool deleted;
603
604 if (m_selectedItem->kind() != RootItem::Kind::Bin) {
605 deleted = DatabaseQueries::deleteOrRestoreMessagesToFromBin(m_db, message_ids, true);
606 }
607 else {
608 deleted = DatabaseQueries::permanentlyDeleteMessages(m_db, message_ids);
609 }
610
611 if (deleted) {
612 return m_selectedItem->getParentServiceRoot()->onAfterMessagesDelete(m_selectedItem, msgs);
613 }
614 else {
615 return false;
616 }
617 }
618
setBatchMessagesRead(const QModelIndexList & messages,RootItem::ReadStatus read)619 bool MessagesModel::setBatchMessagesRead(const QModelIndexList& messages, RootItem::ReadStatus read) {
620 QStringList message_ids; message_ids.reserve(messages.size());
621 QList<Message> msgs; msgs.reserve(messages.size());
622
623 // Obtain IDs of all desired messages.
624 for (const QModelIndex& message : messages) {
625 Message msg = messageAt(message.row());
626
627 msgs.append(msg);
628 message_ids.append(QString::number(msg.m_id));
629 setData(index(message.row(), MSG_DB_READ_INDEX), int(read));
630 }
631
632 reloadWholeLayout();
633
634 if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, msgs, read)) {
635 return false;
636 }
637
638 if (DatabaseQueries::markMessagesReadUnread(m_db, message_ids, read)) {
639 return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, msgs, read);
640 }
641 else {
642 return false;
643 }
644 }
645
setBatchMessagesRestored(const QModelIndexList & messages)646 bool MessagesModel::setBatchMessagesRestored(const QModelIndexList& messages) {
647 QStringList message_ids; message_ids.reserve(messages.size());
648 QList<Message> msgs; msgs.reserve(messages.size());
649
650 // Obtain IDs of all desired messages.
651 for (const QModelIndex& message : messages) {
652 const Message msg = messageAt(message.row());
653
654 msgs.append(msg);
655 message_ids.append(QString::number(msg.m_id));
656 setData(index(message.row(), MSG_DB_PDELETED_INDEX), 0);
657 setData(index(message.row(), MSG_DB_DELETED_INDEX), 0);
658 }
659
660 reloadWholeLayout();
661
662 if (!m_selectedItem->getParentServiceRoot()->onBeforeMessagesRestoredFromBin(m_selectedItem, msgs)) {
663 return false;
664 }
665
666 if (DatabaseQueries::deleteOrRestoreMessagesToFromBin(m_db, message_ids, false)) {
667 return m_selectedItem->getParentServiceRoot()->onAfterMessagesRestoredFromBin(m_selectedItem, msgs);
668 }
669 else {
670 return false;
671 }
672 }
673
headerData(int section,Qt::Orientation orientation,int role) const674 QVariant MessagesModel::headerData(int section, Qt::Orientation orientation, int role) const {
675 Q_UNUSED(orientation)
676
677 switch (role) {
678 case Qt::DisplayRole:
679
680 // Display textual headers for all columns except "read" and
681 // "important" and "has enclosures" columns.
682 if (section != MSG_DB_READ_INDEX &&
683 section != MSG_DB_IMPORTANT_INDEX &&
684 section != MSG_DB_SCORE_INDEX &&
685 section != MSG_DB_HAS_ENCLOSURES) {
686 return m_headerData.at(section);
687 }
688 else {
689 return QVariant();
690 }
691
692 case Qt::ToolTipRole:
693 return m_tooltipData.at(section);
694
695 case Qt::EditRole:
696 return m_headerData.at(section);
697
698 // Display icons for "read" and "important" columns.
699 case Qt::DecorationRole: {
700 switch (section) {
701 case MSG_DB_HAS_ENCLOSURES:
702 return m_enclosuresIcon;
703
704 case MSG_DB_READ_INDEX:
705 return m_readIcon;
706
707 case MSG_DB_IMPORTANT_INDEX:
708 return m_favoriteIcon;
709
710 case MSG_DB_SCORE_INDEX:
711 return m_scoreIcons.at(5);
712
713 default:
714 return QVariant();
715 }
716 }
717
718 default:
719 return QVariant();
720 }
721 }
722