pageviewannotator.cpp 55.4 KB
Newer Older
1 2 3 4 5 6 7 8 9
/***************************************************************************
 *   Copyright (C) 2005 by Enrico Ros <eros.kde@email.it>                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

Albert Astals Cid's avatar
Albert Astals Cid committed
10 11
#include "pageviewannotator.h"

12
// qt / kde includes
Albert Astals Cid's avatar
Albert Astals Cid committed
13 14
#include <KIconLoader>
#include <KLocalizedString>
15 16 17
#include <QApplication>
#include <QColor>
#include <QEvent>
Albert Astals Cid's avatar
Albert Astals Cid committed
18 19
#include <QFile>
#include <QInputDialog>
20
#include <QList>
Albert Astals Cid's avatar
Albert Astals Cid committed
21
#include <QLoggingCategory>
22 23 24
#include <QPainter>
#include <QSet>
#include <QVariant>
Laurent Montel's avatar
Laurent Montel committed
25

26
#include <KUser>
Yuri Chornoivan's avatar
Yuri Chornoivan committed
27
#include <QDebug>
28
#include <QMenu>
29 30

// system includes
31
#include <QKeyEvent>
Albert Astals Cid's avatar
Albert Astals Cid committed
32
#include <QStandardPaths>
33
#include <QTabletEvent>
Albert Astals Cid's avatar
Albert Astals Cid committed
34 35
#include <math.h>
#include <memory>
36 37

// local includes
Albert Astals Cid's avatar
Albert Astals Cid committed
38
#include "annotationtools.h"
Simone Gaiarin's avatar
Simone Gaiarin committed
39
#include "conf/editannottooldialog.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
40
#include "core/annotations.h"
41
#include "core/area.h"
42 43
#include "core/document.h"
#include "core/page.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
44
#include "debug_ui.h"
45
#include "guiutils.h"
46
#include "pageview.h"
Albert Astals Cid's avatar
Albert Astals Cid committed
47 48
#include "settings.h"
#include "ui/annotationactionhandler.h"
49 50 51 52

/** @short PickPointEngine */
class PickPointEngine : public AnnotatorEngine
{
Albert Astals Cid's avatar
Albert Astals Cid committed
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
public:
    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 = GuiUtils::loadStamp(hoverIconName, size);
    }

77
    QRect event(EventType type, Button button, Modifiers modifiers, double nX, double nY, double xScale, double yScale, const Okular::Page *page) override
Albert Astals Cid's avatar
Albert Astals Cid committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
    {
        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();

102 103
        // Constrain to 1:1 form factor (e.g. circle or square)
        if (modifiers.constrainRatioAndAngle) {
104 105 106 107
            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);
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
108 109 110 111 112 113 114 115 116
        // 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;
117
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
118 119 120 121 122 123 124 125 126
        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;
    }
127

Albert Astals Cid's avatar
Albert Astals Cid committed
128 129 130 131 132 133 134 135 136 137 138 139
    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);
140
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
            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;
            f.fromString(m_annotElement.attribute(QStringLiteral("font")));
            ta->setTextFont(f);
        }
        // set font color
        if (m_annotElement.hasAttribute(QStringLiteral("textColor"))) {
            if (inplaceIntent == Okular::TextAnnotation::TypeWriter)
                ta->setTextColor(m_annotElement.attribute(QStringLiteral("textColor")));
167
            else
Albert Astals Cid's avatar
Albert Astals Cid committed
168
                ta->setTextColor(Qt::black);
169
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
        // 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);
    }
188

Albert Astals Cid's avatar
Albert Astals Cid committed
189 190 191 192 193 194 195
    QList<Okular::Annotation *> end() override
    {
        // find out annotation's description node
        if (m_annotElement.isNull()) {
            m_creationCompleted = false;
            clicked = false;
            return QList<Okular::Annotation *>();
196 197
        }

Albert Astals Cid's avatar
Albert Astals Cid committed
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
        // 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")) {
            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("Inline Note"), content, 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();
214
            ann = ta;
Albert Astals Cid's avatar
Albert Astals Cid committed
215 216 217 218 219 220 221 222 223
            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"));
224
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
        // 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;
Pino Toscano's avatar
Pino Toscano committed
246
                }
