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