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

KDE's Doxygen guidelines are available online.