QGIS API Documentation  2.14.0-Essen
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 <QSettings>
17 #include <QMenu>
18 
19 #include "math.h"
20 
23 #include "qgsapplication.h"
24 #include "qgsexpression.h"
25 #include "qgslogger.h"
26 #include "qgsmapcanvas.h"
27 #include "qgsmaptoolcapture.h"
29 #include "qgsmessagebaritem.h"
30 #include "qgspoint.h"
31 #include "qgslinestringv2.h"
32 
34 {
35  bool acceptMatch( const QgsPointLocator::Match& m ) override { return m.hasEdge(); }
36 };
37 
38 bool QgsAdvancedDigitizingDockWidget::lineCircleIntersection( const QgsPoint& center, const double radius, const QList<QgsPoint>& segment, QgsPoint& intersection )
39 {
40  Q_ASSERT( segment.count() == 2 );
41 
42  // formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html
43 
44  const double x1 = segment[0].x() - center.x();
45  const double y1 = segment[0].y() - center.y();
46  const double x2 = segment[1].x() - center.x();
47  const double y2 = segment[1].y() - center.y();
48  const double dx = x2 - x1;
49  const double dy = y2 - y1;
50 
51  const double dr = sqrt( pow( dx, 2 ) + pow( dy, 2 ) );
52  const double d = x1 * y2 - x2 * y1;
53 
54  const double disc = pow( radius, 2 ) * pow( dr, 2 ) - pow( d, 2 );
55 
56  if ( disc < 0 )
57  {
58  //no intersection or tangeant
59  return false;
60  }
61  else
62  {
63  // two solutions
64  const int sgnDy = dy < 0 ? -1 : 1;
65 
66  const double ax = center.x() + ( d * dy + sgnDy * dx * sqrt( pow( radius, 2 ) * pow( dr, 2 ) - pow( d, 2 ) ) ) / ( pow( dr, 2 ) );
67  const double ay = center.y() + ( -d * dx + qAbs( dy ) * sqrt( pow( radius, 2 ) * pow( dr, 2 ) - pow( d, 2 ) ) ) / ( pow( dr, 2 ) );
68  const QgsPoint p1( ax, ay );
69 
70  const double bx = center.x() + ( d * dy - sgnDy * dx * sqrt( pow( radius, 2 ) * pow( dr, 2 ) - pow( d, 2 ) ) ) / ( pow( dr, 2 ) );
71  const double by = center.y() + ( -d * dx - qAbs( dy ) * sqrt( pow( radius, 2 ) * pow( dr, 2 ) - pow( d, 2 ) ) ) / ( pow( dr, 2 ) );
72  const QgsPoint p2( bx, by );
73 
74  // snap to nearest intersection
75 
76  if ( intersection.sqrDist( p1 ) < intersection.sqrDist( p2 ) )
77  {
78  intersection.set( p1.x(), p1.y() );
79  }
80  else
81  {
82  intersection.set( p2.x(), p2.y() );
83  }
84  return true;
85  }
86 }
87 
88 
90  : QDockWidget( parent )
91  , mMapCanvas( canvas )
92  , mCurrentMapToolSupportsCad( false )
93  , mCadEnabled( false )
94  , mConstructionMode( false )
95  , mSnappingMode(( QgsMapMouseEvent::SnappingMode ) QSettings().value( "/Cad/SnappingMode", QgsMapMouseEvent::SnapProjectConfig ).toInt() )
96  , mCommonAngleConstraint( QSettings().value( "/Cad/CommonAngle", 90 ).toInt() )
97  , mSnappedToVertex( false )
98  , mSessionActive( false )
99  , mErrorMessage( nullptr )
100 {
101  setupUi( this );
102 
103  mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this ) ;
104 
105  mAngleConstraint = new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton );
106  mDistanceConstraint = new CadConstraint( mDistanceLineEdit, mLockDistanceButton ) ;
107  mXConstraint = new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton );
108  mYConstraint = new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton ) ;
109  mAdditionalConstraint = NoConstraint ;
110 
111  mMapCanvas->installEventFilter( this );
112  mAngleLineEdit->installEventFilter( this );
113  mDistanceLineEdit->installEventFilter( this );
114  mXLineEdit->installEventFilter( this );
115  mYLineEdit->installEventFilter( this );
116 
117  // this action is also used in the advanced digitizing tool bar
118  mEnableAction = new QAction( this );
119  mEnableAction->setText( tr( "Enable advanced digitizing tools" ) );
120  mEnableAction->setIcon( QgsApplication::getThemeIcon( "/cadtools/cad.png" ) );
121  mEnableAction->setCheckable( true );
122  mEnabledButton->addAction( mEnableAction );
123  mEnabledButton->setDefaultAction( mEnableAction );
124 
125  // Connect the UI to the event filter to update constraints
126  connect( mEnableAction, SIGNAL( triggered( bool ) ), this, SLOT( activateCad( bool ) ) );
127  connect( mConstructionModeButton, SIGNAL( clicked( bool ) ), this, SLOT( setConstructionMode( bool ) ) );
128  connect( mParallelButton, SIGNAL( clicked( bool ) ), this, SLOT( addtionalConstraintClicked( bool ) ) );
129  connect( mPerpendicularButton, SIGNAL( clicked( bool ) ), this, SLOT( addtionalConstraintClicked( bool ) ) );
130  connect( mLockAngleButton, SIGNAL( clicked( bool ) ), this, SLOT( lockConstraint( bool ) ) );
131  connect( mLockDistanceButton, SIGNAL( clicked( bool ) ), this, SLOT( lockConstraint( bool ) ) );
132  connect( mLockXButton, SIGNAL( clicked( bool ) ), this, SLOT( lockConstraint( bool ) ) );
133  connect( mLockYButton, SIGNAL( clicked( bool ) ), this, SLOT( lockConstraint( bool ) ) );
134  connect( mRelativeAngleButton, SIGNAL( clicked( bool ) ), this, SLOT( setConstraintRelative( bool ) ) );
135  connect( mRelativeXButton, SIGNAL( clicked( bool ) ), this, SLOT( setConstraintRelative( bool ) ) );
136  connect( mRelativeYButton, SIGNAL( clicked( bool ) ), this, SLOT( setConstraintRelative( bool ) ) );
137  connect( mAngleLineEdit, SIGNAL( returnPressed() ), this, SLOT( lockConstraint() ) );
138  connect( mDistanceLineEdit, SIGNAL( returnPressed() ), this, SLOT( lockConstraint() ) );
139  connect( mXLineEdit, SIGNAL( returnPressed() ), this, SLOT( lockConstraint() ) );
140  connect( mYLineEdit, SIGNAL( returnPressed() ), this, SLOT( lockConstraint() ) );
141 
142  // config menu
143  QMenu *menu = new QMenu( this );
144  // common angles
145  QActionGroup* angleButtonGroup = new QActionGroup( menu ); // actions are exclusive for common angles
146  mCommonAngleActions = QMap<QAction*, int>();
147  QList< QPair< int, QString > > commonAngles;
148  commonAngles << QPair<int, QString>( 0, tr( "Do not snap to common angles" ) );
149  commonAngles << QPair<int, QString>( 30, tr( "Snap to 30%1 angles" ).arg( QString::fromUtf8( "°" ) ) );
150  commonAngles << QPair<int, QString>( 45, tr( "Snap to 45%1 angles" ).arg( QString::fromUtf8( "°" ) ) );
151  commonAngles << QPair<int, QString>( 90, tr( "Snap to 90%1 angles" ).arg( QString::fromUtf8( "°" ) ) );
152  for ( QList< QPair< int, QString > >::const_iterator it = commonAngles.begin(); it != commonAngles.end(); ++it )
153  {
154  QAction* action = new QAction( it->second, menu );
155  action->setCheckable( true );
156  action->setChecked( it->first == mCommonAngleConstraint );
157  menu->addAction( action );
158  angleButtonGroup->addAction( action );
159  mCommonAngleActions.insert( action, it->first );
160  }
161  // snapping on layers
162  menu->addSeparator();
163  QActionGroup* snapButtonGroup = new QActionGroup( menu ); // actions are exclusive for snapping modes
166  snappingModes << QPair<QgsMapMouseEvent::SnappingMode, QString>( QgsMapMouseEvent::NoSnapping, tr( "Do not snap to vertices or segment" ) );
167  snappingModes << QPair<QgsMapMouseEvent::SnappingMode, QString>( QgsMapMouseEvent::SnapProjectConfig, tr( "Snap according to project configuration" ) );
168  snappingModes << QPair<QgsMapMouseEvent::SnappingMode, QString>( QgsMapMouseEvent::SnapAllLayers, tr( "Snap to all layers" ) );
169  for ( QList< QPair< QgsMapMouseEvent::SnappingMode, QString > >::const_iterator it = snappingModes.begin(); it != snappingModes.end(); ++it )
170  {
171  QAction* action = new QAction( it->second, menu );
172  action->setCheckable( true );
173  action->setChecked( it->first == mSnappingMode );
174  menu->addAction( action );
175  snapButtonGroup->addAction( action );
176  mSnappingActions.insert( action, it->first );
177  }
178 
179  mSettingsButton->setMenu( menu );
180  connect( mSettingsButton, SIGNAL( triggered( QAction* ) ), this, SLOT( settingsButtonTriggered( QAction* ) ) );
181 
182  updateCapacity( true );
183 }
184 
186 {
187  // disable CAD but do not unset map event filter
188  // so it will be reactivated whenever the map tool is show again
189  setCadEnabled( false );
190 }
191 
192 void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
193 {
194  mCadEnabled = enabled;
195  mEnableAction->setChecked( enabled );
196  mCadButtons->setEnabled( enabled );
197  mInputWidgets->setEnabled( enabled );
198 
199  clearPoints();
200  releaseLocks();
201  setConstructionMode( false );
202 }
203 
204 void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
205 {
206  enabled &= mCurrentMapToolSupportsCad;
207 
208  mSessionActive = enabled;
209 
210  if ( enabled && !isVisible() )
211  {
212  show();
213  }
214 
215  setCadEnabled( enabled );
216 }
217 
218 void QgsAdvancedDigitizingDockWidget::addtionalConstraintClicked( bool activated )
219 {
220  if ( !activated )
221  {
222  lockAdditionalConstraint( NoConstraint );
223  }
224  if ( sender() == mParallelButton )
225  {
226  lockAdditionalConstraint( Parallel );
227  }
228  else if ( sender() == mPerpendicularButton )
229  {
230  lockAdditionalConstraint( Perpendicular );
231  }
232 }
233 
234 
235 void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
236 {
237  if ( sender() == mRelativeAngleButton )
238  {
239  mAngleConstraint->setRelative( activate );
240  }
241  else if ( sender() == mRelativeXButton )
242  {
243  mXConstraint->setRelative( activate );
244  }
245  else if ( sender() == mRelativeYButton )
246  {
247  mYConstraint->setRelative( activate );
248  }
249 }
250 
251 void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
252 {
253  mConstructionMode = enabled;
254  mConstructionModeButton->setChecked( enabled );
255 }
256 
257 void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction* action )
258 {
259  // snapping
261  if ( isn != mSnappingActions.constEnd() )
262  {
263  isn.key()->setChecked( true );
264  mSnappingMode = isn.value();
265  QSettings().setValue( "/Cad/SnappingMode", ( int )isn.value() );
266  return;
267  }
268 
269  // common angles
270  QMap<QAction*, int>::const_iterator ica = mCommonAngleActions.constFind( action );
271  if ( ica != mCommonAngleActions.constEnd() )
272  {
273  ica.key()->setChecked( true );
274  mCommonAngleConstraint = ica.value();
275  QSettings().setValue( "/Cad/CommonAngle", ica.value() );
276  return;
277  }
278 }
279 
280 void QgsAdvancedDigitizingDockWidget::releaseLocks()
281 {
282  // release all locks except construction mode
283 
284  lockAdditionalConstraint( NoConstraint );
285 
286  mAngleConstraint->setLockMode( CadConstraint::NoLock );
287  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
288  mXConstraint->setLockMode( CadConstraint::NoLock );
289  mYConstraint->setLockMode( CadConstraint::NoLock );
290 }
291 
292 #if 0
293 void QgsAdvancedDigitizingDockWidget::emit pointChanged()
294 {
295  // run a fake map mouse event to update the paint item
296  QPoint globalPos = mMapCanvas->cursor().pos();
297  QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
298  QMouseEvent* e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
299  mCurrentMapTool->canvasMoveEvent( e );
300 }
301 #endif
302 
303 void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
304 {
305  QObject* obj = sender();
306  CadConstraint* constraint = nullptr;
307  if ( obj == mAngleLineEdit || obj == mLockAngleButton )
308  {
309  constraint = mAngleConstraint;
310  }
311  else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
312  {
313  constraint = mDistanceConstraint;
314  }
315  else if ( obj == mXLineEdit || obj == mLockXButton )
316  {
317  constraint = mXConstraint;
318  }
319  else if ( obj == mYLineEdit || obj == mLockYButton )
320  {
321  constraint = mYConstraint;
322  }
323  if ( !constraint )
324  {
325  return;
326  }
327 
328  if ( activate )
329  {
330  QString textValue = constraint->lineEdit()->text();
331  bool ok;
332  double value = textValue.toDouble( &ok );
333  if ( !textValue.isEmpty() )
334  {
335  if ( ok )
336  {
337  constraint->setValue( value );
338  }
339  else
340  {
341  // try to evalute expression
342  QgsExpression expr( textValue );
343  QVariant result = expr.evaluate();
344  value = result.toDouble( &ok );
345  if ( expr.hasEvalError() || !ok )
346  {
347  activate = false;
348  }
349  else
350  {
351  constraint->setValue( value );
352  }
353  }
354  }
355  else
356  {
357  activate = false;
358  }
359  }
360  constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
361 
362  if ( activate )
363  {
364  // deactivate perpendicular/parallel if angle has been activated
365  if ( constraint == mAngleConstraint )
366  {
367  lockAdditionalConstraint( NoConstraint );
368  }
369 
370  // run a fake map mouse event to update the paint item
371  emit pointChanged( mCadPointList.value( 0 ) );
372  }
373 }
374 
375 void QgsAdvancedDigitizingDockWidget::lockAdditionalConstraint( AdditionalConstraint constraint )
376 {
377  mAdditionalConstraint = constraint;
378  mPerpendicularButton->setChecked( constraint == Perpendicular );
379  mParallelButton->setChecked( constraint == Parallel );
380 }
381 
382 void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
383 {
384  CadCapacities newCapacities = nullptr;
385  // first point is the mouse point (it doesn't count)
386  if ( mCadPointList.count() > 1 )
387  {
388  newCapacities |= AbsoluteAngle | RelativeCoordinates;
389  }
390  if ( mCadPointList.count() > 2 )
391  {
392  newCapacities |= RelativeAngle;
393  }
394  if ( !updateUIwithoutChange && newCapacities == mCapacities )
395  {
396  return;
397  }
398 
399  // update the UI according to new capacities
400  // still keep the old to compare
401 
402  bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
403  bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
404  bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
405 
406  mPerpendicularButton->setEnabled( absoluteAngle );
407  mParallelButton->setEnabled( absoluteAngle );
408  if ( !absoluteAngle )
409  {
410  lockAdditionalConstraint( NoConstraint );
411  }
412 
413  // absolute angle = azimuth, relative = from previous line
414  mLockAngleButton->setEnabled( absoluteAngle );
415  mRelativeAngleButton->setEnabled( relativeAngle );
416  mAngleLineEdit->setEnabled( absoluteAngle );
417  if ( !absoluteAngle )
418  {
419  mAngleConstraint->setLockMode( CadConstraint::NoLock );
420  }
421  if ( !relativeAngle )
422  {
423  mAngleConstraint->setRelative( false );
424  }
425  else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
426  {
427  // set angle mode to relative if can do and wasn't available before
428  mAngleConstraint->setRelative( true );
429  }
430 
431  // distance is alway relative
432  mLockDistanceButton->setEnabled( relativeCoordinates );
433  mDistanceLineEdit->setEnabled( relativeCoordinates );
434  if ( !relativeCoordinates )
435  {
436  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
437  }
438 
439  mRelativeXButton->setEnabled( relativeCoordinates );
440  mRelativeYButton->setEnabled( relativeCoordinates );
441 
442  // update capacities
443  mCapacities = newCapacities;
444 }
445 
446 
448 {
449  bool res = true;
450 
451  QgsDebugMsg( "Constraints (locked / relative / value" );
452  QgsDebugMsg( QString( "Angle: %1 %2 %3" ).arg( mAngleConstraint->isLocked() ).arg( mAngleConstraint->relative() ).arg( mAngleConstraint->value() ) );
453  QgsDebugMsg( QString( "Distance: %1 %2 %3" ).arg( mDistanceConstraint->isLocked() ).arg( mDistanceConstraint->relative() ).arg( mDistanceConstraint->value() ) );
454  QgsDebugMsg( QString( "X: %1 %2 %3" ).arg( mXConstraint->isLocked() ).arg( mXConstraint->relative() ).arg( mXConstraint->value() ) );
455  QgsDebugMsg( QString( "Y: %1 %2 %3" ).arg( mYConstraint->isLocked() ).arg( mYConstraint->relative() ).arg( mYConstraint->value() ) );
456 
457  QgsPoint point = e->snapPoint( mSnappingMode );
458 
459  mSnappedSegment = e->snapSegment( mSnappingMode );
460 
461  bool previousPointExist, penulPointExist;
462  QgsPoint previousPt = previousPoint( &previousPointExist );
463  QgsPoint penultimatePt = penultimatePoint( &penulPointExist );
464 
465  // *****************************
466  // ---- X Constrain
467  if ( mXConstraint->isLocked() )
468  {
469  if ( !mXConstraint->relative() )
470  {
471  point.setX( mXConstraint->value() );
472  }
473  else if ( mCapacities.testFlag( RelativeCoordinates ) )
474  {
475  point.setX( previousPt.x() + mXConstraint->value() );
476  }
477  if ( !mSnappedSegment.isEmpty() && !mXConstraint->isLocked() )
478  {
479  // intersect with snapped segment line at X ccordinate
480  const double dx = mSnappedSegment.at( 1 ).x() - mSnappedSegment.at( 0 ).x();
481  if ( dx == 0 )
482  {
483  point.setY( mSnappedSegment.at( 0 ).y() );
484  }
485  else
486  {
487  const double dy = mSnappedSegment.at( 1 ).y() - mSnappedSegment.at( 0 ).y();
488  point.setY( mSnappedSegment.at( 0 ).y() + ( dy * ( point.x() - mSnappedSegment.at( 0 ).x() ) ) / dx );
489  }
490  }
491  }
492  // *****************************
493  // ---- Y Constrain
494  if ( mYConstraint->isLocked() )
495  {
496  if ( !mYConstraint->relative() )
497  {
498  point.setY( mYConstraint->value() );
499  }
500  else if ( mCapacities.testFlag( RelativeCoordinates ) )
501  {
502  point.setY( previousPt.y() + mYConstraint->value() );
503  }
504  if ( !mSnappedSegment.isEmpty() && !mYConstraint->isLocked() )
505  {
506  // intersect with snapped segment line at Y ccordinate
507  const double dy = mSnappedSegment.at( 1 ).y() - mSnappedSegment.at( 0 ).y();
508  if ( dy == 0 )
509  {
510  point.setX( mSnappedSegment.at( 0 ).x() );
511  }
512  else
513  {
514  const double dx = mSnappedSegment.at( 1 ).x() - mSnappedSegment.at( 0 ).x();
515  point.setX( mSnappedSegment.at( 0 ).x() + ( dx * ( point.y() - mSnappedSegment.at( 0 ).y() ) ) / dy );
516  }
517  }
518  }
519  // *****************************
520  // ---- Angle constrain
521  // input angles are in degrees
522  if ( mAngleConstraint->lockMode() == CadConstraint::SoftLock )
523  {
524  // reset the lock
525  mAngleConstraint->setLockMode( CadConstraint::NoLock );
526  }
527  if ( !mAngleConstraint->isLocked() && mCapacities.testFlag( AbsoluteAngle ) && mCommonAngleConstraint != 0 )
528  {
529  double commonAngle = mCommonAngleConstraint * M_PI / 180;
530  // see if soft common angle constraint should be performed
531  // only if not in HardLock mode
532  double softAngle = qAtan2( point.y() - previousPt.y(),
533  point.x() - previousPt.x() );
534  double deltaAngle = 0;
535  if ( mAngleConstraint->relative() && mCapacities.testFlag( RelativeAngle ) )
536  {
537  // compute the angle relative to the last segment (0° is aligned with last segment)
538  deltaAngle = qAtan2( previousPt.y() - penultimatePt.y(),
539  previousPt.x() - penultimatePt.x() );
540  softAngle -= deltaAngle;
541  }
542  int quo = qRound( softAngle / commonAngle );
543  if ( qAbs( softAngle - quo * commonAngle ) * 180.0 * M_1_PI <= SoftConstraintToleranceDegrees )
544  {
545  // also check the distance in pixel to the line, otherwise it's too sticky at long ranges
546  softAngle = quo * commonAngle ;
547  // http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
548  // use the direction vector (cos(a),sin(a)) from previous point. |x2-x1|=1 since sin2+cos2=1
549  const double dist = qAbs( qCos( softAngle + deltaAngle ) * ( previousPt.y() - point.y() )
550  - qSin( softAngle + deltaAngle ) * ( previousPt.x() - point.x() ) );
551  if ( dist / mMapCanvas->mapSettings().mapUnitsPerPixel() < SoftConstraintTolerancePixel )
552  {
553  mAngleConstraint->setLockMode( CadConstraint::SoftLock );
554  mAngleConstraint->setValue( 180.0 / M_PI * softAngle );
555  }
556  }
557  }
558  if ( mAngleConstraint->isLocked() )
559  {
560  double angleValue = mAngleConstraint->value() * M_PI / 180;
561  if ( mAngleConstraint->relative() && mCapacities.testFlag( RelativeAngle ) )
562  {
563  // compute the angle relative to the last segment (0° is aligned with last segment)
564  angleValue += qAtan2( previousPt.y() - penultimatePt.y(),
565  previousPt.x() - penultimatePt.x() );
566  }
567 
568  double cosa = qCos( angleValue );
569  double sina = qSin( angleValue );
570  double v = ( point.x() - previousPt.x() ) * cosa + ( point.y() - previousPt.y() ) * sina ;
571  if ( mXConstraint->isLocked() && mYConstraint->isLocked() )
572  {
573  // do nothing if both X,Y are already locked
574  }
575  else if ( mXConstraint->isLocked() )
576  {
577  if ( cosa == 0 )
578  {
579  res = false;
580  }
581  else
582  {
583  double x = mXConstraint->value();
584  if ( !mXConstraint->relative() )
585  {
586  x -= previousPt.x();
587  }
588  point.setY( previousPt.y() + x * sina / cosa );
589  }
590  }
591  else if ( mYConstraint->isLocked() )
592  {
593  if ( sina == 0 )
594  {
595  res = false;
596  }
597  else
598  {
599  double y = mYConstraint->value();
600  if ( !mYConstraint->relative() )
601  {
602  y -= previousPt.y();
603  }
604  point.setX( previousPt.x() + y * cosa / sina );
605  }
606  }
607  else
608  {
609  point.setX( previousPt.x() + cosa * v );
610  point.setY( previousPt.y() + sina * v );
611  }
612 
613  if ( !mSnappedSegment.isEmpty() && !mDistanceConstraint->isLocked() )
614  {
615  // magnetize to the intersection of the snapped segment and the lockedAngle
616 
617  // line of previous point + locked angle
618  const double x1 = previousPt.x();
619  const double y1 = previousPt.y();
620  const double x2 = previousPt.x() + cosa;
621  const double y2 = previousPt.y() + sina;
622  // line of snapped segment
623  const double x3 = mSnappedSegment.at( 0 ).x();
624  const double y3 = mSnappedSegment.at( 0 ).y();
625  const double x4 = mSnappedSegment.at( 1 ).x();
626  const double y4 = mSnappedSegment.at( 1 ).y();
627 
628  const double d = ( x1 - x2 ) * ( y3 - y4 ) - ( y1 - y2 ) * ( x3 - x4 );
629 
630  // do not compute intersection if lines are almost parallel
631  // this threshold might be adapted
632  if ( qAbs( d ) > 0.01 )
633  {
634  point.setX((( x3 - x4 )*( x1*y2 - y1*x2 ) - ( x1 - x2 )*( x3*y4 - y3*x4 ) ) / d );
635  point.setY((( y3 - y4 )*( x1*y2 - y1*x2 ) - ( y1 - y2 )*( x3*y4 - y3*x4 ) ) / d );
636  }
637  }
638  }
639  // *****************************
640  // ---- Distance constraint
641  if ( mDistanceConstraint->isLocked() && previousPointExist )
642  {
643  if ( mXConstraint->isLocked() || mYConstraint->isLocked() )
644  {
645  // perform both to detect errors in constraints
646  if ( mXConstraint->isLocked() )
647  {
648  const QList<QgsPoint> verticalSegment = QList<QgsPoint>()
649  << QgsPoint( mXConstraint->value(), point.y() )
650  << QgsPoint( mXConstraint->value(), point.y() + 1 );
651  res &= lineCircleIntersection( previousPt, mDistanceConstraint->value(), verticalSegment, point );
652  }
653  if ( mYConstraint->isLocked() )
654  {
655  const QList<QgsPoint> horizontalSegment = QList<QgsPoint>()
656  << QgsPoint( point.x(), mYConstraint->value() )
657  << QgsPoint( point.x() + 1, mYConstraint->value() );
658  res &= lineCircleIntersection( previousPt, mDistanceConstraint->value(), horizontalSegment, point );
659  }
660  }
661  else
662  {
663  const double dist = sqrt( point.sqrDist( previousPt ) );
664  if ( dist == 0 )
665  {
666  // handle case where mouse is over origin and distance constraint is enabled
667  // take arbitrary horizontal line
668  point.set( previousPt.x() + mDistanceConstraint->value(), previousPt.y() );
669  }
670  else
671  {
672  const double vP = mDistanceConstraint->value() / dist;
673  point.set( previousPt.x() + ( point.x() - previousPt.x() ) * vP,
674  previousPt.y() + ( point.y() - previousPt.y() ) * vP );
675  }
676 
677  if ( !mSnappedSegment.isEmpty() && !mAngleConstraint->isLocked() )
678  {
679  // we will magnietize to the intersection of that segment and the lockedDistance !
680  res &= lineCircleIntersection( previousPt, mDistanceConstraint->value(), snappedSegment(), point );
681  }
682  }
683  }
684 
685  // *****************************
686  // ---- caluclate CAD values
687  QgsDebugMsg( QString( "point: %1 %2" ).arg( point.x() ).arg( point.y() ) );
688  QgsDebugMsg( QString( "previous point: %1 %2" ).arg( previousPt.x() ).arg( previousPt.y() ) );
689  QgsDebugMsg( QString( "penultimate point: %1 %2" ).arg( penultimatePt.x() ).arg( penultimatePt.y() ) );
690  //QgsDebugMsg( QString( "dx: %1 dy: %2" ).arg( point.x() - previousPt.x() ).arg( point.y() - previousPt.y() ) );
691  //QgsDebugMsg( QString( "ddx: %1 ddy: %2" ).arg( previousPt.x() - penultimatePt.x() ).arg( previousPt.y() - penultimatePt.y() ) );
692 
693  // set the point coordinates in the map event
694  e->setMapPoint( point );
695 
696  // update the point list
697  updateCurrentPoint( point );
698 
699  // *****************************
700  // ---- update the GUI with the values
701  // --- angle
702  if ( !mAngleConstraint->isLocked() && previousPointExist )
703  {
704  double angle = 0.0;
705  if ( penulPointExist && mAngleConstraint->relative() )
706  {
707  // previous angle
708  angle = qAtan2( previousPt.y() - penultimatePt.y(),
709  previousPt.x() - penultimatePt.x() );
710  }
711  angle = ( qAtan2( point.y() - previousPt.y(),
712  point.x() - previousPt.x()
713  ) - angle ) * 180 / M_PI;
714  // modulus
715  angle = fmod( angle, 360.0 );
716  mAngleConstraint->setValue( angle );
717  }
718  // --- distance
719  if ( !mDistanceConstraint->isLocked() && previousPointExist )
720  {
721  mDistanceConstraint->setValue( sqrt( previousPt.sqrDist( point ) ) );
722  }
723  // --- X
724  if ( !mXConstraint->isLocked() )
725  {
726  if ( previousPointExist && mXConstraint->relative() )
727  {
728  mXConstraint->setValue( point.x() - previousPt.x() );
729  }
730  else
731  {
732  mXConstraint->setValue( point.x() );
733  }
734  }
735  // --- Y
736  if ( !mYConstraint->isLocked() )
737  {
738  if ( previousPointExist && mYConstraint->relative() )
739  {
740  mYConstraint->setValue( point.y() - previousPt.y() );
741  }
742  else
743  {
744  mYConstraint->setValue( point.y() );
745  }
746  }
747 
748  return res;
749 }
750 
751 
752 bool QgsAdvancedDigitizingDockWidget::alignToSegment( QgsMapMouseEvent* e, CadConstraint::LockMode lockMode )
753 {
754  if ( mAdditionalConstraint == NoConstraint )
755  {
756  return false;
757  }
758 
759  bool previousPointExist, penulPointExist, mSnappedSegmentExist;
760  QgsPoint previousPt = previousPoint( &previousPointExist );
761  QgsPoint penultimatePt = penultimatePoint( &penulPointExist );
762  QList<QgsPoint> mSnappedSegment = e->snapSegment( mSnappingMode, &mSnappedSegmentExist, true );
763 
764  if ( !previousPointExist || !mSnappedSegmentExist )
765  {
766  return false;
767  }
768 
769  double angle = qAtan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
770 
771  if ( mAngleConstraint->relative() && penulPointExist )
772  {
773  angle -= qAtan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
774  }
775 
776  if ( mAdditionalConstraint == Perpendicular )
777  {
778  angle += M_PI_2;
779  }
780 
781  angle *= 180 / M_PI;
782 
783  mAngleConstraint->setValue( angle );
784  mAngleConstraint->setLockMode( lockMode );
785  if ( lockMode == CadConstraint::HardLock )
786  {
787  mAdditionalConstraint = NoConstraint;
788  }
789 
790  return true;
791 }
792 
794 {
795  applyConstraints( e );
796  return mCadEnabled && mConstructionMode;
797 }
798 
800 {
801  if ( !mCadEnabled )
802  return false;
803 
804  emit popWarning();
805 
806  if ( e->button() == Qt::RightButton )
807  {
808  clearPoints();
809  releaseLocks();
810  return false;
811  }
812 
813  applyConstraints( e );
814 
815  if ( alignToSegment( e ) )
816  {
817  // launch a fake move event so rubber bands of map tools will be adapted with new constraints
818  // emit pointChanged( e );
819 
820  // Parallel or perpendicular mode and snapped to segment
821  // this has emitted the lockAngle signal
822  return true;
823  }
824 
825  addPoint( e->mapPoint() );
826 
827  releaseLocks();
828 
829  if ( e->button() == Qt::LeftButton )
830  {
831  // stop digitizing if not intermediate point and if line or polygon
832  if ( !mConstructionMode && !captureSegment )
833  {
834  clearPoints();
835  }
836  }
837  return mConstructionMode;
838 }
839 
841 {
842  if ( !mCadEnabled )
843  return false;
844 
845  if ( !applyConstraints( e ) )
846  {
847  emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
848  }
849  else
850  {
851  popWarning();
852  }
853 
854  // perpendicular/parallel constraint
855  // do a soft lock when snapping to a segment
856  alignToSegment( e, CadConstraint::SoftLock );
857  mCadPaintItem->update();
858 
859  return false;
860 }
861 
863 {
864  // event on map tool
865 
866  if ( !mCadEnabled )
867  return false;
868 
869  switch ( e->key() )
870  {
871  case Qt::Key_Backspace:
872  case Qt::Key_Delete:
873  {
874  removePreviousPoint();
875  releaseLocks();
876  break;
877  }
878  case Qt::Key_Escape:
879  {
880  releaseLocks();
881  break;
882  }
883  default:
884  {
885  keyPressEvent( e );
886  break;
887  }
888  }
889  // for map tools, continues with key press in any case
890  return false;
891 }
892 
894 {
895  clearPoints();
896  releaseLocks();
897 }
898 
900 {
901  // event on dock (this)
902 
903  if ( !mCadEnabled )
904  return;
905 
906  switch ( e->key() )
907  {
908  case Qt::Key_Backspace:
909  case Qt::Key_Delete:
910  {
911  removePreviousPoint();
912  releaseLocks();
913  break;
914  }
915  case Qt::Key_Escape:
916  {
917  releaseLocks();
918  break;
919  }
920  default:
921  {
922  filterKeyPress( e );
923  break;
924  }
925  }
926 }
927 
928 bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject* obj, QEvent* event )
929 {
930  // event for line edits
931  Q_UNUSED( obj );
932  if ( event->type() != QEvent::KeyPress )
933  {
934  return false;
935  }
936  QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>( event );
937  if ( !keyEvent )
938  {
939  return false;
940  }
941  return filterKeyPress( keyEvent ) ;
942 }
943 
944 bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent* e )
945 {
946  switch ( e->key() )
947  {
948  case Qt::Key_X:
949  {
950  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
951  {
952  mXConstraint->toggleLocked();
953  emit pointChanged( mCadPointList.value( 0 ) );
954  }
955  else if ( e->modifiers() == Qt::ShiftModifier )
956  {
957  if ( mCapacities.testFlag( RelativeCoordinates ) )
958  {
959  mXConstraint->toggleRelative();
960  emit pointChanged( mCadPointList.value( 0 ) );
961  }
962  }
963  else
964  {
965  mXLineEdit->setFocus();
966  mXLineEdit->selectAll();
967  }
968  break;
969  }
970  case Qt::Key_Y:
971  {
972  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
973  {
974  mYConstraint->toggleLocked();
975  emit pointChanged( mCadPointList.value( 0 ) );
976  }
977  else if ( e->modifiers() == Qt::ShiftModifier )
978  {
979  if ( mCapacities.testFlag( RelativeCoordinates ) )
980  {
981  mYConstraint->toggleRelative();
982  emit pointChanged( mCadPointList.value( 0 ) );
983  }
984  }
985  else
986  {
987  mYLineEdit->setFocus();
988  mYLineEdit->selectAll();
989  }
990  break;
991  }
992  case Qt::Key_A:
993  {
994  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
995  {
996  if ( mCapacities.testFlag( AbsoluteAngle ) )
997  {
998  mAngleConstraint->toggleLocked();
999  emit pointChanged( mCadPointList.value( 0 ) );
1000  }
1001  }
1002  else if ( e->modifiers() == Qt::ShiftModifier )
1003  {
1004  if ( mCapacities.testFlag( RelativeAngle ) )
1005  {
1006  mAngleConstraint->toggleRelative();
1007  emit pointChanged( mCadPointList.value( 0 ) );
1008  }
1009  }
1010  else
1011  {
1012  mAngleLineEdit->setFocus();
1013  mAngleLineEdit->selectAll();
1014  }
1015  break;
1016  }
1017  case Qt::Key_D:
1018  {
1019  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
1020  {
1021  if ( mCapacities.testFlag( RelativeCoordinates ) )
1022  {
1023  mDistanceConstraint->toggleLocked();
1024  emit pointChanged( mCadPointList.value( 0 ) );
1025  }
1026  }
1027  else
1028  {
1029  mDistanceLineEdit->setFocus();
1030  mDistanceLineEdit->selectAll();
1031  }
1032  break;
1033  }
1034  case Qt::Key_C:
1035  {
1036  setConstructionMode( !mConstructionMode );
1037  break;
1038  }
1039  case Qt::Key_P:
1040  {
1041  bool parallel = mParallelButton->isChecked();
1042  bool perpendicular = mPerpendicularButton->isChecked();
1043 
1044  if ( !parallel && !perpendicular )
1045  {
1046  lockAdditionalConstraint( Perpendicular );
1047  }
1048  else if ( perpendicular )
1049  {
1050  lockAdditionalConstraint( Parallel );
1051  }
1052  else
1053  {
1054  lockAdditionalConstraint( NoConstraint );
1055  }
1056  break;
1057  }
1058  default:
1059  {
1060  return false; // continues
1061  }
1062  }
1063  return true; // stop the event
1064 }
1065 
1067 {
1068  if ( mMapCanvas->mapSettings().destinationCrs().geographicFlag() )
1069  {
1070  mErrorLabel->setText( tr( "CAD tools can not be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1071  mErrorLabel->show();
1072  mEnableAction->setEnabled( false );
1073  setCadEnabled( false );
1074  }
1075  else
1076  {
1077  mEnableAction->setEnabled( true );
1078  mErrorLabel->hide();
1079  mCadWidget->show();
1080  setMaximumHeight( 220 );
1081 
1082  mCurrentMapToolSupportsCad = true;
1083 
1084  if ( mSessionActive && !isVisible() )
1085  {
1086  show();
1087  }
1088  setCadEnabled( mSessionActive );
1089  }
1090 }
1091 
1093 {
1094  mEnableAction->setEnabled( false );
1095  mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
1096  mErrorLabel->show();
1097  mCadWidget->hide();
1098  setMaximumHeight( 80 );
1099 
1100  mCurrentMapToolSupportsCad = false;
1101 
1102  setCadEnabled( false );
1103 }
1104 
1105 void QgsAdvancedDigitizingDockWidget::addPoint( const QgsPoint& point )
1106 {
1107  if ( !pointsCount() )
1108  {
1109  mCadPointList << point;
1110  }
1111  else
1112  {
1113  mCadPointList.insert( 0, point );
1114  }
1115 
1116  updateCapacity();
1117 }
1118 
1119 void QgsAdvancedDigitizingDockWidget::removePreviousPoint()
1120 {
1121  if ( !pointsCount() )
1122  return;
1123 
1124  int i = pointsCount() > 1 ? 1 : 0;
1125  mCadPointList.removeAt( i );
1126  updateCapacity();
1127 }
1128 
1129 void QgsAdvancedDigitizingDockWidget::clearPoints()
1130 {
1131  mCadPointList.clear();
1132  mSnappedSegment.clear();
1133  mSnappedToVertex = false;
1134 
1135  updateCapacity();
1136 }
1137 
1138 void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPoint& point )
1139 {
1140  if ( !pointsCount() )
1141  {
1142  mCadPointList << point;
1143  updateCapacity();
1144  }
1145  else
1146  {
1147  mCadPointList[0] = point ;
1148  }
1149 }
1150 
1151 
1153 {
1154  mLockMode = mode;
1155  mLockerButton->setChecked( mode == HardLock );
1156 
1157  if ( mode == NoLock )
1158  {
1159  mLineEdit->clear();
1160  }
1161 }
1162 
1164 {
1165  mRelative = relative;
1166  if ( mRelativeButton )
1167  {
1168  mRelativeButton->setChecked( relative );
1169  }
1170 }
1171 
1173 {
1174  mValue = value;
1175  mLineEdit->setText( QString::number( value, 'f' ) );
1176 }
1177 
1179 {
1180  setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1181 }
1182 
1184 {
1185  setRelative( mRelative ? false : true );
1186 }
1187 
1189 {
1190  if ( exist )
1191  *exist = pointsCount() > 0;
1192  if ( pointsCount() > 0 )
1193  return mCadPointList.value( 0 );
1194  else
1195  return QgsPoint();
1196 }
1197 
1199 {
1200  if ( exist )
1201  *exist = pointsCount() > 1;
1202  if ( pointsCount() > 1 )
1203  return mCadPointList.value( 1 );
1204  else
1205  return QgsPoint();
1206 }
1207 
1209 {
1210  if ( exist )
1211  *exist = pointsCount() > 2;
1212  if ( pointsCount() > 2 )
1213  return mCadPointList.value( 2 );
1214  else
1215  return QgsPoint();
1216 }
void setText(const QString &text)
this corresponds to distance and relative coordinates
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
void clear()
bool isLocked() const
Is any kind of lock mode enabled.
The CadConstraint is an abstract class for all basic constraints (angle/distance/x/y).
Qt::KeyboardModifiers modifiers() const
QgsPoint penultimatePoint(bool *exists=nullptr) const
The penultimate point.
Type type() const
void setupUi(QWidget *widget)
static const double SoftConstraintToleranceDegrees
QgsPoint mapPoint() const
mapPoint returns the point in coordinates
LockMode lockMode() const
The current lock mode of this constraint.
Q_DECL_DEPRECATED QVariant evaluate(const QgsFeature *f)
Evaluate the feature and return the result.
snap to all rendered layers (tolerance and type from defaultSettings())
bool canvasReleaseEvent(QgsMapMouseEvent *e, bool captureSegment)
Will react on a canvas release event.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QObject * sender() const
bool relative() const
Is the constraint in relative mode.
void setChecked(bool)
bool acceptMatch(const QgsPointLocator::Match &m) override
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
void setIcon(const QIcon &icon)
const T & at(int i) const
void addAction(QAction *action)
void removeAt(int i)
QgsPoint currentPoint(bool *exists=nullptr) const
The last point.
int y() const
bool isVisible() const
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QAction * addAction(QAction *action)
const_iterator constFind(const Key &key) const
virtual bool applyConstraints(QgsMapMouseEvent *e)
apply the CAD constraints.
void setRelative(bool relative)
Set if the constraint should be treated relative.
Interface that allows rejection of some matches in intersection queries (e.g.
void clear()
Clear any cached previous clicks and helper lines.
double toDouble(bool *ok) const
double sqrDist(double x, double y) const
Returns the squared distance between this point and x,y.
Definition: qgspoint.cpp:345
void keyPressEvent(QKeyEvent *e) override
key press event on the dock
QString tr(const char *sourceText, const char *disambiguation, int n)
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:105
void update(const QRectF &rect)
double x() const
Get the x value of the point.
Definition: qgspoint.h:128
#define M_PI_2
Definition: util.cpp:43
T value(int i) const
bool canvasKeyPressEventFilter(QKeyEvent *e)
Filter key events to e.g.
void pointChanged(const QgsPoint &point)
Sometimes a constraint may change the current point out of a mouse event.
void setValue(const QString &key, const QVariant &value)
QgsPoint previousPoint(bool *exists=nullptr) const
The previous point.
int pointsCount() const
The number of points in the CAD point helper list.
QString number(int n, int base)
int count(const T &value) const
snap according to the configuration set in the snapping settings
QString fromUtf8(const char *str, int size)
void installEventFilter(QObject *filterObj)
int x() const
QLineEdit * lineEdit() const
The line edit that manages the value of the constraint.
Qt::MouseButton button() const
void hideEvent(QHideEvent *) override
Disables the CAD tools when hiding the dock.
void setValue(double value)
Set the value of the constraint.
const QgsCoordinateReferenceSystem & destinationCrs() const
returns CRS of destination coordinate reference system
bool isEmpty() const
bool isEmpty() const
const_iterator constEnd() const
#define M_PI
double mapUnitsPerPixel() const
Return the distance in geographical coordinates that equals to one pixel in the map.
QPoint pos() const
QgsAdvancedDigitizingDockWidget(QgsMapCanvas *canvas, QWidget *parent=nullptr)
Create an advanced digitizing dock widget.
QAction * addSeparator()
iterator end()
void enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
static bool lineCircleIntersection(const QgsPoint &center, const double radius, const QList< QgsPoint > &segment, QgsPoint &intersection)
performs the intersection of a circle and a line
void set(double x, double y)
Sets the x and y value of the point.
Definition: qgspoint.h:119
A class to represent a point.
Definition: qgspoint.h:65
iterator begin()
double ANALYSIS_EXPORT angle(Point3D *p1, Point3D *p2, Point3D *p3, Point3D *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
int key() const
void popWarning()
Remove any previously emitted warnings (if any)
void setX(double x)
Sets the x value of the point.
Definition: qgspoint.h:105
void setMapPoint(const QgsPoint &point)
Set the (snapped) point this event points to in map coordinates.
bool canvasMoveEvent(QgsMapMouseEvent *e)
Will react on a canvas move event.
void setY(double y)
Sets the y value of the point.
Definition: qgspoint.h:113
void setCheckable(bool)
const Key key(const T &value) const
void setMaximumHeight(int maxh)
void insert(int i, const T &value)
QPoint mapFromGlobal(const QPoint &pos) const
void pushWarning(const QString &message)
Push a warning.
bool canvasPressEvent(QgsMapMouseEvent *e)
Will react on a canvas press event.
virtual bool event(QEvent *event)
double y() const
Get the y value of the point.
Definition: qgspoint.h:136
QList< QgsPoint > snapSegment(SnappingMode snappingMode, bool *snapped=nullptr, bool allLayers=false) const
Returns the first snapped segment.
double toDouble(bool *ok) const
iterator insert(const Key &key, const T &value)
void show()
static const double SoftConstraintTolerancePixel
const QList< QgsPoint > & snappedSegment() const
Snapped to a segment.
QgsPoint snapPoint(SnappingMode snappingMode)
snapPoint will snap the points using the map canvas snapping utils configuration
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool geographicFlag() const
Returns whether the CRS is a geographic CRS.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
void setEnabled(bool)
AdditionalConstraint
Additional constraints which can be enabled.
double value() const
The value of the constraint.
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (...
const T value(const Key &key) const