Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Last & Next zoom actions in layouts #58789

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions python/PyQt6/gui/auto_additions/qgslayoutview.py
Original file line number Diff line number Diff line change
Expand Up @@ -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', 'extentChanged': 'Emitted whenever the extent of the displayed scene has 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'], 'cursorPosChanged': ['layoutPoint: QPointF'], 'pageChanged': ['page: int'], 'statusMessage': ['message: str'], 'itemFocused': ['item: QgsLayoutItem'], 'zoomLastStatusChanged': [': bool'], 'zoomNextStatusChanged': [': bool']}
QgsLayoutView.__group__ = ['layout']
except NameError:
pass
Expand Down
50 changes: 50 additions & 0 deletions python/PyQt6/gui/auto_generated/layout/qgslayoutview.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ for the given key ``event``.
%Docstring
Sets a section ``label``, to display above the first page shown in the
view.
%End

virtual bool eventFilter( QObject *object, QEvent *event );

%Docstring
Event filter to handle mouse release on scroll bars
%End

public slots:
Expand Down Expand Up @@ -332,6 +338,34 @@ Zooms to the actual size of the layout.
.. seealso:: :py:func:`zoomIn`

.. seealso:: :py:func:`zoomOut`
%End

void zoomLast();
%Docstring
Zooms to the previous zoom level

.. versionadded:: 3.42
%End

void zoomNext();
%Docstring
Zooms to the next zoom level

.. versionadded:: 3.42
%End

bool canZoomLast() const;
%Docstring
Whether zoom last is available

.. versionadded:: 3.42
%End

bool canZoomNext() const;
%Docstring
Whether zoom next is available

.. versionadded:: 3.42
%End

void emitZoomLevelChanged();
Expand Down Expand Up @@ -516,6 +550,11 @@ Emitted when the current ``tool`` is changed.
void zoomLevelChanged();
%Docstring
Emitted whenever the zoom level of the view is changed.
%End

void extentChanged();
%Docstring
Emitted whenever the extent of the displayed scene has changed
%End

void cursorPosChanged( QPointF layoutPoint );
Expand Down Expand Up @@ -554,6 +593,17 @@ 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:
virtual void mousePressEvent( QMouseEvent *event );

Expand Down
4 changes: 2 additions & 2 deletions python/gui/auto_additions/qgslayoutview.py
Original file line number Diff line number Diff line change
@@ -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', 'extentChanged': 'Emitted whenever the extent of the displayed scene has 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'], 'cursorPosChanged': ['layoutPoint: QPointF'], 'pageChanged': ['page: int'], 'statusMessage': ['message: str'], 'itemFocused': ['item: QgsLayoutItem'], 'zoomLastStatusChanged': [': bool'], 'zoomNextStatusChanged': [': bool']}
QgsLayoutView.__group__ = ['layout']
except NameError:
pass
Expand Down
50 changes: 50 additions & 0 deletions python/gui/auto_generated/layout/qgslayoutview.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ for the given key ``event``.
%Docstring
Sets a section ``label``, to display above the first page shown in the
view.
%End

virtual bool eventFilter( QObject *object, QEvent *event );

%Docstring
Event filter to handle mouse release on scroll bars
%End

public slots:
Expand Down Expand Up @@ -332,6 +338,34 @@ Zooms to the actual size of the layout.
.. seealso:: :py:func:`zoomIn`

.. seealso:: :py:func:`zoomOut`
%End

void zoomLast();
%Docstring
Zooms to the previous zoom level

.. versionadded:: 3.42
%End

void zoomNext();
%Docstring
Zooms to the next zoom level

.. versionadded:: 3.42
%End

bool canZoomLast() const;
%Docstring
Whether zoom last is available

.. versionadded:: 3.42
%End

bool canZoomNext() const;
%Docstring
Whether zoom next is available

.. versionadded:: 3.42
%End

void emitZoomLevelChanged();
Expand Down Expand Up @@ -516,6 +550,11 @@ Emitted when the current ``tool`` is changed.
void zoomLevelChanged();
%Docstring
Emitted whenever the zoom level of the view is changed.
%End

void extentChanged();
%Docstring
Emitted whenever the extent of the displayed scene has changed
%End

void cursorPosChanged( QPointF layoutPoint );
Expand Down Expand Up @@ -554,6 +593,17 @@ 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:
virtual void mousePressEvent( QMouseEvent *event );

Expand Down
7 changes: 7 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,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 );
Expand Down Expand Up @@ -4790,6 +4795,8 @@ void QgsLayoutDesignerDialog::toggleActions( bool layoutAvailable )
mActionZoomOut->setEnabled( layoutAvailable );
mActionZoomActual->setEnabled( layoutAvailable );
mActionZoomToWidth->setEnabled( layoutAvailable );
mActionZoomLast->setEnabled( layoutAvailable && mView->canZoomLast() );
mActionZoomNext->setEnabled( layoutAvailable && mView->canZoomNext() );
mActionAddPages->setEnabled( layoutAvailable );
mActionShowGrid->setEnabled( layoutAvailable );
mActionSnapGrid->setEnabled( layoutAvailable );
Expand Down
100 changes: 99 additions & 1 deletion src/gui/layout/qgslayoutview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <QMenu>
#include <QClipboard>
#include <QMimeData>
#include <QScrollBar>

