okular/ui/pageviewmouseannotation.cpp

722 lines
26 KiB
C++
Raw Normal View History

Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
/***************************************************************************
* 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> *
* *
* 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 != 0 );
}
void AnnotationDescription::invalidate()
{
annotation = nullptr;
pageViewItem = nullptr;
pageNumber = -1;
}
AnnotationDescription::AnnotationDescription( PageViewItem * newPageViewItem, const QPoint& eventPos )
{
const Okular::AnnotationObjectRect * annObjRect = 0;
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; */
const QPoint eventPos = m_pageView->contentAreaPoint( helpEvent->pos() );
if ( m_mouseOverAnnotation.isValid() &&
m_mouseOverAnnotation.annotation->subType() != Okular::Annotation::AWidget )
{
const ResizeHandle handle = getHandleAt( eventPos - m_mouseOverAnnotation.pageViewItem->uncroppedGeometry().topLeft(), m_mouseOverAnnotation );
if ( handle != RH_None )
{
/* 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() );
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 ) ) != 0 )
{
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::cancel()
{
if ( isActive() )
{
finishCommand();
setState( StateInactive, m_focusedAnnotation );
}
}
void MouseAnnotation::reset()
{
cancel();
m_focusedAnnotation.invalidate();
m_mouseOverAnnotation.invalidate();
}
Add annotation resize functionality Usage: If you left-click an annotation, it gets selected. Resize handles appear on the selection rectangle. When cursor is moved over one of the 8 resize handles on the corners/edges, the cursor shape changes to indicate resize mode. Everywhere else on the annotation means "move", just as it was before resize feature was added. Pressing ESC or clicking an area outside the annotation cancels a selection. Pressing Del deletes a selected annotation. Feature is only applicable for annotation types AText, AStamp and AGeom. Implementation: It works by eventually changing AnnotationPrivate::m_boundary and notifying generator (i.e. poppler) about that change. Annotation state handling is shifted out of PageView into a new class MouseAnnotation (ui/pageviewmouseannotation.cpp). Some functionality not related to resizing but to annotation interaction in general is also shifted to class MouseAnnotation, to build a single place of responsiblity. Other changes: Add method Document::adjustPageAnnotation, backed by a QUndoCommand. class Okular::AdjustAnnotationCommand. Add Annotation::adjust and Annotation::canBeResized methods. Draw resize handles in PagePainter::paintCroppedPageOnPainter. Resize and move work -for types AText, AStamp and AGeom -on all pages of document -when viewport position changes -when zoom level changes -for all page rotations (0°, 90°, 180°, 270°) Selection is canceled -when currently selected annotation is deleted -on mouse click outside of currently selected annotation -ESC is pressed Viewport is shifted when mouse cursor during move/resize comes close to viewport border. Resize to negative is prevented. Tiny annotations are still selectable. If mouse is moved over an annotation type that we can focus, and the annotation is not yet focused, mouse cursor shape changes to arrow. If mouse cursor rests over an annotation A, while annotation B is focused, a tooltip for annotation A is shown. Selected Annotation is deleted when Del is pressed. Test for regressions: -Annotation interaction (focus, move, resize, start playback, ...) are only done in mode EnumMouseMode::Browse. -If mouse is moved over an annotation type where we can start an action, mouse cursor shape changes to pointing hand. -If mouse is moved over an annotation type that we can't interact with, mouse cursor shape stays a open hand. -If mouse cursor rests over an annotation of any type, a tooltip for that annotation is shown. -Grab/move scroll area (on left click + mouse move) is prevented, if mouse is over focused annotation, or over AMovie/AScreen/AFileAttachment annotation. -A double click on a annotation starts the "annotator". REVIEW: 127366 BUG: 177778 BUG: 314843 BUG: 358060
2017-03-19 22:16:06 +00:00
/* 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 );
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 );
}
}
}