Skip to content

Commit 57637ca

Browse files
committed
Rework sync extent option for map views
Instead of a single sync extent option, the option has been split into a "sync map center" option, and a "sync scale" option. Sync center just does that - syncs the center of the map without changing the scale. This allows you to have an overview style or magnified map which follows the main canvas center. Sync scale borrows from the Auxillary Window/Dockable Mirror Map plugin approach. If sync scale is enabled, a "scale factor" is utilised to multiply the main canvas scale. This allows you to have a view which is e.g. always 2x the scale of the main canvas. Splitting the sync extent option like this allows us to address a much wider set of use cases with map views (at the cost of a bit of UI complexity). It also helps cover more of the feature set from the Aux Window/Dockable Mirror Map plugins, hopefully allowing this built-in view approach to make the need for 3rd party plugins redundant.
1 parent e21b908 commit 57637ca

File tree

5 files changed

+140
-37
lines changed

5 files changed

+140
-37
lines changed

src/app/qgisapp.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -3113,7 +3113,7 @@ QgsMapCanvas *QgisApp::mapCanvas()
31133113
return mMapCanvas;
31143114
}
31153115

3116-
QgsMapCanvas *QgisApp::createNewMapCanvas( const QString &name, bool isFloating, const QRect &dockGeometry, bool synced, bool showCursor )
3116+
QgsMapCanvas *QgisApp::createNewMapCanvas( const QString &name, bool isFloating, const QRect &dockGeometry, bool synced, bool showCursor, bool scaleSynced, double scaleFactor )
31173117
{
31183118
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
31193119
{
@@ -3168,8 +3168,10 @@ QgsMapCanvas *QgisApp::createNewMapCanvas( const QString &name, bool isFloating,
31683168
connect( mapCanvasWidget, &QgsMapCanvasDockWidget::closed, this, &QgisApp::markDirty );
31693169
connect( mapCanvasWidget, &QgsMapCanvasDockWidget::renameTriggered, this, &QgisApp::renameView );
31703170

3171-
mapCanvasWidget->setViewExtentSynchronized( synced );
3171+
mapCanvasWidget->setViewCenterSynchronized( synced );
31723172
mapCanvasWidget->setCursorMarkerVisible( showCursor );
3173+
mapCanvasWidget->setScaleFactor( scaleFactor );
3174+
mapCanvasWidget->setViewScaleSynchronized( scaleSynced );
31733175

31743176
return mapCanvas;
31753177
}
@@ -11753,8 +11755,10 @@ void QgisApp::writeProject( QDomDocument &doc )
1175311755
node.setAttribute( QStringLiteral( "width" ), w->width() );
1175411756
node.setAttribute( QStringLiteral( "height" ), w->height() );
1175511757
node.setAttribute( QStringLiteral( "floating" ), w->isFloating() );
11756-
node.setAttribute( QStringLiteral( "synced" ), w->isViewExtentSynchronized() );
11758+
node.setAttribute( QStringLiteral( "synced" ), w->isViewCenterSynchronized() );
1175711759
node.setAttribute( QStringLiteral( "showCursor" ), w->isCursorMarkerVisible() );
11760+
node.setAttribute( QStringLiteral( "scaleSynced" ), w->isViewScaleSynchronized() );
11761+
node.setAttribute( QStringLiteral( "scaleFactor" ), w->scaleFactor() );
1175811762
mapViewNode.appendChild( node );
1175911763
}
1176011764
qgisNode.appendChild( mapViewNode );
@@ -11791,8 +11795,10 @@ void QgisApp::readProject( const QDomDocument &doc )
1179111795
bool floating = elementNode.attribute( QStringLiteral( "floating" ), QStringLiteral( "0" ) ).toInt();
1179211796
bool synced = elementNode.attribute( QStringLiteral( "synced" ), QStringLiteral( "0" ) ).toInt();
1179311797
bool showCursor = elementNode.attribute( QStringLiteral( "showCursor" ), QStringLiteral( "0" ) ).toInt();
11798+
bool scaleSynced = elementNode.attribute( QStringLiteral( "scaleSynced" ), QStringLiteral( "0" ) ).toInt();
11799+
double scaleFactor = elementNode.attribute( QStringLiteral( "scaleFactor" ), QStringLiteral( "1" ) ).toDouble();
1179411800

11795-
QgsMapCanvas *mapCanvas = createNewMapCanvas( mapName, floating, QRect( x, y, w, h ), synced, showCursor );
11801+
QgsMapCanvas *mapCanvas = createNewMapCanvas( mapName, floating, QRect( x, y, w, h ), synced, showCursor, scaleSynced, scaleFactor );
1179611802
mapCanvas->readProject( doc );
1179711803
}
1179811804
}

