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