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