Albert Astals Cid's avatar
Albert Astals Cid committed
247 248
                rect.right = rect.left + stampxscale;
                rect.bottom = rect.top + stampyscale;
249
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
        }
        // 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);
        }
270

Albert Astals Cid's avatar
Albert Astals Cid committed
271 272
        m_creationCompleted = false;
        clicked = false;
273

Albert Astals Cid's avatar
Albert Astals Cid committed
274 275 276 277 278 279 280 281
        // 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());
282

Albert Astals Cid's avatar
Albert Astals Cid committed
283 284 285 286 287
        // 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;
288
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
289 290 291 292 293 294 295 296 297
        if (rect.bottom > 1) {
            rect.top -= rect.bottom - 1;
            rect.bottom = 1;
        }
        ann->setBoundingRectangle(rect);

        // return annotation
        return QList<Okular::Annotation *>() << ann;
    }
Enrico Ros's avatar
Enrico Ros committed
298

Albert Astals Cid's avatar
Albert Astals Cid committed
299 300 301 302 303 304 305 306 307 308 309 310
private:
    bool clicked;
    Okular::NormalizedRect rect;
    Okular::NormalizedPoint startpoint;
    Okular::NormalizedPoint point;
    QPixmap pixmap;
    QString hoverIconName, iconName;
    int size;
    double xscale, yscale;
    double pagewidth, pageheight;
    bool center;
    bool m_block;
311 312
};

Pino Toscano's avatar
Pino Toscano committed
313 314
/** @short PolyLineEngine */
class PolyLineEngine : public AnnotatorEngine
315
{
Albert Astals Cid's avatar
Albert Astals Cid committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
public:
    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;
    }
Pino Toscano's avatar
Pino Toscano committed
333

334 335 336 337 338 339 340 341 342 343 344 345 346 347
    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
Albert Astals Cid's avatar
Albert Astals Cid committed
348 349 350 351 352
    {
        // only proceed if pressing left button
        //            if ( button != Left )
        //                return rect;

353 354
        // Constrain to 15° steps, except first point of course.
        if (modifiers.constrainRatioAndAngle && !points.isEmpty()) {
355 356 357 358 359
            const Okular::NormalizedPoint constrainedPoint = constrainAngle(points.constLast(), nX, nY, xScale, yScale, M_PI / 12.);
            nX = constrainedPoint.x;
            nY = constrainedPoint.y;
        }
        // process button press
Albert Astals Cid's avatar
Albert Astals Cid committed
360 361 362 363 364
        if (type == Press) {
            newPoint.x = nX;
            newPoint.y = nY;
            if (button == Right)
                last = true;
365
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
        // 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);
383
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
384 385 386 387 388
            // 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);
389 390 391
            }
        }

Albert Astals Cid's avatar
Albert Astals Cid committed
392 393
        return rect;
    }
Tobias Koenig's avatar
Tobias Koenig committed
394

Albert Astals Cid's avatar
Albert Astals Cid committed
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
    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));
        }
    }
415

Albert Astals Cid's avatar
Albert Astals Cid committed
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
    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;
            QLinkedList<Okular::NormalizedPoint> list;
            for (int i = 0; i < points.count(); ++i)
                list.append(points[i]);

            la->setLinePoints(list);

            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());
451
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
452 453 454 455
            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());
456

Albert Astals Cid's avatar
Albert Astals Cid committed
457 458
            la->setBoundingRectangle(normRect);
        }
459

Albert Astals Cid's avatar
Albert Astals Cid committed
460 461 462
        // safety check
        if (!ann)
            return QList<Okular::Annotation *>();
463

Albert Astals Cid's avatar
Albert Astals Cid committed
464 465
        if (m_annotElement.hasAttribute(QStringLiteral("width")))
            ann->style().setWidth(m_annotElement.attribute(QStringLiteral("width")).toDouble());
