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