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