diff --git a/ui/pageview.h b/ui/pageview.h --- a/ui/pageview.h +++ b/ui/pageview.h @@ -214,6 +214,7 @@ void createAnnotationsVideoWidgets(PageViewItem *item, const QLinkedList< Okular::Annotation * > &annotations); + void holdZoomCenter( ZoomMode newZm, QPointF zoomCenter, float newZoom = 0.0 ); // don't want to expose classes in here class PageViewPrivate * d; diff --git a/ui/pageview.cpp b/ui/pageview.cpp --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -155,7 +155,6 @@ QPoint mousePressPos; QPoint mouseSelectPos; QPoint previousMouseMovePos; - int mouseMidLastY; bool mouseSelecting; QRect mouseSelectionRect; QColor mouseSelectionColor; @@ -252,6 +251,8 @@ const Okular::ObjectRect * mouseOverLinkObject; QScroller * scroller; + bool zoomActive; + QPointF scrollRest; }; PageViewPrivate::PageViewPrivate( PageView *qq ) @@ -393,6 +394,8 @@ d->aMouseMagnifier = nullptr; d->aFitWindowToPage = nullptr; d->trimBoundingBox = Okular::NormalizedRect(); // Null box + d->zoomActive = false; + d->scrollRest = QPointF( 0.0, 0.0 ); switch( Okular::Settings::zoomMode() ) { @@ -1685,19 +1688,17 @@ if (pinch->state() == Qt::GestureStarted) { vanillaZoom = d->zoomFactor; + d->zoomActive = true; + d->scroller->stop(); } const QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); // Zoom if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged) { - d->zoomFactor = vanillaZoom * pinch->totalScaleFactor(); - d->blockPixmapsRequest = true; - updateZoom( ZoomRefreshCurrent ); - d->blockPixmapsRequest = false; - viewport()->update(); + holdZoomCenter( ZoomRefreshCurrent, mapFromGlobal( pinch->centerPoint().toPoint()), vanillaZoom * pinch->totalScaleFactor() ); } // Count the number of 90-degree rotations we did since the start of the pinch gesture. @@ -1721,9 +1722,11 @@ } } - if (pinch->state() == Qt::GestureFinished) + if ( pinch->state() == Qt::GestureFinished || pinch->state() == Qt::GestureCanceled ) { rotations = 0; + d->zoomActive = false; + d->scrollRest = QPointF( 0.0, 0.0 ); } return true; @@ -1994,8 +1997,16 @@ return; } - // start a timer that will refresh the pixmap after 0.2s - d->delayResizeEventTimer->start( 200 ); + if ( d->zoomActive ) + { + // if we make a continuous zooming with pinch gesture or mouse, we call delayedResizeEvent() direct. + delayedResizeEvent(); + } else + { + // start a timer that will refresh the pixmap after 0.2s + d->delayResizeEventTimer->start( 200 ); + } + d->verticalScrollBarVisible = verticalScrollBar()->isVisible(); d->horizontalScrollBarVisible = horizontalScrollBar()->isVisible(); } @@ -2106,6 +2117,12 @@ d->scrollIncrement = 0; d->autoScrollTimer->stop(); } + + if ( e->key() == Qt::Key_Control ) + { + d->scrollRest = QPointF( 0.0, 0.0 ); + d->zoomActive = false; + } } void PageView::inputMethodEvent( QInputMethodEvent * e ) @@ -2153,6 +2170,9 @@ void PageView::mouseMoveEvent( QMouseEvent * e ) { + if ( e->source() == Qt::MouseEventSynthesizedByQt && d->zoomActive ) + return; + // For some reason in Qt 5.11.2 (no idea when this started) all wheel // events are followed by mouse move events (without changing position), // so we only actually reset the controlWheelAccumulatedDelta if there is a mouse movement @@ -2169,42 +2189,20 @@ // if holding mouse mid button, perform zoom if ( e->buttons() & Qt::MidButton ) { - int mouseY = e->globalPos().y(); - int deltaY = d->mouseMidLastY - mouseY; + // if we lock the mouse cursor position with QCursor::setPos() we will get a mouseMoveEvent + // so we will ignore all mouseMoveEvent to the position d->mousePressPos + if ( e->globalPos() == d->mousePressPos ) + return; - // wrap mouse from top to bottom - const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); - const int absDeltaY = abs(deltaY); - if ( absDeltaY > mouseContainer.height() / 2 ) - { - deltaY = mouseContainer.height() - absDeltaY; - } - - const float upperZoomLimit = d->document->supportsTiles() ? 15.99 : 3.99; - if ( mouseY <= mouseContainer.top() + 4 && - d->zoomFactor < upperZoomLimit ) - { - mouseY = mouseContainer.bottom() - 5; - QCursor::setPos( e->globalPos().x(), mouseY ); - } - // wrap mouse from bottom to top - else if ( mouseY >= mouseContainer.bottom() - 4 && - d->zoomFactor > 0.101 ) - { - mouseY = mouseContainer.top() + 5; - QCursor::setPos( e->globalPos().x(), mouseY ); - } - // remember last position - d->mouseMidLastY = mouseY; + const int deltaY = d->mousePressPos.y() - e->globalPos().y(); // update zoom level, perform zoom and redraw if ( deltaY ) { - d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) ); - d->blockPixmapsRequest = true; - updateZoom( ZoomRefreshCurrent ); - d->blockPixmapsRequest = false; - viewport()->update(); + d->zoomActive = true; + // lock mouse cursor in the position of the mousePressEvent + QCursor::setPos ( d->mousePressPos ); + holdZoomCenter( ZoomRefreshCurrent, mapFromGlobal( d->mousePressPos ), d->zoomFactor * ( 1.0 + ( (double)deltaY / 500.0 ) ) ); } return; } @@ -2347,10 +2345,12 @@ d->autoScrollTimer->stop(); } + // update press / 'start drag' mouse position + d->mousePressPos = e->globalPos(); + // if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode if ( e->button() == Qt::MidButton ) { - d->mouseMidLastY = e->globalPos().y(); setCursor( Qt::SizeVerCursor ); return; } @@ -2378,9 +2378,6 @@ return; } - // update press / 'start drag' mouse position - d->mousePressPos = e->globalPos(); - // handle mode dependent mouse press actions bool leftButton = e->button() == Qt::LeftButton, rightButton = e->button() == Qt::RightButton; @@ -2585,8 +2582,8 @@ // handle mode independent mid bottom zoom if ( e->button() == Qt::MidButton ) { - // request pixmaps since it was disabled during drag - slotRequestVisiblePixmaps(); + d->zoomActive = false; + d->scrollRest = QPointF( 0.0, 0.0 ); // the cursor may now be over a link.. update it updateCursor( eventPos ); return; @@ -3402,12 +3399,14 @@ d->controlWheelAccumulatedDelta += delta; if ( d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep ) { - slotZoomOut(); + d->zoomActive = true; + holdZoomCenter( ZoomOut, e->pos() ); d->controlWheelAccumulatedDelta = 0; } else if ( d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep ) { - slotZoomIn(); + d->zoomActive = true; + holdZoomCenter( ZoomIn, e->pos() ); d->controlWheelAccumulatedDelta = 0; } } @@ -4604,6 +4603,88 @@ menu->addAction( action ); } +void PageView::holdZoomCenter(PageView::ZoomMode newZm, QPointF zoomCenter, float newZoom) +{ + const Okular::DocumentViewport & vp = d->document->viewport(); + Q_ASSERT( vp.pageNumber >= 0 ); + + //determine the page below zoom center + const QPoint contentPos = contentAreaPoint( zoomCenter.toPoint() ); + const PageViewItem* page = pickItemOnPoint( contentPos.x(), contentPos.y() ); + const int hScrollBarMaximum = horizontalScrollBar()->maximum(); + const int vScrollBarMaximum = verticalScrollBar()->maximum(); + + //if the zoom center is not over a page, use viewport page number + if ( !page ) { + page = d->items[ vp.pageNumber ]; + } + + const QRect beginGeometry = page->croppedGeometry(); + + QPoint offset { beginGeometry.left(), beginGeometry.top() }; + + const QPointF oldScroll = contentAreaPosition() - offset; + + d->blockPixmapsRequest = true; + if ( newZoom ) + d->zoomFactor = newZoom; + + updateZoom( newZm ); + d->blockPixmapsRequest = false; + + const QRect afterGeometry = page->croppedGeometry(); + const double vpZoomY = (double)afterGeometry.height() / (double)beginGeometry.height(); + const double vpZoomX = (double)afterGeometry.width() / (double)beginGeometry.width(); + + QPointF newScroll; + // The calculation for newScroll is taken from Gwenview class Abstractimageview::setZoom + newScroll.setY( vpZoomY * ( oldScroll.y() + zoomCenter.y() ) - ( zoomCenter.y() ) ); + newScroll.setX( vpZoomX * ( oldScroll.x() + zoomCenter.x() ) - ( zoomCenter.x() ) ); + + // add the remaining scroll from the previous zoom event + newScroll.setY( newScroll.y() + d->scrollRest.y() * vpZoomY ); + newScroll.setX( newScroll.x() + d->scrollRest.x() * vpZoomX ); + + // adjust newScroll to the new margins after zooming + offset = QPoint { afterGeometry.left(), afterGeometry.top() }; + newScroll += offset; + + // adjust newScroll for appear and disappear of the scrollbars + if ( Okular::Settings::showScrollBars() ) + { + if ( hScrollBarMaximum == 0 && horizontalScrollBar()->maximum() > 0 ) + newScroll.setY( newScroll.y() - ( horizontalScrollBar()->height() / 2 ) ); + + if ( hScrollBarMaximum > 0 && horizontalScrollBar()->maximum() == 0 ) + newScroll.setY( newScroll.y() + ( horizontalScrollBar()->height() / 2 ) ); + + if ( vScrollBarMaximum == 0 && verticalScrollBar()->maximum() > 0 ) + newScroll.setX( newScroll.x() - ( verticalScrollBar()->width() / 2 ) ); + + if ( vScrollBarMaximum > 0 && verticalScrollBar()->maximum() == 0 ) + newScroll.setX( newScroll.x() + ( verticalScrollBar()->width() / 2 ) ); + } + + const int newScrollX = std::round ( newScroll.x() ); + const int newScrollY = std::round ( newScroll.y() ); + scrollTo( newScrollX, newScrollY, false ); + + viewport()->setUpdatesEnabled( true ); + viewport()->update(); + + // test if target scroll position was reached, if not save + // the difference in d->scrollRest for later use + const QPointF diffF = newScroll - contentAreaPosition(); + if ( abs( diffF.x() ) < 0.5 && abs( diffF.y() ) < 0.5 ) + { + // scroll target reached set d->scrollRest to 0.0 + d->scrollRest = QPointF( 0.0, 0.0 ); + } else + { + d->scrollRest = diffF; + } +} + //BEGIN private SLOTS void PageView::slotRelayoutPages() // called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom @@ -4778,7 +4859,7 @@ viewport()->setUpdatesEnabled( false ); resizeContentArea( QSize( fullWidth, fullHeight ) ); // restore previous viewport if defined and updates enabled - if ( wasUpdatesEnabled ) + if ( wasUpdatesEnabled && !d->zoomActive ) { if ( vp.pageNumber >= 0 ) { @@ -4801,7 +4882,7 @@ } // 5) update the whole viewport if updated enabled - if ( wasUpdatesEnabled ) + if ( wasUpdatesEnabled && !d->zoomActive ) viewport()->update(); }