QGIS API Documentation 3.43.0-Master (6c62b930b02)
qgsadvanceddigitizingdockwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsadvanceddigitizingdockwidget.cpp - dock for CAD tools
3 ----------------------
4 begin : October 2014
5 copyright : (C) Denis Rouzaud
6 email : denis.rouzaud@gmail.com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QMenu>
17#include <QEvent>
18#include <QCoreApplication>
19
20#include <cmath>
21
23#include "moc_qgsadvanceddigitizingdockwidget.cpp"
28#include "qgscadutils.h"
29#include "qgsexpression.h"
30#include "qgsgui.h"
31#include "qgsmapcanvas.h"
32#include "qgsmaptooledit.h"
34#include "qgsmessagebaritem.h"
35#include "qgsfocuswatcher.h"
36#include "qgssettings.h"
37#include "qgssnappingutils.h"
38#include "qgsproject.h"
39#include "qgsmapmouseevent.h"
40#include "qgsmeshlayer.h"
41#include "qgsunittypes.h"
43#include "qgssettingstree.h"
44#include "qgsuserinputwidget.h"
45
46#include <QActionGroup>
47
48
49const QgsSettingsEntryBool *QgsAdvancedDigitizingDockWidget::settingsCadSnappingPriorityPrioritizeFeature = new QgsSettingsEntryBool( QStringLiteral( "cad-snapping-prioritize-feature" ), QgsSettingsTree::sTreeDigitizing, false, tr( "Determines if snapping to features has priority over snapping to common angles." ) );
50const QgsSettingsEntryBool *QgsAdvancedDigitizingDockWidget::settingsCadRecordConstructionGuides = new QgsSettingsEntryBool( QStringLiteral( "cad-record-construction-guides" ), QgsSettingsTree::sTreeDigitizing, false, tr( "Determines if construction guides are being recorded." ) );
51const QgsSettingsEntryBool *QgsAdvancedDigitizingDockWidget::settingsCadShowConstructionGuides = new QgsSettingsEntryBool( QStringLiteral( "cad-show-construction-guides" ), QgsSettingsTree::sTreeDigitizing, true, tr( "Determines whether construction guides are shown." ) );
52const QgsSettingsEntryBool *QgsAdvancedDigitizingDockWidget::settingsCadSnapToConstructionGuides = new QgsSettingsEntryBool( QStringLiteral( "cad-snap-to-construction-guides" ), QgsSettingsTree::sTreeDigitizing, false, tr( "Determines if points will snap to construction guides." ) );
53
54
56 : QgsDockWidget( parent )
57 , mMapCanvas( canvas )
58 , mUserInputWidget( userInputWidget )
59 , mSnapIndicator( std::make_unique<QgsSnapIndicator>( canvas ) )
60 , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 0.0 ).toDouble() )
61{
62 setupUi( this );
63
64 mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this );
65
66 mAngleConstraint.reset( new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton, mRepeatingLockAngleButton ) );
67 mAngleConstraint->setCadConstraintType( Qgis::CadConstraintType::Angle );
68 mAngleConstraint->setMapCanvas( mMapCanvas );
69 mDistanceConstraint.reset( new CadConstraint( mDistanceLineEdit, mLockDistanceButton, nullptr, mRepeatingLockDistanceButton ) );
70 mDistanceConstraint->setCadConstraintType( Qgis::CadConstraintType::Distance );
71 mDistanceConstraint->setMapCanvas( mMapCanvas );
72 mXConstraint.reset( new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton, mRepeatingLockXButton ) );
73 mXConstraint->setCadConstraintType( Qgis::CadConstraintType::XCoordinate );
74 mXConstraint->setMapCanvas( mMapCanvas );
75 mYConstraint.reset( new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton, mRepeatingLockYButton ) );
76 mYConstraint->setCadConstraintType( Qgis::CadConstraintType::YCoordinate );
77 mYConstraint->setMapCanvas( mMapCanvas );
78 mZConstraint.reset( new CadConstraint( mZLineEdit, mLockZButton, mRelativeZButton, mRepeatingLockZButton ) );
79 mZConstraint->setCadConstraintType( Qgis::CadConstraintType::ZValue );
80 mZConstraint->setMapCanvas( mMapCanvas );
81 mMConstraint.reset( new CadConstraint( mMLineEdit, mLockMButton, mRelativeMButton, mRepeatingLockMButton ) );
82 mMConstraint->setCadConstraintType( Qgis::CadConstraintType::MValue );
83 mMConstraint->setMapCanvas( mMapCanvas );
84
85 mLineExtensionConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
86 mXyVertexConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
87 mXyVertexConstraint->setMapCanvas( mMapCanvas );
88
89 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
90
91 mMapCanvas->installEventFilter( this );
92 mAngleLineEdit->installEventFilter( this );
93 mDistanceLineEdit->installEventFilter( this );
94 mXLineEdit->installEventFilter( this );
95 mYLineEdit->installEventFilter( this );
96 mZLineEdit->installEventFilter( this );
97 mMLineEdit->installEventFilter( this );
98
99 // Connect the UI to the event filter to update constraints
100 connect( mEnableAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::activateCad );
101 connect( mConstructionModeAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode );
102 connect( mParallelAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
103 connect( mPerpendicularAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
104 connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
105 connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
106 connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
107 connect( mLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
108 connect( mLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
109 connect( mLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
110 connect( mRelativeAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
111 connect( mRelativeXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
112 connect( mRelativeYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
113 connect( mRelativeZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
114 connect( mRelativeMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
115 connect( mRepeatingLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
116 connect( mRepeatingLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
117 connect( mRepeatingLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
118 connect( mRepeatingLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
119 connect( mRepeatingLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
120 connect( mRepeatingLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
121 connect( mAngleLineEdit, &QLineEdit::returnPressed, this, [=]() { lockConstraint(); } );
122 connect( mDistanceLineEdit, &QLineEdit::returnPressed, this, [=]() { lockConstraint(); } );
123 connect( mXLineEdit, &QLineEdit::returnPressed, this, [=]() { lockConstraint(); } );
124 connect( mYLineEdit, &QLineEdit::returnPressed, this, [=]() { lockConstraint(); } );
125 connect( mZLineEdit, &QLineEdit::returnPressed, this, [=]() { lockConstraint(); } );
126 connect( mMLineEdit, &QLineEdit::returnPressed, this, [=]() { lockConstraint(); } );
127 connect( mAngleLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
128 connect( mDistanceLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
129 connect( mXLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
130 connect( mYLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
131 connect( mZLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
132 connect( mMLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
133 //also watch for focus out events on these widgets
134 QgsFocusWatcher *angleWatcher = new QgsFocusWatcher( mAngleLineEdit );
135 connect( angleWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
136 connect( angleWatcher, &QgsFocusWatcher::focusIn, this, [=]() {
137 const QString cleanedInputValue { QgsAdvancedDigitizingDockWidget::CadConstraint::removeSuffix( mAngleLineEdit->text(), Qgis::CadConstraintType::Angle ) };
138 whileBlocking( mAngleLineEdit )->setText( cleanedInputValue );
139 } );
140 QgsFocusWatcher *distanceWatcher = new QgsFocusWatcher( mDistanceLineEdit );
141 connect( distanceWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
142 connect( distanceWatcher, &QgsFocusWatcher::focusIn, this, [=]() {
143 const QString cleanedInputValue { QgsAdvancedDigitizingDockWidget::CadConstraint::removeSuffix( mDistanceLineEdit->text(), Qgis::CadConstraintType::Distance ) };
144 whileBlocking( mDistanceLineEdit )->setText( cleanedInputValue );
145 } );
146 QgsFocusWatcher *xWatcher = new QgsFocusWatcher( mXLineEdit );
147 connect( xWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
148 QgsFocusWatcher *yWatcher = new QgsFocusWatcher( mYLineEdit );
149 connect( yWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
150 QgsFocusWatcher *zWatcher = new QgsFocusWatcher( mZLineEdit );
151 connect( zWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
152 QgsFocusWatcher *mWatcher = new QgsFocusWatcher( mMLineEdit );
153 connect( mWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
154
155 // Common angle snapping menu
156 mCommonAngleActionsMenu = new QMenu( this );
157 // Suppress warning: Potential leak of memory pointed to by 'angleButtonGroup' [clang-analyzer-cplusplus.NewDeleteLeaks]
158#ifndef __clang_analyzer__
159 QActionGroup *angleButtonGroup = new QActionGroup( mCommonAngleActionsMenu ); // actions are exclusive for common angles NOLINT
160#endif
161 QList<QPair<double, QString>> commonAngles;
162 const QList<double> anglesDouble( { 0.0, 0.1, 0.5, 1.0, 5.0, 10.0, 15.0, 18.0, 22.5, 30.0, 45.0, 90.0 } );
163 for ( QList<double>::const_iterator it = anglesDouble.constBegin(); it != anglesDouble.constEnd(); ++it )
164 {
165 commonAngles << QPair<double, QString>( *it, formatCommonAngleSnapping( *it ) );
166 }
167
168 {
169 QMenu *snappingPriorityMenu = new QMenu( tr( "Snapping Priority" ), mCommonAngleActionsMenu );
170 QActionGroup *snappingPriorityActionGroup = new QActionGroup( snappingPriorityMenu );
171 QAction *featuresAction = new QAction( tr( "Prioritize Snapping to Features" ), snappingPriorityActionGroup );
172 featuresAction->setCheckable( true );
173 QAction *anglesAction = new QAction( tr( "Prioritize Snapping to Common Angles" ), snappingPriorityActionGroup );
174 anglesAction->setCheckable( true );
175 snappingPriorityActionGroup->addAction( featuresAction );
176 snappingPriorityActionGroup->addAction( anglesAction );
177 snappingPriorityMenu->addAction( anglesAction );
178 snappingPriorityMenu->addAction( featuresAction );
179 connect( anglesAction, &QAction::changed, this, [=] {
180 mSnappingPrioritizeFeatures = featuresAction->isChecked();
181 settingsCadSnappingPriorityPrioritizeFeature->setValue( featuresAction->isChecked() );
182 } );
183 featuresAction->setChecked( settingsCadSnappingPriorityPrioritizeFeature->value() );
184 anglesAction->setChecked( !featuresAction->isChecked() );
185 mCommonAngleActionsMenu->addMenu( snappingPriorityMenu );
186 }
187
188 for ( QList<QPair<double, QString>>::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it )
189 {
190 QAction *action = new QAction( it->second, mCommonAngleActionsMenu );
191 action->setCheckable( true );
192 action->setChecked( it->first == mCommonAngleConstraint );
193 mCommonAngleActionsMenu->addAction( action );
194 // Suppress warning: Potential leak of memory pointed to by 'angleButtonGroup' [clang-analyzer-cplusplus.NewDeleteLeaks]
195#ifndef __clang_analyzer__
196 angleButtonGroup->addAction( action );
197#endif
198 mCommonAngleActions.insert( it->first, action );
199 }
200
201 // Construction modes
202 QMenu *constructionSettingsMenu = new QMenu( this );
203
204 mRecordConstructionGuides = new QAction( tr( "Record Construction Guides" ), constructionSettingsMenu );
205 mRecordConstructionGuides->setCheckable( true );
206 mRecordConstructionGuides->setChecked( settingsCadRecordConstructionGuides->value() );
207 constructionSettingsMenu->addAction( mRecordConstructionGuides );
208 connect( mRecordConstructionGuides, &QAction::triggered, this, [=]() { settingsCadRecordConstructionGuides->setValue( mRecordConstructionGuides->isChecked() ); } );
209
210 mShowConstructionGuides = new QAction( tr( "Show Construction Guides" ), constructionSettingsMenu );
211 mShowConstructionGuides->setCheckable( true );
212 mShowConstructionGuides->setChecked( settingsCadShowConstructionGuides->value() );
213 constructionSettingsMenu->addAction( mShowConstructionGuides );
214 connect( mShowConstructionGuides, &QAction::triggered, this, [=]() {
215 settingsCadShowConstructionGuides->setValue( mShowConstructionGuides->isChecked() );
217 } );
218
219 mSnapToConstructionGuides = new QAction( tr( "Snap to Visible Construction Guides" ), constructionSettingsMenu );
220 mSnapToConstructionGuides->setCheckable( true );
221 mSnapToConstructionGuides->setChecked( settingsCadSnapToConstructionGuides->value() );
222 constructionSettingsMenu->addAction( mSnapToConstructionGuides );
223 connect( mSnapToConstructionGuides, &QAction::triggered, this, [=]() { settingsCadSnapToConstructionGuides->setValue( mSnapToConstructionGuides->isChecked() ); } );
224
225 constructionSettingsMenu->addSeparator();
226
227 mClearConstructionGuides = new QAction( tr( "Clear Construction Guides" ), constructionSettingsMenu );
228 constructionSettingsMenu->addAction( mClearConstructionGuides );
229 connect( mClearConstructionGuides, &QAction::triggered, this, [=]() {
230 resetConstructionGuides();
232 } );
233
234 QToolButton *constructionModeToolButton = qobject_cast<QToolButton *>( mToolbar->widgetForAction( mConstructionModeAction ) );
235 constructionModeToolButton->setPopupMode( QToolButton::MenuButtonPopup );
236 constructionModeToolButton->setMenu( constructionSettingsMenu );
237 constructionModeToolButton->setObjectName( QStringLiteral( "ConstructionModeButton" ) );
238
239 // Tools
240 QMenu *toolsMenu = new QMenu( this );
241 connect( toolsMenu, &QMenu::aboutToShow, this, [=]() {
242 toolsMenu->clear();
243 const QStringList toolMetadataNames = QgsGui::instance()->advancedDigitizingToolsRegistry()->toolMetadataNames();
244 for ( const QString &name : toolMetadataNames )
245 {
247 QAction *toolAction = new QAction( toolMetadata->icon(), toolMetadata->visibleName(), toolsMenu );
248 connect( toolAction, &QAction::triggered, this, [=]() {
249 setTool( toolMetadata->createTool( mMapCanvas, this ) );
250 } );
251 toolsMenu->addAction( toolAction );
252 }
253 } );
254 qobject_cast<QToolButton *>( mToolbar->widgetForAction( mToolsAction ) )->setPopupMode( QToolButton::InstantPopup );
255 mToolsAction->setMenu( toolsMenu );
256
257 qobject_cast<QToolButton *>( mToolbar->widgetForAction( mSettingsAction ) )->setPopupMode( QToolButton::InstantPopup );
258 mSettingsAction->setMenu( mCommonAngleActionsMenu );
259 mSettingsAction->setCheckable( true );
260 mSettingsAction->setToolTip( "<b>" + tr( "Snap to common angles" ) + "</b><br>(" + tr( "press n to cycle through the options" ) + ")" );
261 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
262 connect( mCommonAngleActionsMenu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
263
264 // Construction modes
265 QMenu *constructionMenu = new QMenu( this );
266
267 mLineExtensionAction = new QAction( tr( "Line Extension" ), constructionMenu );
268 mLineExtensionAction->setCheckable( true );
269 constructionMenu->addAction( mLineExtensionAction );
270 connect( mLineExtensionAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
271
272 mXyVertexAction = new QAction( tr( "X/Y Point" ), constructionMenu );
273 mXyVertexAction->setCheckable( true );
274 constructionMenu->addAction( mXyVertexAction );
275 connect( mXyVertexAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
276
277 auto constructionToolBar = qobject_cast<QToolButton *>( mToolbar->widgetForAction( mConstructionAction ) );
278 constructionToolBar->setPopupMode( QToolButton::InstantPopup );
279 constructionToolBar->setMenu( constructionMenu );
280 constructionToolBar->setObjectName( QStringLiteral( "ConstructionButton" ) );
281
282 mConstructionAction->setMenu( mCommonAngleActionsMenu );
283 mConstructionAction->setCheckable( true );
284 mConstructionAction->setToolTip( tr( "Construction Tools" ) );
285 // connect( constructionMenu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
286
287 // set tooltips
288 mConstructionModeAction->setToolTip( "<b>" + tr( "Construction mode" ) + "</b><br>(" + tr( "press c to toggle on/off" ) + ")" );
289 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
290 mLockDistanceButton->setToolTip( "<b>" + tr( "Lock distance" ) + "</b><br>(" + tr( "press Ctrl + d for quick access" ) + ")" );
291 mRepeatingLockDistanceButton->setToolTip( "<b>" + tr( "Continuously lock distance" ) + "</b>" );
292
293 mRelativeAngleButton->setToolTip( "<b>" + tr( "Toggles relative angle to previous segment" ) + "</b><br>(" + tr( "press Shift + a for quick access" ) + ")" );
294 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
295 mLockAngleButton->setToolTip( "<b>" + tr( "Lock angle" ) + "</b><br>(" + tr( "press Ctrl + a for quick access" ) + ")" );
296 mRepeatingLockAngleButton->setToolTip( "<b>" + tr( "Continuously lock angle" ) + "</b>" );
297
298 mRelativeXButton->setToolTip( "<b>" + tr( "Toggles relative x to previous node" ) + "</b><br>(" + tr( "press Shift + x for quick access" ) + ")" );
299 mXLineEdit->setToolTip( "<b>" + tr( "X coordinate" ) + "</b><br>(" + tr( "press x for quick access" ) + ")" );
300 mLockXButton->setToolTip( "<b>" + tr( "Lock x coordinate" ) + "</b><br>(" + tr( "press Ctrl + x for quick access" ) + ")" );
301 mRepeatingLockXButton->setToolTip( "<b>" + tr( "Continuously lock x coordinate" ) + "</b>" );
302
303 mRelativeYButton->setToolTip( "<b>" + tr( "Toggles relative y to previous node" ) + "</b><br>(" + tr( "press Shift + y for quick access" ) + ")" );
304 mYLineEdit->setToolTip( "<b>" + tr( "Y coordinate" ) + "</b><br>(" + tr( "press y for quick access" ) + ")" );
305 mLockYButton->setToolTip( "<b>" + tr( "Lock y coordinate" ) + "</b><br>(" + tr( "press Ctrl + y for quick access" ) + ")" );
306 mRepeatingLockYButton->setToolTip( "<b>" + tr( "Continuously lock y coordinate" ) + "</b>" );
307
308 mRelativeZButton->setToolTip( "<b>" + tr( "Toggles relative z to previous node" ) + "</b><br>(" + tr( "press Shift + z for quick access" ) + ")" );
309 mZLineEdit->setToolTip( "<b>" + tr( "Z coordinate" ) + "</b><br>(" + tr( "press z for quick access" ) + ")" );
310 mLockZButton->setToolTip( "<b>" + tr( "Lock z coordinate" ) + "</b><br>(" + tr( "press Ctrl + z for quick access" ) + ")" );
311 mRepeatingLockZButton->setToolTip( "<b>" + tr( "Continuously lock z coordinate" ) + "</b>" );
312
313 mRelativeMButton->setToolTip( "<b>" + tr( "Toggles relative m to previous node" ) + "</b><br>(" + tr( "press Shift + m for quick access" ) + ")" );
314 mMLineEdit->setToolTip( "<b>" + tr( "M coordinate" ) + "</b><br>(" + tr( "press m for quick access" ) + ")" );
315 mLockMButton->setToolTip( "<b>" + tr( "Lock m coordinate" ) + "</b><br>(" + tr( "press Ctrl + m for quick access" ) + ")" );
316 mRepeatingLockMButton->setToolTip( "<b>" + tr( "Continuously lock m coordinate" ) + "</b>" );
317
318 // Create the slots/signals
319 connect( mXLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueXChanged );
320 connect( mYLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueYChanged );
321 connect( mZLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueZChanged );
322 connect( mMLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueMChanged );
323 connect( mDistanceLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueDistanceChanged );
324 connect( mAngleLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueAngleChanged );
325
326 // Create the floater
327 mFloaterActionsMenu = new QMenu( this );
328 qobject_cast<QToolButton *>( mToolbar->widgetForAction( mFloaterAction ) )->setPopupMode( QToolButton::InstantPopup );
329 mFloaterAction->setMenu( mFloaterActionsMenu );
330 mFloaterAction->setCheckable( true );
331 mFloater = new QgsAdvancedDigitizingFloater( canvas, this );
332 mFloaterAction->setChecked( mFloater->active() );
333
334 // Add floater config actions
335 {
336 QAction *action = new QAction( tr( "Show floater" ), mFloaterActionsMenu );
337 action->setCheckable( true );
338 action->setChecked( mFloater->active() );
339 mFloaterActionsMenu->addAction( action );
340 connect( action, &QAction::toggled, this, [=]( bool checked ) {
341 mFloater->setActive( checked );
342 mFloaterAction->setChecked( checked );
343 } );
344 }
345
346 mFloaterActionsMenu->addSeparator();
347
348 {
349 QAction *action = new QAction( tr( "Show distance" ), mFloaterActionsMenu );
350 action->setCheckable( true );
351 mFloaterActionsMenu->addAction( action );
352 connect( action, &QAction::toggled, this, [=]( bool checked ) {
354 } );
355 const bool isDistanceChecked = QgsSettings().value( QStringLiteral( "/Cad/DistanceShowInFloater" ), true ).toBool();
356 action->setChecked( isDistanceChecked );
357 // Call this explicitly because toggled won't be called if there is no change
359 }
360
361 {
362 QAction *action = new QAction( tr( "Show angle" ), mFloaterActionsMenu );
363 action->setCheckable( true );
364 mFloaterActionsMenu->addAction( action );
365 connect( action, &QAction::toggled, this, [=]( bool checked ) {
367 } );
368 const bool isAngleChecked = QgsSettings().value( QStringLiteral( "/Cad/AngleShowInFloater" ), true ).toBool();
369 action->setChecked( isAngleChecked );
370 // Call this explicitly because toggled won't be called if there is no change
372 }
373
374 {
375 QAction *action = new QAction( tr( "Show XY coordinates" ), mFloaterActionsMenu );
376 action->setCheckable( true );
377 mFloaterActionsMenu->addAction( action );
378 connect( action, &QAction::toggled, this, [=]( bool checked ) {
381 } );
382 // There is no separate menu option for X and Y so let's check for X only.
383 const bool isXCoordinateChecked = QgsSettings().value( QStringLiteral( "/Cad/XCoordinateShowInFloater" ), true ).toBool();
384 action->setChecked( isXCoordinateChecked );
385 // Call this explicitly because toggled won't be called if there is no change
386 // See issue #61587
389 }
390
391 {
392 QAction *action = new QAction( tr( "Show Z value" ), mFloaterActionsMenu );
393 action->setCheckable( true );
394 mFloaterActionsMenu->addAction( action );
395 connect( action, &QAction::toggled, this, [=]( bool checked ) {
396 mFloater->setItemVisibility( QgsAdvancedDigitizingFloater::FloaterItem::ZCoordinate, checked && mTargetLayerSupportsZ );
397 } );
398 const bool isZCoordinateChecked = QgsSettings().value( QStringLiteral( "/Cad/ZCoordinateShowInFloater" ), true ).toBool();
399 action->setChecked( isZCoordinateChecked );
400 // Call this explicitly because toggled won't be called if there is no change
401 // See issue #61587
403 }
404
405 {
406 QAction *action = new QAction( tr( "Show M value" ), mFloaterActionsMenu );
407 action->setCheckable( true );
408 mFloaterActionsMenu->addAction( action );
409 connect( action, &QAction::toggled, this, [=]( bool checked ) {
410 mFloater->setItemVisibility( QgsAdvancedDigitizingFloater::FloaterItem::MCoordinate, checked && mTargetLayerSupportsM );
411 } );
412 const bool isMCoordinateChecked = QgsSettings().value( QStringLiteral( "/Cad/MCoordinateShowInFloater" ), true ).toBool();
413 action->setChecked( isMCoordinateChecked );
414 // Call this explicitly because toggled won't be called if there is no change
415 // See issue #61587
417 }
418
419 {
420 QAction *action = new QAction( tr( "Show bearing/azimuth" ), mFloaterActionsMenu );
421 action->setCheckable( true );
422 mFloaterActionsMenu->addAction( action );
423 connect( action, &QAction::toggled, this, [=]( bool checked ) {
425 } );
426 const bool isBearingChecked = QgsSettings().value( QStringLiteral( "/Cad/BearingShowInFloater" ), false ).toBool();
427 action->setChecked( isBearingChecked );
428 // Call this explicitly because toggled won't be called if there is no change
429 // See issue #61587
431 }
432
433 {
434 QAction *action = new QAction( tr( "Show common snapping angle" ), mFloaterActionsMenu );
435 action->setCheckable( true );
436 mFloaterActionsMenu->addAction( action );
437 connect( action, &QAction::toggled, this, [=]( bool checked ) {
439 } );
440 const bool isCommonAngleSnappingChecked = QgsSettings().value( QStringLiteral( "/Cad/CommonAngleSnappingShowInFloater" ), false ).toBool();
441 action->setChecked( isCommonAngleSnappingChecked );
442 // Call this explicitly because toggled won't be called if there is no change
443 // See issue #61587
445 }
446
447 updateCapacity( true );
448 connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, [=] { updateCapacity( true ); } );
449
450 connect( QgsProject::instance(), &QgsProject::cleared, this, [=]() {
451 mConstructionGuidesLayer.reset();
452 } );
453 connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, [=] { updateConstructionGuidesCrs(); } );
454
455 disable();
456}
457
459{
460 if ( mCurrentTool )
461 {
462 mCurrentTool->deleteLater();
463 }
464}
465
467{
468 if ( angle == 0 )
469 return tr( "Do Not Snap to Common Angles" );
470 else
471 return QString( tr( "%1, %2, %3, %4°…" ) ).arg( angle, 0, 'f', 1 ).arg( angle * 2, 0, 'f', 1 ).arg( angle * 3, 0, 'f', 1 ).arg( angle * 4, 0, 'f', 1 );
472}
473
474void QgsAdvancedDigitizingDockWidget::setX( const QString &value, WidgetSetMode mode )
475{
476 mXLineEdit->setText( value );
477 if ( mode == WidgetSetMode::ReturnPressed )
478 {
479 emit mXLineEdit->returnPressed();
480 }
481 else if ( mode == WidgetSetMode::FocusOut )
482 {
483 QEvent *e = new QEvent( QEvent::FocusOut );
484 QCoreApplication::postEvent( mXLineEdit, e );
485 }
486 else if ( mode == WidgetSetMode::TextEdited )
487 {
488 emit mXLineEdit->textEdited( value );
489 }
490}
491void QgsAdvancedDigitizingDockWidget::setY( const QString &value, WidgetSetMode mode )
492{
493 mYLineEdit->setText( value );
494 if ( mode == WidgetSetMode::ReturnPressed )
495 {
496 emit mYLineEdit->returnPressed();
497 }
498 else if ( mode == WidgetSetMode::FocusOut )
499 {
500 QEvent *e = new QEvent( QEvent::FocusOut );
501 QCoreApplication::postEvent( mYLineEdit, e );
502 }
503 else if ( mode == WidgetSetMode::TextEdited )
504 {
505 emit mYLineEdit->textEdited( value );
506 }
507}
508void QgsAdvancedDigitizingDockWidget::setZ( const QString &value, WidgetSetMode mode )
509{
510 mZLineEdit->setText( value );
511 if ( mode == WidgetSetMode::ReturnPressed )
512 {
513 emit mZLineEdit->returnPressed();
514 }
515 else if ( mode == WidgetSetMode::FocusOut )
516 {
517 QEvent *e = new QEvent( QEvent::FocusOut );
518 QCoreApplication::postEvent( mZLineEdit, e );
519 }
520 else if ( mode == WidgetSetMode::TextEdited )
521 {
522 emit mZLineEdit->textEdited( value );
523 }
524}
525void QgsAdvancedDigitizingDockWidget::setM( const QString &value, WidgetSetMode mode )
526{
527 mMLineEdit->setText( value );
528 if ( mode == WidgetSetMode::ReturnPressed )
529 {
530 emit mMLineEdit->returnPressed();
531 }
532 else if ( mode == WidgetSetMode::FocusOut )
533 {
534 QEvent *e = new QEvent( QEvent::FocusOut );
535 QCoreApplication::postEvent( mMLineEdit, e );
536 }
537 else if ( mode == WidgetSetMode::TextEdited )
538 {
539 emit mMLineEdit->textEdited( value );
540 }
541}
543{
544 mAngleLineEdit->setText( value );
545 if ( mode == WidgetSetMode::ReturnPressed )
546 {
547 emit mAngleLineEdit->returnPressed();
548 }
549 else if ( mode == WidgetSetMode::FocusOut )
550 {
551 emit mAngleLineEdit->textEdited( value );
552 }
553}
555{
556 mDistanceLineEdit->setText( value );
557 if ( mode == WidgetSetMode::ReturnPressed )
558 {
559 emit mDistanceLineEdit->returnPressed();
560 }
561 else if ( mode == WidgetSetMode::FocusOut )
562 {
563 QEvent *e = new QEvent( QEvent::FocusOut );
564 QCoreApplication::postEvent( mDistanceLineEdit, e );
565 }
566 else if ( mode == WidgetSetMode::TextEdited )
567 {
568 emit mDistanceLineEdit->textEdited( value );
569 }
570}
571
572
573void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
574{
575 mCadEnabled = enabled;
576 mEnableAction->setChecked( enabled );
577 mConstructionModeAction->setEnabled( enabled );
578 mSettingsAction->setEnabled( enabled );
579 mInputWidgets->setEnabled( enabled );
580 mFloaterAction->setEnabled( enabled );
581 mConstructionAction->setEnabled( enabled );
582 mToolsAction->setEnabled( enabled );
583
584 if ( !enabled )
585 {
586 // uncheck at deactivation
587 mLineExtensionAction->setChecked( false );
588 mXyVertexAction->setChecked( false );
589 // will be reactivated in updateCapacities
590 mParallelAction->setEnabled( false );
591 mPerpendicularAction->setEnabled( false );
592 if ( mCurrentTool )
593 {
594 mCurrentTool->deleteLater();
595 }
596 }
597
598
599 clear();
601 setConstructionMode( false );
602
603 switchZM();
604 emit cadEnabledChanged( enabled );
605
606 if ( enabled )
607 {
608 emit valueCommonAngleSnappingChanged( mCommonAngleConstraint );
609 }
610
611 mLastSnapMatch = QgsPointLocator::Match();
612}
613
614
616{
617 bool enableZ = false;
618 bool enableM = false;
619
620 if ( QgsMapLayer *layer = targetLayer() )
621 {
622 switch ( layer->type() )
623 {
625 {
626 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
627 const Qgis::WkbType type = vlayer->wkbType();
628 enableZ = QgsWkbTypes::hasZ( type );
629 enableM = QgsWkbTypes::hasM( type );
630 break;
631 }
632
634 {
635 QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( layer );
636 enableZ = mlayer->isEditable();
637 break;
638 }
639
647 break;
648 }
649 }
650
651 mTargetLayerSupportsM = enableM;
652 mTargetLayerSupportsZ = enableZ;
653
654 setEnabledZ( enableZ );
655 setEnabledM( enableM );
656}
657
659{
660 if ( enable && !mTargetLayerSupportsZ )
661 {
662 enable = false;
663 }
664 mRelativeZButton->setEnabled( enable );
665 mZLabel->setEnabled( enable );
666 mZLineEdit->setEnabled( enable );
667 if ( mZLineEdit->isEnabled() )
668 mZLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultZValue(), 'f', 6 ) );
669 else
670 mZLineEdit->clear();
671 mLockZButton->setEnabled( enable );
672 emit enabledChangedZ( enable );
673}
674
676{
677 if ( enable && !mTargetLayerSupportsM )
678 {
679 enable = false;
680 }
681 mRelativeMButton->setEnabled( enable );
682 mMLabel->setEnabled( enable );
683 mMLineEdit->setEnabled( enable );
684 if ( mMLineEdit->isEnabled() )
685 mMLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultMValue(), 'f', 6 ) );
686 else
687 mMLineEdit->clear();
688 mLockMButton->setEnabled( enable );
689 emit enabledChangedM( enable );
690}
691
692void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
693{
694 enabled &= mCurrentMapToolSupportsCad;
695
696 mSessionActive = enabled;
697
698 if ( enabled && !isVisible() )
699 {
700 show();
701 }
702
703 setCadEnabled( enabled );
704}
705
707{
708 if ( mCurrentTool )
709 {
710 mCurrentTool->deleteLater();
711 mCurrentTool = nullptr;
712 }
713
714 mCurrentTool = tool;
715
716 if ( mCurrentTool )
717 {
718 if ( QWidget *toolWidget = mCurrentTool->createWidget() )
719 {
720 toolWidget->setParent( mUserInputWidget );
721 mUserInputWidget->addUserInputWidget( toolWidget );
722 }
724 }
725}
726
728{
729 return mCurrentTool.data();
730}
731
732void QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked( bool activated )
733{
734 if ( !activated )
735 {
736 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
737 }
738 else if ( sender() == mParallelAction )
739 {
740 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
741 }
742 else if ( sender() == mPerpendicularAction )
743 {
744 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
745 }
746}
747
748void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
749{
750 if ( sender() == mRelativeAngleButton )
751 {
752 mAngleConstraint->setRelative( activate );
753 emit relativeAngleChanged( activate );
754 }
755 else if ( sender() == mRelativeXButton )
756 {
757 mXConstraint->setRelative( activate );
758 emit relativeXChanged( activate );
759 }
760 else if ( sender() == mRelativeYButton )
761 {
762 mYConstraint->setRelative( activate );
763 emit relativeYChanged( activate );
764 }
765 else if ( sender() == mRelativeZButton )
766 {
767 mZConstraint->setRelative( activate );
768 emit relativeZChanged( activate );
769 }
770 else if ( sender() == mRelativeMButton )
771 {
772 mMConstraint->setRelative( activate );
773 emit relativeMChanged( activate );
774 }
775}
776
777void QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock( bool activate )
778{
779 if ( sender() == mRepeatingLockDistanceButton )
780 {
781 mDistanceConstraint->setRepeatingLock( activate );
782 }
783 else if ( sender() == mRepeatingLockAngleButton )
784 {
785 mAngleConstraint->setRepeatingLock( activate );
786 }
787 else if ( sender() == mRepeatingLockXButton )
788 {
789 mXConstraint->setRepeatingLock( activate );
790 }
791 else if ( sender() == mRepeatingLockYButton )
792 {
793 mYConstraint->setRepeatingLock( activate );
794 }
795 else if ( sender() == mRepeatingLockZButton )
796 {
797 mZConstraint->setRepeatingLock( activate );
798 }
799 else if ( sender() == mRepeatingLockMButton )
800 {
801 mMConstraint->setRepeatingLock( activate );
802 }
803}
804
805void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
806{
807 mConstructionMode = enabled;
808 mConstructionModeAction->setChecked( enabled );
809
811 {
812 if ( enabled && mCadPointList.size() > 1 )
813 {
814 mConstructionGuideLine.addVertex( mCadPointList.at( 1 ) );
815 }
816 }
817}
818
819void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction *action )
820{
821 // common angles
822 for ( auto it = mCommonAngleActions.cbegin(); it != mCommonAngleActions.cend(); ++it )
823 {
824 if ( it.value() == action )
825 {
826 it.value()->setChecked( true );
827 mCommonAngleConstraint = it.key();
828 QgsSettings().setValue( QStringLiteral( "/Cad/CommonAngle" ), it.key() );
829 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
830 emit valueCommonAngleSnappingChanged( mCommonAngleConstraint );
831 return;
832 }
833 }
834}
835
836QgsMapLayer *QgsAdvancedDigitizingDockWidget::targetLayer() const
837{
838 if ( QgsMapToolAdvancedDigitizing *advancedTool = qobject_cast<QgsMapToolAdvancedDigitizing *>( mMapCanvas->mapTool() ) )
839 {
840 return advancedTool->layer();
841 }
842 else
843 {
844 return mMapCanvas->currentLayer();
845 }
846}
847
848void QgsAdvancedDigitizingDockWidget::releaseLocks( bool releaseRepeatingLocks )
849{
850 // release all locks except construction mode
851
852 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
853
854 if ( releaseRepeatingLocks )
855 {
856 mXyVertexAction->setChecked( false );
857 mXyVertexConstraint->setLockMode( CadConstraint::NoLock );
858 emit softLockXyChanged( false );
859
860 mLineExtensionAction->setChecked( false );
861 mLineExtensionConstraint->setLockMode( CadConstraint::NoLock );
862 emit softLockLineExtensionChanged( false );
863
865 }
866
867 if ( releaseRepeatingLocks || !mAngleConstraint->isRepeatingLock() )
868 {
869 mAngleConstraint->setLockMode( CadConstraint::NoLock );
870 emit lockAngleChanged( false );
871 }
872 if ( releaseRepeatingLocks || !mDistanceConstraint->isRepeatingLock() )
873 {
874 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
875 emit lockDistanceChanged( false );
876 }
877 if ( releaseRepeatingLocks || !mXConstraint->isRepeatingLock() )
878 {
879 mXConstraint->setLockMode( CadConstraint::NoLock );
880 emit lockXChanged( false );
881 }
882 if ( releaseRepeatingLocks || !mYConstraint->isRepeatingLock() )
883 {
884 mYConstraint->setLockMode( CadConstraint::NoLock );
885 emit lockYChanged( false );
886 }
887 if ( releaseRepeatingLocks || !mZConstraint->isRepeatingLock() )
888 {
889 mZConstraint->setLockMode( CadConstraint::NoLock );
890 emit lockZChanged( false );
891 }
892 if ( releaseRepeatingLocks || !mMConstraint->isRepeatingLock() )
893 {
894 mMConstraint->setLockMode( CadConstraint::NoLock );
895 emit lockMChanged( false );
896 }
897
898 if ( !mCadPointList.empty() )
899 {
900 if ( !mXConstraint->isLocked() && !mXConstraint->relative() )
901 {
902 mXConstraint->setValue( mCadPointList.constLast().x(), true );
903 }
904 if ( !mYConstraint->isLocked() && !mYConstraint->relative() )
905 {
906 mYConstraint->setValue( mCadPointList.constLast().y(), true );
907 }
908 if ( !mZConstraint->isLocked() && !mZConstraint->relative() )
909 {
910 mZConstraint->setValue( mCadPointList.constLast().z(), true );
911 }
912 if ( !mMConstraint->isLocked() && !mMConstraint->relative() )
913 {
914 mMConstraint->setValue( mCadPointList.constLast().m(), true );
915 }
916 }
917}
918
919#if 0
920void QgsAdvancedDigitizingDockWidget::emit pointChanged()
921{
922 // run a fake map mouse event to update the paint item
923 QPoint globalPos = mMapCanvas->cursor().pos();
924 QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
925 QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
926 mCurrentMapTool->canvasMoveEvent( e );
927}
928#endif
929
930QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
931{
932 CadConstraint *constraint = nullptr;
933 if ( obj == mAngleLineEdit || obj == mLockAngleButton )
934 {
935 constraint = mAngleConstraint.get();
936 }
937 else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
938 {
939 constraint = mDistanceConstraint.get();
940 }
941 else if ( obj == mXLineEdit || obj == mLockXButton )
942 {
943 constraint = mXConstraint.get();
944 }
945 else if ( obj == mYLineEdit || obj == mLockYButton )
946 {
947 constraint = mYConstraint.get();
948 }
949 else if ( obj == mZLineEdit || obj == mLockZButton )
950 {
951 constraint = mZConstraint.get();
952 }
953 else if ( obj == mMLineEdit || obj == mLockMButton )
954 {
955 constraint = mMConstraint.get();
956 }
957 else if ( obj == mLineExtensionAction )
958 {
959 constraint = mLineExtensionConstraint.get();
960 }
961 else if ( obj == mXyVertexAction )
962 {
963 constraint = mXyVertexConstraint.get();
964 }
965 return constraint;
966}
967
968double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, const Qgis::CadConstraintType type, bool &ok ) const
969{
970 ok = false;
971
972 const QString cleanedInputValue { CadConstraint::removeSuffix( inputValue, type ) };
973 double value = qgsPermissiveToDouble( cleanedInputValue, ok );
974
975 if ( !ok )
976 {
977 // try to evaluate expression
978 QgsExpression expr( inputValue );
979 const QVariant result = expr.evaluate();
980 if ( expr.hasEvalError() )
981 {
982 ok = false;
983 QString inputValueC { inputValue };
984
985 // First: try removing group separator
986 if ( inputValue.contains( QLocale().groupSeparator() ) )
987 {
988 inputValueC.remove( QLocale().groupSeparator() );
989 QgsExpression exprC( inputValueC );
990 const QVariant resultC = exprC.evaluate();
991 if ( !exprC.hasEvalError() )
992 {
993 value = resultC.toDouble( &ok );
994 }
995 }
996
997 // Second: be nice with non-dot locales
998 if ( !ok && QLocale().decimalPoint() != QChar( '.' ) && inputValueC.contains( QLocale().decimalPoint() ) )
999 {
1000 QgsExpression exprC( inputValueC.replace( QLocale().decimalPoint(), QChar( '.' ) ) );
1001 const QVariant resultC = exprC.evaluate();
1002 if ( !exprC.hasEvalError() )
1003 {
1004 value = resultC.toDouble( &ok );
1005 }
1006 }
1007 }
1008 else
1009 {
1010 value = result.toDouble( &ok );
1011 }
1012 }
1013
1014 if ( ok && type == Qgis::CadConstraintType::Distance )
1015 {
1016 const Qgis::DistanceUnit displayUnits { QgsProject::instance()->distanceUnits() };
1017 // Convert to canvas units
1018 const Qgis::DistanceUnit canvasUnits { mMapCanvas->mapSettings().mapUnits() };
1019 value *= QgsUnitTypes::fromUnitToUnitFactor( displayUnits, canvasUnits );
1020 }
1021
1022 return value;
1023}
1024
1025void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
1026{
1027 if ( !constraint || textValue.isEmpty() )
1028 {
1029 return;
1030 }
1031
1032 if ( constraint->lockMode() == CadConstraint::NoLock )
1033 return;
1034
1035 bool ok;
1036 const double value = parseUserInput( textValue, constraint->cadConstraintType(), ok );
1037 if ( !ok )
1038 return;
1039
1040 constraint->setValue( value, convertExpression );
1041 // run a fake map mouse event to update the paint item
1042 emit pointChangedV2( mCadPointList.value( 0 ) );
1043}
1044
1045void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
1046{
1047 CadConstraint *constraint = objectToConstraint( sender() );
1048 if ( !constraint )
1049 {
1050 return;
1051 }
1052
1053 if ( activate )
1054 {
1055 const QString textValue = constraint->lineEdit()->text();
1056 if ( !textValue.isEmpty() )
1057 {
1058 bool ok;
1059 const double value = parseUserInput( textValue, constraint->cadConstraintType(), ok );
1060 if ( ok )
1061 {
1062 constraint->setValue( value );
1063 }
1064 else
1065 {
1066 activate = false;
1067 }
1068 }
1069 else
1070 {
1071 activate = false;
1072 }
1073 }
1074 constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
1075
1076 if ( constraint == mXConstraint.get() )
1077 {
1078 emit lockXChanged( activate );
1079 }
1080 else if ( constraint == mYConstraint.get() )
1081 {
1082 emit lockYChanged( activate );
1083 }
1084 else if ( constraint == mZConstraint.get() )
1085 {
1086 emit lockZChanged( activate );
1087 }
1088 else if ( constraint == mMConstraint.get() )
1089 {
1090 emit lockMChanged( activate );
1091 }
1092 else if ( constraint == mDistanceConstraint.get() )
1093 {
1094 emit lockDistanceChanged( activate );
1095 }
1096 else if ( constraint == mAngleConstraint.get() )
1097 {
1098 emit lockAngleChanged( activate );
1099 }
1100
1101 if ( activate )
1102 {
1103 // deactivate perpendicular/parallel if angle has been activated
1104 if ( constraint == mAngleConstraint.get() )
1105 {
1106 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1107 }
1108
1109 // run a fake map mouse event to update the paint item
1110 emit pointChangedV2( mCadPointList.value( 0 ) );
1111 }
1112}
1113
1114void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
1115{
1116 CadConstraint *constraint = objectToConstraint( sender() );
1117 if ( !constraint )
1118 {
1119 return;
1120 }
1121
1122 updateConstraintValue( constraint, textValue, false );
1123}
1124
1125void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
1126{
1127 QLineEdit *lineEdit = qobject_cast<QLineEdit *>( sender()->parent() );
1128 if ( !lineEdit )
1129 return;
1130
1131 CadConstraint *constraint = objectToConstraint( lineEdit );
1132 if ( !constraint )
1133 {
1134 return;
1135 }
1136
1137 updateConstraintValue( constraint, lineEdit->text(), true );
1138}
1139
1140void QgsAdvancedDigitizingDockWidget::lockBetweenLineConstraint( Qgis::BetweenLineConstraint constraint )
1141{
1142 mBetweenLineConstraint = constraint;
1143 mPerpendicularAction->setChecked( constraint == Qgis::BetweenLineConstraint::Perpendicular );
1144 mParallelAction->setChecked( constraint == Qgis::BetweenLineConstraint::Parallel );
1145}
1146
1147void QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint( bool activate /* default true */ )
1148{
1149 CadConstraint *constraint = objectToConstraint( sender() );
1150 if ( !constraint )
1151 {
1152 return;
1153 }
1154
1155 constraint->setLockMode( activate ? CadConstraint::SoftLock : CadConstraint::NoLock );
1156
1157 if ( constraint == mXyVertexConstraint.get() )
1158 {
1159 emit softLockXyChanged( activate );
1160 }
1161 else if ( constraint == mLineExtensionConstraint.get() )
1162 {
1163 emit softLockLineExtensionChanged( activate );
1164 }
1165
1166 if ( activate )
1167 {
1168 // run a fake map mouse event to update the paint item
1169 emit pointChangedV2( mCadPointList.value( 0 ) );
1170 }
1171
1172 clearLockedSnapVertices( false );
1173}
1174
1175void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
1176{
1177 CadCapacities newCapacities = CadCapacities();
1178 const bool isGeographic = mMapCanvas->mapSettings().destinationCrs().isGeographic();
1179
1180 // first point is the mouse point (it doesn't count)
1181 if ( mCadPointList.count() > 1 )
1182 {
1183 newCapacities |= RelativeCoordinates;
1184 if ( !isGeographic )
1185 {
1186 newCapacities |= AbsoluteAngle;
1187 newCapacities |= Distance;
1188 }
1189 }
1190 if ( mCadPointList.count() > 2 )
1191 {
1192 if ( !isGeographic )
1193 newCapacities |= RelativeAngle;
1194 }
1195 if ( !updateUIwithoutChange && newCapacities == mCapacities )
1196 {
1197 return;
1198 }
1199
1200 const bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
1201
1202 // update the UI according to new capacities
1203 // still keep the old to compare
1204
1205 const bool distance = mCadEnabled && newCapacities.testFlag( Distance );
1206 const bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
1207 const bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
1208 const bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
1209
1210 mPerpendicularAction->setEnabled( distance && snappingEnabled );
1211 mParallelAction->setEnabled( distance && snappingEnabled );
1212
1213 mLineExtensionAction->setEnabled( snappingEnabled );
1214 mXyVertexAction->setEnabled( snappingEnabled );
1215 clearLockedSnapVertices( false );
1216
1217 //update tooltips on buttons
1218 if ( !snappingEnabled )
1219 {
1220 mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode." ) );
1221 mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode." ) );
1222 mLineExtensionAction->setToolTip( tr( "Snapping must be enabled to utilize line extension mode." ) );
1223 mXyVertexAction->setToolTip( tr( "Snapping must be enabled to utilize xy point mode." ) );
1224 }
1225 else if ( mCadPointList.count() <= 1 )
1226 {
1227 mPerpendicularAction->setToolTip( tr( "A first vertex should be drawn to utilize perpendicular mode." ) );
1228 mParallelAction->setToolTip( tr( "A first vertex should be drawn to utilize parallel mode." ) );
1229 }
1230 else if ( isGeographic )
1231 {
1232 mPerpendicularAction->setToolTip( tr( "Perpendicular mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1233 mParallelAction->setToolTip( tr( "Parallel mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1234 }
1235 else
1236 {
1237 mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
1238 mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
1239 }
1240
1241
1242 if ( !absoluteAngle )
1243 {
1244 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1245 }
1246
1247 // absolute angle = azimuth, relative = from previous line
1248 mLockAngleButton->setEnabled( absoluteAngle );
1249 mRelativeAngleButton->setEnabled( relativeAngle );
1250 mAngleLineEdit->setEnabled( absoluteAngle );
1251 emit enabledChangedAngle( absoluteAngle );
1252 if ( !absoluteAngle )
1253 {
1254 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1255 }
1256 if ( !relativeAngle )
1257 {
1258 mAngleConstraint->setRelative( false );
1259 emit relativeAngleChanged( false );
1260 }
1261 else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
1262 {
1263 // set angle mode to relative if can do and wasn't available before
1264 mAngleConstraint->setRelative( true );
1265 emit relativeAngleChanged( true );
1266 }
1267
1268 // distance is always relative
1269 mLockDistanceButton->setEnabled( distance && relativeCoordinates );
1270 mDistanceLineEdit->setEnabled( distance && relativeCoordinates );
1271 emit enabledChangedDistance( distance && relativeCoordinates );
1272 if ( !( distance && relativeCoordinates ) )
1273 {
1274 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
1275 }
1276
1277 mRelativeXButton->setEnabled( relativeCoordinates );
1278 mRelativeYButton->setEnabled( relativeCoordinates );
1279 mRelativeZButton->setEnabled( relativeCoordinates );
1280 mRelativeMButton->setEnabled( relativeCoordinates );
1281
1282 // update capacities
1283 mCapacities = newCapacities;
1284 mCadPaintItem->updatePosition();
1285}
1286
1287
1289{
1291 constr.locked = c->isLocked();
1292 constr.relative = c->relative();
1293 constr.value = c->value();
1294 return constr;
1295}
1296
1297void QgsAdvancedDigitizingDockWidget::toggleLockedSnapVertex( const QgsPointLocator::Match &snapMatch, const QgsPointLocator::Match &previouslySnap )
1298{
1299 // do nothing if not activated
1300 if ( !mLineExtensionConstraint->isLocked() && !mXyVertexConstraint->isLocked() )
1301 {
1302 return;
1303 }
1304
1305 // if the first is same actual, not toggle if previously snapped
1306 const int lastIndex = mLockedSnapVertices.length() - 1;
1307 for ( int i = lastIndex; i >= 0; --i )
1308 {
1309 if ( mLockedSnapVertices[i].point() == snapMatch.point() )
1310 {
1311 if ( snapMatch.point() != previouslySnap.point() )
1312 {
1313 mLockedSnapVertices.removeAt( i );
1314 }
1315 return;
1316 }
1317 }
1318
1319 if ( snapMatch.point() != previouslySnap.point() )
1320 {
1321 mLockedSnapVertices.enqueue( snapMatch );
1322 }
1323
1324 if ( mLockedSnapVertices.count() > 3 )
1325 {
1326 mLockedSnapVertices.dequeue();
1327 }
1328}
1329
1331{
1333 context.snappingUtils = mMapCanvas->snappingUtils();
1334 context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
1335 context.xConstraint = _constraint( mXConstraint.get() );
1336 context.yConstraint = _constraint( mYConstraint.get() );
1337 context.zConstraint = _constraint( mZConstraint.get() );
1338 context.mConstraint = _constraint( mMConstraint.get() );
1339 context.distanceConstraint = _constraint( mDistanceConstraint.get() );
1340 context.angleConstraint = _constraint( mAngleConstraint.get() );
1341
1342 // if mAngleConstraint is only soft locked, do not consider that the context angle constraint
1343 // is locked, as this would prevent the common angles constraint from being applied
1344 context.angleConstraint.locked = mAngleConstraint->lockMode() == CadConstraint::HardLock;
1345
1346 context.snappingToFeaturesOverridesCommonAngle = mSnappingPrioritizeFeatures;
1347
1348 context.lineExtensionConstraint = _constraint( mLineExtensionConstraint.get() );
1349 context.xyVertexConstraint = _constraint( mXyVertexConstraint.get() );
1350
1351 context.setCadPoints( mCadPointList );
1352 context.setLockedSnapVertices( mLockedSnapVertices );
1353
1355 {
1356 context.snappingUtils->addExtraSnapLayer( mConstructionGuidesLayer.get() );
1357 }
1358
1361 context.commonAngleConstraint.value = mCommonAngleConstraint;
1362
1364
1365 const bool res = output.valid;
1366 QgsPoint point = pointXYToPoint( output.finalMapPoint );
1367 mSnappedSegment.clear();
1368 if ( output.snapMatch.hasEdge() )
1369 {
1370 QgsPointXY edgePt0, edgePt1;
1371 output.snapMatch.edgePoints( edgePt0, edgePt1 );
1372 mSnappedSegment << edgePt0 << edgePt1;
1373 }
1374 if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
1375 {
1376 if ( output.softLockCommonAngle != -1 )
1377 {
1378 mAngleConstraint->setLockMode( CadConstraint::SoftLock );
1379 mAngleConstraint->setValue( output.softLockCommonAngle );
1380 }
1381 else
1382 {
1383 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1384 }
1385 }
1386
1387 mSoftLockLineExtension = output.softLockLineExtension;
1388 mSoftLockX = output.softLockX;
1389 mSoftLockY = output.softLockY;
1390
1391 if ( output.snapMatch.isValid() )
1392 {
1393 mSnapIndicator->setMatch( output.snapMatch );
1394 mSnapIndicator->setVisible( true );
1395 }
1396 else
1397 {
1398 mSnapIndicator->setVisible( false );
1399 }
1400
1401 /*
1402 * Ensure that Z and M are passed
1403 * It will be dropped as needed later.
1404 */
1407
1408 /*
1409 * Constraints are applied in 2D, they are always called when using the tool
1410 * but they do not take into account if when you snap on a vertex it has
1411 * a Z value.
1412 * To get the value we use the snapPoint method. However, we only apply it
1413 * when the snapped point corresponds to the constrained point or on an edge
1414 * if the topological editing is activated. Also, we don't apply it if
1415 * the point is not linked to a layer.
1416 */
1417 e->setMapPoint( point );
1418
1419 mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
1420 if ( mSnapMatch.layer() )
1421 {
1422 // note ND: I'm not 100% sure if the point == mSnapMatch.point() comparison was intended be done using QgsPointXY or QgsPoint objects here!
1423 // I'm using QgsPointXY here to keep the behavior the same from before a duplicate QgsPointXY == operator was removed...
1424 if ( ( ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() ) && ( QgsPointXY( point ) == mSnapMatch.point() ) )
1425 || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
1426 {
1427 e->snapPoint();
1428 point = mSnapMatch.interpolatedPoint( mMapCanvas->mapSettings().destinationCrs() );
1429 }
1430 }
1431
1432 context.snappingUtils->removeExtraSnapLayer( mConstructionGuidesLayer.get() );
1433
1434 if ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() )
1435 {
1436 toggleLockedSnapVertex( mSnapMatch, mLastSnapMatch );
1437 mLastSnapMatch = mSnapMatch;
1438 }
1439 else
1440 {
1441 mLastSnapMatch = QgsPointLocator::Match();
1442 }
1443
1444 /*
1445 * And if M or Z lock button is activated get the value of the input.
1446 */
1447 if ( mLockZButton->isChecked() )
1448 {
1449 point.setZ( QLocale().toDouble( mZLineEdit->text() ) );
1450 }
1451 if ( mLockMButton->isChecked() )
1452 {
1453 point.setM( QLocale().toDouble( mMLineEdit->text() ) );
1454 }
1455
1456 // update the point list
1457 updateCurrentPoint( point );
1458
1459 updateUnlockedConstraintValues( point );
1460
1461 if ( res )
1462 {
1463 emit popWarning();
1464 }
1465 else
1466 {
1467 emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
1468 }
1469
1470 return res;
1471}
1472
1473
1474void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPoint &point )
1475{
1476 bool previousPointExist, penulPointExist;
1477 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1478 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1479
1480 // --- angle
1481 if ( previousPointExist )
1482 {
1483 double prevAngle = 0.0;
1484
1485 if ( penulPointExist && mAngleConstraint->relative() )
1486 {
1487 // previous angle
1488 prevAngle = std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() ) * 180 / M_PI;
1489 }
1490
1491 const double xAngle { std::atan2( point.y() - previousPt.y(), point.x() - previousPt.x() ) * 180 / M_PI };
1492
1493 // Modulus
1494 const double angle = std::fmod( xAngle - prevAngle, 360.0 );
1495 if ( !mAngleConstraint->isLocked() )
1496 {
1497 mAngleConstraint->setValue( angle );
1498 }
1499
1500 // Bearing (azimuth)
1501 double bearing { std::fmod( xAngle, 360.0 ) };
1502 bearing = bearing <= 90.0 ? 90.0 - bearing : ( bearing > 90 ? 270.0 + 180.0 - bearing : 270.0 - bearing );
1503 const QgsNumericFormatContext context;
1504 const QString bearingText { QgsProject::instance()->displaySettings()->bearingFormat()->formatDouble( bearing, context ) };
1505 emit valueBearingChanged( bearingText );
1506 }
1507 // --- distance
1508 if ( !mDistanceConstraint->isLocked() && previousPointExist )
1509 {
1510 mDistanceConstraint->setValue( std::sqrt( previousPt.distanceSquared( point ) ) );
1511 }
1512 // --- X
1513 if ( !mXConstraint->isLocked() )
1514 {
1515 if ( previousPointExist && mXConstraint->relative() )
1516 {
1517 mXConstraint->setValue( point.x() - previousPt.x() );
1518 }
1519 else
1520 {
1521 mXConstraint->setValue( point.x() );
1522 }
1523 }
1524 // --- Y
1525 if ( !mYConstraint->isLocked() )
1526 {
1527 if ( previousPointExist && mYConstraint->relative() )
1528 {
1529 mYConstraint->setValue( point.y() - previousPt.y() );
1530 }
1531 else
1532 {
1533 mYConstraint->setValue( point.y() );
1534 }
1535 }
1536 // --- Z
1537 if ( !mZConstraint->isLocked() )
1538 {
1539 if ( previousPointExist && mZConstraint->relative() )
1540 {
1541 mZConstraint->setValue( point.z() - previousPt.z() );
1542 }
1543 else
1544 {
1545 mZConstraint->setValue( point.z() );
1546 }
1547 }
1548 // --- M
1549 if ( !mMConstraint->isLocked() )
1550 {
1551 if ( previousPointExist && mMConstraint->relative() )
1552 {
1553 mMConstraint->setValue( point.m() - previousPt.m() );
1554 }
1555 else
1556 {
1557 mMConstraint->setValue( point.m() );
1558 }
1559 }
1560}
1561
1562
1563QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
1564{
1565 QList<QgsPointXY> segment;
1566 QgsPointXY pt1, pt2;
1568
1569 QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
1570
1571 const QgsSnappingConfig canvasConfig = snappingUtils->config();
1572 QgsSnappingConfig localConfig = snappingUtils->config();
1573
1576 snappingUtils->setConfig( localConfig );
1577
1578 match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
1579
1580 snappingUtils->setConfig( canvasConfig );
1581
1582 if ( match.isValid() && match.hasEdge() )
1583 {
1584 match.edgePoints( pt1, pt2 );
1585 segment << pt1 << pt2;
1586 }
1587
1588 if ( snapped )
1589 {
1590 *snapped = segment.count() == 2;
1591 }
1592
1593 return segment;
1594}
1595
1597{
1598 if ( mCurrentTool )
1599 {
1600 mCurrentTool->canvasPressEvent( event );
1601 }
1602
1603 if ( constructionMode() )
1604 {
1605 event->setAccepted( false );
1606 }
1607}
1608
1610{
1611 // perpendicular/parallel constraint
1612 // do a soft lock when snapping to a segment
1614
1615 if ( mCurrentTool )
1616 {
1617 mCurrentTool->canvasMoveEvent( event );
1618 }
1619
1621}
1622
1624{
1625 if ( event->button() == Qt::RightButton )
1626 {
1627 if ( mCurrentTool )
1628 {
1629 mCurrentTool->canvasReleaseEvent( event );
1630 if ( !event->isAccepted() )
1631 {
1632 return;
1633 }
1634 }
1635 clear();
1636 }
1637 else
1638 {
1639 applyConstraints( event ); // updates event's map point
1640 if ( alignToSegment( event ) )
1641 {
1642 event->setAccepted( false );
1643 return;
1644 }
1645
1646 if ( mCurrentTool )
1647 {
1648 mCurrentTool->canvasReleaseEvent( event );
1649 if ( !event->isAccepted() )
1650 {
1651 return;
1652 }
1653 else
1654 {
1655 // update the point list
1656 QgsPoint point( event->mapPoint() );
1659
1660 if ( mLockZButton->isChecked() )
1661 {
1662 point.setZ( QLocale().toDouble( mZLineEdit->text() ) );
1663 }
1664 if ( mLockMButton->isChecked() )
1665 {
1666 point.setM( QLocale().toDouble( mMLineEdit->text() ) );
1667 }
1668 updateCurrentPoint( point );
1669 }
1670 }
1671
1672 addPoint( event->mapPoint() );
1673 releaseLocks( false );
1674
1675 if ( constructionMode() )
1676 {
1677 event->setAccepted( false );
1678 }
1679 }
1680}
1681
1683{
1684 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::NoConstraint )
1685 {
1686 return false;
1687 }
1688
1689 bool previousPointExist, penulPointExist, snappedSegmentExist;
1690 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1691 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1692 mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
1693
1694 if ( !previousPointExist || !snappedSegmentExist )
1695 {
1696 return false;
1697 }
1698
1699 double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
1700
1701 if ( mAngleConstraint->relative() && penulPointExist )
1702 {
1703 angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
1704 }
1705
1706 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::Perpendicular )
1707 {
1708 angle += M_PI_2;
1709 }
1710
1711 angle *= 180 / M_PI;
1712
1713 mAngleConstraint->setValue( angle );
1714 mAngleConstraint->setLockMode( lockMode );
1715 if ( lockMode == CadConstraint::HardLock )
1716 {
1717 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
1718 }
1719
1720 return true;
1721}
1722
1724{
1725 // event on map tool
1726
1727 if ( !mCadEnabled )
1728 return false;
1729
1730 switch ( e->key() )
1731 {
1732 case Qt::Key_Backspace:
1733 case Qt::Key_Delete:
1734 {
1736 releaseLocks( false );
1737 break;
1738 }
1739 case Qt::Key_Escape:
1740 {
1741 releaseLocks();
1742 break;
1743 }
1744 default:
1745 {
1746 keyPressEvent( e );
1747 break;
1748 }
1749 }
1750 // for map tools, continues with key press in any case
1751 return false;
1752}
1753
1755{
1756 if ( mCurrentTool )
1757 {
1758 mCurrentTool->deleteLater();
1759 }
1760
1761 if ( !mConstructionGuideLine.isEmpty() )
1762 {
1763 mConstructionGuideLine.clear();
1764 }
1765
1766 clearPoints();
1767 releaseLocks();
1768}
1769
1771{
1772 // event on dock (this)
1773
1774 if ( !mCadEnabled )
1775 return;
1776
1777 switch ( e->key() )
1778 {
1779 case Qt::Key_Backspace:
1780 case Qt::Key_Delete:
1781 {
1783 releaseLocks( false );
1784 break;
1785 }
1786 case Qt::Key_Escape:
1787 {
1788 releaseLocks();
1789
1790 if ( mConstructionGuideLine.numPoints() >= 2 )
1791 {
1792 mConstructionGuidesLayer->dataProvider()->deleteFeatures( QgsFeatureIds() << mConstructionGuideId );
1793 mConstructionGuideLine.clear();
1794 }
1795
1796 if ( mCurrentTool )
1797 {
1798 mCurrentTool->deleteLater();
1799 }
1800
1801 break;
1802 }
1803 default:
1804 {
1805 filterKeyPress( e );
1806 break;
1807 }
1808 }
1809}
1810
1811void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
1812{
1813 clearPoints();
1814 const auto constPoints = points;
1815 for ( const QgsPointXY &pt : constPoints )
1816 {
1817 addPoint( pt );
1818 }
1819}
1820
1822{
1823 mDistanceConstraint->toggleLocked();
1824 emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1825 emit pointChangedV2( mCadPointList.value( 0 ) );
1826}
1827
1828bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
1829{
1830 if ( !cadEnabled() )
1831 {
1832 return QgsDockWidget::eventFilter( obj, event );
1833 }
1834
1835 // event for line edits and map canvas
1836 // we have to catch both KeyPress events and ShortcutOverride events. This is because
1837 // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
1838 // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
1839 // us to intercept these keystrokes before they are caught by the global shortcuts
1840 if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
1841 {
1842 if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
1843 {
1844 return filterKeyPress( keyEvent );
1845 }
1846 }
1847 return QgsDockWidget::eventFilter( obj, event );
1848}
1849
1850bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
1851{
1852 // we need to be careful here -- because this method is called on both KeyPress events AND
1853 // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
1854 // these event types for a single key press. I.e. pressing "A" may first call trigger a
1855 // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
1856 const QEvent::Type type = e->type();
1857 switch ( e->key() )
1858 {
1859 case Qt::Key_Escape:
1860 {
1861 if ( type == QEvent::KeyPress && mCurrentTool )
1862 {
1863 mCurrentTool->deleteLater();
1864 }
1865 else if ( type == QEvent::KeyPress && mConstructionMode && mConstructionGuideLine.numPoints() >= 2 )
1866 {
1867 mConstructionGuidesLayer->dataProvider()->deleteFeatures( QgsFeatureIds() << mConstructionGuideId );
1868 mConstructionGuideLine.clear();
1869
1870 if ( mCadPointList.size() > 1 )
1871 {
1872 mConstructionGuideLine.addVertex( mCadPointList.at( 1 ) );
1873 }
1874
1876 e->accept();
1877 }
1878 else
1879 {
1880 e->ignore();
1881 }
1882 break;
1883 }
1884 case Qt::Key_X:
1885 {
1886 // modifier+x ONLY caught for ShortcutOverride events...
1887 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1888 {
1889 mXConstraint->toggleLocked();
1890 emit lockXChanged( mXConstraint->isLocked() );
1891 emit pointChangedV2( mCadPointList.value( 0 ) );
1892 e->accept();
1893 }
1894 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1895 {
1896 if ( mCapacities.testFlag( RelativeCoordinates ) )
1897 {
1898 mXConstraint->toggleRelative();
1899 emit relativeXChanged( mXConstraint->relative() );
1900 emit pointChangedV2( mCadPointList.value( 0 ) );
1901 e->accept();
1902 }
1903 }
1904 // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
1905 else if ( type == QEvent::KeyPress )
1906 {
1907 mXLineEdit->setFocus();
1908 mXLineEdit->selectAll();
1909 emit focusOnXRequested();
1910 e->accept();
1911 }
1912 break;
1913 }
1914 case Qt::Key_Y:
1915 {
1916 // modifier+y ONLY caught for ShortcutOverride events...
1917 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1918 {
1919 mYConstraint->toggleLocked();
1920 emit lockYChanged( mYConstraint->isLocked() );
1921 emit pointChangedV2( mCadPointList.value( 0 ) );
1922 e->accept();
1923 }
1924 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1925 {
1926 if ( mCapacities.testFlag( RelativeCoordinates ) )
1927 {
1928 mYConstraint->toggleRelative();
1929 emit relativeYChanged( mYConstraint->relative() );
1930 emit pointChangedV2( mCadPointList.value( 0 ) );
1931 e->accept();
1932 }
1933 }
1934 // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1935 else if ( type == QEvent::KeyPress )
1936 {
1937 mYLineEdit->setFocus();
1938 mYLineEdit->selectAll();
1939 emit focusOnYRequested();
1940 e->accept();
1941 }
1942 break;
1943 }
1944 case Qt::Key_Z:
1945 {
1946 // modifier+z ONLY caught for ShortcutOverride events...
1947 if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::AltModifier )
1948 {
1949 mZConstraint->toggleLocked();
1950 emit lockZChanged( mZConstraint->isLocked() );
1951 emit pointChangedV2( mCadPointList.value( 0 ) );
1952 e->accept();
1953 }
1954 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1955 {
1956 if ( mCapacities.testFlag( RelativeCoordinates ) )
1957 {
1958 mZConstraint->toggleRelative();
1959 emit relativeZChanged( mZConstraint->relative() );
1960 emit pointChangedV2( mCadPointList.value( 0 ) );
1961 e->accept();
1962 }
1963 }
1964 // .. but "z" alone ONLY caught for KeyPress events (see comment at start of function)
1965 else if ( type == QEvent::KeyPress )
1966 {
1967 mZLineEdit->setFocus();
1968 mZLineEdit->selectAll();
1969 emit focusOnZRequested();
1970 e->accept();
1971 }
1972 break;
1973 }
1974 case Qt::Key_M:
1975 {
1976 // modifier+m ONLY caught for ShortcutOverride events...
1977 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1978 {
1979 mMConstraint->toggleLocked();
1980 emit lockMChanged( mMConstraint->isLocked() );
1981 emit pointChangedV2( mCadPointList.value( 0 ) );
1982 e->accept();
1983 }
1984 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1985 {
1986 if ( mCapacities.testFlag( RelativeCoordinates ) )
1987 {
1988 mMConstraint->toggleRelative();
1989 emit relativeMChanged( mMConstraint->relative() );
1990 emit pointChangedV2( mCadPointList.value( 0 ) );
1991 e->accept();
1992 }
1993 }
1994 // .. but "m" alone ONLY caught for KeyPress events (see comment at start of function)
1995 else if ( type == QEvent::KeyPress )
1996 {
1997 mMLineEdit->setFocus();
1998 mMLineEdit->selectAll();
1999 emit focusOnMRequested();
2000 e->accept();
2001 }
2002 break;
2003 }
2004 case Qt::Key_A:
2005 {
2006 // modifier+a ONLY caught for ShortcutOverride events...
2007 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
2008 {
2009 if ( mCapacities.testFlag( AbsoluteAngle ) )
2010 {
2011 mAngleConstraint->toggleLocked();
2012 emit lockAngleChanged( mAngleConstraint->isLocked() );
2013 emit pointChangedV2( mCadPointList.value( 0 ) );
2014 e->accept();
2015 }
2016 }
2017 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
2018 {
2019 if ( mCapacities.testFlag( RelativeAngle ) )
2020 {
2021 mAngleConstraint->toggleRelative();
2022 emit relativeAngleChanged( mAngleConstraint->relative() );
2023 emit pointChangedV2( mCadPointList.value( 0 ) );
2024 e->accept();
2025 }
2026 }
2027 // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
2028 else if ( type == QEvent::KeyPress )
2029 {
2030 mAngleLineEdit->setFocus();
2031 mAngleLineEdit->selectAll();
2032 emit focusOnAngleRequested();
2033 e->accept();
2034 }
2035 break;
2036 }
2037 case Qt::Key_D:
2038 {
2039 // modifier+d ONLY caught for ShortcutOverride events...
2040 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
2041 {
2042 if ( mCapacities.testFlag( RelativeCoordinates ) && mCapacities.testFlag( Distance ) )
2043 {
2045 e->accept();
2046 }
2047 }
2048 // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
2049 else if ( type == QEvent::KeyPress )
2050 {
2051 mDistanceLineEdit->setFocus();
2052 mDistanceLineEdit->selectAll();
2054 e->accept();
2055 }
2056 break;
2057 }
2058 case Qt::Key_C:
2059 {
2060 if ( type == QEvent::KeyPress )
2061 {
2062 setConstructionMode( !mConstructionMode );
2063 e->accept();
2064 }
2065 break;
2066 }
2067 case Qt::Key_P:
2068 {
2069 if ( type == QEvent::KeyPress )
2070 {
2071 const bool parallel = mParallelAction->isChecked();
2072 const bool perpendicular = mPerpendicularAction->isChecked();
2073
2074 if ( !parallel && !perpendicular )
2075 {
2076 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
2077 }
2078 else if ( perpendicular )
2079 {
2080 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
2081 }
2082 else
2083 {
2084 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
2085 }
2086 e->accept();
2087
2088 // run a fake map mouse event to update the paint item
2089 emit pointChangedV2( mCadPointList.value( 0 ) );
2090 }
2091 break;
2092 }
2093 case Qt::Key_N:
2094 {
2095 if ( type == QEvent::ShortcutOverride )
2096 {
2097 const QList<double> constActionKeys { mCommonAngleActions.keys() };
2098 const int currentAngleActionIndex { static_cast<int>( constActionKeys.indexOf( mCommonAngleConstraint ) ) };
2099 const QList<QAction *> constActions { mCommonAngleActions.values() };
2100 QAction *nextAngleAction;
2101 if ( e->modifiers() == Qt::ShiftModifier )
2102 {
2103 nextAngleAction = currentAngleActionIndex == 0 ? constActions.last() : constActions.at( currentAngleActionIndex - 1 );
2104 }
2105 else
2106 {
2107 nextAngleAction = currentAngleActionIndex == constActions.count() - 1 ? constActions.first() : constActions.at( currentAngleActionIndex + 1 );
2108 }
2109 nextAngleAction->trigger();
2110 e->accept();
2111 }
2112 break;
2113 }
2114 default:
2115 {
2116 return false; // continues
2117 }
2118 }
2119 return e->isAccepted();
2120}
2121
2123{
2124 // most of theses lines can be moved to updateCapacity
2125 connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
2126 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
2127 {
2128 mAngleLineEdit->setToolTip( tr( "Angle constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
2129 mDistanceLineEdit->setToolTip( tr( "Distance constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
2130
2131 mLabelX->setText( tr( "Long" ) );
2132 mLabelY->setText( tr( "Lat" ) );
2133
2134 mXConstraint->setPrecision( 8 );
2135 mYConstraint->setPrecision( 8 );
2136 }
2137 else
2138 {
2139 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
2140 mAngleLineEdit->setToolTip( QString() );
2141
2142 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
2143
2144 mLabelX->setText( tr( "x" ) );
2145 mLabelY->setText( tr( "y" ) );
2146
2147 mXConstraint->setPrecision( 6 );
2148 mYConstraint->setPrecision( 6 );
2149 }
2150
2151 updateCapacity();
2152
2153 mEnableAction->setEnabled( true );
2154 mErrorLabel->hide();
2155 mCadWidget->show();
2156
2157 mCurrentMapToolSupportsCad = true;
2158
2159 if ( mSessionActive && !isVisible() )
2160 {
2161 show();
2162 }
2163
2164 setCadEnabled( mSessionActive );
2165
2166 if ( !mConstructionGuidesLayer )
2167 {
2168 resetConstructionGuides();
2169 }
2170
2171 if ( mDeferredUpdateConstructionGuidesCrs )
2172 {
2173 updateConstructionGuidesCrs();
2174 }
2175
2177}
2178
2180{
2182
2183 mEnableAction->setEnabled( false );
2184 mErrorLabel->setText( tr( "Advanced digitizing tools are not enabled for the current map tool" ) );
2185 mErrorLabel->show();
2186 mCadWidget->hide();
2187
2188 mCurrentMapToolSupportsCad = false;
2189
2190 mSnapIndicator->setVisible( false );
2191
2192 setCadEnabled( false );
2193}
2194
2196{
2197 mCadPaintItem->update();
2198}
2199
2201{
2202 if ( !force && ( mLineExtensionConstraint->isLocked() || mXyVertexConstraint->isLocked() ) )
2203 {
2204 return;
2205 }
2206
2207 mLockedSnapVertices.clear();
2208}
2209
2211{
2212 QgsPoint pt = pointXYToPoint( point );
2213 if ( !pointsCount() )
2214 {
2215 mCadPointList << pt;
2216 }
2217 else
2218 {
2219 mCadPointList.insert( 0, pt );
2220 }
2221
2223 {
2224 if ( constructionMode() )
2225 {
2226 mConstructionGuideLine.addVertex( pt );
2227
2228 if ( mConstructionGuideLine.numPoints() == 2 )
2229 {
2230 QgsFeature feature;
2231 QgsGeometry geom( mConstructionGuideLine.clone() );
2232 feature.setGeometry( geom );
2233 mConstructionGuidesLayer->dataProvider()->addFeature( feature );
2234 mConstructionGuideId = feature.id();
2235 }
2236 else if ( mConstructionGuideLine.numPoints() > 2 )
2237 {
2238 QgsGeometry geom( mConstructionGuideLine.clone() );
2239 mConstructionGuidesLayer->dataProvider()->changeGeometryValues( { { mConstructionGuideId, geom } } );
2240 }
2241 }
2242 else
2243 {
2244 if ( !mConstructionGuideLine.isEmpty() )
2245 {
2246 mConstructionGuideLine.addVertex( pt );
2247
2248 QgsGeometry geom( mConstructionGuideLine.clone() );
2249 mConstructionGuidesLayer->dataProvider()->changeGeometryValues( { { mConstructionGuideId, geom } } );
2250 mConstructionGuideLine.clear();
2251 }
2252 }
2253 }
2254
2255 updateCapacity();
2257}
2258
2260{
2261 if ( !pointsCount() )
2262 return;
2263
2264 const int i = pointsCount() > 1 ? 1 : 0;
2265 mCadPointList.removeAt( i );
2266 updateCapacity();
2268}
2269
2271{
2272 mCadPointList.clear();
2273 mSnappedSegment.clear();
2274
2275 updateCapacity();
2277}
2278
2280{
2281 if ( !pointsCount() )
2282 {
2283 mCadPointList << point;
2284 updateCapacity();
2285 }
2286 else
2287 {
2288 mCadPointList[0] = point;
2289 }
2291}
2292
2294{
2295 if ( mode == mLockMode )
2296 {
2297 return;
2298 }
2299 mLockMode = mode;
2300 mLockerButton->setChecked( mode == HardLock );
2301 if ( mRepeatingLockButton )
2302 {
2303 if ( mode == HardLock )
2304 {
2305 mRepeatingLockButton->setEnabled( true );
2306 }
2307 else
2308 {
2309 mRepeatingLockButton->setChecked( false );
2310 mRepeatingLockButton->setEnabled( false );
2311 }
2312 }
2313
2314 if ( mode == NoLock )
2315 {
2316 mLineEdit->clear();
2317 }
2318}
2319
2321{
2322 mRepeatingLock = repeating;
2323 if ( mRepeatingLockButton )
2324 mRepeatingLockButton->setChecked( repeating );
2325}
2326
2328{
2329 mRelative = relative;
2330 if ( mRelativeButton )
2331 {
2332 mRelativeButton->setChecked( relative );
2333 }
2334}
2335
2337{
2338 mValue = value;
2339 if ( updateWidget && mLineEdit->isEnabled() )
2340 mLineEdit->setText( displayValue() );
2341}
2342
2344{
2345 switch ( mCadConstraintType )
2346 {
2348 {
2349 return QLocale().toString( mValue, 'f', mPrecision ).append( tr( " °" ) );
2350 }
2353 {
2354 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
2355 {
2356 return QLocale().toString( mValue, 'f', mPrecision ).append( tr( " °" ) );
2357 }
2358 else
2359 {
2360 return QLocale().toString( mValue, 'f', mPrecision );
2361 }
2362 }
2364 {
2365 const Qgis::DistanceUnit displayUnits { QgsProject::instance()->distanceUnits() };
2366 // Convert from canvas units
2367 const Qgis::DistanceUnit canvasUnits { mMapCanvas->mapSettings().mapUnits() };
2368 const double value { QgsUnitTypes::fromUnitToUnitFactor( canvasUnits, displayUnits ) * mValue };
2369 return QgsDistanceArea::formatDistance( value, mPrecision, displayUnits, true );
2370 }
2374 default:
2375 break;
2376 }
2377 return QLocale().toString( mValue, 'f', mPrecision );
2378}
2379
2381{
2382 setLockMode( mLockMode == HardLock ? NoLock : HardLock );
2383}
2384
2386{
2387 setRelative( !mRelative );
2388}
2389
2391{
2392 mPrecision = precision;
2393 if ( mLineEdit->isEnabled() )
2394 mLineEdit->setText( displayValue() );
2395}
2396
2401
2403{
2404 mCadConstraintType = constraintType;
2405}
2406
2408{
2409 mMapCanvas = mapCanvas;
2410}
2411
2413{
2414 QString value { text.trimmed() };
2415 switch ( constraintType )
2416 {
2418 {
2419 // Remove distance unit suffix
2420 const QString distanceUnit { QgsUnitTypes::toAbbreviatedString( QgsProject::instance()->distanceUnits() ) };
2421 if ( value.endsWith( distanceUnit ) )
2422 {
2423 value.chop( distanceUnit.length() );
2424 }
2425 break;
2426 }
2428 {
2429 // Remove angle suffix
2430 const QString angleUnit { tr( "°" ) };
2431 if ( value.endsWith( angleUnit ) )
2432 {
2433 value.chop( angleUnit.length() );
2434 }
2435 break;
2436 }
2437 default:
2438 break;
2439 }
2440 return value.trimmed();
2441}
2442
2444{
2445 if ( exist )
2446 *exist = pointsCount() > 0;
2447 if ( pointsCount() > 0 )
2448 return mCadPointList.value( 0 );
2449 else
2450 return QgsPoint();
2451}
2452
2454{
2455 if ( pointsCount() > 0 && layer )
2456 {
2457 QgsPoint res = mCadPointList.value( 0 );
2458 const QgsPointXY layerCoordinates = mMapCanvas->mapSettings().mapToLayerCoordinates( layer, res );
2459 res.setX( layerCoordinates.x() );
2460 res.setY( layerCoordinates.y() );
2461 return res;
2462 }
2463 return QgsPoint();
2464}
2465
2467{
2468 if ( exist )
2469 *exist = pointsCount() > 1;
2470 if ( pointsCount() > 1 )
2471 return mCadPointList.value( 1 );
2472 else
2473 return QgsPoint();
2474}
2475
2477{
2478 if ( exist )
2479 *exist = pointsCount() > 2;
2480 if ( pointsCount() > 2 )
2481 return mCadPointList.value( 2 );
2482 else
2483 return QgsPoint();
2484}
2485
2486QgsPoint QgsAdvancedDigitizingDockWidget::pointXYToPoint( const QgsPointXY &point ) const
2487{
2488 return QgsPoint( point.x(), point.y(), getLineZ(), getLineM() );
2489}
2490
2492{
2493 return mZLineEdit->isEnabled() ? QLocale().toDouble( mZLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
2494}
2495
2497{
2498 return mMLineEdit->isEnabled() ? QLocale().toDouble( mMLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
2499}
2500
2502{
2503 return mShowConstructionGuides ? mShowConstructionGuides->isChecked() : false;
2504}
2505
2507{
2508 return mSnapToConstructionGuides ? mShowConstructionGuides->isChecked() && mSnapToConstructionGuides->isChecked() : false;
2509}
2510
2512{
2513 return mRecordConstructionGuides ? mRecordConstructionGuides->isChecked() : false;
2514}
2515
2516void QgsAdvancedDigitizingDockWidget::updateConstructionGuidesCrs()
2517{
2518 if ( !mConstructionGuidesLayer )
2519 {
2520 return;
2521 }
2522
2523 if ( !cadEnabled() )
2524 {
2525 mDeferredUpdateConstructionGuidesCrs = true;
2526 }
2527
2528 QgsCoordinateTransform transform = QgsCoordinateTransform( mConstructionGuidesLayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
2529 mConstructionGuidesLayer->setCrs( mMapCanvas->mapSettings().destinationCrs() );
2530 QgsFeatureIterator it = mConstructionGuidesLayer->getFeatures( QgsFeatureRequest().setNoAttributes() );
2531 QgsFeature feature;
2532 while ( it.nextFeature( feature ) )
2533 {
2534 QgsGeometry geom = feature.geometry();
2535 geom.transform( transform );
2536 mConstructionGuidesLayer->dataProvider()->changeGeometryValues( { { feature.id(), geom } } );
2537 }
2538
2539 mDeferredUpdateConstructionGuidesCrs = false;
2540}
2541
2542void QgsAdvancedDigitizingDockWidget::resetConstructionGuides()
2543{
2544 if ( mConstructionGuidesLayer )
2545 {
2546 mConstructionGuidesLayer.reset();
2547 }
2548
2549 const QgsVectorLayer::LayerOptions options( QgsProject::instance()->transformContext(), false, false );
2550 mConstructionGuidesLayer = std::make_unique<QgsVectorLayer>( QStringLiteral( "LineString?crs=%1" ).arg( mMapCanvas->mapSettings().destinationCrs().authid() ), QStringLiteral( "constructionGuides" ), QStringLiteral( "memory" ), options );
2551}
@ Segment
On segments.
DistanceUnit
Units of distance.
Definition qgis.h:4875
CadConstraintType
Advanced digitizing constraint type.
Definition qgis.h:3956
@ Distance
Distance value.
@ Generic
Generic value.
@ YCoordinate
Y Coordinate value.
@ XCoordinate
X Coordinate value.
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
@ AllLayers
On all vector layers.
BetweenLineConstraint
Between line constraints which can be enabled.
Definition qgis.h:3930
@ NoConstraint
No additional constraint.
@ Perpendicular
Perpendicular.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:256
Draws the graphical elements of the CAD tools (.
void updatePosition() override
called on changed extent or resize event to update position of the item
The CadConstraint is a class for all basic constraints (angle/distance/x/y).
Qgis::CadConstraintType cadConstraintType() const
Returns the constraint type.
void setPrecision(int precision)
Sets the numeric precision (decimal places) to show in the associated widget.
static QString removeSuffix(const QString &text, Qgis::CadConstraintType constraintType)
Removes unit suffix from the constraint text.
QString displayValue() const
Returns a localized formatted string representation of the value.
void setRepeatingLock(bool repeating)
Sets whether a repeating lock is set for the constraint.
void setCadConstraintType(Qgis::CadConstraintType constraintType)
Sets the constraint type to constraintType.
void setRelative(bool relative)
Set if the constraint should be treated relative.
void setValue(double value, bool updateWidget=true)
Set the value of the constraint.
void setMapCanvas(QgsMapCanvas *mapCanvas)
Sets the map canvas to mapCanvas.
void valueDistanceChanged(const QString &value)
Emitted whenever the distance value changes (either the mouse moved, or the user changed the input).
QgsAdvancedDigitizingDockWidget(QgsMapCanvas *canvas, QWidget *parent=nullptr, QgsUserInputWidget *userInputWidget=nullptr)
Create an advanced digitizing dock widget.
void lockZChanged(bool locked)
Emitted whenever the Z parameter is locked.
void setEnabledZ(bool enable)
Sets whether Z is enabled.
void setPoints(const QList< QgsPointXY > &points)
Configures list of current CAD points.
void setZ(const QString &value, WidgetSetMode mode)
Set the Z value on the widget.
void setY(const QString &value, WidgetSetMode mode)
Set the Y value on the widget.
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
bool applyConstraints(QgsMapMouseEvent *e)
apply the CAD constraints.
void setEnabledM(bool enable)
Sets whether M is enabled.
int pointsCount() const
The number of points in the CAD point helper list.
void addPoint(const QgsPointXY &point)
Adds point to the CAD point list.
void releaseLocks(bool releaseRepeatingLocks=true)
unlock all constraints
void switchZM()
Determines if Z or M will be enabled.
void relativeMChanged(bool relative)
Emitted whenever the M parameter is toggled between absolute and relative.
void lockXChanged(bool locked)
Emitted whenever the X parameter is locked.
void softLockLineExtensionChanged(bool locked)
Emitted whenever the soft line extension parameter is locked.
void focusOnXRequested()
Emitted whenever the X field should get the focus using the shortcuts (X).
bool constructionMode() const
Returns whether the construction mode is activated.
void setTool(QgsAdvancedDigitizingTool *tool)
Sets an advanced digitizing tool which will take over digitizing until the tool is close.
void valueYChanged(const QString &value)
Emitted whenever the Y value changes (either the mouse moved, or the user changed the input).
QString formatCommonAngleSnapping(double angle)
Returns the formatted label for common angle snapping option.
void focusOnYRequested()
Emitted whenever the Y field should get the focus using the shortcuts (Y).
double getLineM() const
Convenient method to get the M value from the line edit wiget.
void enabledChangedDistance(bool enabled)
Emitted whenever the distance field is enabled or disabled.
void valueZChanged(const QString &value)
Emitted whenever the Z value changes (either the mouse moved, or the user changed the input).
bool recordConstructionGuides() const
Returns whether construction guides are being recorded.
void clearPoints()
Removes all points from the CAD point list.
QgsPoint previousPointV2(bool *exists=nullptr) const
The previous point.
void lockAngleChanged(bool locked)
Emitted whenever the angle parameter is locked.
void processCanvasReleaseEvent(QgsMapMouseEvent *event)
Processes the canvas release event.
void updateCadPaintItem()
Updates canvas item that displays constraints on the ma.
void removePreviousPoint()
Removes previous point in the CAD point list.
QgsPoint penultimatePointV2(bool *exists=nullptr) const
The penultimate point.
void pointChangedV2(const QgsPoint &point)
Sometimes a constraint may change the current point out of a mouse event.
void processCanvasPressEvent(QgsMapMouseEvent *event)
Processes the canvas press event.
void relativeXChanged(bool relative)
Emitted whenever the X parameter is toggled between absolute and relative.
void focusOnAngleRequested()
Emitted whenever the angle field should get the focus using the shortcuts (A).
bool showConstructionGuides() const
Returns whether the construction guides are visible.
WidgetSetMode
Type of interaction to simulate when editing values from external widget.
void focusOnZRequested()
Emitted whenever the Z field should get the focus using the shortcuts (Z).
void focusOnMRequested()
Emitted whenever the M field should get the focus using the shortcuts (M).
void popWarning()
Remove any previously emitted warnings (if any)
void valueXChanged(const QString &value)
Emitted whenever the X value changes (either the mouse moved, or the user changed the input).
double getLineZ() const
Convenient method to get the Z value from the line edit wiget.
void updateCurrentPoint(const QgsPoint &point)
Updates the current point in the CAD point list.
void lockMChanged(bool locked)
Emitted whenever the M parameter is locked.
void cadEnabledChanged(bool enabled)
Signals for external widgets that need to update according to current values.
void lockYChanged(bool locked)
Emitted whenever the Y parameter is locked.
void valueAngleChanged(const QString &value)
Emitted whenever the angle value changes (either the mouse moved, or the user changed the input).
void processCanvasMoveEvent(QgsMapMouseEvent *event)
Processes the canvas move event.
void valueMChanged(const QString &value)
Emitted whenever the M value changes (either the mouse moved, or the user changed the input).
void enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
void relativeZChanged(bool relative)
Emitted whenever the Z parameter is toggled between absolute and relative.
@ RelativeAngle
Also for parallel and perpendicular.
@ RelativeCoordinates
This corresponds to distance and relative coordinates.
bool canvasKeyPressEventFilter(QKeyEvent *e)
Filter key events to e.g.
void setAngle(const QString &value, WidgetSetMode mode)
Set the angle value on the widget.
void enabledChangedAngle(bool enabled)
Emitted whenever the angle field is enabled or disabled.
bool snapToConstructionGuides() const
Returns whether points should snap to construction guides.
void toggleConstraintDistance()
Toggles the distance constraint.
void enabledChangedZ(bool enabled)
Emitted whenever the Z field is enabled or disabled.
void lockDistanceChanged(bool locked)
Emitted whenever the distance parameter is locked.
void relativeAngleChanged(bool relative)
Emitted whenever the angleX parameter is toggled between absolute and relative.
void softLockXyChanged(bool locked)
Emitted whenever the soft x/y extension parameter is locked.
void valueBearingChanged(const QString &value)
Emitted whenever the bearing value changes.
void enabledChangedM(bool enabled)
Emitted whenever the M field is enabled or disabled.
void setDistance(const QString &value, WidgetSetMode mode)
Set the distance value on the widget.
bool alignToSegment(QgsMapMouseEvent *e, QgsAdvancedDigitizingDockWidget::CadConstraint::LockMode lockMode=QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock)
align to segment for between line constraint.
void setX(const QString &value, WidgetSetMode mode)
Set the X value on the widget.
QgsPoint currentPointV2(bool *exists=nullptr) const
The last point.
QgsAdvancedDigitizingTool * tool() const
Returns the current advanced digitizing tool.
void clear()
Clear any cached previous clicks and helper lines.
void focusOnDistanceRequested()
Emitted whenever the distance field should get the focus using the shortcuts (D).
QgsPoint currentPointLayerCoordinates(QgsMapLayer *layer) const
Returns the last CAD point, in a map layer's coordinates.
void valueCommonAngleSnappingChanged(double angle)
Emitted whenever the snapping to common angle option changes, angle = 0 means that the functionality ...
void setM(const QString &value, WidgetSetMode mode)
Set the M value on the widget.
void pushWarning(const QString &message)
Push a warning.
void clearLockedSnapVertices(bool force=true)
Removes all points from the locked snap vertex list.
void relativeYChanged(bool relative)
Emitted whenever the Y parameter is toggled between absolute and relative.
A widget that floats next to the mouse pointer, and allows interaction with the AdvancedDigitizing fe...
void setItemVisibility(const QgsAdvancedDigitizingFloater::FloaterItem &item, bool visible)
Set whether the floater item should be visible or not.
void setActive(bool active)
Set whether the floater should be active or not.
bool active()
Whether the floater is active or not.
Stores metadata about one advanced digitizing tool class.
QString visibleName() const
Returns the tool's translatable user-friendly name.
virtual QgsAdvancedDigitizingTool * createTool(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
Returns new tool of this type. Return nullptr on error.
An abstract class for advanced digitizing tools.
void paintRequested()
Requests a new painting event to the advanced digitizing canvas item.
QgsAdvancedDigitizingToolAbstractMetadata * toolMetadata(const QString &name)
Returns the advanced digitizing tool matching the provided name or nullptr when no match available.
const QStringList toolMetadataNames() const
Returns the list of registered tool names.
QString formatDouble(double value, const QgsNumericFormatContext &context) const override
Returns a formatted string representation of a numeric double value.
Structure with details of one constraint.
Definition qgscadutils.h:42
bool locked
Whether the constraint is active, i.e. should be considered.
Definition qgscadutils.h:55
double value
Numeric value of the constraint (coordinate/distance in map units or angle in degrees)
Definition qgscadutils.h:59
bool relative
Whether the value is relative to previous value.
Definition qgscadutils.h:57
Defines constraints for the QgsCadUtils::alignMapPoint() method.
QgsCadUtils::AlignMapPointConstraint xyVertexConstraint
QgsCadUtils::AlignMapPointConstraint yConstraint
Constraint for Y coordinate.
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
double mapUnitsPerPixel
Map units/pixel ratio from map canvas.
void setCadPoints(const QList< QgsPoint > &points)
Sets the list of recent CAD points (in map coordinates).
void setLockedSnapVertices(const QQueue< QgsPointLocator::Match > &lockedSnapVertices)
Sets the queue of locked vertices.
QgsCadUtils::AlignMapPointConstraint mConstraint
Constraint for M coordinate.
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
bool snappingToFeaturesOverridesCommonAngle
Flag to set snapping to features priority over common angle.
QgsCadUtils::AlignMapPointConstraint zConstraint
Constraint for Z coordinate.
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be nullptr.
QgsCadUtils::AlignMapPointConstraint commonAngleConstraint
Constraint for soft lock to a common angle.
QgsCadUtils::AlignMapPointConstraint lineExtensionConstraint
QgsCadUtils::AlignMapPointConstraint angleConstraint
Constraint for angle.
Structure returned from alignMapPoint() method.
Definition qgscadutils.h:67
Qgis::LineExtensionSide softLockLineExtension
Definition qgscadutils.h:90
QgsPointXY finalMapPoint
map point aligned according to the constraints
Definition qgscadutils.h:73
bool valid
Whether the combination of constraints is actually valid.
Definition qgscadutils.h:70
QgsPointLocator::Match snapMatch
Snapped point - only valid if actually used for something.
Definition qgscadutils.h:79
double softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition qgscadutils.h:88
static QgsCadUtils::AlignMapPointOutput alignMapPoint(const QgsPointXY &originalMapPoint, const QgsCadUtils::AlignMapPointContext &ctx)
Applies X/Y/angle/distance constraints from the given context to a map point.
Handles coordinate transforms between two coordinate systems.
static QString formatDistance(double distance, int decimals, Qgis::DistanceUnit unit, bool keepBaseUnit=false)
Returns an distance formatted as a friendly string.
A QDockWidget subclass with more fine-grained control over how the widget is closed or opened.
Handles parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
An event filter for watching for focus events on a parent object.
void focusIn()
Emitted when parent object gains focus.
void focusOut()
Emitted when parent object loses focus.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
static QgsGui * instance()
Returns a pointer to the singleton instance.
Definition qgsgui.cpp:79
static QgsAdvancedDigitizingToolsRegistry * advancedDigitizingToolsRegistry()
Returns the global advanced digitizing tools registry, used for registering advanced digitizing tools...
Definition qgsgui.cpp:150
void clear() override
Clears the geometry, ie reset it to a null geometry.
bool isEmpty() const override
Returns true if the geometry is empty.
int numPoints() const override
Returns the number of points in the curve.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
Map canvas is a class for displaying all GIS data types on a canvas.
QgsMapTool * mapTool() const
Returns the currently active tool.
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
void destinationCrsChanged()
Emitted when map CRS has changed.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
Base class for all map layer types.
Definition qgsmaplayer.h:77
A mouse event which is the result of a user interaction with a QgsMapCanvas.
QgsPointXY originalMapPoint() const
Returns the original, unmodified map point of the mouse cursor.
QgsPointXY mapPoint() const
mapPoint returns the point in coordinates
void setMapPoint(const QgsPointXY &point)
Set the (snapped) point this event points to in map coordinates.
QgsPointXY snapPoint()
snapPoint will snap the points using the map canvas snapping utils configuration
Qgis::DistanceUnit mapUnits() const
Returns the units of the map's geographical coordinates - used for scale calculation.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
A QgsMapTool which gives events directly in map coordinates and allows filtering of events.
static double defaultMValue()
Returns default M value.
static double defaultZValue()
Returns default Z value.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
bool isEditable() const override
Returns true if the layer can be edited.
A context for numeric formats.
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:337
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:326
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
void setM(double m)
Sets the point's m-value.
Definition qgspoint.h:365
void setZ(double z)
Sets the point's z-coordinate.
Definition qgspoint.h:350
double distanceSquared(double x, double y) const
Returns the Cartesian 2D squared distance between this point a specified x, y coordinate.
Definition qgspoint.h:409
double m
Definition qgspoint.h:55
double y
Definition qgspoint.h:53
const QgsBearingNumericFormat * bearingFormat() const
Returns the project bearing's format, which controls how bearings associated with the project are dis...
Qgis::DistanceUnit distanceUnits
Definition qgsproject.h:124
static QgsProject * instance()
Returns the QgsProject singleton instance.
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsSnappingConfig snappingConfig
Definition qgsproject.h:116
void cleared()
Emitted when the project is cleared (and additionally when an open project is cleared just before a n...
QgsProjectDisplaySettings * displaySettings
Definition qgsproject.h:126
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:113
bool topologicalEditing
Definition qgsproject.h:123
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
bool setValue(const T &value, const QString &dynamicKeyPart=QString()) const
Set settings value.
A boolean settings entry.
static QgsSettingsTreeNode * sTreeDigitizing
Stores settings for use within QGIS.
Definition qgssettings.h:65
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Shows a snapping marker on map canvas for the current snapping match.
Stores configuration of snapping settings for the project.
void setTypeFlag(Qgis::SnappingTypes type)
define the type of snapping
void setMode(Qgis::SnappingMode mode)
define the mode of snapping
Contains configuration of snapping and can return answers to snapping queries.
void addExtraSnapLayer(QgsVectorLayer *vl)
Supply an extra snapping layer (typically a memory layer).
void removeExtraSnapLayer(QgsVectorLayer *vl)
Removes an extra snapping layer.
QgsSnappingConfig config
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Snap to map according to the current configuration.
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
A floating widget that can be used to display widgets for user inputs.
void addUserInputWidget(QWidget *widget)
Add a widget to be displayed in the dock.
Represents a vector layer which manages a vector based dataset.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
static bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
double qgsPermissiveToDouble(QString string, bool &ok)
Converts a string to a double in a permissive way, e.g., allowing for incorrect numbers of digits bet...
Definition qgis.cpp:81
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6271
QSet< QgsFeatureId > QgsFeatureIds
QLineF segment(int index, QRectF rect, double radius)
int precision
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsPoint interpolatedPoint(const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem()) const
Convenient method to return a point on an edge with linear interpolation of the Z value.
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords,...
bool hasEdge() const
Returns true if the Match is an edge.
void edgePoints(QgsPointXY &pt1, QgsPointXY &pt2) const
Only for a valid edge match - obtain endpoints of the edge.
bool hasLineEndpoint() const
Returns true if the Match is a line endpoint (start or end vertex).
bool hasVertex() const
Returns true if the Match is a vertex.
Setting options for loading vector layers.