Skip to content

Commit ef67275

Browse files
committed
[FEATURE] Add a dock widget showing snap guides for current page
Allows creation of snap lines at specific locations, and adjusting position of existing guides to exact coordinates
1 parent f5126b0 commit ef67275

19 files changed

+269
-34
lines changed

python/core/layout/qgslayoutguidecollection.sip

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class QgsLayoutGuideCollection : QAbstractListModel
145145
PositionRole,
146146
UnitsRole,
147147
PageRole,
148+
LayoutPositionRole,
148149
};
149150

150151
QgsLayoutGuideCollection( QgsLayout *layout );
@@ -203,6 +204,11 @@ class QgsLayoutGuideProxyModel : QSortFilterProxyModel
203204
Page numbers begin at 0.
204205
%End
205206

207+
void setPage( int page );
208+
%Docstring
209+
Sets the current ``page`` for filtering matching guides. Page numbers begin at 0.
210+
%End
211+
206212
virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const;
207213

208214
virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;

python/core/layout/qgslayoutsnapper.sip

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ class QgsLayoutSnapper
7878
:rtype: QPointF
7979
%End
8080

81+
double snapPointToGuides( double original, QgsLayoutGuide::Orientation orientation, double scaleFactor, bool &snapped /Out/ ) const;
82+
%Docstring
83+
Snaps a layout coordinate ``point`` to the grid. If ``point``
84+
was snapped, ``snapped`` will be set to true.
85+
86+
The ``scaleFactor`` argument should be set to the transformation from
87+
scalar transform from layout coordinates to pixels, i.e. the
88+
graphics view transform().m11() value.
89+
90+
If snapToGrid() is disabled, this method will return the point
91+
unchanged.
92+
:rtype: float
93+
%End
94+
8195
};
8296