466

Albert Astals Cid's avatar
Albert Astals Cid committed
467 468 469 470 471 472 473 474
        // 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;
    }
475

Albert Astals Cid's avatar
Albert Astals Cid committed
476 477 478 479 480 481 482 483 484 485
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;
486 487
};

488 489 490
/** @short TextSelectorEngine */
class TextSelectorEngine : public AnnotatorEngine
{
Albert Astals Cid's avatar
Albert Astals Cid committed
491 492 493 494 495 496 497
public:
    TextSelectorEngine(const QDomElement &engineElement, PageView *pageView)
        : AnnotatorEngine(engineElement)
        , m_pageView(pageView)
    {
        // parse engine specific attributes
    }
498

499
    QRect event(EventType type, Button button, Modifiers /*modifiers*/, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/) override
Albert Astals Cid's avatar
Albert Astals Cid committed
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
    {
        // 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;
Pino Toscano's avatar
Pino Toscano committed
525
                    }
Albert Astals Cid's avatar
Albert Astals Cid committed
526 527
                    rect |= newrect;
                    selection = std::move(newselection);
528 529
                }
            }
530
        } else if (type == Release) {
Albert Astals Cid's avatar
Albert Astals Cid committed
531
            m_creationCompleted = true;
532
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
533 534
        return rect;
    }
535

Albert Astals Cid's avatar
Albert Astals Cid committed
536 537 538 539 540 541 542 543 544
    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));
545 546
            }
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
547
    }
548

Albert Astals Cid's avatar
Albert Astals Cid committed
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
    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);
591
            }
Albert Astals Cid's avatar
Albert Astals Cid committed
592 593
            ann = ha;
        }
594

Albert Astals Cid's avatar
Albert Astals Cid committed
595
        selection.reset();
596

Albert Astals Cid's avatar
Albert Astals Cid committed
597 598 599
        // safety check
        if (!ann)
            return QList<Okular::Annotation *>();
600

Albert Astals Cid's avatar
Albert Astals Cid committed
601 602 603 604
        // 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());
605

Albert Astals Cid's avatar
Albert Astals Cid committed
606 607 608
        // return annotations
        return QList<Okular::Annotation *>() << ann;
    }
609

Albert Astals Cid's avatar
Albert Astals Cid committed
610 611 612 613
    QCursor cursor() const override
    {
        return Qt::IBeamCursor;
    }
614

Albert Astals Cid's avatar
Albert Astals Cid committed
615 616 617 618 619 620 621
private:
    // data
    PageView *m_pageView;
    // TODO: support more pages
    std::unique_ptr<Okular::RegularAreaRect> selection;
    Okular::NormalizedPoint lastPoint;
    QRect rect;
622 623
};

Simone Gaiarin's avatar
Simone Gaiarin committed
624 625
/** @short AnnotationTools*/
class AnnotationTools
626
{
Albert Astals Cid's avatar
Albert Astals Cid committed
627 628 629 630 631
public:
    AnnotationTools()
        : m_toolsCount(0)
    {
    }
632

Albert Astals Cid's avatar
Albert Astals Cid committed
633 634 635 636 637 638 639 640 641 642 643 644 645 646
    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";
647 648
            }
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
649
    }
650

Albert Astals Cid's avatar
Albert Astals Cid committed
651 652 653 654 655 656 657 658 659 660 661
    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();
Simone Gaiarin's avatar
Simone Gaiarin committed
662
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
663 664
        return tools;
    }
665

Albert Astals Cid's avatar
Albert Astals Cid committed
666 667 668 669 670
    QDomElement tool(int toolID)
    {
        QDomElement toolElement = m_toolsDefinition.documentElement().firstChildElement();
        while (!toolElement.isNull() && toolElement.attribute(QStringLiteral("id")).toInt() != toolID) {
            toolElement = toolElement.nextSiblingElement();
Simone Gaiarin's avatar
Simone Gaiarin committed
671
        }
Albert Astals Cid's avatar
Albert Astals Cid committed
672 673
        return toolElement; // can return a null element
    }