src/app/qgisapp.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
242242
* and widget geometry rect for the dock.
243243
*/
244244
QgsMapCanvas *createNewMapCanvas( const QString &name, bool isFloating = false, const QRect &dockGeometry = QRect(),
245-
bool synced = false, bool showCursor = true );
245+
bool synced = false, bool showCursor = true, bool scaleSynced = false,
246+
double scaleFactor = 1.0 );
246247

247248
/**
248249
* Closes any additional map canvases. The main map canvas will not

src/app/qgsmapcanvasdockwidget.cpp

+85-24
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,9 @@ QgsMapCanvasDockWidget::QgsMapCanvasDockWidget( const QString &name, QWidget *pa
5757

5858
mMainWidget->layout()->addWidget( mMapCanvas );
5959

60-
connect( mActionSyncView, &QAction::toggled, this, [ = ]( bool active )
60+
connect( mActionSyncView, &QAction::toggled, this, [ = ]
6161
{
62-
syncViewExtent( mMainCanvas );
63-
syncView( active );
62+
syncViewCenter( mMainCanvas );
6463
} );
6564

6665
mMenu = new QMenu();
@@ -110,6 +109,9 @@ QgsMapCanvasDockWidget::QgsMapCanvasDockWidget( const QString &name, QWidget *pa
110109
mScaleCombo = settingsAction->scaleCombo();
111110
mRotationEdit = settingsAction->rotationSpinBox();
112111
mMagnificationEdit = settingsAction->magnifierSpinBox();
112+
mSyncScaleCheckBox = settingsAction->syncScaleCheckBox();
113+
mScaleFactorWidget = settingsAction->scaleFactorSpinBox();
114+
113115
connect( mScaleCombo, &QgsScaleComboBox::scaleChanged, this, [ = ]( double scale )
114116
{
115117
if ( !mBlockScaleUpdate )
@@ -171,11 +173,19 @@ QgsMapCanvasDockWidget::QgsMapCanvasDockWidget( const QString &name, QWidget *pa
171173
}
172174
} );
173175

176+
connect( mScaleFactorWidget, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsMapCanvasDockWidget::mapScaleChanged );
177+
connect( mSyncScaleCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
178+
{
179+
if ( checked )
180+
mapScaleChanged();
181+
}
182+
);
183+
174184
mResizeTimer.setSingleShot( true );
175185
connect( &mResizeTimer, &QTimer::timeout, this, [ = ]
176186
{
177187
mBlockExtentSync = false;
178-
syncViewExtent( mMainCanvas );
188+
syncViewCenter( mMainCanvas );
179189
} );
180190
}
181191

@@ -184,23 +194,28 @@ void QgsMapCanvasDockWidget::setMainCanvas( QgsMapCanvas *canvas )
184194
if ( mMainCanvas )
185195
{
186196
disconnect( mMainCanvas, &QgsMapCanvas::xyCoordinates, this, &QgsMapCanvasDockWidget::syncMarker );
197+
disconnect( mMainCanvas, &QgsMapCanvas::scaleChanged, this, &QgsMapCanvasDockWidget::mapScaleChanged );
198+
disconnect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged );
187199
}
188200

189201
mMainCanvas = canvas;
190202
connect( mMainCanvas, &QgsMapCanvas::xyCoordinates, this, &QgsMapCanvasDockWidget::syncMarker );
203+
connect( mMainCanvas, &QgsMapCanvas::scaleChanged, this, &QgsMapCanvasDockWidget::mapScaleChanged );
204+
connect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged );
205+
connect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged, Qt::UniqueConnection );
191206
}
192207

193208
QgsMapCanvas *QgsMapCanvasDockWidget::mapCanvas()
194209
{
195210
return mMapCanvas;
196211
}
197212

198-
void QgsMapCanvasDockWidget::setViewExtentSynchronized( bool enabled )
213+
void QgsMapCanvasDockWidget::setViewCenterSynchronized( bool enabled )
199214
{
200215
mActionSyncView->setChecked( enabled );
201216
}
202217

203-
bool QgsMapCanvasDockWidget::isViewExtentSynchronized() const
218+
bool QgsMapCanvasDockWidget::isViewCenterSynchronized() const
204219
{
205220
return mActionSyncView->isChecked();
206221
}
@@ -215,6 +230,26 @@ bool QgsMapCanvasDockWidget::isCursorMarkerVisible() const
215230
return mXyMarker->isVisible();
216231
}
217232

233+
void QgsMapCanvasDockWidget::setScaleFactor( double factor )
234+
{
235+
mScaleFactorWidget->setValue( factor );
236+
}
237+
238+
void QgsMapCanvasDockWidget::setViewScaleSynchronized( bool enabled )
239+
{
240+
mSyncScaleCheckBox->setChecked( enabled );
241+
}
242+
243+
bool QgsMapCanvasDockWidget::isViewScaleSynchronized() const
244+
{
245+
return mSyncScaleCheckBox->isChecked();
246+
}
247+
248+
double QgsMapCanvasDockWidget::scaleFactor() const
249+
{
250+
return mScaleFactorWidget->value();
251+
}
252+
218253
void QgsMapCanvasDockWidget::resizeEvent( QResizeEvent * )
219254
{
220255
mBlockExtentSync = true;
@@ -233,21 +268,7 @@ void QgsMapCanvasDockWidget::setMapCrs()
233268
}
234269
}
235270

236-
void QgsMapCanvasDockWidget::syncView( bool enabled )
237-
{
238-
if ( enabled )
239-
{
240-
connect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged, Qt::UniqueConnection );
241-
connect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged, Qt::UniqueConnection );
242-
}
243-
else
244-
{
245-
disconnect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged );
246-
disconnect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged );
247-
}
248-
}
249-
250-
void QgsMapCanvasDockWidget::syncViewExtent( QgsMapCanvas *sourceCanvas )
271+
void QgsMapCanvasDockWidget::syncViewCenter( QgsMapCanvas *sourceCanvas )
251272
{
252273
// avoid infinite recursion
253274
mBlockExtentSync = true;
@@ -259,11 +280,11 @@ void QgsMapCanvasDockWidget::syncViewExtent( QgsMapCanvas *sourceCanvas )
259280
destCanvas->mapSettings().destinationCrs() );
260281
try
261282
{
262-
destCanvas->setExtent( ct.transformBoundingBox( sourceCanvas->extent() ) );
283+
destCanvas->setCenter( ct.transform( sourceCanvas->center() ) );
263284
}
264285
catch ( QgsCsException & )
265286
{
266-
destCanvas->setExtent( sourceCanvas->extent() );
287+
destCanvas->setCenter( sourceCanvas->center() );
267288
}
268289
destCanvas->refresh();
269290

@@ -279,7 +300,14 @@ void QgsMapCanvasDockWidget::mapExtentChanged()
279300
if ( !sourceCanvas )
280301
return;
281302

282-
syncViewExtent( sourceCanvas );
303+
if ( sourceCanvas == mMapCanvas && mSyncScaleCheckBox->isChecked() )
304+
{
305+
double newScaleFactor = mMainCanvas->scale() / mMapCanvas->scale();
306+
mScaleFactorWidget->setValue( newScaleFactor );
307+
}
308+
309+
if ( mActionSyncView->isChecked() )
310+
syncViewCenter( sourceCanvas );
283311
}
284312

285313
void QgsMapCanvasDockWidget::mapCrsChanged()
@@ -351,6 +379,18 @@ void QgsMapCanvasDockWidget::syncMarker( const QgsPoint &p )
351379
mXyMarker->setCenter( t );
352380
}
353381

382+
void QgsMapCanvasDockWidget::mapScaleChanged()
383+
{
384+
if ( !mSyncScaleCheckBox->isChecked() )
385+
return;
386+
387+
double newScale = mMainCanvas->scale() / mScaleFactorWidget->value();
388+
bool prev = mBlockExtentSync;
389+
mBlockExtentSync = true;
390+
mMapCanvas->zoomScale( newScale );
391+
mBlockExtentSync = prev;
392+
}
393+
354394
QgsMapSettingsAction::QgsMapSettingsAction( QWidget *parent )
355395
: QWidgetAction( parent )
356396
{
@@ -397,6 +437,27 @@ QgsMapSettingsAction::QgsMapSettingsAction( QWidget *parent )
397437
gLayout->addWidget( label, 2, 0 );
398438
gLayout->addWidget( mMagnifierWidget, 2, 1 );
399439

440+
mSyncScaleCheckBox = new QCheckBox( tr( "Synchronize scale" ) );
441+
gLayout->addWidget( mSyncScaleCheckBox, 3, 0, 1, 2 );
442+
443+
mScaleFactorWidget = new QgsDoubleSpinBox();
444+
mScaleFactorWidget->setSuffix( QStringLiteral( "×" ) );
445+
mScaleFactorWidget->setDecimals( 2 );
446+
mScaleFactorWidget->setRange( 0.01, 100000 );
447+
mScaleFactorWidget->setWrapping( false );
448+
mScaleFactorWidget->setSingleStep( 0.1 );
449+
mScaleFactorWidget->setToolTip( tr( "Multiplication factor for main canvas scale to view scale" ) );
450+
mScaleFactorWidget->setClearValueMode( QgsDoubleSpinBox::CustomValue );
451+
mScaleFactorWidget->setClearValue( 1.0 );
452+
mScaleFactorWidget->setValue( 1.0 );
453+
mScaleFactorWidget->setEnabled( false );
454+
455+
connect( mSyncScaleCheckBox, &QCheckBox::toggled, mScaleFactorWidget, &QgsDoubleSpinBox::setEnabled );
456+
457+
label = new QLabel( tr( "Scale factor" ) );
458+
gLayout->addWidget( label, 4, 0 );
459+
gLayout->addWidget( mScaleFactorWidget, 4, 1 );
460+
400461
QWidget *w = new QWidget();
401462
w->setLayout( gLayout );
402463
setDefaultWidget( w );

src/app/qgsmapcanvasdockwidget.h

+42-7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class QgsDoubleSpinBox;
3030
class QgsStatusBarMagnifierWidget;
3131
class QgsMapToolPan;
3232
class QgsVertexMarker;
33+
class QCheckBox;
3334

3435
/**
3536
* \class QgsMapCanvasDockWidget
@@ -53,16 +54,16 @@ class APP_EXPORT QgsMapCanvasDockWidget : public QgsDockWidget, private Ui::QgsM
5354
QgsMapCanvas *mapCanvas();
5455

5556
/**
56-
* Sets whether the view extent should be synchronized with the main canvas extent.
57-
* @see isViewExtentSynchronized()
57+
* Sets whether the view center should be synchronized with the main canvas center.
58+
* @see isViewCenterSynchronized()
5859
*/
59-
void setViewExtentSynchronized( bool enabled );
60+
void setViewCenterSynchronized( bool enabled );
6061

