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