mirror of
https://invent.kde.org/graphics/okular
synced 2024-11-05 18:34:53 +00:00
19d5dd8ec7
Source files are no longer separated by UI and non-UI and similar, but only by their build target. * ui/ -> part/ * Move all source files from conf/ to part/ * Keep config skeleton definitions in conf/, needed for the mobile target too * Move editdrawingtooldialogtest.h from conf/autotests/ to autotests/ * ui/data/icons/ -> icons/ * Move /part.cpp, /part.rc and similar files to part/ * Adapt include paths in source files * Adapt CMakeLists.txt files (in / and in subdirectories) * Adapt /Messages.sh
407 lines
12 KiB
C++
407 lines
12 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2006 by Pino Toscano <pino@kde.org> *
|
|
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group *
|
|
* company, info@kdab.com. Work sponsored by the *
|
|
* LiMux project of the city of Munich *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
***************************************************************************/
|
|
|
|
#include "annotationmodel.h"
|
|
|
|
#include <QLinkedList>
|
|
#include <QList>
|
|
#include <QPointer>
|
|
|
|
#include <KLocalizedString>
|
|
#include <QIcon>
|
|
|
|
#include "core/annotations.h"
|
|
#include "core/document.h"
|
|
#include "core/observer.h"
|
|
#include "core/page.h"
|
|
#include "guiutils.h"
|
|
|
|
struct AnnItem {
|
|
AnnItem();
|
|
AnnItem(AnnItem *parent, Okular::Annotation *ann);
|
|
AnnItem(AnnItem *parent, int page);
|
|
~AnnItem();
|
|
|
|
AnnItem(const AnnItem &) = delete;
|
|
AnnItem &operator=(const AnnItem &) = delete;
|
|
|
|
AnnItem *parent;
|
|
QList<AnnItem *> children;
|
|
|
|
Okular::Annotation *annotation;
|
|
int page;
|
|
};
|
|
|
|
static QLinkedList<Okular::Annotation *> filterOutWidgetAnnotations(const QLinkedList<Okular::Annotation *> &annotations)
|
|
{
|
|
QLinkedList<Okular::Annotation *> result;
|
|
|
|
for (Okular::Annotation *annotation : annotations) {
|
|
if (annotation->subType() == Okular::Annotation::AWidget)
|
|
continue;
|
|
|
|
result.append(annotation);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
class AnnotationModelPrivate : public Okular::DocumentObserver
|
|
{
|
|
public:
|
|
AnnotationModelPrivate(AnnotationModel *qq);
|
|
~AnnotationModelPrivate() override;
|
|
|
|
void notifySetup(const QVector<Okular::Page *> &pages, int setupFlags) override;
|
|
void notifyPageChanged(int page, int flags) override;
|
|
|
|
QModelIndex indexForItem(AnnItem *item) const;
|
|
void rebuildTree(const QVector<Okular::Page *> &pages);
|
|
AnnItem *findItem(int page, int *index) const;
|
|
|
|
AnnotationModel *q;
|
|
AnnItem *root;
|
|
QPointer<Okular::Document> document;
|
|
};
|
|
|
|
AnnItem::AnnItem()
|
|
: parent(nullptr)
|
|
, annotation(nullptr)
|
|
, page(-1)
|
|
{
|
|
}
|
|
|
|
AnnItem::AnnItem(AnnItem *_parent, Okular::Annotation *ann)
|
|
: parent(_parent)
|
|
, annotation(ann)
|
|
, page(_parent->page)
|
|
{
|
|
Q_ASSERT(!parent->annotation);
|
|
parent->children.append(this);
|
|
}
|
|
|
|
AnnItem::AnnItem(AnnItem *_parent, int _page)
|
|
: parent(_parent)
|
|
, annotation(nullptr)
|
|
, page(_page)
|
|
{
|
|
Q_ASSERT(!parent->parent);
|
|
parent->children.append(this);
|
|
}
|
|
|
|
AnnItem::~AnnItem()
|
|
{
|
|
qDeleteAll(children);
|
|
}
|
|
|
|
AnnotationModelPrivate::AnnotationModelPrivate(AnnotationModel *qq)
|
|
: q(qq)
|
|
, root(new AnnItem)
|
|
{
|
|
}
|
|
|
|
AnnotationModelPrivate::~AnnotationModelPrivate()
|
|
{
|
|
delete root;
|
|
}
|
|
|
|
static void updateAnnotationPointer(AnnItem *item, const QVector<Okular::Page *> &pages)
|
|
{
|
|
if (item->annotation) {
|
|
item->annotation = pages[item->page]->annotation(item->annotation->uniqueName());
|
|
if (!item->annotation)
|
|
qWarning() << "Lost annotation on document save, something went wrong";
|
|
}
|
|
|
|
for (AnnItem *child : qAsConst(item->children)) {
|
|
updateAnnotationPointer(child, pages);
|
|
}
|
|
}
|
|
|
|
void AnnotationModelPrivate::notifySetup(const QVector<Okular::Page *> &pages, int setupFlags)
|
|
{
|
|
if (!(setupFlags & Okular::DocumentObserver::DocumentChanged)) {
|
|
if (setupFlags & Okular::DocumentObserver::UrlChanged) {
|
|
// Here with UrlChanged and no document changed it means we
|
|
// need to update all the Annotation* otherwise
|
|
// they still point to the old document ones, luckily the old ones are still
|
|
// around so we can look for the new ones using unique ids, etc
|
|
updateAnnotationPointer(root, pages);
|
|
}
|
|
return;
|
|
}
|
|
|
|
q->beginResetModel();
|
|
qDeleteAll(root->children);
|
|
root->children.clear();
|
|
|
|
rebuildTree(pages);
|
|
q->endResetModel();
|
|
}
|
|
|
|
void AnnotationModelPrivate::notifyPageChanged(int page, int flags)
|
|
{
|
|
// we are strictly interested in annotations
|
|
if (!(flags & Okular::DocumentObserver::Annotations))
|
|
return;
|
|
|
|
const QLinkedList<Okular::Annotation *> annots = filterOutWidgetAnnotations(document->page(page)->annotations());
|
|
int annItemIndex = -1;
|
|
AnnItem *annItem = findItem(page, &annItemIndex);
|
|
// case 1: the page has no more annotations
|
|
// => remove the branch, if any
|
|
if (annots.isEmpty()) {
|
|
if (annItem) {
|
|
q->beginRemoveRows(indexForItem(root), annItemIndex, annItemIndex);
|
|
delete root->children.at(annItemIndex);
|
|
root->children.removeAt(annItemIndex);
|
|
q->endRemoveRows();
|
|
}
|
|
return;
|
|
}
|
|
// case 2: no existing branch
|
|
// => add a new branch, and add the annotations for the page
|
|
if (!annItem) {
|
|
int i = 0;
|
|
while (i < root->children.count() && root->children.at(i)->page < page)
|
|
++i;
|
|
|
|
AnnItem *annItem = new AnnItem();
|
|
annItem->page = page;
|
|
annItem->parent = root;
|
|
q->beginInsertRows(indexForItem(root), i, i);
|
|
annItem->parent->children.insert(i, annItem);
|
|
q->endInsertRows();
|
|
QLinkedList<Okular::Annotation *>::ConstIterator it = annots.begin(), itEnd = annots.end();
|
|
int newid = 0;
|
|
for (; it != itEnd; ++it, ++newid) {
|
|
q->beginInsertRows(indexForItem(annItem), newid, newid);
|
|
new AnnItem(annItem, *it);
|
|
q->endInsertRows();
|
|
}
|
|
return;
|
|
}
|
|
// case 3: existing branch, less annotations than items
|
|
// => lookup and remove the annotations
|
|
if (annItem->children.count() > annots.count()) {
|
|
for (int i = annItem->children.count(); i > 0; --i) {
|
|
Okular::Annotation *ref = annItem->children.at(i - 1)->annotation;
|
|
bool found = false;
|
|
QLinkedList<Okular::Annotation *>::ConstIterator it = annots.begin(), itEnd = annots.end();
|
|
for (; !found && it != itEnd; ++it) {
|
|
if ((*it) == ref)
|
|
found = true;
|
|
}
|
|
if (!found) {
|
|
q->beginRemoveRows(indexForItem(annItem), i - 1, i - 1);
|
|
delete annItem->children.at(i - 1);
|
|
annItem->children.removeAt(i - 1);
|
|
q->endRemoveRows();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// case 4: existing branch, less items than annotations
|
|
// => lookup and add annotations if not in the branch
|
|
if (annots.count() > annItem->children.count()) {
|
|
QLinkedList<Okular::Annotation *>::ConstIterator it = annots.begin(), itEnd = annots.end();
|
|
for (; it != itEnd; ++it) {
|
|
Okular::Annotation *ref = *it;
|
|
bool found = false;
|
|
int count = annItem->children.count();
|
|
for (int i = 0; !found && i < count; ++i) {
|
|
if (ref == annItem->children.at(i)->annotation)
|
|
found = true;
|
|
}
|
|
if (!found) {
|
|
q->beginInsertRows(indexForItem(annItem), count, count);
|
|
new AnnItem(annItem, ref);
|
|
q->endInsertRows();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// case 5: the data of some annotation changed
|
|
// TODO: what do we do in this case?
|
|
// FIXME: for now, update ALL the annotations for that page
|
|
for (int i = 0; i < annItem->children.count(); ++i) {
|
|
QModelIndex index = indexForItem(annItem->children.at(i));
|
|
emit q->dataChanged(index, index);
|
|
}
|
|
}
|
|
|
|
QModelIndex AnnotationModelPrivate::indexForItem(AnnItem *item) const
|
|
{
|
|
if (item->parent) {
|
|
int id = item->parent->children.indexOf(item);
|
|
if (id >= 0 && id < item->parent->children.count())
|
|
return q->createIndex(id, 0, item);
|
|
}
|
|
return QModelIndex();
|
|
}
|
|
|
|
void AnnotationModelPrivate::rebuildTree(const QVector<Okular::Page *> &pages)
|
|
{
|
|
if (pages.isEmpty())
|
|
return;
|
|
|
|
emit q->layoutAboutToBeChanged();
|
|
for (int i = 0; i < pages.count(); ++i) {
|
|
const QLinkedList<Okular::Annotation *> annots = filterOutWidgetAnnotations(pages.at(i)->annotations());
|
|
if (annots.isEmpty())
|
|
continue;
|
|
|
|
AnnItem *annItem = new AnnItem(root, i);
|
|
QLinkedList<Okular::Annotation *>::ConstIterator it = annots.begin(), itEnd = annots.end();
|
|
for (; it != itEnd; ++it) {
|
|
new AnnItem(annItem, *it);
|
|
}
|
|
}
|
|
emit q->layoutChanged();
|
|
}
|
|
|
|
AnnItem *AnnotationModelPrivate::findItem(int page, int *index) const
|
|
{
|
|
for (int i = 0; i < root->children.count(); ++i) {
|
|
AnnItem *tmp = root->children.at(i);
|
|
if (tmp->page == page) {
|
|
if (index)
|
|
*index = i;
|
|
return tmp;
|
|
}
|
|
}
|
|
if (index)
|
|
*index = -1;
|
|
return nullptr;
|
|
}
|
|
|
|
AnnotationModel::AnnotationModel(Okular::Document *document, QObject *parent)
|
|
: QAbstractItemModel(parent)
|
|
, d(new AnnotationModelPrivate(this))
|
|
{
|
|
d->document = document;
|
|
|
|
d->document->addObserver(d);
|
|
}
|
|
|
|
AnnotationModel::~AnnotationModel()
|
|
{
|
|
if (d->document)
|
|
d->document->removeObserver(d);
|
|
|
|
delete d;
|
|
}
|
|
|
|
int AnnotationModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
Q_UNUSED(parent)
|
|
return 1;
|
|
}
|
|
|
|
QVariant AnnotationModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
AnnItem *item = static_cast<AnnItem *>(index.internalPointer());
|
|
if (!item->annotation) {
|
|
if (role == Qt::DisplayRole)
|
|
return i18n("Page %1", item->page + 1);
|
|
else if (role == Qt::DecorationRole)
|
|
return QIcon::fromTheme(QStringLiteral("text-plain"));
|
|
else if (role == PageRole)
|
|
return item->page;
|
|
|
|
return QVariant();
|
|
}
|
|
switch (role) {
|
|
case Qt::DisplayRole:
|
|
return GuiUtils::captionForAnnotation(item->annotation);
|
|
break;
|
|
case Qt::DecorationRole:
|
|
return QIcon::fromTheme(QStringLiteral("okular"));
|
|
break;
|
|
case Qt::ToolTipRole:
|
|
return GuiUtils::prettyToolTip(item->annotation);
|
|
break;
|
|
case AuthorRole:
|
|
return item->annotation->author();
|
|
break;
|
|
case PageRole:
|
|
return item->page;
|
|
break;
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
bool AnnotationModel::hasChildren(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid())
|
|
return true;
|
|
|
|
AnnItem *item = static_cast<AnnItem *>(parent.internalPointer());
|
|
return !item->children.isEmpty();
|
|
}
|
|
|
|
QVariant AnnotationModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation != Qt::Horizontal)
|
|
return QVariant();
|
|
|
|
if (section == 0 && role == Qt::DisplayRole)
|
|
return QString::fromLocal8Bit("Annotations");
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QModelIndex AnnotationModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (row < 0 || column != 0)
|
|
return QModelIndex();
|
|
|
|
AnnItem *item = parent.isValid() ? static_cast<AnnItem *>(parent.internalPointer()) : d->root;
|
|
if (row < item->children.count())
|
|
return createIndex(row, column, item->children.at(row));
|
|
|
|
return QModelIndex();
|
|
}
|
|
|
|
QModelIndex AnnotationModel::parent(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return QModelIndex();
|
|
|
|
AnnItem *item = static_cast<AnnItem *>(index.internalPointer());
|
|
return d->indexForItem(item->parent);
|
|
}
|
|
|
|
int AnnotationModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
AnnItem *item = parent.isValid() ? static_cast<AnnItem *>(parent.internalPointer()) : d->root;
|
|
return item->children.count();
|
|
}
|
|
|
|
bool AnnotationModel::isAnnotation(const QModelIndex &index) const
|
|
{
|
|
return annotationForIndex(index);
|
|
}
|
|
|
|
Okular::Annotation *AnnotationModel::annotationForIndex(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return nullptr;
|
|
|
|
AnnItem *item = static_cast<AnnItem *>(index.internalPointer());
|
|
return item->annotation;
|
|
}
|
|
|
|
#include "moc_annotationmodel.cpp"
|