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