okular/core/annotations.cpp

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

3250 lines
89 KiB
C++
Raw Normal View History

2021-05-24 07:25:56 +00:00
/*
SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "annotations.h"
#include "annotations_p.h"
// qt/kde includes
2018-08-31 09:23:45 +00:00
#include <QApplication>
#include <QColor>
#include <QIcon>
#include <QPainter>
#include <QStandardPaths>
#include <QSvgRenderer>
// DBL_MAX
#include <float.h>
// local includes
#include "action.h"
#include "document.h"
#include "document_p.h"
#include "movie.h"
#include "page_p.h"
#include "sound.h"
using namespace Okular;
/**
* True, if point @p c lies to the left of the vector from @p a to @p b
* @internal
*/
static bool isLeftOfVector(const NormalizedPoint &a, const NormalizedPoint &b, const NormalizedPoint &c)
{
// cross product
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) > 0;
}
/**
* @brief Calculates distance of the given point @p x @p y @p xScale @p yScale to the @p path
*
* Does piecewise comparison and selects the distance to the closest segment
*/
static double distanceSqr(double x, double y, double xScale, double yScale, const QList<NormalizedPoint> &path)
{
double distance = DBL_MAX;
double thisDistance;
QList<NormalizedPoint>::const_iterator i = path.constBegin();
NormalizedPoint lastPoint = *i;
for (++i; i != path.constEnd(); ++i) {
thisDistance = NormalizedPoint::distanceSqr(x, y, xScale, yScale, lastPoint, (*i));
if (thisDistance < distance) {
distance = thisDistance;
}
lastPoint = *i;
}
return distance;
}
/**
* Given the squared @p distance from the idealized 0-width line and a pen width @p penWidth,
* (not squared!), returns the final distance
*
* @warning The returned distance is not exact:
2013-06-24 10:46:16 +00:00
* We calculate an (exact) squared distance to the ideal (centered) line, and then subtract
* the squared width of the pen:
* a^2 - b^2 where a = "distance from idealized 0-width line" b = "pen width"
* For an exact result, we would want to calculate "(a - b)^2" but that would require
* a square root operation because we only know the squared distance a^2.
*
* However, the approximation is feasible, because:
* error = (a-b)^2 - (a^2 - b^2) = -2ab + 2b^2 = 2b(b - a)
* Therefore:
* lim_{a->b} a^2 - b^2 - a^2 + 2ab - b^2 --> 0
*
* In other words, this approximation will estimate the distance to be slightly more than it actually is
* for as long as we are far "outside" the line, becoming more accurate the closer we get to the line
2013-06-24 10:46:16 +00:00
* boundary. Trivially, it also fulfils (a1 < a2) => ((a1^2 - b^2) < (a2^2 - b^2)) making it monotonic.
* "Inside" of the drawn line, the distance is 0 anyway.
*/
static double strokeDistance(double distance, double penWidth)
{
return fmax(distance - pow(penWidth, 2), 0);
}
// BEGIN AnnotationUtils implementation
Annotation *AnnotationUtils::createAnnotation(const QDomElement &annElement)
{
// safety check on annotation element
2015-10-29 12:37:11 +00:00
if (!annElement.hasAttribute(QStringLiteral("type"))) {
return nullptr;
}
// build annotation of given type
Annotation *annotation = nullptr;
2015-10-29 12:37:11 +00:00
int typeNumber = annElement.attribute(QStringLiteral("type")).toInt();
switch (typeNumber) {
case Annotation::AText:
annotation = new TextAnnotation(annElement);
break;
case Annotation::ALine:
annotation = new LineAnnotation(annElement);
break;
case Annotation::AGeom:
annotation = new GeomAnnotation(annElement);
break;
case Annotation::AHighlight:
annotation = new HighlightAnnotation(annElement);
break;
case Annotation::AStamp:
annotation = new StampAnnotation(annElement);
break;
case Annotation::AInk:
annotation = new InkAnnotation(annElement);
break;
case Annotation::ACaret:
annotation = new CaretAnnotation(annElement);
break;
}
// return created annotation
return annotation;
}
void AnnotationUtils::storeAnnotation(const Annotation *ann, QDomElement &annElement, QDomDocument &document)
{
// save annotation's type as element's attribute
2015-10-29 12:37:11 +00:00
annElement.setAttribute(QStringLiteral("type"), (uint)ann->subType());
// append all annotation data as children of this node
ann->store(annElement, document);
}
QDomElement AnnotationUtils::findChildElement(const QDomNode &parentNode, const QString &name)
{
// loop through the whole children and return a 'name' named element
QDomNode subNode = parentNode.firstChild();
while (subNode.isElement()) {
QDomElement element = subNode.toElement();
if (element.tagName() == name) {
return element;
}
subNode = subNode.nextSibling();
}
// if the name can't be found, return a dummy null element
return QDomElement();
}
QRect AnnotationUtils::annotationGeometry(const Annotation *annotation, double scaleX, double scaleY)
{
const QRect rect = annotation->transformedBoundingRectangle().geometry((int)scaleX, (int)scaleY);
if (annotation->subType() == Annotation::AText && (((TextAnnotation *)annotation)->textType() == TextAnnotation::Linked)) {
// To be honest i have no clue of why the 24,24 is here, maybe to make sure it's not too small?
// But why only for linked text?
const QRect rect24 = QRect((int)(annotation->transformedBoundingRectangle().left * scaleX), (int)(annotation->transformedBoundingRectangle().top * scaleY), 24, 24);
return rect24.united(rect);
}
return rect;
}
QPixmap AnnotationUtils::loadStamp(const QString &nameOrPath, int size, bool keepAspectRatio)
{
const QString name = nameOrPath.toLower();
static std::unique_ptr<QSvgRenderer> svgStampFile;
if (!svgStampFile.get()) {
const QString stampFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/stamps.svg"));
if (!stampFile.isEmpty()) {
svgStampFile = std::make_unique<QSvgRenderer>(stampFile);
if (!svgStampFile->isValid()) {
svgStampFile.reset();
}
}
}
QSvgRenderer *r = svgStampFile.get();
if (r && r->isValid() && r->elementExists(name)) {
const QSize stampSize = r->boundsOnElement(name).size().toSize();
const QSize pixmapSize = stampSize.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio);
QPixmap pixmap(pixmapSize);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
r->render(&p, name);
p.end();
return pixmap;
}
// _name is a path (do this before loading as icon name to avoid some rare weirdness )
QPixmap pixmap;
pixmap.load(nameOrPath);
if (!pixmap.isNull()) {
pixmap = pixmap.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return pixmap;
}
// _name is an icon name
return QIcon::fromTheme(name).pixmap(size);
}
// END AnnotationUtils implementation
2020-02-21 15:11:42 +00:00
AnnotationProxy::AnnotationProxy()
{
}
AnnotationProxy::~AnnotationProxy()
{
}
// BEGIN Annotation implementation
class Annotation::Style::Private
{
public:
Private()
: m_opacity(1.0)
, m_width(1.0)
, m_style(Solid)
, m_xCorners(0.0)
, m_yCorners(0.0)
, m_marks(3)
, m_spaces(0)
, m_effect(NoEffect)
, m_effectIntensity(1.0)
{
}
QColor m_color;
double m_opacity;
double m_width;
LineStyle m_style;
double m_xCorners;
double m_yCorners;
int m_marks;
int m_spaces;
LineEffect m_effect;
double m_effectIntensity;
};
Annotation::Style::Style()
: d(new Private)
{
}
Annotation::Style::~Style()
{
delete d;
}
Annotation::Style::Style(const Style &other)
: d(new Private)
{
*d = *other.d;
}
Annotation::Style &Annotation::Style::operator=(const Style &other)
{
if (this != &other) {
*d = *other.d;
}
return *this;
}
void Annotation::Style::setColor(const QColor &color)
{
d->m_color = color;
}
QColor Annotation::Style::color() const
{
return d->m_color;
}
void Annotation::Style::setOpacity(double opacity)
{
d->m_opacity = opacity;
}
double Annotation::Style::opacity() const
{
return d->m_opacity;
}
void Annotation::Style::setWidth(double width)
{
d->m_width = width;
}
double Annotation::Style::width() const
{
return d->m_width;
}
void Annotation::Style::setLineStyle(LineStyle style)
{
d->m_style = style;
}
Annotation::LineStyle Annotation::Style::lineStyle() const
{
return d->m_style;
}
void Annotation::Style::setXCorners(double xCorners)
{
d->m_xCorners = xCorners;
}
double Annotation::Style::xCorners() const
{
return d->m_xCorners;
}
void Annotation::Style::setYCorners(double yCorners)
{
d->m_yCorners = yCorners;
}
double Annotation::Style::yCorners() const
{
return d->m_yCorners;
}
void Annotation::Style::setMarks(int marks)
{
d->m_marks = marks;
}
int Annotation::Style::marks() const
{
return d->m_marks;
}
void Annotation::Style::setSpaces(int spaces)
{
d->m_spaces = spaces;
}
int Annotation::Style::spaces() const
{
return d->m_spaces;
}
void Annotation::Style::setLineEffect(LineEffect effect)
{
d->m_effect = effect;
}
Annotation::LineEffect Annotation::Style::lineEffect() const
{
return d->m_effect;
}
void Annotation::Style::setEffectIntensity(double intensity)
{
d->m_effectIntensity = intensity;
}
double Annotation::Style::effectIntensity() const
{
return d->m_effectIntensity;
}
class Annotation::Window::Private
{
public:
Private()
: m_flags(-1)
, m_width(0)
, m_height(0)
{
}
int m_flags;
NormalizedPoint m_topLeft;
int m_width;
int m_height;
QString m_title;
QString m_summary;
};
Annotation::Window::Window()
: d(new Private)
{
}
Annotation::Window::~Window()
{
delete d;
}
Annotation::Window::Window(const Window &other)
: d(new Private)
{
*d = *other.d;
}
Annotation::Window &Annotation::Window::operator=(const Window &other)
{
if (this != &other) {
*d = *other.d;
}
return *this;
}
void Annotation::Window::setFlags(int flags)
{
d->m_flags = flags;
}
int Annotation::Window::flags() const
{
return d->m_flags;
}
void Annotation::Window::setTopLeft(const NormalizedPoint &point)
{
d->m_topLeft = point;
}
NormalizedPoint Annotation::Window::topLeft() const
{
return d->m_topLeft;
}
void Annotation::Window::setWidth(int width)
{
d->m_width = width;
}
int Annotation::Window::width() const
{
return d->m_width;
}
void Annotation::Window::setHeight(int height)
{
d->m_height = height;
}
int Annotation::Window::height() const
{
return d->m_height;
}
void Annotation::Window::setTitle(const QString &title)
{
d->m_title = title;
}
QString Annotation::Window::title() const
{
return d->m_title;
}
void Annotation::Window::setSummary(const QString &summary)
{
d->m_summary = summary;
}
QString Annotation::Window::summary() const
{
return d->m_summary;
}
class Annotation::Revision::Private
{
public:
Private()
: m_annotation(nullptr)
, m_scope(Reply)
, m_type(None)
{
}
Annotation *m_annotation;
RevisionScope m_scope;
RevisionType m_type;
};
Annotation::Revision::Revision()
: d(new Private)
{
}
Annotation::Revision::~Revision()
{
delete d;
}
Annotation::Revision::Revision(const Revision &other)
: d(new Private)
{
*d = *other.d;
}
Annotation::Revision &Annotation::Revision::operator=(const Revision &other)
{
if (this != &other) {
*d = *other.d;
}
return *this;
}
void Annotation::Revision::setAnnotation(Annotation *annotation)
{
d->m_annotation = annotation;
}
Annotation *Annotation::Revision::annotation() const
{
return d->m_annotation;
}
void Annotation::Revision::setScope(RevisionScope scope)
{
d->m_scope = scope;
}
Annotation::RevisionScope Annotation::Revision::scope() const
{
return d->m_scope;
}
void Annotation::Revision::setType(RevisionType type)
{
d->m_type = type;
}
Annotation::RevisionType Annotation::Revision::type() const
{
return d->m_type;
}
AnnotationPrivate::AnnotationPrivate()
: m_page(nullptr)
, m_flags(0)
, m_disposeFunc(nullptr)
{
}
AnnotationPrivate::~AnnotationPrivate()
{
// delete all children revisions
if (m_revisions.isEmpty()) {
return;
}
QLinkedList<Annotation::Revision>::iterator it = m_revisions.begin(), end = m_revisions.end();
for (; it != end; ++it) {
delete (*it).annotation();
}
}
AnnotationPrivate *AnnotationPrivate::get(Annotation *a)
{
return a ? a->d_ptr : nullptr;
}
Annotation::Annotation(AnnotationPrivate &dd)
: d_ptr(&dd)
{
}
Annotation::Annotation(AnnotationPrivate &dd, const QDomNode &description)
: d_ptr(&dd)
{
d_ptr->setAnnotationProperties(description);
}
Annotation::~Annotation()
{
if (d_ptr->m_disposeFunc) {
d_ptr->m_disposeFunc(this);
}
delete d_ptr;
}
void Annotation::setAuthor(const QString &author)
{
Q_D(Annotation);
d->m_author = author;
}
QString Annotation::author() const
{
Q_D(const Annotation);
return d->m_author;
}
void Annotation::setContents(const QString &contents)
{
Q_D(Annotation);
d->m_contents = contents;
}
QString Annotation::contents() const
{
Q_D(const Annotation);
return d->m_contents;
}
void Annotation::setUniqueName(const QString &name)
{
Q_D(Annotation);
d->m_uniqueName = name;
}
QString Annotation::uniqueName() const
{
Q_D(const Annotation);
return d->m_uniqueName;
}
void Annotation::setModificationDate(const QDateTime &date)
{
Q_D(Annotation);
d->m_modifyDate = date;
}
QDateTime Annotation::modificationDate() const
{
Q_D(const Annotation);
return d->m_modifyDate;
}
void Annotation::setCreationDate(const QDateTime &date)
{
Q_D(Annotation);
d->m_creationDate = date;
}
QDateTime Annotation::creationDate() const
{
Q_D(const Annotation);
return d->m_creationDate;
}
void Annotation::setFlags(int flags)
{
Q_D(Annotation);
d->m_flags = flags;
}
int Annotation::flags() const
{
Q_D(const Annotation);
return d->m_flags;
}
void Annotation::setBoundingRectangle(const NormalizedRect &rectangle)
{
Q_D(Annotation);
d->m_boundary = rectangle;
d->resetTransformation();
if (d->m_page) {
d->transform(d->m_page->rotationMatrix());
}
}
NormalizedRect Annotation::boundingRectangle() const
{
Q_D(const Annotation);
return d->m_boundary;
}
NormalizedRect Annotation::transformedBoundingRectangle() const
{
Q_D(const Annotation);
return d->m_transformedBoundary;
}
void Annotation::translate(const NormalizedPoint &coord)
{
Q_D(Annotation);
d->translate(coord);
d->resetTransformation();
if (d->m_page) {
d->transform(d->m_page->rotationMatrix());
}
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
void Annotation::adjust(const NormalizedPoint &deltaCoord1, const NormalizedPoint &deltaCoord2)
{
Q_D(Annotation);
d->adjust(deltaCoord1, deltaCoord2);
d->resetTransformation();
if (d->m_page) {
d->transform(d->m_page->rotationMatrix());
}
}
bool Annotation::openDialogAfterCreation() const
{
Q_D(const Annotation);
return d->openDialogAfterCreation();
}
Annotation::Style &Annotation::style()
{
Q_D(Annotation);
return d->m_style;
}
const Annotation::Style &Annotation::style() const
{
Q_D(const Annotation);
return d->m_style;
}
Annotation::Window &Annotation::window()
{
Q_D(Annotation);
return d->m_window;
}
const Annotation::Window &Annotation::window() const
{
Q_D(const Annotation);
return d->m_window;
}
QLinkedList<Annotation::Revision> &Annotation::revisions()
{
Q_D(Annotation);
return d->m_revisions;
}
const QLinkedList<Annotation::Revision> &Annotation::revisions() const
{
Q_D(const Annotation);
return d->m_revisions;
}
void Annotation::setNativeId(const QVariant &id)
{
Q_D(Annotation);
d->m_nativeId = id;
}
QVariant Annotation::nativeId() const
{
Q_D(const Annotation);
return d->m_nativeId;
}
void Annotation::setDisposeDataFunction(DisposeDataFunction func)
{
Q_D(Annotation);
d->m_disposeFunc = func;
}
bool Annotation::canBeMoved() const
{
Q_D(const Annotation);
// Don't move annotations if they cannot be modified
if (!d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this)) {
return false;
}
// highlight "requires" to be "bounded" to text, and that's tricky for now
if (subType() == AHighlight) {
return false;
}
return true;
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool Annotation::canBeResized() const
{
Q_D(const Annotation);
// Don't resize annotations if they cannot be modified
if (!d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this)) {
return false;
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
return d->canBeResized();
}
void Annotation::store(QDomNode &annNode, QDomDocument &document) const
{
Q_D(const Annotation);
// create [base] element of the annotation node
2015-10-29 12:37:11 +00:00
QDomElement e = document.createElement(QStringLiteral("base"));
annNode.appendChild(e);
// store -contents- attributes
if (!d->m_author.isEmpty()) {
2015-10-29 12:37:11 +00:00
e.setAttribute(QStringLiteral("author"), d->m_author);
}
if (!d->m_contents.isEmpty()) {
2015-10-29 12:37:11 +00:00
e.setAttribute(QStringLiteral("contents"), d->m_contents);
}
if (!d->m_uniqueName.isEmpty()) {
2015-10-29 12:37:11 +00:00
e.setAttribute(QStringLiteral("uniqueName"), d->m_uniqueName);
}
if (d->m_modifyDate.isValid()) {
2015-10-29 12:37:11 +00:00
e.setAttribute(QStringLiteral("modifyDate"), d->m_modifyDate.toString(Qt::ISODate));
}
if (d->m_creationDate.isValid()) {
2015-10-29 12:37:11 +00:00
e.setAttribute(QStringLiteral("creationDate"), d->m_creationDate.toString(Qt::ISODate));
}
// store -other- attributes
if (d->m_flags) { // Strip internal flags
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
e.setAttribute(QStringLiteral("flags"), d->m_flags & ~(External | ExternallyDrawn | BeingMoved | BeingResized));
}
if (d->m_style.color().isValid()) {
e.setAttribute(QStringLiteral("color"), d->m_style.color().name(QColor::HexArgb));
}
if (d->m_style.opacity() != 1.0) {
2015-10-29 12:37:11 +00:00
e.setAttribute(QStringLiteral("opacity"), QString::number(d->m_style.opacity()));
}
// Sub-Node-1 - boundary
2015-10-29 12:37:11 +00:00
QDomElement bE = document.createElement(QStringLiteral("boundary"));
e.appendChild(bE);
2015-10-29 12:37:11 +00:00
bE.setAttribute(QStringLiteral("l"), QString::number(d->m_boundary.left));
bE.setAttribute(QStringLiteral("t"), QString::number(d->m_boundary.top));
bE.setAttribute(QStringLiteral("r"), QString::number(d->m_boundary.right));
bE.setAttribute(QStringLiteral("b"), QString::number(d->m_boundary.bottom));
// Sub-Node-2 - penStyle
if (d->m_style.width() != 1 || d->m_style.lineStyle() != Solid || d->m_style.xCorners() != 0 || d->m_style.yCorners() != 0.0 || d->m_style.marks() != 3 || d->m_style.spaces() != 0) {
2015-10-29 12:37:11 +00:00
QDomElement psE = document.createElement(QStringLiteral("penStyle"));
e.appendChild(psE);
2015-10-29 12:37:11 +00:00
psE.setAttribute(QStringLiteral("width"), QString::number(d->m_style.width()));
psE.setAttribute(QStringLiteral("style"), (int)d->m_style.lineStyle());
psE.setAttribute(QStringLiteral("xcr"), QString::number(d->m_style.xCorners()));
psE.setAttribute(QStringLiteral("ycr"), QString::number(d->m_style.yCorners()));
psE.setAttribute(QStringLiteral("marks"), d->m_style.marks());
psE.setAttribute(QStringLiteral("spaces"), d->m_style.spaces());
}
// Sub-Node-3 - penEffect
if (d->m_style.lineEffect() != NoEffect || d->m_style.effectIntensity() != 1.0) {
2015-10-29 12:37:11 +00:00
QDomElement peE = document.createElement(QStringLiteral("penEffect"));
e.appendChild(peE);
2015-10-29 12:37:11 +00:00
peE.setAttribute(QStringLiteral("effect"), (int)d->m_style.lineEffect());
peE.setAttribute(QStringLiteral("intensity"), QString::number(d->m_style.effectIntensity()));
}
// Sub-Node-4 - window
if (d->m_window.flags() != -1 || !d->m_window.title().isEmpty() || !d->m_window.summary().isEmpty()) {
2015-10-29 12:37:11 +00:00
QDomElement wE = document.createElement(QStringLiteral("window"));
e.appendChild(wE);
2015-10-29 12:37:11 +00:00
wE.setAttribute(QStringLiteral("flags"), d->m_window.flags());
wE.setAttribute(QStringLiteral("top"), QString::number(d->m_window.topLeft().x));
wE.setAttribute(QStringLiteral("left"), QString::number(d->m_window.topLeft().y));
wE.setAttribute(QStringLiteral("width"), d->m_window.width());
wE.setAttribute(QStringLiteral("height"), d->m_window.height());
wE.setAttribute(QStringLiteral("title"), d->m_window.title());
wE.setAttribute(QStringLiteral("summary"), d->m_window.summary());
}
// create [revision] element of the annotation node (if any)
if (d->m_revisions.isEmpty()) {
return;
}
// add all revisions as children of revisions element
QLinkedList<Revision>::const_iterator it = d->m_revisions.begin(), end = d->m_revisions.end();
for (; it != end; ++it) {
// create revision element
const Revision &revision = *it;
2015-10-29 12:37:11 +00:00
QDomElement r = document.createElement(QStringLiteral("revision"));
annNode.appendChild(r);
// set element attributes
2015-10-29 12:37:11 +00:00
r.setAttribute(QStringLiteral("revScope"), (int)revision.scope());
r.setAttribute(QStringLiteral("revType"), (int)revision.type());
// use revision as the annotation element, so fill it up
AnnotationUtils::storeAnnotation(revision.annotation(), r, document);
}
}
2013-04-05 22:28:39 +00:00
QDomNode Annotation::getAnnotationPropertiesDomNode() const
{
2015-10-29 12:37:11 +00:00
QDomDocument doc(QStringLiteral("documentInfo"));
QDomElement node = doc.createElement(QStringLiteral("annotation"));
store(node, doc);
return node;
}
void Annotation::setAnnotationProperties(const QDomNode &node)
{
// Save off internal properties that aren't contained in node
Okular::PagePrivate *p = d_ptr->m_page;
QVariant nativeID = d_ptr->m_nativeId;
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
const int internalFlags = d_ptr->m_flags & (External | ExternallyDrawn | BeingMoved | BeingResized);
Annotation::DisposeDataFunction disposeFunc = d_ptr->m_disposeFunc;
// Replace AnnotationPrivate object with a fresh copy
AnnotationPrivate *new_d_ptr = d_ptr->getNewAnnotationPrivate();
delete (d_ptr);
d_ptr = new_d_ptr;
// Set the annotations properties from node
d_ptr->setAnnotationProperties(node);
// Restore internal properties
d_ptr->m_page = p;
d_ptr->m_nativeId = nativeID;
d_ptr->m_flags = d_ptr->m_flags | internalFlags;
d_ptr->m_disposeFunc = disposeFunc;
// Transform annotation to current page rotation
d_ptr->transform(d_ptr->m_page->rotationMatrix());
}
double AnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
{
return m_transformedBoundary.distanceSqr(x, y, xScale, yScale);
}
void AnnotationPrivate::annotationTransform(const QTransform &matrix)
{
resetTransformation();
transform(matrix);
}
void AnnotationPrivate::transform(const QTransform &matrix)
{
m_transformedBoundary.transform(matrix);
}
void AnnotationPrivate::baseTransform(const QTransform &matrix)
{
m_boundary.transform(matrix);
}
void AnnotationPrivate::resetTransformation()
{
m_transformedBoundary = m_boundary;
}
void AnnotationPrivate::translate(const NormalizedPoint &coord)
{
m_boundary.left = m_boundary.left + coord.x;
m_boundary.right = m_boundary.right + coord.x;
m_boundary.top = m_boundary.top + coord.y;
m_boundary.bottom = m_boundary.bottom + coord.y;
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
void AnnotationPrivate::adjust(const NormalizedPoint &deltaCoord1, const NormalizedPoint &deltaCoord2)
{
m_boundary.left = m_boundary.left + qBound(-m_boundary.left, deltaCoord1.x, m_boundary.right - m_boundary.left);
m_boundary.top = m_boundary.top + qBound(-m_boundary.top, deltaCoord1.y, m_boundary.bottom - m_boundary.top);
;
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
m_boundary.right = m_boundary.right + qBound(m_boundary.left - m_boundary.right, deltaCoord2.x, 1. - m_boundary.right);
m_boundary.bottom = m_boundary.bottom + qBound(m_boundary.top - m_boundary.bottom, deltaCoord2.y, 1. - m_boundary.bottom);
}
bool AnnotationPrivate::openDialogAfterCreation() const
{
return false;
}
void AnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
// get the [base] element of the annotation node
2015-10-29 12:37:11 +00:00
QDomElement e = AnnotationUtils::findChildElement(node, QStringLiteral("base"));
if (e.isNull()) {
return;
}
// parse -contents- attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("author"))) {
m_author = e.attribute(QStringLiteral("author"));
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("contents"))) {
m_contents = e.attribute(QStringLiteral("contents"));
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("uniqueName"))) {
m_uniqueName = e.attribute(QStringLiteral("uniqueName"));
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("modifyDate"))) {
m_modifyDate = QDateTime::fromString(e.attribute(QStringLiteral("modifyDate")), Qt::ISODate);
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("creationDate"))) {
m_creationDate = QDateTime::fromString(e.attribute(QStringLiteral("creationDate")), Qt::ISODate);
}
// parse -other- attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("flags"))) {
m_flags = e.attribute(QStringLiteral("flags")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("color"))) {
m_style.setColor(QColor(e.attribute(QStringLiteral("color"))));
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("opacity"))) {
m_style.setOpacity(e.attribute(QStringLiteral("opacity")).toDouble());
}
// parse -the-subnodes- (describing Style, Window, Revision(s) structures)
// Note: all subnodes if present must be 'attributes complete'
QDomNode eSubNode = e.firstChild();
while (eSubNode.isElement()) {
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
// parse boundary
2015-10-29 12:37:11 +00:00
if (ee.tagName() == QLatin1String("boundary")) {
m_boundary = NormalizedRect(ee.attribute(QStringLiteral("l")).toDouble(), ee.attribute(QStringLiteral("t")).toDouble(), ee.attribute(QStringLiteral("r")).toDouble(), ee.attribute(QStringLiteral("b")).toDouble());
}
// parse penStyle if not default
2015-10-29 12:37:11 +00:00
else if (ee.tagName() == QLatin1String("penStyle")) {
m_style.setWidth(ee.attribute(QStringLiteral("width")).toDouble());
m_style.setLineStyle((Annotation::LineStyle)ee.attribute(QStringLiteral("style")).toInt());
m_style.setXCorners(ee.attribute(QStringLiteral("xcr")).toDouble());
m_style.setYCorners(ee.attribute(QStringLiteral("ycr")).toDouble());
m_style.setMarks(ee.attribute(QStringLiteral("marks")).toInt());
m_style.setSpaces(ee.attribute(QStringLiteral("spaces")).toInt());
}
// parse effectStyle if not default
2015-10-29 12:37:11 +00:00
else if (ee.tagName() == QLatin1String("penEffect")) {
m_style.setLineEffect((Annotation::LineEffect)ee.attribute(QStringLiteral("effect")).toInt());
m_style.setEffectIntensity(ee.attribute(QStringLiteral("intensity")).toDouble());
}
// parse window if present
2015-10-29 12:37:11 +00:00
else if (ee.tagName() == QLatin1String("window")) {
m_window.setFlags(ee.attribute(QStringLiteral("flags")).toInt());
m_window.setTopLeft(NormalizedPoint(ee.attribute(QStringLiteral("top")).toDouble(), ee.attribute(QStringLiteral("left")).toDouble()));
m_window.setWidth(ee.attribute(QStringLiteral("width")).toInt());
m_window.setHeight(ee.attribute(QStringLiteral("height")).toInt());
m_window.setTitle(ee.attribute(QStringLiteral("title")));
m_window.setSummary(ee.attribute(QStringLiteral("summary")));
}
}
// get the [revisions] element of the annotation node
QDomNode revNode = node.firstChild();
for (; revNode.isElement(); revNode = revNode.nextSibling()) {
QDomElement revElement = revNode.toElement();
2015-10-29 12:37:11 +00:00
if (revElement.tagName() != QLatin1String("revision")) {
continue;
}
// compile the Revision structure crating annotation
Annotation::Revision revision;
2015-10-29 12:37:11 +00:00
revision.setScope((Annotation::RevisionScope)revElement.attribute(QStringLiteral("revScope")).toInt());
revision.setType((Annotation::RevisionType)revElement.attribute(QStringLiteral("revType")).toInt());
revision.setAnnotation(AnnotationUtils::createAnnotation(revElement));
// if annotation is valid, add revision to internal list
if (revision.annotation()) {
m_revisions.append(revision);
}
}
m_transformedBoundary = m_boundary;
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool AnnotationPrivate::canBeResized() const
{
return false;
}
// END Annotation implementation
/** TextAnnotation [Annotation] */
class Okular::TextAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
TextAnnotationPrivate()
: AnnotationPrivate()
, m_textType(TextAnnotation::Linked)
2015-10-29 12:37:11 +00:00
, m_textIcon(QStringLiteral("Comment"))
, m_inplaceAlign(0)
, m_inplaceIntent(TextAnnotation::Unknown)
{
}
void transform(const QTransform &matrix) override;
void baseTransform(const QTransform &matrix) override;
void resetTransformation() override;
void translate(const NormalizedPoint &coord) override;
bool openDialogAfterCreation() const override;
void setAnnotationProperties(const QDomNode &node) override;
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool canBeResized() const override;
AnnotationPrivate *getNewAnnotationPrivate() override;
TextAnnotation::TextType m_textType;
QString m_textIcon;
QFont m_textFont;
QColor m_textColor;
int m_inplaceAlign;
NormalizedPoint m_inplaceCallout[3];
NormalizedPoint m_transformedInplaceCallout[3];
TextAnnotation::InplaceIntent m_inplaceIntent;
};
/*
The default textIcon for text annotation is Note as the PDF Reference says
*/
TextAnnotation::TextAnnotation()
: Annotation(*new TextAnnotationPrivate())
{
}
TextAnnotation::TextAnnotation(const QDomNode &description)
: Annotation(*new TextAnnotationPrivate(), description)
{
}
TextAnnotation::~TextAnnotation()
{
}
void TextAnnotation::setTextType(TextType textType)
{
Q_D(TextAnnotation);
d->m_textType = textType;
}
TextAnnotation::TextType TextAnnotation::textType() const
{
Q_D(const TextAnnotation);
return d->m_textType;
}
void TextAnnotation::setTextIcon(const QString &icon)
{
Q_D(TextAnnotation);
d->m_textIcon = icon;
}
QString TextAnnotation::textIcon() const
{
Q_D(const TextAnnotation);
return d->m_textIcon;
}
void TextAnnotation::setTextFont(const QFont &font)
{
Q_D(TextAnnotation);
d->m_textFont = font;
}
QFont TextAnnotation::textFont() const
{
Q_D(const TextAnnotation);
return d->m_textFont;
}
void TextAnnotation::setTextColor(const QColor &color)
{
Q_D(TextAnnotation);
d->m_textColor = color;
}
QColor TextAnnotation::textColor() const
{
Q_D(const TextAnnotation);
return d->m_textColor;
}
void TextAnnotation::setInplaceAlignment(int alignment)
{
Q_D(TextAnnotation);
d->m_inplaceAlign = alignment;
}
int TextAnnotation::inplaceAlignment() const
{
Q_D(const TextAnnotation);
return d->m_inplaceAlign;
}
void TextAnnotation::setInplaceCallout(const NormalizedPoint &point, int index)
{
if (index < 0 || index > 2) {
return;
}
Q_D(TextAnnotation);
d->m_inplaceCallout[index] = point;
}
NormalizedPoint TextAnnotation::inplaceCallout(int index) const
{
if (index < 0 || index > 2) {
return NormalizedPoint();
}
Q_D(const TextAnnotation);
return d->m_inplaceCallout[index];
}
NormalizedPoint TextAnnotation::transformedInplaceCallout(int index) const
{
if (index < 0 || index > 2) {
return NormalizedPoint();
}
Q_D(const TextAnnotation);
return d->m_transformedInplaceCallout[index];
}
void TextAnnotation::setInplaceIntent(InplaceIntent intent)
{
Q_D(TextAnnotation);
d->m_inplaceIntent = intent;
}
TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const
{
Q_D(const TextAnnotation);
return d->m_inplaceIntent;
}
Annotation::SubType TextAnnotation::subType() const
{
return AText;
}
void TextAnnotation::store(QDomNode &node, QDomDocument &document) const
{
Q_D(const TextAnnotation);
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [text] element
2015-10-29 12:37:11 +00:00
QDomElement textElement = document.createElement(QStringLiteral("text"));
node.appendChild(textElement);
// store the optional attributes
if (d->m_textType != Linked) {
2015-10-29 12:37:11 +00:00
textElement.setAttribute(QStringLiteral("type"), (int)d->m_textType);
}
if (!d->m_textIcon.isEmpty()) {
2015-10-29 12:37:11 +00:00
textElement.setAttribute(QStringLiteral("icon"), d->m_textIcon);
}
if (d->m_textFont != QApplication::font()) {
2015-10-29 12:37:11 +00:00
textElement.setAttribute(QStringLiteral("font"), d->m_textFont.toString());
}
if (d->m_textColor.isValid()) {
textElement.setAttribute(QStringLiteral("fontColor"), d->m_textColor.name());
}
if (d->m_inplaceAlign) {
2015-10-29 12:37:11 +00:00
textElement.setAttribute(QStringLiteral("align"), d->m_inplaceAlign);
}
if (d->m_inplaceIntent != Unknown) {
2015-10-29 12:37:11 +00:00
textElement.setAttribute(QStringLiteral("intent"), (int)d->m_inplaceIntent);
}
// Sub-Node - callout
if (d->m_inplaceCallout[0].x != 0.0) {
2015-10-29 12:37:11 +00:00
QDomElement calloutElement = document.createElement(QStringLiteral("callout"));
textElement.appendChild(calloutElement);
2015-10-29 12:37:11 +00:00
calloutElement.setAttribute(QStringLiteral("ax"), QString::number(d->m_inplaceCallout[0].x));
calloutElement.setAttribute(QStringLiteral("ay"), QString::number(d->m_inplaceCallout[0].y));
calloutElement.setAttribute(QStringLiteral("bx"), QString::number(d->m_inplaceCallout[1].x));
calloutElement.setAttribute(QStringLiteral("by"), QString::number(d->m_inplaceCallout[1].y));
calloutElement.setAttribute(QStringLiteral("cx"), QString::number(d->m_inplaceCallout[2].x));
calloutElement.setAttribute(QStringLiteral("cy"), QString::number(d->m_inplaceCallout[2].y));
}
}
void TextAnnotationPrivate::transform(const QTransform &matrix)
{
AnnotationPrivate::transform(matrix);
for (NormalizedPoint &np : m_transformedInplaceCallout) {
np.transform(matrix);
}
}
void TextAnnotationPrivate::baseTransform(const QTransform &matrix)
{
AnnotationPrivate::baseTransform(matrix);
for (NormalizedPoint &np : m_inplaceCallout) {
np.transform(matrix);
}
}
void TextAnnotationPrivate::resetTransformation()
{
AnnotationPrivate::resetTransformation();
for (int i = 0; i < 3; ++i) {
m_transformedInplaceCallout[i] = m_inplaceCallout[i];
}
}
void TextAnnotationPrivate::translate(const NormalizedPoint &coord)
{
AnnotationPrivate::translate(coord);
#define ADD_COORD(c1, c2) \
{ \
c1.x = c1.x + c2.x; \
c1.y = c1.y + c2.y; \
}
ADD_COORD(m_inplaceCallout[0], coord)
ADD_COORD(m_inplaceCallout[1], coord)
ADD_COORD(m_inplaceCallout[2], coord)
#undef ADD_COORD
}
bool TextAnnotationPrivate::openDialogAfterCreation() const
{
return (m_textType == Okular::TextAnnotation::Linked) || (m_inplaceIntent == TextAnnotation::InplaceIntent::Unknown);
}
void TextAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'text' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("text")) {
continue;
}
// parse the attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("type"))) {
m_textType = (TextAnnotation::TextType)e.attribute(QStringLiteral("type")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("icon"))) {
m_textIcon = e.attribute(QStringLiteral("icon"));
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("font"))) {
m_textFont.fromString(e.attribute(QStringLiteral("font")));
}
if (e.hasAttribute(QStringLiteral("fontColor"))) {
m_textColor = QColor(e.attribute(QStringLiteral("fontColor")));
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("align"))) {
m_inplaceAlign = e.attribute(QStringLiteral("align")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("intent"))) {
m_inplaceIntent = (TextAnnotation::InplaceIntent)e.attribute(QStringLiteral("intent")).toInt();
}
// parse the subnodes
QDomNode eSubNode = e.firstChild();
while (eSubNode.isElement()) {
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (ee.tagName() == QLatin1String("escapedText")) {
m_contents = ee.firstChild().toCDATASection().data();
2015-10-29 12:37:11 +00:00
} else if (ee.tagName() == QLatin1String("callout")) {
m_inplaceCallout[0].x = ee.attribute(QStringLiteral("ax")).toDouble();
m_inplaceCallout[0].y = ee.attribute(QStringLiteral("ay")).toDouble();
m_inplaceCallout[1].x = ee.attribute(QStringLiteral("bx")).toDouble();
m_inplaceCallout[1].y = ee.attribute(QStringLiteral("by")).toDouble();
m_inplaceCallout[2].x = ee.attribute(QStringLiteral("cx")).toDouble();
m_inplaceCallout[2].y = ee.attribute(QStringLiteral("cy")).toDouble();
}
}
// loading complete
break;
}
for (int i = 0; i < 3; ++i) {
m_transformedInplaceCallout[i] = m_inplaceCallout[i];
}
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool TextAnnotationPrivate::canBeResized() const
{
if (m_textType != TextAnnotation::Linked) {
return true;
}
return false;
}
AnnotationPrivate *TextAnnotationPrivate::getNewAnnotationPrivate()
{
return new TextAnnotationPrivate();
}
/** LineAnnotation [Annotation] */
class Okular::LineAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
LineAnnotationPrivate()
: AnnotationPrivate()
, m_lineStartStyle(LineAnnotation::None)
, m_lineEndStyle(LineAnnotation::None)
, m_lineClosed(false)
, m_lineShowCaption(false)
, m_lineLeadingFwdPt(0)
, m_lineLeadingBackPt(0)
, m_lineIntent(LineAnnotation::Unknown)
{
}
void transform(const QTransform &matrix) override;
void baseTransform(const QTransform &matrix) override;
void resetTransformation() override;
void translate(const NormalizedPoint &coord) override;
double distanceSqr(double x, double y, double xScale, double yScale) const override;
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
QList<NormalizedPoint> m_linePoints;
QList<NormalizedPoint> m_transformedLinePoints;
LineAnnotation::TermStyle m_lineStartStyle;
LineAnnotation::TermStyle m_lineEndStyle;
bool m_lineClosed : 1;
bool m_lineShowCaption : 1;
QColor m_lineInnerColor;
double m_lineLeadingFwdPt;
double m_lineLeadingBackPt;
LineAnnotation::LineIntent m_lineIntent;
};
LineAnnotation::LineAnnotation()
: Annotation(*new LineAnnotationPrivate())
{
}
LineAnnotation::LineAnnotation(const QDomNode &description)
: Annotation(*new LineAnnotationPrivate(), description)
{
}
LineAnnotation::~LineAnnotation()
{
}
void LineAnnotation::setLinePoints(const QList<NormalizedPoint> &points)
{
Q_D(LineAnnotation);
d->m_linePoints = points;
}
QList<NormalizedPoint> LineAnnotation::linePoints() const
{
Q_D(const LineAnnotation);
return d->m_linePoints;
}
QList<NormalizedPoint> LineAnnotation::transformedLinePoints() const
{
Q_D(const LineAnnotation);
return d->m_transformedLinePoints;
}
void LineAnnotation::setLineStartStyle(TermStyle style)
{
Q_D(LineAnnotation);
d->m_lineStartStyle = style;
}
LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const
{
Q_D(const LineAnnotation);
return d->m_lineStartStyle;
}
void LineAnnotation::setLineEndStyle(TermStyle style)
{
Q_D(LineAnnotation);
d->m_lineEndStyle = style;
}
LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const
{
Q_D(const LineAnnotation);
return d->m_lineEndStyle;
}
void LineAnnotation::setLineClosed(bool closed)
{
Q_D(LineAnnotation);
d->m_lineClosed = closed;
}
bool LineAnnotation::lineClosed() const
{
Q_D(const LineAnnotation);
return d->m_lineClosed;
}
void LineAnnotation::setLineInnerColor(const QColor &color)
{
Q_D(LineAnnotation);
d->m_lineInnerColor = color;
}
QColor LineAnnotation::lineInnerColor() const
{
Q_D(const LineAnnotation);
return d->m_lineInnerColor;
}
void LineAnnotation::setLineLeadingForwardPoint(double point)
{
Q_D(LineAnnotation);
d->m_lineLeadingFwdPt = point;
}
double LineAnnotation::lineLeadingForwardPoint() const
{
Q_D(const LineAnnotation);
return d->m_lineLeadingFwdPt;
}
void LineAnnotation::setLineLeadingBackwardPoint(double point)
{
Q_D(LineAnnotation);
d->m_lineLeadingBackPt = point;
}
double LineAnnotation::lineLeadingBackwardPoint() const
{
Q_D(const LineAnnotation);
return d->m_lineLeadingBackPt;
}
void LineAnnotation::setShowCaption(bool show)
{
Q_D(LineAnnotation);
d->m_lineShowCaption = show;
}
bool LineAnnotation::showCaption() const
{
Q_D(const LineAnnotation);
return d->m_lineShowCaption;
}
void LineAnnotation::setLineIntent(LineIntent intent)
{
Q_D(LineAnnotation);
d->m_lineIntent = intent;
}
LineAnnotation::LineIntent LineAnnotation::lineIntent() const
{
Q_D(const LineAnnotation);
return d->m_lineIntent;
}
Annotation::SubType LineAnnotation::subType() const
{
return ALine;
}
void LineAnnotation::store(QDomNode &node, QDomDocument &document) const
{
Q_D(const LineAnnotation);
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [line] element
2015-10-29 12:37:11 +00:00
QDomElement lineElement = document.createElement(QStringLiteral("line"));
node.appendChild(lineElement);
// store the attributes
if (d->m_lineStartStyle != None) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("startStyle"), (int)d->m_lineStartStyle);
}
if (d->m_lineEndStyle != None) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("endStyle"), (int)d->m_lineEndStyle);
}
if (d->m_lineClosed) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("closed"), d->m_lineClosed);
}
if (d->m_lineInnerColor.isValid()) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("innerColor"), d->m_lineInnerColor.name());
}
if (d->m_lineLeadingFwdPt != 0.0) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("leadFwd"), QString::number(d->m_lineLeadingFwdPt));
}
if (d->m_lineLeadingBackPt != 0.0) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("leadBack"), QString::number(d->m_lineLeadingBackPt));
}
if (d->m_lineShowCaption) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("showCaption"), d->m_lineShowCaption);
}
if (d->m_lineIntent != Unknown) {
2015-10-29 12:37:11 +00:00
lineElement.setAttribute(QStringLiteral("intent"), d->m_lineIntent);
}
// append the list of points
int points = d->m_linePoints.count();
if (points > 1) {
QList<NormalizedPoint>::const_iterator it = d->m_linePoints.begin(), end = d->m_linePoints.end();
while (it != end) {
const NormalizedPoint &p = *it;
2015-10-29 12:37:11 +00:00
QDomElement pElement = document.createElement(QStringLiteral("point"));
lineElement.appendChild(pElement);
2015-10-29 12:37:11 +00:00
pElement.setAttribute(QStringLiteral("x"), QString::number(p.x));
pElement.setAttribute(QStringLiteral("y"), QString::number(p.y));
it++; // to avoid loop
}
}
}
void LineAnnotationPrivate::transform(const QTransform &matrix)
{
AnnotationPrivate::transform(matrix);
QMutableListIterator<NormalizedPoint> it(m_transformedLinePoints);
while (it.hasNext()) {
it.next().transform(matrix);
}
}
void LineAnnotationPrivate::baseTransform(const QTransform &matrix)
{
AnnotationPrivate::baseTransform(matrix);
QMutableListIterator<NormalizedPoint> it(m_linePoints);
while (it.hasNext()) {
it.next().transform(matrix);
}
}
void LineAnnotationPrivate::resetTransformation()
{
AnnotationPrivate::resetTransformation();
m_transformedLinePoints = m_linePoints;
}
void LineAnnotationPrivate::translate(const NormalizedPoint &coord)
{
AnnotationPrivate::translate(coord);
QMutableListIterator<NormalizedPoint> it(m_linePoints);
while (it.hasNext()) {
NormalizedPoint &p = it.next();
p.x = p.x + coord.x;
p.y = p.y + coord.y;
}
}
void LineAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'line' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("line")) {
continue;
}
// parse the attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("startStyle"))) {
m_lineStartStyle = (LineAnnotation::TermStyle)e.attribute(QStringLiteral("startStyle")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("endStyle"))) {
m_lineEndStyle = (LineAnnotation::TermStyle)e.attribute(QStringLiteral("endStyle")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("closed"))) {
m_lineClosed = e.attribute(QStringLiteral("closed")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("innerColor"))) {
m_lineInnerColor = QColor(e.attribute(QStringLiteral("innerColor")));
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("leadFwd"))) {
m_lineLeadingFwdPt = e.attribute(QStringLiteral("leadFwd")).toDouble();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("leadBack"))) {
m_lineLeadingBackPt = e.attribute(QStringLiteral("leadBack")).toDouble();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("showCaption"))) {
m_lineShowCaption = e.attribute(QStringLiteral("showCaption")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("intent"))) {
m_lineIntent = (LineAnnotation::LineIntent)e.attribute(QStringLiteral("intent")).toInt();
}
// parse all 'point' subnodes
QDomNode pointNode = e.firstChild();
while (pointNode.isElement()) {
QDomElement pe = pointNode.toElement();
pointNode = pointNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (pe.tagName() != QLatin1String("point")) {
continue;
}
NormalizedPoint p;
2015-10-29 12:37:11 +00:00
p.x = pe.attribute(QStringLiteral("x"), QStringLiteral("0.0")).toDouble();
p.y = pe.attribute(QStringLiteral("y"), QStringLiteral("0.0")).toDouble();
m_linePoints.append(p);
}
// loading complete
break;
}
m_transformedLinePoints = m_linePoints;
}
AnnotationPrivate *LineAnnotationPrivate::getNewAnnotationPrivate()
{
return new LineAnnotationPrivate();
}
double LineAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
{
QList<NormalizedPoint> transformedLinePoints = m_transformedLinePoints;
if (m_lineClosed) { // Close the path
transformedLinePoints.append(transformedLinePoints.first());
}
if (m_lineInnerColor.isValid()) {
QPolygonF polygon;
for (const NormalizedPoint &p : qAsConst(transformedLinePoints)) {
polygon.append(QPointF(p.x, p.y));
}
if (polygon.containsPoint(QPointF(x, y), Qt::WindingFill)) {
return 0;
}
}
return strokeDistance(::distanceSqr(x, y, xScale, yScale, transformedLinePoints), m_style.width() * xScale / (m_page->m_width * 2));
}
/** GeomAnnotation [Annotation] */
class Okular::GeomAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
GeomAnnotationPrivate()
: AnnotationPrivate()
, m_geomType(GeomAnnotation::InscribedSquare)
{
}
void setAnnotationProperties(const QDomNode &node) override;
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool canBeResized() const override;
AnnotationPrivate *getNewAnnotationPrivate() override;
double distanceSqr(double x, double y, double xScale, double yScale) const override;
GeomAnnotation::GeomType m_geomType;
QColor m_geomInnerColor;
};
GeomAnnotation::GeomAnnotation()
: Annotation(*new GeomAnnotationPrivate())
{
}
GeomAnnotation::GeomAnnotation(const QDomNode &description)
: Annotation(*new GeomAnnotationPrivate(), description)
{
}
GeomAnnotation::~GeomAnnotation()
{
}
void GeomAnnotation::setGeometricalType(GeomType type)
{
Q_D(GeomAnnotation);
d->m_geomType = type;
}
GeomAnnotation::GeomType GeomAnnotation::geometricalType() const
{
Q_D(const GeomAnnotation);
return d->m_geomType;
}
void GeomAnnotation::setGeometricalInnerColor(const QColor &color)
{
Q_D(GeomAnnotation);
d->m_geomInnerColor = color;
}
QColor GeomAnnotation::geometricalInnerColor() const
{
Q_D(const GeomAnnotation);
return d->m_geomInnerColor;
}
Annotation::SubType GeomAnnotation::subType() const
{
return AGeom;
}
void GeomAnnotation::store(QDomNode &node, QDomDocument &document) const
{
Q_D(const GeomAnnotation);
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [geom] element
2015-10-29 12:37:11 +00:00
QDomElement geomElement = document.createElement(QStringLiteral("geom"));
node.appendChild(geomElement);
// append the optional attributes
if (d->m_geomType != InscribedSquare) {
2015-10-29 12:37:11 +00:00
geomElement.setAttribute(QStringLiteral("type"), (int)d->m_geomType);
}
if (d->m_geomInnerColor.isValid()) {
2015-10-29 12:37:11 +00:00
geomElement.setAttribute(QStringLiteral("color"), d->m_geomInnerColor.name());
}
}
void GeomAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'geom' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("geom")) {
continue;
}
// parse the attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("type"))) {
m_geomType = (GeomAnnotation::GeomType)e.attribute(QStringLiteral("type")).toInt();
}
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("color"))) {
m_geomInnerColor = QColor(e.attribute(QStringLiteral("color")));
}
// compatibility
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("width"))) {
m_style.setWidth(e.attribute(QStringLiteral("width")).toInt());
}
// loading complete
break;
}
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool GeomAnnotationPrivate::canBeResized() const
{
return true;
}
AnnotationPrivate *GeomAnnotationPrivate::getNewAnnotationPrivate()
{
return new GeomAnnotationPrivate();
}
double GeomAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
{
double distance = 0;
// the line thickness is applied unevenly (only on the "inside") - account for this
bool withinShape = false;
switch (m_geomType) {
case GeomAnnotation::InscribedCircle: {
// calculate the center point and focus lengths of the ellipse
const double centerX = (m_transformedBoundary.left + m_transformedBoundary.right) / 2.0;
const double centerY = (m_transformedBoundary.top + m_transformedBoundary.bottom) / 2.0;
const double focusX = (m_transformedBoundary.right - centerX);
const double focusY = (m_transformedBoundary.bottom - centerY);
const double focusXSqr = pow(focusX, 2);
const double focusYSqr = pow(focusY, 2);
// to calculate the distance from the ellipse, we will first find the point "projection"
// that lies on the ellipse and is closest to the point (x,y)
// This point can obviously be written as "center + lambda(inputPoint - center)".
// Because the point lies on the ellipse, we know that:
// 1 = ((center.x - projection.x)/focusX)^2 + ((center.y - projection.y)/focusY)^2
// After filling in projection.x = center.x + lambda * (inputPoint.x - center.x)
// and its y-equivalent, we can solve for lambda:
const double lambda = sqrt(focusXSqr * focusYSqr / (focusYSqr * pow(x - centerX, 2) + focusXSqr * pow(y - centerY, 2)));
// if the ellipse is filled, we treat all points within as "on" it
if (lambda > 1) {
if (m_geomInnerColor.isValid()) {
return 0;
} else {
withinShape = true;
}
}
// otherwise we calculate the squared distance from the projected point on the ellipse
NormalizedPoint projection(centerX, centerY);
projection.x += lambda * (x - centerX);
projection.y += lambda * (y - centerY);
distance = projection.distanceSqr(x, y, xScale, yScale);
break;
}
case GeomAnnotation::InscribedSquare:
// if the square is filled, only check the bounding box
if (m_geomInnerColor.isValid()) {
return AnnotationPrivate::distanceSqr(x, y, xScale, yScale);
}
const QList<NormalizedPoint> edges = {NormalizedPoint(m_transformedBoundary.left, m_transformedBoundary.top),
NormalizedPoint(m_transformedBoundary.right, m_transformedBoundary.top),
NormalizedPoint(m_transformedBoundary.right, m_transformedBoundary.bottom),
NormalizedPoint(m_transformedBoundary.left, m_transformedBoundary.bottom),
NormalizedPoint(m_transformedBoundary.left, m_transformedBoundary.top)};
distance = ::distanceSqr(x, y, xScale, yScale, edges);
if (m_transformedBoundary.contains(x, y)) {
withinShape = true;
}
break;
}
if (withinShape) {
distance = strokeDistance(distance, m_style.width() * xScale / m_page->m_width);
}
return distance;
}
/** HighlightAnnotation [Annotation] */
class HighlightAnnotation::Quad::Private
{
public:
Private()
{
}
NormalizedPoint m_points[4];
NormalizedPoint m_transformedPoints[4];
bool m_capStart : 1;
bool m_capEnd : 1;
double m_feather;
};
HighlightAnnotation::Quad::Quad()
: d(new Private)
{
}
HighlightAnnotation::Quad::~Quad()
{
delete d;
}
HighlightAnnotation::Quad::Quad(const Quad &other)
: d(new Private)
{
*d = *other.d;
}
HighlightAnnotation::Quad &HighlightAnnotation::Quad::operator=(const Quad &other)
{
if (this != &other) {
*d = *other.d;
}
return *this;
}
void HighlightAnnotation::Quad::setPoint(const NormalizedPoint &point, int index)
{
if (index < 0 || index > 3) {
return;
}
d->m_points[index] = point;
}
NormalizedPoint HighlightAnnotation::Quad::point(int index) const
{
if (index < 0 || index > 3) {
return NormalizedPoint();
}
return d->m_points[index];
}
NormalizedPoint HighlightAnnotation::Quad::transformedPoint(int index) const
{
if (index < 0 || index > 3) {
return NormalizedPoint();
}
return d->m_transformedPoints[index];
}
void HighlightAnnotation::Quad::setCapStart(bool value)
{
d->m_capStart = value;
}
bool HighlightAnnotation::Quad::capStart() const
{
return d->m_capStart;
}
void HighlightAnnotation::Quad::setCapEnd(bool value)
{
d->m_capEnd = value;
}
bool HighlightAnnotation::Quad::capEnd() const
{
return d->m_capEnd;
}
void HighlightAnnotation::Quad::setFeather(double width)
{
d->m_feather = width;
}
double HighlightAnnotation::Quad::feather() const
{
return d->m_feather;
}
void HighlightAnnotation::Quad::transform(const QTransform &matrix)
{
for (int i = 0; i < 4; ++i) {
d->m_transformedPoints[i] = d->m_points[i];
d->m_transformedPoints[i].transform(matrix);
}
}
class Okular::HighlightAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
HighlightAnnotationPrivate()
: AnnotationPrivate()
, m_highlightType(HighlightAnnotation::Highlight)
{
}
void transform(const QTransform &matrix) override;
void baseTransform(const QTransform &matrix) override;
double distanceSqr(double x, double y, double xScale, double yScale) const override;
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
HighlightAnnotation::HighlightType m_highlightType;
QList<HighlightAnnotation::Quad> m_highlightQuads;
};
HighlightAnnotation::HighlightAnnotation()
: Annotation(*new HighlightAnnotationPrivate())
{
}
HighlightAnnotation::HighlightAnnotation(const QDomNode &description)
: Annotation(*new HighlightAnnotationPrivate(), description)
{
}
HighlightAnnotation::~HighlightAnnotation()
{
}
void HighlightAnnotation::setHighlightType(HighlightType type)
{
Q_D(HighlightAnnotation);
d->m_highlightType = type;
}
HighlightAnnotation::HighlightType HighlightAnnotation::highlightType() const
{
Q_D(const HighlightAnnotation);
return d->m_highlightType;
}
QList<HighlightAnnotation::Quad> &HighlightAnnotation::highlightQuads()
{
Q_D(HighlightAnnotation);
return d->m_highlightQuads;
}
const QList<HighlightAnnotation::Quad> &HighlightAnnotation::highlightQuads() const
{
Q_D(const HighlightAnnotation);
return d->m_highlightQuads;
}
void HighlightAnnotation::store(QDomNode &node, QDomDocument &document) const
{
Q_D(const HighlightAnnotation);
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [hl] element
2015-10-29 12:37:11 +00:00
QDomElement hlElement = document.createElement(QStringLiteral("hl"));
node.appendChild(hlElement);
// append the optional attributes
if (d->m_highlightType != Highlight) {
2015-10-29 12:37:11 +00:00
hlElement.setAttribute(QStringLiteral("type"), (int)d->m_highlightType);
}
if (d->m_highlightQuads.count() < 1) {
return;
}
// append highlight quads, all children describe quads
QList<Quad>::const_iterator it = d->m_highlightQuads.begin(), end = d->m_highlightQuads.end();
for (; it != end; ++it) {
2015-10-29 12:37:11 +00:00
QDomElement quadElement = document.createElement(QStringLiteral("quad"));
hlElement.appendChild(quadElement);
const Quad &q = *it;
2015-10-29 12:37:11 +00:00
quadElement.setAttribute(QStringLiteral("ax"), QString::number(q.point(0).x));
quadElement.setAttribute(QStringLiteral("ay"), QString::number(q.point(0).y));
quadElement.setAttribute(QStringLiteral("bx"), QString::number(q.point(1).x));
quadElement.setAttribute(QStringLiteral("by"), QString::number(q.point(1).y));
quadElement.setAttribute(QStringLiteral("cx"), QString::number(q.point(2).x));
quadElement.setAttribute(QStringLiteral("cy"), QString::number(q.point(2).y));
quadElement.setAttribute(QStringLiteral("dx"), QString::number(q.point(3).x));
quadElement.setAttribute(QStringLiteral("dy"), QString::number(q.point(3).y));
if (q.capStart()) {
2015-10-29 12:37:11 +00:00
quadElement.setAttribute(QStringLiteral("start"), 1);
}
if (q.capEnd()) {
2015-10-29 12:37:11 +00:00
quadElement.setAttribute(QStringLiteral("end"), 1);
}
2015-10-29 12:37:11 +00:00
quadElement.setAttribute(QStringLiteral("feather"), QString::number(q.feather()));
}
}
Annotation::SubType HighlightAnnotation::subType() const
{
return AHighlight;
}
void HighlightAnnotationPrivate::transform(const QTransform &matrix)
{
AnnotationPrivate::transform(matrix);
QMutableListIterator<HighlightAnnotation::Quad> it(m_highlightQuads);
while (it.hasNext()) {
it.next().transform(matrix);
}
}
void HighlightAnnotationPrivate::baseTransform(const QTransform &matrix)
{
AnnotationPrivate::baseTransform(matrix);
QMutableListIterator<HighlightAnnotation::Quad> it(m_highlightQuads);
while (it.hasNext()) {
it.next().transform(matrix);
}
}
void HighlightAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
m_highlightQuads.clear();
// loop through the whole children looking for a 'hl' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("hl")) {
continue;
}
// parse the attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("type"))) {
m_highlightType = (HighlightAnnotation::HighlightType)e.attribute(QStringLiteral("type")).toInt();
}
// parse all 'quad' subnodes
QDomNode quadNode = e.firstChild();
for (; quadNode.isElement(); quadNode = quadNode.nextSibling()) {
QDomElement qe = quadNode.toElement();
2015-10-29 12:37:11 +00:00
if (qe.tagName() != QLatin1String("quad")) {
continue;
}
HighlightAnnotation::Quad q;
2015-10-29 12:37:11 +00:00
q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("ax"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("ay"), QStringLiteral("0.0")).toDouble()), 0);
q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("bx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("by"), QStringLiteral("0.0")).toDouble()), 1);
q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("cx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("cy"), QStringLiteral("0.0")).toDouble()), 2);
q.setPoint(NormalizedPoint(qe.attribute(QStringLiteral("dx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("dy"), QStringLiteral("0.0")).toDouble()), 3);
q.setCapStart(qe.hasAttribute(QStringLiteral("start")));
q.setCapEnd(qe.hasAttribute(QStringLiteral("end")));
q.setFeather(qe.attribute(QStringLiteral("feather"), QStringLiteral("0.1")).toDouble());
q.transform(QTransform());
m_highlightQuads.append(q);
}
// loading complete
break;
}
}
AnnotationPrivate *HighlightAnnotationPrivate::getNewAnnotationPrivate()
{
return new HighlightAnnotationPrivate();
}
double HighlightAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
{
NormalizedPoint point(x, y);
double outsideDistance = DBL_MAX;
for (const HighlightAnnotation::Quad &quad : m_highlightQuads) {
QList<NormalizedPoint> pathPoints;
// first, we check if the point is within the area described by the 4 quads
// this is the case, if the point is always on one side of each segments delimiting the polygon:
pathPoints << quad.transformedPoint(0);
int directionVote = 0;
for (int i = 1; i < 5; ++i) {
NormalizedPoint thisPoint = quad.transformedPoint(i % 4);
directionVote += (isLeftOfVector(pathPoints.back(), thisPoint, point)) ? 1 : -1;
pathPoints << thisPoint;
}
if (abs(directionVote) == 4) {
return 0;
}
// if that's not the case, we treat the outline as path and simply determine
// the distance from the path to the point
const double thisOutsideDistance = ::distanceSqr(x, y, xScale, yScale, pathPoints);
if (thisOutsideDistance < outsideDistance) {
outsideDistance = thisOutsideDistance;
}
}
return outsideDistance;
}
/** StampAnnotation [Annotation] */
class Okular::StampAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
StampAnnotationPrivate()
2015-10-29 12:37:11 +00:00
: AnnotationPrivate()
, m_stampIconName(QStringLiteral("Draft"))
{
}
void setAnnotationProperties(const QDomNode &node) override;
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool canBeResized() const override;
AnnotationPrivate *getNewAnnotationPrivate() override;
QString m_stampIconName;
};
StampAnnotation::StampAnnotation()
: Annotation(*new StampAnnotationPrivate())
{
}
StampAnnotation::StampAnnotation(const QDomNode &description)
: Annotation(*new StampAnnotationPrivate(), description)
{
}
StampAnnotation::~StampAnnotation()
{
}
void StampAnnotation::setStampIconName(const QString &name)
{
Q_D(StampAnnotation);
d->m_stampIconName = name;
}
QString StampAnnotation::stampIconName() const
{
Q_D(const StampAnnotation);
return d->m_stampIconName;
}
Annotation::SubType StampAnnotation::subType() const
{
return AStamp;
}
void StampAnnotation::store(QDomNode &node, QDomDocument &document) const
{
Q_D(const StampAnnotation);
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [stamp] element
2015-10-29 12:37:11 +00:00
QDomElement stampElement = document.createElement(QStringLiteral("stamp"));
node.appendChild(stampElement);
// append the optional attributes
2015-10-29 12:37:11 +00:00
if (d->m_stampIconName != QLatin1String("Draft")) {
stampElement.setAttribute(QStringLiteral("icon"), d->m_stampIconName);
}
}
void StampAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'stamp' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("stamp")) {
continue;
}
// parse the attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("icon"))) {
m_stampIconName = e.attribute(QStringLiteral("icon"));
}
// loading complete
break;
}
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
bool StampAnnotationPrivate::canBeResized() const
{
return true;
}
AnnotationPrivate *StampAnnotationPrivate::getNewAnnotationPrivate()
{
return new StampAnnotationPrivate();
}
/** InkAnnotation [Annotation] */
class Okular::InkAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
InkAnnotationPrivate()
: AnnotationPrivate()
{
}
void transform(const QTransform &matrix) override;
void baseTransform(const QTransform &matrix) override;
void resetTransformation() override;
double distanceSqr(double x, double y, double xScale, double yScale) const override;
void translate(const NormalizedPoint &coord) override;
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
QList<QList<NormalizedPoint>> m_inkPaths;
QList<QList<NormalizedPoint>> m_transformedInkPaths;
};
InkAnnotation::InkAnnotation()
: Annotation(*new InkAnnotationPrivate())
{
}
InkAnnotation::InkAnnotation(const QDomNode &description)
: Annotation(*new InkAnnotationPrivate(), description)
{
}
InkAnnotation::~InkAnnotation()
{
}
void InkAnnotation::setInkPaths(const QList<QList<NormalizedPoint>> &paths)
{
Q_D(InkAnnotation);
d->m_inkPaths = paths;
}
QList<QList<NormalizedPoint>> InkAnnotation::inkPaths() const
{
Q_D(const InkAnnotation);
return d->m_inkPaths;
}
QList<QList<NormalizedPoint>> InkAnnotation::transformedInkPaths() const
{
Q_D(const InkAnnotation);
return d->m_transformedInkPaths;
}
Annotation::SubType InkAnnotation::subType() const
{
return AInk;
}
void InkAnnotation::store(QDomNode &node, QDomDocument &document) const
{
Q_D(const InkAnnotation);
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [ink] element
2015-10-29 12:37:11 +00:00
QDomElement inkElement = document.createElement(QStringLiteral("ink"));
node.appendChild(inkElement);
// append the optional attributes
if (d->m_inkPaths.count() < 1) {
return;
}
QList<QList<NormalizedPoint>>::const_iterator pIt = d->m_inkPaths.begin(), pEnd = d->m_inkPaths.end();
for (; pIt != pEnd; ++pIt) {
2015-10-29 12:37:11 +00:00
QDomElement pathElement = document.createElement(QStringLiteral("path"));
inkElement.appendChild(pathElement);
const QList<NormalizedPoint> &path = *pIt;
for (const NormalizedPoint &point : path) {
2015-10-29 12:37:11 +00:00
QDomElement pointElement = document.createElement(QStringLiteral("point"));
pathElement.appendChild(pointElement);
2015-10-29 12:37:11 +00:00
pointElement.setAttribute(QStringLiteral("x"), QString::number(point.x));
pointElement.setAttribute(QStringLiteral("y"), QString::number(point.y));
}
}
}
double InkAnnotationPrivate::distanceSqr(double x, double y, double xScale, double yScale) const
{
double distance = DBL_MAX;
for (const QList<NormalizedPoint> &path : m_transformedInkPaths) {
const double thisDistance = ::distanceSqr(x, y, xScale, yScale, path);
if (thisDistance < distance) {
distance = thisDistance;
}
}
return strokeDistance(distance, m_style.width() * xScale / (m_page->m_width * 2));
}
void InkAnnotationPrivate::transform(const QTransform &matrix)
{
AnnotationPrivate::transform(matrix);
for (int i = 0; i < m_transformedInkPaths.count(); ++i) {
QMutableListIterator<NormalizedPoint> it(m_transformedInkPaths[i]);
while (it.hasNext()) {
it.next().transform(matrix);
}
}
}
void InkAnnotationPrivate::baseTransform(const QTransform &matrix)
{
AnnotationPrivate::baseTransform(matrix);
for (int i = 0; i < m_inkPaths.count(); ++i) {
QMutableListIterator<NormalizedPoint> it(m_inkPaths[i]);
while (it.hasNext()) {
it.next().transform(matrix);
}
}
}
void InkAnnotationPrivate::resetTransformation()
{
AnnotationPrivate::resetTransformation();
m_transformedInkPaths = m_inkPaths;
}
void InkAnnotationPrivate::translate(const NormalizedPoint &coord)
{
AnnotationPrivate::translate(coord);
for (int i = 0; i < m_inkPaths.count(); ++i) {
QMutableListIterator<NormalizedPoint> it(m_inkPaths[i]);
while (it.hasNext()) {
NormalizedPoint &p = it.next();
p.x = p.x + coord.x;
p.y = p.y + coord.y;
}
}
}
void InkAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
m_inkPaths.clear();
// loop through the whole children looking for a 'ink' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("ink")) {
continue;
}
// parse the 'path' subnodes
QDomNode pathNode = e.firstChild();
while (pathNode.isElement()) {
QDomElement pathElement = pathNode.toElement();
pathNode = pathNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (pathElement.tagName() != QLatin1String("path")) {
continue;
}
// build each path parsing 'point' subnodes
QList<NormalizedPoint> path;
QDomNode pointNode = pathElement.firstChild();
while (pointNode.isElement()) {
QDomElement pointElement = pointNode.toElement();
pointNode = pointNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (pointElement.tagName() != QLatin1String("point")) {
continue;
}
NormalizedPoint p;
2015-10-29 12:37:11 +00:00
p.x = pointElement.attribute(QStringLiteral("x"), QStringLiteral("0.0")).toDouble();
p.y = pointElement.attribute(QStringLiteral("y"), QStringLiteral("0.0")).toDouble();
path.append(p);
}
// add the path to the path list if it contains at least 2 nodes
if (path.count() >= 2) {
m_inkPaths.append(path);
}
}
// loading complete
break;
}
m_transformedInkPaths = m_inkPaths;
}
AnnotationPrivate *InkAnnotationPrivate::getNewAnnotationPrivate()
{
return new InkAnnotationPrivate();
}
/** CaretAnnotation [Annotation] */
class Okular::CaretAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
CaretAnnotationPrivate()
: AnnotationPrivate()
, m_symbol(CaretAnnotation::None)
{
}
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
CaretAnnotation::CaretSymbol m_symbol;
};
static QString caretSymbolToString(CaretAnnotation::CaretSymbol symbol)
{
switch (symbol) {
case CaretAnnotation::None:
2015-10-29 12:37:11 +00:00
return QStringLiteral("None");
case CaretAnnotation::P:
2015-10-29 12:37:11 +00:00
return QStringLiteral("P");
}
return QString();
}
static CaretAnnotation::CaretSymbol caretSymbolFromString(const QString &symbol)
{
if (symbol == QLatin1String("None")) {
return CaretAnnotation::None;
} else if (symbol == QLatin1String("P")) {
return CaretAnnotation::P;
}
return CaretAnnotation::None;
}
void CaretAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'caret' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("caret")) {
continue;
}
// parse the attributes
2015-10-29 12:37:11 +00:00
if (e.hasAttribute(QStringLiteral("symbol"))) {
m_symbol = caretSymbolFromString(e.attribute(QStringLiteral("symbol")));
}
// loading complete
break;
}
}
AnnotationPrivate *CaretAnnotationPrivate::getNewAnnotationPrivate()
{
return new CaretAnnotationPrivate();
}
CaretAnnotation::CaretAnnotation()
: Annotation(*new CaretAnnotationPrivate())
{
}
CaretAnnotation::CaretAnnotation(const QDomNode &description)
: Annotation(*new CaretAnnotationPrivate(), description)
{
}
CaretAnnotation::~CaretAnnotation()
{
}
void CaretAnnotation::setCaretSymbol(CaretAnnotation::CaretSymbol symbol)
{
Q_D(CaretAnnotation);
d->m_symbol = symbol;
}
CaretAnnotation::CaretSymbol CaretAnnotation::caretSymbol() const
{
Q_D(const CaretAnnotation);
return d->m_symbol;
}
Annotation::SubType CaretAnnotation::subType() const
{
return ACaret;
}
void CaretAnnotation::store(QDomNode &node, QDomDocument &document) const
{
Q_D(const CaretAnnotation);
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [caret] element
2015-10-29 12:37:11 +00:00
QDomElement caretElement = document.createElement(QStringLiteral("caret"));
node.appendChild(caretElement);
// append the optional attributes
if (d->m_symbol != None) {
2015-10-29 12:37:11 +00:00
caretElement.setAttribute(QStringLiteral("symbol"), caretSymbolToString(d->m_symbol));
}
}
/** FileAttachmentAnnotation [Annotation] */
class Okular::FileAttachmentAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
FileAttachmentAnnotationPrivate()
: AnnotationPrivate()
, icon(QStringLiteral("PushPin"))
, embfile(nullptr)
{
}
~FileAttachmentAnnotationPrivate() override
{
delete embfile;
}
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
// data fields
QString icon;
EmbeddedFile *embfile;
};
void FileAttachmentAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'fileattachment' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("fileattachment")) {
continue;
}
// loading complete
break;
}
}
AnnotationPrivate *FileAttachmentAnnotationPrivate::getNewAnnotationPrivate()
{
return new FileAttachmentAnnotationPrivate();
}
FileAttachmentAnnotation::FileAttachmentAnnotation()
: Annotation(*new FileAttachmentAnnotationPrivate())
{
}
FileAttachmentAnnotation::FileAttachmentAnnotation(const QDomNode &description)
: Annotation(*new FileAttachmentAnnotationPrivate(), description)
{
}
FileAttachmentAnnotation::~FileAttachmentAnnotation()
{
}
void FileAttachmentAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [fileattachment] element
2015-10-29 12:37:11 +00:00
QDomElement fileAttachmentElement = document.createElement(QStringLiteral("fileattachment"));
node.appendChild(fileAttachmentElement);
}
Annotation::SubType FileAttachmentAnnotation::subType() const
{
return AFileAttachment;
}
QString FileAttachmentAnnotation::fileIconName() const
{
Q_D(const FileAttachmentAnnotation);
return d->icon;
}
void FileAttachmentAnnotation::setFileIconName(const QString &iconName)
{
Q_D(FileAttachmentAnnotation);
d->icon = iconName;
}
EmbeddedFile *FileAttachmentAnnotation::embeddedFile() const
{
Q_D(const FileAttachmentAnnotation);
return d->embfile;
}
void FileAttachmentAnnotation::setEmbeddedFile(EmbeddedFile *ef)
{
Q_D(FileAttachmentAnnotation);
d->embfile = ef;
}
/** SoundAnnotation [Annotation] */
class Okular::SoundAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
SoundAnnotationPrivate()
: AnnotationPrivate()
, icon(QStringLiteral("Speaker"))
, sound(nullptr)
{
}
~SoundAnnotationPrivate() override
{
delete sound;
}
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
// data fields
QString icon;
Sound *sound;
};
void SoundAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'sound' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("sound")) {
continue;
}
// loading complete
break;
}
}
AnnotationPrivate *SoundAnnotationPrivate::getNewAnnotationPrivate()
{
return new SoundAnnotationPrivate();
}
SoundAnnotation::SoundAnnotation()
: Annotation(*new SoundAnnotationPrivate())
{
}
SoundAnnotation::SoundAnnotation(const QDomNode &description)
: Annotation(*new SoundAnnotationPrivate(), description)
{
}
SoundAnnotation::~SoundAnnotation()
{
}
void SoundAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [sound] element
2015-10-29 12:37:11 +00:00
QDomElement soundElement = document.createElement(QStringLiteral("sound"));
node.appendChild(soundElement);
}
Annotation::SubType SoundAnnotation::subType() const
{
return ASound;
}
QString SoundAnnotation::soundIconName() const
{
Q_D(const SoundAnnotation);
return d->icon;
}
void SoundAnnotation::setSoundIconName(const QString &iconName)
{
Q_D(SoundAnnotation);
d->icon = iconName;
}
Sound *SoundAnnotation::sound() const
{
Q_D(const SoundAnnotation);
return d->sound;
}
void SoundAnnotation::setSound(Sound *s)
{
Q_D(SoundAnnotation);
d->sound = s;
}
/** MovieAnnotation [Annotation] */
class Okular::MovieAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
MovieAnnotationPrivate()
: AnnotationPrivate()
, movie(nullptr)
{
}
~MovieAnnotationPrivate() override
{
delete movie;
}
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
// data fields
Movie *movie;
};
void MovieAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'movie' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("movie")) {
continue;
}
// loading complete
break;
}
}
AnnotationPrivate *MovieAnnotationPrivate::getNewAnnotationPrivate()
{
return new MovieAnnotationPrivate();
}
MovieAnnotation::MovieAnnotation()
: Annotation(*new MovieAnnotationPrivate())
{
}
MovieAnnotation::MovieAnnotation(const QDomNode &description)
: Annotation(*new MovieAnnotationPrivate(), description)
{
}
MovieAnnotation::~MovieAnnotation()
{
}
void MovieAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [movie] element
2015-10-29 12:37:11 +00:00
QDomElement movieElement = document.createElement(QStringLiteral("movie"));
node.appendChild(movieElement);
}
Annotation::SubType MovieAnnotation::subType() const
{
return AMovie;
}
Movie *MovieAnnotation::movie() const
{
Q_D(const MovieAnnotation);
return d->movie;
}
void MovieAnnotation::setMovie(Movie *movie)
{
Q_D(MovieAnnotation);
d->movie = movie;
}
/** ScreenAnnotation [Annotation] */
class Okular::ScreenAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
ScreenAnnotationPrivate();
~ScreenAnnotationPrivate() override;
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
Okular::Action *m_action;
QMap<Okular::Annotation::AdditionalActionType, Okular::Action *> m_additionalActions;
};
ScreenAnnotationPrivate::ScreenAnnotationPrivate()
: m_action(nullptr)
{
}
ScreenAnnotationPrivate::~ScreenAnnotationPrivate()
{
delete m_action;
qDeleteAll(m_additionalActions);
}
void ScreenAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'screen' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("screen")) {
continue;
}
// loading complete
break;
}
}
AnnotationPrivate *ScreenAnnotationPrivate::getNewAnnotationPrivate()
{
return new ScreenAnnotationPrivate();
}
ScreenAnnotation::ScreenAnnotation()
: Annotation(*new ScreenAnnotationPrivate())
{
}
ScreenAnnotation::ScreenAnnotation(const QDomNode &description)
: Annotation(*new ScreenAnnotationPrivate(), description)
{
}
ScreenAnnotation::~ScreenAnnotation()
{
}
void ScreenAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [screen] element
2015-10-29 12:37:11 +00:00
QDomElement movieElement = document.createElement(QStringLiteral("screen"));
node.appendChild(movieElement);
}
Annotation::SubType ScreenAnnotation::subType() const
{
return AScreen;
}
void ScreenAnnotation::setAdditionalAction(AdditionalActionType type, Action *action)
{
Q_D(ScreenAnnotation);
if (d->m_additionalActions.contains(type)) {
delete d->m_additionalActions.value(type);
}
d->m_additionalActions.insert(type, action);
}
Action *ScreenAnnotation::additionalAction(AdditionalActionType type) const
{
Q_D(const ScreenAnnotation);
if (!d->m_additionalActions.contains(type)) {
return nullptr;
} else {
return d->m_additionalActions.value(type);
}
}
void ScreenAnnotation::setAction(Action *action)
{
Q_D(ScreenAnnotation);
delete d->m_action;
d->m_action = action;
}
Action *ScreenAnnotation::action() const
{
Q_D(const ScreenAnnotation);
return d->m_action;
}
/** WidgetAnnotation [Annotation] */
class Okular::WidgetAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
~WidgetAnnotationPrivate() override;
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
QMap<Okular::Annotation::AdditionalActionType, Okular::Action *> m_additionalActions;
};
WidgetAnnotationPrivate::~WidgetAnnotationPrivate()
{
qDeleteAll(m_additionalActions);
}
void WidgetAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'widget' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("widget")) {
continue;
}
// loading complete
break;
}
}
AnnotationPrivate *WidgetAnnotationPrivate::getNewAnnotationPrivate()
{
return new WidgetAnnotationPrivate();
}
WidgetAnnotation::WidgetAnnotation()
: Annotation(*new WidgetAnnotationPrivate())
{
}
WidgetAnnotation::WidgetAnnotation(const QDomNode &description)
: Annotation(*new WidgetAnnotationPrivate, description)
{
}
WidgetAnnotation::~WidgetAnnotation()
{
}
void WidgetAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [widget] element
2015-10-29 12:37:11 +00:00
QDomElement movieElement = document.createElement(QStringLiteral("widget"));
node.appendChild(movieElement);
}
Annotation::SubType WidgetAnnotation::subType() const
{
return AWidget;
}
void WidgetAnnotation::setAdditionalAction(AdditionalActionType type, Action *action)
{
Q_D(WidgetAnnotation);
if (d->m_additionalActions.contains(type)) {
delete d->m_additionalActions.value(type);
}
d->m_additionalActions.insert(type, action);
}
Action *WidgetAnnotation::additionalAction(AdditionalActionType type) const
{
Q_D(const WidgetAnnotation);
if (!d->m_additionalActions.contains(type)) {
return nullptr;
} else {
return d->m_additionalActions.value(type);
}
}
/** RichMediaAnnotation [Annotation] */
class Okular::RichMediaAnnotationPrivate : public Okular::AnnotationPrivate
{
public:
RichMediaAnnotationPrivate();
~RichMediaAnnotationPrivate() override;
void setAnnotationProperties(const QDomNode &node) override;
AnnotationPrivate *getNewAnnotationPrivate() override;
// data fields
Movie *movie;
EmbeddedFile *embeddedFile;
};
RichMediaAnnotationPrivate::RichMediaAnnotationPrivate()
: movie(nullptr)
, embeddedFile(nullptr)
{
}
RichMediaAnnotationPrivate::~RichMediaAnnotationPrivate()
{
delete movie;
delete embeddedFile;
}
void RichMediaAnnotationPrivate::setAnnotationProperties(const QDomNode &node)
{
Okular::AnnotationPrivate::setAnnotationProperties(node);
// loop through the whole children looking for a 'richMedia' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
2015-10-29 12:37:11 +00:00
if (e.tagName() != QLatin1String("richMedia")) {
continue;
}
// loading complete
break;
}
}
AnnotationPrivate *RichMediaAnnotationPrivate::getNewAnnotationPrivate()
{
return new RichMediaAnnotationPrivate();
}
RichMediaAnnotation::RichMediaAnnotation()
: Annotation(*new RichMediaAnnotationPrivate())
{
}
RichMediaAnnotation::RichMediaAnnotation(const QDomNode &description)
: Annotation(*new RichMediaAnnotationPrivate, description)
{
}
RichMediaAnnotation::~RichMediaAnnotation()
{
}
void RichMediaAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// recurse to parent objects storing properties
Annotation::store(node, document);
// create [richMedia] element
2015-10-29 12:37:11 +00:00
QDomElement movieElement = document.createElement(QStringLiteral("richMedia"));
node.appendChild(movieElement);
}
Annotation::SubType RichMediaAnnotation::subType() const
{
return ARichMedia;
}
void RichMediaAnnotation::setMovie(Movie *movie)
{
Q_D(RichMediaAnnotation);
delete d->movie;
d->movie = movie;
}
Movie *RichMediaAnnotation::movie() const
{
Q_D(const RichMediaAnnotation);
return d->movie;
}
EmbeddedFile *RichMediaAnnotation::embeddedFile() const
{
Q_D(const RichMediaAnnotation);
return d->embeddedFile;
}
void RichMediaAnnotation::setEmbeddedFile(EmbeddedFile *embeddedFile)
{
Q_D(RichMediaAnnotation);
delete d->embeddedFile;
d->embeddedFile = embeddedFile;
}