Viewport transition refinements for Find and Undo/Redo actions

REVIEW: 114060

This patch introduces viewport transitions for undo/redo actions on annotations and forms.  When an annotation/form action is undone/redone but the associated annotation/form is not currently visible, the viewport is updated to center on the undo/redo action. If the annotation/form is visible, the viewport is not updated.

The viewport transitions for the Find action have also been updated to this same algorithm.  Previously the viewport was moved to center on each matching search term even if the search term was already visible in the viewport. This lead to unnecessary viewport transitions if the search term matched several items in a single paragraph for example.

These proposed changes to the viewport transition behavior are consistent with the find and undo behavior of many existing applications including Kate, Open Office, and Foxit PDF Reader.
remotes/origin/KDE/4.13
Jon Mease 12 years ago committed by Albert Astals Cid
parent b543a6b767
commit 43246c563b
  1. 37
      core/document.cpp
  2. 6
      core/document_p.h
  3. 108
      core/documentcommands.cpp
  4. 19
      core/documentcommands_p.h
  5. 20
      core/page.cpp
  6. 22
      core/utils.cpp
  7. 5
      core/utils_p.h

@ -1699,8 +1699,16 @@ void DocumentPrivate::doProcessSearchMatch( RegularAreaRect *match, RunningSearc
// ..queue page for notifying changes..
pagesToNotify->insert( currentPage );
// Create a normalized rectangle around the search match that includes a 5% buffer on all sides.
const Okular::NormalizedRect matchRectWithBuffer = Okular::NormalizedRect( match->first().left - 0.05,
match->first().top - 0.05,
match->first().right + 0.05,
match->first().bottom + 0.05 );
const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible( matchRectWithBuffer, currentPage );
// ..move the viewport to show the first of the searched word sequence centered
if ( moveViewport )
if ( moveViewport && !matchRectFullyVisible )
{
DocumentViewport searchViewport( currentPage );
searchViewport.rePos.enabled = true;
@ -1999,6 +2007,25 @@ QVariant DocumentPrivate::documentMetaData( const QString &key, const QVariant &
return QVariant();
};
bool DocumentPrivate::isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage )
{
bool rectFullyVisible = false;
const QVector<Okular::VisiblePageRect *> & visibleRects = m_parent->visiblePageRects();
QVector<Okular::VisiblePageRect *>::const_iterator vEnd = visibleRects.end();
QVector<Okular::VisiblePageRect *>::const_iterator vIt = visibleRects.begin();
for ( ; ( vIt != vEnd ) && !rectFullyVisible; ++vIt )
{
if ( (*vIt)->pageNumber == rectPage &&
(*vIt)->rect.contains( rectOfInterest.left, rectOfInterest.top ) &&
(*vIt)->rect.contains( rectOfInterest.right, rectOfInterest.bottom ) )
{
rectFullyVisible = true;
}
}
return rectFullyVisible;
}
Document::Document( QWidget *widget )
: QObject( 0 ), d( new DocumentPrivate( this ) )
{
@ -3445,7 +3472,7 @@ void Document::editFormText( int pageNumber,
int prevCursorPos,
int prevAnchorPos )
{
QUndoCommand *uc = new EditFormTextCommand( this, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos );
QUndoCommand *uc = new EditFormTextCommand( this->d, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos );
d->m_undoStack->push( uc );
}
@ -3454,7 +3481,7 @@ void Document::editFormList( int pageNumber,
const QList< int > & newChoices )
{
const QList< int > prevChoices = form->currentChoices();
QUndoCommand *uc = new EditFormListCommand( this, form, pageNumber, newChoices, prevChoices );
QUndoCommand *uc = new EditFormListCommand( this->d, form, pageNumber, newChoices, prevChoices );
d->m_undoStack->push( uc );
}
@ -3476,13 +3503,13 @@ void Document::editFormCombo( int pageNumber,
prevText = form->choices()[form->currentChoices()[0]];
}
QUndoCommand *uc = new EditFormComboCommand( this, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos );
QUndoCommand *uc = new EditFormComboCommand( this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos );
d->m_undoStack->push( uc );
}
void Document::editFormButtons( int pageNumber, const QList< FormFieldButton* >& formButtons, const QList< bool >& newButtonStates )
{
QUndoCommand *uc = new EditFormButtonsCommand( this, pageNumber, formButtons, newButtonStates );
QUndoCommand *uc = new EditFormButtonsCommand( this->d, pageNumber, formButtons, newButtonStates );
d->m_undoStack->push( uc );
}

@ -183,6 +183,12 @@ class DocumentPrivate
*/
QVariant documentMetaData( const QString &key, const QVariant &option ) const;
/**
* Return whether the normalized rectangle @p rectOfInterest on page number @p rectPage
* is fully visible.
*/
bool isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage );
// member variables
Document *m_parent;
QPointer<QWidget> m_widget;

