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