Add "Trim To Selection" feature

Changes

C1. Added submenu, moved "Trim margins" (TM mode) to it and added  "Trim To Selection" (TS mode).
C2. Activating "Trim To selection" enters a new mousemode, similar to RectSelect for defining a viewport.
C3. Once a viewport has been defined, it serves as a viewport for all pages in the document.
C4. Left/Right pages are not treated differently.

Manual Testing

T1. Switching between modes enforces at most one active.
T2. Can deactivate a mode by selecting it again from the menu.
T3. When draggin bbox selection, clicking outside page does not crash.
T4. When in "Facing Pages" mode, mouse release must be over any page (or is ignored).
T5. Normalized bbox coords are computed relative to page indicated by point of mouse release.
T6. Behave as expected when switching between any pair of No Trim/Trim Margins/Trim To Selection.
T7. TM mode persisted across app restarts (existing behavior).
T8. TS mode forgotten across app restarts (as desired).
T9. Exiting and reselectin "Trim To Selection" prompts for new bbox.
T10. Choosing a small Trim bbox enforces minimium dimensions size (As percentag of total), as
it does in TM mode, because of the "scale big and crop down" implementation, to avoid huge pixmaps.
TS mode minimum set at 20% (vs. TM mode's 50%).

REVIEW: 124716
BUGS: 351156
This commit is contained in:
Jake Linder 2015-08-27 22:09:02 +02:00 committed by Albert Astals Cid
parent 21dfb2127f
commit 172d78c6b3
5 changed files with 179 additions and 20 deletions

View file

@ -221,6 +221,14 @@
<choice name="Summary" />
</choices>
</entry>
<entry key="TrimMode" type="Enum" >
<default>None</default>
<choices>
<choice name="None" />
<choice name="Margins" />
<choice name="Selection" />
</choices>
</entry>
<entry key="MouseMode" type="Enum" >
<default>Browse</default>
<choices>
@ -230,6 +238,7 @@
<choice name="TextSelect" />
<choice name="TableSelect" />
<choice name="Magnifier" />
<choice name="TrimSelect" />
</choices>
</entry>
<entry key="ShowSourceLocationsGraphically" type="Bool" >

View file

@ -1,5 +1,5 @@
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="okular_part_viewermode" version="2">
<kpartgui name="okular_part_viewermode" version="3">
<MenuBar>
<Menu name="file"><text>&amp;File</text>
@ -34,7 +34,7 @@
<Action name="view_orientation_original" group="viewer_menu_merge"/>
</Menu>
<Action name="view_pagesizes" group="viewer_menu_merge"/>
<Action name="view_trim_margins" group="viewer_menu_merge"/>
<Action name="view_trim" group="viewer_menu_merge"/>
<Separator group="viewer_menu_merge"/>
<Action name="go_previous" group="viewer_menu_merge"/>
<Action name="go_next" group="viewer_menu_merge"/>

View file

@ -1,5 +1,5 @@
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="okular_part" version="36">
<kpartgui name="okular_part" version="37">
<MenuBar>
<Menu name="file"><text>&amp;File</text>
<Action name="get_new_stuff" group="file_open"/>
@ -43,7 +43,7 @@
<Action name="view_orientation_original"/>
</Menu>
<Action name="view_pagesizes"/>
<Action name="view_trim_margins"/>
<Action name="view_trim_mode"/>
<Separator/>
<Action name="view_toggle_forms"/>
</Menu>

View file

@ -177,6 +177,9 @@ public:
QTimer * refreshTimer;
int refreshPage;
// bbox state for Trim to Selection mode
Okular::NormalizedRect trimBoundingBox;
// infinite resizing loop prevention
bool verticalScrollBarVisible;
bool horizontalScrollBarVisible;
@ -193,7 +196,9 @@ public:
KAction * aRotateCounterClockwise;
KAction * aRotateOriginal;
KSelectAction * aPageSizes;
KActionMenu * aTrimMode;
KToggleAction * aTrimMargins;
KToggleAction * aTrimToSelection;
KAction * aMouseNormal;
KAction * aMouseSelect;
KAction * aMouseTextSelect;
@ -310,7 +315,9 @@ PageView::PageView( QWidget *parent, Okular::Document *document )
d->aRotateCounterClockwise = 0;
d->aRotateOriginal = 0;
d->aPageSizes = 0;
d->aTrimMode = 0;
d->aTrimMargins = 0;
d->aTrimToSelection = 0;
d->aMouseNormal = 0;
d->aMouseSelect = 0;
d->aMouseTextSelect = 0;
@ -332,6 +339,7 @@ PageView::PageView( QWidget *parent, Okular::Document *document )
d->penDown = false;
d->aMouseMagnifier = 0;
d->aFitWindowToPage = 0;
d->trimBoundingBox = Okular::NormalizedRect(); // Null box
switch( Okular::Settings::zoomMode() )
{
@ -475,11 +483,25 @@ void PageView::setupViewerActions( KActionCollection * ac )
connect( d->aPageSizes , SIGNAL(triggered(int)),
this, SLOT(slotPageSizes(int)) );
d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), this );
ac->addAction("view_trim_margins", d->aTrimMargins );
// Trim View actions
d->aTrimMode = new KActionMenu(i18n( "&Trim View" ), this );
d->aTrimMode->setDelayed( false );
ac->addAction("view_trim_mode", d->aTrimMode );
d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), d->aTrimMode->menu() );
d->aTrimMode->addAction( d->aTrimMargins );
ac->addAction( "view_trim_margins", d->aTrimMargins );
d->aTrimMargins->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Margins ) );
connect( d->aTrimMargins, SIGNAL(toggled(bool)), SLOT(slotTrimMarginsToggled(bool)) );
d->aTrimMargins->setChecked( Okular::Settings::trimMargins() );
d->aTrimToSelection = new KToggleAction( i18n( "Trim To &Selection" ), d->aTrimMode->menu() );
d->aTrimMode->addAction( d->aTrimToSelection);
ac->addAction( "view_trim_selection", d->aTrimToSelection);
d->aTrimToSelection->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Selection ) );
connect( d->aTrimToSelection, SIGNAL(toggled(bool)), SLOT(slotTrimToSelectionToggled(bool)) );
//
d->aZoomFitWidth = new KToggleAction(KIcon( "zoom-fit-width" ), i18n("Fit &Width"), this);
ac->addAction("view_fit_to_width", d->aZoomFitWidth );
connect( d->aZoomFitWidth, SIGNAL(toggled(bool)), SLOT(slotFitToWidthToggled(bool)) );
@ -1025,6 +1047,9 @@ void PageView::updateActionState( bool haspages, bool documentChanged, bool hasf
if ( d->aTrimMargins )
d->aTrimMargins->setEnabled( haspages );
if ( d->aTrimToSelection )
d->aTrimToSelection->setEnabled( haspages );
if ( d->aViewMode )
d->aViewMode->setEnabled( haspages );
@ -2019,6 +2044,7 @@ void PageView::mouseMoveEvent( QMouseEvent * e )
case Okular::Settings::EnumMouseMode::Zoom:
case Okular::Settings::EnumMouseMode::RectSelect:
case Okular::Settings::EnumMouseMode::TableSelect:
case Okular::Settings::EnumMouseMode::TrimSelect:
// set second corner of selection
if ( d->mouseSelecting )
updateSelection( eventPos );
@ -2181,6 +2207,7 @@ void PageView::mousePressEvent( QMouseEvent * e )
break;
case Okular::Settings::EnumMouseMode::RectSelect: // set first corner of the selection rect
case Okular::Settings::EnumMouseMode::TrimSelect:
if ( leftButton )
{
selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
@ -2534,6 +2561,54 @@ void PageView::mouseReleaseEvent( QMouseEvent * e )
d->magnifierView->hide();
break;
case Okular::Settings::EnumMouseMode::TrimSelect:
{
// if mouse is released and selection is null this is a rightClick
if ( rightButton && !d->mouseSelecting )
{
break;
}
PageViewItem * pageItem = pickItemOnPoint(eventPos.x(), eventPos.y());
// ensure end point rests within a page, or ignore
if (!pageItem) {
break;
}
QRect selectionRect = d->mouseSelectionRect.normalized();
double nLeft = pageItem->absToPageX(selectionRect.left());
double nRight = pageItem->absToPageX(selectionRect.right());
double nTop = pageItem->absToPageY(selectionRect.top());
double nBottom = pageItem->absToPageY(selectionRect.bottom());
if ( nLeft < 0 ) nLeft = 0;
if ( nTop < 0 ) nTop = 0;
if ( nRight > 1 ) nRight = 1;
if ( nBottom > 1 ) nBottom = 1;
d->trimBoundingBox = Okular::NormalizedRect(nLeft, nTop, nRight, nBottom);
// Trim Selection successfully done, hide prompt
d->messageWindow->hide();
// clear widget selection and invalidate rect
selectionClear();
// When Trim selection bbox interaction is over, we should switch to another mousemode.
if ( d->aPrevAction )
{
d->aPrevAction->trigger();
d->aPrevAction = 0;
} else {
d->aMouseNormal->trigger();
}
// with d->trimBoundingBox defined, redraw for trim to take visual effect
if ( d->document->pages() > 0 )
{
slotRelayoutPages();
slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
}
break;
}
case Okular::Settings::EnumMouseMode::RectSelect:
{
// if mouse is released and selection is null this is a rightClick
@ -3342,11 +3417,13 @@ void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight
zoom = d->zoomFactor;
Okular::NormalizedRect crop( 0., 0., 1., 1. );
// Handle cropping
if ( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown()
&& !okularPage->boundingBox().isNull() )
// Handle cropping, due to either "Trim Margin" or "Trim to Selection" cases
if (( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown()
&& !okularPage->boundingBox().isNull() ) ||
( d->aTrimToSelection && d->aTrimToSelection->isChecked() && !d->trimBoundingBox.isNull()))
{
crop = okularPage->boundingBox();
crop = Okular::Settings::trimMargins() ? okularPage->boundingBox() : d->trimBoundingBox;
// Rotate the bounding box
for ( int i = okularPage->rotation(); i > 0; --i )
@ -3358,20 +3435,28 @@ void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight
crop.bottom = rot.right;
}
// Expand the crop slightly beyond the bounding box
static const double cropExpandRatio = 0.04;
const double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2;
crop = Okular::NormalizedRect(
crop.left - cropExpand,
crop.top - cropExpand,
crop.right + cropExpand,
crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 );
// Expand the crop slightly beyond the bounding box (for Trim Margins only)
if (Okular::Settings::trimMargins()) {
static const double cropExpandRatio = 0.04;
const double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2;
crop = Okular::NormalizedRect(
crop.left - cropExpand,
crop.top - cropExpand,
crop.right + cropExpand,
crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 );
}
// We currently generate a larger image and then crop it, so if the
// crop rect is very small the generated image is huge. Hence, we shouldn't
// let the crop rect become too small.
// Make sure we crop by at most 50% in either dimension:
static const double minCropRatio = 0.5;
static double minCropRatio;
if (Okular::Settings::trimMargins()) {
// Make sure we crop by at most 50% in either dimension:
minCropRatio = 0.5;
} else {
// Looser Constraint for "Trim Selection"
minCropRatio = 0.20;
}
if ( ( crop.right - crop.left ) < minCropRatio )
{
const double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2;
@ -3848,6 +3933,8 @@ void PageView::updateCursor( const QPoint &p )
setCursor( Qt::CrossCursor );
else if ( d->mouseMode == Okular::Settings::EnumMouseMode::RectSelect )
setCursor( Qt::CrossCursor );
else if ( d->mouseMode == Okular::Settings::EnumMouseMode::TrimSelect )
setCursor( Qt::CrossCursor );
else if ( d->mouseAnn )
setCursor( Qt::ClosedHandCursor );
else if ( d->mouseMode == Okular::Settings::EnumMouseMode::Browse )
@ -4904,8 +4991,24 @@ void PageView::slotPageSizes( int newsize )
d->document->setPageSize( d->document->pageSizes().at( newsize ) );
}
// Enforce mutual-exclusion between trim modes
// Each mode is uniquely identified by a single value
// From Okular::Settings::EnumTrimMode
void PageView::updateTrimMode( int except_id ) {
const QList<QAction *> trimModeActions = d->aTrimMode->menu()->actions();
foreach(QAction *trimModeAction, trimModeActions)
{
if (trimModeAction->data().toInt() != except_id)
trimModeAction->setChecked( false );
}
}
void PageView::slotTrimMarginsToggled( bool on )
{
if (on) { // Turn off any other Trim modes
updateTrimMode(d->aTrimMargins->data().toInt());
}
if ( Okular::Settings::trimMargins() != on )
{
Okular::Settings::setTrimMargins( on );
@ -4918,6 +5021,49 @@ void PageView::slotTrimMarginsToggled( bool on )
}
}
void PageView::slotTrimToSelectionToggled( bool on )
{
if ( on ) { // Turn off any other Trim modes
updateTrimMode(d->aTrimToSelection->data().toInt());
d->mouseMode = Okular::Settings::EnumMouseMode::TrimSelect;
// change the text in messageWindow (and show it if hidden)
d->messageWindow->display( i18n( "Draw a rectangle around the page area you wish to keep visible" ), QString(), PageViewMessage::Info, -1 );
// force hiding of annotator toolbar
if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() )
{
d->aToggleAnnotator->trigger();
d->annotator->setHidingForced( true );
}
// force an update of the cursor
updateCursor();
} else {
// toggled off while making selection
if ( Okular::Settings::EnumMouseMode::TrimSelect == d->mouseMode ) {
// clear widget selection and invalidate rect
selectionClear();
// When Trim selection bbox interaction is over, we should switch to another mousemode.
if ( d->aPrevAction )
{
d->aPrevAction->trigger();
d->aPrevAction = 0;
} else {
d->aMouseNormal->trigger();
}
}
d->trimBoundingBox = Okular::NormalizedRect(); // invalidate box
if ( d->document->pages() > 0 )
{
slotRelayoutPages();
slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
}
}
}
void PageView::slotToggleForms()
{
toggleFormWidgets( !d->m_formsVisible );

View file

@ -187,6 +187,9 @@ Q_OBJECT
// used when selecting stuff, makes the view scroll as necessary to keep the mouse inside the view
void scrollPosIntoView( const QPoint & pos );
// called from slots to turn off trim modes mutually exclusive to id
void updateTrimMode( int except_id );
// don't want to expose classes in here
class PageViewPrivate * d;
@ -238,6 +241,7 @@ Q_OBJECT
void slotRotateOriginal();
void slotPageSizes( int );
void slotTrimMarginsToggled( bool );
void slotTrimToSelectionToggled( bool );
void slotToggleForms();
void slotFormChanged( int pageNumber );
void slotRefreshPage();