QGIS API Documentation 3.27.0-Master (0a97e3138f)
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
25#include "qgsapplication.h"
26#include "qgscadutils.h"
27#include "qgsexpression.h"
28#include "qgslogger.h"
29#include "qgsmapcanvas.h"
30#include "qgsmaptooledit.h"
31#include "qgsmaptoolcapture.h"
33#include "qgsmessagebaritem.h"
34#include "qgslinestring.h"
35#include "qgsfocuswatcher.h"
36#include "qgssettings.h"
37#include "qgssnappingutils.h"
38#include "qgsproject.h"
39#include "qgsmapmouseevent.h"
40#include "qgsmessagelog.h"
41#include "qgsmeshlayer.h"
42
43#include <QActionGroup>
44
45
47 : QgsDockWidget( parent )
48 , mMapCanvas( canvas )
49 , mSnapIndicator( std::make_unique< QgsSnapIndicator>( canvas ) )
50 , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 0.0 ).toDouble() )
51{
52 setupUi( this );
53
54 mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this );
55
56 mAngleConstraint.reset( new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton, mRepeatingLockAngleButton ) );
57 mDistanceConstraint.reset( new CadConstraint( mDistanceLineEdit, mLockDistanceButton, nullptr, mRepeatingLockDistanceButton ) );
58 mXConstraint.reset( new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton, mRepeatingLockXButton ) );
59 mYConstraint.reset( new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton, mRepeatingLockYButton ) );
60 mZConstraint.reset( new CadConstraint( mZLineEdit, mLockZButton, mRelativeZButton, mRepeatingLockZButton ) );
61 mMConstraint.reset( new CadConstraint( mMLineEdit, mLockMButton, mRelativeMButton, mRepeatingLockMButton ) );
62
63 mLineExtensionConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
64 mXyVertexConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
65
66 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
67
68 mMapCanvas->installEventFilter( this );
69 mAngleLineEdit->installEventFilter( this );
70 mDistanceLineEdit->installEventFilter( this );
71 mXLineEdit->installEventFilter( this );
72 mYLineEdit->installEventFilter( this );
73 mZLineEdit->installEventFilter( this );
74 mMLineEdit->installEventFilter( this );
75
76 // Connect the UI to the event filter to update constraints
77 connect( mEnableAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::activateCad );
78 connect( mConstructionModeAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode );
79 connect( mParallelAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
80 connect( mPerpendicularAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
81 connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
82 connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
83 connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
84 connect( mLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
85 connect( mLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
86 connect( mLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
87 connect( mRelativeAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
88 connect( mRelativeXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
89 connect( mRelativeYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
90 connect( mRelativeZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
91 connect( mRelativeMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
92 connect( mRepeatingLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
93 connect( mRepeatingLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
94 connect( mRepeatingLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
95 connect( mRepeatingLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
96 connect( mRepeatingLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
97 connect( mRepeatingLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
98 connect( mAngleLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
99 connect( mDistanceLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
100 connect( mXLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
101 connect( mYLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
102 connect( mZLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
103 connect( mMLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
104 connect( mAngleLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
105 connect( mDistanceLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
106 connect( mXLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
107 connect( mYLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
108 connect( mZLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
109 connect( mMLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
110 //also watch for focus out events on these widgets
111 QgsFocusWatcher *angleWatcher = new QgsFocusWatcher( mAngleLineEdit );
112 connect( angleWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
113 QgsFocusWatcher *distanceWatcher = new QgsFocusWatcher( mDistanceLineEdit );
114 connect( distanceWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
115 QgsFocusWatcher *xWatcher = new QgsFocusWatcher( mXLineEdit );
116 connect( xWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
117 QgsFocusWatcher *yWatcher = new QgsFocusWatcher( mYLineEdit );
118 connect( yWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
119 QgsFocusWatcher *zWatcher = new QgsFocusWatcher( mZLineEdit );
120 connect( zWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
121 QgsFocusWatcher *mWatcher = new QgsFocusWatcher( mMLineEdit );
122 connect( mWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
123
124 // config menu
125 QMenu *menu = new QMenu( this );
126 // common angles
127 QActionGroup *angleButtonGroup = new QActionGroup( menu ); // actions are exclusive for common angles
128 mCommonAngleActions = QMap<QAction *, double>();
129 QList< QPair< double, QString > > commonAngles;
130 QString menuText;
131 const QList<double> anglesDouble( { 0.0, 5.0, 10.0, 15.0, 18.0, 22.5, 30.0, 45.0, 90.0} );
132 for ( QList<double>::const_iterator it = anglesDouble.constBegin(); it != anglesDouble.constEnd(); ++it )
133 {
134 if ( *it == 0 )
135 menuText = tr( "Do Not Snap to Common Angles" );
136 else
137 menuText = QString( tr( "%1, %2, %3, %4°…" ) ).arg( *it, 0, 'f', 1 ).arg( *it * 2, 0, 'f', 1 ).arg( *it * 3, 0, 'f', 1 ).arg( *it * 4, 0, 'f', 1 );
138 commonAngles << QPair<double, QString>( *it, menuText );
139 }
140 for ( QList< QPair<double, QString > >::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it )
141 {
142 QAction *action = new QAction( it->second, menu );
143 action->setCheckable( true );
144 action->setChecked( it->first == mCommonAngleConstraint );
145 menu->addAction( action );
146 angleButtonGroup->addAction( action );
147 mCommonAngleActions.insert( action, it->first );
148 }
149
150 qobject_cast< QToolButton *>( mToolbar->widgetForAction( mSettingsAction ) )->setPopupMode( QToolButton::InstantPopup );
151 mSettingsAction->setMenu( menu );
152 mSettingsAction->setCheckable( true );
153 mSettingsAction->setToolTip( tr( "Snap to common angles" ) );
154 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
155 connect( menu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
156
157 // Construction modes
158 QMenu *constructionMenu = new QMenu( this );
159
160 mLineExtensionAction = new QAction( "Line Extension", constructionMenu );
161 mLineExtensionAction->setCheckable( true );
162 constructionMenu->addAction( mLineExtensionAction );
163 connect( mLineExtensionAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
164
165 mXyVertexAction = new QAction( "X/Y Point", constructionMenu );
166 mXyVertexAction->setCheckable( true );
167 constructionMenu->addAction( mXyVertexAction );
168 connect( mXyVertexAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
169
170 auto constructionToolBar = qobject_cast< QToolButton *>( mToolbar->widgetForAction( mConstructionAction ) );
171 constructionToolBar->setPopupMode( QToolButton::InstantPopup );
172 constructionToolBar->setMenu( constructionMenu );
173 constructionToolBar->setObjectName( QStringLiteral( "ConstructionButton" ) );
174
175 mConstructionAction->setMenu( menu );
176 mConstructionAction->setCheckable( true );
177 mConstructionAction->setToolTip( tr( "Construction Tools" ) );
178// connect( constructionMenu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
179
180 // set tooltips
181 mConstructionModeAction->setToolTip( "<b>" + tr( "Construction mode" ) + "</b><br>(" + tr( "press c to toggle on/off" ) + ")" );
182 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
183 mLockDistanceButton->setToolTip( "<b>" + tr( "Lock distance" ) + "</b><br>(" + tr( "press Ctrl + d for quick access" ) + ")" );
184 mRepeatingLockDistanceButton->setToolTip( "<b>" + tr( "Continuously lock distance" ) + "</b>" );
185
186 mRelativeAngleButton->setToolTip( "<b>" + tr( "Toggles relative angle to previous segment" ) + "</b><br>(" + tr( "press Shift + a for quick access" ) + ")" );
187 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
188 mLockAngleButton->setToolTip( "<b>" + tr( "Lock angle" ) + "</b><br>(" + tr( "press Ctrl + a for quick access" ) + ")" );
189 mRepeatingLockAngleButton->setToolTip( "<b>" + tr( "Continuously lock angle" ) + "</b>" );
190
191 mRelativeXButton->setToolTip( "<b>" + tr( "Toggles relative x to previous node" ) + "</b><br>(" + tr( "press Shift + x for quick access" ) + ")" );
192 mXLineEdit->setToolTip( "<b>" + tr( "X coordinate" ) + "</b><br>(" + tr( "press x for quick access" ) + ")" );
193 mLockXButton->setToolTip( "<b>" + tr( "Lock x coordinate" ) + "</b><br>(" + tr( "press Ctrl + x for quick access" ) + ")" );
194 mRepeatingLockXButton->setToolTip( "<b>" + tr( "Continuously lock x coordinate" ) + "</b>" );
195
196 mRelativeYButton->setToolTip( "<b>" + tr( "Toggles relative y to previous node" ) + "</b><br>(" + tr( "press Shift + y for quick access" ) + ")" );
197 mYLineEdit->setToolTip( "<b>" + tr( "Y coordinate" ) + "</b><br>(" + tr( "press y for quick access" ) + ")" );
198 mLockYButton->setToolTip( "<b>" + tr( "Lock y coordinate" ) + "</b><br>(" + tr( "press Ctrl + y for quick access" ) + ")" );
199 mRepeatingLockYButton->setToolTip( "<b>" + tr( "Continuously lock y coordinate" ) + "</b>" );
200
201 mRelativeZButton->setToolTip( "<b>" + tr( "Toggles relative z to previous node" ) + "</b><br>(" + tr( "press Shift + z for quick access" ) + ")" );
202 mZLineEdit->setToolTip( "<b>" + tr( "Z coordinate" ) + "</b><br>(" + tr( "press z for quick access" ) + ")" );
203 mLockZButton->setToolTip( "<b>" + tr( "Lock z coordinate" ) + "</b><br>(" + tr( "press Ctrl + z for quick access" ) + ")" );
204 mRepeatingLockZButton->setToolTip( "<b>" + tr( "Continuously lock z coordinate" ) + "</b>" );
205
206 mRelativeMButton->setToolTip( "<b>" + tr( "Toggles relative m to previous node" ) + "</b><br>(" + tr( "press Shift + m for quick access" ) + ")" );
207 mMLineEdit->setToolTip( "<b>" + tr( "M coordinate" ) + "</b><br>(" + tr( "press m for quick access" ) + ")" );
208 mLockMButton->setToolTip( "<b>" + tr( "Lock m coordinate" ) + "</b><br>(" + tr( "press Ctrl + m for quick access" ) + ")" );
209 mRepeatingLockMButton->setToolTip( "<b>" + tr( "Continuously lock m coordinate" ) + "</b>" );
210
211 // Create the slots/signals
212 connect( mXLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueXChanged );
213 connect( mYLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueYChanged );
214 connect( mZLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueZChanged );
215 connect( mMLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueMChanged );
216 connect( mDistanceLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueDistanceChanged );
217 connect( mAngleLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueAngleChanged );
218
219 // Create the floater
220 mFloater = new QgsAdvancedDigitizingFloater( canvas, this );
221 connect( mToggleFloaterAction, &QAction::triggered, mFloater, &QgsAdvancedDigitizingFloater::setActive );
222 mToggleFloaterAction->setChecked( mFloater->active() );
223
224 updateCapacity( true );
225 connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, [ = ] { updateCapacity( true ); } );
226
227 disable();
228}
229
230void QgsAdvancedDigitizingDockWidget::setX( const QString &value, WidgetSetMode mode )
231{
232 mXLineEdit->setText( value );
233 if ( mode == WidgetSetMode::ReturnPressed )
234 {
235 emit mXLineEdit->returnPressed();
236 }
237 else if ( mode == WidgetSetMode::FocusOut )
238 {
239 QEvent *e = new QEvent( QEvent::FocusOut );
240 QCoreApplication::postEvent( mXLineEdit, e );
241 }
242 else if ( mode == WidgetSetMode::TextEdited )
243 {
244 emit mXLineEdit->textEdited( value );
245 }
246}
247void QgsAdvancedDigitizingDockWidget::setY( const QString &value, WidgetSetMode mode )
248{
249 mYLineEdit->setText( value );
250 if ( mode == WidgetSetMode::ReturnPressed )
251 {
252 emit mYLineEdit->returnPressed();
253 }
254 else if ( mode == WidgetSetMode::FocusOut )
255 {
256 QEvent *e = new QEvent( QEvent::FocusOut );
257 QCoreApplication::postEvent( mYLineEdit, e );
258 }
259 else if ( mode == WidgetSetMode::TextEdited )
260 {
261 emit mYLineEdit->textEdited( value );
262 }
263}
264void QgsAdvancedDigitizingDockWidget::setZ( const QString &value, WidgetSetMode mode )
265{
266 mZLineEdit->setText( value );
267 if ( mode == WidgetSetMode::ReturnPressed )
268 {
269 emit mZLineEdit->returnPressed();
270 }
271 else if ( mode == WidgetSetMode::FocusOut )
272 {
273 QEvent *e = new QEvent( QEvent::FocusOut );
274 QCoreApplication::postEvent( mZLineEdit, e );
275 }
276 else if ( mode == WidgetSetMode::TextEdited )
277 {
278 emit mZLineEdit->textEdited( value );
279 }
280}
281void QgsAdvancedDigitizingDockWidget::setM( const QString &value, WidgetSetMode mode )
282{
283 mMLineEdit->setText( value );
284 if ( mode == WidgetSetMode::ReturnPressed )
285 {
286 emit mMLineEdit->returnPressed();
287 }
288 else if ( mode == WidgetSetMode::FocusOut )
289 {
290 QEvent *e = new QEvent( QEvent::FocusOut );
291 QCoreApplication::postEvent( mMLineEdit, e );
292 }
293 else if ( mode == WidgetSetMode::TextEdited )
294 {
295 emit mMLineEdit->textEdited( value );
296 }
297}
299{
300 mAngleLineEdit->setText( value );
301 if ( mode == WidgetSetMode::ReturnPressed )
302 {
303 emit mAngleLineEdit->returnPressed();
304 }
305 else if ( mode == WidgetSetMode::FocusOut )
306 {
307 emit mAngleLineEdit->textEdited( value );
308 }
309}
311{
312 mDistanceLineEdit->setText( value );
313 if ( mode == WidgetSetMode::ReturnPressed )
314 {
315 emit mDistanceLineEdit->returnPressed();
316 }
317 else if ( mode == WidgetSetMode::FocusOut )
318 {
319 QEvent *e = new QEvent( QEvent::FocusOut );
320 QCoreApplication::postEvent( mDistanceLineEdit, e );
321 }
322 else if ( mode == WidgetSetMode::TextEdited )
323 {
324 emit mDistanceLineEdit->textEdited( value );
325 }
326}
327
328
329void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
330{
331 mCadEnabled = enabled;
332 mEnableAction->setChecked( enabled );
333 mConstructionModeAction->setEnabled( enabled );
334 mSettingsAction->setEnabled( enabled );
335 mInputWidgets->setEnabled( enabled );
336 mToggleFloaterAction->setEnabled( enabled );
337 mConstructionAction->setEnabled( enabled );
338
339 if ( !enabled )
340 {
341 // uncheck at deactivation
342 mLineExtensionAction->setChecked( false );
343 mXyVertexAction->setChecked( false );
344 // will be reactivated in updateCapacities
345 mParallelAction->setEnabled( false );
346 mPerpendicularAction->setEnabled( false );
347 }
348
349
350 clear();
352 setConstructionMode( false );
353
354 switchZM();
355 emit cadEnabledChanged( enabled );
356
357 mLastSnapMatch = QgsPointLocator::Match();
358}
359
360
362{
363 bool enableZ = false;
364 bool enableM = false;
365
366 if ( QgsMapLayer *layer = targetLayer() )
367 {
368 switch ( layer->type() )
369 {
371 {
372 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
373 const QgsWkbTypes::Type type = vlayer->wkbType();
374 enableZ = QgsWkbTypes::hasZ( type );
375 enableM = QgsWkbTypes::hasM( type );
376 break;
377 }
378
380 {
381 QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( layer );
382 enableZ = mlayer->isEditable();
383 break;
384 }
385
392 break;
393 }
394 }
395
396 setEnabledZ( enableZ );
397 setEnabledM( enableM );
398}
399
401{
402 mRelativeZButton->setEnabled( enable );
403 mZLabel->setEnabled( enable );
404 mZLineEdit->setEnabled( enable );
405 if ( mZLineEdit->isEnabled() )
406 mZLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultZValue(), 'f', 6 ) );
407 else
408 mZLineEdit->clear();
409 mLockZButton->setEnabled( enable );
410 emit enabledChangedZ( enable );
411}
412
414{
415 mRelativeMButton->setEnabled( enable );
416 mMLabel->setEnabled( enable );
417 mMLineEdit->setEnabled( enable );
418 if ( mMLineEdit->isEnabled() )
419 mMLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultMValue(), 'f', 6 ) );
420 else
421 mMLineEdit->clear();
422 mLockMButton->setEnabled( enable );
423 emit enabledChangedM( enable );
424}
425
426void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
427{
428 enabled &= mCurrentMapToolSupportsCad;
429
430 mSessionActive = enabled;
431
432 if ( enabled && !isVisible() )
433 {
434 show();
435 }
436
437 setCadEnabled( enabled );
438}
439
440void QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked( bool activated )
441{
442 if ( !activated )
443 {
444 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
445 }
446 else if ( sender() == mParallelAction )
447 {
448 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
449 }
450 else if ( sender() == mPerpendicularAction )
451 {
452 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
453 }
454}
455
456void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
457{
458 if ( sender() == mRelativeAngleButton )
459 {
460 mAngleConstraint->setRelative( activate );
461 emit relativeAngleChanged( activate );
462 }
463 else if ( sender() == mRelativeXButton )
464 {
465 mXConstraint->setRelative( activate );
466 emit relativeXChanged( activate );
467 }
468 else if ( sender() == mRelativeYButton )
469 {
470 mYConstraint->setRelative( activate );
471 emit relativeYChanged( activate );
472 }
473 else if ( sender() == mRelativeZButton )
474 {
475 mZConstraint->setRelative( activate );
476 emit relativeZChanged( activate );
477 }
478 else if ( sender() == mRelativeMButton )
479 {
480 mMConstraint->setRelative( activate );
481 emit relativeMChanged( activate );
482 }
483}
484
485void QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock( bool activate )
486{
487 if ( sender() == mRepeatingLockDistanceButton )
488 {
489 mDistanceConstraint->setRepeatingLock( activate );
490 }
491 else if ( sender() == mRepeatingLockAngleButton )
492 {
493 mAngleConstraint->setRepeatingLock( activate );
494 }
495 else if ( sender() == mRepeatingLockXButton )
496 {
497 mXConstraint->setRepeatingLock( activate );
498 }
499 else if ( sender() == mRepeatingLockYButton )
500 {
501 mYConstraint->setRepeatingLock( activate );
502 }
503 else if ( sender() == mRepeatingLockZButton )
504 {
505 mZConstraint->setRepeatingLock( activate );
506 }
507 else if ( sender() == mRepeatingLockMButton )
508 {
509 mMConstraint->setRepeatingLock( activate );
510 }
511}
512
513void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
514{
515 mConstructionMode = enabled;
516 mConstructionModeAction->setChecked( enabled );
517}
518
519void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction *action )
520{
521 // common angles
522 const QMap<QAction *, double>::const_iterator ica = mCommonAngleActions.constFind( action );
523 if ( ica != mCommonAngleActions.constEnd() )
524 {
525 ica.key()->setChecked( true );
526 mCommonAngleConstraint = ica.value();
527 QgsSettings().setValue( QStringLiteral( "/Cad/CommonAngle" ), ica.value() );
528 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
529 return;
530 }
531}
532
533QgsMapLayer *QgsAdvancedDigitizingDockWidget::targetLayer() const
534{
535 if ( QgsMapToolAdvancedDigitizing *advancedTool = qobject_cast< QgsMapToolAdvancedDigitizing * >( mMapCanvas->mapTool() ) )
536 {
537 return advancedTool->layer();
538 }
539 else
540 {
541 return mMapCanvas->currentLayer();
542 }
543}
544
545void QgsAdvancedDigitizingDockWidget::releaseLocks( bool releaseRepeatingLocks )
546{
547 // release all locks except construction mode
548
549 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
550
551 if ( releaseRepeatingLocks )
552 {
553 mXyVertexAction->setChecked( false );
554 mXyVertexConstraint->setLockMode( CadConstraint::NoLock );
555 emit softLockXyChanged( false );
556
557 mLineExtensionAction->setChecked( false );
558 mLineExtensionConstraint->setLockMode( CadConstraint::NoLock );
559 emit softLockLineExtensionChanged( false );
560
562 }
563
564 if ( releaseRepeatingLocks || !mAngleConstraint->isRepeatingLock() )
565 {
566 mAngleConstraint->setLockMode( CadConstraint::NoLock );
567 emit lockAngleChanged( false );
568 }
569 if ( releaseRepeatingLocks || !mDistanceConstraint->isRepeatingLock() )
570 {
571 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
572 emit lockDistanceChanged( false );
573 }
574 if ( releaseRepeatingLocks || !mXConstraint->isRepeatingLock() )
575 {
576 mXConstraint->setLockMode( CadConstraint::NoLock );
577 emit lockXChanged( false );
578 }
579 if ( releaseRepeatingLocks || !mYConstraint->isRepeatingLock() )
580 {
581 mYConstraint->setLockMode( CadConstraint::NoLock );
582 emit lockYChanged( false );
583 }
584 if ( releaseRepeatingLocks || !mZConstraint->isRepeatingLock() )
585 {
586 mZConstraint->setLockMode( CadConstraint::NoLock );
587 emit lockZChanged( false );
588 }
589 if ( releaseRepeatingLocks || !mMConstraint->isRepeatingLock() )
590 {
591 mMConstraint->setLockMode( CadConstraint::NoLock );
592 emit lockMChanged( false );
593 }
594
595 if ( !mCadPointList.empty() )
596 {
597 if ( !mXConstraint->isLocked() && !mXConstraint->relative() )
598 {
599 mXConstraint->setValue( mCadPointList.constLast().x(), true );
600 }
601 if ( !mYConstraint->isLocked() && !mYConstraint->relative() )
602 {
603 mYConstraint->setValue( mCadPointList.constLast().y(), true );
604 }
605 if ( !mZConstraint->isLocked() && !mZConstraint->relative() )
606 {
607 mZConstraint->setValue( mCadPointList.constLast().z(), true );
608 }
609 if ( !mMConstraint->isLocked() && !mMConstraint->relative() )
610 {
611 mMConstraint->setValue( mCadPointList.constLast().m(), true );
612 }
613 }
614
615}
616
617#if 0
618void QgsAdvancedDigitizingDockWidget::emit pointChanged()
619{
620 // run a fake map mouse event to update the paint item
621 QPoint globalPos = mMapCanvas->cursor().pos();
622 QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
623 QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
624 mCurrentMapTool->canvasMoveEvent( e );
625}
626#endif
627
628QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
629{
630 CadConstraint *constraint = nullptr;
631 if ( obj == mAngleLineEdit || obj == mLockAngleButton )
632 {
633 constraint = mAngleConstraint.get();
634 }
635 else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
636 {
637 constraint = mDistanceConstraint.get();
638 }
639 else if ( obj == mXLineEdit || obj == mLockXButton )
640 {
641 constraint = mXConstraint.get();
642 }
643 else if ( obj == mYLineEdit || obj == mLockYButton )
644 {
645 constraint = mYConstraint.get();
646 }
647 else if ( obj == mZLineEdit || obj == mLockZButton )
648 {
649 constraint = mZConstraint.get();
650 }
651 else if ( obj == mMLineEdit || obj == mLockMButton )
652 {
653 constraint = mMConstraint.get();
654 }
655 else if ( obj == mLineExtensionAction )
656 {
657 constraint = mLineExtensionConstraint.get();
658 }
659 else if ( obj == mXyVertexAction )
660 {
661 constraint = mXyVertexConstraint.get();
662 }
663 return constraint;
664}
665
666double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, bool &ok ) const
667{
668 ok = false;
669 double value = qgsPermissiveToDouble( inputValue, ok );
670 if ( ok )
671 {
672 return value;
673 }
674 else
675 {
676 // try to evaluate expression
677 QgsExpression expr( inputValue );
678 const QVariant result = expr.evaluate();
679 if ( expr.hasEvalError() )
680 {
681 ok = false;
682 QString inputValueC { inputValue };
683
684 // First: try removing group separator
685 if ( inputValue.contains( QLocale().groupSeparator() ) )
686 {
687 inputValueC.remove( QLocale().groupSeparator() );
688 QgsExpression exprC( inputValueC );
689 const QVariant resultC = exprC.evaluate();
690 if ( ! exprC.hasEvalError() )
691 {
692 value = resultC.toDouble( &ok );
693 }
694 }
695
696 // Second: be nice with non-dot locales
697 if ( !ok && QLocale().decimalPoint() != QChar( '.' ) && inputValueC.contains( QLocale().decimalPoint() ) )
698 {
699 QgsExpression exprC( inputValueC .replace( QLocale().decimalPoint(), QChar( '.' ) ) );
700 const QVariant resultC = exprC.evaluate();
701 if ( ! exprC.hasEvalError() )
702 {
703 value = resultC.toDouble( &ok );
704 }
705 }
706 }
707 else
708 {
709 value = result.toDouble( &ok );
710 }
711 return value;
712 }
713}
714
715void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
716{
717 if ( !constraint || textValue.isEmpty() )
718 {
719 return;
720 }
721
722 if ( constraint->lockMode() == CadConstraint::NoLock )
723 return;
724
725 bool ok;
726 const double value = parseUserInput( textValue, ok );
727 if ( !ok )
728 return;
729
730 constraint->setValue( value, convertExpression );
731 // run a fake map mouse event to update the paint item
732 emit pointChangedV2( mCadPointList.value( 0 ) );
733}
734
735void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
736{
737 CadConstraint *constraint = objectToConstraint( sender() );
738 if ( !constraint )
739 {
740 return;
741 }
742
743 if ( activate )
744 {
745 const QString textValue = constraint->lineEdit()->text();
746 if ( !textValue.isEmpty() )
747 {
748 bool ok;
749 const double value = parseUserInput( textValue, ok );
750 if ( ok )
751 {
752 constraint->setValue( value );
753 }
754 else
755 {
756 activate = false;
757 }
758 }
759 else
760 {
761 activate = false;
762 }
763 }
764 constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
765
766 if ( constraint == mXConstraint.get() )
767 {
768 emit lockXChanged( activate );
769 }
770 else if ( constraint == mYConstraint.get() )
771 {
772 emit lockYChanged( activate );
773 }
774 else if ( constraint == mZConstraint.get() )
775 {
776 emit lockZChanged( activate );
777 }
778 else if ( constraint == mMConstraint.get() )
779 {
780 emit lockMChanged( activate );
781 }
782 else if ( constraint == mDistanceConstraint.get() )
783 {
784 emit lockDistanceChanged( activate );
785 }
786 else if ( constraint == mAngleConstraint.get() )
787 {
788 emit lockAngleChanged( activate );
789 }
790
791 if ( activate )
792 {
793 // deactivate perpendicular/parallel if angle has been activated
794 if ( constraint == mAngleConstraint.get() )
795 {
796 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
797 }
798
799 // run a fake map mouse event to update the paint item
800 emit pointChangedV2( mCadPointList.value( 0 ) );
801 }
802}
803
804void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
805{
806 CadConstraint *constraint = objectToConstraint( sender() );
807 if ( !constraint )
808 {
809 return;
810 }
811
812 updateConstraintValue( constraint, textValue, false );
813}
814
815void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
816{
817 QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
818 if ( !lineEdit )
819 return;
820
821 CadConstraint *constraint = objectToConstraint( lineEdit );
822 if ( !constraint )
823 {
824 return;
825 }
826
827 updateConstraintValue( constraint, lineEdit->text(), true );
828}
829
830void QgsAdvancedDigitizingDockWidget::lockBetweenLineConstraint( Qgis::BetweenLineConstraint constraint )
831{
832 mBetweenLineConstraint = constraint;
833 mPerpendicularAction->setChecked( constraint == Qgis::BetweenLineConstraint::Perpendicular );
834 mParallelAction->setChecked( constraint == Qgis::BetweenLineConstraint::Parallel );
835}
836
837void QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint( bool activate /* default true */ )
838{
839 CadConstraint *constraint = objectToConstraint( sender() );
840 if ( !constraint )
841 {
842 return;
843 }
844
845 constraint->setLockMode( activate ? CadConstraint::SoftLock : CadConstraint::NoLock );
846
847 if ( constraint == mXyVertexConstraint.get() )
848 {
849 emit softLockXyChanged( activate );
850 }
851 else if ( constraint == mLineExtensionConstraint.get() )
852 {
853 emit softLockLineExtensionChanged( activate );
854 }
855
856 if ( activate )
857 {
858 // run a fake map mouse event to update the paint item
859 emit pointChangedV2( mCadPointList.value( 0 ) );
860 }
861
863}
864
865void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
866{
867 CadCapacities newCapacities = CadCapacities();
868 const bool isGeographic = mMapCanvas->mapSettings().destinationCrs().isGeographic();
869
870 // first point is the mouse point (it doesn't count)
871 if ( mCadPointList.count() > 1 )
872 {
873 newCapacities |= RelativeCoordinates;
874 if ( !isGeographic )
875 {
876 newCapacities |= AbsoluteAngle;
877 newCapacities |= Distance;
878 }
879 }
880 if ( mCadPointList.count() > 2 )
881 {
882 if ( !isGeographic )
883 newCapacities |= RelativeAngle;
884 }
885 if ( !updateUIwithoutChange && newCapacities == mCapacities )
886 {
887 return;
888 }
889
890 const bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
891
892 // update the UI according to new capacities
893 // still keep the old to compare
894
895 const bool distance = mCadEnabled && newCapacities.testFlag( Distance );
896 const bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
897 const bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
898 const bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
899
900 mPerpendicularAction->setEnabled( distance && snappingEnabled );
901 mParallelAction->setEnabled( distance && snappingEnabled );
902
903 mLineExtensionAction->setEnabled( snappingEnabled );
904 mXyVertexAction->setEnabled( snappingEnabled );
906
907 //update tooltips on buttons
908 if ( !snappingEnabled )
909 {
910 mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode." ) );
911 mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode." ) );
912 mLineExtensionAction->setToolTip( tr( "Snapping must be enabled to utilize line extension mode." ) );
913 mXyVertexAction->setToolTip( tr( "Snapping must be enabled to utilize xy point mode." ) );
914 }
915 else if ( mCadPointList.count() <= 1 )
916 {
917 mPerpendicularAction->setToolTip( tr( "A first vertex should be drawn to utilize perpendicular mode." ) );
918 mParallelAction->setToolTip( tr( "A first vertex should be drawn to utilize parallel mode." ) );
919 }
920 else if ( isGeographic )
921 {
922 mPerpendicularAction->setToolTip( tr( "Perpendicular mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
923 mParallelAction->setToolTip( tr( "Parallel mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
924 }
925 else
926 {
927 mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
928 mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
929 }
930
931
932 if ( !absoluteAngle )
933 {
934 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
935 }
936
937 // absolute angle = azimuth, relative = from previous line
938 mLockAngleButton->setEnabled( absoluteAngle );
939 mRelativeAngleButton->setEnabled( relativeAngle );
940 mAngleLineEdit->setEnabled( absoluteAngle );
941 emit enabledChangedAngle( absoluteAngle );
942 if ( !absoluteAngle )
943 {
944 mAngleConstraint->setLockMode( CadConstraint::NoLock );
945 }
946 if ( !relativeAngle )
947 {
948 mAngleConstraint->setRelative( false );
949 emit relativeAngleChanged( false );
950 }
951 else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
952 {
953 // set angle mode to relative if can do and wasn't available before
954 mAngleConstraint->setRelative( true );
955 emit relativeAngleChanged( true );
956 }
957
958 // distance is always relative
959 mLockDistanceButton->setEnabled( distance && relativeCoordinates );
960 mDistanceLineEdit->setEnabled( distance && relativeCoordinates );
961 emit enabledChangedDistance( distance && relativeCoordinates );
962 if ( !( distance && relativeCoordinates ) )
963 {
964 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
965 }
966
967 mRelativeXButton->setEnabled( relativeCoordinates );
968 mRelativeYButton->setEnabled( relativeCoordinates );
969 mRelativeZButton->setEnabled( relativeCoordinates );
970 mRelativeMButton->setEnabled( relativeCoordinates );
971
972 // update capacities
973 mCapacities = newCapacities;
974 mCadPaintItem->updatePosition();
975}
976
977
979{
981 constr.locked = c->isLocked();
982 constr.relative = c->relative();
983 constr.value = c->value();
984 return constr;
985}
986
987void QgsAdvancedDigitizingDockWidget::toggleLockedSnapVertex( const QgsPointLocator::Match &snapMatch, QgsPointLocator::Match previouslySnap )
988{
989 // do nothing if not activated
990 if ( !mLineExtensionConstraint->isLocked() && !mXyVertexConstraint->isLocked() )
991 {
992 return;
993 }
994
995 // if the first is same actual, not toggle if previously snapped
996 const int lastIndex = mLockedSnapVertices.length() - 1;
997 for ( int i = lastIndex ; i >= 0; --i )
998 {
999 if ( mLockedSnapVertices[i].point() == snapMatch.point() )
1000 {
1001 if ( snapMatch.point() != previouslySnap.point() )
1002 {
1003 mLockedSnapVertices.removeAt( i );
1004 }
1005 return;
1006 }
1007 }
1008
1009 if ( snapMatch.point() != previouslySnap.point() )
1010 {
1011 mLockedSnapVertices.enqueue( snapMatch );
1012 }
1013
1014 if ( mLockedSnapVertices.count() > 3 )
1015 {
1016 mLockedSnapVertices.dequeue();
1017 }
1018}
1019
1021{
1023 context.snappingUtils = mMapCanvas->snappingUtils();
1024 context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
1025 context.xConstraint = _constraint( mXConstraint.get() );
1026 context.yConstraint = _constraint( mYConstraint.get() );
1027 context.zConstraint = _constraint( mZConstraint.get() );
1028 context.mConstraint = _constraint( mMConstraint.get() );
1029 context.distanceConstraint = _constraint( mDistanceConstraint.get() );
1030 context.angleConstraint = _constraint( mAngleConstraint.get() );
1031
1032 context.lineExtensionConstraint = _constraint( mLineExtensionConstraint.get() );
1033 context.xyVertexConstraint = _constraint( mXyVertexConstraint.get() );
1034
1035 context.setCadPoints( mCadPointList );
1036 context.setLockedSnapVertices( mLockedSnapVertices );
1037
1040 context.commonAngleConstraint.value = mCommonAngleConstraint;
1041
1043
1044 const bool res = output.valid;
1045 QgsPoint point = pointXYToPoint( output.finalMapPoint );
1046 mSnappedSegment.clear();
1047 if ( output.snapMatch.hasEdge() )
1048 {
1049 QgsPointXY edgePt0, edgePt1;
1050 output.snapMatch.edgePoints( edgePt0, edgePt1 );
1051 mSnappedSegment << edgePt0 << edgePt1;
1052 }
1053 if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
1054 {
1055 if ( output.softLockCommonAngle != -1 )
1056 {
1057 mAngleConstraint->setLockMode( CadConstraint::SoftLock );
1058 mAngleConstraint->setValue( output.softLockCommonAngle );
1059 }
1060 else
1061 {
1062 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1063 }
1064 }
1065
1066 mSoftLockLineExtension = output.softLockLineExtension;
1067 mSoftLockX = output.softLockX;
1068 mSoftLockY = output.softLockY;
1069
1070 if ( output.snapMatch.isValid() )
1071 {
1072 mSnapIndicator->setMatch( output.snapMatch );
1073 mSnapIndicator->setVisible( true );
1074 }
1075 else
1076 {
1077 mSnapIndicator->setVisible( false );
1078 }
1079
1080 /*
1081 * Ensure that Z and M are passed
1082 * It will be dropped as needed later.
1083 */
1086
1087 /*
1088 * Constraints are applied in 2D, they are always called when using the tool
1089 * but they do not take into account if when you snap on a vertex it has
1090 * a Z value.
1091 * To get the value we use the snapPoint method. However, we only apply it
1092 * when the snapped point corresponds to the constrained point or on an edge
1093 * if the topological editing is activated. Also, we don't apply it if
1094 * the point is not linked to a layer.
1095 */
1096 e->setMapPoint( point );
1097 mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
1098 if ( mSnapMatch.layer() )
1099 {
1100 if ( ( ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() ) && ( point == mSnapMatch.point() ) )
1101 || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
1102 {
1103 e->snapPoint();
1104 point = mSnapMatch.interpolatedPoint( mMapCanvas->mapSettings().destinationCrs() );
1105 }
1106 }
1107
1108 if ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() )
1109 {
1110 toggleLockedSnapVertex( mSnapMatch, mLastSnapMatch );
1111 mLastSnapMatch = mSnapMatch;
1112 }
1113 else
1114 {
1115 mLastSnapMatch = QgsPointLocator::Match();
1116 }
1117
1118 /*
1119 * And if M or Z lock button is activated get the value of the input.
1120 */
1121 if ( mLockZButton->isChecked() )
1122 {
1123 point.setZ( QLocale().toDouble( mZLineEdit->text() ) );
1124 }
1125 if ( mLockMButton->isChecked() )
1126 {
1127 point.setM( QLocale().toDouble( mMLineEdit->text() ) );
1128 }
1129
1130 // update the point list
1131 updateCurrentPoint( point );
1132
1133 updateUnlockedConstraintValues( point );
1134
1135 if ( res )
1136 {
1137 emit popWarning();
1138 }
1139 else
1140 {
1141 emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
1142 }
1143
1144 return res;
1145}
1146
1147
1148void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPoint &point )
1149{
1150 bool previousPointExist, penulPointExist;
1151 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1152 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1153
1154 // --- angle
1155 if ( !mAngleConstraint->isLocked() && previousPointExist )
1156 {
1157 double angle = 0.0;
1158 if ( penulPointExist && mAngleConstraint->relative() )
1159 {
1160 // previous angle
1161 angle = std::atan2( previousPt.y() - penultimatePt.y(),
1162 previousPt.x() - penultimatePt.x() );
1163 }
1164 angle = ( std::atan2( point.y() - previousPt.y(),
1165 point.x() - previousPt.x()
1166 ) - angle ) * 180 / M_PI;
1167 // modulus
1168 angle = std::fmod( angle, 360.0 );
1169 mAngleConstraint->setValue( angle );
1170 }
1171 // --- distance
1172 if ( !mDistanceConstraint->isLocked() && previousPointExist )
1173 {
1174 mDistanceConstraint->setValue( std::sqrt( previousPt.distanceSquared( point ) ) );
1175 }
1176 // --- X
1177 if ( !mXConstraint->isLocked() )
1178 {
1179 if ( previousPointExist && mXConstraint->relative() )
1180 {
1181 mXConstraint->setValue( point.x() - previousPt.x() );
1182 }
1183 else
1184 {
1185 mXConstraint->setValue( point.x() );
1186 }
1187 }
1188 // --- Y
1189 if ( !mYConstraint->isLocked() )
1190 {
1191 if ( previousPointExist && mYConstraint->relative() )
1192 {
1193 mYConstraint->setValue( point.y() - previousPt.y() );
1194 }
1195 else
1196 {
1197 mYConstraint->setValue( point.y() );
1198 }
1199 }
1200 // --- Z
1201 if ( !mZConstraint->isLocked() )
1202 {
1203 if ( previousPointExist && mZConstraint->relative() )
1204 {
1205 mZConstraint->setValue( point.z() - previousPt.z() );
1206 }
1207 else
1208 {
1209 mZConstraint->setValue( point.z() );
1210 }
1211 }
1212 // --- M
1213 if ( !mMConstraint->isLocked() )
1214 {
1215 if ( previousPointExist && mMConstraint->relative() )
1216 {
1217 mMConstraint->setValue( point.m() - previousPt.m() );
1218 }
1219 else
1220 {
1221 mMConstraint->setValue( point.m() );
1222 }
1223 }
1224}
1225
1226
1227QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
1228{
1229 QList<QgsPointXY> segment;
1230 QgsPointXY pt1, pt2;
1232
1233 QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
1234
1235 const QgsSnappingConfig canvasConfig = snappingUtils->config();
1236 QgsSnappingConfig localConfig = snappingUtils->config();
1237
1239 localConfig.setTypeFlag( Qgis::SnappingType::Segment );
1240 snappingUtils->setConfig( localConfig );
1241
1242 match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
1243
1244 snappingUtils->setConfig( canvasConfig );
1245
1246 if ( match.isValid() && match.hasEdge() )
1247 {
1248 match.edgePoints( pt1, pt2 );
1249 segment << pt1 << pt2;
1250 }
1251
1252 if ( snapped )
1253 {
1254 *snapped = segment.count() == 2;
1255 }
1256
1257 return segment;
1258}
1259
1261{
1262 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::NoConstraint )
1263 {
1264 return false;
1265 }
1266
1267 bool previousPointExist, penulPointExist, snappedSegmentExist;
1268 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1269 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1270 mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
1271
1272 if ( !previousPointExist || !snappedSegmentExist )
1273 {
1274 return false;
1275 }
1276
1277 double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
1278
1279 if ( mAngleConstraint->relative() && penulPointExist )
1280 {
1281 angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
1282 }
1283
1284 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::Perpendicular )
1285 {
1286 angle += M_PI_2;
1287 }
1288
1289 angle *= 180 / M_PI;
1290
1291 mAngleConstraint->setValue( angle );
1292 mAngleConstraint->setLockMode( lockMode );
1293 if ( lockMode == CadConstraint::HardLock )
1294 {
1295 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
1296 }
1297
1298 return true;
1299}
1300
1302{
1303 // event on map tool
1304
1305 if ( !mCadEnabled )
1306 return false;
1307
1308 switch ( e->key() )
1309 {
1310 case Qt::Key_Backspace:
1311 case Qt::Key_Delete:
1312 {
1314 releaseLocks( false );
1315 break;
1316 }
1317 case Qt::Key_Escape:
1318 {
1319 releaseLocks();
1320 break;
1321 }
1322 default:
1323 {
1324 keyPressEvent( e );
1325 break;
1326 }
1327 }
1328 // for map tools, continues with key press in any case
1329 return false;
1330}
1331
1333{
1334 clearPoints();
1335 releaseLocks();
1336}
1337
1339{
1340 // event on dock (this)
1341
1342 if ( !mCadEnabled )
1343 return;
1344
1345 switch ( e->key() )
1346 {
1347 case Qt::Key_Backspace:
1348 case Qt::Key_Delete:
1349 {
1351 releaseLocks( false );
1352 break;
1353 }
1354 case Qt::Key_Escape:
1355 {
1356 releaseLocks();
1357 break;
1358 }
1359 default:
1360 {
1361 filterKeyPress( e );
1362 break;
1363 }
1364 }
1365}
1366
1367void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
1368{
1369 clearPoints();
1370 const auto constPoints = points;
1371 for ( const QgsPointXY &pt : constPoints )
1372 {
1373 addPoint( pt );
1374 }
1375}
1376
1377bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
1378{
1379 if ( !cadEnabled() )
1380 {
1381 return QgsDockWidget::eventFilter( obj, event );
1382 }
1383
1384 // event for line edits and map canvas
1385 // we have to catch both KeyPress events and ShortcutOverride events. This is because
1386 // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
1387 // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
1388 // us to intercept these keystrokes before they are caught by the global shortcuts
1389 if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
1390 {
1391 if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
1392 {
1393 return filterKeyPress( keyEvent );
1394 }
1395 }
1396 return QgsDockWidget::eventFilter( obj, event );
1397}
1398
1399bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
1400{
1401 // we need to be careful here -- because this method is called on both KeyPress events AND
1402 // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
1403 // these event types for a single key press. I.e. pressing "A" may first call trigger a
1404 // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
1405 const QEvent::Type type = e->type();
1406 switch ( e->key() )
1407 {
1408 case Qt::Key_X:
1409 {
1410 // modifier+x ONLY caught for ShortcutOverride events...
1411 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1412 {
1413 mXConstraint->toggleLocked();
1414 emit lockXChanged( mXConstraint->isLocked() );
1415 emit pointChangedV2( mCadPointList.value( 0 ) );
1416 e->accept();
1417 }
1418 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1419 {
1420 if ( mCapacities.testFlag( RelativeCoordinates ) )
1421 {
1422 mXConstraint->toggleRelative();
1423 emit relativeXChanged( mXConstraint->relative() );
1424 emit pointChangedV2( mCadPointList.value( 0 ) );
1425 e->accept();
1426 }
1427 }
1428 // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
1429 else if ( type == QEvent::KeyPress )
1430 {
1431 mXLineEdit->setFocus();
1432 mXLineEdit->selectAll();
1433 emit focusOnXRequested();
1434 e->accept();
1435 }
1436 break;
1437 }
1438 case Qt::Key_Y:
1439 {
1440 // modifier+y ONLY caught for ShortcutOverride events...
1441 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1442 {
1443 mYConstraint->toggleLocked();
1444 emit lockYChanged( mYConstraint->isLocked() );
1445 emit pointChangedV2( mCadPointList.value( 0 ) );
1446 e->accept();
1447 }
1448 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1449 {
1450 if ( mCapacities.testFlag( RelativeCoordinates ) )
1451 {
1452 mYConstraint->toggleRelative();
1453 emit relativeYChanged( mYConstraint->relative() );
1454 emit pointChangedV2( mCadPointList.value( 0 ) );
1455 e->accept();
1456 }
1457 }
1458 // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1459 else if ( type == QEvent::KeyPress )
1460 {
1461 mYLineEdit->setFocus();
1462 mYLineEdit->selectAll();
1463 emit focusOnYRequested();
1464 e->accept();
1465 }
1466 break;
1467 }
1468 case Qt::Key_Z:
1469 {
1470 // modifier+z ONLY caught for ShortcutOverride events...
1471 if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::AltModifier )
1472 {
1473 mZConstraint->toggleLocked();
1474 emit lockZChanged( mZConstraint->isLocked() );
1475 emit pointChangedV2( mCadPointList.value( 0 ) );
1476 e->accept();
1477 }
1478 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1479 {
1480 if ( mCapacities.testFlag( RelativeCoordinates ) )
1481 {
1482 mZConstraint->toggleRelative();
1483 emit relativeZChanged( mZConstraint->relative() );
1484 emit pointChangedV2( mCadPointList.value( 0 ) );
1485 e->accept();
1486 }
1487 }
1488 // .. but "z" alone ONLY caught for KeyPress events (see comment at start of function)
1489 else if ( type == QEvent::KeyPress )
1490 {
1491 mZLineEdit->setFocus();
1492 mZLineEdit->selectAll();
1493 emit focusOnZRequested();
1494 e->accept();
1495 }
1496 break;
1497 }
1498 case Qt::Key_M:
1499 {
1500 // modifier+m ONLY caught for ShortcutOverride events...
1501 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1502 {
1503 mMConstraint->toggleLocked();
1504 emit lockMChanged( mMConstraint->isLocked() );
1505 emit pointChangedV2( mCadPointList.value( 0 ) );
1506 e->accept();
1507 }
1508 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1509 {
1510 if ( mCapacities.testFlag( RelativeCoordinates ) )
1511 {
1512 mMConstraint->toggleRelative();
1513 emit relativeMChanged( mMConstraint->relative() );
1514 emit pointChangedV2( mCadPointList.value( 0 ) );
1515 e->accept();
1516 }
1517 }
1518 // .. but "m" alone ONLY caught for KeyPress events (see comment at start of function)
1519 else if ( type == QEvent::KeyPress )
1520 {
1521 mMLineEdit->setFocus();
1522 mMLineEdit->selectAll();
1523 emit focusOnMRequested();
1524 e->accept();
1525 }
1526 break;
1527 }
1528 case Qt::Key_A:
1529 {
1530 // modifier+a ONLY caught for ShortcutOverride events...
1531 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1532 {
1533 if ( mCapacities.testFlag( AbsoluteAngle ) )
1534 {
1535 mAngleConstraint->toggleLocked();
1536 emit lockAngleChanged( mAngleConstraint->isLocked() );
1537 emit pointChangedV2( mCadPointList.value( 0 ) );
1538 e->accept();
1539 }
1540 }
1541 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1542 {
1543 if ( mCapacities.testFlag( RelativeAngle ) )
1544 {
1545 mAngleConstraint->toggleRelative();
1546 emit relativeAngleChanged( mAngleConstraint->relative() );
1547 emit pointChangedV2( mCadPointList.value( 0 ) );
1548 e->accept();
1549 }
1550 }
1551 // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
1552 else if ( type == QEvent::KeyPress )
1553 {
1554 mAngleLineEdit->setFocus();
1555 mAngleLineEdit->selectAll();
1556 emit focusOnAngleRequested();
1557 e->accept();
1558 }
1559 break;
1560 }
1561 case Qt::Key_D:
1562 {
1563 // modifier+d ONLY caught for ShortcutOverride events...
1564 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1565 {
1566 if ( mCapacities.testFlag( RelativeCoordinates ) && mCapacities.testFlag( Distance ) )
1567 {
1568 mDistanceConstraint->toggleLocked();
1569 emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1570 emit pointChangedV2( mCadPointList.value( 0 ) );
1571 e->accept();
1572 }
1573 }
1574 // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
1575 else if ( type == QEvent::KeyPress )
1576 {
1577 mDistanceLineEdit->setFocus();
1578 mDistanceLineEdit->selectAll();
1580 e->accept();
1581 }
1582 break;
1583 }
1584 case Qt::Key_C:
1585 {
1586 if ( type == QEvent::KeyPress )
1587 {
1588 setConstructionMode( !mConstructionMode );
1589 e->accept();
1590 }
1591 break;
1592 }
1593 case Qt::Key_P:
1594 {
1595 if ( type == QEvent::KeyPress )
1596 {
1597 const bool parallel = mParallelAction->isChecked();
1598 const bool perpendicular = mPerpendicularAction->isChecked();
1599
1600 if ( !parallel && !perpendicular )
1601 {
1602 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
1603 }
1604 else if ( perpendicular )
1605 {
1606 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
1607 }
1608 else
1609 {
1610 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1611 }
1612 e->accept();
1613
1614 // run a fake map mouse event to update the paint item
1615 emit pointChangedV2( mCadPointList.value( 0 ) );
1616 }
1617 break;
1618 }
1619 default:
1620 {
1621 return false; // continues
1622 }
1623 }
1624 return e->isAccepted();
1625}
1626
1628{
1629 // most of theses lines can be moved to updateCapacity
1630 connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
1631 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
1632 {
1633 mAngleLineEdit->setToolTip( tr( "Angle constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1634 mDistanceLineEdit->setToolTip( tr( "Distance constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1635
1636 mLabelX->setText( tr( "Long" ) );
1637 mLabelY->setText( tr( "Lat" ) );
1638
1639 mXConstraint->setPrecision( 8 );
1640 mYConstraint->setPrecision( 8 );
1641 }
1642 else
1643 {
1644 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
1645 mAngleLineEdit->setToolTip( QString() );
1646
1647 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
1648
1649 mLabelX->setText( tr( "x" ) );
1650 mLabelY->setText( tr( "y" ) );
1651
1652 mXConstraint->setPrecision( 6 );
1653 mYConstraint->setPrecision( 6 );
1654 }
1655
1656 updateCapacity();
1657
1658 mEnableAction->setEnabled( true );
1659 mErrorLabel->hide();
1660 mCadWidget->show();
1661
1662 mCurrentMapToolSupportsCad = true;
1663
1664 if ( mSessionActive && !isVisible() )
1665 {
1666 show();
1667 }
1668 setCadEnabled( mSessionActive );
1669}
1670
1672{
1674
1675 mEnableAction->setEnabled( false );
1676 mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
1677 mErrorLabel->show();
1678 mCadWidget->hide();
1679
1680 mCurrentMapToolSupportsCad = false;
1681
1682 setCadEnabled( false );
1683}
1684
1686{
1687 mCadPaintItem->update();
1688}
1689
1691{
1692 if ( !force && ( mLineExtensionConstraint->isLocked() || mXyVertexConstraint->isLocked() ) )
1693 {
1694 return;
1695 }
1696
1697 mLockedSnapVertices.clear();
1698}
1699
1700
1702{
1703 QgsPoint pt = pointXYToPoint( point );
1704 if ( !pointsCount() )
1705 {
1706 mCadPointList << pt;
1707 }
1708 else
1709 {
1710 mCadPointList.insert( 0, pt );
1711 }
1712
1713 updateCapacity();
1715}
1716
1718{
1719 if ( !pointsCount() )
1720 return;
1721
1722 const int i = pointsCount() > 1 ? 1 : 0;
1723 mCadPointList.removeAt( i );
1724 updateCapacity();
1726}
1727
1729{
1730 mCadPointList.clear();
1731 mSnappedSegment.clear();
1732
1733 updateCapacity();
1735}
1736
1737void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPoint &point )
1738{
1739 if ( !pointsCount() )
1740 {
1741 mCadPointList << point;
1742 updateCapacity();
1743 }
1744 else
1745 {
1746 mCadPointList[0] = point;
1747 }
1749}
1750
1751
1753{
1754 mLockMode = mode;
1755 mLockerButton->setChecked( mode == HardLock );
1756 if ( mRepeatingLockButton )
1757 {
1758 if ( mode == HardLock )
1759 {
1760 mRepeatingLockButton->setEnabled( true );
1761 }
1762 else
1763 {
1764 mRepeatingLockButton->setChecked( false );
1765 mRepeatingLockButton->setEnabled( false );
1766 }
1767 }
1768
1769 if ( mode == NoLock )
1770 {
1771 mLineEdit->clear();
1772 }
1773
1774}
1775
1777{
1778 mRepeatingLock = repeating;
1779 if ( mRepeatingLockButton )
1780 mRepeatingLockButton->setChecked( repeating );
1781}
1782
1784{
1785 mRelative = relative;
1786 if ( mRelativeButton )
1787 {
1788 mRelativeButton->setChecked( relative );
1789 }
1790}
1791
1793{
1794 mValue = value;
1795 if ( updateWidget && mLineEdit->isEnabled() )
1796 mLineEdit->setText( QLocale().toString( value, 'f', mPrecision ) );
1797}
1798
1800{
1801 setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1802}
1803
1805{
1806 setRelative( !mRelative );
1807}
1808
1810{
1811 mPrecision = precision;
1812 if ( mLineEdit->isEnabled() )
1813 mLineEdit->setText( QLocale().toString( mValue, 'f', mPrecision ) );
1814}
1815
1817{
1818 if ( exist )
1819 *exist = pointsCount() > 0;
1820 if ( pointsCount() > 0 )
1821 return mCadPointList.value( 0 );
1822 else
1823 return QgsPoint();
1824}
1825
1827{
1828 if ( pointsCount() > 0 && layer )
1829 {
1830 QgsPoint res = mCadPointList.value( 0 );
1831 const QgsPointXY layerCoordinates = mMapCanvas->mapSettings().mapToLayerCoordinates( layer, res );
1832 res.setX( layerCoordinates.x() );
1833 res.setY( layerCoordinates.y() );
1834 return res;
1835 }
1836 return QgsPoint();
1837}
1838
1840{
1841 if ( exist )
1842 *exist = pointsCount() > 1;
1843 if ( pointsCount() > 1 )
1844 return mCadPointList.value( 1 );
1845 else
1846 return QgsPoint();
1847}
1848
1850{
1851 if ( exist )
1852 *exist = pointsCount() > 2;
1853 if ( pointsCount() > 2 )
1854 return mCadPointList.value( 2 );
1855 else
1856 return QgsPoint();
1857}
1858
1859QgsPoint QgsAdvancedDigitizingDockWidget::pointXYToPoint( const QgsPointXY &point ) const
1860{
1861 return QgsPoint( point.x(), point.y(), getLineZ(), getLineM() );
1862}
1863
1865{
1866 return mZLineEdit->isEnabled() ? QLocale().toDouble( mZLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
1867}
1868
1870{
1871 return mMLineEdit->isEnabled() ? QLocale().toDouble( mMLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
1872}
@ AllLayers
On all vector layers.
BetweenLineConstraint
Between line constraints which can be enabled.
Definition: qgis.h:1843
@ NoConstraint
No additional constraint.
@ Perpendicular
Perpendicular.
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 an abstract class for all basic constraints (angle/distance/x/y).
void setPrecision(int precision)
Sets the numeric precision (decimal places) to show in the associated widget.
void setRepeatingLock(bool repeating)
Sets whether a repeating lock is set for the constraint.
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 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).
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()
Remove 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 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 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 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 setActive(bool active)
Set whether the floater should be active or not.
bool active()
Whether the floater is active or not.
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:160
void setLockedSnapVertices(const QQueue< QgsPointLocator::Match > &lockedSnapVertices)
Sets the queue of locked vertices.
Definition: qgscadutils.h:186
QgsCadUtils::AlignMapPointConstraint mConstraint
Constraint for M coordinate.
Definition: qgscadutils.h:126
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
Definition: qgscadutils.h:128
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
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:99
bool isEditable() const override
Returns true if the layer can be edited.
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
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:478
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:113
bool topologicalEditing
Definition: qgsproject.h:120
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
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.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1130
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1080
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
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:71
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.