diff --git a/python/PyQt6/gui/auto_additions/qgslayoutview.py b/python/PyQt6/gui/auto_additions/qgslayoutview.py index 9fe870678c8b..5a7eee652865 100644 --- a/python/PyQt6/gui/auto_additions/qgslayoutview.py +++ b/python/PyQt6/gui/auto_additions/qgslayoutview.py @@ -5,8 +5,8 @@ QgsLayoutView.PasteModeCenter = QgsLayoutView.PasteMode.PasteModeCenter QgsLayoutView.PasteModeInPlace = QgsLayoutView.PasteMode.PasteModeInPlace try: - QgsLayoutView.__attribute_docs__ = {'layoutSet': 'Emitted when a ``layout`` is set for the view.\n\n.. seealso:: :py:func:`currentLayout`\n\n.. seealso:: :py:func:`setCurrentLayout`\n', 'toolSet': 'Emitted when the current ``tool`` is changed.\n\n.. seealso:: :py:func:`setTool`\n', 'zoomLevelChanged': 'Emitted whenever the zoom level of the view is changed.\n', 'cursorPosChanged': 'Emitted when the mouse cursor coordinates change within the view.\nThe ``layoutPoint`` argument indicates the cursor position within\nthe layout coordinate system.\n', 'pageChanged': 'Emitted when the page visible in the view is changed. This signal\nconsiders the page at the center of the view as the current visible\npage.\n\n.. seealso:: :py:func:`currentPage`\n', 'statusMessage': "Emitted when the view has a ``message`` for display in a parent window's\nstatus bar.\n\n.. seealso:: :py:func:`pushStatusMessage`\n", 'itemFocused': 'Emitted when an ``item`` is "focused" in the view, i.e. it becomes the active\nitem and should have its properties displayed in any designer windows.\n', 'willBeDeleted': 'Emitted in the destructor when the view is about to be deleted,\nbut is still in a perfectly valid state.\n'} - QgsLayoutView.__signal_arguments__ = {'layoutSet': ['layout: QgsLayout'], 'toolSet': ['tool: QgsLayoutViewTool'], 'cursorPosChanged': ['layoutPoint: QPointF'], 'pageChanged': ['page: int'], 'statusMessage': ['message: str'], 'itemFocused': ['item: QgsLayoutItem']} + QgsLayoutView.__attribute_docs__ = {'layoutSet': 'Emitted when a ``layout`` is set for the view.\n\n.. seealso:: :py:func:`currentLayout`\n\n.. seealso:: :py:func:`setCurrentLayout`\n', 'toolSet': 'Emitted when the current ``tool`` is changed.\n\n.. seealso:: :py:func:`setTool`\n', 'zoomLevelChanged': 'Emitted whenever the zoom level of the view is changed.\n', 'cursorPosChanged': 'Emitted when the mouse cursor coordinates change within the view.\nThe ``layoutPoint`` argument indicates the cursor position within\nthe layout coordinate system.\n', 'pageChanged': 'Emitted when the page visible in the view is changed. This signal\nconsiders the page at the center of the view as the current visible\npage.\n\n.. seealso:: :py:func:`currentPage`\n', 'statusMessage': "Emitted when the view has a ``message`` for display in a parent window's\nstatus bar.\n\n.. seealso:: :py:func:`pushStatusMessage`\n", 'itemFocused': 'Emitted when an ``item`` is "focused" in the view, i.e. it becomes the active\nitem and should have its properties displayed in any designer windows.\n', 'willBeDeleted': 'Emitted in the destructor when the view is about to be deleted,\nbut is still in a perfectly valid state.\n', 'zoomLastStatusChanged': 'Emitted when zoom last status changed\n', 'zoomNextStatusChanged': 'Emitted when zoom next status changed\n'} + QgsLayoutView.__signal_arguments__ = {'layoutSet': ['layout: QgsLayout'], 'toolSet': ['tool: QgsLayoutViewTool'], 'zoomLevelChanged': ['appendToHistory: bool = True'], 'cursorPosChanged': ['layoutPoint: QPointF'], 'pageChanged': ['page: int'], 'statusMessage': ['message: str'], 'itemFocused': ['item: QgsLayoutItem'], 'zoomLastStatusChanged': [': bool'], 'zoomNextStatusChanged': [': bool']} QgsLayoutView.__group__ = ['layout'] except NameError: pass diff --git a/python/PyQt6/gui/auto_generated/layout/qgslayoutview.sip.in b/python/PyQt6/gui/auto_generated/layout/qgslayoutview.sip.in index 0e3e1432a57a..b5f08e0c4ea9 100644 --- a/python/PyQt6/gui/auto_generated/layout/qgslayoutview.sip.in +++ b/python/PyQt6/gui/auto_generated/layout/qgslayoutview.sip.in @@ -334,6 +334,22 @@ Zooms to the actual size of the layout. .. seealso:: :py:func:`zoomOut` %End + void zoomLast(); +%Docstring +Zooms to the previous zoom level + +.. versionadded:: 3.38 +%End + + void zoomNext(); +%Docstring +Zooms to the next zoom level + +.. versionadded:: 3.38 +%End + + + void emitZoomLevelChanged(); @@ -513,7 +529,7 @@ Emitted when the current ``tool`` is changed. .. seealso:: :py:func:`setTool` %End - void zoomLevelChanged(); + void zoomLevelChanged( bool appendToHistory = true ); %Docstring Emitted whenever the zoom level of the view is changed. %End @@ -552,6 +568,16 @@ item and should have its properties displayed in any designer windows. %Docstring Emitted in the destructor when the view is about to be deleted, but is still in a perfectly valid state. +%End + + void zoomLastStatusChanged( bool ); +%Docstring +Emitted when zoom last status changed +%End + + void zoomNextStatusChanged( bool ); +%Docstring +Emitted when zoom next status changed %End protected: diff --git a/python/gui/auto_additions/qgslayoutview.py b/python/gui/auto_additions/qgslayoutview.py index 135c50987a41..e52f72302af2 100644 --- a/python/gui/auto_additions/qgslayoutview.py +++ b/python/gui/auto_additions/qgslayoutview.py @@ -1,7 +1,7 @@ # The following has been generated automatically from src/gui/layout/qgslayoutview.h try: - QgsLayoutView.__attribute_docs__ = {'layoutSet': 'Emitted when a ``layout`` is set for the view.\n\n.. seealso:: :py:func:`currentLayout`\n\n.. seealso:: :py:func:`setCurrentLayout`\n', 'toolSet': 'Emitted when the current ``tool`` is changed.\n\n.. seealso:: :py:func:`setTool`\n', 'zoomLevelChanged': 'Emitted whenever the zoom level of the view is changed.\n', 'cursorPosChanged': 'Emitted when the mouse cursor coordinates change within the view.\nThe ``layoutPoint`` argument indicates the cursor position within\nthe layout coordinate system.\n', 'pageChanged': 'Emitted when the page visible in the view is changed. This signal\nconsiders the page at the center of the view as the current visible\npage.\n\n.. seealso:: :py:func:`currentPage`\n', 'statusMessage': "Emitted when the view has a ``message`` for display in a parent window's\nstatus bar.\n\n.. seealso:: :py:func:`pushStatusMessage`\n", 'itemFocused': 'Emitted when an ``item`` is "focused" in the view, i.e. it becomes the active\nitem and should have its properties displayed in any designer windows.\n', 'willBeDeleted': 'Emitted in the destructor when the view is about to be deleted,\nbut is still in a perfectly valid state.\n'} - QgsLayoutView.__signal_arguments__ = {'layoutSet': ['layout: QgsLayout'], 'toolSet': ['tool: QgsLayoutViewTool'], 'cursorPosChanged': ['layoutPoint: QPointF'], 'pageChanged': ['page: int'], 'statusMessage': ['message: str'], 'itemFocused': ['item: QgsLayoutItem']} + QgsLayoutView.__attribute_docs__ = {'layoutSet': 'Emitted when a ``layout`` is set for the view.\n\n.. seealso:: :py:func:`currentLayout`\n\n.. seealso:: :py:func:`setCurrentLayout`\n', 'toolSet': 'Emitted when the current ``tool`` is changed.\n\n.. seealso:: :py:func:`setTool`\n', 'zoomLevelChanged': 'Emitted whenever the zoom level of the view is changed.\n', 'cursorPosChanged': 'Emitted when the mouse cursor coordinates change within the view.\nThe ``layoutPoint`` argument indicates the cursor position within\nthe layout coordinate system.\n', 'pageChanged': 'Emitted when the page visible in the view is changed. This signal\nconsiders the page at the center of the view as the current visible\npage.\n\n.. seealso:: :py:func:`currentPage`\n', 'statusMessage': "Emitted when the view has a ``message`` for display in a parent window's\nstatus bar.\n\n.. seealso:: :py:func:`pushStatusMessage`\n", 'itemFocused': 'Emitted when an ``item`` is "focused" in the view, i.e. it becomes the active\nitem and should have its properties displayed in any designer windows.\n', 'willBeDeleted': 'Emitted in the destructor when the view is about to be deleted,\nbut is still in a perfectly valid state.\n', 'zoomLastStatusChanged': 'Emitted when zoom last status changed\n', 'zoomNextStatusChanged': 'Emitted when zoom next status changed\n'} + QgsLayoutView.__signal_arguments__ = {'layoutSet': ['layout: QgsLayout'], 'toolSet': ['tool: QgsLayoutViewTool'], 'zoomLevelChanged': ['appendToHistory: bool = True'], 'cursorPosChanged': ['layoutPoint: QPointF'], 'pageChanged': ['page: int'], 'statusMessage': ['message: str'], 'itemFocused': ['item: QgsLayoutItem'], 'zoomLastStatusChanged': [': bool'], 'zoomNextStatusChanged': [': bool']} QgsLayoutView.__group__ = ['layout'] except NameError: pass diff --git a/python/gui/auto_generated/layout/qgslayoutview.sip.in b/python/gui/auto_generated/layout/qgslayoutview.sip.in index b4c9f9d27ea2..2bbe5e9e0aa1 100644 --- a/python/gui/auto_generated/layout/qgslayoutview.sip.in +++ b/python/gui/auto_generated/layout/qgslayoutview.sip.in @@ -334,6 +334,22 @@ Zooms to the actual size of the layout. .. seealso:: :py:func:`zoomOut` %End + void zoomLast(); +%Docstring +Zooms to the previous zoom level + +.. versionadded:: 3.38 +%End + + void zoomNext(); +%Docstring +Zooms to the next zoom level + +.. versionadded:: 3.38 +%End + + + void emitZoomLevelChanged(); @@ -513,7 +529,7 @@ Emitted when the current ``tool`` is changed. .. seealso:: :py:func:`setTool` %End - void zoomLevelChanged(); + void zoomLevelChanged( bool appendToHistory = true ); %Docstring Emitted whenever the zoom level of the view is changed. %End @@ -552,6 +568,16 @@ item and should have its properties displayed in any designer windows. %Docstring Emitted in the destructor when the view is about to be deleted, but is still in a perfectly valid state. +%End + + void zoomLastStatusChanged( bool ); +%Docstring +Emitted when zoom last status changed +%End + + void zoomNextStatusChanged( bool ); +%Docstring +Emitted when zoom next status changed %End protected: diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 04a30b31c51b..9764a891104c 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -666,6 +666,11 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla connect( mActionZoomActual, &QAction::triggered, mView, &QgsLayoutView::zoomActual ); connect( mActionZoomToWidth, &QAction::triggered, mView, &QgsLayoutView::zoomWidth ); + connect( mActionZoomLast, &QAction::triggered, mView, &QgsLayoutView::zoomLast ); + connect( mActionZoomNext, &QAction::triggered, mView, &QgsLayoutView::zoomNext ); + connect( mView, &QgsLayoutView::zoomLastStatusChanged, mActionZoomLast, &QAction::setEnabled ); + connect( mView, &QgsLayoutView::zoomNextStatusChanged, mActionZoomNext, &QAction::setEnabled ); + connect( mActionSelectAll, &QAction::triggered, mView, &QgsLayoutView::selectAll ); connect( mActionDeselectAll, &QAction::triggered, mView, &QgsLayoutView::deselectAll ); connect( mActionInvertSelection, &QAction::triggered, mView, &QgsLayoutView::invertSelection ); @@ -4768,6 +4773,8 @@ void QgsLayoutDesignerDialog::toggleActions( bool layoutAvailable ) mActionZoomOut->setEnabled( layoutAvailable ); mActionZoomActual->setEnabled( layoutAvailable ); mActionZoomToWidth->setEnabled( layoutAvailable ); + mActionZoomLast->setEnabled( layoutAvailable ); + mActionZoomNext->setEnabled( layoutAvailable ); mActionAddPages->setEnabled( layoutAvailable ); mActionShowGrid->setEnabled( layoutAvailable ); mActionSnapGrid->setEnabled( layoutAvailable ); diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index 2e38ee273521..3dbc77b693c2 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -63,6 +63,7 @@ QgsLayoutView::QgsLayoutView( QWidget *parent ) mPreviewEffect = new QgsPreviewEffect( this ); viewport()->setGraphicsEffect( mPreviewEffect ); + connect( this, &QgsLayoutView::zoomLevelChanged, this, &QgsLayoutView::onZoomLevelChanged ); connect( this, &QgsLayoutView::zoomLevelChanged, this, &QgsLayoutView::invalidateCachedRenders ); mScreenHelper = new QgsScreenHelper( this ); @@ -580,11 +581,63 @@ void QgsLayoutView::zoomActual() setZoomLevel( 1.0 ); } +void QgsLayoutView::zoomLast() +{ + mLastTransformIndex--; + setTransform( mLastTransform[mLastTransformIndex] ); + invalidateCachedRenders(); + // update controls' enabled state + emit zoomLastStatusChanged( mLastTransformIndex > 0 ); + emit zoomNextStatusChanged( mLastTransformIndex < mLastTransform.size() - 1 ); +} + +void QgsLayoutView::zoomNext() +{ + mLastTransformIndex++; + setTransform( mLastTransform[mLastTransformIndex] ); + invalidateCachedRenders(); + // update controls' enabled state + emit zoomLastStatusChanged( mLastTransformIndex > 0 ); + emit zoomNextStatusChanged( mLastTransformIndex < mLastTransform.size() - 1 ); +} + void QgsLayoutView::emitZoomLevelChanged() { emit zoomLevelChanged(); } + +void QgsLayoutView::onZoomLevelChanged( bool appendToHistory ) +{ + if ( !appendToHistory ) + { + return; + } + //clear all transforms items after current index + for ( int i = mLastTransform.size() - 1; i > mLastTransformIndex; i-- ) + { + mLastTransform.removeAt( i ); + } + + if ( mLastTransform.isEmpty() || mLastTransform.last() != transform() ) + { + mLastTransform.append( transform() ); + } + + // adjust history to no more than 100 + if ( mLastTransform.size() > 100 ) + { + mLastTransform.removeAt( 0 ); + } + + // the last item is the current transform + mLastTransformIndex = mLastTransform.size() - 1; + + // update controls' enabled state + emit zoomLastStatusChanged( mLastTransformIndex > 0 ); + emit zoomNextStatusChanged( mLastTransformIndex < mLastTransform.size() - 1 ); +} + void QgsLayoutView::selectAll() { if ( !currentLayout() ) @@ -1119,7 +1172,7 @@ void QgsLayoutView::keyReleaseEvent( QKeyEvent *event ) void QgsLayoutView::resizeEvent( QResizeEvent *event ) { QGraphicsView::resizeEvent( event ); - emit zoomLevelChanged(); + emit zoomLevelChanged( false ); viewChanged(); } diff --git a/src/gui/layout/qgslayoutview.h b/src/gui/layout/qgslayoutview.h index 8ba5d079d741..012fff779361 100644 --- a/src/gui/layout/qgslayoutview.h +++ b/src/gui/layout/qgslayoutview.h @@ -337,6 +337,20 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView */ void zoomActual(); + /** + * Zooms to the previous zoom level + * \since QGIS 3.38 + */ + void zoomLast(); + + /** + * Zooms to the next zoom level + * \since QGIS 3.38 + */ + void zoomNext(); + + + /** * Emits the zoomLevelChanged() signal. This should be called after * calling any of the QGraphicsView base class methods which alter @@ -496,7 +510,7 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView /** * Emitted whenever the zoom level of the view is changed. */ - void zoomLevelChanged(); + void zoomLevelChanged( bool appendToHistory = true ); /** * Emitted when the mouse cursor coordinates change within the view. @@ -532,6 +546,12 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView */ void willBeDeleted(); + //! Emitted when zoom last status changed + void zoomLastStatusChanged( bool ); + + //! Emitted when zoom next status changed + void zoomNextStatusChanged( bool ); + protected: void mousePressEvent( QMouseEvent *event ) override; void mouseReleaseEvent( QMouseEvent *event ) override; @@ -548,6 +568,7 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView private slots: void invalidateCachedRenders(); + void onZoomLevelChanged( bool appendToHistory = true ); private: @@ -584,6 +605,10 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView friend class QgsLayoutMouseHandles; QGraphicsLineItem *createSnapLine() const; + + //! recently used extent + QList mLastTransform; + int mLastTransformIndex = -1; }; diff --git a/src/ui/layout/qgslayoutdesignerbase.ui b/src/ui/layout/qgslayoutdesignerbase.ui index f8dbcfb31932..9a73d1117906 100644 --- a/src/ui/layout/qgslayoutdesignerbase.ui +++ b/src/ui/layout/qgslayoutdesignerbase.ui @@ -116,6 +116,8 @@ + + @@ -182,7 +184,7 @@ 0 0 2180 - 22 + 24 @@ -1585,6 +1587,30 @@ &Keyboard Shortcuts... + + + + :/images/themes/default/mActionZoomLast.svg:/images/themes/default/mActionZoomLast.svg + + + Zoom Last + + + Alt+Left + + + + + + :/images/themes/default/mActionZoomNext.svg:/images/themes/default/mActionZoomNext.svg + + + Zoom Next + + + Alt+Right + +