QGIS API Documentation  3.6.0-Noosa (5873452)
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
6  email : [email protected]
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 
18 #include <cmath>
19 
22 #include "qgsapplication.h"
23 #include "qgscadutils.h"
24 #include "qgsexpression.h"
25 #include "qgslogger.h"
26 #include "qgsmapcanvas.h"
27 #include "qgsmaptoolcapture.h"
29 #include "qgsmessagebaritem.h"
30 #include "qgslinestring.h"
31 #include "qgsfocuswatcher.h"
32 #include "qgssettings.h"
33 #include "qgssnappingutils.h"
34 #include "qgsproject.h"
35 #include "qgsmapmouseevent.h"
36 
37 
39  : QgsDockWidget( parent )
40  , mMapCanvas( canvas )
41  , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 90 ).toDouble() )
42 {
43  setupUi( this );
44 
45  mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this );
46 
47  mAngleConstraint.reset( new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton, mRepeatingLockAngleButton ) );
48  mDistanceConstraint.reset( new CadConstraint( mDistanceLineEdit, mLockDistanceButton, nullptr, mRepeatingLockDistanceButton ) );
49  mXConstraint.reset( new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton, mRepeatingLockXButton ) );
50  mYConstraint.reset( new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton, mRepeatingLockYButton ) );
51  mAdditionalConstraint = NoConstraint;
52 
53  mMapCanvas->installEventFilter( this );
54  mAngleLineEdit->installEventFilter( this );
55  mDistanceLineEdit->installEventFilter( this );
56  mXLineEdit->installEventFilter( this );
57  mYLineEdit->installEventFilter( this );
58 
59  // Connect the UI to the event filter to update constraints
60  connect( mEnableAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::activateCad );
61  connect( mConstructionModeAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode );
62  connect( mParallelAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::additionalConstraintClicked );
63  connect( mPerpendicularAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::additionalConstraintClicked );
64  connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
65  connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
66  connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
67  connect( mLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
68  connect( mRelativeAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
69  connect( mRelativeXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
70  connect( mRelativeYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
71  connect( mRepeatingLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
72  connect( mRepeatingLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
73  connect( mRepeatingLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
74  connect( mRepeatingLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
75  connect( mAngleLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
76  connect( mDistanceLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
77  connect( mXLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
78  connect( mYLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
79  connect( mAngleLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
80  connect( mDistanceLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
81  connect( mXLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
82  connect( mYLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
83  //also watch for focus out events on these widgets
84  QgsFocusWatcher *angleWatcher = new QgsFocusWatcher( mAngleLineEdit );
85  connect( angleWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
86  QgsFocusWatcher *distanceWatcher = new QgsFocusWatcher( mDistanceLineEdit );
87  connect( distanceWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
88  QgsFocusWatcher *xWatcher = new QgsFocusWatcher( mXLineEdit );
89  connect( xWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
90  QgsFocusWatcher *yWatcher = new QgsFocusWatcher( mYLineEdit );
91  connect( yWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
92 
93  // config menu
94  QMenu *menu = new QMenu( this );
95  // common angles
96  QActionGroup *angleButtonGroup = new QActionGroup( menu ); // actions are exclusive for common angles
97  mCommonAngleActions = QMap<QAction *, double>();
98  QList< QPair< double, QString > > commonAngles;
99  QString menuText;
100  QList<double> anglesDouble( { 0.0, 5.0, 10.0, 15.0, 18.0, 22.5, 30.0, 45.0, 90.0} );
101  for ( QList<double>::const_iterator it = anglesDouble.constBegin(); it != anglesDouble.constEnd(); ++it )
102  {
103  if ( *it == 0 )
104  menuText = tr( "Do Not Snap to Common Angles" );
105  else
106  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 );
107  commonAngles << QPair<double, QString>( *it, menuText );
108  }
109  for ( QList< QPair<double, QString > >::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it )
110  {
111  QAction *action = new QAction( it->second, menu );
112  action->setCheckable( true );
113  action->setChecked( it->first == mCommonAngleConstraint );
114  menu->addAction( action );
115  angleButtonGroup->addAction( action );
116  mCommonAngleActions.insert( action, it->first );
117  }
118 
119  qobject_cast< QToolButton *>( mToolbar->widgetForAction( mSettingsAction ) )->setPopupMode( QToolButton::InstantPopup );
120  mSettingsAction->setMenu( menu );
121  connect( menu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
122 
123  // set tooltips
124  mConstructionModeAction->setToolTip( "<b>" + tr( "Construction mode" ) + "</b><br>(" + tr( "press c to toggle on/off" ) + ")" );
125  mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
126  mLockDistanceButton->setToolTip( "<b>" + tr( "Lock distance" ) + "</b><br>(" + tr( "press Ctrl + d for quick access" ) + ")" );
127  mRepeatingLockDistanceButton->setToolTip( "<b>" + tr( "Continuously lock distance" ) + "</b>" );
128 
129  mRelativeAngleButton->setToolTip( "<b>" + tr( "Toggles relative angle to previous segment" ) + "</b><br>(" + tr( "press Shift + a for quick access" ) + ")" );
130  mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
131  mLockAngleButton->setToolTip( "<b>" + tr( "Lock angle" ) + "</b><br>(" + tr( "press Ctrl + a for quick access" ) + ")" );
132  mRepeatingLockAngleButton->setToolTip( "<b>" + tr( "Continuously lock angle" ) + "</b>" );
133 
134  mRelativeXButton->setToolTip( "<b>" + tr( "Toggles relative x to previous node" ) + "</b><br>(" + tr( "press Shift + x for quick access" ) + ")" );
135  mXLineEdit->setToolTip( "<b>" + tr( "X coordinate" ) + "</b><br>(" + tr( "press x for quick access" ) + ")" );
136  mLockXButton->setToolTip( "<b>" + tr( "Lock x coordinate" ) + "</b><br>(" + tr( "press Ctrl + x for quick access" ) + ")" );
137  mRepeatingLockXButton->setToolTip( "<b>" + tr( "Continuously lock x coordinate" ) + "</b>" );
138 
139  mRelativeYButton->setToolTip( "<b>" + tr( "Toggles relative y to previous node" ) + "</b><br>(" + tr( "press Shift + y for quick access" ) + ")" );
140  mYLineEdit->setToolTip( "<b>" + tr( "Y coordinate" ) + "</b><br>(" + tr( "press y for quick access" ) + ")" );
141  mLockYButton->setToolTip( "<b>" + tr( "Lock y coordinate" ) + "</b><br>(" + tr( "press Ctrl + y for quick access" ) + ")" );
142  mRepeatingLockYButton->setToolTip( "<b>" + tr( "Continuously lock y coordinate" ) + "</b>" );
143 
144 
145  updateCapacity( true );
146  connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, [ = ] { updateCapacity( true ); } );
147 
148  disable();
149 }
150 
152 {
153  // disable CAD but do not unset map event filter
154  // so it will be reactivated whenever the map tool is show again
155  setCadEnabled( false );
156 }
157 
158 void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
159 {
160  mCadEnabled = enabled;
161  mEnableAction->setChecked( enabled );
162  mConstructionModeAction->setEnabled( enabled );
163  mParallelAction->setEnabled( enabled );
164  mPerpendicularAction->setEnabled( enabled );
165  mSettingsAction->setEnabled( enabled );
166  mInputWidgets->setEnabled( enabled );
167 
168  clear();
169  setConstructionMode( false );
170 }
171 
172 void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
173 {
174  enabled &= mCurrentMapToolSupportsCad;
175 
176  mSessionActive = enabled;
177 
178  if ( enabled && !isVisible() )
179  {
180  show();
181  }
182 
183  setCadEnabled( enabled );
184 }
185 
186 void QgsAdvancedDigitizingDockWidget::additionalConstraintClicked( bool activated )
187 {
188  if ( !activated )
189  {
190  lockAdditionalConstraint( NoConstraint );
191  }
192  if ( sender() == mParallelAction )
193  {
194  lockAdditionalConstraint( Parallel );
195  }
196  else if ( sender() == mPerpendicularAction )
197  {
198  lockAdditionalConstraint( Perpendicular );
199  }
200 }
201 
202 void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
203 {
204  if ( sender() == mRelativeAngleButton )
205  {
206  mAngleConstraint->setRelative( activate );
207  }
208  else if ( sender() == mRelativeXButton )
209  {
210  mXConstraint->setRelative( activate );
211  }
212  else if ( sender() == mRelativeYButton )
213  {
214  mYConstraint->setRelative( activate );
215  }
216 }
217 
218 void QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock( bool activate )
219 {
220  if ( sender() == mRepeatingLockDistanceButton )
221  {
222  mDistanceConstraint->setRepeatingLock( activate );
223  }
224  else if ( sender() == mRepeatingLockAngleButton )
225  {
226  mAngleConstraint->setRepeatingLock( activate );
227  }
228  else if ( sender() == mRepeatingLockXButton )
229  {
230  mXConstraint->setRepeatingLock( activate );
231  }
232  else if ( sender() == mRepeatingLockYButton )
233  {
234  mYConstraint->setRepeatingLock( activate );
235  }
236 }
237 
238 void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
239 {
240  mConstructionMode = enabled;
241  mConstructionModeAction->setChecked( enabled );
242 }
243 
244 void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction *action )
245 {
246  // common angles
247  QMap<QAction *, double>::const_iterator ica = mCommonAngleActions.constFind( action );
248  if ( ica != mCommonAngleActions.constEnd() )
249  {
250  ica.key()->setChecked( true );
251  mCommonAngleConstraint = ica.value();
252  QgsSettings().setValue( QStringLiteral( "/Cad/CommonAngle" ), ica.value() );
253  return;
254  }
255 }
256 
257 void QgsAdvancedDigitizingDockWidget::releaseLocks( bool releaseRepeatingLocks )
258 {
259  // release all locks except construction mode
260 
261  lockAdditionalConstraint( NoConstraint );
262 
263  if ( releaseRepeatingLocks || !mAngleConstraint->isRepeatingLock() )
264  mAngleConstraint->setLockMode( CadConstraint::NoLock );
265  if ( releaseRepeatingLocks || !mDistanceConstraint->isRepeatingLock() )
266  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
267  if ( releaseRepeatingLocks || !mXConstraint->isRepeatingLock() )
268  mXConstraint->setLockMode( CadConstraint::NoLock );
269  if ( releaseRepeatingLocks || !mYConstraint->isRepeatingLock() )
270  mYConstraint->setLockMode( CadConstraint::NoLock );
271 }
272 
273 #if 0
274 void QgsAdvancedDigitizingDockWidget::emit pointChanged()
275 {
276  // run a fake map mouse event to update the paint item
277  QPoint globalPos = mMapCanvas->cursor().pos();
278  QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
279  QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
280  mCurrentMapTool->canvasMoveEvent( e );
281 }
282 #endif
283 
284 QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
285 {
286  CadConstraint *constraint = nullptr;
287  if ( obj == mAngleLineEdit || obj == mLockAngleButton )
288  {
289  constraint = mAngleConstraint.get();
290  }
291  else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
292  {
293  constraint = mDistanceConstraint.get();
294  }
295  else if ( obj == mXLineEdit || obj == mLockXButton )
296  {
297  constraint = mXConstraint.get();
298  }
299  else if ( obj == mYLineEdit || obj == mLockYButton )
300  {
301  constraint = mYConstraint.get();
302  }
303  return constraint;
304 }
305 
306 double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, bool &ok ) const
307 {
308  ok = false;
309  double value = qgsPermissiveToDouble( inputValue, ok );
310  if ( ok )
311  {
312  return value;
313  }
314  else
315  {
316  // try to evaluate expression
317  QgsExpression expr( inputValue );
318  QVariant result = expr.evaluate();
319  if ( expr.hasEvalError() )
320  ok = false;
321  else
322  value = result.toDouble( &ok );
323  return value;
324  }
325 }
326 
327 void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
328 {
329  if ( !constraint || textValue.isEmpty() )
330  {
331  return;
332  }
333 
334  if ( constraint->lockMode() == CadConstraint::NoLock )
335  return;
336 
337  bool ok;
338  double value = parseUserInput( textValue, ok );
339  if ( !ok )
340  return;
341 
342  constraint->setValue( value, convertExpression );
343  // run a fake map mouse event to update the paint item
344  emit pointChanged( mCadPointList.value( 0 ) );
345 }
346 
347 void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
348 {
349  CadConstraint *constraint = objectToConstraint( sender() );
350  if ( !constraint )
351  {
352  return;
353  }
354 
355  if ( activate )
356  {
357  QString textValue = constraint->lineEdit()->text();
358  if ( !textValue.isEmpty() )
359  {
360  bool ok;
361  double value = parseUserInput( textValue, ok );
362  if ( ok )
363  {
364  constraint->setValue( value );
365  }
366  else
367  {
368  activate = false;
369  }
370  }
371  else
372  {
373  activate = false;
374  }
375  }
376  constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
377 
378  if ( activate )
379  {
380  // deactivate perpendicular/parallel if angle has been activated
381  if ( constraint == mAngleConstraint.get() )
382  {
383  lockAdditionalConstraint( NoConstraint );
384  }
385 
386  // run a fake map mouse event to update the paint item
387  emit pointChanged( mCadPointList.value( 0 ) );
388  }
389 }
390 
391 void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
392 {
393  CadConstraint *constraint = objectToConstraint( sender() );
394  if ( !constraint )
395  {
396  return;
397  }
398 
399  updateConstraintValue( constraint, textValue, false );
400 }
401 
402 void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
403 {
404  QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
405  if ( !lineEdit )
406  return;
407 
408  CadConstraint *constraint = objectToConstraint( lineEdit );
409  if ( !constraint )
410  {
411  return;
412  }
413 
414  updateConstraintValue( constraint, lineEdit->text(), true );
415 }
416 
417 void QgsAdvancedDigitizingDockWidget::lockAdditionalConstraint( AdditionalConstraint constraint )
418 {
419  mAdditionalConstraint = constraint;
420  mPerpendicularAction->setChecked( constraint == Perpendicular );
421  mParallelAction->setChecked( constraint == Parallel );
422 }
423 
424 void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
425 {
426  CadCapacities newCapacities = nullptr;
427  // first point is the mouse point (it doesn't count)
428  if ( mCadPointList.count() > 1 )
429  {
430  newCapacities |= AbsoluteAngle | RelativeCoordinates;
431  }
432  if ( mCadPointList.count() > 2 )
433  {
434  newCapacities |= RelativeAngle;
435  }
436  if ( !updateUIwithoutChange && newCapacities == mCapacities )
437  {
438  return;
439  }
440 
441  bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
442 
443  // update the UI according to new capacities
444  // still keep the old to compare
445 
446  bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
447  bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
448  bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
449 
450  mPerpendicularAction->setEnabled( absoluteAngle && snappingEnabled );
451  mParallelAction->setEnabled( absoluteAngle && snappingEnabled );
452 
453  //update tooltips on buttons
454  if ( !snappingEnabled )
455  {
456  mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode" ) );
457  mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode" ) );
458  }
459  else
460  {
461  mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
462  mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
463  }
464 
465 
466  if ( !absoluteAngle )
467  {
468  lockAdditionalConstraint( NoConstraint );
469  }
470 
471  // absolute angle = azimuth, relative = from previous line
472  mLockAngleButton->setEnabled( absoluteAngle );
473  mRelativeAngleButton->setEnabled( relativeAngle );
474  mAngleLineEdit->setEnabled( absoluteAngle );
475  if ( !absoluteAngle )
476  {
477  mAngleConstraint->setLockMode( CadConstraint::NoLock );
478  }
479  if ( !relativeAngle )
480  {
481  mAngleConstraint->setRelative( false );
482  }
483  else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
484  {
485  // set angle mode to relative if can do and wasn't available before
486  mAngleConstraint->setRelative( true );
487  }
488 
489  // distance is always relative
490  mLockDistanceButton->setEnabled( relativeCoordinates );
491  mDistanceLineEdit->setEnabled( relativeCoordinates );
492  if ( !relativeCoordinates )
493  {
494  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
495  }
496 
497  mRelativeXButton->setEnabled( relativeCoordinates );
498  mRelativeYButton->setEnabled( relativeCoordinates );
499 
500  // update capacities
501  mCapacities = newCapacities;
502 }
503 
504 
506 {
509  constr.relative = c->relative();
510  constr.value = c->value();
511  return constr;
512 }
513 
515 {
517  context.snappingUtils = mMapCanvas->snappingUtils();
518  context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
519  context.xConstraint = _constraint( mXConstraint.get() );
520  context.yConstraint = _constraint( mYConstraint.get() );
521  context.distanceConstraint = _constraint( mDistanceConstraint.get() );
522  context.angleConstraint = _constraint( mAngleConstraint.get() );
523  context.cadPointList = mCadPointList;
524 
525  context.commonAngleConstraint.locked = true;
527  context.commonAngleConstraint.value = mCommonAngleConstraint;
528 
530 
531  bool res = output.valid;
532  QgsPointXY point = output.finalMapPoint;
533  mSnappedSegment.clear();
534  if ( output.edgeMatch.hasEdge() )
535  {
536  QgsPointXY edgePt0, edgePt1;
537  output.edgeMatch.edgePoints( edgePt0, edgePt1 );
538  mSnappedSegment << edgePt0 << edgePt1;
539  }
540  if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
541  {
542  if ( output.softLockCommonAngle != -1 )
543  {
544  mAngleConstraint->setLockMode( CadConstraint::SoftLock );
545  mAngleConstraint->setValue( output.softLockCommonAngle );
546  }
547  else
548  {
549  mAngleConstraint->setLockMode( CadConstraint::NoLock );
550  }
551  }
552 
553  // set the point coordinates in the map event
554  e->setMapPoint( point );
555 
556  mSnapMatch = context.snappingUtils->snapToMap( point );
557 
558  // update the point list
559  updateCurrentPoint( point );
560 
561  updateUnlockedConstraintValues( point );
562 
563  if ( res )
564  {
565  emit popWarning();
566  }
567  else
568  {
569  emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
570  }
571 
572  return res;
573 }
574 
575 
576 void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPointXY &point )
577 {
578  bool previousPointExist, penulPointExist;
579  QgsPointXY previousPt = previousPoint( &previousPointExist );
580  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
581 
582  // --- angle
583  if ( !mAngleConstraint->isLocked() && previousPointExist )
584  {
585  double angle = 0.0;
586  if ( penulPointExist && mAngleConstraint->relative() )
587  {
588  // previous angle
589  angle = std::atan2( previousPt.y() - penultimatePt.y(),
590  previousPt.x() - penultimatePt.x() );
591  }
592  angle = ( std::atan2( point.y() - previousPt.y(),
593  point.x() - previousPt.x()
594  ) - angle ) * 180 / M_PI;
595  // modulus
596  angle = std::fmod( angle, 360.0 );
597  mAngleConstraint->setValue( angle );
598  }
599  // --- distance
600  if ( !mDistanceConstraint->isLocked() && previousPointExist )
601  {
602  mDistanceConstraint->setValue( std::sqrt( previousPt.sqrDist( point ) ) );
603  }
604  // --- X
605  if ( !mXConstraint->isLocked() )
606  {
607  if ( previousPointExist && mXConstraint->relative() )
608  {
609  mXConstraint->setValue( point.x() - previousPt.x() );
610  }
611  else
612  {
613  mXConstraint->setValue( point.x() );
614  }
615  }
616  // --- Y
617  if ( !mYConstraint->isLocked() )
618  {
619  if ( previousPointExist && mYConstraint->relative() )
620  {
621  mYConstraint->setValue( point.y() - previousPt.y() );
622  }
623  else
624  {
625  mYConstraint->setValue( point.y() );
626  }
627  }
628 }
629 
630 
631 QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
632 {
633  QList<QgsPointXY> segment;
634  QgsPointXY pt1, pt2;
636 
637  QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
638 
639  QgsSnappingConfig canvasConfig = snappingUtils->config();
640  QgsSnappingConfig localConfig = snappingUtils->config();
641 
642  localConfig.setMode( QgsSnappingConfig::AllLayers );
643  localConfig.setType( QgsSnappingConfig::Segment );
644  snappingUtils->setConfig( localConfig );
645 
646  match = snappingUtils->snapToMap( originalMapPoint );
647 
648  snappingUtils->setConfig( canvasConfig );
649 
650  if ( match.isValid() && match.hasEdge() )
651  {
652  match.edgePoints( pt1, pt2 );
653  segment << pt1 << pt2;
654  }
655 
656  if ( snapped )
657  {
658  *snapped = segment.count() == 2;
659  }
660 
661  return segment;
662 }
663 
665 {
666  if ( mAdditionalConstraint == NoConstraint )
667  {
668  return false;
669  }
670 
671  bool previousPointExist, penulPointExist, snappedSegmentExist;
672  QgsPointXY previousPt = previousPoint( &previousPointExist );
673  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
674  mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
675 
676  if ( !previousPointExist || !snappedSegmentExist )
677  {
678  return false;
679  }
680 
681  double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
682 
683  if ( mAngleConstraint->relative() && penulPointExist )
684  {
685  angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
686  }
687 
688  if ( mAdditionalConstraint == Perpendicular )
689  {
690  angle += M_PI_2;
691  }
692 
693  angle *= 180 / M_PI;
694 
695  mAngleConstraint->setValue( angle );
696  mAngleConstraint->setLockMode( lockMode );
697  if ( lockMode == CadConstraint::HardLock )
698  {
699  mAdditionalConstraint = NoConstraint;
700  }
701 
702  return true;
703 }
704 
706 {
707  // event on map tool
708 
709  if ( !mCadEnabled )
710  return false;
711 
712  switch ( e->key() )
713  {
714  case Qt::Key_Backspace:
715  case Qt::Key_Delete:
716  {
717  removePreviousPoint();
718  releaseLocks( false );
719  break;
720  }
721  case Qt::Key_Escape:
722  {
723  releaseLocks();
724  break;
725  }
726  default:
727  {
728  keyPressEvent( e );
729  break;
730  }
731  }
732  // for map tools, continues with key press in any case
733  return false;
734 }
735 
737 {
738  clearPoints();
739  releaseLocks();
740 }
741 
743 {
744  // event on dock (this)
745 
746  if ( !mCadEnabled )
747  return;
748 
749  switch ( e->key() )
750  {
751  case Qt::Key_Backspace:
752  case Qt::Key_Delete:
753  {
754  removePreviousPoint();
755  releaseLocks( false );
756  break;
757  }
758  case Qt::Key_Escape:
759  {
760  releaseLocks();
761  break;
762  }
763  default:
764  {
765  filterKeyPress( e );
766  break;
767  }
768  }
769 }
770 
771 void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
772 {
773  clearPoints();
774  Q_FOREACH ( const QgsPointXY &pt, points )
775  {
776  addPoint( pt );
777  }
778 }
779 
780 bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
781 {
782  if ( !cadEnabled() )
783  {
784  return QgsDockWidget::eventFilter( obj, event );
785  }
786 
787  // event for line edits and map canvas
788  // we have to catch both KeyPress events and ShortcutOverride events. This is because
789  // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
790  // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
791  // us to intercept these keystrokes before they are caught by the global shortcuts
792  if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
793  {
794  if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
795  {
796  return filterKeyPress( keyEvent );
797  }
798  }
799  return QgsDockWidget::eventFilter( obj, event );
800 }
801 
802 bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
803 {
804  // we need to be careful here -- because this method is called on both KeyPress events AND
805  // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
806  // these event types for a single key press. I.e. pressing "A" may first call trigger a
807  // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
808  QEvent::Type type = e->type();
809  switch ( e->key() )
810  {
811  case Qt::Key_X:
812  {
813  // modifier+x ONLY caught for ShortcutOverride events...
814  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
815  {
816  mXConstraint->toggleLocked();
817  emit pointChanged( mCadPointList.value( 0 ) );
818  e->accept();
819  }
820  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
821  {
822  if ( mCapacities.testFlag( RelativeCoordinates ) )
823  {
824  mXConstraint->toggleRelative();
825  emit pointChanged( mCadPointList.value( 0 ) );
826  e->accept();
827  }
828  }
829  // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
830  else if ( type == QEvent::KeyPress )
831  {
832  mXLineEdit->setFocus();
833  mXLineEdit->selectAll();
834  e->accept();
835  }
836  break;
837  }
838  case Qt::Key_Y:
839  {
840  // modifier+y ONLY caught for ShortcutOverride events...
841  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
842  {
843  mYConstraint->toggleLocked();
844  emit pointChanged( mCadPointList.value( 0 ) );
845  e->accept();
846  }
847  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
848  {
849  if ( mCapacities.testFlag( RelativeCoordinates ) )
850  {
851  mYConstraint->toggleRelative();
852  emit pointChanged( mCadPointList.value( 0 ) );
853  e->accept();
854  }
855  }
856  // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
857  else if ( type == QEvent::KeyPress )
858  {
859  mYLineEdit->setFocus();
860  mYLineEdit->selectAll();
861  e->accept();
862  }
863  break;
864  }
865  case Qt::Key_A:
866  {
867  // modifier+a ONLY caught for ShortcutOverride events...
868  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
869  {
870  if ( mCapacities.testFlag( AbsoluteAngle ) )
871  {
872  mAngleConstraint->toggleLocked();
873  emit pointChanged( mCadPointList.value( 0 ) );
874  e->accept();
875  }
876  }
877  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
878  {
879  if ( mCapacities.testFlag( RelativeAngle ) )
880  {
881  mAngleConstraint->toggleRelative();
882  emit pointChanged( mCadPointList.value( 0 ) );
883  e->accept();
884  }
885  }
886  // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
887  else if ( type == QEvent::KeyPress )
888  {
889  mAngleLineEdit->setFocus();
890  mAngleLineEdit->selectAll();
891  e->accept();
892  }
893  break;
894  }
895  case Qt::Key_D:
896  {
897  // modifier+d ONLY caught for ShortcutOverride events...
898  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
899  {
900  if ( mCapacities.testFlag( RelativeCoordinates ) )
901  {
902  mDistanceConstraint->toggleLocked();
903  emit pointChanged( mCadPointList.value( 0 ) );
904  e->accept();
905  }
906  }
907  // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
908  else if ( type == QEvent::KeyPress )
909  {
910  mDistanceLineEdit->setFocus();
911  mDistanceLineEdit->selectAll();
912  e->accept();
913  }
914  break;
915  }
916  case Qt::Key_C:
917  {
918  if ( type == QEvent::KeyPress )
919  {
920  setConstructionMode( !mConstructionMode );
921  e->accept();
922  }
923  break;
924  }
925  case Qt::Key_P:
926  {
927  if ( type == QEvent::KeyPress )
928  {
929  bool parallel = mParallelAction->isChecked();
930  bool perpendicular = mPerpendicularAction->isChecked();
931 
932  if ( !parallel && !perpendicular )
933  {
934  lockAdditionalConstraint( Perpendicular );
935  }
936  else if ( perpendicular )
937  {
938  lockAdditionalConstraint( Parallel );
939  }
940  else
941  {
942  lockAdditionalConstraint( NoConstraint );
943  }
944  e->accept();
945  }
946  break;
947  }
948  default:
949  {
950  return false; // continues
951  }
952  }
953  return e->isAccepted();
954 }
955 
957 {
958  connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
959  if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
960  {
961  mErrorLabel->setText( tr( "CAD tools can not be used on geographic coordinates. Change the coordinates system in the project properties." ) );
962  mErrorLabel->show();
963  mEnableAction->setEnabled( false );
964  setCadEnabled( false );
965  }
966  else
967  {
968  mEnableAction->setEnabled( true );
969  mErrorLabel->hide();
970  mCadWidget->show();
971 
972  mCurrentMapToolSupportsCad = true;
973 
974  if ( mSessionActive && !isVisible() )
975  {
976  show();
977  }
978  setCadEnabled( mSessionActive );
979  }
980 }
981 
983 {
985 
986  mEnableAction->setEnabled( false );
987  mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
988  mErrorLabel->show();
989  mCadWidget->hide();
990 
991  mCurrentMapToolSupportsCad = false;
992 
993  setCadEnabled( false );
994 }
995 
997 {
998  mCadPaintItem->update();
999 }
1000 
1002 {
1003  if ( !pointsCount() )
1004  {
1005  mCadPointList << point;
1006  }
1007  else
1008  {
1009  mCadPointList.insert( 0, point );
1010  }
1011 
1012  updateCapacity();
1013 }
1014 
1015 void QgsAdvancedDigitizingDockWidget::removePreviousPoint()
1016 {
1017  if ( !pointsCount() )
1018  return;
1019 
1020  int i = pointsCount() > 1 ? 1 : 0;
1021  mCadPointList.removeAt( i );
1022  updateCapacity();
1023 }
1024 
1026 {
1027  mCadPointList.clear();
1028  mSnappedSegment.clear();
1029  mSnappedToVertex = false;
1030 
1031  updateCapacity();
1032 }
1033 
1034 void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPointXY &point )
1035 {
1036  if ( !pointsCount() )
1037  {
1038  mCadPointList << point;
1039  updateCapacity();
1040  }
1041  else
1042  {
1043  mCadPointList[0] = point;
1044  }
1045 }
1046 
1047 
1049 {
1050  mLockMode = mode;
1051  mLockerButton->setChecked( mode == HardLock );
1052  if ( mRepeatingLockButton )
1053  {
1054  if ( mode == HardLock )
1055  {
1056  mRepeatingLockButton->setEnabled( true );
1057  }
1058  else
1059  {
1060  mRepeatingLockButton->setChecked( false );
1061  mRepeatingLockButton->setEnabled( false );
1062  }
1063  }
1064 
1065  if ( mode == NoLock )
1066  {
1067  mLineEdit->clear();
1068  }
1069 }
1070 
1072 {
1073  mRepeatingLock = repeating;
1074  if ( mRepeatingLockButton )
1075  mRepeatingLockButton->setChecked( repeating );
1076 }
1077 
1079 {
1080  mRelative = relative;
1081  if ( mRelativeButton )
1082  {
1083  mRelativeButton->setChecked( relative );
1084  }
1085 }
1086 
1087 void QgsAdvancedDigitizingDockWidget::CadConstraint::setValue( double value, bool updateWidget )
1088 {
1089  mValue = value;
1090  if ( updateWidget )
1091  mLineEdit->setText( QLocale().toString( value, 'f', 6 ) );
1092 }
1093 
1095 {
1096  setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1097 }
1098 
1100 {
1101  setRelative( !mRelative );
1102 }
1103 
1105 {
1106  if ( exist )
1107  *exist = pointsCount() > 0;
1108  if ( pointsCount() > 0 )
1109  return mCadPointList.value( 0 );
1110  else
1111  return QgsPointXY();
1112 }
1113 
1115 {
1116  if ( exist )
1117  *exist = pointsCount() > 1;
1118  if ( pointsCount() > 1 )
1119  return mCadPointList.value( 1 );
1120  else
1121  return QgsPointXY();
1122 }
1123 
1125 {
1126  if ( exist )
1127  *exist = pointsCount() > 2;
1128  if ( pointsCount() > 2 )
1129  return mCadPointList.value( 2 );
1130  else
1131  return QgsPointXY();
1132 }
int pointsCount() const
The number of points in the CAD point helper list.
This corresponds to distance and relative coordinates.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
The CadConstraint is an abstract class for all basic constraints (angle/distance/x/y).
QgsCadUtils::AlignMapPointConstraint yConstraint
Constraint for Y coordinate.
Definition: qgscadutils.h:64
void clearPoints()
Removes all points from the CAD point list.
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
void focusOut()
Emitted when parent object loses focus.
A event filter for watching for focus events on a parent object.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QgsPointXY previousPoint(bool *exists=nullptr) const
The previous point.
bool enabled() const
Returns if snapping is enabled.
Structure with details of one constraint.
Definition: qgscadutils.h:37
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
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:97
QVariant evaluate()
Evaluate the feature and return the result.
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
bool applyConstraints(QgsMapMouseEvent *e)
apply the CAD constraints.
QgsPointXY currentPoint(bool *exists=nullptr) const
The last point.
On all vector layers.
void setRelative(bool relative)
Set if the constraint should be treated relative.
void clear()
Clear any cached previous clicks and helper lines.
void setMapPoint(const QgsPointXY &point)
Set the (snapped) point this event points to in map coordinates.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:73
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
LockMode lockMode() const
The current lock mode of this constraint.
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
QgsPointLocator::Match edgeMatch
Snapped segment - only valid if actually used for something.
Definition: qgscadutils.h:96
void releaseLocks(bool releaseRepeatingLocks=true)
unlock all constraints
bool canvasKeyPressEventFilter(QKeyEvent *e)
Filter key events to e.g.
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:169
bool locked
Whether the constraint is active, i.e. should be considered.
Definition: qgscadutils.h:46
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
void updateCadPaintItem()
Updates canvas item that displays constraints on the ma.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
void setRepeatingLock(bool repeating)
Sets whether a repeating lock is set for the constraint.
double value() const
The value of the constraint.
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
Definition: qgscadutils.h:66
void addPoint(const QgsPointXY &point)
Adds point to the CAD point list.
QgsPointXY penultimatePoint(bool *exists=nullptr) const
The penultimate point.
void hideEvent(QHideEvent *) override
Disables the CAD tools when hiding the dock.
QgsDockWidget subclass with more fine-grained control over how the widget is closed or opened...
Definition: qgsdockwidget.h:31
Structure defining all constraints for alignMapPoint() method.
Definition: qgscadutils.h:54
void edgePoints(QgsPointXY &pt1, QgsPointXY &pt2) const
Only for a valid edge match - obtain endpoints of the edge.
QgsAdvancedDigitizingDockWidget(QgsMapCanvas *canvas, QWidget *parent=nullptr)
Create an advanced digitizing dock widget.
QgsCadUtils::AlignMapPointConstraint commonAngleConstraint
Constraint for soft lock to a common angle.
Definition: qgscadutils.h:70
void destinationCrsChanged()
Emitted when map CRS has changed.
void enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
QgsPointXY finalMapPoint
map point aligned according to the constraints
Definition: qgscadutils.h:93
void setPoints(const QList< QgsPointXY > &points)
Configures list of current CAD points.
double value
Numeric value of the constraint (coordinate/distance in map units or angle in degrees) ...
Definition: qgscadutils.h:50
double softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition: qgscadutils.h:99
double x
Definition: qgspointxy.h:47
Structure returned from alignMapPoint() method.
Definition: qgscadutils.h:87
double mapUnitsPerPixel
Map units/pixel ratio from map canvas. Needed for.
Definition: qgscadutils.h:59
void pointChanged(const QgsPointXY &point)
Sometimes a constraint may change the current point out of a mouse event.
void popWarning()
Remove any previously emitted warnings (if any)
void setMode(SnappingMode mode)
define the mode of snapping
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsPointXY originalMapPoint() const
Returns the original, unmodified map point of the mouse cursor.
bool relative() const
Is the constraint in relative mode.
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
Definition: qgscadutils.h:62
QLineEdit * lineEdit() const
The line edit that manages the value of the constraint.
QgsSnappingConfig config
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QgsCadUtils::AlignMapPointConstraint angleConstraint
Constraint for angle.
Definition: qgscadutils.h:68
bool valid
Whether the combination of constraints is actually valid.
Definition: qgscadutils.h:90
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:430
void setType(SnappingType type)
define the type of snapping
This class has all the configuration of snapping and can return answers to snapping queries...
void pushWarning(const QString &message)
Push a warning.
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be null.
Definition: qgscadutils.h:57
This is a container for configuration of the snapping of the project.
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:37
void setValue(double value, bool updateWidget=true)
Set the value of the constraint.
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr)
Snap to map according to the current configuration. Optional filter allows discarding unwanted matche...
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
bool relative
Whether the value is relative to previous value.
Definition: qgscadutils.h:48
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
bool alignToSegment(QgsMapMouseEvent *e, QgsAdvancedDigitizingDockWidget::CadConstraint::LockMode lockMode=QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock)
align to segment for additional constraint.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:99
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
AdditionalConstraint
Additional constraints which can be enabled.
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (...
QList< QgsPointXY > cadPointList
List of recent CAD points in map coordinates.
Definition: qgscadutils.h:77