QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsadvanceddigitizingdockwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsadvanceddigitizingdockwidget.cpp - dock for CAD tools
3 ----------------------
4 begin : October 2014
5 copyright : (C) Denis Rouzaud
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QMenu>
17#include <QEvent>
18#include <QCoreApplication>
19
20#include <cmath>
21
26#include "qgscadutils.h"
27#include "qgsexpression.h"
28#include "qgsmapcanvas.h"
29#include "qgsmaptooledit.h"
31#include "qgsmessagebaritem.h"
32#include "qgsfocuswatcher.h"
33#include "qgssettings.h"
34#include "qgssnappingutils.h"
35#include "qgsproject.h"
36#include "qgsmapmouseevent.h"
37#include "qgsmeshlayer.h"
38#include "qgsunittypes.h"
40#include "qgssettingstree.h"
41
42#include <QActionGroup>
43
44
45const QgsSettingsEntryBool *QgsAdvancedDigitizingDockWidget::settingsCadSnappingPriorityPrioritizeFeature = new QgsSettingsEntryBool( QStringLiteral( "cad-snapping-prioritize-feature" ), QgsSettingsTree::sTreeDigitizing, false, tr( "Determines if snapping to features has priority over snapping to common angles." ) ) ;
46
47
49 : QgsDockWidget( parent )
50 , mMapCanvas( canvas )
51 , mSnapIndicator( std::make_unique< QgsSnapIndicator>( canvas ) )
52 , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 0.0 ).toDouble() )
53{
54 setupUi( this );
55
56 mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this );
57
58 mAngleConstraint.reset( new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton, mRepeatingLockAngleButton ) );
59 mAngleConstraint->setCadConstraintType( Qgis::CadConstraintType::Angle );
60 mAngleConstraint->setMapCanvas( mMapCanvas );
61 mDistanceConstraint.reset( new CadConstraint( mDistanceLineEdit, mLockDistanceButton, nullptr, mRepeatingLockDistanceButton ) );
62 mDistanceConstraint->setCadConstraintType( Qgis::CadConstraintType::Distance );
63 mDistanceConstraint->setMapCanvas( mMapCanvas );
64 mXConstraint.reset( new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton, mRepeatingLockXButton ) );
65 mXConstraint->setCadConstraintType( Qgis::CadConstraintType::XCoordinate );
66 mXConstraint->setMapCanvas( mMapCanvas );
67 mYConstraint.reset( new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton, mRepeatingLockYButton ) );
68 mYConstraint->setCadConstraintType( Qgis::CadConstraintType::YCoordinate );
69 mYConstraint->setMapCanvas( mMapCanvas );
70 mZConstraint.reset( new CadConstraint( mZLineEdit, mLockZButton, mRelativeZButton, mRepeatingLockZButton ) );
71 mZConstraint->setCadConstraintType( Qgis::CadConstraintType::ZValue );
72 mZConstraint->setMapCanvas( mMapCanvas );
73 mMConstraint.reset( new CadConstraint( mMLineEdit, mLockMButton, mRelativeMButton, mRepeatingLockMButton ) );
74 mMConstraint->setCadConstraintType( Qgis::CadConstraintType::MValue );
75 mMConstraint->setMapCanvas( mMapCanvas );
76
77 mLineExtensionConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
78 mXyVertexConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
79 mXyVertexConstraint->setMapCanvas( mMapCanvas );
80
81 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
82
83 mMapCanvas->installEventFilter( this );
84 mAngleLineEdit->installEventFilter( this );
85 mDistanceLineEdit->installEventFilter( this );
86 mXLineEdit->installEventFilter( this );
87 mYLineEdit->installEventFilter( this );
88 mZLineEdit->installEventFilter( this );
89 mMLineEdit->installEventFilter( this );
90
91 // Connect the UI to the event filter to update constraints
92 connect( mEnableAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::activateCad );
93 connect( mConstructionModeAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode );
94 connect( mParallelAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
95 connect( mPerpendicularAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
96 connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
97 connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
98 connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
99 connect( mLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
100 connect( mLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
101 connect( mLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
102 connect( mRelativeAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
103 connect( mRelativeXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
104 connect( mRelativeYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
105 connect( mRelativeZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
106 connect( mRelativeMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
107 connect( mRepeatingLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
108 connect( mRepeatingLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
109 connect( mRepeatingLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
110 connect( mRepeatingLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
111 connect( mRepeatingLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
112 connect( mRepeatingLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
113 connect( mAngleLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
114 connect( mDistanceLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
115 connect( mXLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
116 connect( mYLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
117 connect( mZLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
118 connect( mMLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
119 connect( mAngleLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
120 connect( mDistanceLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
121 connect( mXLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
122 connect( mYLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
123 connect( mZLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
124 connect( mMLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
125 //also watch for focus out events on these widgets
126 QgsFocusWatcher *angleWatcher = new QgsFocusWatcher( mAngleLineEdit );
127 connect( angleWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
128 connect( angleWatcher, &QgsFocusWatcher::focusIn, this, [ = ]()
129 {
130 const QString cleanedInputValue { QgsAdvancedDigitizingDockWidget::CadConstraint::removeSuffix( mAngleLineEdit->text(), Qgis::CadConstraintType::Angle ) };
131 whileBlocking( mAngleLineEdit )->setText( cleanedInputValue );
132 } );
133 QgsFocusWatcher *distanceWatcher = new QgsFocusWatcher( mDistanceLineEdit );
134 connect( distanceWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
135 connect( distanceWatcher, &QgsFocusWatcher::focusIn, this, [ = ]()
136 {
137 const QString cleanedInputValue { QgsAdvancedDigitizingDockWidget::CadConstraint::removeSuffix( mDistanceLineEdit->text(), Qgis::CadConstraintType::Distance ) };
138 whileBlocking( mDistanceLineEdit )->setText( cleanedInputValue );
139 } );
140 QgsFocusWatcher *xWatcher = new QgsFocusWatcher( mXLineEdit );
141 connect( xWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
142 QgsFocusWatcher *yWatcher = new QgsFocusWatcher( mYLineEdit );
143 connect( yWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
144 QgsFocusWatcher *zWatcher = new QgsFocusWatcher( mZLineEdit );
145 connect( zWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
146 QgsFocusWatcher *mWatcher = new QgsFocusWatcher( mMLineEdit );
147 connect( mWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
148
149 // Common angle snapping menu
150 mCommonAngleActionsMenu = new QMenu( this );
151 // Suppress warning: Potential leak of memory pointed to by 'angleButtonGroup' [clang-analyzer-cplusplus.NewDeleteLeaks]
152#ifndef __clang_analyzer__
153 QActionGroup *angleButtonGroup = new QActionGroup( mCommonAngleActionsMenu ); // actions are exclusive for common angles NOLINT
154#endif
155 QList< QPair< double, QString > > commonAngles;
156 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} );
157 for ( QList<double>::const_iterator it = anglesDouble.constBegin(); it != anglesDouble.constEnd(); ++it )
158 {
159 commonAngles << QPair<double, QString>( *it, formatCommonAngleSnapping( *it ) );
160 }
161
162 {
163 QMenu *snappingPriorityMenu = new QMenu( tr( "Snapping Priority" ), mCommonAngleActionsMenu );
164 QActionGroup *snappingPriorityActionGroup = new QActionGroup( snappingPriorityMenu );
165 QAction *featuresAction = new QAction( tr( "Prioritize Snapping to Features" ), snappingPriorityActionGroup );
166 featuresAction->setCheckable( true );
167 QAction *anglesAction = new QAction( tr( "Prioritize Snapping to Common Angles" ), snappingPriorityActionGroup );
168 anglesAction->setCheckable( true );
169 snappingPriorityActionGroup->addAction( featuresAction );
170 snappingPriorityActionGroup->addAction( anglesAction );
171 snappingPriorityMenu->addAction( anglesAction );
172 snappingPriorityMenu->addAction( featuresAction );
173 connect( anglesAction, &QAction::changed, this, [ = ]
174 {
175 mSnappingPrioritizeFeatures = featuresAction->isChecked();
176 settingsCadSnappingPriorityPrioritizeFeature->setValue( featuresAction->isChecked() );
177 } );
178 featuresAction->setChecked( settingsCadSnappingPriorityPrioritizeFeature->value( ) );
179 anglesAction->setChecked( ! featuresAction->isChecked() );
180 mCommonAngleActionsMenu->addMenu( snappingPriorityMenu );
181 }
182
183
184 for ( QList< QPair<double, QString > >::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it )
185 {
186 QAction *action = new QAction( it->second, mCommonAngleActionsMenu );
187 action->setCheckable( true );
188 action->setChecked( it->first == mCommonAngleConstraint );
189 mCommonAngleActionsMenu->addAction( action );
190 // Suppress warning: Potential leak of memory pointed to by 'angleButtonGroup' [clang-analyzer-cplusplus.NewDeleteLeaks]
191#ifndef __clang_analyzer__
192 angleButtonGroup->addAction( action );
193#endif
194 mCommonAngleActions.insert( it->first, action );
195 }
196
197 qobject_cast< QToolButton *>( mToolbar->widgetForAction( mSettingsAction ) )->setPopupMode( QToolButton::InstantPopup );
198 mSettingsAction->setMenu( mCommonAngleActionsMenu );
199 mSettingsAction->setCheckable( true );
200 mSettingsAction->setToolTip( "<b>" + tr( "Snap to common angles" ) + "</b><br>(" + tr( "press n to cycle through the options" ) + ")" );
201 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
202 connect( mCommonAngleActionsMenu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
203
204 // Construction modes
205 QMenu *constructionMenu = new QMenu( this );
206
207 mLineExtensionAction = new QAction( tr( "Line Extension" ), constructionMenu );
208 mLineExtensionAction->setCheckable( true );
209 constructionMenu->addAction( mLineExtensionAction );
210 connect( mLineExtensionAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
211
212 mXyVertexAction = new QAction( tr( "X/Y Point" ), constructionMenu );
213 mXyVertexAction->setCheckable( true );
214 constructionMenu->addAction( mXyVertexAction );
215 connect( mXyVertexAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
216
217 auto constructionToolBar = qobject_cast< QToolButton *>( mToolbar->widgetForAction( mConstructionAction ) );
218 constructionToolBar->setPopupMode( QToolButton::InstantPopup );
219 constructionToolBar->setMenu( constructionMenu );
220 constructionToolBar->setObjectName( QStringLiteral( "ConstructionButton" ) );
221
222 mConstructionAction->setMenu( mCommonAngleActionsMenu );
223 mConstructionAction->setCheckable( true );
224 mConstructionAction->setToolTip( tr( "Construction Tools" ) );
225// connect( constructionMenu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
226
227 // set tooltips
228 mConstructionModeAction->setToolTip( "<b>" + tr( "Construction mode" ) + "</b><br>(" + tr( "press c to toggle on/off" ) + ")" );
229 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
230 mLockDistanceButton->setToolTip( "<b>" + tr( "Lock distance" ) + "</b><br>(" + tr( "press Ctrl + d for quick access" ) + ")" );
231 mRepeatingLockDistanceButton->setToolTip( "<b>" + tr( "Continuously lock distance" ) + "</b>" );
232
233 mRelativeAngleButton->setToolTip( "<b>" + tr( "Toggles relative angle to previous segment" ) + "</b><br>(" + tr( "press Shift + a for quick access" ) + ")" );
234 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
235 mLockAngleButton->setToolTip( "<b>" + tr( "Lock angle" ) + "</b><br>(" + tr( "press Ctrl + a for quick access" ) + ")" );
236 mRepeatingLockAngleButton->setToolTip( "<b>" + tr( "Continuously lock angle" ) + "</b>" );
237
238 mRelativeXButton->setToolTip( "<b>" + tr( "Toggles relative x to previous node" ) + "</b><br>(" + tr( "press Shift + x for quick access" ) + ")" );
239 mXLineEdit->setToolTip( "<b>" + tr( "X coordinate" ) + "</b><br>(" + tr( "press x for quick access" ) + ")" );
240 mLockXButton->setToolTip( "<b>" + tr( "Lock x coordinate" ) + "</b><br>(" + tr( "press Ctrl + x for quick access" ) + ")" );
241 mRepeatingLockXButton->setToolTip( "<b>" + tr( "Continuously lock x coordinate" ) + "</b>" );
242
243 mRelativeYButton->setToolTip( "<b>" + tr( "Toggles relative y to previous node" ) + "</b><br>(" + tr( "press Shift + y for quick access" ) + ")" );
244 mYLineEdit->setToolTip( "<b>" + tr( "Y coordinate" ) + "</b><br>(" + tr( "press y for quick access" ) + ")" );
245 mLockYButton->setToolTip( "<b>" + tr( "Lock y coordinate" ) + "</b><br>(" + tr( "press Ctrl + y for quick access" ) + ")" );
246 mRepeatingLockYButton->setToolTip( "<b>" + tr( "Continuously lock y coordinate" ) + "</b>" );
247
248 mRelativeZButton->setToolTip( "<b>" + tr( "Toggles relative z to previous node" ) + "</b><br>(" + tr( "press Shift + z for quick access" ) + ")" );
249 mZLineEdit->setToolTip( "<b>" + tr( "Z coordinate" ) + "</b><br>(" + tr( "press z for quick access" ) + ")" );
250 mLockZButton->setToolTip( "<b>" + tr( "Lock z coordinate" ) + "</b><br>(" + tr( "press Ctrl + z for quick access" ) + ")" );
251 mRepeatingLockZButton->setToolTip( "<b>" + tr( "Continuously lock z coordinate" ) + "</b>" );
252
253 mRelativeMButton->setToolTip( "<b>" + tr( "Toggles relative m to previous node" ) + "</b><br>(" + tr( "press Shift + m for quick access" ) + ")" );
254 mMLineEdit->setToolTip( "<b>" + tr( "M coordinate" ) + "</b><br>(" + tr( "press m for quick access" ) + ")" );
255 mLockMButton->setToolTip( "<b>" + tr( "Lock m coordinate" ) + "</b><br>(" + tr( "press Ctrl + m for quick access" ) + ")" );
256 mRepeatingLockMButton->setToolTip( "<b>" + tr( "Continuously lock m coordinate" ) + "</b>" );
257
258 // Create the slots/signals
259 connect( mXLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueXChanged );
260 connect( mYLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueYChanged );
261 connect( mZLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueZChanged );
262 connect( mMLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueMChanged );
263 connect( mDistanceLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueDistanceChanged );
264 connect( mAngleLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueAngleChanged );
265
266 // Create the floater
267 mFloaterActionsMenu = new QMenu( this );
268 qobject_cast< QToolButton *>( mToolbar->widgetForAction( mFloaterAction ) )->setPopupMode( QToolButton::InstantPopup );
269 mFloaterAction->setMenu( mFloaterActionsMenu );
270 mFloaterAction->setCheckable( true );
271 mFloater = new QgsAdvancedDigitizingFloater( canvas, this );
272 mFloaterAction->setChecked( mFloater->active() );
273
274 // Add floater config actions
275 {
276 QAction *action = new QAction( tr( "Show floater" ), mFloaterActionsMenu );
277 action->setCheckable( true );
278 action->setChecked( mFloater->active() );
279 mFloaterActionsMenu->addAction( action );
280 connect( action, &QAction::toggled, this, [ = ]( bool checked )
281 {
282 mFloater->setActive( checked );
283 mFloaterAction->setChecked( checked );
284 } );
285 }
286
287 mFloaterActionsMenu->addSeparator();
288
289 {
290 QAction *action = new QAction( tr( "Show distance" ), mFloaterActionsMenu );
291 action->setCheckable( true );
292 mFloaterActionsMenu->addAction( action );
293 connect( action, &QAction::toggled, this, [ = ]( bool checked )
294 {
296 } );
297 action->setChecked( QgsSettings().value( QStringLiteral( "/Cad/DistanceShowInFloater" ), true ).toBool() );
298 }
299
300 {
301 QAction *action = new QAction( tr( "Show angle" ), mFloaterActionsMenu );
302 action->setCheckable( true );
303 mFloaterActionsMenu->addAction( action );
304 connect( action, &QAction::toggled, this, [ = ]( bool checked )
305 {
307 } );
308 action->setChecked( QgsSettings().value( QStringLiteral( "/Cad/AngleShowInFloater" ), true ).toBool() );
309 }
310
311 {
312 QAction *action = new QAction( tr( "Show XY coordinates" ), mFloaterActionsMenu );
313 action->setCheckable( true );
314 mFloaterActionsMenu->addAction( action );
315 connect( action, &QAction::toggled, this, [ = ]( bool checked )
316 {
319 } );
320 // There is no separate menu option for X and Y so let's check for X only.
321 action->setChecked( QgsSettings().value( QStringLiteral( "/Cad/XCoordinateShowInFloater" ), true ).toBool() );
322 }
323
324 {
325 QAction *action = new QAction( tr( "Show Z value" ), mFloaterActionsMenu );
326 action->setCheckable( true );
327 mFloaterActionsMenu->addAction( action );
328 connect( action, &QAction::toggled, this, [ = ]( bool checked )
329 {
331 } );
332 action->setChecked( QgsSettings().value( QStringLiteral( "/Cad/ZCoordinateShowInFloater" ), true ).toBool() );
333 }
334
335 {
336 QAction *action = new QAction( tr( "Show M value" ), mFloaterActionsMenu );
337 action->setCheckable( true );
338 mFloaterActionsMenu->addAction( action );
339 connect( action, &QAction::toggled, this, [ = ]( bool checked )
340 {
342 } );
343 action->setChecked( QgsSettings().value( QStringLiteral( "/Cad/MCoordinateShowInFloater" ), true ).toBool() );
344 }
345
346 {
347 QAction *action = new QAction( tr( "Show bearing/azimuth" ), mFloaterActionsMenu );
348 action->setCheckable( true );
349 mFloaterActionsMenu->addAction( action );
350 connect( action, &QAction::toggled, this, [ = ]( bool checked )
351 {
353 } );
354 action->setChecked( QgsSettings().value( QStringLiteral( "/Cad/BearingShowInFloater" ), false ).toBool() );
355 }
356
357 {
358 QAction *action = new QAction( tr( "Show common snapping angle" ), mFloaterActionsMenu );
359 action->setCheckable( true );
360 mFloaterActionsMenu->addAction( action );
361 connect( action, &QAction::toggled, this, [ = ]( bool checked )
362 {
364 } );
365 action->setChecked( QgsSettings().value( QStringLiteral( "/Cad/CommonAngleSnappingShowInFloater" ), false ).toBool() );
366 }
367
368 updateCapacity( true );
369 connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, [ = ] { updateCapacity( true ); } );
370
371 disable();
372}
373
375{
376 if ( angle == 0 )
377 return tr( "Do Not Snap to Common Angles" );
378 else
379 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 );
380}
381
382void QgsAdvancedDigitizingDockWidget::setX( const QString &value, WidgetSetMode mode )
383{
384 mXLineEdit->setText( value );
385 if ( mode == WidgetSetMode::ReturnPressed )
386 {
387 emit mXLineEdit->returnPressed();
388 }
389 else if ( mode == WidgetSetMode::FocusOut )
390 {
391 QEvent *e = new QEvent( QEvent::FocusOut );
392 QCoreApplication::postEvent( mXLineEdit, e );
393 }
394 else if ( mode == WidgetSetMode::TextEdited )
395 {
396 emit mXLineEdit->textEdited( value );
397 }
398}
399void QgsAdvancedDigitizingDockWidget::setY( const QString &value, WidgetSetMode mode )
400{
401 mYLineEdit->setText( value );
402 if ( mode == WidgetSetMode::ReturnPressed )
403 {
404 emit mYLineEdit->returnPressed();
405 }
406 else if ( mode == WidgetSetMode::FocusOut )
407 {
408 QEvent *e = new QEvent( QEvent::FocusOut );
409 QCoreApplication::postEvent( mYLineEdit, e );
410 }
411 else if ( mode == WidgetSetMode::TextEdited )
412 {
413 emit mYLineEdit->textEdited( value );
414 }
415}
416void QgsAdvancedDigitizingDockWidget::setZ( const QString &value, WidgetSetMode mode )
417{
418 mZLineEdit->setText( value );
419 if ( mode == WidgetSetMode::ReturnPressed )
420 {
421 emit mZLineEdit->returnPressed();
422 }
423 else if ( mode == WidgetSetMode::FocusOut )
424 {
425 QEvent *e = new QEvent( QEvent::FocusOut );
426 QCoreApplication::postEvent( mZLineEdit, e );
427 }
428 else if ( mode == WidgetSetMode::TextEdited )
429 {
430 emit mZLineEdit->textEdited( value );
431 }
432}
433void QgsAdvancedDigitizingDockWidget::setM( const QString &value, WidgetSetMode mode )
434{
435 mMLineEdit->setText( value );
436 if ( mode == WidgetSetMode::ReturnPressed )
437 {
438 emit mMLineEdit->returnPressed();
439 }
440 else if ( mode == WidgetSetMode::FocusOut )
441 {
442 QEvent *e = new QEvent( QEvent::FocusOut );
443 QCoreApplication::postEvent( mMLineEdit, e );
444 }
445 else if ( mode == WidgetSetMode::TextEdited )
446 {
447 emit mMLineEdit->textEdited( value );
448 }
449}
451{
452 mAngleLineEdit->setText( value );
453 if ( mode == WidgetSetMode::ReturnPressed )
454 {
455 emit mAngleLineEdit->returnPressed();
456 }
457 else if ( mode == WidgetSetMode::FocusOut )
458 {
459 emit mAngleLineEdit->textEdited( value );
460 }
461}
463{
464 mDistanceLineEdit->setText( value );
465 if ( mode == WidgetSetMode::ReturnPressed )
466 {
467 emit mDistanceLineEdit->returnPressed();
468 }
469 else if ( mode == WidgetSetMode::FocusOut )
470 {
471 QEvent *e = new QEvent( QEvent::FocusOut );
472 QCoreApplication::postEvent( mDistanceLineEdit, e );
473 }
474 else if ( mode == WidgetSetMode::TextEdited )
475 {
476 emit mDistanceLineEdit->textEdited( value );
477 }
478}
479
480
481void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
482{
483 mCadEnabled = enabled;
484 mEnableAction->setChecked( enabled );
485 mConstructionModeAction->setEnabled( enabled );
486 mSettingsAction->setEnabled( enabled );
487 mInputWidgets->setEnabled( enabled );
488 mFloaterAction->setEnabled( enabled );
489 mConstructionAction->setEnabled( enabled );
490
491 if ( !enabled )
492 {
493 // uncheck at deactivation
494 mLineExtensionAction->setChecked( false );
495 mXyVertexAction->setChecked( false );
496 // will be reactivated in updateCapacities
497 mParallelAction->setEnabled( false );
498 mPerpendicularAction->setEnabled( false );
499 }
500
501
502 clear();
504 setConstructionMode( false );
505
506 switchZM();
507 emit cadEnabledChanged( enabled );
508
509 if ( enabled )
510 {
511 emit valueCommonAngleSnappingChanged( mCommonAngleConstraint );
512 }
513
514 mLastSnapMatch = QgsPointLocator::Match();
515}
516
517
519{
520 bool enableZ = false;
521 bool enableM = false;
522
523 if ( QgsMapLayer *layer = targetLayer() )
524 {
525 switch ( layer->type() )
526 {
528 {
529 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
530 const Qgis::WkbType type = vlayer->wkbType();
531 enableZ = QgsWkbTypes::hasZ( type );
532 enableM = QgsWkbTypes::hasM( type );
533 break;
534 }
535
537 {
538 QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( layer );
539 enableZ = mlayer->isEditable();
540 break;
541 }
542
550 break;
551 }
552 }
553
554 setEnabledZ( enableZ );
555 setEnabledM( enableM );
556}
557
559{
560 mRelativeZButton->setEnabled( enable );
561 mZLabel->setEnabled( enable );
562 mZLineEdit->setEnabled( enable );
563 if ( mZLineEdit->isEnabled() )
564 mZLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultZValue(), 'f', 6 ) );
565 else
566 mZLineEdit->clear();
567 mLockZButton->setEnabled( enable );
568 emit enabledChangedZ( enable );
569}
570
572{
573 mRelativeMButton->setEnabled( enable );
574 mMLabel->setEnabled( enable );
575 mMLineEdit->setEnabled( enable );
576 if ( mMLineEdit->isEnabled() )
577 mMLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultMValue(), 'f', 6 ) );
578 else
579 mMLineEdit->clear();
580 mLockMButton->setEnabled( enable );
581 emit enabledChangedM( enable );
582}
583
584void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
585{
586 enabled &= mCurrentMapToolSupportsCad;
587
588 mSessionActive = enabled;
589
590 if ( enabled && !isVisible() )
591 {
592 show();
593 }
594
595 setCadEnabled( enabled );
596}
597
598void QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked( bool activated )
599{
600 if ( !activated )
601 {
602 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
603 }
604 else if ( sender() == mParallelAction )
605 {
606 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
607 }
608 else if ( sender() == mPerpendicularAction )
609 {
610 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
611 }
612}
613
614void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
615{
616 if ( sender() == mRelativeAngleButton )
617 {
618 mAngleConstraint->setRelative( activate );
619 emit relativeAngleChanged( activate );
620 }
621 else if ( sender() == mRelativeXButton )
622 {
623 mXConstraint->setRelative( activate );
624 emit relativeXChanged( activate );
625 }
626 else if ( sender() == mRelativeYButton )
627 {
628 mYConstraint->setRelative( activate );
629 emit relativeYChanged( activate );
630 }
631 else if ( sender() == mRelativeZButton )
632 {
633 mZConstraint->setRelative( activate );
634 emit relativeZChanged( activate );
635 }
636 else if ( sender() == mRelativeMButton )
637 {
638 mMConstraint->setRelative( activate );
639 emit relativeMChanged( activate );
640 }
641}
642
643void QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock( bool activate )
644{
645 if ( sender() == mRepeatingLockDistanceButton )
646 {
647 mDistanceConstraint->setRepeatingLock( activate );
648 }
649 else if ( sender() == mRepeatingLockAngleButton )
650 {
651 mAngleConstraint->setRepeatingLock( activate );
652 }
653 else if ( sender() == mRepeatingLockXButton )
654 {
655 mXConstraint->setRepeatingLock( activate );
656 }
657 else if ( sender() == mRepeatingLockYButton )
658 {
659 mYConstraint->setRepeatingLock( activate );
660 }
661 else if ( sender() == mRepeatingLockZButton )
662 {
663 mZConstraint->setRepeatingLock( activate );
664 }
665 else if ( sender() == mRepeatingLockMButton )
666 {
667 mMConstraint->setRepeatingLock( activate );
668 }
669}
670
671void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
672{
673 mConstructionMode = enabled;
674 mConstructionModeAction->setChecked( enabled );
675}
676
677void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction *action )
678{
679 // common angles
680 for ( auto it = mCommonAngleActions.cbegin(); it != mCommonAngleActions.cend(); ++it )
681 {
682 if ( it.value() == action )
683 {
684 it.value()->setChecked( true );
685 mCommonAngleConstraint = it.key();
686 QgsSettings().setValue( QStringLiteral( "/Cad/CommonAngle" ), it.key() );
687 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
688 emit valueCommonAngleSnappingChanged( mCommonAngleConstraint );
689 return;
690 }
691 }
692}
693
694QgsMapLayer *QgsAdvancedDigitizingDockWidget::targetLayer() const
695{
696 if ( QgsMapToolAdvancedDigitizing *advancedTool = qobject_cast< QgsMapToolAdvancedDigitizing * >( mMapCanvas->mapTool() ) )
697 {
698 return advancedTool->layer();
699 }
700 else
701 {
702 return mMapCanvas->currentLayer();
703 }
704}
705
706void QgsAdvancedDigitizingDockWidget::releaseLocks( bool releaseRepeatingLocks )
707{
708 // release all locks except construction mode
709
710 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
711
712 if ( releaseRepeatingLocks )
713 {
714 mXyVertexAction->setChecked( false );
715 mXyVertexConstraint->setLockMode( CadConstraint::NoLock );
716 emit softLockXyChanged( false );
717
718 mLineExtensionAction->setChecked( false );
719 mLineExtensionConstraint->setLockMode( CadConstraint::NoLock );
720 emit softLockLineExtensionChanged( false );
721
723 }
724
725 if ( releaseRepeatingLocks || !mAngleConstraint->isRepeatingLock() )
726 {
727 mAngleConstraint->setLockMode( CadConstraint::NoLock );
728 emit lockAngleChanged( false );
729 }
730 if ( releaseRepeatingLocks || !mDistanceConstraint->isRepeatingLock() )
731 {
732 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
733 emit lockDistanceChanged( false );
734 }
735 if ( releaseRepeatingLocks || !mXConstraint->isRepeatingLock() )
736 {
737 mXConstraint->setLockMode( CadConstraint::NoLock );
738 emit lockXChanged( false );
739 }
740 if ( releaseRepeatingLocks || !mYConstraint->isRepeatingLock() )
741 {
742 mYConstraint->setLockMode( CadConstraint::NoLock );
743 emit lockYChanged( false );
744 }
745 if ( releaseRepeatingLocks || !mZConstraint->isRepeatingLock() )
746 {
747 mZConstraint->setLockMode( CadConstraint::NoLock );
748 emit lockZChanged( false );
749 }
750 if ( releaseRepeatingLocks || !mMConstraint->isRepeatingLock() )
751 {
752 mMConstraint->setLockMode( CadConstraint::NoLock );
753 emit lockMChanged( false );
754 }
755
756 if ( !mCadPointList.empty() )
757 {
758 if ( !mXConstraint->isLocked() && !mXConstraint->relative() )
759 {
760 mXConstraint->setValue( mCadPointList.constLast().x(), true );
761 }
762 if ( !mYConstraint->isLocked() && !mYConstraint->relative() )
763 {
764 mYConstraint->setValue( mCadPointList.constLast().y(), true );
765 }
766 if ( !mZConstraint->isLocked() && !mZConstraint->relative() )
767 {
768 mZConstraint->setValue( mCadPointList.constLast().z(), true );
769 }
770 if ( !mMConstraint->isLocked() && !mMConstraint->relative() )
771 {
772 mMConstraint->setValue( mCadPointList.constLast().m(), true );
773 }
774 }
775
776}
777
778#if 0
779void QgsAdvancedDigitizingDockWidget::emit pointChanged()
780{
781 // run a fake map mouse event to update the paint item
782 QPoint globalPos = mMapCanvas->cursor().pos();
783 QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
784 QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
785 mCurrentMapTool->canvasMoveEvent( e );
786}
787#endif
788
789QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
790{
791 CadConstraint *constraint = nullptr;
792 if ( obj == mAngleLineEdit || obj == mLockAngleButton )
793 {
794 constraint = mAngleConstraint.get();
795 }
796 else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
797 {
798 constraint = mDistanceConstraint.get();
799 }
800 else if ( obj == mXLineEdit || obj == mLockXButton )
801 {
802 constraint = mXConstraint.get();
803 }
804 else if ( obj == mYLineEdit || obj == mLockYButton )
805 {
806 constraint = mYConstraint.get();
807 }
808 else if ( obj == mZLineEdit || obj == mLockZButton )
809 {
810 constraint = mZConstraint.get();
811 }
812 else if ( obj == mMLineEdit || obj == mLockMButton )
813 {
814 constraint = mMConstraint.get();
815 }
816 else if ( obj == mLineExtensionAction )
817 {
818 constraint = mLineExtensionConstraint.get();
819 }
820 else if ( obj == mXyVertexAction )
821 {
822 constraint = mXyVertexConstraint.get();
823 }
824 return constraint;
825}
826
827double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, const Qgis::CadConstraintType type, bool &ok ) const
828{
829 ok = false;
830
831 const QString cleanedInputValue { CadConstraint::removeSuffix( inputValue, type ) };
832 double value = qgsPermissiveToDouble( cleanedInputValue, ok );
833
834 if ( ! ok )
835 {
836 // try to evaluate expression
837 QgsExpression expr( inputValue );
838 const QVariant result = expr.evaluate();
839 if ( expr.hasEvalError() )
840 {
841 ok = false;
842 QString inputValueC { inputValue };
843
844 // First: try removing group separator
845 if ( inputValue.contains( QLocale().groupSeparator() ) )
846 {
847 inputValueC.remove( QLocale().groupSeparator() );
848 QgsExpression exprC( inputValueC );
849 const QVariant resultC = exprC.evaluate();
850 if ( ! exprC.hasEvalError() )
851 {
852 value = resultC.toDouble( &ok );
853 }
854 }
855
856 // Second: be nice with non-dot locales
857 if ( !ok && QLocale().decimalPoint() != QChar( '.' ) && inputValueC.contains( QLocale().decimalPoint() ) )
858 {
859 QgsExpression exprC( inputValueC .replace( QLocale().decimalPoint(), QChar( '.' ) ) );
860 const QVariant resultC = exprC.evaluate();
861 if ( ! exprC.hasEvalError() )
862 {
863 value = resultC.toDouble( &ok );
864 }
865 }
866 }
867 else
868 {
869 value = result.toDouble( &ok );
870 }
871 }
872 return value;
873}
874
875void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
876{
877 if ( !constraint || textValue.isEmpty() )
878 {
879 return;
880 }
881
882 if ( constraint->lockMode() == CadConstraint::NoLock )
883 return;
884
885 bool ok;
886 const double value = parseUserInput( textValue, constraint->cadConstraintType(), ok );
887 if ( !ok )
888 return;
889
890 constraint->setValue( value, convertExpression );
891 // run a fake map mouse event to update the paint item
892 emit pointChangedV2( mCadPointList.value( 0 ) );
893}
894
895void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
896{
897 CadConstraint *constraint = objectToConstraint( sender() );
898 if ( !constraint )
899 {
900 return;
901 }
902
903 if ( activate )
904 {
905 const QString textValue = constraint->lineEdit()->text();
906 if ( !textValue.isEmpty() )
907 {
908 bool ok;
909 const double value = parseUserInput( textValue, constraint->cadConstraintType(), ok );
910 if ( ok )
911 {
912 constraint->setValue( value );
913 }
914 else
915 {
916 activate = false;
917 }
918 }
919 else
920 {
921 activate = false;
922 }
923 }
924 constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
925
926 if ( constraint == mXConstraint.get() )
927 {
928 emit lockXChanged( activate );
929 }
930 else if ( constraint == mYConstraint.get() )
931 {
932 emit lockYChanged( activate );
933 }
934 else if ( constraint == mZConstraint.get() )
935 {
936 emit lockZChanged( activate );
937 }
938 else if ( constraint == mMConstraint.get() )
939 {
940 emit lockMChanged( activate );
941 }
942 else if ( constraint == mDistanceConstraint.get() )
943 {
944 emit lockDistanceChanged( activate );
945 }
946 else if ( constraint == mAngleConstraint.get() )
947 {
948 emit lockAngleChanged( activate );
949 }
950
951 if ( activate )
952 {
953 // deactivate perpendicular/parallel if angle has been activated
954 if ( constraint == mAngleConstraint.get() )
955 {
956 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
957 }
958
959 // run a fake map mouse event to update the paint item
960 emit pointChangedV2( mCadPointList.value( 0 ) );
961 }
962}
963
964void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
965{
966 CadConstraint *constraint = objectToConstraint( sender() );
967 if ( !constraint )
968 {
969 return;
970 }
971
972 updateConstraintValue( constraint, textValue, false );
973}
974
975void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
976{
977 QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
978 if ( !lineEdit )
979 return;
980
981 CadConstraint *constraint = objectToConstraint( lineEdit );
982 if ( !constraint )
983 {
984 return;
985 }
986
987 updateConstraintValue( constraint, lineEdit->text(), true );
988}
989
990void QgsAdvancedDigitizingDockWidget::lockBetweenLineConstraint( Qgis::BetweenLineConstraint constraint )
991{
992 mBetweenLineConstraint = constraint;
993 mPerpendicularAction->setChecked( constraint == Qgis::BetweenLineConstraint::Perpendicular );
994 mParallelAction->setChecked( constraint == Qgis::BetweenLineConstraint::Parallel );
995}
996
997void QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint( bool activate /* default true */ )
998{
999 CadConstraint *constraint = objectToConstraint( sender() );
1000 if ( !constraint )
1001 {
1002 return;
1003 }
1004
1005 constraint->setLockMode( activate ? CadConstraint::SoftLock : CadConstraint::NoLock );
1006
1007 if ( constraint == mXyVertexConstraint.get() )
1008 {
1009 emit softLockXyChanged( activate );
1010 }
1011 else if ( constraint == mLineExtensionConstraint.get() )
1012 {
1013 emit softLockLineExtensionChanged( activate );
1014 }
1015
1016 if ( activate )
1017 {
1018 // run a fake map mouse event to update the paint item
1019 emit pointChangedV2( mCadPointList.value( 0 ) );
1020 }
1021
1022 clearLockedSnapVertices( false );
1023}
1024
1025void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
1026{
1027 CadCapacities newCapacities = CadCapacities();
1028 const bool isGeographic = mMapCanvas->mapSettings().destinationCrs().isGeographic();
1029
1030 // first point is the mouse point (it doesn't count)
1031 if ( mCadPointList.count() > 1 )
1032 {
1033 newCapacities |= RelativeCoordinates;
1034 if ( !isGeographic )
1035 {
1036 newCapacities |= AbsoluteAngle;
1037 newCapacities |= Distance;
1038 }
1039 }
1040 if ( mCadPointList.count() > 2 )
1041 {
1042 if ( !isGeographic )
1043 newCapacities |= RelativeAngle;
1044 }
1045 if ( !updateUIwithoutChange && newCapacities == mCapacities )
1046 {
1047 return;
1048 }
1049
1050 const bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
1051
1052 // update the UI according to new capacities
1053 // still keep the old to compare
1054
1055 const bool distance = mCadEnabled && newCapacities.testFlag( Distance );
1056 const bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
1057 const bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
1058 const bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
1059
1060 mPerpendicularAction->setEnabled( distance && snappingEnabled );
1061 mParallelAction->setEnabled( distance && snappingEnabled );
1062
1063 mLineExtensionAction->setEnabled( snappingEnabled );
1064 mXyVertexAction->setEnabled( snappingEnabled );
1065 clearLockedSnapVertices( false );
1066
1067 //update tooltips on buttons
1068 if ( !snappingEnabled )
1069 {
1070 mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode." ) );
1071 mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode." ) );
1072 mLineExtensionAction->setToolTip( tr( "Snapping must be enabled to utilize line extension mode." ) );
1073 mXyVertexAction->setToolTip( tr( "Snapping must be enabled to utilize xy point mode." ) );
1074 }
1075 else if ( mCadPointList.count() <= 1 )
1076 {
1077 mPerpendicularAction->setToolTip( tr( "A first vertex should be drawn to utilize perpendicular mode." ) );
1078 mParallelAction->setToolTip( tr( "A first vertex should be drawn to utilize parallel mode." ) );
1079 }
1080 else if ( isGeographic )
1081 {
1082 mPerpendicularAction->setToolTip( tr( "Perpendicular mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1083 mParallelAction->setToolTip( tr( "Parallel mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1084 }
1085 else
1086 {
1087 mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
1088 mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
1089 }
1090
1091
1092 if ( !absoluteAngle )
1093 {
1094 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1095 }
1096
1097 // absolute angle = azimuth, relative = from previous line
1098 mLockAngleButton->setEnabled( absoluteAngle );
1099 mRelativeAngleButton->setEnabled( relativeAngle );
1100 mAngleLineEdit->setEnabled( absoluteAngle );
1101 emit enabledChangedAngle( absoluteAngle );
1102 if ( !absoluteAngle )
1103 {
1104 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1105 }
1106 if ( !relativeAngle )
1107 {
1108 mAngleConstraint->setRelative( false );
1109 emit relativeAngleChanged( false );
1110 }
1111 else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
1112 {
1113 // set angle mode to relative if can do and wasn't available before
1114 mAngleConstraint->setRelative( true );
1115 emit relativeAngleChanged( true );
1116 }
1117
1118 // distance is always relative
1119 mLockDistanceButton->setEnabled( distance && relativeCoordinates );
1120 mDistanceLineEdit->setEnabled( distance && relativeCoordinates );
1121 emit enabledChangedDistance( distance && relativeCoordinates );
1122 if ( !( distance && relativeCoordinates ) )
1123 {
1124 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
1125 }
1126
1127 mRelativeXButton->setEnabled( relativeCoordinates );
1128 mRelativeYButton->setEnabled( relativeCoordinates );
1129 mRelativeZButton->setEnabled( relativeCoordinates );
1130 mRelativeMButton->setEnabled( relativeCoordinates );
1131
1132 // update capacities
1133 mCapacities = newCapacities;
1134 mCadPaintItem->updatePosition();
1135}
1136
1137
1139{
1141 constr.locked = c->isLocked();
1142 constr.relative = c->relative();
1143 constr.value = c->value();
1144 return constr;
1145}
1146
1147void QgsAdvancedDigitizingDockWidget::toggleLockedSnapVertex( const QgsPointLocator::Match &snapMatch, QgsPointLocator::Match previouslySnap )
1148{
1149 // do nothing if not activated
1150 if ( !mLineExtensionConstraint->isLocked() && !mXyVertexConstraint->isLocked() )
1151 {
1152 return;
1153 }
1154
1155 // if the first is same actual, not toggle if previously snapped
1156 const int lastIndex = mLockedSnapVertices.length() - 1;
1157 for ( int i = lastIndex ; i >= 0; --i )
1158 {
1159 if ( mLockedSnapVertices[i].point() == snapMatch.point() )
1160 {
1161 if ( snapMatch.point() != previouslySnap.point() )
1162 {
1163 mLockedSnapVertices.removeAt( i );
1164 }
1165 return;
1166 }
1167 }
1168
1169 if ( snapMatch.point() != previouslySnap.point() )
1170 {
1171 mLockedSnapVertices.enqueue( snapMatch );
1172 }
1173
1174 if ( mLockedSnapVertices.count() > 3 )
1175 {
1176 mLockedSnapVertices.dequeue();
1177 }
1178}
1179
1181{
1183 context.snappingUtils = mMapCanvas->snappingUtils();
1184 context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
1185 context.xConstraint = _constraint( mXConstraint.get() );
1186 context.yConstraint = _constraint( mYConstraint.get() );
1187 context.zConstraint = _constraint( mZConstraint.get() );
1188 context.mConstraint = _constraint( mMConstraint.get() );
1189 context.distanceConstraint = _constraint( mDistanceConstraint.get() );
1190 context.angleConstraint = _constraint( mAngleConstraint.get() );
1191 context.snappingToFeaturesOverridesCommonAngle = mSnappingPrioritizeFeatures;
1192
1193 context.lineExtensionConstraint = _constraint( mLineExtensionConstraint.get() );
1194 context.xyVertexConstraint = _constraint( mXyVertexConstraint.get() );
1195
1196 context.setCadPoints( mCadPointList );
1197 context.setLockedSnapVertices( mLockedSnapVertices );
1198
1201 context.commonAngleConstraint.value = mCommonAngleConstraint;
1202
1204
1205 const bool res = output.valid;
1206 QgsPoint point = pointXYToPoint( output.finalMapPoint );
1207 mSnappedSegment.clear();
1208 if ( output.snapMatch.hasEdge() )
1209 {
1210 QgsPointXY edgePt0, edgePt1;
1211 output.snapMatch.edgePoints( edgePt0, edgePt1 );
1212 mSnappedSegment << edgePt0 << edgePt1;
1213 }
1214 if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
1215 {
1216 if ( output.softLockCommonAngle != -1 )
1217 {
1218 mAngleConstraint->setLockMode( CadConstraint::SoftLock );
1219 mAngleConstraint->setValue( output.softLockCommonAngle );
1220 }
1221 else
1222 {
1223 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1224 }
1225 }
1226
1227 mSoftLockLineExtension = output.softLockLineExtension;
1228 mSoftLockX = output.softLockX;
1229 mSoftLockY = output.softLockY;
1230
1231 if ( output.snapMatch.isValid() )
1232 {
1233 mSnapIndicator->setMatch( output.snapMatch );
1234 mSnapIndicator->setVisible( true );
1235 }
1236 else
1237 {
1238 mSnapIndicator->setVisible( false );
1239 }
1240
1241 /*
1242 * Ensure that Z and M are passed
1243 * It will be dropped as needed later.
1244 */
1247
1248 /*
1249 * Constraints are applied in 2D, they are always called when using the tool
1250 * but they do not take into account if when you snap on a vertex it has
1251 * a Z value.
1252 * To get the value we use the snapPoint method. However, we only apply it
1253 * when the snapped point corresponds to the constrained point or on an edge
1254 * if the topological editing is activated. Also, we don't apply it if
1255 * the point is not linked to a layer.
1256 */
1257 e->setMapPoint( point );
1258 mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
1259 if ( mSnapMatch.layer() )
1260 {
1261 if ( ( ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() ) && ( point == mSnapMatch.point() ) )
1262 || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
1263 {
1264 e->snapPoint();
1265 point = mSnapMatch.interpolatedPoint( mMapCanvas->mapSettings().destinationCrs() );
1266 }
1267 }
1268
1269 if ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() )
1270 {
1271 toggleLockedSnapVertex( mSnapMatch, mLastSnapMatch );
1272 mLastSnapMatch = mSnapMatch;
1273 }
1274 else
1275 {
1276 mLastSnapMatch = QgsPointLocator::Match();
1277 }
1278
1279 /*
1280 * And if M or Z lock button is activated get the value of the input.
1281 */
1282 if ( mLockZButton->isChecked() )
1283 {
1284 point.setZ( QLocale().toDouble( mZLineEdit->text() ) );
1285 }
1286 if ( mLockMButton->isChecked() )
1287 {
1288 point.setM( QLocale().toDouble( mMLineEdit->text() ) );
1289 }
1290
1291 // update the point list
1292 updateCurrentPoint( point );
1293
1294 updateUnlockedConstraintValues( point );
1295
1296 if ( res )
1297 {
1298 emit popWarning();
1299 }
1300 else
1301 {
1302 emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
1303 }
1304
1305 return res;
1306}
1307
1308
1309void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPoint &point )
1310{
1311 bool previousPointExist, penulPointExist;
1312 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1313 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1314
1315 // --- angle
1316 if ( !mAngleConstraint->isLocked() && previousPointExist )
1317 {
1318 double prevAngle = 0.0;
1319
1320 if ( penulPointExist && mAngleConstraint->relative() )
1321 {
1322 // previous angle
1323 prevAngle = std::atan2( previousPt.y() - penultimatePt.y(),
1324 previousPt.x() - penultimatePt.x() ) * 180 / M_PI;
1325 }
1326
1327 const double xAngle { std::atan2( point.y() - previousPt.y(),
1328 point.x() - previousPt.x() ) * 180 / M_PI };
1329
1330 // Modulus
1331 const double angle = std::fmod( xAngle - prevAngle, 360.0 );
1332 mAngleConstraint->setValue( angle );
1333
1334 // Bearing (azimuth)
1335 double bearing { std::fmod( xAngle, 360.0 ) };
1336 bearing = bearing <= 90.0 ? 90.0 - bearing : ( bearing > 90 ? 270.0 + 180.0 - bearing : 270.0 - bearing );
1337 const QgsNumericFormatContext context;
1338 const QString bearingText { QgsProject::instance()->displaySettings()->bearingFormat()->formatDouble( bearing, context ) };
1339 emit valueBearingChanged( bearingText );
1340
1341 }
1342 // --- distance
1343 if ( !mDistanceConstraint->isLocked() && previousPointExist )
1344 {
1345 mDistanceConstraint->setValue( std::sqrt( previousPt.distanceSquared( point ) ) );
1346 }
1347 // --- X
1348 if ( !mXConstraint->isLocked() )
1349 {
1350 if ( previousPointExist && mXConstraint->relative() )
1351 {
1352 mXConstraint->setValue( point.x() - previousPt.x() );
1353 }
1354 else
1355 {
1356 mXConstraint->setValue( point.x() );
1357 }
1358 }
1359 // --- Y
1360 if ( !mYConstraint->isLocked() )
1361 {
1362 if ( previousPointExist && mYConstraint->relative() )
1363 {
1364 mYConstraint->setValue( point.y() - previousPt.y() );
1365 }
1366 else
1367 {
1368 mYConstraint->setValue( point.y() );
1369 }
1370 }
1371 // --- Z
1372 if ( !mZConstraint->isLocked() )
1373 {
1374 if ( previousPointExist && mZConstraint->relative() )
1375 {
1376 mZConstraint->setValue( point.z() - previousPt.z() );
1377 }
1378 else
1379 {
1380 mZConstraint->setValue( point.z() );
1381 }
1382 }
1383 // --- M
1384 if ( !mMConstraint->isLocked() )
1385 {
1386 if ( previousPointExist && mMConstraint->relative() )
1387 {
1388 mMConstraint->setValue( point.m() - previousPt.m() );
1389 }
1390 else
1391 {
1392 mMConstraint->setValue( point.m() );
1393 }
1394 }
1395}
1396
1397
1398QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
1399{
1400 QList<QgsPointXY> segment;
1401 QgsPointXY pt1, pt2;
1403
1404 QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
1405
1406 const QgsSnappingConfig canvasConfig = snappingUtils->config();
1407 QgsSnappingConfig localConfig = snappingUtils->config();
1408
1411 snappingUtils->setConfig( localConfig );
1412
1413 match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
1414
1415 snappingUtils->setConfig( canvasConfig );
1416
1417 if ( match.isValid() && match.hasEdge() )
1418 {
1419 match.edgePoints( pt1, pt2 );
1420 segment << pt1 << pt2;
1421 }
1422
1423 if ( snapped )
1424 {
1425 *snapped = segment.count() == 2;
1426 }
1427
1428 return segment;
1429}
1430
1432{
1433 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::NoConstraint )
1434 {
1435 return false;
1436 }
1437
1438 bool previousPointExist, penulPointExist, snappedSegmentExist;
1439 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1440 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1441 mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
1442
1443 if ( !previousPointExist || !snappedSegmentExist )
1444 {
1445 return false;
1446 }
1447
1448 double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
1449
1450 if ( mAngleConstraint->relative() && penulPointExist )
1451 {
1452 angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
1453 }
1454
1455 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::Perpendicular )
1456 {
1457 angle += M_PI_2;
1458 }
1459
1460 angle *= 180 / M_PI;
1461
1462 mAngleConstraint->setValue( angle );
1463 mAngleConstraint->setLockMode( lockMode );
1464 if ( lockMode == CadConstraint::HardLock )
1465 {
1466 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
1467 }
1468
1469 return true;
1470}
1471
1473{
1474 // event on map tool
1475
1476 if ( !mCadEnabled )
1477 return false;
1478
1479 switch ( e->key() )
1480 {
1481 case Qt::Key_Backspace:
1482 case Qt::Key_Delete:
1483 {
1485 releaseLocks( false );
1486 break;
1487 }
1488 case Qt::Key_Escape:
1489 {
1490 releaseLocks();
1491 break;
1492 }
1493 default:
1494 {
1495 keyPressEvent( e );
1496 break;
1497 }
1498 }
1499 // for map tools, continues with key press in any case
1500 return false;
1501}
1502
1504{
1505 clearPoints();
1506 releaseLocks();
1507}
1508
1510{
1511 // event on dock (this)
1512
1513 if ( !mCadEnabled )
1514 return;
1515
1516 switch ( e->key() )
1517 {
1518 case Qt::Key_Backspace:
1519 case Qt::Key_Delete:
1520 {
1522 releaseLocks( false );
1523 break;
1524 }
1525 case Qt::Key_Escape:
1526 {
1527 releaseLocks();
1528 break;
1529 }
1530 default:
1531 {
1532 filterKeyPress( e );
1533 break;
1534 }
1535 }
1536}
1537
1538void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
1539{
1540 clearPoints();
1541 const auto constPoints = points;
1542 for ( const QgsPointXY &pt : constPoints )
1543 {
1544 addPoint( pt );
1545 }
1546}
1547
1548bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
1549{
1550 if ( !cadEnabled() )
1551 {
1552 return QgsDockWidget::eventFilter( obj, event );
1553 }
1554
1555 // event for line edits and map canvas
1556 // we have to catch both KeyPress events and ShortcutOverride events. This is because
1557 // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
1558 // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
1559 // us to intercept these keystrokes before they are caught by the global shortcuts
1560 if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
1561 {
1562 if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
1563 {
1564 return filterKeyPress( keyEvent );
1565 }
1566 }
1567 return QgsDockWidget::eventFilter( obj, event );
1568}
1569
1570bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
1571{
1572 // we need to be careful here -- because this method is called on both KeyPress events AND
1573 // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
1574 // these event types for a single key press. I.e. pressing "A" may first call trigger a
1575 // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
1576 const QEvent::Type type = e->type();
1577 switch ( e->key() )
1578 {
1579 case Qt::Key_X:
1580 {
1581 // modifier+x ONLY caught for ShortcutOverride events...
1582 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1583 {
1584 mXConstraint->toggleLocked();
1585 emit lockXChanged( mXConstraint->isLocked() );
1586 emit pointChangedV2( mCadPointList.value( 0 ) );
1587 e->accept();
1588 }
1589 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1590 {
1591 if ( mCapacities.testFlag( RelativeCoordinates ) )
1592 {
1593 mXConstraint->toggleRelative();
1594 emit relativeXChanged( mXConstraint->relative() );
1595 emit pointChangedV2( mCadPointList.value( 0 ) );
1596 e->accept();
1597 }
1598 }
1599 // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
1600 else if ( type == QEvent::KeyPress )
1601 {
1602 mXLineEdit->setFocus();
1603 mXLineEdit->selectAll();
1604 emit focusOnXRequested();
1605 e->accept();
1606 }
1607 break;
1608 }
1609 case Qt::Key_Y:
1610 {
1611 // modifier+y ONLY caught for ShortcutOverride events...
1612 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1613 {
1614 mYConstraint->toggleLocked();
1615 emit lockYChanged( mYConstraint->isLocked() );
1616 emit pointChangedV2( mCadPointList.value( 0 ) );
1617 e->accept();
1618 }
1619 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1620 {
1621 if ( mCapacities.testFlag( RelativeCoordinates ) )
1622 {
1623 mYConstraint->toggleRelative();
1624 emit relativeYChanged( mYConstraint->relative() );
1625 emit pointChangedV2( mCadPointList.value( 0 ) );
1626 e->accept();
1627 }
1628 }
1629 // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1630 else if ( type == QEvent::KeyPress )
1631 {
1632 mYLineEdit->setFocus();
1633 mYLineEdit->selectAll();
1634 emit focusOnYRequested();
1635 e->accept();
1636 }
1637 break;
1638 }
1639 case Qt::Key_Z:
1640 {
1641 // modifier+z ONLY caught for ShortcutOverride events...
1642 if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::AltModifier )
1643 {
1644 mZConstraint->toggleLocked();
1645 emit lockZChanged( mZConstraint->isLocked() );
1646 emit pointChangedV2( mCadPointList.value( 0 ) );
1647 e->accept();
1648 }
1649 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1650 {
1651 if ( mCapacities.testFlag( RelativeCoordinates ) )
1652 {
1653 mZConstraint->toggleRelative();
1654 emit relativeZChanged( mZConstraint->relative() );
1655 emit pointChangedV2( mCadPointList.value( 0 ) );
1656 e->accept();
1657 }
1658 }
1659 // .. but "z" alone ONLY caught for KeyPress events (see comment at start of function)
1660 else if ( type == QEvent::KeyPress )
1661 {
1662 mZLineEdit->setFocus();
1663 mZLineEdit->selectAll();
1664 emit focusOnZRequested();
1665 e->accept();
1666 }
1667 break;
1668 }
1669 case Qt::Key_M:
1670 {
1671 // modifier+m ONLY caught for ShortcutOverride events...
1672 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1673 {
1674 mMConstraint->toggleLocked();
1675 emit lockMChanged( mMConstraint->isLocked() );
1676 emit pointChangedV2( mCadPointList.value( 0 ) );
1677 e->accept();
1678 }
1679 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1680 {
1681 if ( mCapacities.testFlag( RelativeCoordinates ) )
1682 {
1683 mMConstraint->toggleRelative();
1684 emit relativeMChanged( mMConstraint->relative() );
1685 emit pointChangedV2( mCadPointList.value( 0 ) );
1686 e->accept();
1687 }
1688 }
1689 // .. but "m" alone ONLY caught for KeyPress events (see comment at start of function)
1690 else if ( type == QEvent::KeyPress )
1691 {
1692 mMLineEdit->setFocus();
1693 mMLineEdit->selectAll();
1694 emit focusOnMRequested();
1695 e->accept();
1696 }
1697 break;
1698 }
1699 case Qt::Key_A:
1700 {
1701 // modifier+a ONLY caught for ShortcutOverride events...
1702 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1703 {
1704 if ( mCapacities.testFlag( AbsoluteAngle ) )
1705 {
1706 mAngleConstraint->toggleLocked();
1707 emit lockAngleChanged( mAngleConstraint->isLocked() );
1708 emit pointChangedV2( mCadPointList.value( 0 ) );
1709 e->accept();
1710 }
1711 }
1712 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1713 {
1714 if ( mCapacities.testFlag( RelativeAngle ) )
1715 {
1716 mAngleConstraint->toggleRelative();
1717 emit relativeAngleChanged( mAngleConstraint->relative() );
1718 emit pointChangedV2( mCadPointList.value( 0 ) );
1719 e->accept();
1720 }
1721 }
1722 // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
1723 else if ( type == QEvent::KeyPress )
1724 {
1725 mAngleLineEdit->setFocus();
1726 mAngleLineEdit->selectAll();
1727 emit focusOnAngleRequested();
1728 e->accept();
1729 }
1730 break;
1731 }
1732 case Qt::Key_D:
1733 {
1734 // modifier+d ONLY caught for ShortcutOverride events...
1735 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1736 {
1737 if ( mCapacities.testFlag( RelativeCoordinates ) && mCapacities.testFlag( Distance ) )
1738 {
1739 mDistanceConstraint->toggleLocked();
1740 emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1741 emit pointChangedV2( mCadPointList.value( 0 ) );
1742 e->accept();
1743 }
1744 }
1745 // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
1746 else if ( type == QEvent::KeyPress )
1747 {
1748 mDistanceLineEdit->setFocus();
1749 mDistanceLineEdit->selectAll();
1751 e->accept();
1752 }
1753 break;
1754 }
1755 case Qt::Key_C:
1756 {
1757 if ( type == QEvent::KeyPress )
1758 {
1759 setConstructionMode( !mConstructionMode );
1760 e->accept();
1761 }
1762 break;
1763 }
1764 case Qt::Key_P:
1765 {
1766 if ( type == QEvent::KeyPress )
1767 {
1768 const bool parallel = mParallelAction->isChecked();
1769 const bool perpendicular = mPerpendicularAction->isChecked();
1770
1771 if ( !parallel && !perpendicular )
1772 {
1773 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
1774 }
1775 else if ( perpendicular )
1776 {
1777 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
1778 }
1779 else
1780 {
1781 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1782 }
1783 e->accept();
1784
1785 // run a fake map mouse event to update the paint item
1786 emit pointChangedV2( mCadPointList.value( 0 ) );
1787 }
1788 break;
1789 }
1790 case Qt::Key_N:
1791 {
1792 if ( type == QEvent::ShortcutOverride )
1793 {
1794 const QList<double> constActionKeys { mCommonAngleActions.keys() };
1795 const int currentAngleActionIndex { static_cast<int>( constActionKeys .indexOf( mCommonAngleConstraint ) ) };
1796 const QList<QAction *> constActions { mCommonAngleActions.values( ) };
1797 QAction *nextAngleAction;
1798 if ( e->modifiers() == Qt::ShiftModifier )
1799 {
1800 nextAngleAction = currentAngleActionIndex == 0 ? constActions.last() : constActions.at( currentAngleActionIndex - 1 );
1801 }
1802 else
1803 {
1804 nextAngleAction = currentAngleActionIndex == constActions.count() - 1 ? constActions.first() : constActions.at( currentAngleActionIndex + 1 );
1805 }
1806 nextAngleAction->trigger();
1807 e->accept();
1808 }
1809 break;
1810 }
1811 default:
1812 {
1813 return false; // continues
1814 }
1815 }
1816 return e->isAccepted();
1817}
1818
1820{
1821 // most of theses lines can be moved to updateCapacity
1822 connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
1823 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
1824 {
1825 mAngleLineEdit->setToolTip( tr( "Angle constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1826 mDistanceLineEdit->setToolTip( tr( "Distance constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1827
1828 mLabelX->setText( tr( "Long" ) );
1829 mLabelY->setText( tr( "Lat" ) );
1830
1831 mXConstraint->setPrecision( 8 );
1832 mYConstraint->setPrecision( 8 );
1833 }
1834 else
1835 {
1836 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
1837 mAngleLineEdit->setToolTip( QString() );
1838
1839 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
1840
1841 mLabelX->setText( tr( "x" ) );
1842 mLabelY->setText( tr( "y" ) );
1843
1844 mXConstraint->setPrecision( 6 );
1845 mYConstraint->setPrecision( 6 );
1846 }
1847
1848 updateCapacity();
1849
1850 mEnableAction->setEnabled( true );
1851 mErrorLabel->hide();
1852 mCadWidget->show();
1853
1854 mCurrentMapToolSupportsCad = true;
1855
1856 if ( mSessionActive && !isVisible() )
1857 {
1858 show();
1859 }
1860 setCadEnabled( mSessionActive );
1861}
1862
1864{
1866
1867 mEnableAction->setEnabled( false );
1868 mErrorLabel->setText( tr( "Advanced digitizing tools are not enabled for the current map tool" ) );
1869 mErrorLabel->show();
1870 mCadWidget->hide();
1871
1872 mCurrentMapToolSupportsCad = false;
1873
1874 mSnapIndicator->setVisible( false );
1875
1876 setCadEnabled( false );
1877}
1878
1880{
1881 mCadPaintItem->update();
1882}
1883
1885{
1886 if ( !force && ( mLineExtensionConstraint->isLocked() || mXyVertexConstraint->isLocked() ) )
1887 {
1888 return;
1889 }
1890
1891 mLockedSnapVertices.clear();
1892}
1893
1894
1896{
1897 QgsPoint pt = pointXYToPoint( point );
1898 if ( !pointsCount() )
1899 {
1900 mCadPointList << pt;
1901 }
1902 else
1903 {
1904 mCadPointList.insert( 0, pt );
1905 }
1906
1907 updateCapacity();
1909}
1910
1912{
1913 if ( !pointsCount() )
1914 return;
1915
1916 const int i = pointsCount() > 1 ? 1 : 0;
1917 mCadPointList.removeAt( i );
1918 updateCapacity();
1920}
1921
1923{
1924 mCadPointList.clear();
1925 mSnappedSegment.clear();
1926
1927 updateCapacity();
1929}
1930
1932{
1933 if ( !pointsCount() )
1934 {
1935 mCadPointList << point;
1936 updateCapacity();
1937 }
1938 else
1939 {
1940 mCadPointList[0] = point;
1941 }
1943}
1944
1946{
1947 if ( mode == mLockMode )
1948 {
1949 return;
1950 }
1951 mLockMode = mode;
1952 mLockerButton->setChecked( mode == HardLock );
1953 if ( mRepeatingLockButton )
1954 {
1955 if ( mode == HardLock )
1956 {
1957 mRepeatingLockButton->setEnabled( true );
1958 }
1959 else
1960 {
1961 mRepeatingLockButton->setChecked( false );
1962 mRepeatingLockButton->setEnabled( false );
1963 }
1964 }
1965
1966 if ( mode == NoLock )
1967 {
1968 mLineEdit->clear();
1969 }
1970
1971}
1972
1974{
1975 mRepeatingLock = repeating;
1976 if ( mRepeatingLockButton )
1977 mRepeatingLockButton->setChecked( repeating );
1978}
1979
1981{
1982 mRelative = relative;
1983 if ( mRelativeButton )
1984 {
1985 mRelativeButton->setChecked( relative );
1986 }
1987}
1988
1990{
1991 mValue = value;
1992 if ( updateWidget && mLineEdit->isEnabled() )
1993 mLineEdit->setText( displayValue() );
1994}
1995
1997{
1998 switch ( mCadConstraintType )
1999 {
2001 {
2002 return QLocale().toString( mValue, 'f', mPrecision ).append( tr( " °" ) );
2003 }
2006 {
2007 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
2008 {
2009 return QLocale().toString( mValue, 'f', mPrecision ).append( tr( " °" ) );
2010 }
2011 else
2012 {
2013 return QLocale().toString( mValue, 'f', mPrecision );
2014 }
2015 }
2017 {
2019 return QgsDistanceArea::formatDistance( mValue, mPrecision, units, true );
2020 }
2024 default:
2025 break;
2026 }
2027 return QLocale().toString( mValue, 'f', mPrecision );
2028}
2029
2031{
2032 setLockMode( mLockMode == HardLock ? NoLock : HardLock );
2033}
2034
2036{
2037 setRelative( !mRelative );
2038}
2039
2041{
2042 mPrecision = precision;
2043 if ( mLineEdit->isEnabled() )
2044 mLineEdit->setText( displayValue() );
2045}
2046
2048{
2049 return mCadConstraintType;
2050}
2051
2053{
2054 mCadConstraintType = constraintType;
2055}
2056
2058{
2059 mMapCanvas = mapCanvas;
2060}
2061
2063{
2064 QString value { text.trimmed() };
2065 switch ( constraintType )
2066 {
2068 {
2069 // Remove distance unit suffix
2070 const QString distanceUnit { QgsUnitTypes::toAbbreviatedString( QgsProject::instance()->distanceUnits() ) };
2071 if ( value.endsWith( distanceUnit ) )
2072 {
2073 value.chop( distanceUnit.length() );
2074 }
2075 break;
2076 }
2078 {
2079 // Remove angle suffix
2080 const QString angleUnit { tr( "°" ) };
2081 if ( value.endsWith( angleUnit ) )
2082 {
2083 value.chop( angleUnit.length() );
2084 }
2085 break;
2086 }
2087 default:
2088 break;
2089 }
2090 return value.trimmed();
2091}
2092
2094{
2095 if ( exist )
2096 *exist = pointsCount() > 0;
2097 if ( pointsCount() > 0 )
2098 return mCadPointList.value( 0 );
2099 else
2100 return QgsPoint();
2101}
2102
2104{
2105 if ( pointsCount() > 0 && layer )
2106 {
2107 QgsPoint res = mCadPointList.value( 0 );
2108 const QgsPointXY layerCoordinates = mMapCanvas->mapSettings().mapToLayerCoordinates( layer, res );
2109 res.setX( layerCoordinates.x() );
2110 res.setY( layerCoordinates.y() );
2111 return res;
2112 }
2113 return QgsPoint();
2114}
2115
2117{
2118 if ( exist )
2119 *exist = pointsCount() > 1;
2120 if ( pointsCount() > 1 )
2121 return mCadPointList.value( 1 );
2122 else
2123 return QgsPoint();
2124}
2125
2127{
2128 if ( exist )
2129 *exist = pointsCount() > 2;
2130 if ( pointsCount() > 2 )
2131 return mCadPointList.value( 2 );
2132 else
2133 return QgsPoint();
2134}
2135
2136QgsPoint QgsAdvancedDigitizingDockWidget::pointXYToPoint( const QgsPointXY &point ) const
2137{
2138 return QgsPoint( point.x(), point.y(), getLineZ(), getLineM() );
2139}
2140
2142{
2143 return mZLineEdit->isEnabled() ? QLocale().toDouble( mZLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
2144}
2145
2147{
2148 return mMLineEdit->isEnabled() ? QLocale().toDouble( mMLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
2149}
@ Segment
On segments.
DistanceUnit
Units of distance.
Definition: qgis.h:4124
CadConstraintType
Advanced digitizing constraint type.
Definition: qgis.h:3329
@ Distance
Distance value.
@ Generic
Generic value.
@ YCoordinate
Y Coordinate value.
@ XCoordinate
X Coordinate value.
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
@ AllLayers
On all vector layers.
BetweenLineConstraint
Between line constraints which can be enabled.
Definition: qgis.h:3303
@ NoConstraint
No additional constraint.
@ Perpendicular
Perpendicular.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:182
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (.
void updatePosition() override
called on changed extent or resize event to update position of the item
The CadConstraint is a class for all basic constraints (angle/distance/x/y).
Qgis::CadConstraintType cadConstraintType() const
Returns the constraint type.
void setPrecision(int precision)
Sets the numeric precision (decimal places) to show in the associated widget.
static QString removeSuffix(const QString &text, Qgis::CadConstraintType constraintType)
Removes unit suffix from the constraint text.
QString displayValue() const
Returns a localized formatted string representation of the value.
void setRepeatingLock(bool repeating)
Sets whether a repeating lock is set for the constraint.
void setCadConstraintType(Qgis::CadConstraintType constraintType)
Sets the constraint type to constraintType.
void setRelative(bool relative)
Set if the constraint should be treated relative.
void setValue(double value, bool updateWidget=true)
Set the value of the constraint.
void setMapCanvas(QgsMapCanvas *mapCanvas)
Sets the map canvas to mapCanvas.
void valueDistanceChanged(const QString &value)
Emitted whenever the distance value changes (either the mouse moved, or the user changed the input).
void lockZChanged(bool locked)
Emitted whenever the Z parameter is locked.
void setEnabledZ(bool enable)
Sets whether Z is enabled.
void setPoints(const QList< QgsPointXY > &points)
Configures list of current CAD points.
void setZ(const QString &value, WidgetSetMode mode)
Set the Z value on the widget.
void setY(const QString &value, WidgetSetMode mode)
Set the Y value on the widget.
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
bool applyConstraints(QgsMapMouseEvent *e)
apply the CAD constraints.
void setEnabledM(bool enable)
Sets whether M is enabled.
int pointsCount() const
The number of points in the CAD point helper list.
void addPoint(const QgsPointXY &point)
Adds point to the CAD point list.
void releaseLocks(bool releaseRepeatingLocks=true)
unlock all constraints
void switchZM()
Determines if Z or M will be enabled.
void relativeMChanged(bool relative)
Emitted whenever the M parameter is toggled between absolute and relative.
void lockXChanged(bool locked)
Emitted whenever the X parameter is locked.
void softLockLineExtensionChanged(bool locked)
Emitted whenever the soft line extension parameter is locked.
void focusOnXRequested()
Emitted whenever the X field should get the focus using the shortcuts (X).
void valueYChanged(const QString &value)
Emitted whenever the Y value changes (either the mouse moved, or the user changed the input).
QString formatCommonAngleSnapping(double angle)
Returns the formatted label for common angle snapping option.
void focusOnYRequested()
Emitted whenever the Y field should get the focus using the shortcuts (Y).
double getLineM() const
Convenient method to get the M value from the line edit wiget.
void enabledChangedDistance(bool enabled)
Emitted whenever the distance field is enabled or disabled.
void valueZChanged(const QString &value)
Emitted whenever the Z value changes (either the mouse moved, or the user changed the input).
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 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 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).
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 valueMChanged(const QString &value)
Emitted whenever the M value changes (either the mouse moved, or the user changed the input).
void enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
void relativeZChanged(bool relative)
Emitted whenever the Z parameter is toggled between absolute and relative.
@ RelativeAngle
Also for parallel and perpendicular.
@ RelativeCoordinates
This corresponds to distance and relative coordinates.
bool canvasKeyPressEventFilter(QKeyEvent *e)
Filter key events to e.g.
void setAngle(const QString &value, WidgetSetMode mode)
Set the angle value on the widget.
void enabledChangedAngle(bool enabled)
Emitted whenever the angle field is enabled or disabled.
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.
QgsAdvancedDigitizingDockWidget(QgsMapCanvas *canvas, QWidget *parent=nullptr)
Create an advanced digitizing dock widget.
void clear()
Clear any cached previous clicks and helper lines.
void focusOnDistanceRequested()
Emitted whenever the distance field should get the focus using the shortcuts (D).
QgsPoint currentPointLayerCoordinates(QgsMapLayer *layer) const
Returns the last CAD point, in a map layer's coordinates.
void valueCommonAngleSnappingChanged(double angle)
Emitted whenever the snapping to common angle option changes, angle = 0 means that the functionality ...
void setM(const QString &value, WidgetSetMode mode)
Set the M value on the widget.
void pushWarning(const QString &message)
Push a warning.
void clearLockedSnapVertices(bool force=true)
Removes all points from the locked snap vertex list.
void relativeYChanged(bool relative)
Emitted whenever the Y parameter is toggled between absolute and relative.
The QgsAdvancedDigitizingFloater class is widget that floats next to the mouse pointer,...
void setItemVisibility(const QgsAdvancedDigitizingFloater::FloaterItem &item, bool visible)
Set whether the floater item should be visible or not.
void setActive(bool active)
Set whether the floater should be active or not.
bool active()
Whether the floater is active or not.
QString formatDouble(double value, const QgsNumericFormatContext &context) const override
Returns a formatted string representation of a numeric double value.
Structure with details of one constraint.
Definition: qgscadutils.h:42
bool locked
Whether the constraint is active, i.e. should be considered.
Definition: qgscadutils.h:55
double value
Numeric value of the constraint (coordinate/distance in map units or angle in degrees)
Definition: qgscadutils.h:59
bool relative
Whether the value is relative to previous value.
Definition: qgscadutils.h:57
Defines constraints for the QgsCadUtils::alignMapPoint() method.
Definition: qgscadutils.h:100
QgsCadUtils::AlignMapPointConstraint xyVertexConstraint
Definition: qgscadutils.h:131
QgsCadUtils::AlignMapPointConstraint yConstraint
Constraint for Y coordinate.
Definition: qgscadutils.h:110
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
Definition: qgscadutils.h:108
double mapUnitsPerPixel
Map units/pixel ratio from map canvas.
Definition: qgscadutils.h:105
void setCadPoints(const QList< QgsPoint > &points)
Sets the list of recent CAD points (in map coordinates).
Definition: qgscadutils.h:162
void setLockedSnapVertices(const QQueue< QgsPointLocator::Match > &lockedSnapVertices)
Sets the queue of locked vertices.
Definition: qgscadutils.h:188
QgsCadUtils::AlignMapPointConstraint mConstraint
Constraint for M coordinate.
Definition: qgscadutils.h:122
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
Definition: qgscadutils.h:124
bool snappingToFeaturesOverridesCommonAngle
Flag to set snapping to features priority over common angle.
Definition: qgscadutils.h:137
QgsCadUtils::AlignMapPointConstraint zConstraint
Constraint for Z coordinate.
Definition: qgscadutils.h:116
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be nullptr.
Definition: qgscadutils.h:103
QgsCadUtils::AlignMapPointConstraint commonAngleConstraint
Constraint for soft lock to a common angle.
Definition: qgscadutils.h:128
QgsCadUtils::AlignMapPointConstraint lineExtensionConstraint
Definition: qgscadutils.h:130
QgsCadUtils::AlignMapPointConstraint angleConstraint
Constraint for angle.
Definition: qgscadutils.h:126
Structure returned from alignMapPoint() method.
Definition: qgscadutils.h:67
Qgis::LineExtensionSide softLockLineExtension
Definition: qgscadutils.h:90
QgsPointXY finalMapPoint
map point aligned according to the constraints
Definition: qgscadutils.h:73
bool valid
Whether the combination of constraints is actually valid.
Definition: qgscadutils.h:70
QgsPointLocator::Match snapMatch
Snapped point - only valid if actually used for something.
Definition: qgscadutils.h:79
double softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition: qgscadutils.h:88
static QgsCadUtils::AlignMapPointOutput alignMapPoint(const QgsPointXY &originalMapPoint, const QgsCadUtils::AlignMapPointContext &ctx)
Applies X/Y/angle/distance constraints from the given context to a map point.
Definition: qgscadutils.cpp:39
static QString formatDistance(double distance, int decimals, Qgis::DistanceUnit unit, bool keepBaseUnit=false)
Returns an distance formatted as a friendly string.
QgsDockWidget subclass with more fine-grained control over how the widget is closed or opened.
Definition: qgsdockwidget.h:31
Class for parsing and evaluation of expressions (formerly called "search strings").
A event filter for watching for focus events on a parent object.
void focusIn()
Emitted when parent object gains focus.
void focusOut()
Emitted when parent object loses focus.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:93
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
void destinationCrsChanged()
Emitted when map CRS has changed.
QgsMapTool * mapTool()
Returns the currently active tool.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
Base class for all map layer types.
Definition: qgsmaplayer.h:75
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
QgsPointXY originalMapPoint() const
Returns the original, unmodified map point of the mouse cursor.
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
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
The QgsMapToolAdvancedDigitizing class is a QgsMapTool which gives event directly in map coordinates ...
static double defaultMValue()
Returns default M value.
static double defaultZValue()
Returns default Z value.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:101
bool isEditable() const override
Returns true if the layer can be edited.
A context for numeric formats.
A class to represent a 2D point.
Definition: qgspointxy.h:60
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
void setY(double y)
Sets the point's y-coordinate.
Definition: qgspoint.h:343
void setX(double x)
Sets the point's x-coordinate.
Definition: qgspoint.h:332
Q_GADGET double x
Definition: qgspoint.h:52
double z
Definition: qgspoint.h:54
void setM(double m)
Sets the point's m-value.
Definition: qgspoint.h:371
void setZ(double z)
Sets the point's z-coordinate.
Definition: qgspoint.h:356
double distanceSquared(double x, double y) const
Returns the Cartesian 2D squared distance between this point a specified x, y coordinate.
Definition: qgspoint.h:415
double m
Definition: qgspoint.h:55
double y
Definition: qgspoint.h:53
const QgsBearingNumericFormat * bearingFormat() const
Returns the project bearing's format, which controls how bearings associated with the project are dis...
Qgis::DistanceUnit distanceUnits
Definition: qgsproject.h:124
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:116
QgsProjectDisplaySettings * displaySettings
Definition: qgsproject.h:126
bool topologicalEditing
Definition: qgsproject.h:123
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
bool setValue(const T &value, const QString &dynamicKeyPart=QString()) const
Set settings value.
A boolean settings entry.
static QgsSettingsTreeNode * sTreeDigitizing
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Class that shows snapping marker on map canvas for the current snapping match.
This is a container for configuration of the snapping of the project.
void setTypeFlag(Qgis::SnappingTypes type)
define the type of snapping
void setMode(Qgis::SnappingMode mode)
define the mode of snapping
This class has all the configuration of snapping and can return answers to snapping queries.
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 QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
static bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:973
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1023
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)
Definition: MathUtils.cpp:716
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
double qgsPermissiveToDouble(QString string, bool &ok)
Converts a string to a double in a permissive way, e.g., allowing for incorrect numbers of digits bet...
Definition: qgis.cpp:72
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:5111
QLineF segment(int index, QRectF rect, double radius)
int precision
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsPoint interpolatedPoint(const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem()) const
Convenient method to return a point on an edge with linear interpolation of the Z value.
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords,...
bool hasEdge() const
Returns true if the Match is an edge.
void edgePoints(QgsPointXY &pt1, QgsPointXY &pt2) const
Only for a valid edge match - obtain endpoints of the edge.
bool hasLineEndpoint() const
Returns true if the Match is a line endpoint (start or end vertex).
bool hasVertex() const
Returns true if the Match is a vertex.