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"