Messagelib

attachmentmodel.cpp
1/*
2 * This file is part of KMail.
3 * SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "attachmentmodel.h"
9
10#include <QMimeData>
11#include <QUrl>
12
13#include "messagecomposer_debug.h"
14#include <KIO/Global>
15#include <KLocalizedString>
16#include <QFileDevice>
17#include <QTemporaryDir>
18
19#include <KMime/Headers>
20#include <KMime/Util>
21
22using namespace MessageComposer;
23using namespace MessageCore;
24
25static Qt::CheckState boolToCheckState(bool checked) // local
26{
27 if (checked) {
28 return Qt::Checked;
29 } else {
30 return Qt::Unchecked;
31 }
32}
33
34class MessageComposer::AttachmentModel::AttachmentModelPrivate
35{
36public:
37 explicit AttachmentModelPrivate(AttachmentModel *qq);
38 ~AttachmentModelPrivate();
39
42 AttachmentModel *const q;
43 bool modified = false;
44 bool encryptEnabled = false;
45 bool signEnabled = false;
46 bool encryptSelected = false;
47 bool signSelected = false;
48 bool autoDisplayEnabled = false;
49};
50
51AttachmentModel::AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel *qq)
52 : q(qq)
53{
54}
55
56AttachmentModel::AttachmentModelPrivate::~AttachmentModelPrivate()
57{
58 // There should be an automatic way to manage the lifetime of these...
59 qDeleteAll(tempDirs);
60}
61
62AttachmentModel::AttachmentModel(QObject *parent)
64 , d(new AttachmentModelPrivate(this))
65{
66}
67
68AttachmentModel::~AttachmentModel() = default;
69
70bool AttachmentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
71{
72 Q_UNUSED(row)
73 Q_UNUSED(column)
75
76 qCDebug(MESSAGECOMPOSER_LOG) << "data has formats" << data->formats() << "urls" << data->urls() << "action" << int(action);
77
78 if (action == Qt::IgnoreAction) {
79 return true;
80 //} else if( action != Qt::CopyAction ) {
81 // return false;
82 }
83 // The dropped data is a list of URLs.
84 const QList<QUrl> urls = data->urls();
85 if (!urls.isEmpty()) {
87 for (const QUrl &url : urls) {
88 const Akonadi::Item item = Akonadi::Item::fromUrl(url);
89 if (item.isValid()) {
90 items << item;
91 }
92 }
93 if (items.isEmpty()) {
94 Q_EMIT attachUrlsRequested(urls);
95 } else {
96 Q_EMIT attachItemsRequester(items);
97 }
98 return true;
99 } else {
100 return false;
101 }
102}
103
104QMimeData *AttachmentModel::mimeData(const QModelIndexList &indexes) const
105{
106 qCDebug(MESSAGECOMPOSER_LOG);
107 QList<QUrl> urls;
108 for (const QModelIndex &index : indexes) {
109 if (index.column() != 0) {
110 // Avoid processing the same attachment more than once, since the entire
111 // row is selected.
112 qCWarning(MESSAGECOMPOSER_LOG) << "column != 0. Possibly duplicate rows passed to mimeData().";
113 continue;
114 }
115
116 const AttachmentPart::Ptr part = d->parts[index.row()];
117 QString attachmentName = part->fileName();
118 if (attachmentName.isEmpty()) {
119 attachmentName = part->name();
120 }
121 if (attachmentName.isEmpty()) {
122 attachmentName = i18n("unnamed attachment");
123 }
124
125 auto tempDir = new QTemporaryDir; // Will remove the directory on destruction.
126 d->tempDirs.append(tempDir);
127 const QString fileName = tempDir->path() + QLatin1Char('/') + attachmentName;
128 QFile f(fileName);
129 if (!f.open(QIODevice::WriteOnly)) {
130 qCWarning(MESSAGECOMPOSER_LOG) << "Cannot write attachment:" << f.errorString();
131 continue;
132 }
133 const QByteArray data = part->data();
134 if (f.write(data) != data.length()) {
135 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to write all data to file!";
136 continue;
137 }
138 f.setPermissions(f.permissions() | QFileDevice::ReadUser | QFileDevice::WriteUser);
139 f.close();
140
141 const QUrl url = QUrl::fromLocalFile(fileName);
142 qCDebug(MESSAGECOMPOSER_LOG) << " temporary file " << url;
143 urls.append(url);
144 }
145
146 auto mimeData = new QMimeData;
147 mimeData->setUrls(urls);
148 return mimeData;
149}
150
151QStringList AttachmentModel::mimeTypes() const
152{
153 const QStringList types = {QStringLiteral("text/uri-list")};
154 return types;
155}
156
157Qt::DropActions AttachmentModel::supportedDropActions() const
158{
160}
161
163{
164 return d->modified; // TODO actually set modified=true sometime...
165}
166
167void AttachmentModel::setModified(bool modified)
168{
169 d->modified = modified;
170}
171
172bool AttachmentModel::isEncryptEnabled() const
173{
174 return d->encryptEnabled;
175}
176
177void AttachmentModel::setEncryptEnabled(bool enabled)
178{
179 d->encryptEnabled = enabled;
180 Q_EMIT encryptEnabled(enabled);
181}
182
183bool AttachmentModel::isAutoDisplayEnabled() const
184{
185 return d->autoDisplayEnabled;
186}
187
188void AttachmentModel::setAutoDisplayEnabled(bool enabled)
189{
190 d->autoDisplayEnabled = enabled;
191 Q_EMIT autoDisplayEnabled(enabled);
192}
193
194bool AttachmentModel::isSignEnabled() const
195{
196 return d->signEnabled;
197}
198
199void AttachmentModel::setSignEnabled(bool enabled)
200{
201 d->signEnabled = enabled;
202 Q_EMIT signEnabled(enabled);
203}
204
205bool AttachmentModel::isEncryptSelected() const
206{
207 return d->encryptSelected;
208}
209
211{
212 d->encryptSelected = selected;
213 for (AttachmentPart::Ptr part : std::as_const(d->parts)) {
214 part->setEncrypted(selected);
215 }
216 Q_EMIT dataChanged(index(0, EncryptColumn), index(rowCount() - 1, EncryptColumn));
217}
218
219bool AttachmentModel::isSignSelected() const
220{
221 return d->signSelected;
222}
223
225{
226 d->signSelected = selected;
227 for (AttachmentPart::Ptr part : std::as_const(d->parts)) {
228 part->setSigned(selected);
229 }
230 Q_EMIT dataChanged(index(0, SignColumn), index(rowCount() - 1, SignColumn));
231}
232
233QVariant AttachmentModel::data(const QModelIndex &index, int role) const
234{
235 if (!index.isValid()) {
236 return {};
237 }
238
239 const AttachmentPart::Ptr part = d->parts.at(index.row());
240
241 if (role == Qt::DisplayRole) {
242 switch (index.column()) {
243 case NameColumn:
244 return QVariant::fromValue(part->name().isEmpty() ? part->fileName() : part->name());
245 case SizeColumn:
246 return QVariant::fromValue(KIO::convertSize(part->size()));
247 case EncodingColumn:
248 return QVariant::fromValue(KMime::nameForEncoding(part->encoding()));
249 case MimeTypeColumn:
250 return QVariant::fromValue(part->mimeType());
251 default:
252 return {};
253 }
254 } else if (role == Qt::ToolTipRole) {
255 return QVariant::fromValue(i18nc("@info:tooltip",
256 "Name: %1<br>Size: %2<br>Encoding: %3<br>MimeType=%4",
257 part->name().isEmpty() ? part->fileName() : part->name(),
258 KIO::convertSize(part->size()),
259 KMime::nameForEncoding(part->encoding()),
260 QString::fromLatin1(part->mimeType().data())));
261 } else if (role == Qt::CheckStateRole) {
262 switch (index.column()) {
263 case CompressColumn:
264 return QVariant::fromValue(int(boolToCheckState(part->isCompressed())));
265 case EncryptColumn:
266 return QVariant::fromValue(int(boolToCheckState(part->isEncrypted())));
267 case SignColumn:
268 return QVariant::fromValue(int(boolToCheckState(part->isSigned())));
269 case AutoDisplayColumn:
270 return QVariant::fromValue(int(boolToCheckState(part->isInline())));
271 default:
272 return {};
273 }
274 } else if (role == AttachmentPartRole) {
275 if (index.column() == 0) {
276 return QVariant::fromValue(part);
277 } else {
278 qCWarning(MESSAGECOMPOSER_LOG) << "AttachmentPartRole and column != 0.";
279 return {};
280 }
281 } else if (role == NameRole) {
282 return QVariant::fromValue(part->fileName().isEmpty() ? part->name() : part->fileName());
283 } else if (role == SizeRole) {
284 return QVariant::fromValue(KIO::convertSize(part->size()));
285 } else if (role == EncodingRole) {
286 return QVariant::fromValue(KMime::nameForEncoding(part->encoding()));
287 } else if (role == MimeTypeRole) {
288 return QVariant::fromValue(part->mimeType());
289 } else if (role == CompressRole) {
290 return QVariant::fromValue(part->isCompressed());
291 } else if (role == EncryptRole) {
292 return QVariant::fromValue(part->isEncrypted());
293 } else if (role == SignRole) {
294 return QVariant::fromValue(part->isSigned());
295 } else if (role == AutoDisplayRole) {
296 return QVariant::fromValue(part->isInline());
297 } else {
298 return {};
299 }
300}
301
302bool AttachmentModel::setData(const QModelIndex &index, const QVariant &value, int role)
303{
304 bool emitDataChanged = true;
305 AttachmentPart::Ptr part = d->parts[index.row()];
306
307 if (role == Qt::EditRole) {
308 switch (index.column()) {
309 case NameColumn:
310 if (!value.toString().isEmpty()) {
311 part->setName(value.toString());
312 } else {
313 return false;
314 }
315 break;
316 default:
317 return false;
318 }
319 } else if (role == Qt::CheckStateRole) {
320 switch (index.column()) {
321 case CompressColumn: {
322 bool toZip = value.toBool();
323 if (toZip != part->isCompressed()) {
324 Q_EMIT attachmentCompressRequested(part, toZip);
325 emitDataChanged = false; // Will Q_EMIT when the part is updated.
326 }
327 break;
328 }
329 case EncryptColumn:
330 part->setEncrypted(value.toBool());
331 break;
332 case SignColumn:
333 part->setSigned(value.toBool());
334 break;
335 case AutoDisplayColumn:
336 part->setInline(value.toBool());
337 break;
338 default:
339 break; // Do nothing.
340 }
341 } else {
342 return false;
343 }
344
345 if (emitDataChanged) {
346 Q_EMIT dataChanged(index, index);
347 }
348 return true;
349}
350
351void AttachmentModel::addAttachment(const AttachmentPart::Ptr &part)
352{
353 Q_ASSERT(!d->parts.contains(part));
354 if (!part->url().isEmpty()) {
355 for (const AttachmentPart::Ptr &partElement : std::as_const(d->parts)) {
356 if (partElement->url() == part->url()) {
357 return;
358 }
359 }
360 }
361
362 beginInsertRows(QModelIndex(), rowCount(), rowCount());
363 d->parts.append(part);
365}
366
367bool AttachmentModel::updateAttachment(const AttachmentPart::Ptr &part)
368{
369 const int idx = d->parts.indexOf(part);
370 if (idx == -1) {
371 qCWarning(MESSAGECOMPOSER_LOG) << "Tried to update non-existent part.";
372 return false;
373 }
374 // Emit dataChanged() for the whole row.
375 Q_EMIT dataChanged(index(idx, 0), index(idx, LastColumn - 1));
376 return true;
377}
378
379bool AttachmentModel::replaceAttachment(const AttachmentPart::Ptr &oldPart, const AttachmentPart::Ptr &newPart)
380{
382
383 const int idx = d->parts.indexOf(oldPart);
384 if (idx == -1) {
385 qCWarning(MESSAGECOMPOSER_LOG) << "Tried to replace non-existent part.";
386 return false;
387 }
388 d->parts[idx] = newPart;
389 // Emit dataChanged() for the whole row.
390 Q_EMIT dataChanged(index(idx, 0), index(idx, LastColumn - 1));
391 return true;
392}
393
394bool AttachmentModel::removeAttachment(const AttachmentPart::Ptr &part)
395{
396 const int idx = d->parts.indexOf(part);
397 if (idx < 0) {
398 qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found.";
399 return false;
400 }
401
403 d->parts.removeAt(idx);
405 Q_EMIT attachmentRemoved(part);
406 return true;
407}
408
409AttachmentPart::List AttachmentModel::attachments() const
410{
411 return d->parts;
412}
413
414Qt::ItemFlags AttachmentModel::flags(const QModelIndex &index) const
415{
417
418 if (!index.isValid()) {
420 }
421
422 if (index.column() == CompressColumn || index.column() == EncryptColumn || index.column() == SignColumn || index.column() == AutoDisplayColumn) {
424 } else if (index.column() == NameColumn) {
426 } else {
428 }
429}
430
431QVariant AttachmentModel::headerData(int section, Qt::Orientation orientation, int role) const
432{
433 if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
434 return {};
435 }
436
437 switch (section) {
438 case NameColumn:
439 return i18nc("@title column attachment name.", "Name");
440 case SizeColumn:
441 return i18nc("@title column attachment size.", "Size");
442 case EncodingColumn:
443 return i18nc("@title column attachment encoding.", "Encoding");
444 case MimeTypeColumn:
445 return i18nc("@title column attachment type.", "Type");
446 case CompressColumn:
447 return i18nc("@title column attachment compression checkbox.", "Compress");
448 case EncryptColumn:
449 return i18nc("@title column attachment encryption checkbox.", "Encrypt");
450 case SignColumn:
451 return i18nc("@title column attachment signed checkbox.", "Sign");
452 case AutoDisplayColumn:
453 return i18nc("@title column attachment inlined checkbox.", "Suggest Automatic Display");
454 default:
455 qCWarning(MESSAGECOMPOSER_LOG) << "Bad column" << section;
456 return {};
457 }
458}
459
460QModelIndex AttachmentModel::index(int row, int column, const QModelIndex &parent) const
461{
462 if (!hasIndex(row, column, parent)) {
463 return {};
464 }
465 Q_ASSERT(row >= 0 && row < rowCount());
466
467 if (parent.isValid()) {
468 qCWarning(MESSAGECOMPOSER_LOG) << "Called with weird parent.";
469 return {};
470 }
471
472 return createIndex(row, column);
473}
474
476{
477 Q_UNUSED(index)
478 return {}; // No parent.
479}
480
481int AttachmentModel::rowCount(const QModelIndex &parent) const
482{
483 if (parent.isValid()) {
484 return 0; // Items have no children.
485 }
486 return d->parts.count();
487}
488
489int AttachmentModel::columnCount(const QModelIndex &parent) const
490{
492 return LastColumn;
493}
494
495#include "moc_attachmentmodel.cpp"
bool isValid() const
static Item fromUrl(const QUrl &url)
The AttachmentModel class.
void setEncryptSelected(bool selected)
sets for all
bool isModified() const
for the save/discard warning
void setSignSelected(bool selected)
sets for all
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT QString convertSize(KIO::filesize_t size)
QString name(StandardShortcut id)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
void beginInsertRows(const QModelIndex &parent, int first, int last)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
QModelIndex createIndex(int row, int column, const void *ptr) const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
bool hasIndex(int row, int column, const QModelIndex &parent) const const
qsizetype length() const const
void append(QList< T > &&value)
bool isEmpty() const const
virtual QStringList formats() const const
void setUrls(const QList< QUrl > &urls)
QList< QUrl > urls() const const
int column() const const
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
QObject * parent() const const
T qobject_cast(QObject *object)
bool isEmpty() const const
CheckState
DropAction
DisplayRole
typedef ItemFlags
Orientation
QUrl fromLocalFile(const QString &localFile)
QVariant fromValue(T &&value)
bool toBool() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.