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"