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"