File indexing completed on 2024-04-28 15:51:51

0001 /*
0002     SPDX-FileCopyrightText: 2017 Tobias Deiminger <haxtibal@t-online.de>
0003     SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it>
0004     SPDX-FileCopyrightText: 2004-2006 Albert Astals Cid <aacid@kde.org>
0005 
0006     Work sponsored by the LiMux project of the city of Munich:
0007     SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
0008 
0009     With portions of code from kpdf/kpdf_pagewidget.cc by:
0010     SPDX-FileCopyrightText: 2002 Wilco Greven <greven@kde.org>
0011     SPDX-FileCopyrightText: 2003 Christophe Devriese <Christophe.Devriese@student.kuleuven.ac.be>
0012     SPDX-FileCopyrightText: 2003 Laurent Montel <montel@kde.org>
0013     SPDX-FileCopyrightText: 2003 Dirk Mueller <mueller@kde.org>
0014     SPDX-FileCopyrightText: 2004 James Ots <kde@jamesots.com>
0015     SPDX-FileCopyrightText: 2011 Jiri Baum - NICTA <jiri@baum.com.au>
0016 
0017     SPDX-License-Identifier: GPL-2.0-or-later
0018 */
0019 
0020 #include "pageviewmouseannotation.h"
0021 
0022 #include <qevent.h>
0023 #include <qpainter.h>
0024 #include <qtooltip.h>
0025 
0026 #include "core/document.h"
0027 #include "core/page.h"
0028 #include "gui/guiutils.h"
0029 #include "pageview.h"
0030 #include "videowidget.h"
0031 
0032 static const int handleSize = 10;
0033 static const int handleSizeHalf = handleSize / 2;
0034 
0035 bool AnnotationDescription::isValid() const
0036 {
0037     return (annotation != nullptr);
0038 }
0039 
0040 bool AnnotationDescription::isContainedInPage(const Okular::Document *document, int pageNumber) const
0041 {
0042     if (AnnotationDescription::pageNumber == pageNumber) {
0043         /* Don't access page via pageViewItem here. pageViewItem might have been deleted. */
0044         const Okular::Page *page = document->page(pageNumber);
0045         if (page != nullptr) {
0046             if (page->annotations().contains(annotation)) {
0047                 return true;
0048             }
0049         }
0050     }
0051     return false;
0052 }
0053 
0054 void AnnotationDescription::invalidate()
0055 {
0056     annotation = nullptr;
0057     pageViewItem = nullptr;
0058     pageNumber = -1;
0059 }
0060 
0061 AnnotationDescription::AnnotationDescription(PageViewItem *newPageViewItem, const QPoint eventPos)
0062 {
0063     const Okular::AnnotationObjectRect *annObjRect = nullptr;
0064     if (newPageViewItem) {
0065         const QRect &uncroppedPage = newPageViewItem->uncroppedGeometry();
0066         /* find out normalized mouse coords inside current item (nX and nY will be in the range of 0..1). */
0067         const double nX = newPageViewItem->absToPageX(eventPos.x());
0068         const double nY = newPageViewItem->absToPageY(eventPos.y());
0069         annObjRect = (Okular::AnnotationObjectRect *)newPageViewItem->page()->objectRect(Okular::ObjectRect::OAnnotation, nX, nY, uncroppedPage.width(), uncroppedPage.height());
0070     }
0071 
0072     if (annObjRect) {
0073         annotation = annObjRect->annotation();
0074         pageViewItem = newPageViewItem;
0075         pageNumber = pageViewItem->pageNumber();
0076     } else {
0077         invalidate();
0078     }
0079 }
0080 
0081 MouseAnnotation::MouseAnnotation(PageView *parent, Okular::Document *document)
0082     : QObject(parent)
0083     , m_document(document)
0084     , m_pageView(parent)
0085     , m_state(StateInactive)
0086     , m_handle(RH_None)
0087 {
0088     m_resizeHandleList << RH_Left << RH_Right << RH_Top << RH_Bottom << RH_TopLeft << RH_TopRight << RH_BottomLeft << RH_BottomRight;
0089 }
0090 
0091 MouseAnnotation::~MouseAnnotation()
0092 {
0093 }
0094 
0095 void MouseAnnotation::routeMousePressEvent(PageViewItem *pageViewItem, const QPoint eventPos)
0096 {
0097     /* Is there a selected annotation? */
0098     if (m_focusedAnnotation.isValid()) {
0099         m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
0100         m_handle = getHandleAt(m_mousePosition, m_focusedAnnotation);
0101         if (m_handle != RH_None) {
0102             /* Returning here means, the selection-rectangle gets control, unconditionally.
0103              * Even if it overlaps with another annotation. */
0104             return;
0105         }
0106     }
0107 
0108     AnnotationDescription ad(pageViewItem, eventPos);
0109     /* qDebug() << "routeMousePressEvent: eventPos = " << eventPos; */
0110     if (ad.isValid()) {
0111         if (ad.annotation->subType() == Okular::Annotation::AMovie || ad.annotation->subType() == Okular::Annotation::AScreen || ad.annotation->subType() == Okular::Annotation::AFileAttachment ||
0112             ad.annotation->subType() == Okular::Annotation::ARichMedia) {
0113             /* qDebug() << "routeMousePressEvent: trigger action for AMovie/AScreen/AFileAttachment"; */
0114             processAction(ad);
0115         } else {
0116             /* qDebug() << "routeMousePressEvent: select for modification"; */
0117             m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
0118             m_handle = getHandleAt(m_mousePosition, ad);
0119             if (m_handle != RH_None) {
0120                 setState(StateFocused, ad);
0121             }
0122         }
0123     } else {
0124         /* qDebug() << "routeMousePressEvent: no annotation under mouse, enter StateInactive"; */
0125         setState(StateInactive, ad);
0126     }
0127 }
0128 
0129 void MouseAnnotation::routeMouseReleaseEvent()
0130 {
0131     if (isModified()) {
0132         /* qDebug() << "routeMouseReleaseEvent: finish command"; */
0133         finishCommand();
0134         setState(StateFocused, m_focusedAnnotation);
0135     }
0136     /*
0137     else
0138     {
0139         qDebug() << "routeMouseReleaseEvent: ignore";
0140     }
0141     */
0142 }
0143 
0144 void MouseAnnotation::routeMouseMoveEvent(PageViewItem *pageViewItem, const QPoint eventPos, bool leftButtonPressed)
0145 {
0146     if (!pageViewItem) {
0147         /* qDebug() << "routeMouseMoveEvent: no pageViewItem provided, ignore"; */
0148         return;
0149     }
0150 
0151     if (leftButtonPressed) {
0152         if (isFocused()) {
0153             /* On first move event after annotation is selected, enter modification state */
0154             if (m_handle == RH_Content) {
0155                 /* qDebug() << "routeMouseMoveEvent: handle " << m_handle << ", enter StateMoving"; */
0156                 setState(StateMoving, m_focusedAnnotation);
0157             } else if (m_handle != RH_None) {
0158                 /* qDebug() << "routeMouseMoveEvent: handle " << m_handle << ", enter StateResizing"; */
0159                 setState(StateResizing, m_focusedAnnotation);
0160             }
0161         }
0162 
0163         if (isModified()) {
0164             /* qDebug() << "routeMouseMoveEvent: perform command, delta " << eventPos - m_mousePosition; */
0165             updateViewport(m_focusedAnnotation);
0166             performCommand(eventPos);
0167             m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
0168             updateViewport(m_focusedAnnotation);
0169         }
0170     } else {
0171         if (isFocused()) {
0172             /* qDebug() << "routeMouseMoveEvent: update cursor for focused annotation, new eventPos " << eventPos; */
0173             m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
0174             m_handle = getHandleAt(m_mousePosition, m_focusedAnnotation);
0175             m_pageView->updateCursor();
0176         }
0177 
0178         /* We get here quite frequently. */
0179         const AnnotationDescription ad(pageViewItem, eventPos);
0180         m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
0181         if (ad.isValid()) {
0182             if (!(m_mouseOverAnnotation == ad)) {
0183                 /* qDebug() << "routeMouseMoveEvent: Annotation under mouse (subtype " << ad.annotation->subType() << ", flags " << ad.annotation->flags() << ")"; */
0184                 m_mouseOverAnnotation = ad;
0185                 m_pageView->updateCursor();
0186             }
0187         } else {
0188             if (!(m_mouseOverAnnotation == ad)) {
0189                 /* qDebug() << "routeMouseMoveEvent: Annotation disappeared under mouse."; */
0190                 m_mouseOverAnnotation.invalidate();
0191                 m_pageView->updateCursor();
0192             }
0193         }
0194     }
0195 }
0196 
0197 void MouseAnnotation::routeKeyPressEvent(const QKeyEvent *e)
0198 {
0199     switch (e->key()) {
0200     case Qt::Key_Escape:
0201         cancel();
0202         break;
0203     case Qt::Key_Delete:
0204         if (m_focusedAnnotation.isValid()) {
0205             AnnotationDescription adToBeDeleted = m_focusedAnnotation;
0206             cancel();
0207             m_document->removePageAnnotation(adToBeDeleted.pageNumber, adToBeDeleted.annotation);
0208         }
0209         break;
0210     }
0211 }
0212 
0213 void MouseAnnotation::routeTooltipEvent(const QHelpEvent *helpEvent)
0214 {
0215     /* qDebug() << "MouseAnnotation::routeTooltipEvent, event " << helpEvent; */
0216     if (m_mouseOverAnnotation.isValid() && m_mouseOverAnnotation.annotation->subType() != Okular::Annotation::AWidget) {
0217         /* get boundingRect in uncropped page coordinates */
0218         QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(m_mouseOverAnnotation.annotation, m_mouseOverAnnotation.pageViewItem->uncroppedWidth(), m_mouseOverAnnotation.pageViewItem->uncroppedHeight());
0219 
0220         /* uncropped page to content area */
0221         boundingRect.translate(m_mouseOverAnnotation.pageViewItem->uncroppedGeometry().topLeft());
0222         /* content area to viewport */
0223         boundingRect.translate(-m_pageView->contentAreaPosition());
0224 
0225         const QString tip = GuiUtils::prettyToolTip(m_mouseOverAnnotation.annotation);
0226         QToolTip::showText(helpEvent->globalPos(), tip, m_pageView->viewport(), boundingRect);
0227     }
0228 }
0229 
0230 void MouseAnnotation::routePaint(QPainter *painter, const QRect paintRect)
0231 {
0232     /* QPainter draws relative to the origin of uncropped viewport. */
0233     static const QColor borderColor = QColor::fromHsvF(0, 0, 1.0);
0234     static const QColor fillColor = QColor::fromHsvF(0, 0, 0.75, 0.66);
0235 
0236     if (!isFocused()) {
0237         return;
0238     }
0239     /*
0240      * Get annotation bounding rectangle in uncropped page coordinates.
0241      * Distinction between AnnotationUtils::annotationGeometry() and AnnotationObjectRect::boundingRect() is,
0242      * that boundingRect would enlarge the QRect to a minimum size of 14 x 14.
0243      * This is useful for getting focus an a very small annotation,
0244      * but for drawing and modification we want the real size.
0245      */
0246     const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(m_focusedAnnotation.annotation, m_focusedAnnotation.pageViewItem->uncroppedWidth(), m_focusedAnnotation.pageViewItem->uncroppedHeight());
0247 
0248     if (!paintRect.intersects(boundingRect.translated(m_focusedAnnotation.pageViewItem->uncroppedGeometry().topLeft()).adjusted(-handleSizeHalf, -handleSizeHalf, handleSizeHalf, handleSizeHalf))) {
0249         /* Our selection rectangle is not in a region that needs to be (re-)drawn. */
0250         return;
0251     }
0252 
0253     painter->save();
0254     painter->translate(m_focusedAnnotation.pageViewItem->uncroppedGeometry().topLeft());
0255     painter->setPen(QPen(fillColor, 2, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin));
0256     painter->drawRect(boundingRect);
0257     if (m_focusedAnnotation.annotation->canBeResized()) {
0258         painter->setPen(borderColor);
0259         painter->setBrush(fillColor);
0260         for (const ResizeHandle &handle : std::as_const(m_resizeHandleList)) {
0261             QRect rect = getHandleRect(handle, m_focusedAnnotation);
0262             painter->drawRect(rect);
0263         }
0264     }
0265     painter->restore();
0266 }
0267 
0268 Okular::Annotation *MouseAnnotation::annotation() const
0269 {
0270     if (m_focusedAnnotation.isValid()) {
0271         return m_focusedAnnotation.annotation;
0272     }
0273     return nullptr;
0274 }
0275 
0276 bool MouseAnnotation::isActive() const
0277 {
0278     return (m_state != StateInactive);
0279 }
0280 
0281 bool MouseAnnotation::isMouseOver() const
0282 {
0283     return (m_mouseOverAnnotation.isValid() || m_handle != RH_None);
0284 }
0285 
0286 bool MouseAnnotation::isFocused() const
0287 {
0288     return (m_state == StateFocused);
0289 }
0290 
0291 bool MouseAnnotation::isMoved() const
0292 {
0293     return (m_state == StateMoving);
0294 }
0295 
0296 bool MouseAnnotation::isResized() const
0297 {
0298     return (m_state == StateResizing);
0299 }
0300 
0301 bool MouseAnnotation::isModified() const
0302 {
0303     return (m_state == StateMoving || m_state == StateResizing);
0304 }
0305 
0306 Qt::CursorShape MouseAnnotation::cursor() const
0307 {
0308     if (m_handle != RH_None) {
0309         if (isMoved()) {
0310             return Qt::SizeAllCursor;
0311         } else if (isFocused() || isResized()) {
0312             switch (m_handle) {
0313             case RH_Top:
0314                 return Qt::SizeVerCursor;
0315             case RH_TopRight:
0316                 return Qt::SizeBDiagCursor;
0317             case RH_Right:
0318                 return Qt::SizeHorCursor;
0319             case RH_BottomRight:
0320                 return Qt::SizeFDiagCursor;
0321             case RH_Bottom:
0322                 return Qt::SizeVerCursor;
0323             case RH_BottomLeft:
0324                 return Qt::SizeBDiagCursor;
0325             case RH_Left:
0326                 return Qt::SizeHorCursor;
0327             case RH_TopLeft:
0328                 return Qt::SizeFDiagCursor;
0329             case RH_Content:
0330                 return Qt::SizeAllCursor;
0331             default:
0332                 return Qt::OpenHandCursor;
0333             }
0334         }
0335     } else if (m_mouseOverAnnotation.isValid()) {
0336         /* Mouse is over annotation, but the annotation is not yet selected. */
0337         if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AMovie) {
0338             return Qt::PointingHandCursor;
0339         } else if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::ARichMedia) {
0340             return Qt::PointingHandCursor;
0341         } else if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AScreen) {
0342             if (GuiUtils::renditionMovieFromScreenAnnotation(static_cast<const Okular::ScreenAnnotation *>(m_mouseOverAnnotation.annotation)) != nullptr) {
0343                 return Qt::PointingHandCursor;
0344             }
0345         } else if (m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AFileAttachment) {
0346             return Qt::PointingHandCursor;
0347         } else {
0348             return Qt::ArrowCursor;
0349         }
0350     }
0351 
0352     /* There's no none cursor, so we still have to return something. */
0353     return Qt::ArrowCursor;
0354 }
0355 
0356 void MouseAnnotation::notifyAnnotationChanged(int pageNumber)
0357 {
0358     const AnnotationDescription emptyAd;
0359 
0360     if (m_focusedAnnotation.isValid() && !m_focusedAnnotation.isContainedInPage(m_document, pageNumber)) {
0361         setState(StateInactive, emptyAd);
0362     }
0363 
0364     if (m_mouseOverAnnotation.isValid() && !m_mouseOverAnnotation.isContainedInPage(m_document, pageNumber)) {
0365         m_mouseOverAnnotation = emptyAd;
0366         m_pageView->updateCursor();
0367     }
0368 }
0369 
0370 void MouseAnnotation::updateAnnotationPointers()
0371 {
0372     if (m_focusedAnnotation.annotation) {
0373         m_focusedAnnotation.annotation = m_document->page(m_focusedAnnotation.pageNumber)->annotation(m_focusedAnnotation.annotation->uniqueName());
0374     }
0375 
0376     if (m_mouseOverAnnotation.annotation) {
0377         m_mouseOverAnnotation.annotation = m_document->page(m_mouseOverAnnotation.pageNumber)->annotation(m_mouseOverAnnotation.annotation->uniqueName());
0378     }
0379 }
0380 
0381 void MouseAnnotation::cancel()
0382 {
0383     if (isActive()) {
0384         finishCommand();
0385         setState(StateInactive, m_focusedAnnotation);
0386     }
0387 }
0388 
0389 void MouseAnnotation::reset()
0390 {
0391     cancel();
0392     m_focusedAnnotation.invalidate();
0393     m_mouseOverAnnotation.invalidate();
0394 }
0395 
0396 /* Handle state changes for the focused annotation. */
0397 void MouseAnnotation::setState(MouseAnnotationState state, const AnnotationDescription &ad)
0398 {
0399     /* qDebug() << "setState: requested " << state; */
0400     if (m_focusedAnnotation.isValid()) {
0401         /* If there was a annotation before, request also repaint for the previous area. */
0402         updateViewport(m_focusedAnnotation);
0403     }
0404 
0405     if (!ad.isValid()) {
0406         /* qDebug() << "No annotation provided, forcing state inactive." << state; */
0407         state = StateInactive;
0408     } else if ((state == StateMoving && !ad.annotation->canBeMoved()) || (state == StateResizing && !ad.annotation->canBeResized())) {
0409         /* qDebug() << "Annotation does not support requested state, forcing state selected." << state; */
0410         state = StateInactive;
0411     }
0412 
0413     switch (state) {
0414     case StateMoving:
0415         m_focusedAnnotation = ad;
0416         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() | Okular::Annotation::BeingMoved);
0417         updateViewport(m_focusedAnnotation);
0418         break;
0419     case StateResizing:
0420         m_focusedAnnotation = ad;
0421         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() | Okular::Annotation::BeingResized);
0422         updateViewport(m_focusedAnnotation);
0423         break;
0424     case StateFocused:
0425         m_focusedAnnotation = ad;
0426         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~(Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized));
0427         updateViewport(m_focusedAnnotation);
0428         break;
0429     case StateInactive:
0430     default:
0431         if (m_focusedAnnotation.isValid()) {
0432             m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~(Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized));
0433         }
0434         m_focusedAnnotation.invalidate();
0435         m_handle = RH_None;
0436     }
0437 
0438     /* qDebug() << "setState: enter " << state; */
0439     m_state = state;
0440     m_pageView->updateCursor();
0441 }
0442 
0443 /* Get the rectangular boundary of the given annotation, enlarged for space needed by resize handles.
0444  * Returns a QRect in page view item coordinates. */
0445 QRect MouseAnnotation::getFullBoundingRect(const AnnotationDescription &ad) const
0446 {
0447     QRect boundingRect;
0448     if (ad.isValid()) {
0449         boundingRect = Okular::AnnotationUtils::annotationGeometry(ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight());
0450         boundingRect = boundingRect.adjusted(-handleSizeHalf, -handleSizeHalf, handleSizeHalf, handleSizeHalf);
0451     }
0452     return boundingRect;
0453 }
0454 
0455 /* Apply the command determined by m_state to the currently focused annotation. */
0456 void MouseAnnotation::performCommand(const QPoint newPos)
0457 {
0458     const QRect &pageViewItemRect = m_focusedAnnotation.pageViewItem->uncroppedGeometry();
0459     QPointF mouseDelta(newPos - pageViewItemRect.topLeft() - m_mousePosition);
0460     QPointF normalizedRotatedMouseDelta(rotateInRect(QPointF(mouseDelta.x() / pageViewItemRect.width(), mouseDelta.y() / pageViewItemRect.height()), m_focusedAnnotation.pageViewItem->page()->rotation()));
0461 
0462     if (isMoved()) {
0463         Okular::NormalizedPoint delta(normalizedRotatedMouseDelta.x(), normalizedRotatedMouseDelta.y());
0464         const Okular::NormalizedRect annotRect = m_focusedAnnotation.annotation->boundingRectangle();
0465 
0466         // if moving annot to the left && delta.x is big enough to move annot outside the page
0467         if (delta.x < 0 && (annotRect.left + delta.x) < 0) {
0468             delta.x = -annotRect.left; // update delta.x to move annot only to the left edge of the page
0469         }
0470         // similar checks for right, top and bottom
0471         if (delta.x > 0 && (annotRect.right + delta.x) > 1) {
0472             delta.x = 1 - annotRect.right;
0473         }
0474         if (delta.y < 0 && (annotRect.top + delta.y) < 0) {
0475             delta.y = -annotRect.top;
0476         }
0477         if (delta.y > 0 && (annotRect.bottom + delta.y) > 1) {
0478             delta.y = 1 - annotRect.bottom;
0479         }
0480         m_document->translatePageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, delta);
0481 
0482     } else if (isResized()) {
0483         QPointF delta1, delta2;
0484         handleToAdjust(normalizedRotatedMouseDelta, delta1, delta2, m_handle, m_focusedAnnotation.pageViewItem->page()->rotation());
0485         m_document->adjustPageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, Okular::NormalizedPoint(delta1.x(), delta1.y()), Okular::NormalizedPoint(delta2.x(), delta2.y()));
0486     }
0487 }
0488 
0489 /* Finalize a command in progress for the currently focused annotation. */
0490 void MouseAnnotation::finishCommand()
0491 {
0492     /*
0493      * Note:
0494      * Translate-/resizePageAnnotation causes PopplerAnnotationProxy::notifyModification,
0495      * where modify flag needs to be already cleared. So it is important to call
0496      * setFlags before translatePageAnnotation-/adjustPageAnnotation.
0497      */
0498     if (isMoved()) {
0499         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~Okular::Annotation::BeingMoved);
0500         m_document->translatePageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, Okular::NormalizedPoint(0.0, 0.0));
0501     } else if (isResized()) {
0502         m_focusedAnnotation.annotation->setFlags(m_focusedAnnotation.annotation->flags() & ~Okular::Annotation::BeingResized);
0503         m_document->adjustPageAnnotation(m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation, Okular::NormalizedPoint(0.0, 0.0), Okular::NormalizedPoint(0.0, 0.0));
0504     }
0505 }
0506 
0507 /* Tell viewport widget that the rectangular of the given annotation needs to be repainted. */
0508 void MouseAnnotation::updateViewport(const AnnotationDescription &ad) const
0509 {
0510     const QRect &changedPageViewItemRect = getFullBoundingRect(ad);
0511     if (changedPageViewItemRect.isValid()) {
0512         m_pageView->viewport()->update(changedPageViewItemRect.translated(ad.pageViewItem->uncroppedGeometry().topLeft()).translated(-m_pageView->contentAreaPosition()));
0513     }
0514 }
0515 
0516 /* eventPos: Mouse position in uncropped page coordinates.
0517    ad: The annotation to get the handle for. */
0518 MouseAnnotation::ResizeHandle MouseAnnotation::getHandleAt(const QPoint eventPos, const AnnotationDescription &ad) const
0519 {
0520     ResizeHandle selected = RH_None;
0521 
0522     if (ad.annotation->canBeResized()) {
0523         for (const ResizeHandle &handle : m_resizeHandleList) {
0524             const QRect rect = getHandleRect(handle, ad);
0525             if (rect.contains(eventPos)) {
0526                 selected |= handle;
0527             }
0528         }
0529 
0530         /*
0531          * Handles may overlap when selection is very small.
0532          * Then it can happen that cursor is over more than one handles,
0533          * and therefore maybe more than two flags are set.
0534          * Favor one handle in that case.
0535          */
0536         if ((selected & RH_BottomRight) == RH_BottomRight) {
0537             return RH_BottomRight;
0538         }
0539         if ((selected & RH_TopRight) == RH_TopRight) {
0540             return RH_TopRight;
0541         }
0542         if ((selected & RH_TopLeft) == RH_TopLeft) {
0543             return RH_TopLeft;
0544         }
0545         if ((selected & RH_BottomLeft) == RH_BottomLeft) {
0546             return RH_BottomLeft;
0547         }
0548     }
0549 
0550     if (selected == RH_None && ad.annotation->canBeMoved()) {
0551         const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight());
0552         if (boundingRect.contains(eventPos)) {
0553             return RH_Content;
0554         }
0555     }
0556 
0557     return selected;
0558 }
0559 
0560 /* Get the rectangle for a specified resizie handle. */
0561 QRect MouseAnnotation::getHandleRect(ResizeHandle handle, const AnnotationDescription &ad) const
0562 {
0563     const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight());
0564     int left, top;
0565 
0566     if (handle & RH_Top) {
0567         top = boundingRect.top() - handleSizeHalf;
0568     } else if (handle & RH_Bottom) {
0569         top = boundingRect.bottom() - handleSizeHalf;
0570     } else {
0571         top = boundingRect.top() + boundingRect.height() / 2 - handleSizeHalf;
0572     }
0573 
0574     if (handle & RH_Left) {
0575         left = boundingRect.left() - handleSizeHalf;
0576     } else if (handle & RH_Right) {
0577         left = boundingRect.right() - handleSizeHalf;
0578     } else {
0579         left = boundingRect.left() + boundingRect.width() / 2 - handleSizeHalf;
0580     }
0581 
0582     return QRect(left, top, handleSize, handleSize);
0583 }
0584 
0585 /* Convert a resize handle delta into two adjust delta coordinates. */
0586 void MouseAnnotation::handleToAdjust(const QPointF dIn, QPointF &dOut1, QPointF &dOut2, MouseAnnotation::ResizeHandle handle, Okular::Rotation rotation)
0587 {
0588     const MouseAnnotation::ResizeHandle rotatedHandle = MouseAnnotation::rotateHandle(handle, rotation);
0589     dOut1.rx() = (rotatedHandle & MouseAnnotation::RH_Left) ? dIn.x() : 0;
0590     dOut1.ry() = (rotatedHandle & MouseAnnotation::RH_Top) ? dIn.y() : 0;
0591     dOut2.rx() = (rotatedHandle & MouseAnnotation::RH_Right) ? dIn.x() : 0;
0592     dOut2.ry() = (rotatedHandle & MouseAnnotation::RH_Bottom) ? dIn.y() : 0;
0593 }
0594 
0595 QPointF MouseAnnotation::rotateInRect(const QPointF rotated, Okular::Rotation rotation)
0596 {
0597     QPointF ret;
0598 
0599     switch (rotation) {
0600     case Okular::Rotation90:
0601         ret = QPointF(rotated.y(), -rotated.x());
0602         break;
0603     case Okular::Rotation180:
0604         ret = QPointF(-rotated.x(), -rotated.y());
0605         break;
0606     case Okular::Rotation270:
0607         ret = QPointF(-rotated.y(), rotated.x());
0608         break;
0609     case Okular::Rotation0: /* no modifications */
0610     default:                /* other cases */
0611         ret = rotated;
0612     }
0613 
0614     return ret;
0615 }
0616 
0617 MouseAnnotation::ResizeHandle MouseAnnotation::rotateHandle(MouseAnnotation::ResizeHandle handle, Okular::Rotation rotation)
0618 {
0619     unsigned int rotatedHandle = 0;
0620     switch (rotation) {
0621     case Okular::Rotation90:
0622         /* bit rotation: #1 => #4, #2 => #1, #3 => #2, #4 => #3 */
0623         rotatedHandle = (handle << 3 | handle >> (4 - 3)) & RH_AllHandles;
0624         break;
0625     case Okular::Rotation180:
0626         /* bit rotation: #1 => #3, #2 => #4, #3 => #1, #4 => #2 */
0627         rotatedHandle = (handle << 2 | handle >> (4 - 2)) & RH_AllHandles;
0628         break;
0629     case Okular::Rotation270:
0630         /* bit rotation: #1 => #2, #2 => #3, #3 => #4, #4 => #1 */
0631         rotatedHandle = (handle << 1 | handle >> (4 - 1)) & RH_AllHandles;
0632         break;
0633     case Okular::Rotation0: /* no modifications */
0634     default:                /* other cases */
0635         rotatedHandle = handle;
0636         break;
0637     }
0638     return (MouseAnnotation::ResizeHandle)rotatedHandle;
0639 }
0640 
0641 /* Start according action for AMovie/ARichMedia/AScreen/AFileAttachment.
0642  * It was formerly (before mouse annotation refactoring) called on mouse release event.
0643  * Now it's called on mouse press. Should we keep the former behavior? */
0644 void MouseAnnotation::processAction(const AnnotationDescription &ad)
0645 {
0646     if (ad.isValid()) {
0647         Okular::Annotation *ann = ad.annotation;
0648         PageViewItem *pageItem = ad.pageViewItem;
0649 
0650         if (ann->subType() == Okular::Annotation::AMovie) {
0651             VideoWidget *vw = pageItem->videoWidgets().value(static_cast<Okular::MovieAnnotation *>(ann)->movie());
0652             vw->show();
0653             vw->play();
0654         } else if (ann->subType() == Okular::Annotation::ARichMedia) {
0655             VideoWidget *vw = pageItem->videoWidgets().value(static_cast<Okular::RichMediaAnnotation *>(ann)->movie());
0656             vw->show();
0657             vw->play();
0658         } else if (ann->subType() == Okular::Annotation::AScreen) {
0659             m_document->processAction(static_cast<Okular::ScreenAnnotation *>(ann)->action());
0660         } else if (ann->subType() == Okular::Annotation::AFileAttachment) {
0661             const Okular::FileAttachmentAnnotation *fileAttachAnnot = static_cast<Okular::FileAttachmentAnnotation *>(ann);
0662             GuiUtils::saveEmbeddedFile(fileAttachAnnot->embeddedFile(), m_pageView);
0663         }
0664     }
0665 }