@ -13,11 +13,49 @@
#include "debug_p.h"
#include "document_p.h"
#include "form.h"
#include "utils_p.h"
#include "page.h"
#include <KLocalizedString>
namespace Okular {
void moveViewportIfBoundingRectNotFullyVisible( Okular::NormalizedRect boundingRect,
DocumentPrivate *docPriv,
int pageNumber )
{
const Rotation pageRotation = docPriv->m_parent->page( pageNumber )->rotation();
const QTransform rotationMatrix = Okular::buildRotationMatrix( pageRotation );
boundingRect.transform( rotationMatrix );
if ( !docPriv->isNormalizedRectangleFullyVisible( boundingRect, pageNumber ) )
{
DocumentViewport searchViewport( pageNumber );
searchViewport.rePos.enabled = true;
searchViewport.rePos.normalizedX = ( boundingRect.left + boundingRect.right ) / 2.0;
searchViewport.rePos.normalizedY = ( boundingRect.top + boundingRect.bottom ) / 2.0;
docPriv->m_parent->setViewport( searchViewport, 0, true );
}
}
Okular::NormalizedRect buildBoundingRectangleForButtons( const QList<Okular::FormFieldButton*> & formButtons )
{
// Initialize coordinates of the bounding rect
double left = 1.0;
double top = 1.0;
double right = 0.0;
double bottom = 0.0;
foreach( FormFieldButton* formButton, formButtons )
{
left = qMin<double>( left, formButton->rect().left );
top = qMin<double>( top, formButton->rect().top );
right = qMax<double>( right, formButton->rect().right );
bottom = qMax<double>( bottom, formButton->rect().bottom );
}
Okular::NormalizedRect boundingRect( left, top, right, bottom );
return boundingRect;
}
AddAnnotationCommand::AddAnnotationCommand( Okular::DocumentPrivate * docPriv, Okular::Annotation* annotation, int pageNumber )
: m_docPriv( docPriv ),
m_annotation( annotation ),
@ -37,19 +75,21 @@ AddAnnotationCommand::~AddAnnotationCommand()
void AddAnnotationCommand::undo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performRemovePageAnnotation( m_pageNumber, m_annotation );
m_done = false;
}
void AddAnnotationCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performAddPageAnnotation( m_pageNumber, m_annotation );
m_done = true;
}
RemoveAnnotationCommand::RemoveAnnotationCommand(Okular::DocumentPrivate * doc, Okular::Annotation* annotation, int pageNumber)
: m_doc( doc ),
: m_docPriv( doc ),
m_annotation( annotation ),
m_pageNumber( pageNumber ),
m_done( false )
@ -67,12 +107,14 @@ RemoveAnnotationCommand::~RemoveAnnotationCommand()
void RemoveAnnotationCommand::undo()
{
m_doc->performAddPageAnnotation( m_pageNumber, m_annotation );
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performAddPageAnnotation( m_pageNumber, m_annotation );
m_done = false;
}
void RemoveAnnotationCommand::redo(){
m_doc->performRemovePageAnnotation( m_pageNumber, m_annotation );
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performRemovePageAnnotation( m_pageNumber, m_annotation );
m_done = true;
}
@ -93,12 +135,14 @@ ModifyAnnotationPropertiesCommand::ModifyAnnotationPropertiesCommand( DocumentPr
void ModifyAnnotationPropertiesCommand::undo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_annotation->setAnnotationProperties( m_prevProperties );
m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true );
}
void ModifyAnnotationPropertiesCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_annotation->setAnnotationProperties( m_newProperties );
m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true );
}
@ -119,12 +163,14 @@ TranslateAnnotationCommand::TranslateAnnotationCommand( DocumentPrivate* docPriv
void TranslateAnnotationCommand::undo()
{
moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle( minusDelta() ), m_docPriv, m_pageNumber );
m_annotation->translate( minusDelta() );
m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true );
}
void TranslateAnnotationCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle( m_delta ), m_docPriv, m_pageNumber );
m_annotation->translate( m_delta );
m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true );
}
@ -155,6 +201,16 @@ Okular::NormalizedPoint TranslateAnnotationCommand::minusDelta()
return Okular::NormalizedPoint( -m_delta.x, -m_delta.y );
}
Okular::NormalizedRect TranslateAnnotationCommand::translateBoundingRectangle( const Okular::NormalizedPoint & delta )
{
Okular::NormalizedRect annotBoundingRect = m_annotation->boundingRectangle();
double left = qMin<double>( annotBoundingRect.left, annotBoundingRect.left + delta.x );
double right = qMax<double>( annotBoundingRect.right, annotBoundingRect.right + delta.x );
double top = qMin<double>( annotBoundingRect.top, annotBoundingRect.top + delta.y );
double bottom = qMax<double>( annotBoundingRect.bottom, annotBoundingRect.bottom + delta.y );
Okular::NormalizedRect boundingRect( left, top, right, bottom );
return boundingRect;
}
EditTextCommand::EditTextCommand( const QString & newContents,
int newCursorPos,
@ -260,12 +316,14 @@ EditAnnotationContentsCommand::EditAnnotationContentsCommand( DocumentPrivate* d
void EditAnnotationContentsCommand::undo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performSetAnnotationContents( m_prevContents, m_annotation, m_pageNumber );
emit m_docPriv->m_parent->annotationContentsChangedByUndoRedo( m_annotation, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
}
void EditAnnotationContentsCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performSetAnnotationContents( m_newContents, m_annotation, m_pageNumber );
emit m_docPriv->m_parent->annotationContentsChangedByUndoRedo( m_annotation, m_newContents, m_newCursorPos, m_newCursorPos );
}
@ -289,7 +347,7 @@ bool EditAnnotationContentsCommand::mergeWith(const QUndoCommand* uc)
}
}
EditFormTextCommand::EditFormTextCommand( Okular::Document* doc,
EditFormTextCommand::EditFormTextCommand( Okular::DocumentPrivate* docPriv,
Okular::FormFieldText* form,
int pageNumber,
const QString & newContents,
@ -298,7 +356,7 @@ EditFormTextCommand::EditFormTextCommand( Okular::Document* doc,
int prevCursorPos,
int prevAnchorPos )
: EditTextCommand( newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ),
m_doc ( doc ),
m_docPriv ( docPriv ),
m_form( form ),
m_pageNumber( pageNumber )
{
@ -307,14 +365,16 @@ EditFormTextCommand::EditFormTextCommand( Okular::Document* doc,
void EditFormTextCommand::undo()
{
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setText( m_prevContents );
m_doc->formTextChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
}
void EditFormTextCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setText( m_newContents );
m_doc->formTextChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos );
m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos );
}
int EditFormTextCommand::id() const
@ -336,12 +396,12 @@ bool EditFormTextCommand::mergeWith(const QUndoCommand* uc)
}
}
EditFormListCommand::EditFormListCommand( Okular::Document* doc,
EditFormListCommand::EditFormListCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form,
int pageNumber,
const QList< int > & newChoices,
const QList< int > & prevChoices )
: m_doc( doc ),
: m_docPriv( docPriv ),
m_form( form ),
m_pageNumber( pageNumber ),
m_newChoices( newChoices ),
@ -352,17 +412,19 @@ EditFormListCommand::EditFormListCommand( Okular::Document* doc,
void EditFormListCommand::undo()
{
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setCurrentChoices( m_prevChoices );
m_doc->formListChangedByUndoRedo( m_pageNumber, m_form, m_prevChoices );
m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_prevChoices );
}
void EditFormListCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setCurrentChoices( m_newChoices );
m_doc->formListChangedByUndoRedo( m_pageNumber, m_form, m_newChoices );
m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_newChoices );
}
EditFormComboCommand::EditFormComboCommand( Okular::Document* doc,
EditFormComboCommand::EditFormComboCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form,
int pageNumber,
const QString & newContents,
@ -371,7 +433,7 @@ EditFormComboCommand::EditFormComboCommand( Okular::Document* doc,
int prevCursorPos,
int prevAnchorPos )
: EditTextCommand( newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ),
m_doc( doc ),
m_docPriv( docPriv ),
m_form( form ),
m_pageNumber( pageNumber ),
m_newIndex( -1 ),
@ -404,7 +466,8 @@ void EditFormComboCommand::undo()
{
m_form->setEditChoice( m_prevContents );
}
m_doc->formComboChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
}
void EditFormComboCommand::redo()
@ -417,7 +480,8 @@ void EditFormComboCommand::redo()
{
m_form->setEditChoice( m_newContents );
}
m_doc->formComboChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos );
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos );
}
int EditFormComboCommand::id() const
@ -444,11 +508,11 @@ bool EditFormComboCommand::mergeWith( const QUndoCommand *uc )
}
}
EditFormButtonsCommand::EditFormButtonsCommand( Okular::Document* doc,
EditFormButtonsCommand::EditFormButtonsCommand( Okular::DocumentPrivate* docPriv,
int pageNumber,
const QList< FormFieldButton* > & formButtons,
const QList< bool > & newButtonStates )
: m_doc( doc ),
: m_docPriv( docPriv ),
m_pageNumber( pageNumber ),
m_formButtons( formButtons ),
m_newButtonStates( newButtonStates ),
@ -470,7 +534,10 @@ void EditFormButtonsCommand::undo()
if ( checked )
m_formButtons.at( i )->setState( checked );
}
m_doc->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons );
Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons );
moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber );
m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons );
}
void EditFormButtonsCommand::redo()
@ -482,7 +549,10 @@ void EditFormButtonsCommand::redo()
if ( checked )
m_formButtons.at( i )->setState( checked );
}
m_doc->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons );
Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons );
moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber );
m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons );
}
void EditFormButtonsCommand::clearFormButtonStates()

