1 /*
2     SPDX-FileCopyrightText: 2017 Tobias Deiminger <haxtibal@t-online.de>
3     SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it>
4     SPDX-FileCopyrightText: 2004-2006 Albert Astals Cid <aacid@kde.org>
5 
6     Work sponsored by the LiMux project of the city of Munich:
7     SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
8 
9     With portions of code from kpdf/kpdf_pagewidget.cc by:
10     SPDX-FileCopyrightText: 2002 Wilco Greven <greven@kde.org>
11     SPDX-FileCopyrightText: 2003 Christophe Devriese <Christophe.Devriese@student.kuleuven.ac.be>
12     SPDX-FileCopyrightText: 2003 Laurent Montel <montel@kde.org>
13     SPDX-FileCopyrightText: 2003 Dirk Mueller <mueller@kde.org>
14     SPDX-FileCopyrightText: 2004 James Ots <kde@jamesots.com>
15     SPDX-FileCopyrightText: 2011 Jiri Baum - NICTA <jiri@baum.com.au>
16 
17     SPDX-License-Identifier: GPL-2.0-or-later
18 */
19 
20 #include "pageviewmouseannotation.h"
21 
22 #include <qevent.h>
23 #include <qpainter.h>
24 #include <qtooltip.h>
25 
26 #include "core/document.h"
27 #include "core/page.h"
28 #include "guiutils.h"
29 #include "pageview.h"
30 #include "videowidget.h"
31 
32 static const int handleSize = 10;
33 static const int handleSizeHalf = handleSize / 2;
34 
isValid() const35 bool AnnotationDescription::isValid() const
36 {
37     return (annotation != nullptr);
38 }
39 
isContainedInPage(const Okular::Document * document,int pageNumber) const40 bool AnnotationDescription::isContainedInPage(const Okular::Document *document, int pageNumber) const
41 {
42     if (AnnotationDescription::pageNumber == pageNumber) {
43         /* Don't access page via pageViewItem here. pageViewItem might have been deleted. */
44         const Okular::Page *page = document->page(pageNumber);
45         if (page != nullptr) {
46             if (page->annotations().contains(annotation)) {
47                 return true;
48             }
49         }
50     }
51     return false;
52 }
53 
invalidate()54 void AnnotationDescription::invalidate()
55 {
56     annotation = nullptr;
57     pageViewItem = nullptr;
58     pageNumber = -1;
59 }
60 
AnnotationDescription(PageViewItem * newPageViewItem,const QPoint eventPos)61 AnnotationDescription::AnnotationDescription(PageViewItem *newPageViewItem, const QPoint eventPos)
62 {
63     const Okular::AnnotationObjectRect *annObjRect = nullptr;
64     if (newPageViewItem) {
65         const QRect &uncroppedPage = newPageViewItem->uncroppedGeometry();
66         /* find out normalized mouse coords inside current item (nX and nY will be in the range of 0..1). */
67         const double nX = newPageViewItem->absToPageX(eventPos.x());
68         const double nY = newPageViewItem->absToPageY(eventPos.y());
69         annObjRect = (Okular::AnnotationObjectRect *)newPageViewItem->page()->objectRect(Okular::ObjectRect::OAnnotation, nX, nY, uncroppedPage.width(), uncroppedPage.height());
70     }
71 
72     if (annObjRect) {
73         annotation = annObjRect->annotation();
74         pageViewItem = newPageViewItem;
75         pageNumber = pageViewItem->pageNumber();
76     } else {
77         invalidate();
78     }
79 }
80 
MouseAnnotation(PageView * parent,Okular::Document * document)81 MouseAnnotation::MouseAnnotation(PageView *parent, Okular::Document *document)
82     : QObject(parent)
83     , m_document(document)
84     , m_pageView(parent)
85     , m_state(StateInactive)
86     , m_handle(RH_None)
87 {
88     m_resizeHandleList << RH_Left << RH_Right << RH_Top << RH_Bottom << RH_TopLeft << RH_TopRight << RH_BottomLeft << RH_BottomRight;
89 }
90 
~MouseAnnotation()91 MouseAnnotation::~MouseAnnotation()
92 {
93 }
94 
routeMousePressEvent(PageViewItem * pageViewItem,const QPoint eventPos)95 void MouseAnnotation::routeMousePressEvent(PageViewItem *pageViewItem, const QPoint eventPos)
96 {
97     /* Is there a selected annotation? */
98     if (m_focusedAnnotation.isValid()) {
99         m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
100         m_handle = getHandleAt(m_mousePosition, m_focusedAnnotation);
101         if (m_handle != RH_None) {
102             /* Returning here means, the selection-rectangle gets control, unconditionally.
103              * Even if it overlaps with another annotation. */
104             return;
105         }
106     }
107 
108     AnnotationDescription ad(pageViewItem, eventPos);
109     /* qDebug() << "routeMousePressEvent: eventPos = " << eventPos; */
110     if (ad.isValid()) {
111         if (ad.annotation->subType() == Okular::Annotation::AMovie || ad.annotation->subType() == Okular::Annotation::AScreen || ad.annotation->subType() == Okular::Annotation::AFileAttachment ||
112             ad.annotation->subType() == Okular::Annotation::ARichMedia) {
113             /* qDebug() << "routeMousePressEvent: trigger action for AMovie/AScreen/AFileAttachment"; */
114             processAction(ad);
115         } else {
116             /* qDebug() << "routeMousePressEvent: select for modification"; */
117             m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
118             m_handle = getHandleAt(m_mousePosition, ad);
119             if (m_handle != RH_None) {
120                 setState(StateFocused, ad);
121             }
122         }
123     } else {
124         /* qDebug() << "routeMousePressEvent: no annotation under mouse, enter StateInactive"; */
125         setState(StateInactive, ad);
126     }
127 }
128 
routeMouseReleaseEvent()129 void MouseAnnotation::routeMouseReleaseEvent()
130 {
131     if (isModified()) {
132         /* qDebug() << "routeMouseReleaseEvent: finish command"; */
133         finishCommand();
134         setState(StateFocused, m_focusedAnnotation);
135     }
136     /*
137     else
138     {
139         qDebug() << "routeMouseReleaseEvent: ignore";
140     }
141     */
142 }
143 
routeMouseMoveEvent(PageViewItem * pageViewItem,const QPoint eventPos,bool leftButtonPressed)144 void MouseAnnotation::routeMouseMoveEvent(PageViewItem *pageViewItem, const QPoint eventPos, bool leftButtonPressed)
145 {
146     if (!pageViewItem) {
147         /* qDebug() << "routeMouseMoveEvent: no pageViewItem provided, ignore"; */
148         return;
149     }
150 
151     if (leftButtonPressed) {
152         if (isFocused()) {
153             /* On first move event after annotation is selected, enter modification state */
154             if (m_handle == RH_Content) {
155                 /* qDebug() << "routeMouseMoveEvent: handle " << m_handle << ", enter StateMoving"; */
156                 setState(StateMoving, m_focusedAnnotation);
157             } else if (m_handle != RH_None) {
158                 /* qDebug() << "routeMouseMoveEvent: handle " << m_handle << ", enter StateResizing"; */
159                 setState(StateResizing, m_focusedAnnotation);
160             }
161         }
162 
163         if (isModified()) {
164             /* qDebug() << "routeMouseMoveEvent: perform command, delta " << eventPos - m_mousePosition; */
165             updateViewport(m_focusedAnnotation);
166             performCommand(eventPos);
167             m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
168             updateViewport(m_focusedAnnotation);
169         }
170     } else {
171         if (isFocused()) {
172             /* qDebug() << "routeMouseMoveEvent: update cursor for focused annotation, new eventPos " << eventPos; */
173             m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
174             m_handle = getHandleAt(m_mousePosition, m_focusedAnnotation);
175             m_pageView->updateCursor();
176         }
177 
178         /* We get here quite frequently. */
179         const AnnotationDescription ad(pageViewItem, eventPos);
180         m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
181         if (ad.isValid()) {
182             if (!(m_mouseOverAnnotation == ad)) {
183                 /* qDebug() << "routeMouseMoveEvent: Annotation under mouse (subtype " << ad.annotation->subType() << ", flags " << ad.annotation->flags() << ")"; */
184                 m_mouseOverAnnotation = ad;
185                 m_pageView->updateCursor();
186             }
187         } else {
188             if (!(m_mouseOverAnnotation == ad)) {
189                 /* qDebug() << "routeMouseMoveEvent: Annotation disappeared under mouse."; */
190                 m_mouseOverAnnotation.invalidate();
191                 m_pageView->updateCursor();
192             }
193         }
194     }
195 }
196 
routeKeyPressEvent(const QKeyEvent * e)197 void MouseAnnotation::routeKeyPressEvent(const QKeyEvent *e)
198 {
199     switch (e->key()) {
200     case Qt::Key_Escape:
201         cancel();
202         break;
203     case Qt::Key_Delete:
204         if (m_focusedAnnotation.isValid()) {
205             AnnotationDescription adToBeDeleted = m_focusedAnnotation;
206             cancel();
207             m_document->removePageAnnotation(adToBeDeleted.pageNumber, adToBeDeleted.annotation);
208         }
209         break;
210     }
211 }
212 
routeTooltipEvent(const QHelpEvent * helpEvent)213 void MouseAnnotation::routeTooltipEvent(const QHelpEvent *helpEvent)
214 {
215     /* qDebug() << "MouseAnnotation::routeTooltipEvent, event " << helpEvent; */
216     if (m_mouseOverAnnotation.isValid() && m_mouseOverAnnotation.annotation->subType() != Okular::Annotation::AWidget) {
217         /* get boundingRect in uncropped page coordinates */
218         QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(m_mouseOverAnnotation.annotation, m_mouseOverAnnotation.pageViewItem->uncroppedWidth(), m_mouseOverAnnotation.pageViewItem->uncroppedHeight());
219 
220         /* uncropped page to content area */
221         boundingRect.translate(m_mouseOverAnnotation.pageViewItem->uncroppedGeometry().topLeft());
222         /* content area to viewport */
223         boundingRect.translate(-m_pageView->contentAreaPosition());
224 
225         const QString tip = GuiUtils::prettyToolTip(m_mouseOverAnnotation.annotation);
226         QToolTip::showText(helpEvent->globalPos(), tip, m_pageView->viewport(), boundingRect);
227     }
228 }
229 
routePaint(QPainter * painter,const QRect paintRect)230 void MouseAnnotation::routePaint(QPainter *painter, const QRect paintRect)
231 {
232     /* QPainter draws relative to the origin of uncropped viewport. */
233     static const QColor borderColor = QColor::fromHsvF(0, 0, 1.0);
234     static const QColor fillColor = QColor::fromHsvF(0, 0, 0.75, 0.66);
235 
236     if (!isFocused())
237         return;
238     /*
239      * Get annotation bounding rectangle in uncropped page coordinates.
240      * Distinction between AnnotationUtils::annotationGeometry() and AnnotationObjectRect::boundingRect() is,
241      * that boundingRect would enlarge the QRect to a minimum size of 14 x 14.
242      * This is useful for getting focus an a very small annotation,
243      * but for drawing and modification we want the real size.
244      */
245     const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(m_focusedAnnotation.annotation, m_focusedAnnotation.pageViewItem->uncroppedWidth(), m_focusedAnnotation.pageViewItem->uncroppedHeight());
246 
247     if (!paintRect.intersects(boundingRect.translated(m_focusedAnnotation.pageViewItem->uncroppedGeometry().topLeft()).adjusted(-handleSizeHalf, -handleSizeHalf, handleSizeHalf, handleSizeHalf))) {
248         /* Our selection rectangle is not in a region that needs to be (re-)drawn. */
249         return;
250     }
251 
252     painter->save();
253     painter->translate(m_focusedAnnotation.pageViewItem->uncroppedGeometry().topLeft());
254     painter->setPen(QPen(fillColor, 2, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin));
255     painter->drawRect(boundingRect);
256     if (m_focusedAnnotation.annotation->canBeResized()) {
257         painter->setPen(borderColor);
258         painter->setBrush(fillColor);
259         for (const ResizeHandle &handle : qAsConst(m_resizeHandleList)) {
260             QRect rect = getHandleRect(handle, m_focusedAnnotation);
261             painter->drawRect(rect);
262         }
263     }
264     painter->restore();
265 }
266 
annotation() const267 Okular::Annotation *MouseAnnotation::annotation() const
268 {
269     if (m_focusedAnnotation.isValid()) {
270         return m_focusedAnnotation.annotation;
271     }
272     return nullptr;
273 }
274 
isActive() const275 bool MouseAnnotation::isActive() const
276 {
277     return (m_state != StateInactive);
278 }
279 
isMouseOver() const280 bool MouseAnnotation::isMouseOver() const
281 {
282     return (m_mouseOverAnnotation.isValid() || m_handle != RH_None);
283 }
284 
isFocused() const285 bool MouseAnnotation::isFocused() const
286 {
287     return (m_state == StateFocused);
288 }
289 
isMoved() const290 bool MouseAnnotation::isMoved() const
291 {
292     return (m_state == StateMoving);
293 }
294 
isResized() const295 bool MouseAnnotation::isResized() const
296 {
297     return (m_state == StateResizing);
298 }
299 
isModified() const300 bool MouseAnnotation::isModified() const
301 {
302     return (m_state == StateMoving || m_state == StateResizing);
303 }
304 
cursor() const305 Qt::CursorShape MouseAnnotation::cursor() const
306 {
307     if (m_handle != RH_None) {
308         if (isMoved()) {
309             return Qt::SizeAllCursor;
310         } else if (isFocused() || isResized()) {
311             switch (m_handle) {
312             case RH_Top:
313                 return Qt::SizeVerCursor;
314             case RH_TopRight:
315                 return Qt::SizeBDiagCursor;
316             case RH_Right:
317                 return Qt::SizeHorCursor;
318             case RH_BottomRight:
319                 return Qt::SizeFDiagCursor;
320             case RH_Bottom:
321                 return Qt::SizeVerCursor;
322             case RH_BottomLeft:
323                 return Qt::SizeBDiagCursor;
324             case RH_Left:
325                 return Qt::SizeHorCursor;
326             case RH_TopLeft:
327                 return Qt::SizeFDiagCursor;
328             case RH_Content:
329                 return Qt::SizeAllCursor;
330             default:
331                 return Qt::OpenHandCursor;
332             }
333         }
334     } else if (m_mouseOverAnnotation.isValid()) {
335         /* Mouse is over annotation, but the annotation is not yet selected. */
336         if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AMovie) {
337             return Qt::PointingHandCursor;
338         } else if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::ARichMedia) {
339             return Qt::PointingHandCursor;
340         } else if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AScreen) {
341             if (GuiUtils::renditionMovieFromScreenAnnotation(static_cast<const Okular::ScreenAnnotation *>(m_mouseOverAnnotation.annotation)) != nullptr) {
342                 return Qt::PointingHandCursor;
343             }
344         } else if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AFileAttachment) {
345             return Qt::PointingHandCursor;
346         } else {
347             return Qt::ArrowCursor;
348         }
349     }
350 
351     /* There's no none cursor, so we still have to return something. */
352     return Qt::ArrowCursor;
353 }
354 
notifyAnnotationChanged(int pageNumber)355 void MouseAnnotation::notifyAnnotationChanged(int pageNumber)
356 {
357     const AnnotationDescription emptyAd;
358 
359     if (m_focusedAnnotation.isValid() && !m_focusedAnnotation.isContainedInPage(m_document, pageNumber)) {
360         setState(StateInactive, emptyAd);
361     }
362 
363     if (m_mouseOverAnnotation.isValid() && !m_mouseOverAnnotation.isContainedInPage(m_document, pageNumber)) {
364         m_mouseOverAnnotation = emptyAd;
365         m_pageView->updateCursor();
366     }
367 }
368 
updateAnnotationPointers()369 void MouseAnnotation::updateAnnotationPointers()
370 {
371     if (m_focusedAnnotation.annotation) {
372         m_focusedAnnotation.annotation = m_document->page(m_focusedAnnotation.pageNumber)->annotation(m_focusedAnnotation.annotation->uniqueName());
373     }
374 
375     if (m_mouseOverAnnotation.annotation) {
376         m_mouseOverAnnotation.annotation = m_document->page(m_mouseOverAnnotation.pageNumber)->annotation(m_mouseOverAnnotation.annotation->uniqueName());
377     }
378 }
379 
cancel()380 void MouseAnnotation::cancel()
381 {
382     if (isActive()) {
383         finishCommand();
384         setState(StateInactive, m_focusedAnnotation);
385     }
386 }
387 
reset()388 void MouseAnnotation::reset()
389 {
390     cancel();
391     m_focusedAnnotation.invalidate();
392     m_mouseOverAnnotation.invalidate();
393 }
394 
395 /* Handle state changes for the focused annotation. */
setState(MouseAnnotationState state,const AnnotationDescription & ad)396 void MouseAnnotation::setState(MouseAnnotationState state, const AnnotationDescription &ad)
397 {
398     /* qDebug() << "setState: requested " << state; */
399     if (m_focusedAnnotation.isValid()) {
400         /* If there was a annotation before, request also repaint for the previous area. */
401         updateViewport(m_focusedAnnotation);
402     }
403 
404     if (!ad.isValid()) {
405         /* qDebug() << "No annotation provided, forcing state inactive." << state; */
406         state = StateInactive;
407     } else if ((state == StateMoving && !ad.annotation->canBeMoved()) || (state == StateResizing && !ad.annotation->canBeResized())) {
408         /* qDebug() << "Annotation does not support requested state, forcing state selected." << state; */
409         state = StateInactive;
410     }
411 
412     switch (state) {
413     case StateMoving:
414         m_focusedAnnotation = ad;
415         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() | Okular::Annotation::BeingMoved);
416         updateViewport(m_focusedAnnotation);
417         break;
418     case StateResizing:
419         m_focusedAnnotation = ad;
420         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() | Okular::Annotation::BeingResized);
421         updateViewport(m_focusedAnnotation);
422         break;
423     case StateFocused:
424         m_focusedAnnotation = ad;
425         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~(Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized));
426         updateViewport(m_focusedAnnotation);
427         break;
428     case StateInactive:
429     default:
430         if (m_focusedAnnotation.isValid()) {
431             m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~(Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized));
432         }
433         m_focusedAnnotation.invalidate();
434         m_handle = RH_None;
435     }
436 
437     /* qDebug() << "setState: enter " << state; */
438     m_state = state;
439     m_pageView->updateCursor();
440 }
441 
442 /* Get the rectangular boundary of the given annotation, enlarged for space needed by resize handles.
443  * Returns a QRect in page view item coordinates. */
getFullBoundingRect(const AnnotationDescription & ad) const444 QRect MouseAnnotation::getFullBoundingRect(const AnnotationDescription &ad) const
445 {
446     QRect boundingRect;
447     if (ad.isValid()) {
448         boundingRect = Okular::AnnotationUtils::annotationGeometry(ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight());
449         boundingRect = boundingRect.adjusted(-handleSizeHalf, -handleSizeHalf, handleSizeHalf, handleSizeHalf);
450     }
451     return boundingRect;
452 }
453 
454 /* Apply the command determined by m_state to the currently focused annotation. */
performCommand(const QPoint newPos)455 void MouseAnnotation::performCommand(const QPoint newPos)
456 {
457     const QRect &pageViewItemRect = m_focusedAnnotation.pageViewItem->uncroppedGeometry();
458     QPointF mouseDelta(newPos - pageViewItemRect.topLeft() - m_mousePosition);
459     QPointF normalizedRotatedMouseDelta(rotateInRect(QPointF(mouseDelta.x() / pageViewItemRect.width(), mouseDelta.y() / pageViewItemRect.height()), m_focusedAnnotation.pageViewItem->page()->rotation()));
460 
461     if (isMoved()) {
462         m_document->translatePageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, Okular::NormalizedPoint(normalizedRotatedMouseDelta.x(), normalizedRotatedMouseDelta.y()));
463     } else if (isResized()) {
464         QPointF delta1, delta2;
465         handleToAdjust(normalizedRotatedMouseDelta, delta1, delta2, m_handle, m_focusedAnnotation.pageViewItem->page()->rotation());
466         m_document->adjustPageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, Okular::NormalizedPoint(delta1.x(), delta1.y()), Okular::NormalizedPoint(delta2.x(), delta2.y()));
467     }
468 }
469 
470 /* Finalize a command in progress for the currently focused annotation. */
finishCommand()471 void MouseAnnotation::finishCommand()
472 {
473     /*
474      * Note:
475      * Translate-/resizePageAnnotation causes PopplerAnnotationProxy::notifyModification,
476      * where modify flag needs to be already cleared. So it is important to call
477      * setFlags before translatePageAnnotation-/adjustPageAnnotation.
478      */
479     if (isMoved()) {
480         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~Okular::Annotation::BeingMoved);
481         m_document->translatePageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, Okular::NormalizedPoint(0.0, 0.0));
482     } else if (isResized()) {
483         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~Okular::Annotation::BeingResized);
484         m_document->adjustPageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, Okular::NormalizedPoint(0.0, 0.0), Okular::NormalizedPoint(0.0, 0.0));
485     }
486 }
487 
488 /* Tell viewport widget that the rectangular of the given annotation needs to be repainted. */
updateViewport(const AnnotationDescription & ad) const489 void MouseAnnotation::updateViewport(const AnnotationDescription &ad) const
490 {
491     const QRect &changedPageViewItemRect = getFullBoundingRect(ad);
492     if (changedPageViewItemRect.isValid()) {
493         m_pageView->viewport()->update(changedPageViewItemRect.translated(ad.pageViewItem->uncroppedGeometry().topLeft()).translated(-m_pageView->contentAreaPosition()));
494     }
495 }
496 
497 /* eventPos: Mouse position in uncropped page coordinates.
498    ad: The annotation to get the handle for. */
getHandleAt(const QPoint eventPos,const AnnotationDescription & ad) const499 MouseAnnotation::ResizeHandle MouseAnnotation::getHandleAt(const QPoint eventPos, const AnnotationDescription &ad) const
500 {
501     ResizeHandle selected = RH_None;
502 
503     if (ad.annotation->canBeResized()) {
504         for (const ResizeHandle &handle : m_resizeHandleList) {
505             const QRect rect = getHandleRect(handle, ad);
506             if (rect.contains(eventPos)) {
507                 selected |= handle;
508             }
509         }
510 
511         /*
512          * Handles may overlap when selection is very small.
513          * Then it can happen that cursor is over more than one handles,
514          * and therefore maybe more than two flags are set.
515          * Favor one handle in that case.
516          */
517         if ((selected & RH_BottomRight) == RH_BottomRight)
518             return RH_BottomRight;
519         if ((selected & RH_TopRight) == RH_TopRight)
520             return RH_TopRight;
521         if ((selected & RH_TopLeft) == RH_TopLeft)
522             return RH_TopLeft;
523         if ((selected & RH_BottomLeft) == RH_BottomLeft)
524             return RH_BottomLeft;
525     }
526 
527     if (selected == RH_None && ad.annotation->canBeMoved()) {
528         const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight());
529         if (boundingRect.contains(eventPos)) {
530             return RH_Content;
531         }
532     }
533 
534     return selected;
535 }
536 
537 /* Get the rectangle for a specified resizie handle. */
getHandleRect(ResizeHandle handle,const AnnotationDescription & ad) const538 QRect MouseAnnotation::getHandleRect(ResizeHandle handle, const AnnotationDescription &ad) const
539 {
540     const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight());
541     int left, top;
542 
543     if (handle & RH_Top) {
544         top = boundingRect.top() - handleSizeHalf;
545     } else if (handle & RH_Bottom) {
546         top = boundingRect.bottom() - handleSizeHalf;
547     } else {
548         top = boundingRect.top() + boundingRect.height() / 2 - handleSizeHalf;
549     }
550 
551     if (handle & RH_Left) {
552         left = boundingRect.left() - handleSizeHalf;
553     } else if (handle & RH_Right) {
554         left = boundingRect.right() - handleSizeHalf;
555     } else {
556         left = boundingRect.left() + boundingRect.width() / 2 - handleSizeHalf;
557     }
558 
559     return QRect(left, top, handleSize, handleSize);
560 }
561 
562 /* Convert a resize handle delta into two adjust delta coordinates. */
handleToAdjust(const QPointF dIn,QPointF & dOut1,QPointF & dOut2,MouseAnnotation::ResizeHandle handle,Okular::Rotation rotation)563 void MouseAnnotation::handleToAdjust(const QPointF dIn, QPointF &dOut1, QPointF &dOut2, MouseAnnotation::ResizeHandle handle, Okular::Rotation rotation)
564 {
565     const MouseAnnotation::ResizeHandle rotatedHandle = MouseAnnotation::rotateHandle(handle, rotation);
566     dOut1.rx() = (rotatedHandle & MouseAnnotation::RH_Left) ? dIn.x() : 0;
567     dOut1.ry() = (rotatedHandle & MouseAnnotation::RH_Top) ? dIn.y() : 0;
568     dOut2.rx() = (rotatedHandle & MouseAnnotation::RH_Right) ? dIn.x() : 0;
569     dOut2.ry() = (rotatedHandle & MouseAnnotation::RH_Bottom) ? dIn.y() : 0;
570 }
571 
rotateInRect(const QPointF rotated,Okular::Rotation rotation)572 QPointF MouseAnnotation::rotateInRect(const QPointF rotated, Okular::Rotation rotation)
573 {
574     QPointF ret;
575 
576     switch (rotation) {
577     case Okular::Rotation90:
578         ret = QPointF(rotated.y(), -rotated.x());
579         break;
580     case Okular::Rotation180:
581         ret = QPointF(-rotated.x(), -rotated.y());
582         break;
583     case Okular::Rotation270:
584         ret = QPointF(-rotated.y(), rotated.x());
585         break;
586     case Okular::Rotation0: /* no modifications */
587     default:                /* other cases */
588         ret = rotated;
589     }
590 
591     return ret;
592 }
593 
rotateHandle(MouseAnnotation::ResizeHandle handle,Okular::Rotation rotation)594 MouseAnnotation::ResizeHandle MouseAnnotation::rotateHandle(MouseAnnotation::ResizeHandle handle, Okular::Rotation rotation)
595 {
596     unsigned int rotatedHandle = 0;
597     switch (rotation) {
598     case Okular::Rotation90:
599         /* bit rotation: #1 => #4, #2 => #1, #3 => #2, #4 => #3 */
600         rotatedHandle = (handle << 3 | handle >> (4 - 3)) & RH_AllHandles;
601         break;
602     case Okular::Rotation180:
603         /* bit rotation: #1 => #3, #2 => #4, #3 => #1, #4 => #2 */
604         rotatedHandle = (handle << 2 | handle >> (4 - 2)) & RH_AllHandles;
605         break;
606     case Okular::Rotation270:
607         /* bit rotation: #1 => #2, #2 => #3, #3 => #4, #4 => #1 */
608         rotatedHandle = (handle << 1 | handle >> (4 - 1)) & RH_AllHandles;
609         break;
610     case Okular::Rotation0: /* no modifications */
611     default:                /* other cases */
612         rotatedHandle = handle;
613         break;
614     }
615     return (MouseAnnotation::ResizeHandle)rotatedHandle;
616 }
617 
618 /* Start according action for AMovie/ARichMedia/AScreen/AFileAttachment.
619  * It was formerly (before mouse annotation refactoring) called on mouse release event.
620  * Now it's called on mouse press. Should we keep the former behavior? */
processAction(const AnnotationDescription & ad)621 void MouseAnnotation::processAction(const AnnotationDescription &ad)
622 {
623     if (ad.isValid()) {
624         Okular::Annotation *ann = ad.annotation;
625         PageViewItem *pageItem = ad.pageViewItem;
626 
627         if (ann->subType() == Okular::Annotation::AMovie) {
628             VideoWidget *vw = pageItem->videoWidgets().value(static_cast<Okular::MovieAnnotation *>(ann)->movie());
629             vw->show();
630             vw->play();
631         } else if (ann->subType() == Okular::Annotation::ARichMedia) {
632             VideoWidget *vw = pageItem->videoWidgets().value(static_cast<Okular::RichMediaAnnotation *>(ann)->movie());
633             vw->show();
634             vw->play();
635         } else if (ann->subType() == Okular::Annotation::AScreen) {
636             m_document->processAction(static_cast<Okular::ScreenAnnotation *>(ann)->action());
637         } else if (ann->subType() == Okular::Annotation::AFileAttachment) {
638             const Okular::FileAttachmentAnnotation *fileAttachAnnot = static_cast<Okular::FileAttachmentAnnotation *>(ann);
639             GuiUtils::saveEmbeddedFile(fileAttachAnnot->embeddedFile(), m_pageView);
640         }
641     }
642 }
643