#define MIN_VIEW_SCALE 0.05
#define MAX_VIEW_SCALE 1000.0
Expand All @@ -64,7 +65,12 @@ QgsLayoutView::QgsLayoutView( QWidget *parent )
mPreviewEffect = new QgsPreviewEffect( this );
viewport()->setGraphicsEffect( mPreviewEffect );

connect( this, &QgsLayoutView::extentChanged, this, &QgsLayoutView::onExtentChanged );
connect( this, &QgsLayoutView::zoomLevelChanged, this, &QgsLayoutView::invalidateCachedRenders );
connect( this, &QgsLayoutView::zoomLevelChanged, this, &QgsLayoutView::extentChanged );

verticalScrollBar()->installEventFilter( this );
horizontalScrollBar()->installEventFilter( this );
Comment on lines +70 to +73
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could all this logic be moved into the existing QgsLayoutView::viewChanged method? That should already be getting called whenever the view extent is changed, and already handles all the different ways this can happen.

That should also avoid the emit view()->extentChanged(); calls from other classes, which is rather fragile.

Copy link
Contributor Author

@YoannQDQ YoannQDQ Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is viewChanged is called every time the scrollbar moves. So dragging the scrollbar handle will trigger many MANY viewChanged calls, and we dont want them to clutter the zoom level history. Similarly, we do not want to add entries in the history while dragging the view with the pan tool.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above -- I'd handle this by using a timer to "collapse" extents which were used very close to each other.


mScreenHelper = new QgsScreenHelper( this );
}
Expand Down Expand Up @@ -581,11 +587,80 @@ void QgsLayoutView::zoomActual()
setZoomLevel( 1.0 );
}

void QgsLayoutView::zoomLast()
{
if ( !canZoomLast() )
return;
mLastTransformIndex--;
centerOn( mLastTransform[mLastTransformIndex].first );
if ( transform() != mLastTransform[mLastTransformIndex].second )
{
setTransform( mLastTransform[mLastTransformIndex].second );
invalidateCachedRenders();
}
// update controls' enabled state
emit zoomLastStatusChanged( canZoomLast() );
emit zoomNextStatusChanged( canZoomNext() );
}

void QgsLayoutView::zoomNext()
{
if ( !canZoomNext() )
return;
mLastTransformIndex++;
centerOn( mLastTransform[mLastTransformIndex].first );
if ( transform() != mLastTransform[mLastTransformIndex].second )
{
setTransform( mLastTransform[mLastTransformIndex].second );
invalidateCachedRenders();
}
// update controls' enabled state
emit zoomLastStatusChanged( canZoomLast() );
emit zoomNextStatusChanged( canZoomNext() );
}

bool QgsLayoutView::canZoomNext() const
{
return mLastTransformIndex < mLastTransform.size() - 1;
}

bool QgsLayoutView::canZoomLast() const
{
return mLastTransformIndex > 0;
}

void QgsLayoutView::emitZoomLevelChanged()
{
emit zoomLevelChanged();
}

void QgsLayoutView::onExtentChanged()
{

//clear all transforms items after current index
mLastTransform = mLastTransform.mid( 0, mLastTransformIndex + 1 );

QPointF center = mapToScene( viewport()->rect().center() );

if ( mLastTransform.isEmpty() || mLastTransform.last().second != transform() || mLastTransform.last().first != center )
{
mLastTransform.append( {center, 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( canZoomLast() );
emit zoomNextStatusChanged( canZoomNext() );
}

void QgsLayoutView::selectAll()
{
if ( !currentLayout() )
Expand Down Expand Up @@ -975,7 +1050,20 @@ void QgsLayoutView::mouseReleaseEvent( QMouseEvent *event )
}

if ( !mTool || !event->isAccepted() )
{

if ( event->button() == Qt::BackButton )
{
zoomLast();
return;
}
else if ( event->button() == Qt::ForwardButton )
{
zoomNext();
return;
}
QGraphicsView::mouseReleaseEvent( event );
}
}

void QgsLayoutView::mouseMoveEvent( QMouseEvent *event )
Expand Down Expand Up @@ -1120,7 +1208,6 @@ void QgsLayoutView::keyReleaseEvent( QKeyEvent *event )
void QgsLayoutView::resizeEvent( QResizeEvent *event )
{
QGraphicsView::resizeEvent( event );
emit zoomLevelChanged();
viewChanged();
}

Expand Down Expand Up @@ -1246,6 +1333,17 @@ void QgsLayoutView::wheelZoom( QWheelEvent *event )
}
}


bool QgsLayoutView::eventFilter( QObject *object, QEvent *event )
{
if ( event->type() == QEvent::MouseButtonRelease )
{
emit extentChanged();
}
return QGraphicsView::eventFilter( object, event );

}

QGraphicsLineItem *QgsLayoutView::createSnapLine() const
{
std::unique_ptr< QGraphicsLineItem> item( new QGraphicsLineItem( nullptr ) );
Expand Down
Loading
Loading