6162
/**
6263
* Returns true if the view extent is synchronized with the main canvas extent.
63-
* @see setViewExtentSynchronized()
64+
* @see setViewCenterSynchronized()
6465
*/
65-
bool isViewExtentSynchronized() const;
66+
bool isViewCenterSynchronized() const;
6667

6768
/**
6869
* Sets whether the cursor position marker is visible.
@@ -76,6 +77,34 @@ class APP_EXPORT QgsMapCanvasDockWidget : public QgsDockWidget, private Ui::QgsM
7677
*/
7778
bool isCursorMarkerVisible() const;
7879

80+
/**
81+
* Returns the scaling factor for main canvas scale to view scale.
82+
* @see setScaleFactor()
83+
* @see isViewScaleSynchronized()
84+
*/
85+
double scaleFactor() const;
86+
87+
/**
88+
* Sets the scaling \a factor for main canvas scale to view scale.
89+
* @see scaleFactor()
90+
* @see setViewScaleSynchronized()
91+
*/
92+
void setScaleFactor( double factor );
93+
94+
/**
95+
* Sets whether the view scale should be synchronized with the main canvas center.
96+
* @see isViewScaleSynchronized()
97+
* @see setScaleFactor()
98+
*/
99+
void setViewScaleSynchronized( bool enabled );
100+
101+
/**
102+
* Returns true if the view scale is synchronized with the main canvas extent.
103+
* @see setViewScaleSynchronized()
104+
* @see scaleFactor()
105+
*/
106+
bool isViewScaleSynchronized() const;
107+
79108
signals:
80109

