File indexing completed on 2024-06-23 05:18:24
0001 /* 0002 * This file is part of KMail. 0003 * SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "attachmentmodel.h" 0009 0010 #include <QMimeData> 0011 #include <QUrl> 0012 0013 #include "messagecomposer_debug.h" 0014 #include <KIO/Global> 0015 #include <KLocalizedString> 0016 #include <QFileDevice> 0017 #include <QTemporaryDir> 0018 0019 #include <KMime/Headers> 0020 #include <KMime/Util> 0021 0022 using namespace MessageComposer; 0023 using namespace MessageCore; 0024 0025 static Qt::CheckState boolToCheckState(bool checked) // local 0026 { 0027 if (checked) { 0028 return Qt::Checked; 0029 } else { 0030 return Qt::Unchecked; 0031 } 0032 } 0033 0034 class MessageComposer::AttachmentModel::AttachmentModelPrivate 0035 { 0036 public: 0037 explicit AttachmentModelPrivate(AttachmentModel *qq); 0038 ~AttachmentModelPrivate(); 0039 0040 AttachmentPart::List parts; 0041 QList<QTemporaryDir *> tempDirs; 0042 AttachmentModel *const q; 0043 bool modified = false; 0044 bool encryptEnabled = false; 0045 bool signEnabled = false; 0046 bool encryptSelected = false; 0047 bool signSelected = false; 0048 bool autoDisplayEnabled = false; 0049 }; 0050 0051 AttachmentModel::AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel *qq) 0052 : q(qq) 0053 { 0054 } 0055 0056 AttachmentModel::AttachmentModelPrivate::~AttachmentModelPrivate() 0057 { 0058 // There should be an automatic way to manage the lifetime of these... 0059 qDeleteAll(tempDirs); 0060 } 0061 0062 AttachmentModel::AttachmentModel(QObject *parent) 0063 : QAbstractItemModel(parent) 0064 , d(new AttachmentModelPrivate(this)) 0065 { 0066 } 0067 0068 AttachmentModel::~AttachmentModel() = default; 0069 0070 bool AttachmentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) 0071 { 0072 Q_UNUSED(row) 0073 Q_UNUSED(column) 0074 Q_UNUSED(parent) 0075 0076 qCDebug(MESSAGECOMPOSER_LOG) << "data has formats" << data->formats() << "urls" << data->urls() << "action" << int(action); 0077 0078 if (action == Qt::IgnoreAction) { 0079 return true; 0080 //} else if( action != Qt::CopyAction ) { 0081 // return false; 0082 } 0083 // The dropped data is a list of URLs. 0084 const QList<QUrl> urls = data->urls(); 0085 if (!urls.isEmpty()) { 0086 Akonadi::Item::List items; 0087 for (const QUrl &url : urls) { 0088 const Akonadi::Item item = Akonadi::Item::fromUrl(url); 0089 if (item.isValid()) { 0090 items << item; 0091 } 0092 } 0093 if (items.isEmpty()) { 0094 Q_EMIT attachUrlsRequested(urls); 0095 } else { 0096 Q_EMIT attachItemsRequester(items); 0097 } 0098 return true; 0099 } else { 0100 return false; 0101 } 0102 } 0103 0104 QMimeData *AttachmentModel::mimeData(const QModelIndexList &indexes) const 0105 { 0106 qCDebug(MESSAGECOMPOSER_LOG); 0107 QList<QUrl> urls; 0108 for (const QModelIndex &index : indexes) { 0109 if (index.column() != 0) { 0110 // Avoid processing the same attachment more than once, since the entire 0111 // row is selected. 0112 qCWarning(MESSAGECOMPOSER_LOG) << "column != 0. Possibly duplicate rows passed to mimeData()."; 0113 continue; 0114 } 0115 0116 const AttachmentPart::Ptr part = d->parts[index.row()]; 0117 QString attachmentName = part->fileName(); 0118 if (attachmentName.isEmpty()) { 0119 attachmentName = part->name(); 0120 } 0121 if (attachmentName.isEmpty()) { 0122 attachmentName = i18n("unnamed attachment"); 0123 } 0124 0125 auto tempDir = new QTemporaryDir; // Will remove the directory on destruction. 0126 d->tempDirs.append(tempDir); 0127 const QString fileName = tempDir->path() + QLatin1Char('/') + attachmentName; 0128 QFile f(fileName); 0129 if (!f.open(QIODevice::WriteOnly)) { 0130 qCWarning(MESSAGECOMPOSER_LOG) << "Cannot write attachment:" << f.errorString(); 0131 continue; 0132 } 0133 const QByteArray data = part->data(); 0134 if (f.write(data) != data.length()) { 0135 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to write all data to file!"; 0136 continue; 0137 } 0138 f.setPermissions(f.permissions() | QFileDevice::ReadUser | QFileDevice::WriteUser); 0139 f.close(); 0140 0141 const QUrl url = QUrl::fromLocalFile(fileName); 0142 qCDebug(MESSAGECOMPOSER_LOG) << " temporary file " << url; 0143 urls.append(url); 0144 } 0145 0146 auto mimeData = new QMimeData; 0147 mimeData->setUrls(urls); 0148 return mimeData; 0149 } 0150 0151 QStringList AttachmentModel::mimeTypes() const 0152 { 0153 const QStringList types = {QStringLiteral("text/uri-list")}; 0154 return types; 0155 } 0156 0157 Qt::DropActions AttachmentModel::supportedDropActions() const 0158 { 0159 return Qt::CopyAction | Qt::MoveAction; 0160 } 0161 0162 bool AttachmentModel::isModified() const 0163 { 0164 return d->modified; // TODO actually set modified=true sometime... 0165 } 0166 0167 void AttachmentModel::setModified(bool modified) 0168 { 0169 d->modified = modified; 0170 } 0171 0172 bool AttachmentModel::isEncryptEnabled() const 0173 { 0174 return d->encryptEnabled; 0175 } 0176 0177 void AttachmentModel::setEncryptEnabled(bool enabled) 0178 { 0179 d->encryptEnabled = enabled; 0180 Q_EMIT encryptEnabled(enabled); 0181 } 0182 0183 bool AttachmentModel::isAutoDisplayEnabled() const 0184 { 0185 return d->autoDisplayEnabled; 0186 } 0187 0188 void AttachmentModel::setAutoDisplayEnabled(bool enabled) 0189 { 0190 d->autoDisplayEnabled = enabled; 0191 Q_EMIT autoDisplayEnabled(enabled); 0192 } 0193 0194 bool AttachmentModel::isSignEnabled() const 0195 { 0196 return d->signEnabled; 0197 } 0198 0199 void AttachmentModel::setSignEnabled(bool enabled) 0200 { 0201 d->signEnabled = enabled; 0202 Q_EMIT signEnabled(enabled); 0203 } 0204 0205 bool AttachmentModel::isEncryptSelected() const 0206 { 0207 return d->encryptSelected; 0208 } 0209 0210 void AttachmentModel::setEncryptSelected(bool selected) 0211 { 0212 d->encryptSelected = selected; 0213 for (AttachmentPart::Ptr part : std::as_const(d->parts)) { 0214 part->setEncrypted(selected); 0215 } 0216 Q_EMIT dataChanged(index(0, EncryptColumn), index(rowCount() - 1, EncryptColumn)); 0217 } 0218 0219 bool AttachmentModel::isSignSelected() const 0220 { 0221 return d->signSelected; 0222 } 0223 0224 void AttachmentModel::setSignSelected(bool selected) 0225 { 0226 d->signSelected = selected; 0227 for (AttachmentPart::Ptr part : std::as_const(d->parts)) { 0228 part->setSigned(selected); 0229 } 0230 Q_EMIT dataChanged(index(0, SignColumn), index(rowCount() - 1, SignColumn)); 0231 } 0232 0233 QVariant AttachmentModel::data(const QModelIndex &index, int role) const 0234 { 0235 if (!index.isValid()) { 0236 return {}; 0237 } 0238 0239 const AttachmentPart::Ptr part = d->parts.at(index.row()); 0240 0241 if (role == Qt::DisplayRole) { 0242 switch (index.column()) { 0243 case NameColumn: 0244 return QVariant::fromValue(part->name().isEmpty() ? part->fileName() : part->name()); 0245 case SizeColumn: 0246 return QVariant::fromValue(KIO::convertSize(part->size())); 0247 case EncodingColumn: 0248 return QVariant::fromValue(KMime::nameForEncoding(part->encoding())); 0249 case MimeTypeColumn: 0250 return QVariant::fromValue(part->mimeType()); 0251 default: 0252 return {}; 0253 } 0254 } else if (role == Qt::ToolTipRole) { 0255 return QVariant::fromValue(i18nc("@info:tooltip", 0256 "Name: %1<br>Size: %2<br>Encoding: %3<br>MimeType=%4", 0257 part->name().isEmpty() ? part->fileName() : part->name(), 0258 KIO::convertSize(part->size()), 0259 KMime::nameForEncoding(part->encoding()), 0260 QString::fromLatin1(part->mimeType().data()))); 0261 } else if (role == Qt::CheckStateRole) { 0262 switch (index.column()) { 0263 case CompressColumn: 0264 return QVariant::fromValue(int(boolToCheckState(part->isCompressed()))); 0265 case EncryptColumn: 0266 return QVariant::fromValue(int(boolToCheckState(part->isEncrypted()))); 0267 case SignColumn: 0268 return QVariant::fromValue(int(boolToCheckState(part->isSigned()))); 0269 case AutoDisplayColumn: 0270 return QVariant::fromValue(int(boolToCheckState(part->isInline()))); 0271 default: 0272 return {}; 0273 } 0274 } else if (role == AttachmentPartRole) { 0275 if (index.column() == 0) { 0276 return QVariant::fromValue(part); 0277 } else { 0278 qCWarning(MESSAGECOMPOSER_LOG) << "AttachmentPartRole and column != 0."; 0279 return {}; 0280 } 0281 } else if (role == NameRole) { 0282 return QVariant::fromValue(part->fileName().isEmpty() ? part->name() : part->fileName()); 0283 } else if (role == SizeRole) { 0284 return QVariant::fromValue(KIO::convertSize(part->size())); 0285 } else if (role == EncodingRole) { 0286 return QVariant::fromValue(KMime::nameForEncoding(part->encoding())); 0287 } else if (role == MimeTypeRole) { 0288 return QVariant::fromValue(part->mimeType()); 0289 } else if (role == CompressRole) { 0290 return QVariant::fromValue(part->isCompressed()); 0291 } else if (role == EncryptRole) { 0292 return QVariant::fromValue(part->isEncrypted()); 0293 } else if (role == SignRole) { 0294 return QVariant::fromValue(part->isSigned()); 0295 } else if (role == AutoDisplayRole) { 0296 return QVariant::fromValue(part->isInline()); 0297 } else { 0298 return {}; 0299 } 0300 } 0301 0302 bool AttachmentModel::setData(const QModelIndex &index, const QVariant &value, int role) 0303 { 0304 bool emitDataChanged = true; 0305 AttachmentPart::Ptr part = d->parts[index.row()]; 0306 0307 if (role == Qt::EditRole) { 0308 switch (index.column()) { 0309 case NameColumn: 0310 if (!value.toString().isEmpty()) { 0311 part->setName(value.toString()); 0312 } else { 0313 return false; 0314 } 0315 break; 0316 default: 0317 return false; 0318 } 0319 } else if (role == Qt::CheckStateRole) { 0320 switch (index.column()) { 0321 case CompressColumn: { 0322 bool toZip = value.toBool(); 0323 if (toZip != part->isCompressed()) { 0324 Q_EMIT attachmentCompressRequested(part, toZip); 0325 emitDataChanged = false; // Will Q_EMIT when the part is updated. 0326 } 0327 break; 0328 } 0329 case EncryptColumn: 0330 part->setEncrypted(value.toBool()); 0331 break; 0332 case SignColumn: 0333 part->setSigned(value.toBool()); 0334 break; 0335 case AutoDisplayColumn: 0336 part->setInline(value.toBool()); 0337 break; 0338 default: 0339 break; // Do nothing. 0340 } 0341 } else { 0342 return false; 0343 } 0344 0345 if (emitDataChanged) { 0346 Q_EMIT dataChanged(index, index); 0347 } 0348 return true; 0349 } 0350 0351 void AttachmentModel::addAttachment(const AttachmentPart::Ptr &part) 0352 { 0353 Q_ASSERT(!d->parts.contains(part)); 0354 if (!part->url().isEmpty()) { 0355 for (const AttachmentPart::Ptr &partElement : std::as_const(d->parts)) { 0356 if (partElement->url() == part->url()) { 0357 return; 0358 } 0359 } 0360 } 0361 0362 beginInsertRows(QModelIndex(), rowCount(), rowCount()); 0363 d->parts.append(part); 0364 endInsertRows(); 0365 } 0366 0367 bool AttachmentModel::updateAttachment(const AttachmentPart::Ptr &part) 0368 { 0369 const int idx = d->parts.indexOf(part); 0370 if (idx == -1) { 0371 qCWarning(MESSAGECOMPOSER_LOG) << "Tried to update non-existent part."; 0372 return false; 0373 } 0374 // Emit dataChanged() for the whole row. 0375 Q_EMIT dataChanged(index(idx, 0), index(idx, LastColumn - 1)); 0376 return true; 0377 } 0378 0379 bool AttachmentModel::replaceAttachment(const AttachmentPart::Ptr &oldPart, const AttachmentPart::Ptr &newPart) 0380 { 0381 Q_ASSERT(oldPart != newPart); 0382 0383 const int idx = d->parts.indexOf(oldPart); 0384 if (idx == -1) { 0385 qCWarning(MESSAGECOMPOSER_LOG) << "Tried to replace non-existent part."; 0386 return false; 0387 } 0388 d->parts[idx] = newPart; 0389 // Emit dataChanged() for the whole row. 0390 Q_EMIT dataChanged(index(idx, 0), index(idx, LastColumn - 1)); 0391 return true; 0392 } 0393 0394 bool AttachmentModel::removeAttachment(const AttachmentPart::Ptr &part) 0395 { 0396 const int idx = d->parts.indexOf(part); 0397 if (idx < 0) { 0398 qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found."; 0399 return false; 0400 } 0401 0402 beginRemoveRows(QModelIndex(), idx, idx); 0403 d->parts.removeAt(idx); 0404 endRemoveRows(); 0405 Q_EMIT attachmentRemoved(part); 0406 return true; 0407 } 0408 0409 AttachmentPart::List AttachmentModel::attachments() const 0410 { 0411 return d->parts; 0412 } 0413 0414 Qt::ItemFlags AttachmentModel::flags(const QModelIndex &index) const 0415 { 0416 Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); 0417 0418 if (!index.isValid()) { 0419 return Qt::ItemIsDropEnabled | defaultFlags; 0420 } 0421 0422 if (index.column() == CompressColumn || index.column() == EncryptColumn || index.column() == SignColumn || index.column() == AutoDisplayColumn) { 0423 return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable | defaultFlags; 0424 } else if (index.column() == NameColumn) { 0425 return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | defaultFlags; 0426 } else { 0427 return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; 0428 } 0429 } 0430 0431 QVariant AttachmentModel::headerData(int section, Qt::Orientation orientation, int role) const 0432 { 0433 if (orientation != Qt::Horizontal || role != Qt::DisplayRole) { 0434 return {}; 0435 } 0436 0437 switch (section) { 0438 case NameColumn: 0439 return i18nc("@title column attachment name.", "Name"); 0440 case SizeColumn: 0441 return i18nc("@title column attachment size.", "Size"); 0442 case EncodingColumn: 0443 return i18nc("@title column attachment encoding.", "Encoding"); 0444 case MimeTypeColumn: 0445 return i18nc("@title column attachment type.", "Type"); 0446 case CompressColumn: 0447 return i18nc("@title column attachment compression checkbox.", "Compress"); 0448 case EncryptColumn: 0449 return i18nc("@title column attachment encryption checkbox.", "Encrypt"); 0450 case SignColumn: 0451 return i18nc("@title column attachment signed checkbox.", "Sign"); 0452 case AutoDisplayColumn: 0453 return i18nc("@title column attachment inlined checkbox.", "Suggest Automatic Display"); 0454 default: 0455 qCWarning(MESSAGECOMPOSER_LOG) << "Bad column" << section; 0456 return {}; 0457 } 0458 } 0459 0460 QModelIndex AttachmentModel::index(int row, int column, const QModelIndex &parent) const 0461 { 0462 if (!hasIndex(row, column, parent)) { 0463 return {}; 0464 } 0465 Q_ASSERT(row >= 0 && row < rowCount()); 0466 0467 if (parent.isValid()) { 0468 qCWarning(MESSAGECOMPOSER_LOG) << "Called with weird parent."; 0469 return {}; 0470 } 0471 0472 return createIndex(row, column); 0473 } 0474 0475 QModelIndex AttachmentModel::parent(const QModelIndex &index) const 0476 { 0477 Q_UNUSED(index) 0478 return {}; // No parent. 0479 } 0480 0481 int AttachmentModel::rowCount(const QModelIndex &parent) const 0482 { 0483 if (parent.isValid()) { 0484 return 0; // Items have no children. 0485 } 0486 return d->parts.count(); 0487 } 0488 0489 int AttachmentModel::columnCount(const QModelIndex &parent) const 0490 { 0491 Q_UNUSED(parent) 0492 return LastColumn; 0493 } 0494 0495 #include "moc_attachmentmodel.cpp"