QGIS API Documentation  2.14.0-Essen
Go to the documentation of this file.
1 /***************************************************************************
3  ----------------------
4  begin : October 2014
5  copyright : (C) Denis Rouzaud
6  email : denis.rouzaud@gmail.com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
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>
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 )
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
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 ) ;
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" ) );
121  mEnableAction->setCheckable( true );
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
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 );
159  mCommonAngleActions.insert( action, it->first );
160  }
161  // snapping on layers
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 );
176  mSnappingActions.insert( action, it->first );
177  }
178
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
190 }
191
193 {
195  mEnableAction->setChecked( enabled );
197  mInputWidgets->setEnabled( enabled );
198
199  clearPoints();
200  releaseLocks();
201  setConstructionMode( false );
202 }
203
205 {
207
208  mSessionActive = enabled;
209
210  if ( enabled && !isVisible() )
211  {
212  show();
213  }
214
216 }
217
219 {
220  if ( !activated )
221  {
223  }
224  if ( sender() == mParallelButton )
225  {
227  }
228  else if ( sender() == mPerpendicularButton )
229  {
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();
276  return;
277  }
278 }
279
281 {
282  // release all locks except construction mode
283
285
290 }
291
292 #if 0
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();
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  }
361
362  if ( activate )
363  {
364  // deactivate perpendicular/parallel if angle has been activated
365  if ( constraint == mAngleConstraint )
366  {
368  }
369
370  // run a fake map mouse event to update the paint item
371  emit pointChanged( mCadPointList.value( 0 ) );
372  }
373 }
374
376 {
378  mPerpendicularButton->setChecked( constraint == Perpendicular );
379  mParallelButton->setChecked( constraint == Parallel );
380 }
381
382 void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
383 {
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  {
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  {
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  {
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
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  {
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
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  {
788  }
789
790  return true;
791 }
792
794 {
795  applyConstraints( e );
797 }
798
800 {
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
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 {
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
858
859  return false;
860 }
861
863 {
864  // event on map tool
865
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
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  {
1047  }
1048  else if ( perpendicular )
1049  {
1051  }
1052  else
1053  {
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 );
1074  }
1075  else
1076  {
1077  mEnableAction->setEnabled( true );
1078  mErrorLabel->hide();
1080  setMaximumHeight( 220 );
1081
1083
1084  if ( mSessionActive && !isVisible() )
1085  {
1086  show();
1087  }
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();
1098  setMaximumHeight( 80 );
1099
1101
1103 }
1104
1106 {
1107  if ( !pointsCount() )
1108  {
1110  }
1111  else
1112  {
1114  }
1115
1116  updateCapacity();
1117 }
1118
1120 {
1121  if ( !pointsCount() )
1122  return;
1123
1124  int i = pointsCount() > 1 ? 1 : 0;
1126  updateCapacity();
1127 }
1128
1130 {
1132  mSnappedSegment.clear();
1133  mSnappedToVertex = false;
1134
1135  updateCapacity();
1136 }
1137
1138 void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPoint& point )
1139 {
1140  if ( !pointsCount() )
1141  {
1143  updateCapacity();
1144  }
1145  else
1146  {
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 )
1194  else
1195  return QgsPoint();
1196 }
1197
1199 {
1200  if ( exist )
1201  *exist = pointsCount() > 1;
1202  if ( pointsCount() > 1 )
1204  else
1205  return QgsPoint();
1206 }
1207
1209 {
1210  if ( exist )
1211  *exist = pointsCount() > 2;
1212  if ( pointsCount() > 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 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
const_iterator constFind(const Key &key) const
virtual bool applyConstraints(QgsMapMouseEvent *e)
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
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
Create an advanced digitizing dock widget.
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)