From 172d78c6b3f9d7894a1233fb12e5e292f13710ee Mon Sep 17 00:00:00 2001 From: Jake Linder Date: Thu, 27 Aug 2015 22:09:02 +0200 Subject: [PATCH] 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 --- conf/okular.kcfg | 9 +++ part-viewermode.rc | 4 +- part.rc | 4 +- ui/pageview.cpp | 178 +++++++++++++++++++++++++++++++++++++++++---- ui/pageview.h | 4 + 5 files changed, 179 insertions(+), 20 deletions(-) diff --git a/conf/okular.kcfg b/conf/okular.kcfg index 332eafb7d..cd9b4df92 100644 --- a/conf/okular.kcfg +++ b/conf/okular.kcfg @@ -221,6 +221,14 @@ + + None + + + + + + Browse @@ -230,6 +238,7 @@ + diff --git a/part-viewermode.rc b/part-viewermode.rc index bccd4912f..97f13ef96 100644 --- a/part-viewermode.rc +++ b/part-viewermode.rc @@ -1,5 +1,5 @@ - + &File @@ -34,7 +34,7 @@ - + diff --git a/part.rc b/part.rc index 60f86e5ba..34a1160ef 100644 --- a/part.rc +++ b/part.rc @@ -1,5 +1,5 @@ - + &File @@ -43,7 +43,7 @@ - + diff --git a/ui/pageview.cpp b/ui/pageview.cpp index 5e7fc2917..dc9eab830 100644 --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -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 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 ); diff --git a/ui/pageview.h b/ui/pageview.h index e65b57500..376bbdc5e 100644 --- a/ui/pageview.h +++ b/ui/pageview.h @@ -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();