8397
/************************************************************************

python/gui/layout/qgslayoutview.sip

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ class QgsLayoutView: QGraphicsView
103103
:rtype: QgsLayoutViewMenuProvider
104104
%End
105105

106+
int currentPage() const;
107+
%Docstring
108+
Returns the page visible in the view. This method
109+
considers the page at the center of the view as the current visible
110+
page.
111+
.. seealso:: pageChanged()
112+
:rtype: int
113+
%End
114+
106115
public slots:
107116

108117
void zoomFull();
@@ -147,9 +156,9 @@ class QgsLayoutView: QGraphicsView
147156

148157
void emitZoomLevelChanged();
149158

150-
void updateRulers();
159+
void viewChanged();
151160
%Docstring
152-
Updates associated rulers after view extent or zoom has changed.
161+
Updates associated rulers and other widgets after view extent or zoom has changed.
153162
This should be called after calling any of the QGraphicsView
154163
base class methods which alter the view's zoom level or extent,
155164
i.e. QGraphicsView.fitInView().
@@ -182,6 +191,14 @@ class QgsLayoutView: QGraphicsView
182191
the layout coordinate system.
183192
%End
184193

194+
void pageChanged( int page );
195+
%Docstring
196+
Emitted when the page visible in the view is changed. This signal
197+
considers the page at the center of the view as the current visible
198+
page.
199+
.. seealso:: currentPage()
200+
%End
201+
185202
protected:
186203
virtual void mousePressEvent( QMouseEvent *event );
187204

src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ SET(QGIS_APP_SRCS
157157

158158
layout/qgslayoutaddpagesdialog.cpp
159159
layout/qgslayoutdesignerdialog.cpp
160+
layout/qgslayoutguidewidget.cpp
160161
layout/qgslayoutappmenuprovider.cpp
161162
layout/qgslayoutpagepropertieswidget.cpp
162163
layout/qgslayoutpropertieswidget.cpp
@@ -339,6 +340,7 @@ SET (QGIS_APP_MOC_HDRS
339340
layout/qgslayoutaddpagesdialog.h
340341
layout/qgslayoutappmenuprovider.h
341342
layout/qgslayoutdesignerdialog.h
343+
layout/qgslayoutguidewidget.h
342344
layout/qgslayoutpagepropertieswidget.h
343345
layout/qgslayoutpropertieswidget.h
344346

src/app/layout/qgslayoutdesignerdialog.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "qgspanelwidget.h"
3838
#include "qgsdockwidget.h"
3939
#include "qgslayoutpagepropertieswidget.h"
40+
#include "qgslayoutguidewidget.h"
4041
#include <QShortcut>
4142
#include <QComboBox>
4243
#include <QLineEdit>
@@ -257,8 +258,20 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
257258
mItemDock->setWidget( mItemPropertiesStack );
258259
mPanelsMenu->addAction( mItemDock->toggleViewAction() );
259260

261+
mGuideDock = new QgsDockWidget( tr( "Guides" ), this );
262+
mGuideDock->setObjectName( QStringLiteral( "GuideDock" ) );
263+
mGuideDock->setMinimumWidth( minDockWidth );
264+
mGuideStack = new QgsPanelWidgetStack();
265+
mGuideDock->setWidget( mGuideStack );
266+
mPanelsMenu->addAction( mGuideDock->toggleViewAction() );
267+
connect( mActionManageGuides, &QAction::triggered, this, [ = ]
268+
{
269+
mGuideDock->setUserVisible( true );
270+
} );
271+
260272
addDockWidget( Qt::RightDockWidgetArea, mItemDock );
261273
addDockWidget( Qt::RightDockWidgetArea, mGeneralDock );
274+
addDockWidget( Qt::RightDockWidgetArea, mGuideDock );
262275

263276
createLayoutPropertiesWidget();
264277

@@ -317,6 +330,7 @@ void QgsLayoutDesignerDialog::showItemOptions( QgsLayoutItem *item )
317330
delete mItemPropertiesStack->takeMainPanel();
318331
widget->setDockMode( true );
319332
mItemPropertiesStack->setMainPanel( widget.release() );
333+
mItemDock->setUserVisible( true );
320334
}
321335

322336
void QgsLayoutDesignerDialog::open()
@@ -615,13 +629,19 @@ void QgsLayoutDesignerDialog::createLayoutPropertiesWidget()
615629
return;
616630
}
617631

618-
// update composition widget
632+
// update layout based widgets
619633
QgsLayoutPropertiesWidget *oldCompositionWidget = qobject_cast<QgsLayoutPropertiesWidget *>( mGeneralPropertiesStack->takeMainPanel() );
620634
delete oldCompositionWidget;
635+
QgsLayoutGuideWidget *oldGuideWidget = qobject_cast<QgsLayoutGuideWidget *>( mGuideStack->takeMainPanel() );
636+
delete oldGuideWidget;
621637

622638
QgsLayoutPropertiesWidget *widget = new QgsLayoutPropertiesWidget( mGeneralDock, mLayout );
623639
widget->setDockMode( true );
624640
mGeneralPropertiesStack->setMainPanel( widget );
641+
642+
QgsLayoutGuideWidget *guideWidget = new QgsLayoutGuideWidget( mGuideDock, mLayout, mView );
643+
guideWidget->setDockMode( true );
644+
mGuideStack->setMainPanel( guideWidget );
625645
}
626646

627647
void QgsLayoutDesignerDialog::initializeRegistry()

src/app/layout/qgslayoutdesignerdialog.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
193193
QgsPanelWidgetStack *mItemPropertiesStack = nullptr;
194194
QgsDockWidget *mGeneralDock = nullptr;
195195
QgsPanelWidgetStack *mGeneralPropertiesStack = nullptr;
196+
QgsDockWidget *mGuideDock = nullptr;
197+
QgsPanelWidgetStack *mGuideStack = nullptr;
196198

197199
//! Save window state
198200
void saveWindowState();

src/core/layout/qgslayoutguidecollection.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,12 @@ QgsLayoutGuideProxyModel::QgsLayoutGuideProxyModel( QObject *parent, QgsLayoutGu
286286
sort( 0 );
287287
}
288288

289+
void QgsLayoutGuideProxyModel::setPage( int page )
290+
{
291+
mPage = page;
292+
invalidateFilter();
293+
}
294+
289295
bool QgsLayoutGuideProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
290296
{
291297
QModelIndex index = sourceModel()->index( source_row, 0, source_parent );

src/core/layout/qgslayoutguidecollection.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractListModel
175175
PositionRole, //!< Guide position role
176176
UnitsRole, //!< Guide position units role
177177
PageRole, //!< Guide page role
178+
LayoutPositionRole, //!< Guide position in layout coordinates
178179
};
179180

180181
/**
@@ -234,6 +235,11 @@ class CORE_EXPORT QgsLayoutGuideProxyModel : public QSortFilterProxyModel
234235
*/
235236
explicit QgsLayoutGuideProxyModel( QObject *parent SIP_TRANSFERTHIS, QgsLayoutGuide::Orientation orientation, int page );
236237

