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