File indexing completed on 2024-04-28 15:51:38

0001 /*
0002     SPDX-FileCopyrightText: 2006 Pino Toscano <pino@kde.org>
0003 
0004     Work sponsored by the LiMux project of the city of Munich:
0005     SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "annotationmodel.h"
0011 
0012 #include <QList>
0013 #include <QPointer>
0014 
0015 #include <KLocalizedString>
0016 #include <QIcon>
0017 
0018 #include "core/annotations.h"
0019 #include "core/document.h"
0020 #include "core/observer.h"
0021 #include "core/page.h"
0022 #include "gui/guiutils.h"
0023 
0024 struct AnnItem {
0025     AnnItem();
0026     AnnItem(AnnItem *parent, Okular::Annotation *ann);
0027     AnnItem(AnnItem *parent, int page);
0028     ~AnnItem();
0029 
0030     AnnItem(const AnnItem &) = delete;
0031     AnnItem &operator=(const AnnItem &) = delete;
0032 
0033     AnnItem *parent;
0034     QList<AnnItem *> children;
0035 
0036     Okular::Annotation *annotation;
0037     int page;
0038 };
0039 
0040 static QList<Okular::Annotation *> filterOutWidgetAnnotations(const QList<Okular::Annotation *> &annotations)
0041 {
0042     QList<Okular::Annotation *> result;
0043 
0044     for (Okular::Annotation *annotation : annotations) {
0045         if (annotation->subType() == Okular::Annotation::AWidget) {
0046             continue;
0047         }
0048 
0049         result.append(annotation);
0050     }
0051 
0052     return result;
0053 }
0054 
0055 class AnnotationModelPrivate : public Okular::DocumentObserver
0056 {
0057 public:
0058     explicit AnnotationModelPrivate(AnnotationModel *qq);
0059     ~AnnotationModelPrivate() override;
0060 
0061     void notifySetup(const QVector<Okular::Page *> &pages, int setupFlags) override;
0062     void notifyPageChanged(int page, int flags) override;
0063 
0064     QModelIndex indexForItem(AnnItem *item) const;
0065     void rebuildTree(const QVector<Okular::Page *> &pages);
0066     AnnItem *findItem(int page, int *index) const;
0067 
0068     AnnotationModel *q;
0069     AnnItem *root;
0070     QPointer<Okular::Document> document;
0071 };
0072 
0073 AnnItem::AnnItem()
0074     : parent(nullptr)
0075     , annotation(nullptr)
0076     , page(-1)
0077 {
0078 }
0079 
0080 AnnItem::AnnItem(AnnItem *_parent, Okular::Annotation *ann)
0081     : parent(_parent)
0082     , annotation(ann)
0083     , page(_parent->page)
0084 {
0085     Q_ASSERT(!parent->annotation);
0086     parent->children.append(this);
0087 }
0088 
0089 AnnItem::AnnItem(AnnItem *_parent, int _page)
0090     : parent(_parent)
0091     , annotation(nullptr)
0092     , page(_page)
0093 {
0094     Q_ASSERT(!parent->parent);
0095     parent->children.append(this);
0096 }
0097 
0098 AnnItem::~AnnItem()
0099 {
0100     qDeleteAll(children);
0101 }
0102 
0103 AnnotationModelPrivate::AnnotationModelPrivate(AnnotationModel *qq)
0104     : q(qq)
0105     , root(new AnnItem)
0106 {
0107 }
0108 
0109 AnnotationModelPrivate::~AnnotationModelPrivate()
0110 {
0111     delete root;
0112 }
0113 
0114 static void updateAnnotationPointer(AnnItem *item, const QVector<Okular::Page *> &pages)
0115 {
0116     if (item->annotation) {
0117         item->annotation = pages[item->page]->annotation(item->annotation->uniqueName());
0118         if (!item->annotation) {
0119             qWarning() << "Lost annotation on document save, something went wrong";
0120         }
0121     }
0122 
0123     for (AnnItem *child : std::as_const(item->children)) {
0124         updateAnnotationPointer(child, pages);
0125     }
0126 }
0127 
0128 void AnnotationModelPrivate::notifySetup(const QVector<Okular::Page *> &pages, int setupFlags)
0129 {
0130     if (!(setupFlags & Okular::DocumentObserver::DocumentChanged)) {
0131         if (setupFlags & Okular::DocumentObserver::UrlChanged) {
0132             // Here with UrlChanged and no document changed it means we
0133             // need to update all the Annotation* otherwise
0134             // they still point to the old document ones, luckily the old ones are still
0135             // around so we can look for the new ones using unique ids, etc
0136             updateAnnotationPointer(root, pages);
0137         }
0138         return;
0139     }
0140 
0141     q->beginResetModel();
0142     qDeleteAll(root->children);
0143     root->children.clear();
0144 
0145     rebuildTree(pages);
0146     q->endResetModel();
0147 }
0148 
0149 void AnnotationModelPrivate::notifyPageChanged(int page, int flags)
0150 {
0151     // we are strictly interested in annotations
0152     if (!(flags & Okular::DocumentObserver::Annotations)) {
0153         return;
0154     }
0155 
0156     const QList<Okular::Annotation *> annots = filterOutWidgetAnnotations(document->page(page)->annotations());
0157     int annItemIndex = -1;
0158     AnnItem *annItem = findItem(page, &annItemIndex);
0159     // case 1: the page has no more annotations
0160     //         => remove the branch, if any
0161     if (annots.isEmpty()) {
0162         if (annItem) {
0163             q->beginRemoveRows(indexForItem(root), annItemIndex, annItemIndex);
0164             delete root->children.at(annItemIndex);
0165             root->children.removeAt(annItemIndex);
0166             q->endRemoveRows();
0167         }
0168         return;
0169     }
0170     // case 2: no existing branch
0171     //         => add a new branch, and add the annotations for the page
0172     if (!annItem) {
0173         int i = 0;
0174         while (i < root->children.count() && root->children.at(i)->page < page) {
0175             ++i;
0176         }
0177 
0178         AnnItem *annItem = new AnnItem();
0179         annItem->page = page;
0180         annItem->parent = root;
0181         q->beginInsertRows(indexForItem(root), i, i);
0182         annItem->parent->children.insert(i, annItem);
0183         q->endInsertRows();
0184         int newid = 0;
0185         for (Okular::Annotation *annot : annots) {
0186             q->beginInsertRows(indexForItem(annItem), newid, newid);
0187             new AnnItem(annItem, annot);
0188             q->endInsertRows();
0189             ++newid;
0190         }
0191         return;
0192     }
0193     // case 3: existing branch, less annotations than items
0194     //         => lookup and remove the annotations
0195     if (annItem->children.count() > annots.count()) {
0196         for (int i = annItem->children.count(); i > 0; --i) {
0197             Okular::Annotation *ref = annItem->children.at(i - 1)->annotation;
0198             bool found = false;
0199             for (Okular::Annotation *annot : annots) {
0200                 if (annot == ref) {
0201                     found = true;
0202                     break;
0203                 }
0204             }
0205             if (!found) {
0206                 q->beginRemoveRows(indexForItem(annItem), i - 1, i - 1);
0207                 delete annItem->children.at(i - 1);
0208                 annItem->children.removeAt(i - 1);
0209                 q->endRemoveRows();
0210             }
0211         }
0212         return;
0213     }
0214     // case 4: existing branch, less items than annotations
0215     //         => lookup and add annotations if not in the branch
0216     if (annots.count() > annItem->children.count()) {
0217         for (Okular::Annotation *ref : annots) {
0218             bool found = false;
0219             int count = annItem->children.count();
0220             for (int i = 0; !found && i < count; ++i) {
0221                 if (ref == annItem->children.at(i)->annotation) {
0222                     found = true;
0223                 }
0224             }
0225             if (!found) {
0226                 q->beginInsertRows(indexForItem(annItem), count, count);
0227                 new AnnItem(annItem, ref);
0228                 q->endInsertRows();
0229             }
0230         }
0231         return;
0232     }
0233     // case 5: the data of some annotation changed
0234     // TODO: what do we do in this case?
0235     // FIXME: for now, update ALL the annotations for that page
0236     for (int i = 0; i < annItem->children.count(); ++i) {
0237         QModelIndex index = indexForItem(annItem->children.at(i));
0238         Q_EMIT q->dataChanged(index, index);
0239     }
0240 }
0241 
0242 QModelIndex AnnotationModelPrivate::indexForItem(AnnItem *item) const
0243 {
0244     if (item->parent) {
0245         int id = item->parent->children.indexOf(item);
0246         if (id >= 0 && id < item->parent->children.count()) {
0247             return q->createIndex(id, 0, item);
0248         }
0249     }
0250     return QModelIndex();
0251 }
0252 
0253 void AnnotationModelPrivate::rebuildTree(const QVector<Okular::Page *> &pages)
0254 {
0255     if (pages.isEmpty()) {
0256         return;
0257     }
0258 
0259     Q_EMIT q->layoutAboutToBeChanged();
0260     for (int i = 0; i < pages.count(); ++i) {
0261         const QList<Okular::Annotation *> annots = filterOutWidgetAnnotations(pages.at(i)->annotations());
0262         if (annots.isEmpty()) {
0263             continue;
0264         }
0265 
0266         AnnItem *annItem = new AnnItem(root, i);
0267         for (Okular::Annotation *annot : annots) {
0268             new AnnItem(annItem, annot);
0269         }
0270     }
0271     Q_EMIT q->layoutChanged();
0272 }
0273 
0274 AnnItem *AnnotationModelPrivate::findItem(int page, int *index) const
0275 {
0276     for (int i = 0; i < root->children.count(); ++i) {
0277         AnnItem *tmp = root->children.at(i);
0278         if (tmp->page == page) {
0279             if (index) {
0280                 *index = i;
0281             }
0282             return tmp;
0283         }
0284     }
0285     if (index) {
0286         *index = -1;
0287     }
0288     return nullptr;
0289 }
0290 
0291 AnnotationModel::AnnotationModel(Okular::Document *document, QObject *parent)
0292     : QAbstractItemModel(parent)
0293     , d(new AnnotationModelPrivate(this))
0294 {
0295     d->document = document;
0296 
0297     d->document->addObserver(d);
0298 }
0299 
0300 AnnotationModel::~AnnotationModel()
0301 {
0302     if (d->document) {
0303         d->document->removeObserver(d);
0304     }
0305 
0306     delete d;
0307 }
0308 
0309 int AnnotationModel::columnCount(const QModelIndex &parent) const
0310 {
0311     Q_UNUSED(parent)
0312     return 1;
0313 }
0314 
0315 QVariant AnnotationModel::data(const QModelIndex &index, int role) const
0316 {
0317     if (!index.isValid()) {
0318         return QVariant();
0319     }
0320 
0321     AnnItem *item = static_cast<AnnItem *>(index.internalPointer());
0322     if (!item->annotation) {
0323         if (role == Qt::DisplayRole) {
0324             auto *page = d->document->page(item->page);
0325             if (page && !page->label().isEmpty() && page->label().toInt() != item->page + 1) {
0326                 return i18nc("Page label (number)", "Page %1 (%2)", page->label(), item->page + 1);
0327             } else {
0328                 return i18n("Page %1", item->page + 1);
0329             }
0330         } else if (role == Qt::DecorationRole) {
0331             return QIcon::fromTheme(QStringLiteral("text-plain"));
0332         } else if (role == PageRole) {
0333             return item->page;
0334         }
0335 
0336         return QVariant();
0337     }
0338     switch (role) {
0339     case Qt::DisplayRole: {
0340         const QString contents = item->annotation->contents().simplified();
0341         if (!contents.isEmpty()) {
0342             return i18nc("Annotation type: contents", "%1: %2", GuiUtils::captionForAnnotation(item->annotation), contents);
0343         } else {
0344             return GuiUtils::captionForAnnotation(item->annotation);
0345         }
0346         break;
0347     }
0348     case Qt::DecorationRole:
0349         return QIcon::fromTheme(QStringLiteral("okular"));
0350         break;
0351     case Qt::ToolTipRole:
0352         return GuiUtils::prettyToolTip(item->annotation);
0353         break;
0354     case AuthorRole:
0355         return item->annotation->author();
0356         break;
0357     case PageRole:
0358         return item->page;
0359         break;
0360     }
0361     return QVariant();
0362 }
0363 
0364 bool AnnotationModel::hasChildren(const QModelIndex &parent) const
0365 {
0366     if (!parent.isValid()) {
0367         return true;
0368     }
0369 
0370     AnnItem *item = static_cast<AnnItem *>(parent.internalPointer());
0371     return !item->children.isEmpty();
0372 }
0373 
0374 QVariant AnnotationModel::headerData(int section, Qt::Orientation orientation, int role) const
0375 {
0376     if (orientation != Qt::Horizontal) {
0377         return QVariant();
0378     }
0379 
0380     if (section == 0 && role == Qt::DisplayRole) {
0381         return QString::fromLocal8Bit("Annotations");
0382     }
0383 
0384     return QVariant();
0385 }
0386 
0387 QModelIndex AnnotationModel::index(int row, int column, const QModelIndex &parent) const
0388 {
0389     if (row < 0 || column != 0) {
0390         return QModelIndex();
0391     }
0392 
0393     AnnItem *item = parent.isValid() ? static_cast<AnnItem *>(parent.internalPointer()) : d->root;
0394     if (row < item->children.count()) {
0395         return createIndex(row, column, item->children.at(row));
0396     }
0397 
0398     return QModelIndex();
0399 }
0400 
0401 QModelIndex AnnotationModel::parent(const QModelIndex &index) const
0402 {
0403     if (!index.isValid()) {
0404         return QModelIndex();
0405     }
0406 
0407     AnnItem *item = static_cast<AnnItem *>(index.internalPointer());
0408     return d->indexForItem(item->parent);
0409 }
0410 
0411 int AnnotationModel::rowCount(const QModelIndex &parent) const
0412 {
0413     AnnItem *item = parent.isValid() ? static_cast<AnnItem *>(parent.internalPointer()) : d->root;
0414     return item->children.count();
0415 }
0416 
0417 bool AnnotationModel::isAnnotation(const QModelIndex &index) const
0418 {
0419     return annotationForIndex(index);
0420 }
0421 
0422 Okular::Annotation *AnnotationModel::annotationForIndex(const QModelIndex &index) const
0423 {
0424     if (!index.isValid()) {
0425         return nullptr;
0426     }
0427 
0428     AnnItem *item = static_cast<AnnItem *>(index.internalPointer());
0429     return item->annotation;
0430 }
0431 
0432 #include "moc_annotationmodel.cpp"