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