Messagelib

attachmentmodel.cpp
1 /*
2  * This file is part of KMail.
3  * SPDX-FileCopyrightText: 2009 Constantin Berzan <[email protected]>
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 
22 using namespace MessageComposer;
23 using namespace MessageCore;
24 
25 static Qt::CheckState boolToCheckState(bool checked) // local
26 {
27  if (checked) {
28  return Qt::Checked;
29  } else {
30  return Qt::Unchecked;
31  }
32 }
33 
34 class MessageComposer::AttachmentModel::AttachmentModelPrivate
35 {
36 public:
37  AttachmentModelPrivate(AttachmentModel *qq);
38  ~AttachmentModelPrivate();
39 
41  QVector<QTemporaryDir *> tempDirs;
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 
51 AttachmentModel::AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel *qq)
52  : q(qq)
53 {
54 }
55 
56 AttachmentModel::AttachmentModelPrivate::~AttachmentModelPrivate()
57 {
58  // There should be an automatic way to manage the lifetime of these...
59  qDeleteAll(tempDirs);
60 }
61 
62 AttachmentModel::AttachmentModel(QObject *parent)
63  : QAbstractItemModel(parent)
64  , d(new AttachmentModelPrivate(this))
65 {
66 }
67 
68 AttachmentModel::~AttachmentModel() = default;
69 
70 bool AttachmentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
71 {
72  Q_UNUSED(row)
73  Q_UNUSED(column)
74  Q_UNUSED(parent)
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()) {
86  Akonadi::Item::List items;
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 
104 QMimeData *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 
151 QStringList AttachmentModel::mimeTypes() const
152 {
153  const QStringList types = {QStringLiteral("text/uri-list")};
154  return types;
155 }
156 
157 Qt::DropActions AttachmentModel::supportedDropActions() const
158 {
160 }
161 
162 bool AttachmentModel::isModified() const
163 {
164  return d->modified; // TODO actually set modified=true sometime...
165 }
166 
167 void AttachmentModel::setModified(bool modified)
168 {
169  d->modified = modified;
170 }
171 
172 bool AttachmentModel::isEncryptEnabled() const
173 {
174  return d->encryptEnabled;
175 }
176 
177 void AttachmentModel::setEncryptEnabled(bool enabled)
178 {
179  d->encryptEnabled = enabled;
180  Q_EMIT encryptEnabled(enabled);
181 }
182 
183 bool AttachmentModel::isAutoDisplayEnabled() const
184 {
185  return d->autoDisplayEnabled;
186 }
187 
188 void AttachmentModel::setAutoDisplayEnabled(bool enabled)
189 {
190  d->autoDisplayEnabled = enabled;
191  Q_EMIT autoDisplayEnabled(enabled);
192 }
193 
194 bool AttachmentModel::isSignEnabled() const
195 {
196  return d->signEnabled;
197 }
198 
199 void AttachmentModel::setSignEnabled(bool enabled)
200 {
201  d->signEnabled = enabled;
202  Q_EMIT signEnabled(enabled);
203 }
204 
205 bool AttachmentModel::isEncryptSelected() const
206 {
207  return d->encryptSelected;
208 }
209 
210 void AttachmentModel::setEncryptSelected(bool selected)
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 
219 bool AttachmentModel::isSignSelected() const
220 {
221  return d->signSelected;
222 }
223 
224 void AttachmentModel::setSignSelected(bool selected)
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 
233 QVariant 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 
302 bool 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 
351 void 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);
364  endInsertRows();
365 }
366 
367 bool 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 
379 bool AttachmentModel::replaceAttachment(const AttachmentPart::Ptr &oldPart, const AttachmentPart::Ptr &newPart)
380 {
381  Q_ASSERT(oldPart != newPart);
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 
394 bool 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 
402  beginRemoveRows(QModelIndex(), idx, idx);
403  d->parts.removeAt(idx);
404  endRemoveRows();
405  Q_EMIT attachmentRemoved(part);
406  return true;
407 }
408 
409 AttachmentPart::List AttachmentModel::attachments() const
410 {
411  return d->parts;
412 }
413 
414 Qt::ItemFlags AttachmentModel::flags(const QModelIndex &index) const
415 {
416  Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
417 
418  if (!index.isValid()) {
419  return Qt::ItemIsDropEnabled | defaultFlags;
420  }
421 
422  if (index.column() == CompressColumn || index.column() == EncryptColumn || index.column() == SignColumn || index.column() == AutoDisplayColumn) {
424  } else if (index.column() == NameColumn) {
426  } else {
427  return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
428  }
429 }
430 
431 QVariant 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 
460 QModelIndex 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 
475 QModelIndex AttachmentModel::parent(const QModelIndex &index) const
476 {
477  Q_UNUSED(index)
478  return {}; // No parent.
479 }
480 
481 int 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 
489 int AttachmentModel::columnCount(const QModelIndex &parent) const
490 {
491  Q_UNUSED(parent)
492  return LastColumn;
493 }
bool isValid() const
void append(const T &value)
T * data() const const
bool isEmpty() const const
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
DisplayRole
QVariant fromValue(const T &value)
void setUrls(const QList< QUrl > &urls)
int column() const const
QStringList types(Mode mode=Writing)
typedef ItemFlags
QString i18n(const char *text, const TYPE &arg...)
static Item fromUrl(const QUrl &url)
Orientation
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
bool isValid() const const
bool toBool() const const
int row() const const
QList< QUrl > urls() const const
DropAction
QString fromLatin1(const char *str, int size)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
The AttachmentModel class.
CheckState
int length() const const
virtual QStringList formats() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Mar 24 2023 04:08:30 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.