674

Albert Astals Cid's avatar
Albert Astals Cid committed
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
    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();
    }
692

693 694 695
    int findToolId(const QString &type)
    {
        int toolID = -1;
696 697 698
        if (type.isEmpty()) {
            return -1;
        }
699 700 701 702 703 704
        // 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();
        }
705 706 707 708 709 710
        if (!toolElement.isNull() && toolElement.hasAttribute(QStringLiteral("id"))) {
            bool ok;
            toolID = toolElement.attribute(QStringLiteral("id")).toInt(&ok);
            if (!ok) {
                return -1;
            }
711 712 713 714
        }
        return toolID;
    }

Albert Astals Cid's avatar
Albert Astals Cid committed
715 716 717
private:
    QDomDocument m_toolsDefinition;
    int m_toolsCount;
Simone Gaiarin's avatar
Simone Gaiarin committed
718
};
719

Simone Gaiarin's avatar
Simone Gaiarin committed
720 721
/** PageViewAnnotator **/
const int PageViewAnnotator::STAMP_TOOL_ID = 14;
722

Albert Astals Cid's avatar
Albert Astals Cid committed
723 724 725 726 727 728
PageViewAnnotator::PageViewAnnotator(PageView *parent, Okular::Document *storage)
    : QObject(parent)
    , m_document(storage)
    , m_pageView(parent)
    , m_actionHandler(nullptr)
    , m_engine(nullptr)
729
    , m_builtinToolsDefinition(nullptr)
Albert Astals Cid's avatar
Albert Astals Cid committed
730 731
    , m_quickToolsDefinition(nullptr)
    , m_continuousMode(true)
732
    , m_constrainRatioAndAngle(false)
Albert Astals Cid's avatar
Albert Astals Cid committed
733 734
    , m_lastToolID(-1)
    , m_lockedItem(nullptr)
735
{
Simone Gaiarin's avatar
Simone Gaiarin committed
736
    reparseConfig();
Pino Toscano's avatar
Pino Toscano committed
737 738
}

Simone Gaiarin's avatar
Simone Gaiarin committed
739
void PageViewAnnotator::reparseConfig()
Pino Toscano's avatar
Pino Toscano committed
740
{
Simone Gaiarin's avatar
Simone Gaiarin committed
741
    // Read tool list from configuration. It's a list of XML <tool></tool> elements
742 743 744
    if (!m_builtinToolsDefinition)
        m_builtinToolsDefinition = new AnnotationTools();
    m_builtinToolsDefinition->setTools(Okular::Settings::builtinAnnotationTools());
745

Albert Astals Cid's avatar
Albert Astals Cid committed
746
    if (!m_quickToolsDefinition)
Simone Gaiarin's avatar
Simone Gaiarin committed
747
        m_quickToolsDefinition = new AnnotationTools();
Albert Astals Cid's avatar
Albert Astals Cid committed
748
    m_quickToolsDefinition->setTools(Okular::Settings::quickAnnotationTools());
Simone Gaiarin's avatar
Simone Gaiarin committed
749 750 751

    m_continuousMode = Okular::Settings::annotationContinuousMode();

Albert Astals Cid's avatar
Albert Astals Cid committed
752
    if (Okular::Settings::identityAuthor().isEmpty())
Simone Gaiarin's avatar
Simone Gaiarin committed
753 754
        detachAnnotation();

Albert Astals Cid's avatar
Albert Astals Cid committed
755
    if (m_actionHandler)
Simone Gaiarin's avatar
Simone Gaiarin committed
756
        m_actionHandler->reparseTools();
757 758
}

Simone Gaiarin's avatar
Simone Gaiarin committed
759
PageViewAnnotator::~PageViewAnnotator()
760
{
Simone Gaiarin's avatar
Simone Gaiarin committed
761
    delete m_engine;
762 763
}

764
bool PageViewAnnotator::active() const
765
{
Simone Gaiarin's avatar
Simone Gaiarin committed
766
    return m_engine != nullptr;
767 768
}

