File indexing completed on 2025-03-09 04:54:39
0001 /* 0002 SPDX-FileCopyrightText: 2007, 2008 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "mimetreemodel.h" 0008 #include "messageviewer_debug.h" 0009 0010 #include <MimeTreeParser/NodeHelper> 0011 #include <MimeTreeParser/Util> 0012 0013 #include <KMime/Content> 0014 #include <KMime/Message> 0015 0016 #include <KIO/Global> 0017 #include <KLocalizedString> 0018 0019 #include <QIcon> 0020 #include <QMimeData> 0021 #include <QMimeDatabase> 0022 #include <QTemporaryDir> 0023 #include <QUrl> 0024 0025 using namespace MessageViewer; 0026 0027 class Q_DECL_HIDDEN MimeTreeModel::MimeTreeModelPrivate 0028 { 0029 public: 0030 MimeTreeModelPrivate() = default; 0031 0032 ~MimeTreeModelPrivate() 0033 { 0034 qDeleteAll(tempDirs); 0035 } 0036 0037 void clearTempDir() 0038 { 0039 qDeleteAll(tempDirs); 0040 tempDirs.clear(); 0041 } 0042 0043 QString descriptionForContent(KMime::Content *content) 0044 { 0045 auto const message = dynamic_cast<KMime::Message *>(content); 0046 if (message && message->subject(false)) { 0047 return message->subject(false)->asUnicodeString(); 0048 } 0049 const QString name = MimeTreeParser::NodeHelper::fileName(content); 0050 if (!name.isEmpty()) { 0051 return name; 0052 } 0053 if (auto ct = content->contentDescription(false)) { 0054 const QString desc = ct->asUnicodeString(); 0055 if (!desc.isEmpty()) { 0056 return desc; 0057 } 0058 } 0059 return i18n("body part"); 0060 } 0061 0062 QString mimeTypeForContent(KMime::Content *content) 0063 { 0064 if (auto ct = content->contentType(false)) { 0065 return QString::fromLatin1(ct->mimeType()); 0066 } 0067 return {}; 0068 } 0069 0070 QString typeForContent(KMime::Content *content) 0071 { 0072 if (auto ct = content->contentType(false)) { 0073 const QString contentMimeType = QString::fromLatin1(ct->mimeType()); 0074 const auto mimeType = m_mimeDb.mimeTypeForName(contentMimeType); 0075 if (!mimeType.isValid()) { 0076 return contentMimeType; 0077 } 0078 return mimeType.comment(); 0079 } else { 0080 return {}; 0081 } 0082 } 0083 0084 QString sizeOfContent(KMime::Content *content) 0085 { 0086 if (content->body().isEmpty()) { 0087 return {}; 0088 } 0089 return KIO::convertSize(content->body().size()); 0090 } 0091 0092 QIcon iconForContent(KMime::Content *content) 0093 { 0094 if (auto ct = content->contentType(false)) { 0095 auto iconName = MimeTreeParser::Util::iconNameForMimetype(QLatin1StringView(ct->mimeType())); 0096 0097 auto mimeType = m_mimeDb.mimeTypeForName(QString::fromLatin1(ct->mimeType())); 0098 if (!mimeType.isValid() || mimeType.name() == QLatin1StringView("application/octet-stream")) { 0099 const QString name = descriptionForContent(content); 0100 mimeType = MimeTreeParser::Util::mimetype(name); 0101 } 0102 0103 if (mimeType.isValid() && mimeType.name().startsWith(QLatin1StringView("multipart/"))) { 0104 return QIcon::fromTheme(QStringLiteral("folder")); 0105 } else if (!iconName.isEmpty() && iconName != QLatin1StringView("unknown")) { 0106 return QIcon::fromTheme(iconName); 0107 } else if (mimeType.isValid() && !mimeType.iconName().isEmpty()) { 0108 return QIcon::fromTheme(mimeType.iconName()); 0109 } 0110 0111 return {}; 0112 } else { 0113 return {}; 0114 } 0115 } 0116 0117 QList<QTemporaryDir *> tempDirs; 0118 KMime::Content *root = nullptr; 0119 QMimeDatabase m_mimeDb; 0120 }; 0121 0122 MimeTreeModel::MimeTreeModel(QObject *parent) 0123 : QAbstractItemModel(parent) 0124 , d(new MimeTreeModelPrivate) 0125 { 0126 } 0127 0128 MimeTreeModel::~MimeTreeModel() = default; 0129 0130 void MimeTreeModel::setRoot(KMime::Content *root) 0131 { 0132 if (d->root != root) { 0133 beginResetModel(); 0134 d->clearTempDir(); 0135 d->root = root; 0136 endResetModel(); 0137 } 0138 } 0139 0140 KMime::Content *MimeTreeModel::root() 0141 { 0142 return d->root; 0143 } 0144 0145 QModelIndex MimeTreeModel::index(int row, int column, const QModelIndex &parent) const 0146 { 0147 if (!parent.isValid()) { 0148 if (row != 0) { 0149 return {}; 0150 } 0151 return createIndex(row, column, d->root); 0152 } 0153 0154 auto parentContent = static_cast<KMime::Content *>(parent.internalPointer()); 0155 if (!parentContent || parentContent->contents().count() <= row || row < 0) { 0156 return {}; 0157 } 0158 KMime::Content *content = parentContent->contents().at(row); 0159 return createIndex(row, column, content); 0160 } 0161 0162 QModelIndex MimeTreeModel::parent(const QModelIndex &index) const 0163 { 0164 if (!index.isValid()) { 0165 return {}; 0166 } 0167 auto currentContent = static_cast<KMime::Content *>(index.internalPointer()); 0168 if (!currentContent) { 0169 return {}; 0170 } 0171 0172 KMime::ContentIndex currentIndex = d->root->indexForContent(currentContent); 0173 if (!currentIndex.isValid()) { 0174 return {}; 0175 } 0176 currentIndex.up(); 0177 KMime::Content *parentContent = d->root->content(currentIndex); 0178 int row = 0; 0179 if (currentIndex.isValid()) { 0180 row = currentIndex.up() - 1; // 1 based -> 0 based 0181 } 0182 0183 return createIndex(row, 0, parentContent); 0184 } 0185 0186 int MimeTreeModel::rowCount(const QModelIndex &parent) const 0187 { 0188 if (!d->root) { 0189 return 0; 0190 } 0191 if (!parent.isValid()) { 0192 return 1; 0193 } 0194 auto parentContent = static_cast<KMime::Content *>(parent.internalPointer()); 0195 if (parentContent) { 0196 return parentContent->contents().count(); 0197 } 0198 return 0; 0199 } 0200 0201 int MimeTreeModel::columnCount(const QModelIndex &parent) const 0202 { 0203 Q_UNUSED(parent) 0204 return 3; 0205 } 0206 0207 QVariant MimeTreeModel::data(const QModelIndex &index, int role) const 0208 { 0209 auto content = static_cast<KMime::Content *>(index.internalPointer()); 0210 if (!content) { 0211 return {}; 0212 } 0213 if (role == Qt::ToolTipRole) { 0214 // TODO 0215 // return d->root->indexForContent( content ).toString(); 0216 return {}; 0217 } 0218 if (role == Qt::DisplayRole) { 0219 switch (index.column()) { 0220 case 0: 0221 return d->descriptionForContent(content); 0222 case 1: 0223 return d->typeForContent(content); 0224 case 2: 0225 return d->sizeOfContent(content); 0226 } 0227 } 0228 if (role == Qt::DecorationRole && index.column() == 0) { 0229 return d->iconForContent(content); 0230 } 0231 if (role == ContentIndexRole) { 0232 return QVariant::fromValue(d->root->indexForContent(content)); 0233 } 0234 if (role == ContentRole) { 0235 return QVariant::fromValue(content); 0236 } 0237 if (role == MimeTypeRole) { 0238 return d->mimeTypeForContent(content); 0239 } 0240 if (role == MainBodyPartRole) { 0241 auto topLevelMsg = dynamic_cast<KMime::Message *>(d->root); 0242 if (!topLevelMsg) { 0243 return false; 0244 } 0245 return topLevelMsg->mainBodyPart() == content; 0246 } 0247 if (role == AlternativeBodyPartRole) { 0248 auto topLevelMsg = dynamic_cast<KMime::Message *>(d->root); 0249 if (!topLevelMsg) { 0250 return false; 0251 } 0252 return topLevelMsg->mainBodyPart(content->contentType()->mimeType()) == content; 0253 } 0254 return {}; 0255 } 0256 0257 QVariant MimeTreeModel::headerData(int section, Qt::Orientation orientation, int role) const 0258 { 0259 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { 0260 switch (section) { 0261 case 0: 0262 return i18n("Description"); 0263 case 1: 0264 return i18n("Type"); 0265 case 2: 0266 return i18n("Size"); 0267 } 0268 } 0269 return QAbstractItemModel::headerData(section, orientation, role); 0270 } 0271 0272 QMimeData *MimeTreeModel::mimeData(const QModelIndexList &indexes) const 0273 { 0274 QList<QUrl> urls; 0275 for (const QModelIndex &index : indexes) { 0276 // create dnd for one item not for all three columns. 0277 if (index.column() != 0) { 0278 continue; 0279 } 0280 auto content = static_cast<KMime::Content *>(index.internalPointer()); 0281 if (!content) { 0282 continue; 0283 } 0284 const QByteArray data = content->decodedContent(); 0285 if (data.isEmpty()) { 0286 continue; 0287 } 0288 0289 auto tempDir = new QTemporaryDir; // Will remove the directory on destruction. 0290 d->tempDirs.append(tempDir); 0291 const QString fileName = tempDir->path() + QLatin1Char('/') + d->descriptionForContent(content); 0292 QFile f(fileName); 0293 if (!f.open(QIODevice::WriteOnly)) { 0294 qCWarning(MESSAGEVIEWER_LOG) << "Cannot write attachment:" << f.errorString(); 0295 continue; 0296 } 0297 if (f.write(data) != data.length()) { 0298 qCWarning(MESSAGEVIEWER_LOG) << "Failed to write all data to file!"; 0299 continue; 0300 } 0301 f.close(); 0302 0303 const QUrl url = QUrl::fromLocalFile(fileName); 0304 qCDebug(MESSAGEVIEWER_LOG) << " temporary file " << url; 0305 urls.append(url); 0306 } 0307 0308 auto mimeData = new QMimeData; 0309 mimeData->setUrls(urls); 0310 return mimeData; 0311 } 0312 0313 Qt::ItemFlags MimeTreeModel::flags(const QModelIndex &index) const 0314 { 0315 Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); 0316 return Qt::ItemIsDragEnabled | defaultFlags; 0317 } 0318 0319 QStringList MimeTreeModel::mimeTypes() const 0320 { 0321 const QStringList types = {QStringLiteral("text/uri-list")}; 0322 return types; 0323 } 0324 0325 #include "moc_mimetreemodel.cpp"