File indexing completed on 2024-11-24 04:53:12
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include <QDebug> 0024 #include <QFontMetrics> 0025 #include <QIcon> 0026 #include <QMimeData> 0027 #include "Imap/Model/DragAndDrop.h" 0028 #include "Imap/Model/MailboxTree.h" 0029 #include "Imap/Model/MailboxModel.h" 0030 #include "Imap/Model/MsgListModel.h" 0031 #include "UiUtils/IconLoader.h" 0032 0033 0034 namespace Imap 0035 { 0036 namespace Mailbox 0037 { 0038 0039 MsgListModel::MsgListModel(QObject *parent, Model *model): QAbstractProxyModel(parent), msgListPtr(0), waitingForMessages(false) 0040 { 0041 setSourceModel(model); 0042 0043 // FIXME: will need to be expanded when Model supports more signals... 0044 connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &MsgListModel::resetMe); 0045 connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &QAbstractItemModel::layoutAboutToBeChanged); 0046 connect(model, &QAbstractItemModel::layoutChanged, this, &QAbstractItemModel::layoutChanged); 0047 connect(model, &QAbstractItemModel::dataChanged, this, &MsgListModel::handleDataChanged); 0048 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &MsgListModel::handleRowsAboutToBeRemoved); 0049 connect(model, &QAbstractItemModel::rowsRemoved, this, &MsgListModel::handleRowsRemoved); 0050 connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &MsgListModel::handleRowsAboutToBeInserted); 0051 connect(model, &QAbstractItemModel::rowsInserted, this, &MsgListModel::handleRowsInserted); 0052 0053 connect(this, &QAbstractItemModel::layoutChanged, this, &MsgListModel::indexStateChanged); 0054 connect(this, &QAbstractItemModel::modelReset, this, &MsgListModel::indexStateChanged); 0055 connect(this, &QAbstractItemModel::rowsInserted, this, &MsgListModel::indexStateChanged); 0056 connect(this, &QAbstractItemModel::rowsRemoved, this, &MsgListModel::indexStateChanged); 0057 } 0058 0059 QHash<int, QByteArray> MsgListModel::roleNames() const 0060 { 0061 static QHash<int, QByteArray> roleNames; 0062 if (roleNames.isEmpty()) { 0063 roleNames[RoleIsFetched] = "isFetched"; 0064 roleNames[RoleIsUnavailable] = "isUnavailable"; 0065 roleNames[RoleMessageUid] = "messageUid"; 0066 roleNames[RoleMessageIsMarkedDeleted] = "isMarkedDeleted"; 0067 roleNames[RoleMessageIsMarkedRead] = "isMarkedRead"; 0068 roleNames[RoleMessageIsMarkedForwarded] = "isMarkedForwarded"; 0069 roleNames[RoleMessageIsMarkedReplied] = "isMarkedReplied"; 0070 roleNames[RoleMessageIsMarkedRecent] = "isMarkedRecent"; 0071 roleNames[RoleMessageIsMarkedFlagged] = "isMarkedFlagged"; 0072 roleNames[RoleMessageIsMarkedJunk] = "isMarkedJunk"; 0073 roleNames[RoleMessageIsMarkedNotJunk] = "isMarkedNotJunk"; 0074 roleNames[RoleMessageDate] = "date"; 0075 roleNames[RoleMessageInternalDate] = "receivedDate"; 0076 roleNames[RoleMessageFrom] = "from"; 0077 roleNames[RoleMessageTo] = "to"; 0078 roleNames[RoleMessageCc] = "cc"; 0079 roleNames[RoleMessageBcc] = "bcc"; 0080 roleNames[RoleMessageSender] = "sender"; 0081 roleNames[RoleMessageReplyTo] = "replyTo"; 0082 roleNames[RoleMessageInReplyTo] = "inReplyTo"; 0083 roleNames[RoleMessageMessageId] = "messageId"; 0084 roleNames[RoleMessageSubject] = "subject"; 0085 roleNames[RoleMessageFlags] = "flags"; 0086 roleNames[RoleMessageSize] = "size"; 0087 roleNames[RoleMessageFuzzyDate] = "fuzzyDate"; 0088 roleNames[RoleMessageHasAttachments] = "hasAttachments"; 0089 } 0090 return roleNames; 0091 } 0092 0093 void MsgListModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) 0094 { 0095 QModelIndex first = mapFromSource(topLeft); 0096 QModelIndex second = mapFromSource(bottomRight); 0097 0098 if (! first.isValid() || ! second.isValid()) { 0099 return; 0100 } 0101 0102 second = createIndex(second.row(), COLUMN_COUNT - 1, Model::realTreeItem(second)); 0103 0104 if (first.parent() == second.parent()) { 0105 emit dataChanged(first, second); 0106 } else { 0107 Q_ASSERT(false); 0108 return; 0109 } 0110 } 0111 0112 void MsgListModel::checkPersistentIndex() const 0113 { 0114 if (!msgList.isValid()) { 0115 if (msgListPtr) { 0116 emit const_cast<MsgListModel*>(this)->indexStateChanged(); 0117 } 0118 msgListPtr = 0; 0119 } 0120 } 0121 0122 QModelIndex MsgListModel::index(int row, int column, const QModelIndex &parent) const 0123 { 0124 checkPersistentIndex(); 0125 0126 if (! msgListPtr) 0127 return QModelIndex(); 0128 0129 if (parent.isValid()) 0130 return QModelIndex(); 0131 0132 if (column < 0 || column >= COLUMN_COUNT) 0133 return QModelIndex(); 0134 0135 if (row >= msgListPtr->m_children.size() || row < 0) 0136 return QModelIndex(); 0137 0138 return createIndex(row, column, msgListPtr->m_children[row]); 0139 } 0140 0141 QModelIndex MsgListModel::parent(const QModelIndex &index) const 0142 { 0143 Q_UNUSED(index); 0144 return QModelIndex(); 0145 } 0146 0147 bool MsgListModel::hasChildren(const QModelIndex &parent) const 0148 { 0149 return ! parent.isValid(); 0150 } 0151 0152 int MsgListModel::rowCount(const QModelIndex &parent) const 0153 { 0154 if (parent.isValid()) 0155 return 0; 0156 0157 checkPersistentIndex(); 0158 if (! msgListPtr) 0159 return 0; 0160 0161 return msgListPtr->rowCount(dynamic_cast<Model *>(sourceModel())); 0162 } 0163 0164 int MsgListModel::columnCount(const QModelIndex &parent) const 0165 { 0166 if (parent.isValid()) 0167 return 0; 0168 if (parent.column() != 0 && parent.column() != -1) 0169 return 0; 0170 return COLUMN_COUNT; 0171 } 0172 0173 QModelIndex MsgListModel::mapToSource(const QModelIndex &proxyIndex) const 0174 { 0175 checkPersistentIndex(); 0176 if (!msgListPtr || !proxyIndex.isValid()) 0177 return QModelIndex(); 0178 0179 if (proxyIndex.parent().isValid()) 0180 return QModelIndex(); 0181 0182 Model *model = dynamic_cast<Model *>(sourceModel()); 0183 Q_ASSERT(model); 0184 return model->createIndex(proxyIndex.row(), 0, msgListPtr->m_children[proxyIndex.row()]); 0185 } 0186 0187 QModelIndex MsgListModel::mapFromSource(const QModelIndex &sourceIndex) const 0188 { 0189 checkPersistentIndex(); 0190 if (! msgListPtr) 0191 return QModelIndex(); 0192 0193 if (sourceIndex.model() != sourceModel()) 0194 return QModelIndex(); 0195 if (dynamic_cast<TreeItemMessage *>(Model::realTreeItem(sourceIndex))) { 0196 return index(sourceIndex.row(), 0, QModelIndex()); 0197 } else { 0198 return QModelIndex(); 0199 } 0200 } 0201 0202 QVariant MsgListModel::data(const QModelIndex &proxyIndex, int role) const 0203 { 0204 checkPersistentIndex(); 0205 if (! msgListPtr) 0206 return QVariant(); 0207 0208 if (! proxyIndex.isValid() || proxyIndex.model() != this) 0209 return QVariant(); 0210 0211 TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(Model::realTreeItem(proxyIndex)); 0212 Q_ASSERT(message); 0213 0214 switch (role) { 0215 case Qt::DisplayRole: 0216 case Qt::ToolTipRole: 0217 switch (proxyIndex.column()) { 0218 case SUBJECT: 0219 return QAbstractProxyModel::data(proxyIndex, Qt::DisplayRole); 0220 case FROM: 0221 return QLatin1String("[from]"); 0222 case TO: 0223 return QLatin1String("[to]"); 0224 case CC: 0225 return QLatin1String("[cc]"); 0226 case BCC: 0227 return QLatin1String("[bcc]"); 0228 case DATE: 0229 return message->envelope(static_cast<Model *>(sourceModel())).date; 0230 case RECEIVED_DATE: 0231 return message->internalDate(static_cast<Model *>(sourceModel())); 0232 case SIZE: 0233 return QVariant::fromValue(message->size(static_cast<Model *>(sourceModel()))); 0234 default: 0235 return QVariant(); 0236 } 0237 0238 case RoleIsFetched: 0239 case RoleIsUnavailable: 0240 case RoleMessageUid: 0241 case RoleMessageIsMarkedDeleted: 0242 case RoleMessageIsMarkedRead: 0243 case RoleMessageIsMarkedForwarded: 0244 case RoleMessageIsMarkedReplied: 0245 case RoleMessageIsMarkedRecent: 0246 case RoleMessageIsMarkedFlagged: 0247 case RoleMessageIsMarkedJunk: 0248 case RoleMessageIsMarkedNotJunk: 0249 case RoleMessageDate: 0250 case RoleMessageFrom: 0251 case RoleMessageTo: 0252 case RoleMessageCc: 0253 case RoleMessageBcc: 0254 case RoleMessageSender: 0255 case RoleMessageReplyTo: 0256 case RoleMessageInReplyTo: 0257 case RoleMessageMessageId: 0258 case RoleMessageSubject: 0259 case RoleMessageFlags: 0260 case RoleMessageSize: 0261 case RoleMessageHeaderReferences: 0262 case RoleMessageHeaderListPost: 0263 case RoleMessageHeaderListPostNo: 0264 case RoleMessageHasAttachments: 0265 return dynamic_cast<TreeItemMessage *>(Model::realTreeItem( 0266 proxyIndex))->data(static_cast<Model *>(sourceModel()), role); 0267 default: 0268 return QAbstractProxyModel::data(createIndex(proxyIndex.row(), 0, proxyIndex.internalPointer()), role); 0269 } 0270 } 0271 0272 QVariant MsgListModel::headerData(int section, Qt::Orientation orientation, int role) const 0273 { 0274 if (orientation == Qt::Horizontal && role == Qt::DecorationRole && section == ATTACHMENT) 0275 return UiUtils::loadIcon(QStringLiteral("mail-attachment")); 0276 0277 if (orientation != Qt::Horizontal || role != Qt::DisplayRole) 0278 return QAbstractItemModel::headerData(section, orientation, role); 0279 0280 switch (section) { 0281 case SUBJECT: 0282 return tr("Subject"); 0283 case FROM: 0284 return tr("From"); 0285 case TO: 0286 return tr("To"); 0287 case CC: 0288 return tr("Cc"); 0289 case BCC: 0290 return tr("Bcc"); 0291 case DATE: 0292 return tr("Date"); 0293 case RECEIVED_DATE: 0294 return tr("Received"); 0295 case SIZE: 0296 return tr("Size"); 0297 default: 0298 return QVariant(); 0299 } 0300 } 0301 0302 Qt::ItemFlags MsgListModel::flags(const QModelIndex &index) const 0303 { 0304 if (!index.isValid()) 0305 return Qt::NoItemFlags; 0306 0307 TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(Model::realTreeItem(index)); 0308 Q_ASSERT(message); 0309 0310 if (!message->uid()) 0311 return Qt::NoItemFlags; 0312 0313 return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; 0314 } 0315 0316 Qt::DropActions MsgListModel::supportedDragActions() const 0317 { 0318 return Qt::CopyAction | Qt::MoveAction; 0319 } 0320 0321 Qt::DropActions MsgListModel::supportedDropActions() const 0322 { 0323 // Right now the GUI code doesn't allow dropping items into mailbox by dragging them 0324 // to a listing of mailbox' content, but if we just return Qt::IgnoreAction form here, 0325 // stuff breaks on Qt prior to 5.4.0 because on old Qt, the proxy models call 0326 // supportedDropActions() unless supportedDragActions is reimplemented on each level. 0327 #if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) 0328 return Qt::CopyAction | Qt::MoveAction; 0329 #else 0330 return Qt::IgnoreAction; 0331 #endif 0332 } 0333 0334 QStringList MsgListModel::mimeTypes() const 0335 { 0336 return QStringList() << MimeTypes::xTrojitaMessageList; 0337 } 0338 0339 QMimeData *MsgListModel::mimeData(const QModelIndexList &indexes) const 0340 { 0341 if (indexes.isEmpty()) 0342 return 0; 0343 0344 QMimeData *res = new QMimeData(); 0345 QByteArray encodedData; 0346 QDataStream stream(&encodedData, QIODevice::WriteOnly); 0347 stream.setVersion(QDataStream::Qt_4_6); 0348 0349 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(Model::realTreeItem( 0350 indexes.front())->parent()->parent()); 0351 Q_ASSERT(mailbox); 0352 stream << mailbox->mailbox() << mailbox->syncState.uidValidity(); 0353 0354 QList<uint> uids; 0355 for (QModelIndexList::const_iterator it = indexes.begin(); it != indexes.end(); ++it) { 0356 TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(Model::realTreeItem(*it)); 0357 Q_ASSERT(message); 0358 Q_ASSERT(message->parent()->parent() == mailbox); 0359 Q_ASSERT(message->uid() > 0); 0360 uids << message->uid(); 0361 } 0362 const QSet<uint> uidsSet(uids.begin(), uids.end()); 0363 uids = uidsSet.values(); 0364 stream << uids; 0365 res->setData(MimeTypes::xTrojitaMessageList, encodedData); 0366 return res; 0367 } 0368 0369 void MsgListModel::resetMe() 0370 { 0371 beginResetModel(); 0372 msgListPtr = 0; 0373 msgList = QModelIndex(); 0374 endResetModel(); 0375 } 0376 0377 void MsgListModel::handleRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) 0378 { 0379 if (! parent.isValid()) { 0380 // Top-level items are tricky :(. As a quick hack, let's just die. 0381 resetMe(); 0382 return; 0383 } 0384 0385 checkPersistentIndex(); 0386 if (! msgListPtr) 0387 return; 0388 0389 TreeItem *item = Model::realTreeItem(parent); 0390 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(item); 0391 TreeItemMsgList *newList = dynamic_cast<TreeItemMsgList *>(item); 0392 0393 if (parent.isValid()) { 0394 Q_ASSERT(parent.model() == sourceModel()); 0395 } else { 0396 // a top-level mailbox might have been deleted, so we gotta setup proper pointer 0397 mailbox = static_cast<Model *>(sourceModel())->m_mailboxes; 0398 Q_ASSERT(mailbox); 0399 } 0400 0401 if (newList) { 0402 if (newList == msgListPtr) { 0403 beginRemoveRows(mapFromSource(parent), start, end); 0404 for (int i = start; i <= end; ++i) 0405 emit messageRemoved(msgListPtr->m_children[i]); 0406 } 0407 } else if (mailbox) { 0408 Q_ASSERT(start > 0); 0409 // if we're below it, we're gonna die 0410 for (int i = start; i <= end; ++i) { 0411 const Model *model = 0; 0412 QModelIndex translatedParent; 0413 Model::realTreeItem(parent, &model, &translatedParent); 0414 // FIXME: this assumes that no rows were removed by the proxy model 0415 TreeItemMailbox *m = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(model->index(i, 0, translatedParent).internalPointer())); 0416 Q_ASSERT(m); 0417 TreeItem *up = msgListPtr->parent(); 0418 while (up) { 0419 if (m == up) { 0420 resetMe(); 0421 return; 0422 } 0423 up = up->parent(); 0424 } 0425 } 0426 } 0427 } 0428 0429 void MsgListModel::handleRowsRemoved(const QModelIndex &parent, int start, int end) 0430 { 0431 Q_UNUSED(start); 0432 Q_UNUSED(end); 0433 0434 checkPersistentIndex(); 0435 if (! msgListPtr) 0436 return; 0437 0438 if (! parent.isValid()) { 0439 // already handled by resetMe() in handleRowsAboutToBeRemoved() 0440 return; 0441 } 0442 0443 if (dynamic_cast<TreeItemMsgList *>(Model::realTreeItem(parent)) == msgListPtr) 0444 endRemoveRows(); 0445 } 0446 0447 void MsgListModel::handleRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) 0448 { 0449 checkPersistentIndex(); 0450 if (! parent.isValid()) 0451 return; 0452 0453 TreeItemMsgList *newList = dynamic_cast<TreeItemMsgList *>(Model::realTreeItem(parent)); 0454 if (msgListPtr && msgListPtr == newList) { 0455 beginInsertRows(mapFromSource(parent), start, end); 0456 } 0457 } 0458 0459 void MsgListModel::handleRowsInserted(const QModelIndex &parent, int start, int end) 0460 { 0461 checkPersistentIndex(); 0462 if (! parent.isValid()) 0463 return; 0464 0465 Q_UNUSED(start); 0466 Q_UNUSED(end); 0467 TreeItemMsgList *newList = dynamic_cast<TreeItemMsgList *>(Model::realTreeItem(parent)); 0468 if (msgListPtr && msgListPtr == newList) { 0469 endInsertRows(); 0470 } 0471 0472 if (waitingForMessages) { 0473 waitingForMessages = false; 0474 emit messagesAvailable(); 0475 } 0476 } 0477 0478 void MsgListModel::setMailbox(const QModelIndex &index) 0479 { 0480 if (!index.isValid() || !index.data(Imap::Mailbox::RoleMailboxIsSelectable).toBool()) 0481 return; 0482 0483 waitingForMessages = true; 0484 0485 const Model *model = 0; 0486 TreeItemMailbox *mbox = dynamic_cast<TreeItemMailbox *>(Model::realTreeItem(index, &model)); 0487 Q_ASSERT(mbox); 0488 TreeItemMsgList *newList = dynamic_cast<TreeItemMsgList *>( 0489 mbox->child(0, const_cast<Model *>(model))); 0490 Q_ASSERT(newList); 0491 checkPersistentIndex(); 0492 if (newList != msgListPtr) { 0493 msgListPtr = newList; 0494 msgList = msgListPtr->toIndex(const_cast<Model*>(model)); 0495 msgListPtr->resetWasUnreadState(); 0496 beginResetModel(); 0497 endResetModel(); 0498 emit mailboxChanged(index); 0499 } 0500 0501 // We want to tell the Model that it should consider starting the IDLE command. 0502 // This shall happen regardless on what this model's idea about a "current mailbox" is because the IMAP connection might have 0503 // switched to another mailbox behind the scenes. 0504 const_cast<Model *>(model)->switchToMailbox(index); 0505 } 0506 0507 /** @short Change mailbox to the one specified by its name */ 0508 void MsgListModel::setMailbox(const QString &mailboxName) 0509 { 0510 Model *model = dynamic_cast<Model*>(sourceModel()); 0511 Q_ASSERT(model); 0512 TreeItemMailbox *mailboxPtr = model->findMailboxByName(mailboxName); 0513 if (mailboxPtr) 0514 setMailbox(mailboxPtr->toIndex(model)); 0515 } 0516 0517 QModelIndex MsgListModel::currentMailbox() const 0518 { 0519 checkPersistentIndex(); 0520 return msgList.parent(); 0521 } 0522 0523 bool MsgListModel::itemsValid() const 0524 { 0525 return currentMailbox().isValid(); 0526 } 0527 0528 } 0529 }