769 770 771 772 773
bool PageViewAnnotator::annotating() const
{
    return active() && m_lockedItem;
}

774 775 776 777 778
QCursor PageViewAnnotator::cursor() const
{
    return m_engine->cursor();
}

779
QRect PageViewAnnotator::performRouteMouseOrTabletEvent(const AnnotatorEngine::EventType eventType, const AnnotatorEngine::Button button, const AnnotatorEngine::Modifiers modifiers, const QPointF pos, PageViewItem *item)
780
{
781 782 783
    // 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.
Albert Astals Cid's avatar
Albert Astals Cid committed
784
    if (m_engine->creationCompleted()) {
785 786 787
        return QRect();
    }

788
    // if the right mouse button was pressed, we simply do nothing. In this way, we are still editing the annotation
789
    // and so this function will receive and process the right mouse button release event too. If we detach now the annotation tool,
790
    // the release event will be processed by the PageView class which would create the annotation property widget, and we do not want this.
Albert Astals Cid's avatar
Albert Astals Cid committed
791
    if (button == AnnotatorEngine::Right && eventType == AnnotatorEngine::Press)
792
        return QRect();
Albert Astals Cid's avatar
Albert Astals Cid committed
793
    else if (button == AnnotatorEngine::Right && eventType == AnnotatorEngine::Release) {
794
        detachAnnotation();
795
        return QRect();
796
    }
797

Enrico Ros's avatar
Enrico Ros committed
798
    // 1. lock engine to current item
Albert Astals Cid's avatar
Albert Astals Cid committed
799
    if (!m_lockedItem && eventType == AnnotatorEngine::Press) {
800
        m_lockedItem = item;
Albert Astals Cid's avatar
Albert Astals Cid committed
801
        m_engine->setItem(m_lockedItem);
802
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
803
    if (!m_lockedItem) {
804 805 806 807
        return QRect();
    }

    // find out normalized mouse coords inside current item
Albert Astals Cid's avatar
Albert Astals Cid committed
808 809 810 811
    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);
812 813

    QRect modifiedRect;
814

Enrico Ros's avatar
Enrico Ros committed
815
    // 2. use engine to perform operations
816
    const QRect paintRect = m_engine->event(eventType, button, modifiers, nX, nY, itemRect.width(), itemRect.height(), m_lockedItem->page());
817

Enrico Ros's avatar
Enrico Ros committed
818
    // 3. update absolute extents rect and send paint event(s)
Albert Astals Cid's avatar
Albert Astals Cid committed
819
    if (paintRect.isValid()) {
820
        // 3.1. unite old and new painting regions
Albert Astals Cid's avatar
Albert Astals Cid committed
821
        QRegion compoundRegion(m_lastDrawnRect);
822
        m_lastDrawnRect = paintRect;
Albert Astals Cid's avatar
Albert Astals Cid committed
823
        m_lastDrawnRect.translate(itemRect.left(), itemRect.top());
824
        // 3.2. decompose paint region in rects and send paint events
Albert Astals Cid's avatar
Albert Astals Cid committed
825
        const QRegion rgn = compoundRegion.united(m_lastDrawnRect);
826
        const QPoint areaPos = m_pageView->contentAreaPosition();
Albert Astals Cid's avatar
Albert Astals Cid committed
827 828
        for (const QRect &r : rgn)
            m_pageView->viewport()->update(r.translated(-areaPos));
829
        modifiedRect = compoundRegion.boundingRect() | m_lastDrawnRect;
830 831
    }

Enrico Ros's avatar
Enrico Ros committed
832
    // 4. if engine has finished, apply Annotation to the page
Albert Astals Cid's avatar
Albert Astals Cid committed
833
    if (m_engine->creationCompleted()) {
834
        // apply engine data to the Annotation's and reset engine
Albert Astals Cid's avatar
Albert Astals Cid committed
835
        const QList<Okular::Annotation *> annotations = m_engine->end();
836
        // attach the newly filled annotations to the page
Albert Astals Cid's avatar
Albert Astals Cid committed
837 838 839 840 841 842 843 844 845 846 847
        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());
848
        }
