File indexing completed on 2025-01-05 04:47:40
0001 /* 0002 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0003 SPDX-FileContributor: Tobias Koenig <tokoe@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "incidenceviewer.h" 0009 #include "attachmenthandler.h" 0010 #include "incidenceviewer_p.h" 0011 #include "urihandler.h" 0012 #include "utils.h" 0013 0014 #include "incidenceattachmentmodel.h" 0015 0016 #include <Akonadi/CalendarBase> 0017 #include <Akonadi/CalendarUtils> 0018 #include <Akonadi/CollectionFetchJob> 0019 #include <Akonadi/ETMCalendar> 0020 #include <Akonadi/ItemFetchScope> 0021 0022 #include <KCalUtils/IncidenceFormatter> 0023 0024 #include <KJob> 0025 #include <QRegularExpression> 0026 #include <QTextBrowser> 0027 0028 #include <QVBoxLayout> 0029 0030 using namespace CalendarSupport; 0031 0032 TextBrowser::TextBrowser(QWidget *parent) 0033 : QTextBrowser(parent) 0034 { 0035 setFrameStyle(QFrame::NoFrame); 0036 } 0037 0038 void TextBrowser::doSetSource(const QUrl &name, QTextDocument::ResourceType type) 0039 { 0040 Q_UNUSED(type); 0041 QString uri = name.toString(); 0042 // QTextBrowser for some reason insists on putting // or / in links, 0043 // this is a crude workaround 0044 if (uri.startsWith(QLatin1StringView("uid:")) || uri.startsWith(QLatin1StringView("kmail:")) 0045 || uri.startsWith(QStringLiteral("urn:x-ical").section(QLatin1Char(':'), 0, 0)) || uri.startsWith(QLatin1StringView("news:")) 0046 || uri.startsWith(QLatin1StringView("mailto:"))) { 0047 uri.replace(QRegularExpression(QLatin1StringView("^([^:]+:)/+")), QStringLiteral("\\1")); 0048 } 0049 0050 if (uri.startsWith(QLatin1StringView("ATTACH:"))) { 0051 Q_EMIT attachmentUrlClicked(uri); 0052 } else { 0053 UriHandler::process(uri); 0054 } 0055 } 0056 0057 class CalendarSupport::IncidenceViewerPrivate 0058 { 0059 public: 0060 explicit IncidenceViewerPrivate(IncidenceViewer *parent) 0061 : mParent(parent) 0062 { 0063 mAttachmentHandler = new AttachmentHandler(parent); 0064 mBrowser = new TextBrowser; 0065 parent->connect(mBrowser, &TextBrowser::attachmentUrlClicked, parent, [this](const QString &str) { 0066 slotAttachmentUrlClicked(str); 0067 }); 0068 } 0069 0070 void updateView() 0071 { 0072 QString text; 0073 0074 if (mCurrentItem.isValid()) { 0075 text = KCalUtils::IncidenceFormatter::extensiveDisplayStr(Akonadi::CalendarUtils::displayName(mETM, mParentCollection), 0076 Akonadi::CalendarUtils::incidence(mCurrentItem), 0077 mDate); 0078 text.prepend(mHeaderText); 0079 mBrowser->setHtml(text); 0080 } else { 0081 text = mDefaultText; 0082 if (!mDelayedClear) { 0083 mBrowser->setHtml(text); 0084 } 0085 } 0086 } 0087 0088 void slotParentCollectionFetched(KJob *job) 0089 { 0090 mParentCollectionFetchJob = nullptr; 0091 mParentCollection = Akonadi::Collection(); 0092 0093 if (!job->error()) { 0094 auto fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job); 0095 if (!fetchJob->collections().isEmpty()) { 0096 mParentCollection = fetchJob->collections().at(0); 0097 } 0098 } 0099 0100 updateView(); 0101 } 0102 0103 void slotAttachmentUrlClicked(const QString &uri) 0104 { 0105 const QString attachmentName = QString::fromUtf8(QByteArray::fromBase64(uri.mid(7).toUtf8())); 0106 mAttachmentHandler->view(attachmentName, Akonadi::CalendarUtils::incidence(mCurrentItem)); 0107 } 0108 0109 Akonadi::EntityTreeModel *mETM = nullptr; 0110 IncidenceViewer *const mParent; 0111 TextBrowser *mBrowser = nullptr; 0112 Akonadi::Item mCurrentItem; 0113 QString mHeaderText; 0114 QString mDefaultText; 0115 Akonadi::Collection mParentCollection; 0116 Akonadi::CollectionFetchJob *mParentCollectionFetchJob = nullptr; 0117 IncidenceAttachmentModel *mAttachmentModel = nullptr; 0118 AttachmentHandler *mAttachmentHandler = nullptr; 0119 QDate mDate; 0120 bool mDelayedClear = false; 0121 }; 0122 0123 IncidenceViewer::IncidenceViewer(Akonadi::ETMCalendar *calendar, QWidget *parent) 0124 : QWidget(parent) 0125 , d(new IncidenceViewerPrivate(this)) 0126 { 0127 d->mETM = calendar->entityTreeModel(); 0128 init(); 0129 } 0130 0131 IncidenceViewer::IncidenceViewer(Akonadi::EntityTreeModel *etm, QWidget *parent) 0132 : QWidget(parent) 0133 , d(new IncidenceViewerPrivate(this)) 0134 { 0135 d->mETM = etm; 0136 init(); 0137 } 0138 0139 IncidenceViewer::IncidenceViewer(QWidget *parent) 0140 : QWidget(parent) 0141 , d(new IncidenceViewerPrivate(this)) 0142 { 0143 init(); 0144 } 0145 0146 void IncidenceViewer::init() 0147 { 0148 auto layout = new QVBoxLayout(this); 0149 layout->setContentsMargins(0, 0, 0, 0); 0150 0151 d->mBrowser->setOpenLinks(true); 0152 d->mBrowser->setMinimumHeight(1); 0153 0154 layout->addWidget(d->mBrowser); 0155 0156 // always fetch full payload for incidences 0157 fetchScope().fetchFullPayload(); 0158 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); 0159 0160 d->updateView(); 0161 } 0162 0163 IncidenceViewer::~IncidenceViewer() = default; 0164 0165 void IncidenceViewer::setCalendar(Akonadi::ETMCalendar *calendar) 0166 { 0167 d->mETM = calendar->entityTreeModel(); 0168 } 0169 0170 void IncidenceViewer::setModel(Akonadi::EntityTreeModel *model) 0171 { 0172 d->mETM = model; 0173 } 0174 0175 Akonadi::Item IncidenceViewer::incidence() const 0176 { 0177 return ItemMonitor::item(); 0178 } 0179 0180 QDate IncidenceViewer::activeDate() const 0181 { 0182 return d->mDate; 0183 } 0184 0185 QAbstractItemModel *IncidenceViewer::attachmentModel() const 0186 { 0187 if (!d->mAttachmentModel) { 0188 d->mAttachmentModel = new IncidenceAttachmentModel(const_cast<IncidenceViewer *>(this)); 0189 } 0190 return d->mAttachmentModel; 0191 } 0192 0193 void IncidenceViewer::setDelayedClear(bool delayed) 0194 { 0195 d->mDelayedClear = delayed; 0196 } 0197 0198 void IncidenceViewer::setDefaultMessage(const QString &message) 0199 { 0200 d->mDefaultText = message; 0201 } 0202 0203 void IncidenceViewer::setHeaderText(const QString &text) 0204 { 0205 d->mHeaderText = text; 0206 } 0207 0208 void IncidenceViewer::setIncidence(const Akonadi::Item &incidence, QDate date) 0209 { 0210 d->mDate = date; 0211 ItemMonitor::setItem(incidence); 0212 0213 d->updateView(); 0214 } 0215 0216 void IncidenceViewer::itemChanged(const Akonadi::Item &item) 0217 { 0218 if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) { 0219 d->mBrowser->clear(); 0220 return; 0221 } 0222 0223 d->mCurrentItem = item; 0224 0225 if (d->mAttachmentModel) { 0226 d->mAttachmentModel->setItem(d->mCurrentItem); 0227 } 0228 0229 if (d->mParentCollectionFetchJob) { 0230 disconnect(d->mParentCollectionFetchJob, SIGNAL(result(KJob *)), this, SLOT(slotParentCollectionFetched(KJob *))); 0231 delete d->mParentCollectionFetchJob; 0232 } 0233 0234 d->mParentCollectionFetchJob = new Akonadi::CollectionFetchJob(d->mCurrentItem.parentCollection(), Akonadi::CollectionFetchJob::Base, this); 0235 0236 connect(d->mParentCollectionFetchJob, SIGNAL(result(KJob *)), this, SLOT(slotParentCollectionFetched(KJob *))); 0237 } 0238 0239 void IncidenceViewer::itemRemoved() 0240 { 0241 d->mCurrentItem = Akonadi::Item(); 0242 d->mBrowser->clear(); 0243 } 0244 0245 #include "moc_incidenceviewer.cpp" 0246 #include "moc_incidenceviewer_p.cpp"