QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
Loading...
Searching...
No Matches
qgsadvanceddigitizingdockwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsadvanceddigitizingdockwidget.cpp - dock for CAD tools
3 ----------------------
4 begin : October 2014
5 copyright : (C) Denis Rouzaud
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QMenu>
17#include <QEvent>
18#include <QCoreApplication>
19
20#include <cmath>
21
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
991 if ( ok && type == Qgis::CadConstraintType::Distance )
992 {
993 const Qgis::DistanceUnit displayUnits { QgsProject::instance()->distanceUnits() };
994 // Convert to canvas units
995 const Qgis::DistanceUnit canvasUnits { mMapCanvas->mapSettings().mapUnits() };
996 value *= QgsUnitTypes::fromUnitToUnitFactor( displayUnits, canvasUnits );
997 }
998
999 return value;
1000}
1001
1002void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
1003{
1004 if ( !constraint || textValue.isEmpty() )
1005 {
1006 return;
1007 }
1008
1009 if ( constraint->lockMode() == CadConstraint::NoLock )
1010 return;
1011
1012 bool ok;
1013 const double value = parseUserInput( textValue, constraint->cadConstraintType(), ok );
1014 if ( !ok )
1015 return;
1016
1017 constraint->setValue( value, convertExpression );
1018 // run a fake map mouse event to update the paint item
1019 emit pointChangedV2( mCadPointList.value( 0 ) );
1020}
1021
1022void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
1023{
1024 CadConstraint *constraint = objectToConstraint( sender() );
1025 if ( !constraint )
1026 {
1027 return;
1028 }
1029
1030 if ( activate )
1031 {
1032 const QString textValue = constraint->lineEdit()->text();
1033 if ( !textValue.isEmpty() )
1034 {
1035 bool ok;
1036 const double value = parseUserInput( textValue, constraint->cadConstraintType(), ok );
1037 if ( ok )
1038 {
1039 constraint->setValue( value );
1040 }
1041 else
1042 {
1043 activate = false;
1044 }
1045 }
1046 else
1047 {
1048 activate = false;
1049 }
1050 }
1051 constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
1052
1053 if ( constraint == mXConstraint.get() )
1054 {
1055 emit lockXChanged( activate );
1056 }
1057 else if ( constraint == mYConstraint.get() )
1058 {
1059 emit lockYChanged( activate );
1060 }
1061 else if ( constraint == mZConstraint.get() )
1062 {
1063 emit lockZChanged( activate );
1064 }
1065 else if ( constraint == mMConstraint.get() )
1066 {
1067 emit lockMChanged( activate );
1068 }
1069 else if ( constraint == mDistanceConstraint.get() )
1070 {
1071 emit lockDistanceChanged( activate );
1072 }
1073 else if ( constraint == mAngleConstraint.get() )
1074 {
1075 emit lockAngleChanged( activate );
1076 }
1077
1078 if ( activate )
1079 {
1080 // deactivate perpendicular/parallel if angle has been activated
1081 if ( constraint == mAngleConstraint.get() )
1082 {
1083 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1084 }
1085
1086 // run a fake map mouse event to update the paint item
1087 emit pointChangedV2( mCadPointList.value( 0 ) );
1088 }
1089}
1090
1091void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
1092{
1093 CadConstraint *constraint = objectToConstraint( sender() );
1094 if ( !constraint )
1095 {
1096 return;
1097 }
1098
1099 updateConstraintValue( constraint, textValue, false );
1100}
1101
1102void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
1103{
1104 QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
1105 if ( !lineEdit )
1106 return;
1107
1108 CadConstraint *constraint = objectToConstraint( lineEdit );
1109 if ( !constraint )
1110 {
1111 return;
1112 }
1113
1114 updateConstraintValue( constraint, lineEdit->text(), true );
1115}
1116
1117void QgsAdvancedDigitizingDockWidget::lockBetweenLineConstraint( Qgis::BetweenLineConstraint constraint )
1118{
1119 mBetweenLineConstraint = constraint;
1120 mPerpendicularAction->setChecked( constraint == Qgis::BetweenLineConstraint::Perpendicular );
1121 mParallelAction->setChecked( constraint == Qgis::BetweenLineConstraint::Parallel );
1122}
1123
1124void QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint( bool activate /* default true */ )
1125{
1126 CadConstraint *constraint = objectToConstraint( sender() );
1127 if ( !constraint )
1128 {
1129 return;
1130 }
1131
1132 constraint->setLockMode( activate ? CadConstraint::SoftLock : CadConstraint::NoLock );
1133
1134 if ( constraint == mXyVertexConstraint.get() )
1135 {
1136 emit softLockXyChanged( activate );
1137 }
1138 else if ( constraint == mLineExtensionConstraint.get() )
1139 {
1140 emit softLockLineExtensionChanged( activate );
1141 }
1142
1143 if ( activate )
1144 {
1145 // run a fake map mouse event to update the paint item
1146 emit pointChangedV2( mCadPointList.value( 0 ) );
1147 }
1148
1149 clearLockedSnapVertices( false );
1150}
1151
1152void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
1153{
1154 CadCapacities newCapacities = CadCapacities();
1155 const bool isGeographic = mMapCanvas->mapSettings().destinationCrs().isGeographic();
1156
1157 // first point is the mouse point (it doesn't count)
1158 if ( mCadPointList.count() > 1 )
1159 {
1160 newCapacities |= RelativeCoordinates;
1161 if ( !isGeographic )
1162 {
1163 newCapacities |= AbsoluteAngle;
1164 newCapacities |= Distance;
1165 }
1166 }
1167 if ( mCadPointList.count() > 2 )
1168 {
1169 if ( !isGeographic )
1170 newCapacities |= RelativeAngle;
1171 }
1172 if ( !updateUIwithoutChange && newCapacities == mCapacities )
1173 {
1174 return;
1175 }
1176
1177 const bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
1178
1179 // update the UI according to new capacities
1180 // still keep the old to compare
1181
1182 const bool distance = mCadEnabled && newCapacities.testFlag( Distance );
1183 const bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
1184 const bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
1185 const bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
1186
1187 mPerpendicularAction->setEnabled( distance && snappingEnabled );
1188 mParallelAction->setEnabled( distance && snappingEnabled );
1189
1190 mLineExtensionAction->setEnabled( snappingEnabled );
1191 mXyVertexAction->setEnabled( snappingEnabled );
1192 clearLockedSnapVertices( false );
1193
1194 //update tooltips on buttons
1195 if ( !snappingEnabled )
1196 {
1197 mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode." ) );
1198 mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode." ) );
1199 mLineExtensionAction->setToolTip( tr( "Snapping must be enabled to utilize line extension mode." ) );
1200 mXyVertexAction->setToolTip( tr( "Snapping must be enabled to utilize xy point mode." ) );
1201 }
1202 else if ( mCadPointList.count() <= 1 )
1203 {
1204 mPerpendicularAction->setToolTip( tr( "A first vertex should be drawn to utilize perpendicular mode." ) );
1205 mParallelAction->setToolTip( tr( "A first vertex should be drawn to utilize parallel mode." ) );
1206 }
1207 else if ( isGeographic )
1208 {
1209 mPerpendicularAction->setToolTip( tr( "Perpendicular mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1210 mParallelAction->setToolTip( tr( "Parallel mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1211 }
1212 else
1213 {
1214 mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
1215 mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
1216 }
1217
1218
1219 if ( !absoluteAngle )
1220 {
1221 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1222 }
1223
1224 // absolute angle = azimuth, relative = from previous line
1225 mLockAngleButton->setEnabled( absoluteAngle );
1226 mRelativeAngleButton->setEnabled( relativeAngle );
1227 mAngleLineEdit->setEnabled( absoluteAngle );
1228 emit enabledChangedAngle( absoluteAngle );
1229 if ( !absoluteAngle )
1230 {
1231 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1232 }
1233 if ( !relativeAngle )
1234 {
1235 mAngleConstraint->setRelative( false );
1236 emit relativeAngleChanged( false );
1237 }
1238 else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
1239 {
1240 // set angle mode to relative if can do and wasn't available before
1241 mAngleConstraint->setRelative( true );
1242 emit relativeAngleChanged( true );
1243 }
1244
1245 // distance is always relative
1246 mLockDistanceButton->setEnabled( distance && relativeCoordinates );
1247 mDistanceLineEdit->setEnabled( distance && relativeCoordinates );
1248 emit enabledChangedDistance( distance && relativeCoordinates );
1249 if ( !( distance && relativeCoordinates ) )
1250 {
1251 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
1252 }
1253
1254 mRelativeXButton->setEnabled( relativeCoordinates );
1255 mRelativeYButton->setEnabled( relativeCoordinates );
1256 mRelativeZButton->setEnabled( relativeCoordinates );
1257 mRelativeMButton->setEnabled( relativeCoordinates );
1258
1259 // update capacities
1260 mCapacities = newCapacities;
1261 mCadPaintItem->updatePosition();
1262}
1263
1264
1266{
1268 constr.locked = c->isLocked();
1269 constr.relative = c->relative();
1270 constr.value = c->value();
1271 return constr;
1272}
1273
1274void QgsAdvancedDigitizingDockWidget::toggleLockedSnapVertex( const QgsPointLocator::Match &snapMatch, QgsPointLocator::Match previouslySnap )
1275{
1276 // do nothing if not activated
1277 if ( !mLineExtensionConstraint->isLocked() && !mXyVertexConstraint->isLocked() )
1278 {
1279 return;
1280 }
1281
1282 // if the first is same actual, not toggle if previously snapped
1283 const int lastIndex = mLockedSnapVertices.length() - 1;
1284 for ( int i = lastIndex ; i >= 0; --i )
1285 {
1286 if ( mLockedSnapVertices[i].point() == snapMatch.point() )
1287 {
1288 if ( snapMatch.point() != previouslySnap.point() )
1289 {
1290 mLockedSnapVertices.removeAt( i );
1291 }
1292 return;
1293 }
1294 }
1295
1296 if ( snapMatch.point() != previouslySnap.point() )
1297 {
1298 mLockedSnapVertices.enqueue( snapMatch );
1299 }
1300
1301 if ( mLockedSnapVertices.count() > 3 )
1302 {
1303 mLockedSnapVertices.dequeue();
1304 }
1305}
1306
1308{
1310 context.snappingUtils = mMapCanvas->snappingUtils();
1311 context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
1312 context.xConstraint = _constraint( mXConstraint.get() );
1313 context.yConstraint = _constraint( mYConstraint.get() );
1314 context.zConstraint = _constraint( mZConstraint.get() );
1315 context.mConstraint = _constraint( mMConstraint.get() );
1316 context.distanceConstraint = _constraint( mDistanceConstraint.get() );
1317 context.angleConstraint = _constraint( mAngleConstraint.get() );
1318 context.snappingToFeaturesOverridesCommonAngle = mSnappingPrioritizeFeatures;
1319
1320 context.lineExtensionConstraint = _constraint( mLineExtensionConstraint.get() );
1321 context.xyVertexConstraint = _constraint( mXyVertexConstraint.get() );
1322
1323 context.setCadPoints( mCadPointList );
1324 context.setLockedSnapVertices( mLockedSnapVertices );
1325
1327 {
1328 context.snappingUtils->addExtraSnapLayer( mConstructionGuidesLayer.get() );
1329 }
1330
1333 context.commonAngleConstraint.value = mCommonAngleConstraint;
1334
1336
1337 const bool res = output.valid;
1338 QgsPoint point = pointXYToPoint( output.finalMapPoint );
1339 mSnappedSegment.clear();
1340 if ( output.snapMatch.hasEdge() )
1341 {
1342 QgsPointXY edgePt0, edgePt1;
1343 output.snapMatch.edgePoints( edgePt0, edgePt1 );
1344 mSnappedSegment << edgePt0 << edgePt1;
1345 }
1346 if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
1347 {
1348 if ( output.softLockCommonAngle != -1 )
1349 {
1350 mAngleConstraint->setLockMode( CadConstraint::SoftLock );
1351 mAngleConstraint->setValue( output.softLockCommonAngle );
1352 }
1353 else
1354 {
1355 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1356 }
1357 }
1358
1359 mSoftLockLineExtension = output.softLockLineExtension;
1360 mSoftLockX = output.softLockX;
1361 mSoftLockY = output.softLockY;
1362
1363 if ( output.snapMatch.isValid() )
1364 {
1365 mSnapIndicator->setMatch( output.snapMatch );
1366 mSnapIndicator->setVisible( true );
1367 }
1368 else
1369 {
1370 mSnapIndicator->setVisible( false );
1371 }
1372
1373 /*
1374 * Ensure that Z and M are passed
1375 * It will be dropped as needed later.
1376 */
1379
1380 /*
1381 * Constraints are applied in 2D, they are always called when using the tool
1382 * but they do not take into account if when you snap on a vertex it has
1383 * a Z value.
1384 * To get the value we use the snapPoint method. However, we only apply it
1385 * when the snapped point corresponds to the constrained point or on an edge
1386 * if the topological editing is activated. Also, we don't apply it if
1387 * the point is not linked to a layer.
1388 */
1389 e->setMapPoint( point );
1390
1391 mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
1392 if ( mSnapMatch.layer() )
1393 {
1394 // note ND: I'm not 100% sure if the point == mSnapMatch.point() comparison was intended be done using QgsPointXY or QgsPoint objects here!
1395 // I'm using QgsPointXY here to keep the behavior the same from before a duplicate QgsPointXY == operator was removed...
1396 if ( ( ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() ) && ( QgsPointXY( point ) == mSnapMatch.point() ) )
1397 || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
1398 {
1399 e->snapPoint();
1400 point = mSnapMatch.interpolatedPoint( mMapCanvas->mapSettings().destinationCrs() );
1401 }
1402 }
1403
1404 context.snappingUtils->removeExtraSnapLayer( mConstructionGuidesLayer.get() );
1405
1406 if ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() )
1407 {
1408 toggleLockedSnapVertex( mSnapMatch, mLastSnapMatch );
1409 mLastSnapMatch = mSnapMatch;
1410 }
1411 else
1412 {
1413 mLastSnapMatch = QgsPointLocator::Match();
1414 }
1415
1416 /*
1417 * And if M or Z lock button is activated get the value of the input.
1418 */
1419 if ( mLockZButton->isChecked() )
1420 {
1421 point.setZ( QLocale().toDouble( mZLineEdit->text() ) );
1422 }
1423 if ( mLockMButton->isChecked() )
1424 {
1425 point.setM( QLocale().toDouble( mMLineEdit->text() ) );
1426 }
1427
1428 // update the point list
1429 updateCurrentPoint( point );
1430
1431 updateUnlockedConstraintValues( point );
1432
1433 if ( res )
1434 {
1435 emit popWarning();
1436 }
1437 else
1438 {
1439 emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
1440 }
1441
1442 return res;
1443}
1444
1445
1446void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPoint &point )
1447{
1448 bool previousPointExist, penulPointExist;
1449 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1450 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1451
1452 // --- angle
1453 if ( !mAngleConstraint->isLocked() && previousPointExist )
1454 {
1455 double prevAngle = 0.0;
1456
1457 if ( penulPointExist && mAngleConstraint->relative() )
1458 {
1459 // previous angle
1460 prevAngle = std::atan2( previousPt.y() - penultimatePt.y(),
1461 previousPt.x() - penultimatePt.x() ) * 180 / M_PI;
1462 }
1463
1464 const double xAngle { std::atan2( point.y() - previousPt.y(),
1465 point.x() - previousPt.x() ) * 180 / M_PI };
1466
1467 // Modulus
1468 const double angle = std::fmod( xAngle - prevAngle, 360.0 );
1469 mAngleConstraint->setValue( angle );
1470
1471 // Bearing (azimuth)
1472 double bearing { std::fmod( xAngle, 360.0 ) };
1473 bearing = bearing <= 90.0 ? 90.0 - bearing : ( bearing > 90 ? 270.0 + 180.0 - bearing : 270.0 - bearing );
1474 const QgsNumericFormatContext context;
1475 const QString bearingText { QgsProject::instance()->displaySettings()->bearingFormat()->formatDouble( bearing, context ) };
1476 emit valueBearingChanged( bearingText );
1477
1478 }
1479 // --- distance
1480 if ( !mDistanceConstraint->isLocked() && previousPointExist )
1481 {
1482 mDistanceConstraint->setValue( std::sqrt( previousPt.distanceSquared( point ) ) );
1483 }
1484 // --- X
1485 if ( !mXConstraint->isLocked() )
1486 {
1487 if ( previousPointExist && mXConstraint->relative() )
1488 {
1489 mXConstraint->setValue( point.x() - previousPt.x() );
1490 }
1491 else
1492 {
1493 mXConstraint->setValue( point.x() );
1494 }
1495 }
1496 // --- Y
1497 if ( !mYConstraint->isLocked() )
1498 {
1499 if ( previousPointExist && mYConstraint->relative() )
1500 {
1501 mYConstraint->setValue( point.y() - previousPt.y() );
1502 }
1503 else
1504 {
1505 mYConstraint->setValue( point.y() );
1506 }
1507 }
1508 // --- Z
1509 if ( !mZConstraint->isLocked() )
1510 {
1511 if ( previousPointExist && mZConstraint->relative() )
1512 {
1513 mZConstraint->setValue( point.z() - previousPt.z() );
1514 }
1515 else
1516 {
1517 mZConstraint->setValue( point.z() );
1518 }
1519 }
1520 // --- M
1521 if ( !mMConstraint->isLocked() )
1522 {
1523 if ( previousPointExist && mMConstraint->relative() )
1524 {
1525 mMConstraint->setValue( point.m() - previousPt.m() );
1526 }
1527 else
1528 {
1529 mMConstraint->setValue( point.m() );
1530 }
1531 }
1532}
1533
1534
1535QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
1536{
1537 QList<QgsPointXY> segment;
1538 QgsPointXY pt1, pt2;
1540
1541 QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
1542
1543 const QgsSnappingConfig canvasConfig = snappingUtils->config();
1544 QgsSnappingConfig localConfig = snappingUtils->config();
1545
1548 snappingUtils->setConfig( localConfig );
1549
1550 match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
1551
1552 snappingUtils->setConfig( canvasConfig );
1553
1554 if ( match.isValid() && match.hasEdge() )
1555 {
1556 match.edgePoints( pt1, pt2 );
1557 segment << pt1 << pt2;
1558 }
1559
1560 if ( snapped )
1561 {
1562 *snapped = segment.count() == 2;
1563 }
1564
1565 return segment;
1566}
1567
1569{
1570 if ( mCurrentTool )
1571 {
1572 mCurrentTool->canvasPressEvent( event );
1573 }
1574
1575 if ( constructionMode() )
1576 {
1577 event->setAccepted( false );
1578 }
1579}
1580
1582{
1583 // perpendicular/parallel constraint
1584 // do a soft lock when snapping to a segment
1586
1587 if ( mCurrentTool )
1588 {
1589 mCurrentTool->canvasMoveEvent( event );
1590 }
1591
1593}
1594
1596{
1597 if ( event->button() == Qt::RightButton )
1598 {
1599 if ( mCurrentTool )
1600 {
1601 mCurrentTool->canvasReleaseEvent( event );
1602 if ( !event->isAccepted() )
1603 {
1604 return;
1605 }
1606 }
1607 clear();
1608 }
1609 else
1610 {
1611 applyConstraints( event ); // updates event's map point
1612 if ( alignToSegment( event ) )
1613 {
1614 event->setAccepted( false );
1615 return;
1616 }
1617
1618 if ( mCurrentTool )
1619 {
1620 mCurrentTool->canvasReleaseEvent( event );
1621 if ( !event->isAccepted() )
1622 {
1623 return;
1624 }
1625 else
1626 {
1627 // update the point list
1628 QgsPoint point( event->mapPoint() );
1631
1632 if ( mLockZButton->isChecked() )
1633 {
1634 point.setZ( QLocale().toDouble( mZLineEdit->text() ) );
1635 }
1636 if ( mLockMButton->isChecked() )
1637 {
1638 point.setM( QLocale().toDouble( mMLineEdit->text() ) );
1639 }
1640 updateCurrentPoint( point );
1641 }
1642 }
1643
1644 addPoint( event->mapPoint() );
1645 releaseLocks( false );
1646
1647 if ( constructionMode() )
1648 {
1649 event->setAccepted( false );
1650 }
1651 }
1652}
1653
1655{
1656 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::NoConstraint )
1657 {
1658 return false;
1659 }
1660
1661 bool previousPointExist, penulPointExist, snappedSegmentExist;
1662 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1663 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1664 mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
1665
1666 if ( !previousPointExist || !snappedSegmentExist )
1667 {
1668 return false;
1669 }
1670
1671 double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
1672
1673 if ( mAngleConstraint->relative() && penulPointExist )
1674 {
1675 angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
1676 }
1677
1678 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::Perpendicular )
1679 {
1680 angle += M_PI_2;
1681 }
1682
1683 angle *= 180 / M_PI;
1684
1685 mAngleConstraint->setValue( angle );
1686 mAngleConstraint->setLockMode( lockMode );
1687 if ( lockMode == CadConstraint::HardLock )
1688 {
1689 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
1690 }
1691
1692 return true;
1693}
1694
1696{
1697 // event on map tool
1698
1699 if ( !mCadEnabled )
1700 return false;
1701
1702 switch ( e->key() )
1703 {
1704 case Qt::Key_Backspace:
1705 case Qt::Key_Delete:
1706 {
1708 releaseLocks( false );
1709 break;
1710 }
1711 case Qt::Key_Escape:
1712 {
1713 releaseLocks();
1714 break;
1715 }
1716 default:
1717 {
1718 keyPressEvent( e );
1719 break;
1720 }
1721 }
1722 // for map tools, continues with key press in any case
1723 return false;
1724}
1725
1727{
1728 if ( mCurrentTool )
1729 {
1730 mCurrentTool->deleteLater();
1731 }
1732
1733 if ( !mConstructionGuideLine.isEmpty() )
1734 {
1735 mConstructionGuideLine.clear();
1736 }
1737
1738 clearPoints();
1739 releaseLocks();
1740}
1741
1743{
1744 // event on dock (this)
1745
1746 if ( !mCadEnabled )
1747 return;
1748
1749 switch ( e->key() )
1750 {
1751 case Qt::Key_Backspace:
1752 case Qt::Key_Delete:
1753 {
1755 releaseLocks( false );
1756 break;
1757 }
1758 case Qt::Key_Escape:
1759 {
1760 releaseLocks();
1761
1762 if ( mConstructionGuideLine.numPoints() >= 2 )
1763 {
1764 mConstructionGuidesLayer->dataProvider()->deleteFeatures( QgsFeatureIds() << mConstructionGuideId );
1765 mConstructionGuideLine.clear();
1766 }
1767
1768 if ( mCurrentTool )
1769 {
1770 mCurrentTool->deleteLater();
1771 }
1772
1773 break;
1774 }
1775 default:
1776 {
1777 filterKeyPress( e );
1778 break;
1779 }
1780 }
1781}
1782
1783void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
1784{
1785 clearPoints();
1786 const auto constPoints = points;
1787 for ( const QgsPointXY &pt : constPoints )
1788 {
1789 addPoint( pt );
1790 }
1791}
1792
1794{
1795 mDistanceConstraint->toggleLocked();
1796 emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1797 emit pointChangedV2( mCadPointList.value( 0 ) );
1798}
1799
1800bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
1801{
1802 if ( !cadEnabled() )
1803 {
1804 return QgsDockWidget::eventFilter( obj, event );
1805 }
1806
1807 // event for line edits and map canvas
1808 // we have to catch both KeyPress events and ShortcutOverride events. This is because
1809 // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
1810 // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
1811 // us to intercept these keystrokes before they are caught by the global shortcuts
1812 if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
1813 {
1814 if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
1815 {
1816 return filterKeyPress( keyEvent );
1817 }
1818 }
1819 return QgsDockWidget::eventFilter( obj, event );
1820}
1821
1822bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
1823{
1824 // we need to be careful here -- because this method is called on both KeyPress events AND
1825 // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
1826 // these event types for a single key press. I.e. pressing "A" may first call trigger a
1827 // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
1828 const QEvent::Type type = e->type();
1829 switch ( e->key() )
1830 {
1831 case Qt::Key_Escape:
1832 {
1833 if ( type == QEvent::KeyPress && mCurrentTool )
1834 {
1835 mCurrentTool->deleteLater();
1836 }
1837 else if ( type == QEvent::KeyPress && mConstructionMode && mConstructionGuideLine.numPoints() >= 2 )
1838 {
1839 mConstructionGuidesLayer->dataProvider()->deleteFeatures( QgsFeatureIds() << mConstructionGuideId );
1840 mConstructionGuideLine.clear();
1841
1842 if ( mCadPointList.size() > 1 )
1843 {
1844 mConstructionGuideLine.addVertex( mCadPointList.at( 1 ) );
1845 }
1846
1848 e->accept();
1849 }
1850 else
1851 {
1852 e->ignore();
1853 }
1854 break;
1855 }
1856 case Qt::Key_X:
1857 {
1858 // modifier+x ONLY caught for ShortcutOverride events...
1859 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1860 {
1861 mXConstraint->toggleLocked();
1862 emit lockXChanged( mXConstraint->isLocked() );
1863 emit pointChangedV2( mCadPointList.value( 0 ) );
1864 e->accept();
1865 }
1866 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1867 {
1868 if ( mCapacities.testFlag( RelativeCoordinates ) )
1869 {
1870 mXConstraint->toggleRelative();
1871 emit relativeXChanged( mXConstraint->relative() );
1872 emit pointChangedV2( mCadPointList.value( 0 ) );
1873 e->accept();
1874 }
1875 }
1876 // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
1877 else if ( type == QEvent::KeyPress )
1878 {
1879 mXLineEdit->setFocus();
1880 mXLineEdit->selectAll();
1881 emit focusOnXRequested();
1882 e->accept();
1883 }
1884 break;
1885 }
1886 case Qt::Key_Y:
1887 {
1888 // modifier+y ONLY caught for ShortcutOverride events...
1889 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1890 {
1891 mYConstraint->toggleLocked();
1892 emit lockYChanged( mYConstraint->isLocked() );
1893 emit pointChangedV2( mCadPointList.value( 0 ) );
1894 e->accept();
1895 }
1896 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1897 {
1898 if ( mCapacities.testFlag( RelativeCoordinates ) )
1899 {
1900 mYConstraint->toggleRelative();
1901 emit relativeYChanged( mYConstraint->relative() );
1902 emit pointChangedV2( mCadPointList.value( 0 ) );
1903 e->accept();
1904 }
1905 }
1906 // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1907 else if ( type == QEvent::KeyPress )
1908 {
1909 mYLineEdit->setFocus();
1910 mYLineEdit->selectAll();
1911 emit focusOnYRequested();
1912 e->accept();
1913 }
1914 break;
1915 }
1916 case Qt::Key_Z:
1917 {
1918 // modifier+z ONLY caught for ShortcutOverride events...
1919 if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::AltModifier )
1920 {
1921 mZConstraint->toggleLocked();
1922 emit lockZChanged( mZConstraint->isLocked() );
1923 emit pointChangedV2( mCadPointList.value( 0 ) );
1924 e->accept();
1925 }
1926 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1927 {
1928 if ( mCapacities.testFlag( RelativeCoordinates ) )
1929 {
1930 mZConstraint->toggleRelative();
1931 emit relativeZChanged( mZConstraint->relative() );
1932 emit pointChangedV2( mCadPointList.value( 0 ) );
1933 e->accept();
1934 }
1935 }
1936 // .. but "z" alone ONLY caught for KeyPress events (see comment at start of function)
1937 else if ( type == QEvent::KeyPress )
1938 {
1939 mZLineEdit->setFocus();
1940 mZLineEdit->selectAll();
1941 emit focusOnZRequested();
1942 e->accept();
1943 }
1944 break;
1945 }
1946 case Qt::Key_M:
1947 {
1948 // modifier+m ONLY caught for ShortcutOverride events...
1949 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1950 {
1951 mMConstraint->toggleLocked();
1952 emit lockMChanged( mMConstraint->isLocked() );
1953 emit pointChangedV2( mCadPointList.value( 0 ) );
1954 e->accept();
1955 }
1956 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1957 {
1958 if ( mCapacities.testFlag( RelativeCoordinates ) )
1959 {
1960 mMConstraint->toggleRelative();
1961 emit relativeMChanged( mMConstraint->relative() );
1962 emit pointChangedV2( mCadPointList.value( 0 ) );
1963 e->accept();
1964 }
1965 }
1966 // .. but "m" alone ONLY caught for KeyPress events (see comment at start of function)
1967 else if ( type == QEvent::KeyPress )
1968 {
1969 mMLineEdit->setFocus();
1970 mMLineEdit->selectAll();
1971 emit focusOnMRequested();
1972 e->accept();
1973 }
1974 break;
1975 }
1976 case Qt::Key_A:
1977 {
1978 // modifier+a ONLY caught for ShortcutOverride events...
1979 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1980 {
1981 if ( mCapacities.testFlag( AbsoluteAngle ) )
1982 {
1983 mAngleConstraint->toggleLocked();
1984 emit lockAngleChanged( mAngleConstraint->isLocked() );
1985 emit pointChangedV2( mCadPointList.value( 0 ) );
1986 e->accept();
1987 }
1988 }
1989 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1990 {
1991 if ( mCapacities.testFlag( RelativeAngle ) )
1992 {
1993 mAngleConstraint->toggleRelative();
1994 emit relativeAngleChanged( mAngleConstraint->relative() );
1995 emit pointChangedV2( mCadPointList.value( 0 ) );
1996 e->accept();
1997 }
1998 }
1999 // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
2000 else if ( type == QEvent::KeyPress )
2001 {
2002 mAngleLineEdit->setFocus();
2003 mAngleLineEdit->selectAll();
2004 emit focusOnAngleRequested();
2005 e->accept();
2006 }
2007 break;
2008 }
2009 case Qt::Key_D:
2010 {
2011 // modifier+d ONLY caught for ShortcutOverride events...
2012 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
2013 {
2014 if ( mCapacities.testFlag( RelativeCoordinates ) && mCapacities.testFlag( Distance ) )
2015 {
2017 e->accept();
2018 }
2019 }
2020 // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
2021 else if ( type == QEvent::KeyPress )
2022 {
2023 mDistanceLineEdit->setFocus();
2024 mDistanceLineEdit->selectAll();
2026 e->accept();
2027 }
2028 break;
2029 }
2030 case Qt::Key_C:
2031 {
2032 if ( type == QEvent::KeyPress )
2033 {
2034 setConstructionMode( !mConstructionMode );
2035 e->accept();
2036 }
2037 break;
2038 }
2039 case Qt::Key_P:
2040 {
2041 if ( type == QEvent::KeyPress )
2042 {
2043 const bool parallel = mParallelAction->isChecked();
2044 const bool perpendicular = mPerpendicularAction->isChecked();
2045
2046 if ( !parallel && !perpendicular )
2047 {
2048 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
2049 }
2050 else if ( perpendicular )
2051 {
2052 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
2053 }
2054 else
2055 {
2056 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
2057 }
2058 e->accept();
2059
2060 // run a fake map mouse event to update the paint item
2061 emit pointChangedV2( mCadPointList.value( 0 ) );
2062 }
2063 break;
2064 }
2065 case Qt::Key_N:
2066 {
2067 if ( type == QEvent::ShortcutOverride )
2068 {
2069 const QList<double> constActionKeys { mCommonAngleActions.keys() };
2070 const int currentAngleActionIndex { static_cast<int>( constActionKeys .indexOf( mCommonAngleConstraint ) ) };
2071 const QList<QAction *> constActions { mCommonAngleActions.values( ) };
2072 QAction *nextAngleAction;
2073 if ( e->modifiers() == Qt::ShiftModifier )
2074 {
2075 nextAngleAction = currentAngleActionIndex == 0 ? constActions.last() : constActions.at( currentAngleActionIndex - 1 );
2076 }
2077 else
2078 {
2079 nextAngleAction = currentAngleActionIndex == constActions.count() - 1 ? constActions.first() : constActions.at( currentAngleActionIndex + 1 );
2080 }
2081 nextAngleAction->trigger();
2082 e->accept();
2083 }
2084 break;
2085 }
2086 default:
2087 {
2088 return false; // continues
2089 }
2090 }
2091 return e->isAccepted();
2092}
2093
2095{
2096 // most of theses lines can be moved to updateCapacity
2097 connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
2098 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
2099 {
2100 mAngleLineEdit->setToolTip( tr( "Angle constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
2101 mDistanceLineEdit->setToolTip( tr( "Distance constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
2102
2103 mLabelX->setText( tr( "Long" ) );
2104 mLabelY->setText( tr( "Lat" ) );
2105
2106 mXConstraint->setPrecision( 8 );
2107 mYConstraint->setPrecision( 8 );
2108 }
2109 else
2110 {
2111 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
2112 mAngleLineEdit->setToolTip( QString() );
2113
2114 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
2115
2116 mLabelX->setText( tr( "x" ) );
2117 mLabelY->setText( tr( "y" ) );
2118
2119 mXConstraint->setPrecision( 6 );
2120 mYConstraint->setPrecision( 6 );
2121 }
2122
2123 updateCapacity();
2124
2125 mEnableAction->setEnabled( true );
2126 mErrorLabel->hide();
2127 mCadWidget->show();
2128
2129 mCurrentMapToolSupportsCad = true;
2130
2131 if ( mSessionActive && !isVisible() )
2132 {
2133 show();
2134 }
2135
2136 setCadEnabled( mSessionActive );
2137
2138 if ( !mConstructionGuidesLayer )
2139 {
2140 resetConstructionGuides();
2141 }
2142
2143 if ( mDeferredUpdateConstructionGuidesCrs )
2144 {
2145 updateConstructionGuidesCrs();
2146 }
2147
2149}
2150
2152{
2154
2155 mEnableAction->setEnabled( false );
2156 mErrorLabel->setText( tr( "Advanced digitizing tools are not enabled for the current map tool" ) );
2157 mErrorLabel->show();
2158 mCadWidget->hide();
2159
2160 mCurrentMapToolSupportsCad = false;
2161
2162 mSnapIndicator->setVisible( false );
2163
2164 setCadEnabled( false );
2165}
2166
2168{
2169 mCadPaintItem->update();
2170}
2171
2173{
2174 if ( !force && ( mLineExtensionConstraint->isLocked() || mXyVertexConstraint->isLocked() ) )
2175 {
2176 return;
2177 }
2178
2179 mLockedSnapVertices.clear();
2180}
2181
2183{
2184 QgsPoint pt = pointXYToPoint( point );
2185 if ( !pointsCount() )
2186 {
2187 mCadPointList << pt;
2188 }
2189 else
2190 {
2191 mCadPointList.insert( 0, pt );
2192 }
2193
2195 {
2196 if ( constructionMode() )
2197 {
2198 mConstructionGuideLine.addVertex( pt );
2199
2200 if ( mConstructionGuideLine.numPoints() == 2 )
2201 {
2202 QgsFeature feature;
2203 QgsGeometry geom( mConstructionGuideLine.clone() );
2204 feature.setGeometry( geom );
2205 mConstructionGuidesLayer->dataProvider()->addFeature( feature );
2206 mConstructionGuideId = feature.id();
2207 }
2208 else if ( mConstructionGuideLine.numPoints() > 2 )
2209 {
2210 QgsGeometry geom( mConstructionGuideLine.clone() );
2211 mConstructionGuidesLayer->dataProvider()->changeGeometryValues( { { mConstructionGuideId, geom } } );
2212 }
2213 }
2214 else
2215 {
2216 if ( !mConstructionGuideLine.isEmpty() )
2217 {
2218 mConstructionGuideLine.addVertex( pt );
2219
2220 QgsGeometry geom( mConstructionGuideLine.clone() );
2221 mConstructionGuidesLayer->dataProvider()->changeGeometryValues( { { mConstructionGuideId, geom } } );
2222 mConstructionGuideLine.clear();
2223 }
2224 }
2225 }
2226
2227 updateCapacity();
2229}
2230
2232{
2233 if ( !pointsCount() )
2234 return;
2235
2236 const int i = pointsCount() > 1 ? 1 : 0;
2237 mCadPointList.removeAt( i );
2238 updateCapacity();
2240}
2241
2243{
2244 mCadPointList.clear();
2245 mSnappedSegment.clear();
2246
2247 updateCapacity();
2249}
2250
2252{
2253 if ( !pointsCount() )
2254 {
2255 mCadPointList << point;
2256 updateCapacity();
2257 }
2258 else
2259 {
2260 mCadPointList[0] = point;
2261 }
2263}
2264
2266{
2267 if ( mode == mLockMode )
2268 {
2269 return;
2270 }
2271 mLockMode = mode;
2272 mLockerButton->setChecked( mode == HardLock );
2273 if ( mRepeatingLockButton )
2274 {
2275 if ( mode == HardLock )
2276 {
2277 mRepeatingLockButton->setEnabled( true );
2278 }
2279 else
2280 {
2281 mRepeatingLockButton->setChecked( false );
2282 mRepeatingLockButton->setEnabled( false );
2283 }
2284 }
2285
2286 if ( mode == NoLock )
2287 {
2288 mLineEdit->clear();
2289 }
2290
2291}
2292
2294{
2295 mRepeatingLock = repeating;
2296 if ( mRepeatingLockButton )
2297 mRepeatingLockButton->setChecked( repeating );
2298}
2299
2301{
2302 mRelative = relative;
2303 if ( mRelativeButton )
2304 {
2305 mRelativeButton->setChecked( relative );
2306 }
2307}
2308
2310{
2311 mValue = value;
2312 if ( updateWidget && mLineEdit->isEnabled() )
2313 mLineEdit->setText( displayValue() );
2314}
2315
2317{
2318 switch ( mCadConstraintType )
2319 {
2321 {
2322 return QLocale().toString( mValue, 'f', mPrecision ).append( tr( " °" ) );
2323 }
2326 {
2327 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
2328 {
2329 return QLocale().toString( mValue, 'f', mPrecision ).append( tr( " °" ) );
2330 }
2331 else
2332 {
2333 return QLocale().toString( mValue, 'f', mPrecision );
2334 }
2335 }
2337 {
2338 const Qgis::DistanceUnit displayUnits { QgsProject::instance()->distanceUnits() };
2339 // Convert from canvas units
2340 const Qgis::DistanceUnit canvasUnits { mMapCanvas->mapSettings().mapUnits() };
2341 const double value { QgsUnitTypes::fromUnitToUnitFactor( canvasUnits, displayUnits ) *mValue };
2342 return QgsDistanceArea::formatDistance( value, mPrecision, displayUnits, true );
2343 }
2347 default:
2348 break;
2349 }
2350 return QLocale().toString( mValue, 'f', mPrecision );
2351}
2352
2354{
2355 setLockMode( mLockMode == HardLock ? NoLock : HardLock );
2356}
2357
2359{
2360 setRelative( !mRelative );
2361}
2362
2364{
2365 mPrecision = precision;
2366 if ( mLineEdit->isEnabled() )
2367 mLineEdit->setText( displayValue() );
2368}
2369
2374
2376{
2377 mCadConstraintType = constraintType;
2378}
2379
2381{
2382 mMapCanvas = mapCanvas;
2383}
2384
2386{
2387 QString value { text.trimmed() };
2388 switch ( constraintType )
2389 {
2391 {
2392 // Remove distance unit suffix
2393 const QString distanceUnit { QgsUnitTypes::toAbbreviatedString( QgsProject::instance()->distanceUnits() ) };
2394 if ( value.endsWith( distanceUnit ) )
2395 {
2396 value.chop( distanceUnit.length() );
2397 }
2398 break;
2399 }
2401 {
2402 // Remove angle suffix
2403 const QString angleUnit { tr( "°" ) };
2404 if ( value.endsWith( angleUnit ) )
2405 {
2406 value.chop( angleUnit.length() );
2407 }
2408 break;
2409 }
2410 default:
2411 break;
2412 }
2413 return value.trimmed();
2414}
2415
2417{
2418 if ( exist )
2419 *exist = pointsCount() > 0;
2420 if ( pointsCount() > 0 )
2421 return mCadPointList.value( 0 );
2422 else
2423 return QgsPoint();
2424}
2425
2427{
2428 if ( pointsCount() > 0 && layer )
2429 {
2430 QgsPoint res = mCadPointList.value( 0 );
2431 const QgsPointXY layerCoordinates = mMapCanvas->mapSettings().mapToLayerCoordinates( layer, res );
2432 res.setX( layerCoordinates.x() );
2433 res.setY( layerCoordinates.y() );
2434 return res;
2435 }
2436 return QgsPoint();
2437}
2438
2440{
2441 if ( exist )
2442 *exist = pointsCount() > 1;
2443 if ( pointsCount() > 1 )
2444 return mCadPointList.value( 1 );
2445 else
2446 return QgsPoint();
2447}
2448
2450{
2451 if ( exist )
2452 *exist = pointsCount() > 2;
2453 if ( pointsCount() > 2 )
2454 return mCadPointList.value( 2 );
2455 else
2456 return QgsPoint();
2457}
2458
2459QgsPoint QgsAdvancedDigitizingDockWidget::pointXYToPoint( const QgsPointXY &point ) const
2460{
2461 return QgsPoint( point.x(), point.y(), getLineZ(), getLineM() );
2462}
2463
2465{
2466 return mZLineEdit->isEnabled() ? QLocale().toDouble( mZLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
2467}
2468
2470{
2471 return mMLineEdit->isEnabled() ? QLocale().toDouble( mMLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
2472}
2473
2475{
2476 return mShowConstructionGuides ? mShowConstructionGuides->isChecked() : false;
2477}
2478
2480{
2481 return mSnapToConstructionGuides ? mShowConstructionGuides->isChecked() && mSnapToConstructionGuides->isChecked() : false;
2482}
2483
2485{
2486 return mRecordConstructionGuides ? mRecordConstructionGuides->isChecked() : false;
2487}
2488
2489void QgsAdvancedDigitizingDockWidget::updateConstructionGuidesCrs()
2490{
2491 if ( !mConstructionGuidesLayer )
2492 {
2493 return;
2494 }
2495
2496 if ( !cadEnabled() )
2497 {
2498 mDeferredUpdateConstructionGuidesCrs = true;
2499 }
2500
2501 QgsCoordinateTransform transform = QgsCoordinateTransform( mConstructionGuidesLayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
2502 mConstructionGuidesLayer->setCrs( mMapCanvas->mapSettings().destinationCrs() );
2503 QgsFeatureIterator it = mConstructionGuidesLayer->getFeatures( QgsFeatureRequest().setNoAttributes() );
2504 QgsFeature feature;
2505 while ( it.nextFeature( feature ) )
2506 {
2507 QgsGeometry geom = feature.geometry();
2508 geom.transform( transform );
2509 mConstructionGuidesLayer->dataProvider()->changeGeometryValues( { { feature.id(), geom } } );
2510 }
2511
2512 mDeferredUpdateConstructionGuidesCrs = false;
2513}
2514
2515void QgsAdvancedDigitizingDockWidget::resetConstructionGuides()
2516{
2517 if ( mConstructionGuidesLayer )
2518 {
2519 mConstructionGuidesLayer.reset();
2520 }
2521
2522 const QgsVectorLayer::LayerOptions options( QgsProject::instance()->transformContext(), false, false );
2523 mConstructionGuidesLayer = std::make_unique<QgsVectorLayer>( QStringLiteral( "LineString?crs=%1" ).arg( mMapCanvas->mapSettings().destinationCrs().authid() ),
2524 QStringLiteral( "constructionGuides" ),
2525 QStringLiteral( "memory" ),
2526 options );
2527}
@ Segment
On segments.
DistanceUnit
Units of distance.
Definition qgis.h:4669
CadConstraintType
Advanced digitizing constraint type.
Definition qgis.h:3783
@ Distance
Distance value.
@ Generic
Generic value.
@ YCoordinate
Y Coordinate value.
@ XCoordinate
X Coordinate value.
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
@ AllLayers
On all vector layers.
BetweenLineConstraint
Between line constraints which can be enabled.
Definition qgis.h:3757
@ NoConstraint
No additional constraint.
@ Perpendicular
Perpendicular.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:256
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (.
void updatePosition() override
called on changed extent or resize event to update position of the item
The CadConstraint is a class for all basic constraints (angle/distance/x/y).
Qgis::CadConstraintType cadConstraintType() const
Returns the constraint type.
void setPrecision(int precision)
Sets the numeric precision (decimal places) to show in the associated widget.
static QString removeSuffix(const QString &text, Qgis::CadConstraintType constraintType)
Removes unit suffix from the constraint text.
QString displayValue() const
Returns a localized formatted string representation of the value.
void setRepeatingLock(bool repeating)
Sets whether a repeating lock is set for the constraint.
void setCadConstraintType(Qgis::CadConstraintType constraintType)
Sets the constraint type to constraintType.
void setRelative(bool relative)
Set if the constraint should be treated relative.
void setValue(double value, bool updateWidget=true)
Set the value of the constraint.
void setMapCanvas(QgsMapCanvas *mapCanvas)
Sets the map canvas to mapCanvas.
void valueDistanceChanged(const QString &value)
Emitted whenever the distance value changes (either the mouse moved, or the user changed the input).
QgsAdvancedDigitizingDockWidget(QgsMapCanvas *canvas, QWidget *parent=nullptr, QgsUserInputWidget *userInputWidget=nullptr)
Create an advanced digitizing dock widget.
void lockZChanged(bool locked)
Emitted whenever the Z parameter is locked.
void setEnabledZ(bool enable)
Sets whether Z is enabled.
void setPoints(const QList< QgsPointXY > &points)
Configures list of current CAD points.
void setZ(const QString &value, WidgetSetMode mode)
Set the Z value on the widget.
void setY(const QString &value, WidgetSetMode mode)
Set the Y value on the widget.
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
bool applyConstraints(QgsMapMouseEvent *e)
apply the CAD constraints.
void setEnabledM(bool enable)
Sets whether M is enabled.
int pointsCount() const
The number of points in the CAD point helper list.
void addPoint(const QgsPointXY &point)
Adds point to the CAD point list.
void releaseLocks(bool releaseRepeatingLocks=true)
unlock all constraints
void switchZM()
Determines if Z or M will be enabled.
void relativeMChanged(bool relative)
Emitted whenever the M parameter is toggled between absolute and relative.
void lockXChanged(bool locked)
Emitted whenever the X parameter is locked.
void softLockLineExtensionChanged(bool locked)
Emitted whenever the soft line extension parameter is locked.
void focusOnXRequested()
Emitted whenever the X field should get the focus using the shortcuts (X).
bool constructionMode() const
Returns whether the construction mode is activated.
void setTool(QgsAdvancedDigitizingTool *tool)
Sets an advanced digitizing tool which will take over digitizing until the tool is close.
void valueYChanged(const QString &value)
Emitted whenever the Y value changes (either the mouse moved, or the user changed the input).
QString formatCommonAngleSnapping(double angle)
Returns the formatted label for common angle snapping option.
void focusOnYRequested()
Emitted whenever the Y field should get the focus using the shortcuts (Y).
double getLineM() const
Convenient method to get the M value from the line edit wiget.
void enabledChangedDistance(bool enabled)
Emitted whenever the distance field is enabled or disabled.
void valueZChanged(const QString &value)
Emitted whenever the Z value changes (either the mouse moved, or the user changed the input).
bool recordConstructionGuides() const
Returns whether construction guides are being recorded.
void clearPoints()
Removes all points from the CAD point list.
QgsPoint previousPointV2(bool *exists=nullptr) const
The previous point.
void lockAngleChanged(bool locked)
Emitted whenever the angle parameter is locked.
void processCanvasReleaseEvent(QgsMapMouseEvent *event)
Processes the canvas release event.
void updateCadPaintItem()
Updates canvas item that displays constraints on the ma.
void removePreviousPoint()
Removes previous point in the CAD point list.
QgsPoint penultimatePointV2(bool *exists=nullptr) const
The penultimate point.
void pointChangedV2(const QgsPoint &point)
Sometimes a constraint may change the current point out of a mouse event.
void processCanvasPressEvent(QgsMapMouseEvent *event)
Processes the canvas press event.
void relativeXChanged(bool relative)
Emitted whenever the X parameter is toggled between absolute and relative.
void focusOnAngleRequested()
Emitted whenever the angle field should get the focus using the shortcuts (A).
bool showConstructionGuides() const
Returns whether the construction guides are visible.
WidgetSetMode
Type of interaction to simulate when editing values from external widget.
void focusOnZRequested()
Emitted whenever the Z field should get the focus using the shortcuts (Z).
void focusOnMRequested()
Emitted whenever the M field should get the focus using the shortcuts (M).
void popWarning()
Remove any previously emitted warnings (if any)
void valueXChanged(const QString &value)
Emitted whenever the X value changes (either the mouse moved, or the user changed the input).
double getLineZ() const
Convenient method to get the Z value from the line edit wiget.
void updateCurrentPoint(const QgsPoint &point)
Updates the current point in the CAD point list.
void lockMChanged(bool locked)
Emitted whenever the M parameter is locked.
void cadEnabledChanged(bool enabled)
Signals for external widgets that need to update according to current values.
void lockYChanged(bool locked)
Emitted whenever the Y parameter is locked.
void valueAngleChanged(const QString &value)
Emitted whenever the angle value changes (either the mouse moved, or the user changed the input).
void processCanvasMoveEvent(QgsMapMouseEvent *event)
Processes the canvas move event.
void valueMChanged(const QString &value)
Emitted whenever the M value changes (either the mouse moved, or the user changed the input).
void enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
void relativeZChanged(bool relative)
Emitted whenever the Z parameter is toggled between absolute and relative.
@ RelativeAngle
Also for parallel and perpendicular.
@ RelativeCoordinates
This corresponds to distance and relative coordinates.
bool canvasKeyPressEventFilter(QKeyEvent *e)
Filter key events to e.g.
void setAngle(const QString &value, WidgetSetMode mode)
Set the angle value on the widget.
void enabledChangedAngle(bool enabled)
Emitted whenever the angle field is enabled or disabled.
bool snapToConstructionGuides() const
Returns whether points should snap to construction guides.
void toggleConstraintDistance()
Toggles the distance constraint.
void enabledChangedZ(bool enabled)
Emitted whenever the Z field is enabled or disabled.
void lockDistanceChanged(bool locked)
Emitted whenever the distance parameter is locked.
void relativeAngleChanged(bool relative)
Emitted whenever the angleX parameter is toggled between absolute and relative.
void softLockXyChanged(bool locked)
Emitted whenever the soft x/y extension parameter is locked.
void valueBearingChanged(const QString &value)
Emitted whenever the bearing value changes.
void enabledChangedM(bool enabled)
Emitted whenever the M field is enabled or disabled.
void setDistance(const QString &value, WidgetSetMode mode)
Set the distance value on the widget.
bool alignToSegment(QgsMapMouseEvent *e, QgsAdvancedDigitizingDockWidget::CadConstraint::LockMode lockMode=QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock)
align to segment for between line constraint.
void setX(const QString &value, WidgetSetMode mode)
Set the X value on the widget.
QgsPoint currentPointV2(bool *exists=nullptr) const
The last point.
QgsAdvancedDigitizingTool * tool() const
Returns the current advanced digitizing tool.
void clear()
Clear any cached previous clicks and helper lines.
void focusOnDistanceRequested()
Emitted whenever the distance field should get the focus using the shortcuts (D).
QgsPoint currentPointLayerCoordinates(QgsMapLayer *layer) const
Returns the last CAD point, in a map layer's coordinates.
void valueCommonAngleSnappingChanged(double angle)
Emitted whenever the snapping to common angle option changes, angle = 0 means that the functionality ...
void setM(const QString &value, WidgetSetMode mode)
Set the M value on the widget.
void pushWarning(const QString &message)
Push a warning.
void clearLockedSnapVertices(bool force=true)
Removes all points from the locked snap vertex list.
void relativeYChanged(bool relative)
Emitted whenever the Y parameter is toggled between absolute and relative.
The QgsAdvancedDigitizingFloater class is widget that floats next to the mouse pointer,...
void setItemVisibility(const QgsAdvancedDigitizingFloater::FloaterItem &item, bool visible)
Set whether the floater item should be visible or not.
void setActive(bool active)
Set whether the floater should be active or not.
bool active()
Whether the floater is active or not.
Stores metadata about one advanced digitizing tool class.
QString visibleName() const
Returns the tool's translatable user-friendly name.
virtual QgsAdvancedDigitizingTool * createTool(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
Returns new tool of this type. Return nullptr on error.
An abstract class for advanced digitizing tools.
void paintRequested()
Requests a new painting event to the advanced digitizing canvas item.
QgsAdvancedDigitizingToolAbstractMetadata * toolMetadata(const QString &name)
Returns the advanced digitizing tool matching the provided name or nullptr when no match available.
const QStringList toolMetadataNames() const
Returns the list of registered tool names.
QString formatDouble(double value, const QgsNumericFormatContext &context) const override
Returns a formatted string representation of a numeric double value.
Structure with details of one constraint.
Definition qgscadutils.h:42
bool locked
Whether the constraint is active, i.e. should be considered.
Definition qgscadutils.h:55
double value
Numeric value of the constraint (coordinate/distance in map units or angle in degrees)
Definition qgscadutils.h:59
bool relative
Whether the value is relative to previous value.
Definition qgscadutils.h:57
Defines constraints for the QgsCadUtils::alignMapPoint() method.
QgsCadUtils::AlignMapPointConstraint xyVertexConstraint
QgsCadUtils::AlignMapPointConstraint yConstraint
Constraint for Y coordinate.
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
double mapUnitsPerPixel
Map units/pixel ratio from map canvas.
void setCadPoints(const QList< QgsPoint > &points)
Sets the list of recent CAD points (in map coordinates).
void setLockedSnapVertices(const QQueue< QgsPointLocator::Match > &lockedSnapVertices)
Sets the queue of locked vertices.
QgsCadUtils::AlignMapPointConstraint mConstraint
Constraint for M coordinate.
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
bool snappingToFeaturesOverridesCommonAngle
Flag to set snapping to features priority over common angle.
QgsCadUtils::AlignMapPointConstraint zConstraint
Constraint for Z coordinate.
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be nullptr.
QgsCadUtils::AlignMapPointConstraint commonAngleConstraint
Constraint for soft lock to a common angle.
QgsCadUtils::AlignMapPointConstraint lineExtensionConstraint
QgsCadUtils::AlignMapPointConstraint angleConstraint
Constraint for angle.
Structure returned from alignMapPoint() method.
Definition qgscadutils.h:67
Qgis::LineExtensionSide softLockLineExtension
Definition qgscadutils.h:90
QgsPointXY finalMapPoint
map point aligned according to the constraints
Definition qgscadutils.h:73
bool valid
Whether the combination of constraints is actually valid.
Definition qgscadutils.h:70
QgsPointLocator::Match snapMatch
Snapped point - only valid if actually used for something.
Definition qgscadutils.h:79
double softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition qgscadutils.h:88
static QgsCadUtils::AlignMapPointOutput alignMapPoint(const QgsPointXY &originalMapPoint, const QgsCadUtils::AlignMapPointContext &ctx)
Applies X/Y/angle/distance constraints from the given context to a map point.
Class for doing transforms between two map coordinate systems.
static QString formatDistance(double distance, int decimals, Qgis::DistanceUnit unit, bool keepBaseUnit=false)
Returns an distance formatted as a friendly string.
QgsDockWidget subclass with more fine-grained control over how the widget is closed or opened.
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
This class wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
A event filter for watching for focus events on a parent object.
void focusIn()
Emitted when parent object gains focus.
void focusOut()
Emitted when parent object loses focus.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
static QgsGui * instance()
Returns a pointer to the singleton instance.
Definition qgsgui.cpp: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.
QgsMapTool * mapTool() const
Returns the currently active tool.
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
void destinationCrsChanged()
Emitted when map CRS has changed.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
Base class for all map layer types.
Definition qgsmaplayer.h:76
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
QgsPointXY originalMapPoint() const
Returns the original, unmodified map point of the mouse cursor.
QgsPointXY mapPoint() const
mapPoint returns the point in coordinates
void setMapPoint(const QgsPointXY &point)
Set the (snapped) point this event points to in map coordinates.
QgsPointXY snapPoint()
snapPoint will snap the points using the map canvas snapping utils configuration
Qgis::DistanceUnit mapUnits() const
Returns the units of the map's geographical coordinates - used for scale calculation.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
The QgsMapToolAdvancedDigitizing class is a QgsMapTool which gives event directly in map coordinates ...
static double defaultMValue()
Returns default M value.
static double defaultZValue()
Returns default Z value.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
bool isEditable() const override
Returns true if the layer can be edited.
A context for numeric formats.
A class to represent a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:343
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:332
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
void setM(double m)
Sets the point's m-value.
Definition qgspoint.h:371
void setZ(double z)
Sets the point's z-coordinate.
Definition qgspoint.h:356
double distanceSquared(double x, double y) const
Returns the Cartesian 2D squared distance between this point a specified x, y coordinate.
Definition qgspoint.h:415
double m
Definition qgspoint.h:55
double y
Definition qgspoint.h:53
const QgsBearingNumericFormat * bearingFormat() const
Returns the project bearing's format, which controls how bearings associated with the project are dis...
Qgis::DistanceUnit distanceUnits
Definition qgsproject.h:124
static QgsProject * instance()
Returns the QgsProject singleton instance.
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsSnappingConfig snappingConfig
Definition qgsproject.h:116
void cleared()
Emitted when the project is cleared (and additionally when an open project is cleared just before a n...
QgsProjectDisplaySettings * displaySettings
Definition qgsproject.h:126
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:113
bool topologicalEditing
Definition qgsproject.h:123
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
bool setValue(const T &value, const QString &dynamicKeyPart=QString()) const
Set settings value.
A boolean settings entry.
static QgsSettingsTreeNode * sTreeDigitizing
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Class that shows snapping marker on map canvas for the current snapping match.
This is a container for configuration of the snapping of the project.
void setTypeFlag(Qgis::SnappingTypes type)
define the type of snapping
void setMode(Qgis::SnappingMode mode)
define the mode of snapping
This class has all the configuration of snapping and can return answers to snapping queries.
void addExtraSnapLayer(QgsVectorLayer *vl)
Supply an extra snapping layer (typically a memory layer).
void removeExtraSnapLayer(QgsVectorLayer *vl)
Removes an extra snapping layer.
QgsSnappingConfig config
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Snap to map according to the current configuration.
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
The QgsUserInputWidget class is a floating widget that shall be used to display widgets for user inpu...
void addUserInputWidget(QWidget *widget)
Add a widget to be displayed in the dock.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
static bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
double qgsPermissiveToDouble(QString string, bool &ok)
Converts a string to a double in a permissive way, e.g., allowing for incorrect numbers of digits bet...
Definition qgis.cpp:72
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:5821
QSet< QgsFeatureId > QgsFeatureIds
QLineF segment(int index, QRectF rect, double radius)
int precision
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsPoint interpolatedPoint(const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem()) const
Convenient method to return a point on an edge with linear interpolation of the Z value.
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords,...
bool hasEdge() const
Returns true if the Match is an edge.
void edgePoints(QgsPointXY &pt1, QgsPointXY &pt2) const
Only for a valid edge match - obtain endpoints of the edge.
bool hasLineEndpoint() const
Returns true if the Match is a line endpoint (start or end vertex).
bool hasVertex() const
Returns true if the Match is a vertex.
Setting options for loading vector layers.