849

Albert Astals Cid's avatar
Albert Astals Cid committed
850 851
        if (m_continuousMode)
            selectTool(m_lastToolID);
852 853
        else
            detachAnnotation();
854
    }
855 856

    return modifiedRect;
857 858
}

Albert Astals Cid's avatar
Albert Astals Cid committed
859
QRect PageViewAnnotator::routeMouseEvent(QMouseEvent *e, PageViewItem *item)
860 861 862
{
    AnnotatorEngine::EventType eventType;
    AnnotatorEngine::Button button;
863
    AnnotatorEngine::Modifiers modifiers;
864 865

    // figure out the event type and button
866 867 868 869
    AnnotatorEngine::decodeEvent(e, &eventType, &button);

    // Constrain angle if action checked XOR shift button pressed.
    modifiers.constrainRatioAndAngle = (bool(constrainRatioAndAngleActive()) != bool(e->modifiers() & Qt::ShiftModifier));
870

871
    return performRouteMouseOrTabletEvent(eventType, button, modifiers, e->localPos(), item);
872 873
}

Albert Astals Cid's avatar
Albert Astals Cid committed
874
QRect PageViewAnnotator::routeTabletEvent(QTabletEvent *e, PageViewItem *item, const QPoint localOriginInGlobal)
875 876 877
{
    // Unlike routeMouseEvent, routeTabletEvent must explicitly ignore events it doesn't care about so that
    // the corresponding mouse event will later be delivered.
Albert Astals Cid's avatar
Albert Astals Cid committed
878
    if (!item) {
879 880 881 882 883 884
        e->ignore();
        return QRect();
    }

    AnnotatorEngine::EventType eventType;
    AnnotatorEngine::Button button;
885
    AnnotatorEngine::Modifiers modifiers;
886 887

    // figure out the event type and button
888 889 890 891
    AnnotatorEngine::decodeEvent(e, &eventType, &button);

    // Constrain angle if action checked XOR shift button pressed.
    modifiers.constrainRatioAndAngle = (bool(constrainRatioAndAngleActive()) != bool(e->modifiers() & Qt::ShiftModifier));
892

893
    const QPointF globalPosF = e->globalPosF();
894
    const QPointF localPosF = globalPosF - localOriginInGlobal;
895
    return performRouteMouseOrTabletEvent(eventType, button, modifiers, localPosF, item);
896 897
}