238+
/**
239+
* Sets the current \a page for filtering matching guides. Page numbers begin at 0.
240+
*/
241+
void setPage( int page );
242+
237243
bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override;
238244
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override;
239245

src/core/layout/qgslayoutsnapper.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &sn
3535
return res;
3636
}
3737

38+
bool snappedToHozGuides = false;
39+
double newX = snapPointToGuides( point.x(), QgsLayoutGuide::Vertical, scaleFactor, snappedToHozGuides );
40+
if ( snappedToHozGuides )
41+
{
42+
snapped = true;
43+
point.setX( newX );
44+
}
45+
bool snappedToVertGuides = false;
46+
double newY = snapPointToGuides( point.y(), QgsLayoutGuide::Horizontal, scaleFactor, snappedToVertGuides );
47+
if ( snappedToVertGuides )
48+
{
49+
snapped = true;
50+
point.setY( newY );
51+
}
52+
3853
return point;
3954
}
4055

@@ -88,3 +103,35 @@ QPointF QgsLayoutSnapper::snapPointToGrid( QPointF point, double scaleFactor, bo
88103

89104
return QPointF( xSnapped, ySnapped );
90105
}
106+
107+
double QgsLayoutSnapper::snapPointToGuides( double original, QgsLayoutGuide::Orientation orientation, double scaleFactor, bool &snapped ) const
108+
{
109+
snapped = false;
110+
111+
//convert snap tolerance from pixels to layout units
112+
double alignThreshold = mTolerance / scaleFactor;
113+
114+
double bestPos = original;
115+
double smallestDiff = DBL_MAX;
116+
117+
Q_FOREACH ( QgsLayoutGuide *guide, mLayout->guides().guides( orientation ) )
118+
{
119+
double guidePos = guide->layoutPosition();
120+
double diff = qAbs( original - guidePos );
121+
if ( diff < smallestDiff )
122+
{
123+
smallestDiff = diff;
124+
bestPos = guidePos;
125+
}
126+
}
127+
128+
if ( smallestDiff <= alignThreshold )
129+
{
130+
snapped = true;
131+
return bestPos;
132+
}
133+
else
134+
{
135+
return original;
136+
}
137+
}

src/core/layout/qgslayoutsnapper.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "qgis_core.h"
2020
#include "qgslayoutmeasurement.h"
2121
#include "qgslayoutpoint.h"
22+
#include "qgslayoutguidecollection.h"
2223
#include <QPen>
2324

2425
class QgsLayout;
@@ -88,6 +89,19 @@ class CORE_EXPORT QgsLayoutSnapper
8889
*/
8990
QPointF snapPointToGrid( QPointF point, double scaleFactor, bool &snapped SIP_OUT ) const;
9091

92+
/**
93+
* Snaps a layout coordinate \a point to the grid. If \a point
94+
* was snapped, \a snapped will be set to true.
95+
*
96+
* The \a scaleFactor argument should be set to the transformation from
97+
* scalar transform from layout coordinates to pixels, i.e. the
98+
* graphics view transform().m11() value.
99+
*
100+
* If snapToGrid() is disabled, this method will return the point
101+
* unchanged.
102+
*/
103+
double snapPointToGuides( double original, QgsLayoutGuide::Orientation orientation, double scaleFactor, bool &snapped SIP_OUT ) const;
104+
91105
private:
92106

93107
QgsLayout *mLayout = nullptr;

0 commit comments

Comments
 (0)