okular/ui/pageviewmouseannotation.cpp
Tobias Deiminger 3c4f16ea4b Fix crash due to dangling pointer in MouseAnnotation
Summary:
BUG: 388228

Diff applies to Applications/17.12, and should be easy to merge to master. It's kept quite minimal as suggested by Albert.

Albert also suggested to add a dedicated unit test and I'd agree, but am not yet sure how to do it. The original bug involves several classes, including UI: Document, Page, AddAnnotationCommand, PageView, PageViewAnnotator, MouseAnnotation - to name a few. So a test for the exact bug scenario would become a bigger integration test rather than an isolated unit test. The other approach would be to do a real unit test on MouseAnnotation. But again, MouseAnnotation has nasty dependencies (e.g., needs a parent PageView) which I'd have to mock. Any ideas? I'd be interested in a discussion on this topic.

Test Plan:
 # Load a document (e.g. [[ http://www.philipebert.info/resources/WhatMathematicalKnowledgeCouldNotBe.pdf | linked PDF from bug report ]]) and enable highlight toolbar (F6).
 # Create highlight annotation.
 # Move the view port so that the annotation position is right beside the highlight tool icon.
 # Move the mouse over the annotation, and then horizontally left until you reach the tool icon; it's important to stay over the highlight annotation as long as in viewport.
 # Press ctrl-z for undo.
 # Click on highlight tool, move right into the document, create new highlight annotation.
 # Okular doesn't crash.

Reviewers: #okular

Subscribers: aacid, ngraham

Tags: #okular

Differential Revision: https://phabricator.kde.org/D9852
2018-02-25 19:11:17 +01:00

770 lines
28 KiB
C++

