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