File indexing completed on 2024-05-19 05:19:21
0001 /* 0002 This file is part of KJots. 0003 0004 SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com> 0005 2020 Igor Poboiko <igor.poboiko@gmail.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "kjotsmodel.h" 0011 0012 #include <QAbstractProxyModel> 0013 #include <QTextDocument> 0014 #include <QIcon> 0015 0016 #include <akonadi_version.h> 0017 #include <Akonadi/ChangeRecorder> 0018 #include <Akonadi/EntityDisplayAttribute> 0019 #include <Akonadi/NoteUtils> 0020 0021 #include <KMime/Message> 0022 #include <KPIMTextEdit/TextUtils> 0023 #include <KPIMTextEdit/RichTextComposerImages> 0024 #include <KPIMTextEdit/TextHTMLBuilder> 0025 #include <KPIMTextEdit/MarkupDirector> 0026 #include <KPIMTextEdit/PlainTextMarkupBuilder> 0027 #include <KLocalizedString> 0028 #include <KFormat> 0029 0030 #include "noteshared/notelockattribute.h" 0031 #include "noteshared/notepinattribute.h" 0032 0033 Q_DECLARE_METATYPE(QTextDocument *) 0034 Q_DECLARE_METATYPE(KPIMTextEdit::ImageList) 0035 0036 using namespace Akonadi; 0037 0038 KJotsEntity::KJotsEntity(const QModelIndex &index, QObject *parent) 0039 : QObject(parent) 0040 , m_index(index) 0041 { 0042 } 0043 0044 void KJotsEntity::setIndex(const QModelIndex &index) 0045 { 0046 m_index = QPersistentModelIndex(index); 0047 } 0048 0049 QString KJotsEntity::title() const 0050 { 0051 return m_index.data().toString(); 0052 } 0053 0054 QString KJotsEntity::content() const 0055 { 0056 auto *document = m_index.data(KJotsModel::DocumentRole).value<QTextDocument *>(); 0057 if (!document) { 0058 return QString(); 0059 } 0060 0061 KPIMTextEdit::TextHTMLBuilder builder; 0062 KPIMTextEdit::MarkupDirector director(&builder); 0063 0064 director.processDocument(document); 0065 QString result = builder.getResult(); 0066 0067 return result; 0068 } 0069 0070 QString KJotsEntity::plainContent() const 0071 { 0072 auto *document = m_index.data(KJotsModel::DocumentRole).value<QTextDocument *>(); 0073 if (!document) { 0074 return QString(); 0075 } 0076 0077 KPIMTextEdit::PlainTextMarkupBuilder builder; 0078 KPIMTextEdit::MarkupDirector director(&builder); 0079 0080 director.processDocument(document); 0081 const QString result = builder.getResult(); 0082 0083 return result; 0084 } 0085 0086 QString KJotsEntity::url() const 0087 { 0088 return m_index.data(KJotsModel::EntityUrlRole).toString(); 0089 } 0090 0091 qint64 KJotsEntity::entityId() const 0092 { 0093 Item item = m_index.data(EntityTreeModel::ItemRole).value<Item>(); 0094 if (!item.isValid()) { 0095 Collection col = m_index.data(EntityTreeModel::CollectionRole).value<Collection>(); 0096 if (!col.isValid()) { 0097 return -1; 0098 } 0099 return col.id(); 0100 } 0101 return item.id(); 0102 } 0103 0104 bool KJotsEntity::isBook() const 0105 { 0106 Collection col = m_index.data(EntityTreeModel::CollectionRole).value<Collection>(); 0107 0108 if (col.isValid()) { 0109 return col.contentMimeTypes().contains(Akonadi::NoteUtils::noteMimeType()); 0110 } 0111 return false; 0112 } 0113 0114 bool KJotsEntity::isPage() const 0115 { 0116 Item item = m_index.data(EntityTreeModel::ItemRole).value<Item>(); 0117 if (item.isValid()) { 0118 return item.hasPayload<KMime::Message::Ptr>(); 0119 } 0120 return false; 0121 } 0122 0123 QVariantList KJotsEntity::entities() const 0124 { 0125 const QAbstractItemModel *model = m_index.model(); 0126 QVariantList list; 0127 int row = 0; 0128 const int column = 0; 0129 QModelIndex childIndex = model->index(row++, column, m_index); 0130 while (childIndex.isValid()) { 0131 auto *obj = new KJotsEntity(childIndex); 0132 list << QVariant::fromValue(obj); 0133 childIndex = model->index(row++, column, m_index); 0134 } 0135 return list; 0136 } 0137 0138 QVariantList KJotsEntity::breadcrumbs() const 0139 { 0140 QVariantList list; 0141 QModelIndex parent = m_index.parent(); 0142 0143 while (parent.isValid()) { 0144 QObject *obj = new KJotsEntity(parent); 0145 list << QVariant::fromValue(obj); 0146 parent = parent.parent(); 0147 } 0148 return list; 0149 } 0150 0151 KJotsModel::KJotsModel(ChangeRecorder *monitor, QObject *parent) 0152 : EntityTreeModel(monitor, parent) 0153 { 0154 0155 } 0156 0157 KJotsModel::~KJotsModel() 0158 { 0159 qDeleteAll(m_documents); 0160 } 0161 0162 bool KJotsModel::setData(const QModelIndex &index, const QVariant &value, int role) 0163 { 0164 if (role == Qt::EditRole) { 0165 Item item = index.data(ItemRole).value<Item>(); 0166 0167 if (!item.isValid()) { 0168 Collection col = index.data(CollectionRole).value<Collection>(); 0169 col.setName(value.toString()); 0170 if (col.hasAttribute<EntityDisplayAttribute>()) { 0171 auto *eda = col.attribute<EntityDisplayAttribute>(); 0172 eda->setDisplayName(value.toString()); 0173 } 0174 return EntityTreeModel::setData(index, QVariant::fromValue(col), CollectionRole); 0175 } 0176 NoteUtils::NoteMessageWrapper note(item.payload<KMime::Message::Ptr>()); 0177 note.setTitle(value.toString()); 0178 item.setPayload(note.message()); 0179 0180 if (item.hasAttribute<EntityDisplayAttribute>()) { 0181 auto *displayAttribute = item.attribute<EntityDisplayAttribute>(); 0182 displayAttribute->setDisplayName(value.toString()); 0183 } 0184 return EntityTreeModel::setData(index, QVariant::fromValue(item), ItemRole); 0185 } 0186 0187 if (role == KJotsModel::DocumentRole) { 0188 Item item = updateItem(index.data(ItemRole).value<Item>(), value.value<QTextDocument *>()); 0189 return EntityTreeModel::setData(index, QVariant::fromValue(item), ItemRole); 0190 } 0191 0192 return EntityTreeModel::setData(index, value, role); 0193 } 0194 0195 QVariant KJotsModel::data(const QModelIndex &index, int role) const 0196 { 0197 if (GrantleeObjectRole == role) { 0198 auto obj = new KJotsEntity(index); 0199 obj->setIndex(index); 0200 return QVariant::fromValue(obj); 0201 } 0202 0203 if (role == KJotsModel::DocumentRole) { 0204 const Item item = index.data(ItemRole).value<Item>(); 0205 Item::Id itemId = item.id(); 0206 if (m_documents.contains(itemId)) { 0207 return QVariant::fromValue(m_documents.value(itemId)); 0208 } 0209 if (!item.hasPayload<KMime::Message::Ptr>()) { 0210 return QVariant(); 0211 } 0212 0213 NoteUtils::NoteMessageWrapper note(item.payload<KMime::Message::Ptr>()); 0214 const QString doc = note.text(); 0215 auto document = new QTextDocument(); 0216 if (note.textFormat() == Qt::RichText 0217 || doc.startsWith(u"<!DOCTYPE") 0218 || doc.startsWith(u"<html>")) 0219 { 0220 document->setHtml(doc); 0221 } else { 0222 document->setPlainText(doc); 0223 } 0224 document->setModified(false); 0225 // Loading embedded images 0226 const QVector<NoteUtils::Attachment> attachments = note.attachments(); 0227 for (const auto &attachment : attachments) { 0228 if (attachment.mimetype() == QStringLiteral("image/png") && !attachment.contentID().isEmpty()) { 0229 const QImage img = QImage::fromData(attachment.data(), "PNG"); 0230 document->addResource(QTextDocument::ImageResource, 0231 QUrl(QStringLiteral("cid:")+attachment.contentID()), img); 0232 } 0233 } 0234 0235 m_documents.insert(itemId, document); 0236 return QVariant::fromValue(document); 0237 } 0238 0239 if (role == Qt::DecorationRole && index.column() == Title) { 0240 const Item item = index.data(ItemRole).value<Item>(); 0241 if (item.isValid() && item.hasAttribute<NoteShared::NoteLockAttribute>()) { 0242 return QIcon::fromTheme(QStringLiteral("object-locked")); 0243 } 0244 if (item.isValid() && item.hasAttribute<NoteShared::NotePinAttribute>()) { 0245 return QIcon::fromTheme(QStringLiteral("pin")); 0246 } 0247 const Collection col = index.data(CollectionRole).value<Collection>(); 0248 if (col.isValid() && col.hasAttribute<NoteShared::NoteLockAttribute>()) { 0249 return QIcon::fromTheme(QStringLiteral("object-locked")); 0250 } 0251 } 0252 0253 return EntityTreeModel::data(index, role); 0254 } 0255 0256 QVariant KJotsModel::entityData(const Akonadi::Item &item, int column, int role) const 0257 { 0258 if (item.hasPayload<KMime::Message::Ptr>()) { 0259 auto message = item.payload<KMime::Message::Ptr>(); 0260 NoteUtils::NoteMessageWrapper note(message); 0261 if (role == Qt::DisplayRole) { 0262 switch (column) { 0263 case Title: 0264 return note.title(); 0265 case ModificationTime: 0266 return KFormat().formatRelativeDateTime(note.lastModifiedDate(), QLocale::ShortFormat); 0267 case CreationTime: 0268 return KFormat().formatRelativeDateTime(note.creationDate(), QLocale::ShortFormat); 0269 case Size: 0270 return KFormat().formatByteSize(message->storageSize()); 0271 } 0272 } else if (role == Qt::EditRole) { 0273 switch (column) { 0274 case Title: 0275 return note.title(); 0276 case ModificationTime: 0277 return note.lastModifiedDate(); 0278 case CreationTime: 0279 return note.creationDate(); 0280 case Size: 0281 return message->size(); 0282 } 0283 0284 } 0285 } 0286 return EntityTreeModel::entityData(item, column, role); 0287 } 0288 0289 QVariant KJotsModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const 0290 { 0291 if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { 0292 return EntityTreeModel:: entityHeaderData(section, orientation, role, headerGroup); 0293 } 0294 if (headerGroup == EntityTreeModel::CollectionTreeHeaders) { 0295 return i18nc("@title:column", "Name"); 0296 } else if (headerGroup == EntityTreeModel::ItemListHeaders) { 0297 switch (section) { 0298 case Title: 0299 return i18nc("@title:column title of a note", "Title"); 0300 case CreationTime: 0301 return i18nc("@title:column creation date and time of a note", "Created"); 0302 case ModificationTime: 0303 return i18nc("@title:column last modification date and time of a note", "Modified"); 0304 case Size: 0305 return i18nc("@title:column size of a note", "Size"); 0306 } 0307 } 0308 return EntityTreeModel::entityHeaderData(section, orientation, role, headerGroup); 0309 } 0310 0311 int KJotsModel::entityColumnCount(HeaderGroup headerGroup) const 0312 { 0313 if (headerGroup == EntityTreeModel::CollectionTreeHeaders) { 0314 return 1; 0315 } else if (headerGroup == EntityTreeModel::ItemListHeaders) { 0316 return 4; 0317 } else { 0318 return EntityTreeModel::entityColumnCount(headerGroup); 0319 } 0320 } 0321 0322 QModelIndex KJotsModel::modelIndexForUrl(const QAbstractItemModel *model, const QUrl &url) 0323 { 0324 if (url.scheme() != QStringLiteral("akonadi")) { 0325 return {}; 0326 } 0327 const auto item = Item::fromUrl(url); 0328 const auto col = Collection::fromUrl(url); 0329 if (item.isValid()) { 0330 const QModelIndexList idxs = EntityTreeModel::modelIndexesForItem(model, item); 0331 if (!idxs.isEmpty()) { 0332 return idxs.first(); 0333 } 0334 } else if (col.isValid()) { 0335 return EntityTreeModel::modelIndexForCollection(model, col); 0336 } 0337 return {}; 0338 } 0339 0340 Item KJotsModel::updateItem(const Item &item, QTextDocument *document) 0341 { 0342 if (!item.hasPayload<KMime::Message::Ptr>()) { 0343 return {}; 0344 } 0345 NoteUtils::NoteMessageWrapper note(item.payload<KMime::Message::Ptr>()); 0346 // Saving embedded images 0347 const auto images = document->property("images").value<KPIMTextEdit::ImageList>(); 0348 QVector<NoteUtils::Attachment> &attachments = note.attachments(); 0349 attachments.clear(); 0350 attachments.reserve(images.count()); 0351 std::transform(images.cbegin(), images.cend(), std::back_inserter(attachments), 0352 [](const QSharedPointer<KPIMTextEdit::EmbeddedImage> &img) { 0353 NoteUtils::Attachment attachment(img->image, QStringLiteral("image/png")); 0354 attachment.setDataBase64Encoded(true); 0355 attachment.setContentID(img->contentID); 0356 return attachment; 0357 }); 0358 // Setting text 0359 bool isRichText = KPIMTextEdit::TextUtils::containsFormatting(document); 0360 if (isRichText) { 0361 const QByteArray html = KPIMTextEdit::RichTextComposerImages::imageNamesToContentIds( document->toHtml().toUtf8(), images); 0362 note.setText( QString::fromUtf8(html), Qt::RichText ); 0363 } else { 0364 note.setText( document->toPlainText(), Qt::PlainText ); 0365 } 0366 note.setLastModifiedDate(QDateTime::currentDateTime()); 0367 0368 Item newItem = item; 0369 newItem.setPayload(note.message()); 0370 return newItem; 0371 } 0372 0373 QString KJotsModel::itemPath(const QModelIndex &index, const QString &sep) 0374 { 0375 QStringList path; 0376 QModelIndex curIndex = index; 0377 while (curIndex.isValid()) { 0378 path.prepend(curIndex.data().toString()); 0379 curIndex = curIndex.parent(); 0380 } 0381 return path.join(sep); 0382 } 0383 0384 QModelIndex KJotsModel::etmIndex(const QModelIndex &index) 0385 { 0386 QModelIndex result = index; 0387 const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(index.model()); 0388 while (proxy) { 0389 result = proxy->mapToSource(result); 0390 proxy = qobject_cast<const QAbstractProxyModel *>(result.model()); 0391 } 0392 return result; 0393 } 0394 0395 #include "moc_kjotsmodel.cpp"