@ -51,7 +51,7 @@ class RemoveAnnotationCommand : public QUndoCommand
virtual void redo();
private:
Okular::DocumentPrivate * m_doc;
Okular::DocumentPrivate * m_docPriv;
Okular::Annotation* m_annotation;
int m_pageNumber;
bool m_done;
@ -90,6 +90,7 @@ class TranslateAnnotationCommand : public QUndoCommand
virtual int id() const;
virtual bool mergeWith(const QUndoCommand *uc);
Okular::NormalizedPoint minusDelta();
Okular::NormalizedRect translateBoundingRectangle( const Okular::NormalizedPoint & delta );
private:
Okular::DocumentPrivate * m_docPriv;
@ -164,7 +165,7 @@ class EditAnnotationContentsCommand : public EditTextCommand
class EditFormTextCommand : public EditTextCommand
{
public:
EditFormTextCommand( Okular::Document* doc,
EditFormTextCommand( Okular::DocumentPrivate* docPriv,
Okular::FormFieldText* form,
int pageNumber,
const QString & newContents,
@ -177,7 +178,7 @@ class EditFormTextCommand : public EditTextCommand
virtual int id() const;
virtual bool mergeWith( const QUndoCommand *uc );
private:
Okular::Document* m_doc;
Okular::DocumentPrivate* m_docPriv;
Okular::FormFieldText* m_form;
int m_pageNumber;
};
@ -185,7 +186,7 @@ class EditFormTextCommand : public EditTextCommand
class EditFormListCommand : public QUndoCommand
{
public:
EditFormListCommand( Okular::Document* doc,
EditFormListCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form,
int pageNumber,
const QList< int > & newChoices,
@ -196,7 +197,7 @@ class EditFormListCommand : public QUndoCommand
virtual void redo();
private:
Okular::Document* m_doc;
Okular::DocumentPrivate* m_docPriv;
FormFieldChoice* m_form;
int m_pageNumber;
QList< int > m_newChoices;
@ -206,7 +207,7 @@ class EditFormListCommand : public QUndoCommand
class EditFormComboCommand : public EditTextCommand
{
public:
EditFormComboCommand( Okular::Document* doc,
EditFormComboCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form,
int pageNumber,
const QString & newText,
@ -222,7 +223,7 @@ class EditFormComboCommand : public EditTextCommand
virtual bool mergeWith( const QUndoCommand *uc );
private:
Okular::Document* m_doc;
Okular::DocumentPrivate* m_docPriv;
FormFieldChoice* m_form;
int m_pageNumber;
int m_newIndex;
@ -232,7 +233,7 @@ class EditFormComboCommand : public EditTextCommand
class EditFormButtonsCommand : public QUndoCommand
{
public:
EditFormButtonsCommand( Okular::Document* doc,
EditFormButtonsCommand( Okular::DocumentPrivate* docPriv,
int pageNumber,
const QList< FormFieldButton* > & formButtons,
const QList< bool > & newButtonStates
@ -245,7 +246,7 @@ class EditFormButtonsCommand : public QUndoCommand
void clearFormButtonStates();
private:
Okular::Document* m_doc;
Okular::DocumentPrivate* m_docPriv;
int m_pageNumber;
QList< FormFieldButton* > m_formButtons;
QList< bool > m_newButtonStates;

@ -40,6 +40,7 @@
#include "textpage_p.h"
#include "tile.h"
#include "tilesmanager_p.h"
#include "utils_p.h"
#include <limits>
@ -118,24 +119,7 @@ void PagePrivate::imageRotationDone( RotationJob * job )
QTransform PagePrivate::rotationMatrix() const
{
QTransform matrix;
matrix.rotate( (int)m_rotation * 90 );
switch ( m_rotation )
{
case Rotation90:
matrix.translate( 0, -1 );
break;
case Rotation180:
matrix.translate( -1, -1 );
break;
case Rotation270:
matrix.translate( -1, 0 );
break;
default: ;
}
return matrix;
return Okular::buildRotationMatrix( m_rotation );
}
/** class Page **/

@ -259,3 +259,25 @@ void Okular::copyQIODevice( QIODevice *from, QIODevice *to )
break;
}
}
QTransform Okular::buildRotationMatrix(Rotation rotation)
{
QTransform matrix;
matrix.rotate( (int)rotation * 90 );
switch ( rotation )
{
case Rotation90:
matrix.translate( 0, -1 );
break;
case Rotation180:
matrix.translate( -1, -1 );
break;
case Rotation270:
matrix.translate( -1, 0 );
break;
default: ;
}
return matrix;
}

@ -17,6 +17,11 @@ namespace Okular
void copyQIODevice( QIODevice *from, QIODevice *to );
/**
* Return a rotation matrix corresponding to the @p rotation enumeration.
*/
QTransform buildRotationMatrix( Rotation rotation );
}
#endif

Loading…
Cancel
Save