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