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