1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 #include <QMimeDatabase>
28 #include "Imap/Model/DragAndDrop.h"
29 #include "Imap/Model/ItemRoles.h"
30 #include "Imap/Model/MailboxModel.h"
31 #include "Imap/Model/MailboxTree.h"
32 #include "Imap/Model/SpecialFlagNames.h"
39 /** @short Does this URL point to an Internet e-mail message, according to the MIME type? */
40 static bool isFileWithMimeMessage(const QUrl
&url
)
42 QMimeDatabase mimeDb
; // the docs say this is cheap to construct
43 return url
.isLocalFile() && mimeDb
.mimeTypeForFile(url
.path()).inherits(QStringLiteral("message/rfc822"));
46 MailboxModel::MailboxModel(QObject
*parent
, Model
*model
): QAbstractProxyModel(parent
)
48 setSourceModel(model
);
50 // FIXME: will need to be expanded when Model supports more signals...
51 connect(model
, &QAbstractItemModel::modelAboutToBeReset
, this, &MailboxModel::handleModelAboutToBeReset
);
52 connect(model
, &QAbstractItemModel::modelReset
, this, &MailboxModel::handleModelReset
);
53 connect(model
, &QAbstractItemModel::layoutAboutToBeChanged
, this, &QAbstractItemModel::layoutAboutToBeChanged
);
54 connect(model
, &QAbstractItemModel::layoutChanged
, this, &QAbstractItemModel::layoutChanged
);
55 connect(model
, &QAbstractItemModel::dataChanged
, this, &MailboxModel::handleDataChanged
);
56 connect(model
, &QAbstractItemModel::rowsAboutToBeRemoved
, this, &MailboxModel::handleRowsAboutToBeRemoved
);
57 connect(model
, &QAbstractItemModel::rowsRemoved
, this, &MailboxModel::handleRowsRemoved
);
58 connect(model
, &QAbstractItemModel::rowsAboutToBeInserted
, this, &MailboxModel::handleRowsAboutToBeInserted
);
59 connect(model
, &QAbstractItemModel::rowsInserted
, this, &MailboxModel::handleRowsInserted
);
60 connect(model
, &Model::messageCountPossiblyChanged
, this, &MailboxModel::handleMessageCountPossiblyChanged
);
63 QHash
<int, QByteArray
> MailboxModel::roleNames() const
65 static QHash
<int, QByteArray
> roleNames
;
66 if (roleNames
.isEmpty()) {
67 roleNames
[RoleIsFetched
] = "isFetched";
68 roleNames
[RoleShortMailboxName
] = "shortMailboxName";
69 roleNames
[RoleMailboxName
] = "mailboxName";
70 roleNames
[RoleMailboxSeparator
] = "mailboxSeparator";
71 roleNames
[RoleMailboxHasChildMailboxes
] = "mailboxHasChildMailboxes";
72 roleNames
[RoleMailboxIsINBOX
] = "mailboxIsINBOX";
73 roleNames
[RoleMailboxIsSelectable
] = "mailboxIsSelectable";
74 roleNames
[RoleMailboxNumbersFetched
] = "mailboxNumbersFetched";
75 roleNames
[RoleTotalMessageCount
] = "totalMessageCount";
76 roleNames
[RoleUnreadMessageCount
] = "unreadMessageCount";
77 roleNames
[RoleRecentMessageCount
] = "recentMessageCount";
78 roleNames
[RoleMailboxItemsAreLoading
] = "mailboxItemsAreLoading";
83 void MailboxModel::handleModelAboutToBeReset()
88 void MailboxModel::handleModelReset()
93 bool MailboxModel::hasChildren(const QModelIndex
&parent
) const
95 if (parent
.isValid() && parent
.column() != 0)
98 QModelIndex index
= mapToSource(parent
);
100 TreeItemMailbox
*mbox
= dynamic_cast<TreeItemMailbox
*>(
101 static_cast<TreeItem
*>(
102 index
.internalPointer()
105 mbox
->hasChildMailboxes(static_cast<Model
*>(sourceModel())) :
106 sourceModel()->hasChildren(index
);
109 void MailboxModel::handleDataChanged(const QModelIndex
&topLeft
, const QModelIndex
&bottomRight
)
111 QModelIndex first
= mapFromSource(topLeft
);
112 QModelIndex second
= mapFromSource(bottomRight
);
114 if (! first
.isValid() || ! second
.isValid()) {
115 // It's something completely alien...
119 if (first
.parent() == second
.parent() && first
.column() == second
.column()) {
120 emit
dataChanged(first
, second
);
122 // FIXME: batched updates aren't used yet
128 QModelIndex
MailboxModel::index(int row
, int column
, const QModelIndex
&parent
) const
130 if (row
< 0 || column
!= 0)
131 return QModelIndex();
133 if (parent
.column() != 0 && parent
.column() != -1)
134 return QModelIndex();
136 QModelIndex translatedParent
= mapToSource(parent
);
138 if (row
< sourceModel()->rowCount(translatedParent
) - 1) {
139 void *ptr
= sourceModel()->index(row
+ 1, 0, translatedParent
).internalPointer();
141 return createIndex(row
, column
, ptr
);
143 return QModelIndex();
147 QModelIndex
MailboxModel::parent(const QModelIndex
&index
) const
149 return mapFromSource(mapToSource(index
).parent());
152 int MailboxModel::rowCount(const QModelIndex
&parent
) const
154 if (parent
.column() != 0 && parent
.column() != -1)
156 int res
= sourceModel()->rowCount(mapToSource(parent
));
162 int MailboxModel::columnCount(const QModelIndex
&parent
) const
164 return parent
.column() == 0 || parent
.column() == -1 ? 1 : 0;
167 QModelIndex
MailboxModel::mapToSource(const QModelIndex
&proxyIndex
) const
169 int row
= proxyIndex
.row();
170 if (row
< 0 || proxyIndex
.column() != 0)
171 return QModelIndex();
173 return static_cast<Imap::Mailbox::Model
*>(sourceModel())->createIndex(row
, 0, proxyIndex
.internalPointer());
176 QModelIndex
MailboxModel::mapFromSource(const QModelIndex
&sourceIndex
) const
178 if (!sourceIndex
.isValid())
179 return QModelIndex();
181 if (! dynamic_cast<Imap::Mailbox::TreeItemMailbox
*>(
182 static_cast<Imap::Mailbox::TreeItem
*>(sourceIndex
.internalPointer())))
183 return QModelIndex();
185 int row
= sourceIndex
.row();
187 return QModelIndex();
190 if (sourceIndex
.column() != 0)
191 return QModelIndex();
193 return createIndex(row
, 0, sourceIndex
.internalPointer());
196 QVariant
MailboxModel::data(const QModelIndex
&proxyIndex
, int role
) const
198 if (! proxyIndex
.isValid() || proxyIndex
.model() != this)
201 if (proxyIndex
.column() != 0)
204 TreeItemMailbox
*mbox
= dynamic_cast<TreeItemMailbox
*>(
205 static_cast<TreeItem
*>(proxyIndex
.internalPointer())
208 if (role
> RoleBase
&& role
< RoleInvalidLastOne
)
209 return mbox
->data(static_cast<Imap::Mailbox::Model
*>(sourceModel()), role
);
211 return QAbstractProxyModel::data(createIndex(proxyIndex
.row(), 0, proxyIndex
.internalPointer()), role
);
214 void MailboxModel::handleMessageCountPossiblyChanged(const QModelIndex
&mailbox
)
216 QModelIndex translated
= mapFromSource(mailbox
);
217 if (translated
.isValid()) {
218 emit
dataChanged(translated
, translated
);
222 Qt::ItemFlags
MailboxModel::flags(const QModelIndex
&index
) const
224 if (! index
.isValid())
225 return QAbstractProxyModel::flags(index
);
227 TreeItemMailbox
*mbox
= dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(index
.internalPointer()));
230 Qt::ItemFlags res
= QAbstractProxyModel::flags(index
);
231 if (!mbox
->isSelectable()) {
232 res
&= ~Qt::ItemIsSelectable
;
233 res
|= Qt::ItemIsEnabled
;
235 if (static_cast<Model
*>(sourceModel())->isNetworkAvailable()) {
236 res
|= Qt::ItemIsDropEnabled
;
241 Qt::DropActions
MailboxModel::supportedDropActions() const
243 return Qt::CopyAction
| Qt::MoveAction
;
246 QStringList
MailboxModel::mimeTypes() const
248 return QStringList() << MimeTypes::xTrojitaMessageList
;
251 bool MailboxModel::canDropMimeData(const QMimeData
*data
, Qt::DropAction action
, int row
, int column
, const QModelIndex
&parent
) const
253 // At first, check for dropping of URLs. We have to handle this with a priority because otherwise mimeTypes() gets called,
254 // and we deliberately do not list our messages as URLs because our URLs are proprietary.
255 const auto urls
= data
->urls();
256 if (std::any_of(urls
.begin(), urls
.end(), isFileWithMimeMessage
)) {
260 // We cannot delegate this to QAbstractProxyModel::canDropMimeData because that code delegates the decision
261 // to the *source* model. That's bad, because our source model doesn't know anything about drag-and-drops
263 // However, calling the default implementation *at this level* of proxy chain makes sure that this proxy's
264 // mimeTypes() and supportedDropActions() gets consulted, which is the correct thing to do.
265 return QAbstractItemModel::canDropMimeData(data
, action
, row
, column
, parent
);
268 bool MailboxModel::dropMimeData(const QMimeData
*data
, Qt::DropAction action
,
269 int row
, int column
, const QModelIndex
&parent
)
271 Q_UNUSED(row
); Q_UNUSED(column
);
272 if (action
!= Qt::CopyAction
&& action
!= Qt::MoveAction
)
275 if (! parent
.isValid())
278 if (! static_cast<Model
*>(sourceModel())->isNetworkAvailable())
281 TreeItemMailbox
*target
= dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(parent
.internalPointer()));
284 if (! target
->isSelectable())
287 if (data
->hasFormat(MimeTypes::xTrojitaMessageList
)) {
288 return dropTrojitaMessageList(target
->mailbox(), action
, data
->data(MimeTypes::xTrojitaMessageList
));
289 } else if (data
->hasUrls()) {
290 return dropFileUrlList(target
->mailbox(), data
->urls());
296 bool MailboxModel::dropTrojitaMessageList(const QString
&mailboxName
, const Qt::DropAction action
, const QByteArray
&encodedData
)
298 QDataStream
stream(&const_cast<QByteArray
&>(encodedData
), QIODevice::ReadOnly
);
300 Q_ASSERT(!stream
.atEnd());
301 QString origMboxName
;
302 stream
>> origMboxName
;
303 TreeItemMailbox
*origMbox
= static_cast<Model
*>(sourceModel())->findMailboxByName(origMboxName
);
305 qDebug() << "Can't find original mailbox when performing a drag&drop on messages";
310 stream
>> uidValidity
;
311 if (uidValidity
!= origMbox
->syncState
.uidValidity()) {
312 qDebug() << "UID validity for original mailbox got changed, can't copy messages";
319 static_cast<Model
*>(sourceModel())->copyMoveMessages(origMbox
, mailboxName
, uids
,
320 (action
== Qt::MoveAction
) ? MOVE
: COPY
);
324 bool MailboxModel::dropFileUrlList(const QString
&mailboxName
, QList
<QUrl
> files
)
328 files
.erase(std::remove_if(files
.begin(), files
.end(), std::not1(std::ptr_fun(isFileWithMimeMessage
))), files
.end());
329 std::for_each(files
.begin(), files
.end(), [this, mailboxName
, &ok
](const QUrl
&url
){
331 if (!f
.open(QIODevice::ReadOnly
))
334 auto content
= f
.readAll();
335 // Random heuristics: strip one leading line which starts with "From ", also known as "the mailbox header".
336 // Yeah, RFC 4155 says that there's a special MIME type application/mbox just for that, but nope, it's actually not being used.
337 // So one gets ".eml" messages which are in fact not message/rfc822 stuff.
338 if (content
.startsWith("From ")) {
339 auto pos
= content
.indexOf("\n");
341 // random heuristic: don't chop off "too much"
343 // random heiristic: three == one for "\n", two for CR LF which separates the headers from the body...
344 && pos
+ 3 < content
.size()) {
345 content
= content
.mid(pos
+ 1 /* for the LF */);
349 static_cast<Imap::Mailbox::Model
*>(sourceModel())->appendIntoMailbox(
350 mailboxName
, content
, QStringList() << Imap::Mailbox::FlagNames::seen
,
351 QFileInfo(url
.path()).lastModified());
358 void MailboxModel::handleRowsAboutToBeRemoved(const QModelIndex
&parent
, int first
, int last
)
360 TreeItemMailbox
*parentMbox
= dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(parent
.internalPointer()));
361 if (parent
.internalPointer() && ! parentMbox
)
364 parentMbox
= static_cast<Imap::Mailbox::Model
*>(sourceModel())->m_mailboxes
;
365 Q_ASSERT(first
>= 1);
366 Q_ASSERT(last
<= parentMbox
->m_children
.size() - 1);
367 Q_ASSERT(first
<= last
);
368 beginRemoveRows(mapFromSource(parent
), first
- 1, last
- 1);
371 void MailboxModel::handleRowsRemoved(const QModelIndex
&parent
, int first
, int last
)
375 TreeItemMailbox
*parentMbox
= dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(parent
.internalPointer()));
376 if (parent
.internalPointer() && ! parentMbox
)
381 void MailboxModel::handleRowsAboutToBeInserted(const QModelIndex
&parent
, int first
, int last
)
383 if (parent
.internalPointer() && ! dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(parent
.internalPointer())))
385 if (first
== 0 && last
== 0)
387 beginInsertRows(mapFromSource(parent
), first
- 1, last
- 1);
390 void MailboxModel::handleRowsInserted(const QModelIndex
&parent
, int first
, int last
)
392 if (parent
.internalPointer() && ! dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(parent
.internalPointer())))
394 if (first
== 0 && last
== 0)