okular/core/page.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1068 lines
32 KiB
C++
Raw Normal View History

/***************************************************************************
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
* 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 "page.h"
#include "page_p.h"
// qt/kde includes
2018-08-31 09:23:45 +00:00
#include <QDomDocument>
#include <QDomElement>
#include <QHash>
#include <QPixmap>
#include <QSet>
#include <QString>
#include <QUuid>
#include <QVariant>
2018-08-31 09:23:45 +00:00
#include <QDebug>
// local includes
#include "action.h"
#include "annotations.h"
#include "annotations_p.h"
#include "debug_p.h"
#include "document.h"
#include "document_p.h"
#include "form.h"
#include "form_p.h"
#include "observer.h"
#include "pagecontroller_p.h"
#include "pagesize.h"
#include "pagetransition.h"
#include "rotationjob_p.h"
#include "textpage_p.h"
#include "tile.h"
2012-08-17 18:23:58 +00:00
#include "tilesmanager_p.h"
#include "utils_p.h"
#include <limits>
#ifdef PAGE_PROFILE
2018-08-31 09:23:45 +00:00
#include <QTime>
#endif
using namespace Okular;
static const double distanceConsideredEqual = 25; // 5px
static void deleteObjectRects(QLinkedList<ObjectRect *> &rects, const QSet<ObjectRect::ObjectType> &which)
{
QLinkedList<ObjectRect *>::iterator it = rects.begin(), end = rects.end();
for (; it != end;)
if (which.contains((*it)->objectType())) {
delete *it;
it = rects.erase(it);
} else
++it;
}
PagePrivate::PagePrivate(Page *page, uint n, double w, double h, Rotation o)
: m_page(page)
, m_number(n)
, m_orientation(o)
, m_width(w)
, m_height(h)
, m_doc(nullptr)
, m_boundingBox(0, 0, 1, 1)
, m_rotation(Rotation0)
, m_text(nullptr)
, m_transition(nullptr)
, m_textSelections(nullptr)
, m_openingAction(nullptr)
, m_closingAction(nullptr)
, m_duration(-1)
, m_isBoundingBoxKnown(false)
{
// avoid Division-By-Zero problems in the program
if (m_width <= 0)
m_width = 1;
if (m_height <= 0)
m_height = 1;
}
PagePrivate::~PagePrivate()
{
qDeleteAll(formfields);
delete m_openingAction;
delete m_closingAction;
delete m_text;
delete m_transition;
}
PagePrivate *PagePrivate::get(Page *page)
{
return page ? page->d : nullptr;
}
void PagePrivate::imageRotationDone(RotationJob *job)
{
TilesManager *tm = tilesManager(job->observer());
2012-08-17 17:25:58 +00:00
if (tm) {
QPixmap *pixmap = new QPixmap(QPixmap::fromImage(job->image()));
tm->setPixmap(pixmap, job->rect(), job->isPartialUpdate());
2012-08-17 17:25:58 +00:00
delete pixmap;
return;
}
QMap<DocumentObserver *, PixmapObject>::iterator it = m_pixmaps.find(job->observer());
if (it != m_pixmaps.end()) {
PixmapObject &object = it.value();
(*object.m_pixmap) = QPixmap::fromImage(job->image());
object.m_rotation = job->rotation();
object.m_isPartialPixmap = job->isPartialUpdate();
} else {
PixmapObject object;
object.m_pixmap = new QPixmap(QPixmap::fromImage(job->image()));
object.m_rotation = job->rotation();
object.m_isPartialPixmap = job->isPartialUpdate();
m_pixmaps.insert(job->observer(), object);
}
}
QTransform PagePrivate::rotationMatrix() const
{
return Okular::buildRotationMatrix(m_rotation);
}
/** class Page **/
Page::Page(uint pageNumber, double w, double h, Rotation o)
: d(new PagePrivate(this, pageNumber, w, h, o))
{
}
Page::~Page()
{
if (d) {
deletePixmaps();
deleteRects();
d->deleteHighlights();
deleteAnnotations();
d->deleteTextSelections();
deleteSourceReferences();
delete d;
}
}
int Page::number() const
{
return d->m_number;
}
Rotation Page::orientation() const
{
return d->m_orientation;
}
Rotation Page::rotation() const
{
return d->m_rotation;
}
Rotation Page::totalOrientation() const
{
return (Rotation)(((int)d->m_orientation + (int)d->m_rotation) % 4);
}
double Page::width() const
{
return d->m_width;
}
double Page::height() const
{
return d->m_height;
}
double Page::ratio() const
{
return d->m_height / d->m_width;
}
NormalizedRect Page::boundingBox() const
{
return d->m_boundingBox;
}
bool Page::isBoundingBoxKnown() const
{
return d->m_isBoundingBoxKnown;
}
void Page::setBoundingBox(const NormalizedRect &bbox)
{
if (d->m_isBoundingBoxKnown && d->m_boundingBox == bbox)
return;
// Allow tiny rounding errors (happens during rotation)
static const double epsilon = 0.00001;
Q_ASSERT(bbox.left >= -epsilon && bbox.top >= -epsilon && bbox.right <= 1 + epsilon && bbox.bottom <= 1 + epsilon);
d->m_boundingBox = bbox & NormalizedRect(0., 0., 1., 1.);
d->m_isBoundingBoxKnown = true;
}
bool Page::hasPixmap(DocumentObserver *observer, int width, int height, const NormalizedRect &rect) const
{
TilesManager *tm = d->tilesManager(observer);
if (tm) {
if (width != tm->width() || height != tm->height()) {
// FIXME hasPixmap should not be calling setSize on the TilesManager this is not very "const"
// as this function claims to be
if (width != -1 && height != -1) {
tm->setSize(width, height);
}
return false;
}
return tm->hasPixmap(rect);
}
QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator it = d->m_pixmaps.constFind(observer);
if (it == d->m_pixmaps.constEnd())
return false;
if (width == -1 || height == -1)
return true;
if (it.value().m_isPartialPixmap)
return false;
const QPixmap *pixmap = it.value().m_pixmap;
return (pixmap->width() == width && pixmap->height() == height);
}
bool Page::hasTextPage() const
{
return d->m_text != nullptr;
}
RegularAreaRect *Page::wordAt(const NormalizedPoint &p, QString *word) const
{
if (d->m_text)
return d->m_text->wordAt(p, word);
return nullptr;
}
RegularAreaRect *Page::textArea(TextSelection *selection) const
{
if (d->m_text)
return d->m_text->textArea(selection);
return nullptr;
}
bool Page::hasObjectRect(double x, double y, double xScale, double yScale) const
{
if (m_rects.isEmpty())
return false;
QLinkedList<ObjectRect *>::const_iterator it = m_rects.begin(), end = m_rects.end();
for (; it != end; ++it)
if ((*it)->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual)
return true;
return false;
}
bool Page::hasHighlights(int s_id) const
{
// simple case: have no highlights
if (m_highlights.isEmpty())
return false;
// simple case: we have highlights and no id to match
if (s_id == -1)
return true;
// iterate on the highlights list to find an entry by id
QLinkedList<HighlightAreaRect *>::const_iterator it = m_highlights.begin(), end = m_highlights.end();
for (; it != end; ++it)
if ((*it)->s_id == s_id)
return true;
return false;
}
bool Page::hasTransition() const
{
return d->m_transition != nullptr;
}
bool Page::hasAnnotations() const
{
return !m_annotations.isEmpty();
}
RegularAreaRect *Page::findText(int id, const QString &text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect) const
{
RegularAreaRect *rect = nullptr;
if (text.isEmpty() || !d->m_text)
return rect;
rect = d->m_text->findText(id, text, direction, caseSensitivity, lastRect);
return rect;
}
QString Page::text(const RegularAreaRect *area) const
{
return text(area, TextPage::AnyPixelTextAreaInclusionBehaviour);
}
QString Page::text(const RegularAreaRect *area, TextPage::TextAreaInclusionBehaviour b) const
{
QString ret;
if (!d->m_text)
return ret;
if (area) {
RegularAreaRect rotatedArea = *area;
rotatedArea.transform(d->rotationMatrix().inverted());
ret = d->m_text->text(&rotatedArea, b);
} else
ret = d->m_text->text(nullptr, b);
return ret;
}
TextEntity::List Page::words(const RegularAreaRect *area, TextPage::TextAreaInclusionBehaviour b) const
{
TextEntity::List ret;
if (!d->m_text)
return ret;
if (area) {
RegularAreaRect rotatedArea = *area;
rotatedArea.transform(d->rotationMatrix().inverted());
ret = d->m_text->words(&rotatedArea, b);
} else
ret = d->m_text->words(nullptr, b);
for (auto &retI : ret) {
const TextEntity *orig = retI;
retI = new TextEntity(orig->text(), new Okular::NormalizedRect(orig->transformedArea(d->rotationMatrix())));
delete orig;
}
return ret;
}
void PagePrivate::rotateAt(Rotation orientation)
{
if (orientation == m_rotation)
return;
deleteTextSelections();
if (((int)m_orientation + (int)m_rotation) % 2 != ((int)m_orientation + (int)orientation) % 2)
qSwap(m_width, m_height);
Rotation oldRotation = m_rotation;
m_rotation = orientation;
/**
* Rotate the images of the page.
*/
QMapIterator<DocumentObserver *, PagePrivate::PixmapObject> it(m_pixmaps);
while (it.hasNext()) {
it.next();
const PagePrivate::PixmapObject &object = it.value();
RotationJob *job = new RotationJob(object.m_pixmap->toImage(), object.m_rotation, m_rotation, it.key());
job->setPage(this);
m_doc->m_pageController->addRotationJob(job);
}
2012-08-17 17:25:58 +00:00
/**
* Rotate tiles manager
2012-08-17 17:25:58 +00:00
*/
QMapIterator<const DocumentObserver *, TilesManager *> i(m_tilesManagers);
while (i.hasNext()) {
i.next();
TilesManager *tm = i.value();
if (tm)
tm->setRotation(m_rotation);
}
2012-08-17 17:25:58 +00:00
/**
* Rotate the object rects on the page.
*/
const QTransform matrix = rotationMatrix();
2020-02-20 09:15:24 +00:00
for (ObjectRect *objRect : qAsConst(m_page->m_rects))
objRect->transform(matrix);
const QTransform highlightRotationMatrix = Okular::buildRotationMatrix((Rotation)(((int)m_rotation - (int)oldRotation + 4) % 4));
2020-02-20 09:15:24 +00:00
for (HighlightAreaRect *hlar : qAsConst(m_page->m_highlights)) {
hlar->transform(highlightRotationMatrix);
}
}
void PagePrivate::changeSize(const PageSize &size)
{
if (size.isNull() || (size.width() == m_width && size.height() == m_height))
return;
m_page->deletePixmaps();
// deleteHighlights();
// deleteTextSelections();
m_width = size.width();
m_height = size.height();
if (m_rotation % 2)
qSwap(m_width, m_height);
}
const ObjectRect *Page::objectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
{
// Walk list in reverse order so that annotations in the foreground are preferred
QLinkedListIterator<ObjectRect *> it(m_rects);
it.toBack();
while (it.hasPrevious()) {
const ObjectRect *objrect = it.previous();
if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual)
return objrect;
}
return nullptr;
}
QLinkedList<const ObjectRect *> Page::objectRects(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale) const
{
QLinkedList<const ObjectRect *> result;
QLinkedListIterator<ObjectRect *> it(m_rects);
it.toBack();
while (it.hasPrevious()) {
const ObjectRect *objrect = it.previous();
if ((objrect->objectType() == type) && objrect->distanceSqr(x, y, xScale, yScale) < distanceConsideredEqual)
result.append(objrect);
}
return result;
}
const ObjectRect *Page::nearestObjectRect(ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double *distance) const
{
ObjectRect *res = nullptr;
double minDistance = std::numeric_limits<double>::max();
QLinkedList<ObjectRect *>::const_iterator it = m_rects.constBegin(), end = m_rects.constEnd();
for (; it != end; ++it) {
if ((*it)->objectType() == type) {
double d = (*it)->distanceSqr(x, y, xScale, yScale);
if (d < minDistance) {
res = (*it);
minDistance = d;
}
}
}
if (distance)
*distance = minDistance;
return res;
}
const PageTransition *Page::transition() const
{
return d->m_transition;
}
QLinkedList<Annotation *> Page::annotations() const
{
return m_annotations;
}
2017-10-25 13:21:46 +00:00
Annotation *Page::annotation(const QString &uniqueName) const
{
for (Annotation *a : m_annotations) {
2017-10-25 13:21:46 +00:00
if (a->uniqueName() == uniqueName)
return a;
}
return nullptr;
}
const Action *Page::pageAction(PageAction action) const
{
switch (action) {
case Page::Opening:
return d->m_openingAction;
break;
case Page::Closing:
return d->m_closingAction;
break;
}
return nullptr;
}
QLinkedList<FormField *> Page::formFields() const
{
return d->formfields;
}
void Page::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect)
{
d->setPixmap(observer, pixmap, rect, false /*isPartialPixmap*/);
}
void PagePrivate::setPixmap(DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap)
{
if (m_rotation == Rotation0) {
TilesManager *tm = tilesManager(observer);
2012-08-17 17:25:58 +00:00
if (tm) {
tm->setPixmap(pixmap, rect, isPartialPixmap);
2012-08-17 17:25:58 +00:00
delete pixmap;
return;
}
QMap<DocumentObserver *, PagePrivate::PixmapObject>::iterator it = m_pixmaps.find(observer);
if (it != m_pixmaps.end()) {
delete it.value().m_pixmap;
} else {
it = m_pixmaps.insert(observer, PagePrivate::PixmapObject());
}
it.value().m_pixmap = pixmap;
it.value().m_rotation = m_rotation;
it.value().m_isPartialPixmap = isPartialPixmap;
} else {
// it can happen that we get a setPixmap while closing and thus the page controller is gone
if (m_doc->m_pageController) {
RotationJob *job = new RotationJob(pixmap->toImage(), Rotation0, m_rotation, observer);
job->setPage(this);
job->setRect(TilesManager::toRotatedRect(rect, m_rotation));
job->setIsPartialUpdate(isPartialPixmap);
m_doc->m_pageController->addRotationJob(job);
}
delete pixmap;
}
}
void Page::setTextPage(TextPage *textPage)
{
delete d->m_text;
d->m_text = textPage;
if (d->m_text) {
d->m_text->d->m_page = this;
// Correct/optimize text order for search and text selection
d->m_text->d->correctTextOrder();
}
}
void Page::setObjectRects(const QLinkedList<ObjectRect *> &rects)
{
QSet<ObjectRect::ObjectType> which;
which << ObjectRect::Action << ObjectRect::Image;
deleteObjectRects(m_rects, which);
/**
* Rotate the object rects of the page.
*/
const QTransform matrix = d->rotationMatrix();
QLinkedList<ObjectRect *>::const_iterator objectIt = rects.begin(), end = rects.end();
for (; objectIt != end; ++objectIt)
(*objectIt)->transform(matrix);
m_rects << rects;
}
void PagePrivate::setHighlight(int s_id, RegularAreaRect *rect, const QColor &color)
{
HighlightAreaRect *hr = new HighlightAreaRect(rect);
hr->s_id = s_id;
hr->color = color;
m_page->m_highlights.append(hr);
}
void PagePrivate::setTextSelections(RegularAreaRect *r, const QColor &color)
{
deleteTextSelections();
if (r) {
HighlightAreaRect *hr = new HighlightAreaRect(r);
hr->s_id = -1;
hr->color = color;
m_textSelections = hr;
delete r;
}
}
void Page::setSourceReferences(const QLinkedList<SourceRefObjectRect *> &refRects)
{
deleteSourceReferences();
for (SourceRefObjectRect *rect : refRects) {
m_rects << rect;
}
}
void Page::setDuration(double seconds)
{
d->m_duration = seconds;
}
double Page::duration() const
{
return d->m_duration;
}
void Page::setLabel(const QString &label)
{
d->m_label = label;
}
QString Page::label() const
{
return d->m_label;
}
const RegularAreaRect *Page::textSelection() const
{
return d->m_textSelections;
}
QColor Page::textSelectionColor() const
{
return d->m_textSelections ? d->m_textSelections->color : QColor();
}
void Page::addAnnotation(Annotation *annotation)
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
{
// Generate uniqueName: okular-{UUID}
if (annotation->uniqueName().isEmpty()) {
QString uniqueName = QStringLiteral("okular-") + QUuid::createUuid().toString();
annotation->setUniqueName(uniqueName);
}
annotation->d_ptr->m_page = d;
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
m_annotations.append(annotation);
AnnotationObjectRect *rect = new AnnotationObjectRect(annotation);
// Rotate the annotation on the page.
const QTransform matrix = d->rotationMatrix();
annotation->d_ptr->annotationTransform(matrix);
m_rects.append(rect);
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
}
bool Page::removeAnnotation(Annotation *annotation)
{
if (!d->m_doc->m_parent->canRemovePageAnnotation(annotation))
return false;
QLinkedList<Annotation *>::iterator aIt = m_annotations.begin(), aEnd = m_annotations.end();
for (; aIt != aEnd; ++aIt) {
if ((*aIt) && (*aIt)->uniqueName() == annotation->uniqueName()) {
int rectfound = false;
QLinkedList<ObjectRect *>::iterator it = m_rects.begin(), end = m_rects.end();
for (; it != end && !rectfound; ++it)
if (((*it)->objectType() == ObjectRect::OAnnotation) && ((*it)->object() == (*aIt))) {
delete *it;
it = m_rects.erase(it);
rectfound = true;
}
2014-09-11 17:36:01 +00:00
qCDebug(OkularCoreDebug) << "removed annotation:" << annotation->uniqueName();
annotation->d_ptr->m_page = nullptr;
m_annotations.erase(aIt);
break;
}
}
return true;
}
void Page::setTransition(PageTransition *transition)
{
delete d->m_transition;
d->m_transition = transition;
}
void Page::setPageAction(PageAction action, Action *link)
{
switch (action) {
case Page::Opening:
delete d->m_openingAction;
d->m_openingAction = link;
break;
case Page::Closing:
delete d->m_closingAction;
d->m_closingAction = link;
break;
}
}
void Page::setFormFields(const QLinkedList<FormField *> &fields)
{
qDeleteAll(d->formfields);
d->formfields = fields;
2020-02-20 09:15:24 +00:00
for (FormField *ff : qAsConst(d->formfields)) {
ff->d_ptr->setDefault();
}
}
void Page::deletePixmap(DocumentObserver *observer)
{
TilesManager *tm = d->tilesManager(observer);
if (tm) {
delete tm;
d->m_tilesManagers.remove(observer);
2012-07-16 15:57:51 +00:00
} else {
PagePrivate::PixmapObject object = d->m_pixmaps.take(observer);
2012-07-16 15:57:51 +00:00
delete object.m_pixmap;
}
}
void Page::deletePixmaps()
{
QMapIterator<DocumentObserver *, PagePrivate::PixmapObject> it(d->m_pixmaps);
while (it.hasNext()) {
it.next();
delete it.value().m_pixmap;
}
d->m_pixmaps.clear();
qDeleteAll(d->m_tilesManagers);
d->m_tilesManagers.clear();
}
void Page::deleteRects()
{
// delete ObjectRects of type Link and Image
QSet<ObjectRect::ObjectType> which;
which << ObjectRect::Action << ObjectRect::Image;
deleteObjectRects(m_rects, which);
}
void PagePrivate::deleteHighlights(int s_id)
{
// delete highlights by ID
QLinkedList<HighlightAreaRect *>::iterator it = m_page->m_highlights.begin(), end = m_page->m_highlights.end();
while (it != end) {
HighlightAreaRect *highlight = *it;
if (s_id == -1 || highlight->s_id == s_id) {
it = m_page->m_highlights.erase(it);
delete highlight;
} else
++it;
}
}
void PagePrivate::deleteTextSelections()
{
delete m_textSelections;
m_textSelections = nullptr;
}
void Page::deleteSourceReferences()
{
deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::SourceRef);
}
void Page::deleteAnnotations()
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
{
// delete ObjectRects of type Annotation
deleteObjectRects(m_rects, QSet<ObjectRect::ObjectType>() << ObjectRect::OAnnotation);
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
// delete all stored annotations
2020-02-20 09:15:24 +00:00
qDeleteAll(m_annotations);
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
m_annotations.clear();
}
bool PagePrivate::restoreLocalContents(const QDomNode &pageNode)
{
bool loadedAnything = false; // set if something actually gets loaded
2018-11-14 19:12:15 +00:00
// iterate over all children (annotationList, ...)
QDomNode childNode = pageNode.firstChild();
while (childNode.isElement()) {
QDomElement childElement = childNode.toElement();
childNode = childNode.nextSibling();
// parse annotationList child element
2015-10-29 12:37:11 +00:00
if (childElement.tagName() == QLatin1String("annotationList")) {
#ifdef PAGE_PROFILE
QTime time;
time.start();
#endif
// Clone annotationList as root node in restoredLocalAnnotationList
const QDomNode clonedNode = restoredLocalAnnotationList.importNode(childElement, true);
restoredLocalAnnotationList.appendChild(clonedNode);
// iterate over all annotations
QDomNode annotationNode = childElement.firstChild();
while (annotationNode.isElement()) {
// get annotation element and advance to next annot
QDomElement annotElement = annotationNode.toElement();
annotationNode = annotationNode.nextSibling();
// get annotation from the dom element
Annotation *annotation = AnnotationUtils::createAnnotation(annotElement);
// append annotation to the list or show warning
if (annotation) {
m_doc->performAddPageAnnotation(m_number, annotation);
2014-09-11 17:36:01 +00:00
qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName();
loadedAnything = true;
} else
2014-09-11 19:12:27 +00:00
qCWarning(OkularCoreDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML.";
}
#ifdef PAGE_PROFILE
2014-09-11 17:36:01 +00:00
qCDebug(OkularCoreDebug).nospace() << "annots: XML Load time: " << time.elapsed() << "ms";
#endif
}
// parse formList child element
2015-10-29 12:37:11 +00:00
else if (childElement.tagName() == QLatin1String("forms")) {
// Clone forms as root node in restoredFormFieldList
const QDomNode clonedNode = restoredFormFieldList.importNode(childElement, true);
restoredFormFieldList.appendChild(clonedNode);
if (formfields.isEmpty())
continue;
QHash<int, FormField *> hashedforms;
2020-02-20 09:15:24 +00:00
for (FormField *ff : qAsConst(formfields)) {
hashedforms[ff->id()] = ff;
}
// iterate over all forms
QDomNode formsNode = childElement.firstChild();
while (formsNode.isElement()) {
// get annotation element and advance to next annot
QDomElement formElement = formsNode.toElement();
formsNode = formsNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (formElement.tagName() != QLatin1String("form"))
continue;
bool ok = true;
2015-10-29 12:37:11 +00:00
int index = formElement.attribute(QStringLiteral("id")).toInt(&ok);
if (!ok)
continue;
QHash<int, FormField *>::const_iterator wantedIt = hashedforms.constFind(index);
if (wantedIt == hashedforms.constEnd())
continue;
2015-10-29 12:37:11 +00:00
QString value = formElement.attribute(QStringLiteral("value"));
(*wantedIt)->d_ptr->setValue(value);
loadedAnything = true;
}
}
}
return loadedAnything;
}
void PagePrivate::saveLocalContents(QDomNode &parentNode, QDomDocument &document, PageItems what) const
{
// create the page node and set the 'number' attribute
2015-10-29 12:37:11 +00:00
QDomElement pageElement = document.createElement(QStringLiteral("page"));
pageElement.setAttribute(QStringLiteral("number"), m_number);
#if 0
// add bookmark info if is bookmarked
if ( d->m_bookmarked )
{
// create the pageElement's 'bookmark' child
QDomElement bookmarkElement = document.createElement( "bookmark" );
pageElement.appendChild( bookmarkElement );
// add attributes to the element
//bookmarkElement.setAttribute( "name", bookmark name );
}
#endif
// add annotations info if has got any
if ((what & AnnotationPageItems) && (what & OriginalAnnotationPageItems)) {
const QDomElement savedDocRoot = restoredLocalAnnotationList.documentElement();
if (!savedDocRoot.isNull()) {
// Import and append node in target document
const QDomNode importedNode = document.importNode(savedDocRoot, true);
pageElement.appendChild(importedNode);
}
} else if ((what & AnnotationPageItems) && !m_page->m_annotations.isEmpty()) {
// create the annotationList
2015-10-29 12:37:11 +00:00
QDomElement annotListElement = document.createElement(QStringLiteral("annotationList"));
// add every annotation to the annotationList
QLinkedList<Annotation *>::const_iterator aIt = m_page->m_annotations.constBegin(), aEnd = m_page->m_annotations.constEnd();
for (; aIt != aEnd; ++aIt) {
// get annotation
const Annotation *a = *aIt;
// only save okular annotations (not the embedded in file ones)
if (!(a->flags() & Annotation::External)) {
// append an filled-up element called 'annotation' to the list
2015-10-29 12:37:11 +00:00
QDomElement annElement = document.createElement(QStringLiteral("annotation"));
AnnotationUtils::storeAnnotation(a, annElement, document);
annotListElement.appendChild(annElement);
2014-09-11 17:36:01 +00:00
qCDebug(OkularCoreDebug) << "save annotation:" << a->uniqueName();
}
}
// append the annotationList element if annotations have been set
if (annotListElement.hasChildNodes())
pageElement.appendChild(annotListElement);
}
// add forms info if has got any
if ((what & FormFieldPageItems) && (what & OriginalFormFieldPageItems)) {
const QDomElement savedDocRoot = restoredFormFieldList.documentElement();
if (!savedDocRoot.isNull()) {
// Import and append node in target document
const QDomNode importedNode = document.importNode(savedDocRoot, true);
pageElement.appendChild(importedNode);
}
} else if ((what & FormFieldPageItems) && !formfields.isEmpty()) {
// create the formList
2015-10-29 12:37:11 +00:00
QDomElement formListElement = document.createElement(QStringLiteral("forms"));
// add every form data to the formList
QLinkedList<FormField *>::const_iterator fIt = formfields.constBegin(), fItEnd = formfields.constEnd();
for (; fIt != fItEnd; ++fIt) {
// get the form field
const FormField *f = *fIt;
QString newvalue = f->d_ptr->value();
if (f->d_ptr->m_default == newvalue)
continue;
// append an filled-up element called 'annotation' to the list
2015-10-29 12:37:11 +00:00
QDomElement formElement = document.createElement(QStringLiteral("form"));
formElement.setAttribute(QStringLiteral("id"), f->id());
formElement.setAttribute(QStringLiteral("value"), newvalue);
formListElement.appendChild(formElement);
}
// append the annotationList element if annotations have been set
if (formListElement.hasChildNodes())
pageElement.appendChild(formListElement);
}
// append the page element only if has children
if (pageElement.hasChildNodes())
parentNode.appendChild(pageElement);
}
const QPixmap *Page::_o_nearestPixmap(DocumentObserver *observer, int w, int h) const
{
Q_UNUSED(h)
const QPixmap *pixmap = nullptr;
// if a pixmap is present for given id, use it
QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator itPixmap = d->m_pixmaps.constFind(observer);
if (itPixmap != d->m_pixmaps.constEnd())
pixmap = itPixmap.value().m_pixmap;
// else find the closest match using pixmaps of other IDs (great optim!)
else if (!d->m_pixmaps.isEmpty()) {
int minDistance = -1;
QMap<DocumentObserver *, PagePrivate::PixmapObject>::const_iterator it = d->m_pixmaps.constBegin(), end = d->m_pixmaps.constEnd();
for (; it != end; ++it) {
int pixWidth = (*it).m_pixmap->width(), distance = pixWidth > w ? pixWidth - w : w - pixWidth;
if (minDistance == -1 || distance < minDistance) {
pixmap = (*it).m_pixmap;
minDistance = distance;
}
}
}
return pixmap;
}
bool Page::hasTilesManager(const DocumentObserver *observer) const
{
return d->tilesManager(observer) != nullptr;
}
QList<Tile> Page::tilesAt(const DocumentObserver *observer, const NormalizedRect &rect) const
{
TilesManager *tm = d->m_tilesManagers.value(observer);
if (tm)
return tm->tilesAt(rect, TilesManager::PixmapTile);
else
return QList<Tile>();
}
TilesManager *PagePrivate::tilesManager(const DocumentObserver *observer) const
{
return m_tilesManagers.value(observer);
}
void PagePrivate::setTilesManager(const DocumentObserver *observer, TilesManager *tm)
{
TilesManager *old = m_tilesManagers.value(observer);
delete old;
m_tilesManagers.insert(observer, tm);
}
void PagePrivate::adoptGeneratedContents(PagePrivate *oldPage)
{
rotateAt(oldPage->m_rotation);
m_pixmaps = oldPage->m_pixmaps;
oldPage->m_pixmaps.clear();
m_tilesManagers = oldPage->m_tilesManagers;
oldPage->m_tilesManagers.clear();
m_boundingBox = oldPage->m_boundingBox;
m_isBoundingBoxKnown = oldPage->m_isBoundingBoxKnown;
m_text = oldPage->m_text;
oldPage->m_text = nullptr;
m_textSelections = oldPage->m_textSelections;
oldPage->m_textSelections = nullptr;
restoredLocalAnnotationList = oldPage->restoredLocalAnnotationList;
restoredFormFieldList = oldPage->restoredFormFieldList;
}
FormField *PagePrivate::findEquivalentForm(const Page *p, FormField *oldField)
{
// given how id is not very good of id (at least for pdf) we do a few passes
// same rect, type and id
for (FormField *f : qAsConst(p->d->formfields)) {
if (f->rect() == oldField->rect() && f->type() == oldField->type() && f->id() == oldField->id())
return f;
}
// same rect and type
for (FormField *f : qAsConst(p->d->formfields)) {
if (f->rect() == oldField->rect() && f->type() == oldField->type())
return f;
}
// fuzzy rect, same type and id
for (FormField *f : qAsConst(p->d->formfields)) {
if (f->type() == oldField->type() && f->id() == oldField->id() && qFuzzyCompare(f->rect().left, oldField->rect().left) && qFuzzyCompare(f->rect().top, oldField->rect().top) &&
qFuzzyCompare(f->rect().right, oldField->rect().right) && qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) {
return f;
}
}
// fuzzy rect and same type
for (FormField *f : qAsConst(p->d->formfields)) {
if (f->type() == oldField->type() && qFuzzyCompare(f->rect().left, oldField->rect().left) && qFuzzyCompare(f->rect().top, oldField->rect().top) && qFuzzyCompare(f->rect().right, oldField->rect().right) &&
qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) {
return f;
}
}
return nullptr;
}