81110
void renameTriggered();
@@ -87,12 +116,12 @@ class APP_EXPORT QgsMapCanvasDockWidget : public QgsDockWidget, private Ui::QgsM
87116
private slots:
88117

89118
void setMapCrs();
90-
void syncView( bool enabled );
91119
void mapExtentChanged();
92120
void mapCrsChanged();
93121
void menuAboutToShow();
94122
void settingsMenuAboutToShow();
95123
void syncMarker( const QgsPoint &p );
124+
void mapScaleChanged();
96125

97126
private:
98127

@@ -103,14 +132,16 @@ class APP_EXPORT QgsMapCanvasDockWidget : public QgsDockWidget, private Ui::QgsM
103132
QgsScaleComboBox *mScaleCombo = nullptr;
104133
QgsDoubleSpinBox *mRotationEdit = nullptr;
105134
QgsDoubleSpinBox *mMagnificationEdit = nullptr;
135+
QgsDoubleSpinBox *mScaleFactorWidget = nullptr;
136+
QCheckBox *mSyncScaleCheckBox = nullptr;
106137
bool mBlockScaleUpdate = false;
107138
bool mBlockRotationUpdate = false;
108139
bool mBlockMagnificationUpdate = false;
109140
bool mBlockExtentSync = false;
110141
QgsMapToolPan *mPanTool = nullptr;
111142
QTimer mResizeTimer;
112143
QgsVertexMarker *mXyMarker = nullptr;
113-
void syncViewExtent( QgsMapCanvas *sourceCanvas );
144+
void syncViewCenter( QgsMapCanvas *sourceCanvas );
114145
};
115146

