mirror of
https://invent.kde.org/graphics/okular
synced 2024-08-27 11:40:07 +00:00
1650 lines
63 KiB
C++
1650 lines
63 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "pageviewannotator.h"
|
|
|
|
// qt / kde includes
|
|
#include <KLocalizedString>
|
|
#include <KMessageBox>
|
|
#include <QApplication>
|
|
#include <QColor>
|
|
#include <QEvent>
|
|
#include <QFile>
|
|
#include <QFileDialog>
|
|
#include <QInputDialog>
|
|
#include <QList>
|
|
#include <QLoggingCategory>
|
|
#include <QPainter>
|
|
#include <QSet>
|
|
#include <QVariant>
|
|
|
|
#include <KUser>
|
|
#include <QDebug>
|
|
#include <QMenu>
|
|
|
|
// system includes
|
|
#include <QKeyEvent>
|
|
#include <QStandardPaths>
|
|
#include <QTabletEvent>
|
|
#include <math.h>
|
|
#include <memory>
|
|
|
|
// local includes
|
|
#include "annotationactionhandler.h"
|
|
#include "core/annotations.h"
|
|
#include "core/area.h"
|
|
#include "core/document.h"
|
|
#include "core/page.h"
|
|
#include "core/signatureutils.h"
|
|
#include "editannottooldialog.h"
|
|
#include "gui/debug_ui.h"
|
|
#include "gui/guiutils.h"
|
|
#include "pageview.h"
|
|
#include "settings.h"
|
|
#include "signaturepartutils.h"
|
|
|
|
/** @short PickPointEngine */
|
|
class PickPointEngine : public AnnotatorEngine
|
|
{
|
|
public:
|
|
explicit PickPointEngine(const QDomElement &engineElement)
|
|
: AnnotatorEngine(engineElement)
|
|
, clicked(false)
|
|
, xscale(1.0)
|
|
, yscale(1.0)
|
|
{
|
|
// parse engine specific attributes
|
|
hoverIconName = engineElement.attribute(QStringLiteral("hoverIcon"));
|
|
iconName = m_annotElement.attribute(QStringLiteral("icon"));
|
|
if (m_annotElement.attribute(QStringLiteral("type")) == QLatin1String("Stamp") && !iconName.simplified().isEmpty()) {
|
|
hoverIconName = iconName;
|
|
}
|
|
center = QVariant(engineElement.attribute(QStringLiteral("center"))).toBool();
|
|
bool ok = true;
|
|
size = engineElement.attribute(QStringLiteral("size"), QStringLiteral("32")).toInt(&ok);
|
|
if (!ok) {
|
|
size = 32;
|
|
}
|
|
m_block = QVariant(engineElement.attribute(QStringLiteral("block"))).toBool();
|
|
|
|
// create engine objects
|
|
if (!hoverIconName.simplified().isEmpty()) {
|
|
pixmap = Okular::AnnotationUtils::loadStamp(hoverIconName, size);
|
|
}
|
|
}
|
|
|
|
QRect event(EventType type, Button button, Modifiers modifiers, double nX, double nY, double xScale, double yScale, const Okular::Page *page) override
|
|
{
|
|
xscale = xScale;
|
|
yscale = yScale;
|
|
pagewidth = page->width();
|
|
pageheight = page->height();
|
|
// only proceed if pressing left button
|
|
if (button != Left) {
|
|
return QRect();
|
|
}
|
|
|
|
// start operation on click
|
|
if (type == Press && clicked == false) {
|
|
clicked = true;
|
|
startpoint.x = nX;
|
|
startpoint.y = nY;
|
|
}
|
|
// repaint if moving while pressing
|
|
else if (type == Move && clicked == true) {
|
|
}
|
|
// operation finished on release
|
|
else if (type == Release && clicked == true) {
|
|
m_creationCompleted = true;
|
|
} else {
|
|
return QRect();
|
|
}
|
|
|
|
// Constrain to 1:1 form factor (e.g. circle or square)
|
|
if (modifiers.constrainRatioAndAngle) {
|
|
double side = qMin(qAbs(nX - startpoint.x) * xScale, qAbs(nY - startpoint.y) * yScale);
|
|
nX = qBound(startpoint.x - side / xScale, nX, startpoint.x + side / xScale);
|
|
nY = qBound(startpoint.y - side / yScale, nY, startpoint.y + side / yScale);
|
|
}
|
|
// update variables and extents (zoom invariant rect)
|
|
point.x = nX;
|
|
point.y = nY;
|
|
if (center) {
|
|
rect.left = nX - (size / (xScale * 2.0));
|
|
rect.top = nY - (size / (yScale * 2.0));
|
|
} else {
|
|
rect.left = nX;
|
|
rect.top = nY;
|
|
}
|
|
rect.right = rect.left + size;
|
|
rect.bottom = rect.top + size;
|
|
QRect boundrect = rect.geometry((int)xScale, (int)yScale).adjusted(0, 0, 1, 1);
|
|
if (m_block) {
|
|
const Okular::NormalizedRect tmprect(qMin(startpoint.x, point.x), qMin(startpoint.y, point.y), qMax(startpoint.x, point.x), qMax(startpoint.y, point.y));
|
|
boundrect |= tmprect.geometry((int)xScale, (int)yScale).adjusted(0, 0, 1, 1);
|
|
}
|
|
return boundrect;
|
|
}
|
|
|
|
void paint(QPainter *painter, double xScale, double yScale, const QRect & /*clipRect*/) override
|
|
{
|
|
if (clicked) {
|
|
if (m_block) {
|
|
const QPen origpen = painter->pen();
|
|
QPen pen = painter->pen();
|
|
pen.setStyle(Qt::DashLine);
|
|
painter->setPen(pen);
|
|
const Okular::NormalizedRect tmprect(qMin(startpoint.x, point.x), qMin(startpoint.y, point.y), qMax(startpoint.x, point.x), qMax(startpoint.y, point.y));
|
|
const QRect realrect = tmprect.geometry((int)xScale, (int)yScale);
|
|
painter->drawRect(realrect);
|
|
painter->setPen(origpen);
|
|
}
|
|
if (!pixmap.isNull()) {
|
|
painter->drawPixmap(QPointF(rect.left * xScale, rect.top * yScale), pixmap);
|
|
}
|
|
}
|
|
}
|
|
|
|
void addInPlaceTextAnnotation(Okular::Annotation *&ann, const QString &summary, const QString &content, Okular::TextAnnotation::InplaceIntent inplaceIntent)
|
|
{
|
|
Okular::TextAnnotation *ta = new Okular::TextAnnotation();
|
|
ann = ta;
|
|
ta->setFlags(ta->flags() | Okular::Annotation::FixedRotation);
|
|
ta->setContents(content);
|
|
ta->setTextType(Okular::TextAnnotation::InPlace);
|
|
ta->setInplaceIntent(inplaceIntent);
|
|
// set alignment
|
|
if (m_annotElement.hasAttribute(QStringLiteral("align"))) {
|
|
ta->setInplaceAlignment(m_annotElement.attribute(QStringLiteral("align")).toInt());
|
|
}
|
|
// set font
|
|
if (m_annotElement.hasAttribute(QStringLiteral("font"))) {
|
|
QFont f;
|
|
// Workaround broken old code that saved fonts incorrectly with extra backslashes
|
|
QString fontString = m_annotElement.attribute(QStringLiteral("font"));
|
|
if (fontString.count(QStringLiteral("\\\\,")) > 9) {
|
|
fontString.replace(QStringLiteral("\\\\,"), QStringLiteral(","));
|
|
}
|
|
f.fromString(fontString);
|
|
ta->setTextFont(f);
|
|
}
|
|
// set font color
|
|
if (m_annotElement.hasAttribute(QStringLiteral("textColor"))) {
|
|
if (inplaceIntent == Okular::TextAnnotation::TypeWriter) {
|
|
ta->setTextColor(m_annotElement.attribute(QStringLiteral("textColor")));
|
|
} else {
|
|
ta->setTextColor(Qt::black);
|
|
}
|
|
}
|
|
// set width
|
|
if (m_annotElement.hasAttribute(QStringLiteral("width"))) {
|
|
ta->style().setWidth(m_annotElement.attribute(QStringLiteral("width")).toDouble());
|
|
}
|
|
// set boundary
|
|
rect.left = qMin(startpoint.x, point.x);
|
|
rect.top = qMin(startpoint.y, point.y);
|
|
rect.right = qMax(startpoint.x, point.x);
|
|
rect.bottom = qMax(startpoint.y, point.y);
|
|
qCDebug(OkularUiDebug).nospace() << "xyScale=" << xscale << "," << yscale;
|
|
static const int padding = 2;
|
|
const QFontMetricsF mf(ta->textFont());
|
|
const QRectF rcf =
|
|
mf.boundingRect(Okular::NormalizedRect(rect.left, rect.top, 1.0, 1.0).geometry((int)pagewidth, (int)pageheight).adjusted(padding, padding, -padding, -padding), Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap, ta->contents());
|
|
rect.right = qMax(rect.right, rect.left + (rcf.width() + padding * 2) / pagewidth);
|
|
rect.bottom = qMax(rect.bottom, rect.top + (rcf.height() + padding * 2) / pageheight);
|
|
ta->window().setSummary(summary);
|
|
}
|
|
|
|
QList<Okular::Annotation *> end() override
|
|
{
|
|
// find out annotation's description node
|
|
if (m_annotElement.isNull()) {
|
|
m_creationCompleted = false;
|
|
clicked = false;
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// find out annotation's type
|
|
Okular::Annotation *ann = nullptr;
|
|
const QString typeString = m_annotElement.attribute(QStringLiteral("type"));
|
|
// create InPlace TextAnnotation from path
|
|
if (typeString == QLatin1String("FreeText")) {
|
|
addInPlaceTextAnnotation(ann, i18n("Inline Note"), QString(), Okular::TextAnnotation::Unknown);
|
|
} else if (typeString == QLatin1String("Typewriter")) {
|
|
bool resok;
|
|
const QString content = QInputDialog::getMultiLineText(nullptr, i18n("New Text Note"), i18n("Text of the new note:"), QString(), &resok);
|
|
if (resok) {
|
|
addInPlaceTextAnnotation(ann, i18n("Typewriter"), content, Okular::TextAnnotation::TypeWriter);
|
|
}
|
|
} else if (typeString == QLatin1String("Text")) {
|
|
Okular::TextAnnotation *ta = new Okular::TextAnnotation();
|
|
ann = ta;
|
|
ta->setTextType(Okular::TextAnnotation::Linked);
|
|
ta->setTextIcon(iconName);
|
|
// ta->window.flags &= ~(Okular::Annotation::Hidden);
|
|
const double iconhei = 0.03;
|
|
rect.left = point.x;
|
|
rect.top = point.y;
|
|
rect.right = rect.left + iconhei;
|
|
rect.bottom = rect.top + iconhei * xscale / yscale;
|
|
ta->window().setSummary(i18n("Pop-up Note"));
|
|
}
|
|
// create StampAnnotation from path
|
|
else if (typeString == QLatin1String("Stamp")) {
|
|
Okular::StampAnnotation *sa = new Okular::StampAnnotation();
|
|
ann = sa;
|
|
sa->setStampIconName(iconName);
|
|
// set boundary
|
|
rect.left = qMin(startpoint.x, point.x);
|
|
rect.top = qMin(startpoint.y, point.y);
|
|
rect.right = qMax(startpoint.x, point.x);
|
|
rect.bottom = qMax(startpoint.y, point.y);
|
|
const QRectF rcf = rect.geometry((int)xscale, (int)yscale);
|
|
const int ml = (rcf.bottomRight() - rcf.topLeft()).toPoint().manhattanLength();
|
|
if (ml <= QApplication::startDragDistance()) {
|
|
const double stampxscale = pixmap.width() / xscale;
|
|
const double stampyscale = pixmap.height() / yscale;
|
|
if (center) {
|
|
rect.left = point.x - stampxscale / 2;
|
|
rect.top = point.y - stampyscale / 2;
|
|
} else {
|
|
rect.left = point.x;
|
|
rect.top = point.y;
|
|
}
|
|
rect.right = rect.left + stampxscale;
|
|
rect.bottom = rect.top + stampyscale;
|
|
}
|
|
}
|
|
// create GeomAnnotation
|
|
else if (typeString == QLatin1String("GeomSquare") || typeString == QLatin1String("GeomCircle")) {
|
|
Okular::GeomAnnotation *ga = new Okular::GeomAnnotation();
|
|
ann = ga;
|
|
// set the type
|
|
if (typeString == QLatin1String("GeomSquare")) {
|
|
ga->setGeometricalType(Okular::GeomAnnotation::InscribedSquare);
|
|
} else {
|
|
ga->setGeometricalType(Okular::GeomAnnotation::InscribedCircle);
|
|
}
|
|
if (m_annotElement.hasAttribute(QStringLiteral("width"))) {
|
|
ann->style().setWidth(m_annotElement.attribute(QStringLiteral("width")).toDouble());
|
|
}
|
|
if (m_annotElement.hasAttribute(QStringLiteral("innerColor"))) {
|
|
ga->setGeometricalInnerColor(QColor(m_annotElement.attribute(QStringLiteral("innerColor"))));
|
|
}
|
|
// set boundary
|
|
rect.left = qMin(startpoint.x, point.x);
|
|
rect.top = qMin(startpoint.y, point.y);
|
|
rect.right = qMax(startpoint.x, point.x);
|
|
rect.bottom = qMax(startpoint.y, point.y);
|
|
}
|
|
|
|
m_creationCompleted = false;
|
|
clicked = false;
|
|
|
|
// safety check
|
|
if (!ann) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// set common attributes
|
|
ann->style().setColor(m_annotElement.hasAttribute(QStringLiteral("color")) ? m_annotElement.attribute(QStringLiteral("color")) : m_engineColor);
|
|
if (m_annotElement.hasAttribute(QStringLiteral("opacity"))) {
|
|
ann->style().setOpacity(m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble());
|
|
}
|
|
|
|
// set the bounding rectangle, and make sure that the newly created
|
|
// annotation lies within the page by translating it if necessary
|
|
if (rect.right > 1) {
|
|
rect.left -= rect.right - 1;
|
|
rect.right = 1;
|
|
}
|
|
if (rect.bottom > 1) {
|
|
rect.top -= rect.bottom - 1;
|
|
rect.bottom = 1;
|
|
}
|
|
ann->setBoundingRectangle(rect);
|
|
|
|
// return annotation
|
|
return QList<Okular::Annotation *>() << ann;
|
|
}
|
|
|
|
protected:
|
|
bool clicked;
|
|
bool m_block;
|
|
double xscale, yscale;
|
|
Okular::NormalizedRect rect;
|
|
Okular::NormalizedPoint startpoint;
|
|
Okular::NormalizedPoint point;
|
|
|
|
private:
|
|
QPixmap pixmap;
|
|
QString hoverIconName, iconName;
|
|
int size;
|
|
double pagewidth, pageheight;
|
|
bool center;
|
|
};
|
|
|
|
class PickPointEngineSignature : public PickPointEngine
|
|
{
|
|
public:
|
|
PickPointEngineSignature(Okular::Document *document, PageView *pageView)
|
|
: PickPointEngine({})
|
|
, m_document(document)
|
|
, m_page(nullptr)
|
|
, m_pageView(pageView)
|
|
, m_startOver(false)
|
|
, m_aborted(false)
|
|
{
|
|
m_block = true;
|
|
}
|
|
|
|
QRect event(EventType type, Button button, Modifiers modifiers, double nX, double nY, double xScale, double yScale, const Okular::Page *page) override
|
|
{
|
|
m_page = page;
|
|
return PickPointEngine::event(type, button, modifiers, nX, nY, xScale, yScale, page);
|
|
}
|
|
|
|
QList<Okular::Annotation *> end() override
|
|
{
|
|
m_startOver = false;
|
|
rect.left = qMin(startpoint.x, point.x);
|
|
rect.top = qMin(startpoint.y, point.y);
|
|
rect.right = qMax(startpoint.x, point.x);
|
|
rect.bottom = qMax(startpoint.y, point.y);
|
|
|
|
// FIXME this is a bit arbitrary, try to figure out a better rule, potentially based in cm and not pixels?
|
|
if (rect.width() * m_page->width() < 100 || rect.height() * m_page->height() < 100) {
|
|
const KMessageBox::ButtonCode answer = KMessageBox::questionYesNo(
|
|
m_pageView,
|
|
xi18nc("@info", "A signature of this size may be too small to read. If you would like to create a potentially more readable signature, press <interface>Start over</interface> and draw a bigger rectangle."),
|
|
QString(),
|
|
KGuiItem(i18nc("@action:button", "Start Over")),
|
|
KGuiItem(i18nc("@action:button", "Sign")),
|
|
QStringLiteral("TooSmallDigitalSignatureQuestion"));
|
|
if (answer == KMessageBox::Yes) {
|
|
m_startOver = true;
|
|
return {};
|
|
}
|
|
}
|
|
|
|
const auto signInfo = SignaturePartUtils::getCertificateAndPasswordForSigning(m_pageView, m_document, SignaturePartUtils::SigningInformationOption::BackgroundImage);
|
|
if (!signInfo) {
|
|
m_aborted = true;
|
|
passToUse.clear();
|
|
documentPassword.clear();
|
|
} else {
|
|
certNicknameToUse = signInfo->certificate->nickName();
|
|
certCommonName = signInfo->certificate->subjectInfo(Okular::CertificateInfo::CommonName, Okular::CertificateInfo::EmptyString::TranslatedNotAvailable);
|
|
passToUse = signInfo->certificatePassword;
|
|
documentPassword = signInfo->documentPassword;
|
|
reason = signInfo->reason;
|
|
location = signInfo->location;
|
|
backgroundImagePath = signInfo->backgroundImagePath;
|
|
}
|
|
|
|
m_creationCompleted = false;
|
|
clicked = false;
|
|
|
|
return {};
|
|
}
|
|
|
|
bool isAccepted() const
|
|
{
|
|
return !m_aborted && !certNicknameToUse.isEmpty();
|
|
}
|
|
|
|
bool userWantsToStartOver() const
|
|
{
|
|
return m_startOver;
|
|
}
|
|
|
|
bool isAborted() const
|
|
{
|
|
return m_aborted;
|
|
}
|
|
|
|
bool sign(const QString &newFilePath)
|
|
{
|
|
Okular::NewSignatureData data;
|
|
data.setCertNickname(certNicknameToUse);
|
|
data.setCertSubjectCommonName(certCommonName);
|
|
data.setPassword(passToUse);
|
|
data.setDocumentPassword(documentPassword);
|
|
data.setPage(m_page->number());
|
|
data.setBoundingRectangle(rect);
|
|
data.setReason(reason);
|
|
data.setLocation(location);
|
|
data.setBackgroundImagePath(backgroundImagePath);
|
|
passToUse.clear();
|
|
documentPassword.clear();
|
|
return m_document->sign(data, newFilePath);
|
|
}
|
|
|
|
private:
|
|
QString certNicknameToUse;
|
|
QString certCommonName;
|
|
QString passToUse;
|
|
QString documentPassword;
|
|
QString location;
|
|
QString backgroundImagePath;
|
|
QString reason;
|
|
|
|
Okular::Document *m_document;
|
|
const Okular::Page *m_page;
|
|
PageView *m_pageView;
|
|
|
|
bool m_startOver;
|
|
bool m_aborted;
|
|
};
|
|
|
|
/** @short PolyLineEngine */
|
|
class PolyLineEngine : public AnnotatorEngine
|
|
{
|
|
public:
|
|
explicit PolyLineEngine(const QDomElement &engineElement)
|
|
: AnnotatorEngine(engineElement)
|
|
, last(false)
|
|
{
|
|
// parse engine specific attributes
|
|
m_block = engineElement.attribute(QStringLiteral("block")) == QLatin1String("true");
|
|
bool ok = true;
|
|
// numofpoints represents the max number of points for the current
|
|
// polygon/polyline, with a pair of exceptions:
|
|
// -1 means: the polyline must close on the first point (polygon)
|
|
// 0 means: construct as many points as you want, right-click
|
|
// to construct the last point
|
|
numofpoints = engineElement.attribute(QStringLiteral("points")).toInt(&ok);
|
|
if (!ok) {
|
|
numofpoints = -1;
|
|
}
|
|
}
|
|
|
|
static Okular::NormalizedPoint constrainAngle(const Okular::NormalizedPoint &p1, double x, double y, double xScale, double yScale, double angleIncrement)
|
|
{
|
|
// given the normalized point (x, y), return the closest point such that the line segment from p1 forms an angle
|
|
// with the horizontal axis which is an integer multiple of angleIncrement on a reference area of size xScale x yScale
|
|
double dist = sqrt(p1.distanceSqr(x, y, xScale, yScale));
|
|
double angle = atan2((y - p1.y) * yScale, (x - p1.x) * xScale);
|
|
double constrainedAngle = round(angle / angleIncrement) * angleIncrement;
|
|
double offset = dist * sin(angle - constrainedAngle);
|
|
x += offset * sin(constrainedAngle) / xScale;
|
|
y -= offset * cos(constrainedAngle) / yScale;
|
|
return Okular::NormalizedPoint(x, y);
|
|
}
|
|
|
|
QRect event(EventType type, Button button, Modifiers modifiers, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/) override
|
|
{
|
|
// only proceed if pressing left button
|
|
// if ( button != Left )
|
|
// return rect;
|
|
|
|
// Constrain to 15° steps, except first point of course.
|
|
if (modifiers.constrainRatioAndAngle && !points.isEmpty()) {
|
|
const Okular::NormalizedPoint constrainedPoint = constrainAngle(points.constLast(), nX, nY, xScale, yScale, M_PI / 12.);
|
|
nX = constrainedPoint.x;
|
|
nY = constrainedPoint.y;
|
|
}
|
|
// process button press
|
|
if (type == Press) {
|
|
newPoint.x = nX;
|
|
newPoint.y = nY;
|
|
if (button == Right) {
|
|
last = true;
|
|
}
|
|
}
|
|
// move the second point
|
|
else if (type == Move) {
|
|
movingpoint.x = nX;
|
|
movingpoint.y = nY;
|
|
const QRect oldmovingrect = movingrect;
|
|
movingrect = rect | QRect((int)(movingpoint.x * xScale), (int)(movingpoint.y * yScale), 1, 1);
|
|
return oldmovingrect | movingrect;
|
|
} else if (type == Release) {
|
|
const Okular::NormalizedPoint tmppoint(nX, nY);
|
|
if (fabs(tmppoint.x - newPoint.x) + fabs(tmppoint.y - newPoint.y) > 1e-2) {
|
|
return rect;
|
|
}
|
|
|
|
if (numofpoints == -1 && points.count() > 1 && (fabs(points[0].x - newPoint.x) + fabs(points[0].y - newPoint.y) < 1e-2)) {
|
|
last = true;
|
|
} else {
|
|
points.append(newPoint);
|
|
rect |= QRect((int)(newPoint.x * xScale), (int)(newPoint.y * yScale), 1, 1);
|
|
}
|
|
// end creation if we have constructed the last point of enough points
|
|
if (last || points.count() == numofpoints) {
|
|
m_creationCompleted = true;
|
|
last = false;
|
|
normRect = Okular::NormalizedRect(rect, xScale, yScale);
|
|
}
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
void paint(QPainter *painter, double xScale, double yScale, const QRect & /*clipRect*/) override
|
|
{
|
|
if (points.count() < 1) {
|
|
return;
|
|
}
|
|
|
|
if (m_block && points.count() == 2) {
|
|
const Okular::NormalizedPoint first = points[0];
|
|
const Okular::NormalizedPoint second = points[1];
|
|
// draw a semitransparent block around the 2 points
|
|
painter->setPen(m_engineColor);
|
|
painter->setBrush(QBrush(m_engineColor.lighter(), Qt::Dense4Pattern));
|
|
painter->drawRect((int)(first.x * (double)xScale), (int)(first.y * (double)yScale), (int)((second.x - first.x) * (double)xScale), (int)((second.y - first.y) * (double)yScale));
|
|
} else {
|
|
// draw a polyline that connects the constructed points
|
|
painter->setPen(QPen(m_engineColor, 2));
|
|
for (int i = 1; i < points.count(); ++i) {
|
|
painter->drawLine((int)(points[i - 1].x * (double)xScale), (int)(points[i - 1].y * (double)yScale), (int)(points[i].x * (double)xScale), (int)(points[i].y * (double)yScale));
|
|
}
|
|
painter->drawLine((int)(points.last().x * (double)xScale), (int)(points.last().y * (double)yScale), (int)(movingpoint.x * (double)xScale), (int)(movingpoint.y * (double)yScale));
|
|
}
|
|
}
|
|
|
|
QList<Okular::Annotation *> end() override
|
|
{
|
|
m_creationCompleted = false;
|
|
|
|
// find out annotation's description node
|
|
if (m_annotElement.isNull()) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// find out annotation's type
|
|
Okular::Annotation *ann = nullptr;
|
|
const QString typeString = m_annotElement.attribute(QStringLiteral("type"));
|
|
|
|
// create LineAnnotation from path
|
|
if (typeString == QLatin1String("Line") || typeString == QLatin1String("Polyline") || typeString == QLatin1String("Polygon")) {
|
|
if (points.count() < 2) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// add note
|
|
Okular::LineAnnotation *la = new Okular::LineAnnotation();
|
|
ann = la;
|
|
|
|
la->setLinePoints(points);
|
|
|
|
if (numofpoints == -1) {
|
|
la->setLineClosed(true);
|
|
if (m_annotElement.hasAttribute(QStringLiteral("innerColor"))) {
|
|
la->setLineInnerColor(QColor(m_annotElement.attribute(QStringLiteral("innerColor"))));
|
|
}
|
|
} else if (numofpoints == 2) {
|
|
if (m_annotElement.hasAttribute(QStringLiteral("leadFwd"))) {
|
|
la->setLineLeadingForwardPoint(m_annotElement.attribute(QStringLiteral("leadFwd")).toDouble());
|
|
}
|
|
if (m_annotElement.hasAttribute(QStringLiteral("leadBack"))) {
|
|
la->setLineLeadingBackwardPoint(m_annotElement.attribute(QStringLiteral("leadBack")).toDouble());
|
|
}
|
|
}
|
|
if (m_annotElement.hasAttribute(QStringLiteral("startStyle"))) {
|
|
la->setLineStartStyle((Okular::LineAnnotation::TermStyle)m_annotElement.attribute(QStringLiteral("startStyle")).toInt());
|
|
}
|
|
if (m_annotElement.hasAttribute(QStringLiteral("endStyle"))) {
|
|
la->setLineEndStyle((Okular::LineAnnotation::TermStyle)m_annotElement.attribute(QStringLiteral("endStyle")).toInt());
|
|
}
|
|
|
|
la->setBoundingRectangle(normRect);
|
|
}
|
|
|
|
// safety check
|
|
if (!ann) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
if (m_annotElement.hasAttribute(QStringLiteral("width"))) {
|
|
ann->style().setWidth(m_annotElement.attribute(QStringLiteral("width")).toDouble());
|
|
}
|
|
|
|
// set common attributes
|
|
ann->style().setColor(m_annotElement.hasAttribute(QStringLiteral("color")) ? m_annotElement.attribute(QStringLiteral("color")) : m_engineColor);
|
|
if (m_annotElement.hasAttribute(QStringLiteral("opacity"))) {
|
|
ann->style().setOpacity(m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble());
|
|
}
|
|
// return annotation
|
|
|
|
return QList<Okular::Annotation *>() << ann;
|
|
}
|
|
|
|
private:
|
|
QList<Okular::NormalizedPoint> points;
|
|
Okular::NormalizedPoint newPoint;
|
|
Okular::NormalizedPoint movingpoint;
|
|
QRect rect;
|
|
QRect movingrect;
|
|
Okular::NormalizedRect normRect;
|
|
bool m_block;
|
|
bool last;
|
|
int numofpoints;
|
|
};
|
|
|
|
/** @short TextSelectorEngine */
|
|
class TextSelectorEngine : public AnnotatorEngine
|
|
{
|
|
public:
|
|
TextSelectorEngine(const QDomElement &engineElement, PageView *pageView)
|
|
: AnnotatorEngine(engineElement)
|
|
, m_pageView(pageView)
|
|
{
|
|
// parse engine specific attributes
|
|
}
|
|
|
|
QRect event(EventType type, Button button, Modifiers /*modifiers*/, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/) override
|
|
{
|
|
// only proceed if pressing left button
|
|
if (button != Left) {
|
|
return QRect();
|
|
}
|
|
|
|
if (type == Press) {
|
|
lastPoint.x = nX;
|
|
lastPoint.y = nY;
|
|
const QRect oldrect = rect;
|
|
rect = QRect();
|
|
return oldrect;
|
|
} else if (type == Move) {
|
|
if (item()) {
|
|
const QPoint start((int)(lastPoint.x * item()->uncroppedWidth()), (int)(lastPoint.y * item()->uncroppedHeight()));
|
|
const QPoint end((int)(nX * item()->uncroppedWidth()), (int)(nY * item()->uncroppedHeight()));
|
|
selection.reset();
|
|
std::unique_ptr<Okular::RegularAreaRect> newselection(m_pageView->textSelectionForItem(item(), start, end));
|
|
if (newselection && !newselection->isEmpty()) {
|
|
const QList<QRect> geom = newselection->geometry((int)xScale, (int)yScale);
|
|
QRect newrect;
|
|
for (const QRect &r : geom) {
|
|
if (newrect.isNull()) {
|
|
newrect = r;
|
|
} else {
|
|
newrect |= r;
|
|
}
|
|
}
|
|
rect |= newrect;
|
|
selection = std::move(newselection);
|
|
}
|
|
}
|
|
} else if (type == Release) {
|
|
m_creationCompleted = true;
|
|
}
|
|
return rect;
|
|
}
|
|
|
|
void paint(QPainter *painter, double xScale, double yScale, const QRect & /*clipRect*/) override
|
|
{
|
|
if (selection) {
|
|
painter->setPen(Qt::NoPen);
|
|
QColor col = m_engineColor;
|
|
col.setAlphaF(0.5);
|
|
painter->setBrush(col);
|
|
for (const Okular::NormalizedRect &r : qAsConst(*selection)) {
|
|
painter->drawRect(r.geometry((int)xScale, (int)yScale));
|
|
}
|
|
}
|
|
}
|
|
|
|
QList<Okular::Annotation *> end() override
|
|
{
|
|
m_creationCompleted = false;
|
|
|
|
// safety checks
|
|
if (m_annotElement.isNull() || !selection) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// find out annotation's type
|
|
Okular::Annotation *ann = nullptr;
|
|
const QString typeString = m_annotElement.attribute(QStringLiteral("type"));
|
|
|
|
Okular::HighlightAnnotation::HighlightType type = Okular::HighlightAnnotation::Highlight;
|
|
bool typevalid = false;
|
|
// create HighlightAnnotation's from the selected area
|
|
if (typeString == QLatin1String("Highlight")) {
|
|
type = Okular::HighlightAnnotation::Highlight;
|
|
typevalid = true;
|
|
} else if (typeString == QLatin1String("Squiggly")) {
|
|
type = Okular::HighlightAnnotation::Squiggly;
|
|
typevalid = true;
|
|
} else if (typeString == QLatin1String("Underline")) {
|
|
type = Okular::HighlightAnnotation::Underline;
|
|
typevalid = true;
|
|
} else if (typeString == QLatin1String("StrikeOut")) {
|
|
type = Okular::HighlightAnnotation::StrikeOut;
|
|
typevalid = true;
|
|
}
|
|
if (typevalid) {
|
|
Okular::HighlightAnnotation *ha = new Okular::HighlightAnnotation();
|
|
ha->setHighlightType(type);
|
|
ha->setBoundingRectangle(Okular::NormalizedRect(rect, item()->uncroppedWidth(), item()->uncroppedHeight()));
|
|
for (const Okular::NormalizedRect &r : qAsConst(*selection)) {
|
|
Okular::HighlightAnnotation::Quad q;
|
|
q.setCapStart(false);
|
|
q.setCapEnd(false);
|
|
q.setFeather(1.0);
|
|
q.setPoint(Okular::NormalizedPoint(r.left, r.bottom), 0);
|
|
q.setPoint(Okular::NormalizedPoint(r.right, r.bottom), 1);
|
|
q.setPoint(Okular::NormalizedPoint(r.right, r.top), 2);
|
|
q.setPoint(Okular::NormalizedPoint(r.left, r.top), 3);
|
|
ha->highlightQuads().append(q);
|
|
}
|
|
ann = ha;
|
|
}
|
|
|
|
selection.reset();
|
|
|
|
// safety check
|
|
if (!ann) {
|
|
return QList<Okular::Annotation *>();
|
|
}
|
|
|
|
// set common attributes
|
|
ann->style().setColor(m_annotElement.hasAttribute(QStringLiteral("color")) ? m_annotElement.attribute(QStringLiteral("color")) : m_engineColor);
|
|
if (m_annotElement.hasAttribute(QStringLiteral("opacity"))) {
|
|
ann->style().setOpacity(m_annotElement.attribute(QStringLiteral("opacity"), QStringLiteral("1.0")).toDouble());
|
|
}
|
|
|
|
// return annotations
|
|
return QList<Okular::Annotation *>() << ann;
|
|
}
|
|
|
|
QCursor cursor() const override
|
|
{
|
|
return Qt::IBeamCursor;
|
|
}
|
|
|
|
private:
|
|
// data
|
|
PageView *m_pageView;
|
|
// TODO: support more pages
|
|
std::unique_ptr<Okular::RegularAreaRect> selection;
|
|
Okular::NormalizedPoint lastPoint;
|
|
QRect rect;
|
|
};
|
|
|
|
/** @short AnnotationTools*/
|
|
class AnnotationTools
|
|
{
|
|
public:
|
|
AnnotationTools()
|
|
: m_toolsCount(0)
|
|
{
|
|
}
|
|
|
|
void setTools(const QStringList &tools)
|
|
{
|
|
// Populate m_toolsDefinition
|
|
m_toolsCount = 0;
|
|
m_toolsDefinition.clear();
|
|
QDomElement root = m_toolsDefinition.createElement(QStringLiteral("root"));
|
|
m_toolsDefinition.appendChild(root);
|
|
for (const QString &toolXml : tools) {
|
|
QDomDocument entryParser;
|
|
if (entryParser.setContent(toolXml)) {
|
|
root.appendChild(m_toolsDefinition.importNode(entryParser.documentElement(), true));
|
|
m_toolsCount++;
|
|
} else {
|
|
qCWarning(OkularUiDebug) << "Skipping malformed tool XML in AnnotationTools setting";
|
|
}
|
|
}
|
|
}
|
|
|
|
QStringList toStringList()
|
|
{
|
|
QStringList tools;
|
|
QDomElement toolElement = m_toolsDefinition.documentElement().firstChildElement();
|
|
QString str;
|
|
QTextStream stream(&str);
|
|
while (!toolElement.isNull()) {
|
|
str.clear();
|
|
toolElement.save(stream, -1 /* indent disabled */);
|
|
tools << str;
|
|
toolElement = toolElement.nextSiblingElement();
|
|
}
|
|
return tools;
|
|
}
|
|
|
|
QDomElement tool(int toolId)
|
|
{
|
|
QDomElement toolElement = m_toolsDefinition.documentElement().firstChildElement();
|
|
while (!toolElement.isNull() && toolElement.attribute(QStringLiteral("id")).toInt() != toolId) {
|
|
toolElement = toolElement.nextSiblingElement();
|
|
}
|
|
return toolElement; // can return a null element
|
|
}
|
|
|
|
void appendTool(QDomElement toolElement)
|
|
{
|
|
toolElement = toolElement.cloneNode().toElement();
|
|
toolElement.setAttribute(QStringLiteral("id"), ++m_toolsCount);
|
|
m_toolsDefinition.documentElement().appendChild(toolElement);
|
|
}
|
|
|
|
bool updateTool(QDomElement newToolElement, int toolId)
|
|
{
|
|
QDomElement toolElement = tool(toolId);
|
|
if (toolElement.isNull()) {
|
|
return false;
|
|
}
|
|
newToolElement = newToolElement.cloneNode().toElement();
|
|
newToolElement.setAttribute(QStringLiteral("id"), toolId);
|
|
QDomNode oldTool = m_toolsDefinition.documentElement().replaceChild(newToolElement, toolElement);
|
|
return !oldTool.isNull();
|
|
}
|
|
|
|
int findToolId(const QString &type)
|
|
{
|
|
int toolId = -1;
|
|
if (type.isEmpty()) {
|
|
return -1;
|
|
}
|
|
// FIXME: search from left. currently searching from right side as a workaround to avoid matching
|
|
// straight line tools to the arrow tool, which is also of type straight-line
|
|
QDomElement toolElement = m_toolsDefinition.documentElement().lastChildElement();
|
|
while (!toolElement.isNull() && toolElement.attribute(QStringLiteral("type")) != type) {
|
|
toolElement = toolElement.previousSiblingElement();
|
|
}
|
|
if (!toolElement.isNull() && toolElement.hasAttribute(QStringLiteral("id"))) {
|
|
bool ok;
|
|
toolId = toolElement.attribute(QStringLiteral("id")).toInt(&ok);
|
|
if (!ok) {
|
|
return -1;
|
|
}
|
|
}
|
|
return toolId;
|
|
}
|
|
|
|
private:
|
|
QDomDocument m_toolsDefinition;
|
|
int m_toolsCount;
|
|
};
|
|
|
|
/** PageViewAnnotator **/
|
|
const int PageViewAnnotator::STAMP_TOOL_ID = 14;
|
|
|
|
PageViewAnnotator::PageViewAnnotator(PageView *parent, Okular::Document *storage)
|
|
: QObject(parent)
|
|
, m_document(storage)
|
|
, m_pageView(parent)
|
|
, m_actionHandler(nullptr)
|
|
, m_engine(nullptr)
|
|
, m_builtinToolsDefinition(nullptr)
|
|
, m_quickToolsDefinition(nullptr)
|
|
, m_continuousMode(true)
|
|
, m_constrainRatioAndAngle(false)
|
|
, m_signatureMode(false)
|
|
, m_lastToolsDefinition(nullptr)
|
|
, m_lastToolId(-1)
|
|
, m_lockedItem(nullptr)
|
|
{
|
|
reparseConfig();
|
|
reparseBuiltinToolsConfig();
|
|
reparseQuickToolsConfig();
|
|
connect(Okular::Settings::self(), &Okular::Settings::builtinAnnotationToolsChanged, this, &PageViewAnnotator::reparseBuiltinToolsConfig);
|
|
connect(Okular::Settings::self(), &Okular::Settings::quickAnnotationToolsChanged, this, &PageViewAnnotator::reparseQuickToolsConfig, Qt::QueuedConnection);
|
|
}
|
|
|
|
void PageViewAnnotator::reparseConfig()
|
|
{
|
|
m_continuousMode = Okular::Settings::annotationContinuousMode();
|
|
|
|
if (Okular::Settings::identityAuthor().isEmpty()) {
|
|
detachAnnotation();
|
|
}
|
|
}
|
|
|
|
void PageViewAnnotator::reparseBuiltinToolsConfig()
|
|
{
|
|
// Read tool list from configuration. It's a list of XML <tool></tool> elements
|
|
if (!m_builtinToolsDefinition) {
|
|
m_builtinToolsDefinition = new AnnotationTools();
|
|
}
|
|
m_builtinToolsDefinition->setTools(Okular::Settings::builtinAnnotationTools());
|
|
|
|
if (m_actionHandler) {
|
|
m_actionHandler->reparseBuiltinToolsConfig();
|
|
}
|
|
}
|
|
|
|
void PageViewAnnotator::reparseQuickToolsConfig()
|
|
{
|
|
// Read tool list from configuration. It's a list of XML <tool></tool> elements
|
|
if (!m_quickToolsDefinition) {
|
|
m_quickToolsDefinition = new AnnotationTools();
|
|
}
|
|
m_quickToolsDefinition->setTools(Okular::Settings::quickAnnotationTools());
|
|
|
|
if (m_actionHandler) {
|
|
m_actionHandler->reparseQuickToolsConfig();
|
|
}
|
|
}
|
|
|
|
PageViewAnnotator::~PageViewAnnotator()
|
|
{
|
|
delete m_engine;
|
|
delete m_builtinToolsDefinition;
|
|
delete m_quickToolsDefinition;
|
|
}
|
|
|
|
void PageViewAnnotator::setSignatureMode(bool enabled)
|
|
{
|
|
m_signatureMode = enabled;
|
|
}
|
|
|
|
bool PageViewAnnotator::signatureMode() const
|
|
{
|
|
return m_signatureMode;
|
|
}
|
|
|
|
bool PageViewAnnotator::active() const
|
|
{
|
|
return (m_engine != nullptr) || m_signatureMode;
|
|
}
|
|
|
|
bool PageViewAnnotator::annotating() const
|
|
{
|
|
return active() && m_lockedItem;
|
|
}
|
|
|
|
QCursor PageViewAnnotator::cursor() const
|
|
{
|
|
return m_engine ? m_engine->cursor() : Qt::CrossCursor;
|
|
}
|
|
|
|
QRect PageViewAnnotator::performRouteMouseOrTabletEvent(const AnnotatorEngine::EventType eventType, const AnnotatorEngine::Button button, const AnnotatorEngine::Modifiers modifiers, const QPointF pos, PageViewItem *item)
|
|
{
|
|
// creationCompleted is intended to be set by event(), handled subsequently by end(), and cleared within end().
|
|
// If it's set here, we recursed for some reason (e.g., stacked event loop).
|
|
// Just bail out, all we want to do is already on stack.
|
|
if (m_engine && m_engine->creationCompleted()) {
|
|
return QRect();
|
|
}
|
|
|
|
// if the right mouse button was pressed, we simply do nothing. In this way, we are still editing the annotation
|
|
// and so this function will receive and process the right mouse button release event too. If we detach now the annotation tool,
|
|
// the release event will be processed by the PageView class which would create the annotation property widget, and we do not want this.
|
|
if (button == AnnotatorEngine::Right && eventType == AnnotatorEngine::Press) {
|
|
return QRect();
|
|
} else if (button == AnnotatorEngine::Right && eventType == AnnotatorEngine::Release) {
|
|
detachAnnotation();
|
|
return QRect();
|
|
}
|
|
|
|
if (signatureMode() && eventType == AnnotatorEngine::Press) {
|
|
m_engine = new PickPointEngineSignature(m_document, m_pageView);
|
|
}
|
|
|
|
// 1. lock engine to current item
|
|
if (!m_lockedItem && eventType == AnnotatorEngine::Press) {
|
|
m_lockedItem = item;
|
|
m_engine->setItem(m_lockedItem);
|
|
}
|
|
if (!m_lockedItem) {
|
|
return QRect();
|
|
}
|
|
|
|
// find out normalized mouse coords inside current item
|
|
const QRect &itemRect = m_lockedItem->uncroppedGeometry();
|
|
const QPointF eventPos = m_pageView->contentAreaPoint(pos);
|
|
const double nX = qBound(0.0, m_lockedItem->absToPageX(eventPos.x()), 1.0);
|
|
const double nY = qBound(0.0, m_lockedItem->absToPageY(eventPos.y()), 1.0);
|
|
|
|
QRect modifiedRect;
|
|
|
|
// 2. use engine to perform operations
|
|
const QRect paintRect = m_engine->event(eventType, button, modifiers, nX, nY, itemRect.width(), itemRect.height(), m_lockedItem->page());
|
|
|
|
// 3. update absolute extents rect and send paint event(s)
|
|
if (paintRect.isValid()) {
|
|
// 3.1. unite old and new painting regions
|
|
QRegion compoundRegion(m_lastDrawnRect);
|
|
m_lastDrawnRect = paintRect;
|
|
m_lastDrawnRect.translate(itemRect.left(), itemRect.top());
|
|
// 3.2. decompose paint region in rects and send paint events
|
|
const QRegion rgn = compoundRegion.united(m_lastDrawnRect);
|
|
const QPoint areaPos = m_pageView->contentAreaPosition();
|
|
for (const QRect &r : rgn) {
|
|
m_pageView->viewport()->update(r.translated(-areaPos));
|
|
}
|
|
modifiedRect = compoundRegion.boundingRect() | m_lastDrawnRect;
|
|
}
|
|
|
|
// 4. if engine has finished, apply Annotation to the page
|
|
if (m_engine->creationCompleted()) {
|
|
// apply engine data to the Annotation's and reset engine
|
|
const QList<Okular::Annotation *> annotations = m_engine->end();
|
|
// attach the newly filled annotations to the page
|
|
for (Okular::Annotation *annotation : annotations) {
|
|
if (!annotation) {
|
|
continue;
|
|
}
|
|
|
|
annotation->setCreationDate(QDateTime::currentDateTime());
|
|
annotation->setModificationDate(QDateTime::currentDateTime());
|
|
annotation->setAuthor(Okular::Settings::identityAuthor());
|
|
m_document->addPageAnnotation(m_lockedItem->pageNumber(), annotation);
|
|
|
|
if (annotation->openDialogAfterCreation()) {
|
|
m_pageView->openAnnotationWindow(annotation, m_lockedItem->pageNumber());
|
|
}
|
|
}
|
|
|
|
if (signatureMode()) {
|
|
auto signEngine = static_cast<PickPointEngineSignature *>(m_engine);
|
|
if (signEngine->isAccepted()) {
|
|
const QString newFilePath = SignaturePartUtils::getFileNameForNewSignedFile(m_pageView, m_document);
|
|
|
|
if (!newFilePath.isEmpty()) {
|
|
const bool success = static_cast<PickPointEngineSignature *>(m_engine)->sign(newFilePath);
|
|
if (success) {
|
|
Q_EMIT requestOpenFile(newFilePath, m_lockedItem->pageNumber() + 1);
|
|
} else {
|
|
KMessageBox::error(m_pageView, i18nc("%1 is a file path", "Could not sign. Invalid certificate password or could not write to '%1'", newFilePath));
|
|
}
|
|
}
|
|
// Exit the signature mode.
|
|
setSignatureMode(false);
|
|
selectBuiltinTool(-1, ShowTip::No);
|
|
} else if (signEngine->userWantsToStartOver()) {
|
|
delete m_engine;
|
|
m_engine = new PickPointEngineSignature(m_document, m_pageView);
|
|
return {};
|
|
} else if (signEngine->isAborted()) {
|
|
// Exit the signature mode.
|
|
setSignatureMode(false);
|
|
selectBuiltinTool(-1, ShowTip::No);
|
|
}
|
|
m_continuousMode = false;
|
|
}
|
|
|
|
if (m_continuousMode) {
|
|
selectLastTool();
|
|
} else {
|
|
detachAnnotation();
|
|
}
|
|
}
|
|
|
|
return modifiedRect;
|
|
}
|
|
|
|
QRect PageViewAnnotator::routeMouseEvent(QMouseEvent *e, PageViewItem *item)
|
|
{
|
|
AnnotatorEngine::EventType eventType;
|
|
AnnotatorEngine::Button button;
|
|
AnnotatorEngine::Modifiers modifiers;
|
|
|
|
// figure out the event type and button
|
|
AnnotatorEngine::decodeEvent(e, &eventType, &button);
|
|
|
|
// Constrain angle if action checked XOR shift button pressed.
|
|
modifiers.constrainRatioAndAngle = (bool(constrainRatioAndAngleActive()) != bool(e->modifiers() & Qt::ShiftModifier));
|
|
|
|
return performRouteMouseOrTabletEvent(eventType, button, modifiers, e->localPos(), item);
|
|
}
|
|
|
|
QRect PageViewAnnotator::routeTabletEvent(QTabletEvent *e, PageViewItem *item, const QPoint localOriginInGlobal)
|
|
{
|
|
// Unlike routeMouseEvent, routeTabletEvent must explicitly ignore events it doesn't care about so that
|
|
// the corresponding mouse event will later be delivered.
|
|
if (!item) {
|
|
e->ignore();
|
|
return QRect();
|
|
}
|
|
|
|
AnnotatorEngine::EventType eventType;
|
|
AnnotatorEngine::Button button;
|
|
AnnotatorEngine::Modifiers modifiers;
|
|
|
|
// figure out the event type and button
|
|
AnnotatorEngine::decodeEvent(e, &eventType, &button);
|
|
|
|
// Constrain angle if action checked XOR shift button pressed.
|
|
modifiers.constrainRatioAndAngle = (bool(constrainRatioAndAngleActive()) != bool(e->modifiers() & Qt::ShiftModifier));
|
|
|
|
const QPointF globalPosF = e->globalPosF();
|
|
const QPointF localPosF = globalPosF - localOriginInGlobal;
|
|
return performRouteMouseOrTabletEvent(eventType, button, modifiers, localPosF, item);
|
|
}
|
|
|
|
bool PageViewAnnotator::routeKeyEvent(QKeyEvent *event)
|
|
{
|
|
if (event->key() == Qt::Key_Escape) {
|
|
detachAnnotation();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PageViewAnnotator::routePaints(const QRect wantedRect) const
|
|
{
|
|
return m_engine && wantedRect.intersects(m_lastDrawnRect) && m_lockedItem;
|
|
}
|
|
|
|
void PageViewAnnotator::routePaint(QPainter *painter, const QRect paintRect)
|
|
{
|
|
// if there's no locked item, then there's no decided place to draw on
|
|
if (!m_lockedItem) {
|
|
return;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
// [DEBUG] draw the paint region if enabled
|
|
if (Okular::Settings::debugDrawAnnotationRect()) {
|
|
painter->drawRect(paintRect);
|
|
}
|
|
#endif
|
|
// move painter to current itemGeometry rect
|
|
const QRect &itemRect = m_lockedItem->uncroppedGeometry();
|
|
painter->save();
|
|
painter->translate(itemRect.topLeft());
|
|
// TODO: Clip annotation painting to cropped page.
|
|
|
|
// transform cliprect from absolute to item relative coords
|
|
QRect annotRect = paintRect.intersected(m_lastDrawnRect);
|
|
annotRect.translate(-itemRect.topLeft());
|
|
|
|
// use current engine for painting (in virtual page coordinates)
|
|
m_engine->paint(painter, m_lockedItem->uncroppedWidth(), m_lockedItem->uncroppedHeight(), annotRect);
|
|
painter->restore();
|
|
}
|
|
|
|
void PageViewAnnotator::selectBuiltinTool(int toolId, ShowTip showTip)
|
|
{
|
|
selectTool(m_builtinToolsDefinition, toolId, showTip);
|
|
}
|
|
|
|
void PageViewAnnotator::selectQuickTool(int toolId)
|
|
{
|
|
selectTool(m_quickToolsDefinition, toolId, ShowTip::Yes);
|
|
}
|
|
|
|
void PageViewAnnotator::selectTool(AnnotationTools *toolsDefinition, int toolId, ShowTip showTip)
|
|
{
|
|
// ask for Author's name if not already set
|
|
if (toolId > 0 && Okular::Settings::identityAuthor().isEmpty()) {
|
|
// get default username from the kdelibs/kdecore/KUser
|
|
KUser currentUser;
|
|
QString userName = currentUser.property(KUser::FullName).toString();
|
|
// ask the user for confirmation/change
|
|
if (userName.isEmpty()) {
|
|
bool ok = false;
|
|
userName = QInputDialog::getText(nullptr, i18n("Author name"), i18n("Author name for the annotation:"), QLineEdit::Normal, QString(), &ok);
|
|
|
|
if (!ok) {
|
|
detachAnnotation();
|
|
return;
|
|
}
|
|
}
|
|
// save the name
|
|
Okular::Settings::setIdentityAuthor(userName);
|
|
Okular::Settings::self()->save();
|
|
}
|
|
|
|
// terminate any previous operation
|
|
if (m_engine) {
|
|
delete m_engine;
|
|
m_engine = nullptr;
|
|
}
|
|
m_lockedItem = nullptr;
|
|
if (m_lastDrawnRect.isValid()) {
|
|
m_pageView->viewport()->update(m_lastDrawnRect.translated(-m_pageView->contentAreaPosition()));
|
|
m_lastDrawnRect = QRect();
|
|
}
|
|
|
|
// store current tool for later usage
|
|
m_lastToolId = toolId;
|
|
m_lastToolsDefinition = toolsDefinition;
|
|
|
|
// handle tool deselection
|
|
if (toolId == -1) {
|
|
m_pageView->displayMessage(QString());
|
|
m_pageView->updateCursor();
|
|
Q_EMIT toolActive(false);
|
|
return;
|
|
}
|
|
|
|
// for the selected tool create the Engine
|
|
QDomElement toolElement = toolsDefinition->tool(toolId);
|
|
if (!toolElement.isNull()) {
|
|
// parse tool properties
|
|
QDomElement engineElement = toolElement.firstChildElement(QStringLiteral("engine"));
|
|
if (!engineElement.isNull()) {
|
|
// create the AnnotatorEngine
|
|
QString type = engineElement.attribute(QStringLiteral("type"));
|
|
if (type == QLatin1String("SmoothLine")) {
|
|
m_engine = new SmoothPathEngine(engineElement);
|
|
} else if (type == QLatin1String("PickPoint")) {
|
|
m_engine = new PickPointEngine(engineElement);
|
|
} else if (type == QLatin1String("PolyLine")) {
|
|
m_engine = new PolyLineEngine(engineElement);
|
|
} else if (type == QLatin1String("TextSelector")) {
|
|
m_engine = new TextSelectorEngine(engineElement, m_pageView);
|
|
} else {
|
|
qCWarning(OkularUiDebug).nospace() << "tools.xml: engine type:'" << type << "' is not defined!";
|
|
}
|
|
|
|
if (showTip == ShowTip::Yes) {
|
|
// display the tooltip
|
|
const QString annotType = toolElement.attribute(QStringLiteral("type"));
|
|
QString tip;
|
|
|
|
if (annotType == QLatin1String("ellipse")) {
|
|
tip = i18nc("Annotation tool", "Draw an ellipse (drag to select a zone)");
|
|
} else if (annotType == QLatin1String("highlight")) {
|
|
tip = i18nc("Annotation tool", "Highlight text");
|
|
} else if (annotType == QLatin1String("ink")) {
|
|
tip = i18nc("Annotation tool", "Draw a freehand line");
|
|
} else if (annotType == QLatin1String("note-inline")) {
|
|
tip = i18nc("Annotation tool", "Inline Text Annotation (drag to select a zone)");
|
|
} else if (annotType == QLatin1String("note-linked")) {
|
|
tip = i18nc("Annotation tool", "Put a pop-up note");
|
|
} else if (annotType == QLatin1String("polygon")) {
|
|
tip = i18nc("Annotation tool", "Draw a polygon (click on the first point to close it)");
|
|
} else if (annotType == QLatin1String("rectangle")) {
|
|
tip = i18nc("Annotation tool", "Draw a rectangle");
|
|
} else if (annotType == QLatin1String("squiggly")) {
|
|
tip = i18nc("Annotation tool", "Squiggle text");
|
|
} else if (annotType == QLatin1String("stamp")) {
|
|
tip = i18nc("Annotation tool", "Put a stamp symbol");
|
|
} else if (annotType == QLatin1String("straight-line")) {
|
|
tip = i18nc("Annotation tool", "Draw a straight line");
|
|
} else if (annotType == QLatin1String("strikeout")) {
|
|
tip = i18nc("Annotation tool", "Strike out text");
|
|
} else if (annotType == QLatin1String("underline")) {
|
|
tip = i18nc("Annotation tool", "Underline text");
|
|
} else if (annotType == QLatin1String("typewriter")) {
|
|
tip = i18nc("Annotation tool", "Typewriter Annotation (drag to select a zone)");
|
|
}
|
|
|
|
if (!tip.isEmpty()) {
|
|
m_pageView->displayMessage(tip, QString(), PageViewMessage::Annotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// consistency warning
|
|
if (!m_engine) {
|
|
qCWarning(OkularUiDebug) << "tools.xml: couldn't find good engine description. check xml.";
|
|
}
|
|
|
|
m_pageView->updateCursor();
|
|
}
|
|
|
|
Q_EMIT toolActive(true);
|
|
}
|
|
|
|
void PageViewAnnotator::selectLastTool()
|
|
{
|
|
selectTool(m_lastToolsDefinition, m_lastToolId, ShowTip::No);
|
|
}
|
|
|
|
void PageViewAnnotator::selectStampTool(const QString &stampSymbol)
|
|
{
|
|
QDomElement toolElement = builtinTool(STAMP_TOOL_ID);
|
|
QDomElement engineElement = toolElement.firstChildElement(QStringLiteral("engine"));
|
|
QDomElement annotationElement = engineElement.firstChildElement(QStringLiteral("annotation"));
|
|
engineElement.setAttribute(QStringLiteral("hoverIcon"), stampSymbol);
|
|
annotationElement.setAttribute(QStringLiteral("icon"), stampSymbol);
|
|
saveBuiltinAnnotationTools();
|
|
selectBuiltinTool(STAMP_TOOL_ID, ShowTip::Yes);
|
|
}
|
|
|
|
void PageViewAnnotator::detachAnnotation()
|
|
{
|
|
if (m_lastToolId == -1) {
|
|
return;
|
|
}
|
|
selectBuiltinTool(-1, ShowTip::No);
|
|
if (!signatureMode()) {
|
|
if (m_actionHandler) {
|
|
m_actionHandler->deselectAllAnnotationActions();
|
|
}
|
|
} else {
|
|
m_pageView->displayMessage(QString());
|
|
setSignatureMode(false);
|
|
}
|
|
}
|
|
|
|
QString PageViewAnnotator::defaultToolName(const QDomElement &toolElement)
|
|
{
|
|
const QString annotType = toolElement.attribute(QStringLiteral("type"));
|
|
|
|
if (annotType == QLatin1String("ellipse")) {
|
|
return i18n("Ellipse");
|
|
} else if (annotType == QLatin1String("highlight")) {
|
|
return i18n("Highlighter");
|
|
} else if (annotType == QLatin1String("ink")) {
|
|
return i18n("Freehand Line");
|
|
} else if (annotType == QLatin1String("note-inline")) {
|
|
return i18n("Inline Note");
|
|
} else if (annotType == QLatin1String("note-linked")) {
|
|
return i18n("Pop-up Note");
|
|
} else if (annotType == QLatin1String("polygon")) {
|
|
return i18n("Polygon");
|
|
} else if (annotType == QLatin1String("rectangle")) {
|
|
return i18n("Rectangle");
|
|
} else if (annotType == QLatin1String("squiggly")) {
|
|
return i18n("Squiggle");
|
|
} else if (annotType == QLatin1String("stamp")) {
|
|
return i18n("Stamp");
|
|
} else if (annotType == QLatin1String("straight-line")) {
|
|
return i18n("Straight Line");
|
|
} else if (annotType == QLatin1String("strikeout")) {
|
|
return i18n("Strike out");
|
|
} else if (annotType == QLatin1String("underline")) {
|
|
return i18n("Underline");
|
|
} else if (annotType == QLatin1String("typewriter")) {
|
|
return i18n("Typewriter");
|
|
} else {
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
QPixmap PageViewAnnotator::makeToolPixmap(const QDomElement &toolElement)
|
|
{
|
|
QPixmap pixmap(32 * qApp->devicePixelRatio(), 32 * qApp->devicePixelRatio());
|
|
pixmap.setDevicePixelRatio(qApp->devicePixelRatio());
|
|
const QString annotType = toolElement.attribute(QStringLiteral("type"));
|
|
|
|
// Load HiDPI variant on HiDPI screen
|
|
QString imageVariant;
|
|
if (qApp->devicePixelRatio() > 1.05) {
|
|
imageVariant = QStringLiteral("@2x");
|
|
}
|
|
|
|
// Load base pixmap. We'll draw on top of it
|
|
pixmap.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/tool-base-okular") + imageVariant + QStringLiteral(".png")));
|
|
|
|
/* Parse color, innerColor and icon (if present) */
|
|
QColor engineColor, innerColor, textColor, annotColor;
|
|
QString icon;
|
|
QDomNodeList engineNodeList = toolElement.elementsByTagName(QStringLiteral("engine"));
|
|
if (engineNodeList.size() > 0) {
|
|
QDomElement engineEl = engineNodeList.item(0).toElement();
|
|
if (!engineEl.isNull() && engineEl.hasAttribute(QStringLiteral("color"))) {
|
|
engineColor = QColor(engineEl.attribute(QStringLiteral("color")));
|
|
}
|
|
}
|
|
QDomNodeList annotationNodeList = toolElement.elementsByTagName(QStringLiteral("annotation"));
|
|
if (annotationNodeList.size() > 0) {
|
|
QDomElement annotationEl = annotationNodeList.item(0).toElement();
|
|
if (!annotationEl.isNull()) {
|
|
if (annotationEl.hasAttribute(QStringLiteral("color"))) {
|
|
annotColor = annotationEl.attribute(QStringLiteral("color"));
|
|
}
|
|
if (annotationEl.hasAttribute(QStringLiteral("innerColor"))) {
|
|
innerColor = QColor(annotationEl.attribute(QStringLiteral("innerColor")));
|
|
}
|
|
if (annotationEl.hasAttribute(QStringLiteral("textColor"))) {
|
|
textColor = QColor(annotationEl.attribute(QStringLiteral("textColor")));
|
|
}
|
|
if (annotationEl.hasAttribute(QStringLiteral("icon"))) {
|
|
icon = annotationEl.attribute(QStringLiteral("icon"));
|
|
}
|
|
}
|
|
}
|
|
|
|
QPainter p(&pixmap);
|
|
|
|
if (annotType == QLatin1String("ellipse")) {
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
if (innerColor.isValid()) {
|
|
p.setBrush(innerColor);
|
|
}
|
|
p.setPen(QPen(engineColor, 2));
|
|
p.drawEllipse(2, 7, 21, 14);
|
|
} else if (annotType == QLatin1String("highlight")) {
|
|
QImage overlay(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/tool-highlighter-okular-colorizable") + imageVariant + QStringLiteral(".png")));
|
|
QImage colorizedOverlay = overlay;
|
|
GuiUtils::colorizeImage(colorizedOverlay, engineColor);
|
|
|
|
p.drawImage(QPoint(0, 0), colorizedOverlay); // Trail
|
|
p.drawImage(QPoint(0, -32), overlay); // Text + Shadow (uncolorized)
|
|
p.drawImage(QPoint(0, -64), colorizedOverlay); // Pen
|
|
} else if (annotType == QLatin1String("ink")) {
|
|
QImage overlay(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/tool-ink-okular-colorizable") + imageVariant + QStringLiteral(".png")));
|
|
QImage colorizedOverlay = overlay;
|
|
GuiUtils::colorizeImage(colorizedOverlay, engineColor);
|
|
|
|
p.drawImage(QPoint(0, 0), colorizedOverlay); // Trail
|
|
p.drawImage(QPoint(0, -32), overlay); // Shadow (uncolorized)
|
|
p.drawImage(QPoint(0, -64), colorizedOverlay); // Pen
|
|
} else if (annotType == QLatin1String("note-inline")) {
|
|
QImage overlay(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/tool-note-inline-okular-colorizable") + imageVariant + QStringLiteral(".png")));
|
|
GuiUtils::colorizeImage(overlay, engineColor);
|
|
p.drawImage(QPoint(0, 0), overlay);
|
|
} else if (annotType == QLatin1String("note-linked")) {
|
|
QImage overlay(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/tool-note-okular-colorizable") + imageVariant + QStringLiteral(".png")));
|
|
GuiUtils::colorizeImage(overlay, engineColor);
|
|
p.drawImage(QPoint(0, 0), overlay);
|
|
} else if (annotType == QLatin1String("polygon")) {
|
|
QPainterPath path;
|
|
path.moveTo(0, 7);
|
|
path.lineTo(19, 7);
|
|
path.lineTo(19, 14);
|
|
path.lineTo(23, 14);
|
|
path.lineTo(23, 20);
|
|
path.lineTo(0, 20);
|
|
if (innerColor.isValid()) {
|
|
p.setBrush(innerColor);
|
|
}
|
|
p.setPen(QPen(engineColor, 1));
|
|
p.drawPath(path);
|
|
} else if (annotType == QLatin1String("rectangle")) {
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
if (innerColor.isValid()) {
|
|
p.setBrush(innerColor);
|
|
}
|
|
p.setPen(QPen(engineColor, 2));
|
|
p.drawRect(2, 7, 21, 14);
|
|
} else if (annotType == QLatin1String("squiggly")) {
|
|
QPen pen(engineColor, 1);
|
|
pen.setDashPattern(QVector<qreal>() << 1 << 1);
|
|
p.setPen(pen);
|
|
p.drawLine(1, 13, 16, 13);
|
|
p.drawLine(2, 14, 15, 14);
|
|
p.drawLine(0, 20, 19, 20);
|
|
p.drawLine(1, 21, 18, 21);
|
|
} else if (annotType == QLatin1String("stamp")) {
|
|
QPixmap stamp = Okular::AnnotationUtils::loadStamp(icon, 16, false /* keepAspectRatio */);
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
p.drawPixmap(16, 14, stamp);
|
|
} else if (annotType == QLatin1String("straight-line")) {
|
|
QPainterPath path;
|
|
path.moveTo(1, 8);
|
|
path.lineTo(20, 8);
|
|
path.lineTo(1, 27);
|
|
path.lineTo(20, 27);
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
p.setPen(QPen(engineColor, 1));
|
|
p.drawPath(path); // TODO To be discussed: This is not a straight line!
|
|
} else if (annotType == QLatin1String("strikeout")) {
|
|
p.setPen(QPen(engineColor, 1));
|
|
p.drawLine(1, 10, 16, 10);
|
|
p.drawLine(0, 17, 19, 17);
|
|
} else if (annotType == QLatin1String("underline")) {
|
|
p.setPen(QPen(engineColor, 1));
|
|
p.drawLine(1, 13, 16, 13);
|
|
p.drawLine(0, 20, 19, 20);
|
|
} else if (annotType == QLatin1String("typewriter")) {
|
|
QImage overlay(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/tool-typewriter-okular-colorizable") + imageVariant + QStringLiteral(".png")));
|
|
GuiUtils::colorizeImage(overlay, textColor);
|
|
p.drawImage(QPoint(-2, 2), overlay);
|
|
} else {
|
|
/* Unrecognized annotation type -- It shouldn't happen */
|
|
p.setPen(QPen(engineColor));
|
|
p.drawText(QPoint(20, 31), QStringLiteral("?"));
|
|
}
|
|
|
|
return pixmap;
|
|
}
|
|
|
|
void PageViewAnnotator::setupActions(KActionCollection *ac)
|
|
{
|
|
if (!m_actionHandler) {
|
|
m_actionHandler = new AnnotationActionHandler(this, ac);
|
|
}
|
|
}
|
|
|
|
void PageViewAnnotator::setupActionsPostGUIActivated()
|
|
{
|
|
m_actionHandler->setupAnnotationToolBarVisibilityAction();
|
|
}
|
|
|
|
bool PageViewAnnotator::continuousMode()
|
|
{
|
|
return m_continuousMode;
|
|
}
|
|
|
|
void PageViewAnnotator::setContinuousMode(bool enabled)
|
|
{
|
|
m_continuousMode = enabled;
|
|
Okular::Settings::setAnnotationContinuousMode(enabled);
|
|
Okular::Settings::self()->save();
|
|
}
|
|
|
|
bool PageViewAnnotator::constrainRatioAndAngleActive()
|
|
{
|
|
return m_constrainRatioAndAngle;
|
|
}
|
|
|
|
void PageViewAnnotator::setConstrainRatioAndAngle(bool enabled)
|
|
{
|
|
m_constrainRatioAndAngle = enabled;
|
|
}
|
|
|
|
void PageViewAnnotator::setToolsEnabled(bool enabled)
|
|
{
|
|
if (m_actionHandler) {
|
|
m_actionHandler->setToolsEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
void PageViewAnnotator::setTextToolsEnabled(bool enabled)
|
|
{
|
|
if (m_actionHandler) {
|
|
m_actionHandler->setTextToolsEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
void PageViewAnnotator::saveBuiltinAnnotationTools()
|
|
{
|
|
Okular::Settings::setBuiltinAnnotationTools(m_builtinToolsDefinition->toStringList());
|
|
Okular::Settings::self()->save();
|
|
}
|
|
|
|
QDomElement PageViewAnnotator::builtinTool(int toolId)
|
|
{
|
|
return m_builtinToolsDefinition->tool(toolId);
|
|
}
|
|
|
|
QDomElement PageViewAnnotator::quickTool(int toolId)
|
|
{
|
|
return m_quickToolsDefinition->tool(toolId);
|
|
}
|
|
|
|
QDomElement PageViewAnnotator::currentEngineElement()
|
|
{
|
|
return m_builtinToolsDefinition->tool(m_lastToolId).firstChildElement(QStringLiteral("engine"));
|
|
}
|
|
|
|
QDomElement PageViewAnnotator::currentAnnotationElement()
|
|
{
|
|
return currentEngineElement().firstChildElement(QStringLiteral("annotation"));
|
|
}
|
|
|
|
void PageViewAnnotator::setAnnotationWidth(double width)
|
|
{
|
|
currentAnnotationElement().setAttribute(QStringLiteral("width"), QString::number(width));
|
|
saveBuiltinAnnotationTools();
|
|
selectLastTool();
|
|
}
|
|
|
|
void PageViewAnnotator::setAnnotationColor(const QColor &color)
|
|
{
|
|
currentEngineElement().setAttribute(QStringLiteral("color"), color.name(QColor::HexRgb));
|
|
QDomElement annotationElement = currentAnnotationElement();
|
|
QString annotType = annotationElement.attribute(QStringLiteral("type"));
|
|
if (annotType == QLatin1String("Typewriter")) {
|
|
annotationElement.setAttribute(QStringLiteral("textColor"), color.name(QColor::HexRgb));
|
|
} else {
|
|
annotationElement.setAttribute(QStringLiteral("color"), color.name(QColor::HexRgb));
|
|
}
|
|
saveBuiltinAnnotationTools();
|
|
selectLastTool();
|
|
}
|
|
|
|
void PageViewAnnotator::setAnnotationInnerColor(const QColor &color)
|
|
{
|
|
QDomElement annotationElement = currentAnnotationElement();
|
|
if (color == Qt::transparent) {
|
|
annotationElement.removeAttribute(QStringLiteral("innerColor"));
|
|
} else {
|
|
annotationElement.setAttribute(QStringLiteral("innerColor"), color.name(QColor::HexRgb));
|
|
}
|
|
saveBuiltinAnnotationTools();
|
|
selectLastTool();
|
|
}
|
|
|
|
void PageViewAnnotator::setAnnotationOpacity(double opacity)
|
|
{
|
|
currentAnnotationElement().setAttribute(QStringLiteral("opacity"), QString::number(opacity));
|
|
saveBuiltinAnnotationTools();
|
|
selectLastTool();
|
|
}
|
|
|
|
void PageViewAnnotator::setAnnotationFont(const QFont &font)
|
|
{
|
|
currentAnnotationElement().setAttribute(QStringLiteral("font"), font.toString());
|
|
saveBuiltinAnnotationTools();
|
|
selectLastTool();
|
|
}
|
|
|
|
void PageViewAnnotator::addToQuickAnnotations()
|
|
{
|
|
QDomElement sourceToolElement = m_builtinToolsDefinition->tool(m_lastToolId);
|
|
if (sourceToolElement.isNull()) {
|
|
return;
|
|
}
|
|
|
|
// set custom name for quick annotation
|
|
bool ok = false;
|
|
QString itemText = QInputDialog::getText(nullptr, i18n("Add favorite annotation"), i18n("Custom annotation name:"), QLineEdit::Normal, defaultToolName(sourceToolElement), &ok);
|
|
if (!ok) {
|
|
return;
|
|
}
|
|
|
|
QDomElement toolElement = sourceToolElement.cloneNode().toElement();
|
|
// store name attribute only if the user specified a customized name
|
|
if (!itemText.isEmpty()) {
|
|
toolElement.setAttribute(QStringLiteral("name"), itemText);
|
|
}
|
|
m_quickToolsDefinition->appendTool(toolElement);
|
|
Okular::Settings::setQuickAnnotationTools(m_quickToolsDefinition->toStringList());
|
|
Okular::Settings::self()->save();
|
|
}
|
|
|
|
void PageViewAnnotator::slotAdvancedSettings()
|
|
{
|
|
QDomElement toolElement = m_builtinToolsDefinition->tool(m_lastToolId);
|
|
|
|
EditAnnotToolDialog t(nullptr, toolElement, true);
|
|
if (t.exec() != QDialog::Accepted) {
|
|
return;
|
|
}
|
|
|
|
QDomElement toolElementUpdated = t.toolXml().documentElement();
|
|
int toolId = toolElement.attribute(QStringLiteral("id")).toInt();
|
|
m_builtinToolsDefinition->updateTool(toolElementUpdated, toolId);
|
|
saveBuiltinAnnotationTools();
|
|
selectLastTool();
|
|
}
|
|
|
|
/* kate: replace-tabs on; indent-width 4; */
|