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