116147
/**
@@ -130,11 +161,15 @@ class QgsMapSettingsAction: public QWidgetAction
130161
QgsScaleComboBox *scaleCombo() { return mScaleCombo; }
131162
QgsDoubleSpinBox *rotationSpinBox() { return mRotationWidget; }
132163
QgsDoubleSpinBox *magnifierSpinBox() { return mMagnifierWidget; }
164+
QgsDoubleSpinBox *scaleFactorSpinBox() { return mScaleFactorWidget; }
165+
QCheckBox *syncScaleCheckBox() { return mSyncScaleCheckBox; }
133166

134167
private:
135168
QgsScaleComboBox *mScaleCombo = nullptr;
136169
QgsDoubleSpinBox *mRotationWidget = nullptr;
137170
QgsDoubleSpinBox *mMagnifierWidget = nullptr;
171+
QCheckBox *mSyncScaleCheckBox = nullptr;
172+
QgsDoubleSpinBox *mScaleFactorWidget = nullptr;
138173
};
139174

140175

src/ui/qgsmapcanvasdockwidgetbase.ui

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
<string>Synchronize View</string>
9595
</property>
9696
<property name="toolTip">
97-
<string>Synchronize View with Main Map</string>
97+
<string>Synchronize View Center with Main Map</string>
9898
</property>
9999
</action>
100100
<action name="mActionRename">

0 commit comments

Comments
 (0)