File indexing completed on 2024-05-19 12:17:57
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 }