Albert Astals Cid's avatar
Albert Astals Cid committed
898
bool PageViewAnnotator::routeKeyEvent(QKeyEvent *event)
899
{
Albert Astals Cid's avatar
Albert Astals Cid committed
900
    if (event->key() == Qt::Key_Escape) {
901
        detachAnnotation();
902 903 904 905 906
        return true;
    }
    return false;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
907
bool PageViewAnnotator::routePaints(const QRect wantedRect) const
908
{
Albert Astals Cid's avatar
Albert Astals Cid committed
909
    return m_engine && wantedRect.intersects(m_lastDrawnRect) && m_lockedItem;
910 911
}

Albert Astals Cid's avatar
Albert Astals Cid committed
912
void PageViewAnnotator::routePaint(QPainter *painter, const QRect paintRect)
913
{
Pino Toscano's avatar
Pino Toscano committed
914
    // if there's no locked item, then there's no decided place to draw on
Albert Astals Cid's avatar
Albert Astals Cid committed
915
    if (!m_lockedItem)
Pino Toscano's avatar
Pino Toscano committed
916 917
        return;

918
#ifndef NDEBUG
Enrico Ros's avatar
Enrico Ros committed
919
    // [DEBUG] draw the paint region if enabled
Albert Astals Cid's avatar
Albert Astals Cid committed
920 921
    if (Okular::Settings::debugDrawAnnotationRect())
        painter->drawRect(paintRect);
922 923
#endif
    // move painter to current itemGeometry rect
Albert Astals Cid's avatar
Albert Astals Cid committed
924
    const QRect &itemRect = m_lockedItem->uncroppedGeometry();
925
    painter->save();
Albert Astals Cid's avatar
Albert Astals Cid committed
926
    painter->translate(itemRect.topLeft());
927
    // TODO: Clip annotation painting to cropped page.
928 929

    // transform cliprect from absolute to item relative coords
Albert Astals Cid's avatar
Albert Astals Cid committed
930 931
    QRect annotRect = paintRect.intersected(m_lastDrawnRect);
    annotRect.translate(-itemRect.topLeft());
932

933
    // use current engine for painting (in virtual page coordinates)
Albert Astals Cid's avatar
Albert Astals Cid committed
934
    m_engine->paint(painter, m_lockedItem->uncroppedWidth(), m_lockedItem->uncroppedHeight(), annotRect);
935 936 937
    painter->restore();
}

Albert Astals Cid's avatar
Albert Astals Cid committed
938
void PageViewAnnotator::selectTool(int toolID)
939
{
Simone Gaiarin's avatar
Simone Gaiarin committed
940
    // ask for Author's name if not already set
Albert Astals Cid's avatar
Albert Astals Cid committed
941
    if (toolID > 0 && Okular::Settings::identityAuthor().isEmpty()) {
Simone Gaiarin's avatar
Simone Gaiarin committed
942 943
        // get default username from the kdelibs/kdecore/KUser
        KUser currentUser;
Albert Astals Cid's avatar
Albert Astals Cid committed
944
        QString userName = currentUser.property(KUser::FullName).toString();
Simone Gaiarin's avatar
Simone Gaiarin committed
945
        // ask the user for confirmation/change
Albert Astals Cid's avatar
Albert Astals Cid committed
946
        if (userName.isEmpty()) {
Simone Gaiarin's avatar
Simone Gaiarin committed
947
            bool ok = false;
Albert Astals Cid's avatar
Albert Astals Cid committed
948 949 950
            userName = QInputDialog::getText(nullptr, i18n("Bookmark annotation"), i18n("Insert a custom name for the annotation:"), QLineEdit::Normal, QString(), &ok);

            if (!ok) {
Simone Gaiarin's avatar
Simone Gaiarin committed
951 952 953 954 955
                detachAnnotation();
                return;
            }
        }
        // save the name
Albert Astals Cid's avatar
Albert Astals Cid committed
956
        Okular::Settings::setIdentityAuthor(userName);
Simone Gaiarin's avatar
Simone Gaiarin committed
957 958 959
        Okular::Settings::self()->save();
    }

960
    // terminate any previous operation
Albert Astals Cid's avatar
Albert Astals Cid committed
961
    if (m_engine) {
962
        delete m_engine;
963
        m_engine = nullptr;
964
    }
965
    m_lockedItem = nullptr;
Albert Astals Cid's avatar
Albert Astals Cid committed
966 967
    if (m_lastDrawnRect.isValid()) {
        m_pageView->viewport()->update(m_lastDrawnRect.translated(-m_pageView->contentAreaPosition()));
968 969 970 971 972 973 974
        m_lastDrawnRect = QRect();
    }

    // store current tool for later usage
    m_lastToolID = toolID;

    // handle tool deselection
Albert Astals Cid's avatar
Albert Astals Cid committed
975 976
    if (toolID == -1) {
        m_pageView->displayMessage(QString());
977
        m_pageView->updateCursor();
978 979 980
        return;
    }

Enrico Ros's avatar
Enrico Ros committed
981
    // for the selected tool create the Engine
982
    QDomElement toolElement = m_builtinToolsDefinition->tool(toolID);
Albert Astals Cid's avatar
Albert Astals Cid committed
983
    if (!toolElement.isNull()) {
Enrico Ros's avatar
Enrico Ros committed
984
        // parse tool properties
Albert Astals Cid's avatar
Albert Astals Cid committed
985 986
        QDomElement engineElement = toolElement.firstChildElement(QStringLiteral("engine"));
        if (!engineElement.isNull()) {
Enrico Ros's avatar