/***************************************************************************
* Copyright (C) 2017 by Tobias Deiminger <haxtibal@t-online.de> *
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group *
* company, info@kdab.com. Work sponsored by the *
* LiMux project of the city of Munich *
* *
* With portions of code from kpdf/kpdf_pagewidget.cc by: *
* Copyright (C) 2002 by Wilco Greven <greven@kde.org> *
* Copyright (C) 2003 by Christophe Devriese *
* <Christophe.Devriese@student.kuleuven.ac.be> *
* Copyright (C) 2003 by Laurent Montel <montel@kde.org> *
* Copyright (C) 2003 by Dirk Mueller <mueller@kde.org> *
* Copyright (C) 2004 by James Ots <kde@jamesots.com> *
* Copyright (C) 2011 by Jiri Baum - NICTA <jiri@baum.com.au> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "pageviewmouseannotation.h"
#include <qpainter.h>
#include <qtooltip.h>
#include <qevent.h>
#include "core/annotations.h"
#include "core/document.h"
#include "core/page.h"
#include "ui/guiutils.h"
#include "ui/pageview.h"
#include "ui/videowidget.h"
static const int handleSize = 10;
static const int handleSizeHalf = handleSize / 2;
bool AnnotationDescription::isValid() const
{
return ( annotation != nullptr );
}
bool AnnotationDescription::isContainedInPage( const Okular::Document * document, int pageNumber ) const
{
if ( AnnotationDescription::pageNumber == pageNumber )
{
/* Don't access page via pageViewItem here. pageViewItem might have been deleted. */
const Okular::Page * page = document->page( pageNumber );
if ( page != nullptr )
{
if ( page->annotations().contains( annotation ) )
{
return true;
}
}
}
return false;
}
void AnnotationDescription::invalidate()
{
annotation = nullptr;
pageViewItem = nullptr;
pageNumber = -1;
}
AnnotationDescription::AnnotationDescription( PageViewItem * newPageViewItem, const QPoint& eventPos )
{
const Okular::AnnotationObjectRect * annObjRect = nullptr;
if ( newPageViewItem )
{
const QRect & uncroppedPage = newPageViewItem->uncroppedGeometry();
/* find out normalized mouse coords inside current item (nX and nY will be in the range of 0..1). */
const double nX = newPageViewItem->absToPageX( eventPos.x() );
const double nY = newPageViewItem->absToPageY( eventPos.y() );
annObjRect = (Okular::AnnotationObjectRect *) newPageViewItem->page()->objectRect(
Okular::ObjectRect::OAnnotation, nX, nY, uncroppedPage.width(), uncroppedPage.height() );
}
if ( annObjRect )
{
annotation = annObjRect->annotation();
pageViewItem = newPageViewItem;
pageNumber = pageViewItem->pageNumber();
}
else
{
invalidate();
}
}
MouseAnnotation::MouseAnnotation( PageView * parent, Okular::Document * document)
: QObject( parent ), m_document( document ), m_pageView( parent ),
m_state( StateInactive ), m_handle( RH_None )
{
m_resizeHandleList << RH_Left << RH_Right << RH_Top << RH_Bottom
<< RH_TopLeft << RH_TopRight << RH_BottomLeft << RH_BottomRight;
}
MouseAnnotation::~MouseAnnotation()
{
}
void MouseAnnotation::routeMousePressEvent( PageViewItem * pageViewItem, const QPoint & eventPos )
{
/* Is there a selected annotation? */
if ( m_focusedAnnotation.isValid() )
{
m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
m_handle = getHandleAt( m_mousePosition, m_focusedAnnotation );
if ( m_handle != RH_None )
{
/* Returning here means, the selection-rectangle gets control, unconditionally.
* Even if it overlaps with another annotation. */
return;
}
}
AnnotationDescription ad( pageViewItem, eventPos );
/* qDebug() << "routeMousePressEvent: eventPos = " << eventPos; */
if ( ad.isValid() )
{
if ( ad.annotation->subType() == Okular::Annotation::AMovie ||
ad.annotation->subType() == Okular::Annotation::AScreen ||
ad.annotation->subType() == Okular::Annotation::AFileAttachment )
{
/* qDebug() << "routeMousePressEvent: trigger action for AMovie/AScreen/AFileAttachment"; */
processAction( ad );
}
else
{
/* qDebug() << "routeMousePressEvent: select for modification"; */
m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
m_handle = getHandleAt( m_mousePosition, ad );
if ( m_handle != RH_None )
{
setState( StateFocused, ad );
}
}
}
else
{
/* qDebug() << "routeMousePressEvent: no annotation under mouse, enter StateInactive"; */
setState( StateInactive, ad );
}
}
void MouseAnnotation::routeMouseReleaseEvent()
{
if ( isModified() )
{
/* qDebug() << "routeMouseReleaseEvent: finish command"; */
finishCommand();
setState( StateFocused, m_focusedAnnotation );
}
/*
else
{
qDebug() << "routeMouseReleaseEvent: ignore";
}
*/
}
void MouseAnnotation::routeMouseMoveEvent( PageViewItem * pageViewItem, const QPoint & eventPos, bool leftButtonPressed )
{
if ( !pageViewItem )
{
/* qDebug() << "routeMouseMoveEvent: no pageViewItem provided, ignore"; */
return;
}
if ( leftButtonPressed )
{
if ( isFocused() )
{
/* On first move event after annotation is selected, enter modification state */
if ( m_handle == RH_Content )
{
/* qDebug() << "routeMouseMoveEvent: handle " << m_handle << ", enter StateMoving"; */
setState( StateMoving, m_focusedAnnotation );
}
else if ( m_handle != RH_None )
{
/* qDebug() << "routeMouseMoveEvent: handle " << m_handle << ", enter StateResizing"; */
setState( StateResizing, m_focusedAnnotation );
}
}
if ( isModified() )
{
/* qDebug() << "routeMouseMoveEvent: perform command, delta " << eventPos - m_mousePosition; */
updateViewport( m_focusedAnnotation );
performCommand( eventPos );
m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
updateViewport( m_focusedAnnotation );
}
}
else
{
if ( isFocused() )
{
/* qDebug() << "routeMouseMoveEvent: update cursor for focused annotation, new eventPos " << eventPos; */
m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
m_handle = getHandleAt( m_mousePosition, m_focusedAnnotation );
m_pageView->updateCursor();
}
/* We get here quite frequently. */
const AnnotationDescription ad( pageViewItem, eventPos );
m_mousePosition = eventPos - pageViewItem->uncroppedGeometry().topLeft();
if ( ad.isValid() )
{
if ( !( m_mouseOverAnnotation == ad ) )
{
/* qDebug() << "routeMouseMoveEvent: Annotation under mouse (subtype " << ad.annotation->subType() << ", flags " << ad.annotation->flags() << ")"; */
m_mouseOverAnnotation = ad;
m_pageView->updateCursor();
}
}
else
{
if ( !( m_mouseOverAnnotation == ad ) )
{
/* qDebug() << "routeMouseMoveEvent: Annotation disappeared under mouse."; */
m_mouseOverAnnotation.invalidate();
m_pageView->updateCursor();
}
}
}
}
void MouseAnnotation::routeKeyPressEvent( const QKeyEvent * e )
{
switch ( e->key() )
{
case Qt::Key_Escape:
cancel();
break;
case Qt::Key_Delete:
if ( m_focusedAnnotation.isValid() )
{
AnnotationDescription adToBeDeleted = m_focusedAnnotation;
cancel();
m_document->removePageAnnotation( adToBeDeleted.pageNumber, adToBeDeleted.annotation );
}
break;
}
}
void MouseAnnotation::routeTooltipEvent( const QHelpEvent * helpEvent )
{
/* qDebug() << "MouseAnnotation::routeTooltipEvent, event " << helpEvent; */
if ( m_mouseOverAnnotation.isValid() &&
m_mouseOverAnnotation.annotation->subType() != Okular::Annotation::AWidget )
{
/* get boundingRect in uncropped page coordinates */
QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(
m_mouseOverAnnotation.annotation, m_mouseOverAnnotation.pageViewItem->uncroppedWidth(),
m_mouseOverAnnotation.pageViewItem->uncroppedHeight() );
/* uncropped page to content area */
boundingRect.translate( m_mouseOverAnnotation.pageViewItem->uncroppedGeometry().topLeft() );
/* content area to viewport */
boundingRect.translate( -m_pageView->contentAreaPosition() );
const QString tip = GuiUtils::prettyToolTip( m_mouseOverAnnotation.annotation );
QToolTip::showText( helpEvent->globalPos(), tip, m_pageView->viewport(), boundingRect );
}
}
void MouseAnnotation::routePaint( QPainter * painter, const QRect & paintRect )
{
/* QPainter draws relative to the origin of uncropped viewport. */
static const QColor borderColor = QColor::fromHsvF( 0, 0, 1.0 );
static const QColor fillColor = QColor::fromHsvF( 0, 0, 0.75, 0.66 );
if ( !isFocused() )
return;
/*
* Get annotation bounding rectangle in uncropped page coordinates.
* Distinction between AnnotationUtils::annotationGeometry() and AnnotationObjectRect::boundingRect() is,
* that boundingRect would enlarge the QRect to a minimum size of 14 x 14.
* This is useful for getting focus an a very small annotation,
* but for drawing and modification we want the real size.
*/
const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(
m_focusedAnnotation.annotation, m_focusedAnnotation.pageViewItem->uncroppedWidth(),
m_focusedAnnotation.pageViewItem->uncroppedHeight() );
if ( !paintRect.intersects( boundingRect
.translated( m_focusedAnnotation.pageViewItem->uncroppedGeometry().topLeft() )
.adjusted( -handleSizeHalf, -handleSizeHalf, handleSizeHalf, handleSizeHalf ) ) )
{
/* Our selection rectangle is not in a region that needs to be (re-)drawn. */
return;
}
painter->save();
painter->translate( m_focusedAnnotation.pageViewItem->uncroppedGeometry().topLeft() );
painter->setPen( QPen( fillColor, 2, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin ) );
painter->drawRect( boundingRect );
if ( m_focusedAnnotation.annotation->canBeResized() )
{
painter->setPen(borderColor);
painter->setBrush(fillColor);
Q_FOREACH( const ResizeHandle & handle, m_resizeHandleList )
{
QRect rect = getHandleRect( handle, m_focusedAnnotation );
painter->drawRect( rect );
}
}
painter->restore();
}
Okular::Annotation * MouseAnnotation::annotation() const
{
if ( m_focusedAnnotation.isValid() )
{
return m_focusedAnnotation.annotation;
}
return nullptr;
}
bool MouseAnnotation::isActive() const
{
return ( m_state != StateInactive );
}
bool MouseAnnotation::isMouseOver() const
{
return ( m_mouseOverAnnotation.isValid() || m_handle != RH_None );
}
bool MouseAnnotation::isFocused() const
{
return ( m_state == StateFocused );
}
bool MouseAnnotation::isMoved() const
{
return ( m_state == StateMoving );
}
bool MouseAnnotation::isResized() const
{
return ( m_state == StateResizing );
}
bool MouseAnnotation::isModified() const
{
return ( m_state == StateMoving || m_state == StateResizing );
}
Qt::CursorShape MouseAnnotation::cursor() const
{
if ( m_handle != RH_None )
{
if ( isMoved() )
{
return Qt::SizeAllCursor;
}
else if ( isFocused() || isResized() )
{
switch ( m_handle )
{
case RH_Top:
return Qt::SizeVerCursor;
case RH_TopRight:
return Qt::SizeBDiagCursor;
case RH_Right:
return Qt::SizeHorCursor;
case RH_BottomRight:
return Qt::SizeFDiagCursor;
case RH_Bottom:
return Qt::SizeVerCursor;
case RH_BottomLeft:
return Qt::SizeBDiagCursor;
case RH_Left:
return Qt::SizeHorCursor;
case RH_TopLeft:
return Qt::SizeFDiagCursor;
case RH_Content:
return Qt::SizeAllCursor;
default:
return Qt::OpenHandCursor;
}
}
}
else if ( m_mouseOverAnnotation.isValid() )
{
/* Mouse is over annotation, but the annotation is not yet selected. */
if ( m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AMovie )
{
return Qt::PointingHandCursor;
}
else if ( m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::ARichMedia )
{
return Qt::PointingHandCursor;
}
else if ( m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AScreen )
{
if ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( m_mouseOverAnnotation.annotation ) ) != nullptr )
{
return Qt::PointingHandCursor;
}
}
else if ( m_mouseOverAnnotation.annotation->subType() == Okular::Annotation::AFileAttachment )
{
return Qt::PointingHandCursor;
}
else
{
return Qt::ArrowCursor;
}
}
/* There's no none cursor, so we still have to return something. */
return Qt::ArrowCursor;
}
void MouseAnnotation::notifyAnnotationChanged( int pageNumber )
{
const AnnotationDescription emptyAd;
if ( m_focusedAnnotation.isValid() &&
! m_focusedAnnotation.isContainedInPage( m_document, pageNumber ) )
{
setState( StateInactive, emptyAd );
}
if ( m_mouseOverAnnotation.isValid() &&
! m_mouseOverAnnotation.isContainedInPage( m_document, pageNumber ) )
{
m_mouseOverAnnotation = emptyAd;
m_pageView->updateCursor();
}
}
void MouseAnnotation::updateAnnotationPointers()
{
if (m_focusedAnnotation.annotation)
{
m_focusedAnnotation.annotation = m_document->page( m_focusedAnnotation.pageNumber )->annotation( m_focusedAnnotation.annotation->uniqueName() );
}
if (m_mouseOverAnnotation.annotation)
{
m_mouseOverAnnotation.annotation = m_document->page( m_mouseOverAnnotation.pageNumber )->annotation( m_mouseOverAnnotation.annotation->uniqueName() );
}
}
void MouseAnnotation::cancel()
{
if ( isActive() )
{
finishCommand();
setState( StateInactive, m_focusedAnnotation );
}
}
void MouseAnnotation::reset()
{
cancel();
m_focusedAnnotation.invalidate();
m_mouseOverAnnotation.invalidate();
}
/* Handle state changes for the focused annotation. */
void MouseAnnotation::setState( MouseAnnotationState state, const AnnotationDescription & ad )
{
/* qDebug() << "setState: requested " << state; */
if ( m_focusedAnnotation.isValid() )
{
/* If there was a annotation before, request also repaint for the previous area. */
updateViewport( m_focusedAnnotation );
}
if ( !ad.isValid() )
{
/* qDebug() << "No annotation provided, forcing state inactive." << state; */
state = StateInactive;
}
else if ( ( state == StateMoving && !ad.annotation->canBeMoved() ) ||
( state == StateResizing && !ad.annotation->canBeResized() ) )
{
/* qDebug() << "Annotation does not support requested state, forcing state selected." << state; */
state = StateInactive;
}
switch( state )
{
case StateMoving:
m_focusedAnnotation = ad;
m_focusedAnnotation.annotation->setFlags( m_focusedAnnotation.annotation->flags() | Okular::Annotation::BeingMoved );
updateViewport( m_focusedAnnotation );
break;
case StateResizing:
m_focusedAnnotation = ad;
m_focusedAnnotation.annotation->setFlags( m_focusedAnnotation.annotation->flags() | Okular::Annotation::BeingResized );
updateViewport( m_focusedAnnotation );
break;
case StateFocused:
m_focusedAnnotation = ad;
m_focusedAnnotation.annotation->setFlags( m_focusedAnnotation.annotation->flags() & ~(Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) );
updateViewport( m_focusedAnnotation );
break;
case StateInactive:
default:
if ( m_focusedAnnotation.isValid() )
{
m_focusedAnnotation.annotation->setFlags( m_focusedAnnotation.annotation->flags() & ~(Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) );
}
m_focusedAnnotation.invalidate();
m_handle = RH_None;
}
/* qDebug() << "setState: enter " << state; */
m_state = state;
m_pageView->updateCursor();
}
/* Get the rectangular boundary of the given annotation, enlarged for space needed by resize handles.
* Returns a QRect in page view item coordinates. */
QRect MouseAnnotation::getFullBoundingRect( const AnnotationDescription & ad ) const
{
QRect boundingRect;
if ( ad.isValid() )
{
boundingRect = Okular::AnnotationUtils::annotationGeometry(
ad.annotation, ad.pageViewItem->uncroppedWidth(),
ad.pageViewItem->uncroppedHeight() );
boundingRect = boundingRect.adjusted( -handleSizeHalf, -handleSizeHalf, handleSizeHalf, handleSizeHalf );
}
return boundingRect;
}
/* Apply the command determined by m_state to the currently focused annotation. */
void MouseAnnotation::performCommand( const QPoint & newPos )
{
const QRect & pageViewItemRect = m_focusedAnnotation.pageViewItem->uncroppedGeometry();
QPointF mouseDelta( newPos - pageViewItemRect.topLeft() - m_mousePosition );
QPointF normalizedRotatedMouseDelta( rotateInRect(
QPointF( mouseDelta.x() / pageViewItemRect.width(),
mouseDelta.y() / pageViewItemRect.height() ),
m_focusedAnnotation.pageViewItem->page()->rotation() ) );
if ( isMoved() )
{
m_document->translatePageAnnotation( m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation,
Okular::NormalizedPoint( normalizedRotatedMouseDelta.x(), normalizedRotatedMouseDelta.y() ) );
}
else if ( isResized() )
{
QPointF delta1, delta2;
handleToAdjust( normalizedRotatedMouseDelta, delta1, delta2, m_handle, m_focusedAnnotation.pageViewItem->page()->rotation() );
m_document->adjustPageAnnotation( m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation,
Okular::NormalizedPoint( delta1.x(), delta1.y() ), Okular::NormalizedPoint( delta2.x(), delta2.y() ) );
}
}
/* Finalize a command in progress for the currently focused annotation. */
void MouseAnnotation::finishCommand()
{
/*
* Note:
* Translate-/resizePageAnnotation causes PopplerAnnotationProxy::notifyModification,
* where modify flag needs to be already cleared. So it is important to call
* setFlags before translatePageAnnotation-/adjustPageAnnotation.
*/
if ( isMoved() )
{
m_focusedAnnotation.annotation->setFlags( m_focusedAnnotation.annotation->flags() & ~Okular::Annotation::BeingMoved );
m_document->translatePageAnnotation( m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation,
Okular::NormalizedPoint( 0.0, 0.0 ) );
}
else if ( isResized() )
{
m_focusedAnnotation.annotation->setFlags( m_focusedAnnotation.annotation->flags() & ~Okular::Annotation::BeingResized );
m_document->adjustPageAnnotation( m_focusedAnnotation.pageNumber, m_focusedAnnotation.annotation,
Okular::NormalizedPoint( 0.0, 0.0 ), Okular::NormalizedPoint( 0.0, 0.0 ) );
}
}
/* Tell viewport widget that the rectangular of the given annotation needs to be repainted. */
void MouseAnnotation::updateViewport( const AnnotationDescription & ad ) const
{
const QRect & changedPageViewItemRect = getFullBoundingRect( ad );
if ( changedPageViewItemRect.isValid() )
{
m_pageView->viewport()->update( changedPageViewItemRect
.translated( ad.pageViewItem->uncroppedGeometry().topLeft() )
.translated( -m_pageView->contentAreaPosition() ) );
}
}
/* eventPos: Mouse position in uncropped page coordinates.
ad: The annotation to get the handle for. */
MouseAnnotation::ResizeHandle MouseAnnotation::getHandleAt( const QPoint & eventPos, const AnnotationDescription & ad ) const
{
ResizeHandle selected = RH_None;
if ( ad.annotation->canBeResized() )
{
Q_FOREACH( const ResizeHandle & handle, m_resizeHandleList )
{
const QRect rect = getHandleRect( handle, ad );
if ( rect.contains( eventPos ) )
{
selected |= handle;
}
}
/*
* Handles may overlap when selection is very small.
* Then it can happen that cursor is over more than one handles,
* and therefore maybe more than two flags are set.
* Favor one handle in that case.
*/
if ( ( selected & RH_BottomRight ) == RH_BottomRight )
return RH_BottomRight;
if ( ( selected & RH_TopRight ) == RH_TopRight )
return RH_TopRight;
if ( ( selected & RH_TopLeft ) == RH_TopLeft )
return RH_TopLeft;
if ( ( selected & RH_BottomLeft ) == RH_BottomLeft )
return RH_BottomLeft;
}
if ( selected == RH_None && ad.annotation->canBeMoved() )
{
const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(
ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight() );
if ( boundingRect.contains( eventPos ) )
{
return RH_Content;
}
}
return selected;
}
/* Get the rectangle for a specified resizie handle. */
QRect MouseAnnotation::getHandleRect( ResizeHandle handle, const AnnotationDescription & ad ) const
{
const QRect boundingRect = Okular::AnnotationUtils::annotationGeometry(
ad.annotation, ad.pageViewItem->uncroppedWidth(), ad.pageViewItem->uncroppedHeight() );
int left, top;
if ( handle & RH_Top )
{
top = boundingRect.top() - handleSizeHalf;
}
else if ( handle & RH_Bottom )
{
top = boundingRect.bottom() - handleSizeHalf;
}
else
{
top = boundingRect.top() + boundingRect.height() / 2 - handleSizeHalf;
}
if ( handle & RH_Left )
{
left = boundingRect.left() - handleSizeHalf;
}
else if ( handle & RH_Right )
{
left = boundingRect.right() - handleSizeHalf;
}
else
{
left = boundingRect.left() + boundingRect.width() / 2 - handleSizeHalf;
}
return QRect( left, top, handleSize, handleSize );
}
/* Convert a resize handle delta into two adjust delta coordinates. */
void MouseAnnotation::handleToAdjust( const QPointF & dIn, QPointF & dOut1, QPointF & dOut2, MouseAnnotation::ResizeHandle handle, Okular::Rotation rotation )
{
const MouseAnnotation::ResizeHandle rotatedHandle = MouseAnnotation::rotateHandle( handle, rotation);
dOut1.rx() = ( rotatedHandle & MouseAnnotation::RH_Left ) ? dIn.x() : 0;
dOut1.ry() = ( rotatedHandle & MouseAnnotation::RH_Top ) ? dIn.y() : 0;
dOut2.rx() = ( rotatedHandle & MouseAnnotation::RH_Right ) ? dIn.x() : 0;
dOut2.ry() = ( rotatedHandle & MouseAnnotation::RH_Bottom ) ? dIn.y() : 0;
}
QPointF MouseAnnotation::rotateInRect( const QPointF & rotated, Okular::Rotation rotation )
{
QPointF ret;
switch ( rotation )
{
case Okular::Rotation90:
ret = QPointF( rotated.y(), -rotated.x() );
break;
case Okular::Rotation180:
ret = QPointF( -rotated.x(), -rotated.y() );
break;
case Okular::Rotation270:
ret = QPointF( -rotated.y(), rotated.x() );
break;
case Okular::Rotation0: /* no modifications */
default: /* other cases */
ret = rotated;
}
return ret;
}
MouseAnnotation::ResizeHandle MouseAnnotation::rotateHandle( MouseAnnotation::ResizeHandle handle, Okular::Rotation rotation )
{
unsigned int rotatedHandle = 0;
switch( rotation )
{
case Okular::Rotation90:
/* bit rotation: #1 => #4, #2 => #1, #3 => #2, #4 => #3 */
rotatedHandle = (handle << 3 | handle >> (4-3)) & RH_AllHandles;
break;
case Okular::Rotation180:
/* bit rotation: #1 => #3, #2 => #4, #3 => #1, #4 => #2 */
rotatedHandle = (handle << 2 | handle >> (4-2)) & RH_AllHandles;
break;
case Okular::Rotation270:
/* bit rotation: #1 => #2, #2 => #3, #3 => #4, #4 => #1 */
rotatedHandle = (handle << 1 | handle >> (4-1)) & RH_AllHandles;
break;
case Okular::Rotation0: /* no modifications */
default: /* other cases */
rotatedHandle = handle;
break;
}
return (MouseAnnotation::ResizeHandle) rotatedHandle;
}
/* Start according action for AMovie/ARichMedia/AScreen/AFileAttachment.
* It was formerly (before mouse annotation refactoring) called on mouse release event.
* Now it's called on mouse press. Should we keep the former behavior? */
void MouseAnnotation::processAction( const AnnotationDescription& ad )
{
if ( ad.isValid() )
{
Okular::Annotation *ann = ad.annotation;
PageViewItem * pageItem = ad.pageViewItem;
if ( ann->subType() == Okular::Annotation::AMovie )
{
VideoWidget *vw = pageItem->videoWidgets().value( static_cast<Okular::MovieAnnotation*>( ann )->movie() );
vw->show();
vw->play();
}
else if ( ann->subType() == Okular::Annotation::ARichMedia )
{
VideoWidget *vw = pageItem->videoWidgets().value( static_cast<Okular::RichMediaAnnotation*>( ann )->movie() );
vw->show();
vw->play();
}
else if ( ann->subType() == Okular::Annotation::AScreen )
{
m_document->processAction( static_cast<Okular::ScreenAnnotation*>( ann )->action() );
}
else if ( ann->subType() == Okular::Annotation::AFileAttachment )
{
const Okular::FileAttachmentAnnotation * fileAttachAnnot = static_cast< Okular::FileAttachmentAnnotation * >( ann );
GuiUtils::saveEmbeddedFile( fileAttachAnnot->embeddedFile(), m_pageView );
}
}
}