QGIS API Documentation  3.0.2-Girona (307d082)
qgsadvanceddigitizingdockwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsadvanceddigitizingdockwidget.cpp - dock for CAD tools
3  ----------------------
4  begin : October 2014
5  copyright : (C) Denis Rouzaud
6  email : denis.rouzaud@gmail.com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include <QMenu>
17 
18 #include <cmath>
19 
22 #include "qgsapplication.h"
23 #include "qgscadutils.h"
24 #include "qgsexpression.h"
25 #include "qgslogger.h"
26 #include "qgsmapcanvas.h"
27 #include "qgsmaptoolcapture.h"
29 #include "qgsmessagebaritem.h"
30 #include "qgspointxy.h"
31 #include "qgslinestring.h"
32 #include "qgsfocuswatcher.h"
33 #include "qgssettings.h"
34 #include "qgssnappingutils.h"
35 #include "qgsproject.h"
36 
37 
39  : QgsDockWidget( parent )
40  , mMapCanvas( canvas )
41  , mCurrentMapToolSupportsCad( false )
42  , mCadEnabled( false )
43  , mConstructionMode( false )
44  , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 90 ).toInt() )
45  , mSnappedToVertex( false )
46  , mSessionActive( false )
47  , mErrorMessage( nullptr )
48 {
49  setupUi( this );
50 
51  mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this );
52 
53  mAngleConstraint.reset( new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton, mRepeatingLockAngleButton ) );
54  mDistanceConstraint.reset( new CadConstraint( mDistanceLineEdit, mLockDistanceButton, nullptr, mRepeatingLockDistanceButton ) );
55  mXConstraint.reset( new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton, mRepeatingLockXButton ) );
56  mYConstraint.reset( new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton, mRepeatingLockYButton ) );
57  mAdditionalConstraint = NoConstraint;
58 
59  mMapCanvas->installEventFilter( this );
60  mAngleLineEdit->installEventFilter( this );
61  mDistanceLineEdit->installEventFilter( this );
62  mXLineEdit->installEventFilter( this );
63  mYLineEdit->installEventFilter( this );
64 
65  // this action is also used in the advanced digitizing tool bar
66  mEnableAction = new QAction( this );
67  mEnableAction->setText( tr( "Enable advanced digitizing tools" ) );
68  mEnableAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/cadtools/cad.svg" ) ) );
69  mEnableAction->setCheckable( true );
70  mEnabledButton->addAction( mEnableAction );
71  mEnabledButton->setDefaultAction( mEnableAction );
72 
73  // Connect the UI to the event filter to update constraints
74  connect( mEnableAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::activateCad );
75  connect( mConstructionModeButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode );
76  connect( mParallelButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::additionalConstraintClicked );
77  connect( mPerpendicularButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::additionalConstraintClicked );
78  connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
79  connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
80  connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
81  connect( mLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
82  connect( mRelativeAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
83  connect( mRelativeXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
84  connect( mRelativeYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
85  connect( mRepeatingLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
86  connect( mRepeatingLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
87  connect( mRepeatingLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
88  connect( mRepeatingLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
89  connect( mAngleLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
90  connect( mDistanceLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
91  connect( mXLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
92  connect( mYLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
93  connect( mAngleLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
94  connect( mDistanceLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
95  connect( mXLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
96  connect( mYLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
97  //also watch for focus out events on these widgets
98  QgsFocusWatcher *angleWatcher = new QgsFocusWatcher( mAngleLineEdit );
99  connect( angleWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
100  QgsFocusWatcher *distanceWatcher = new QgsFocusWatcher( mDistanceLineEdit );
101  connect( distanceWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
102  QgsFocusWatcher *xWatcher = new QgsFocusWatcher( mXLineEdit );
103  connect( xWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
104  QgsFocusWatcher *yWatcher = new QgsFocusWatcher( mYLineEdit );
105  connect( yWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
106 
107  // config menu
108  QMenu *menu = new QMenu( this );
109  // common angles
110  QActionGroup *angleButtonGroup = new QActionGroup( menu ); // actions are exclusive for common angles
111  mCommonAngleActions = QMap<QAction *, int>();
112  QList< QPair< int, QString > > commonAngles;
113  commonAngles << QPair<int, QString>( 0, tr( "Do not snap to common angles" ) );
114  commonAngles << QPair<int, QString>( 30, tr( "Snap to 30° angles" ) );
115  commonAngles << QPair<int, QString>( 45, tr( "Snap to 45° angles" ) );
116  commonAngles << QPair<int, QString>( 90, tr( "Snap to 90° angles" ) );
117  for ( QList< QPair< int, QString > >::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it )
118  {
119  QAction *action = new QAction( it->second, menu );
120  action->setCheckable( true );
121  action->setChecked( it->first == mCommonAngleConstraint );
122  menu->addAction( action );
123  angleButtonGroup->addAction( action );
124  mCommonAngleActions.insert( action, it->first );
125  }
126 
127  mSettingsButton->setMenu( menu );
128  connect( mSettingsButton, SIGNAL( triggered( QAction * ) ), this, SLOT( settingsButtonTriggered( QAction * ) ) );
129 
130  // set tooltips
131  mConstructionModeButton->setToolTip( "<b>" + tr( "Construction mode" ) + "</b><br>(" + tr( "press c to toggle on/off" ) + ")" );
132  mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
133  mLockDistanceButton->setToolTip( "<b>" + tr( "Lock distance" ) + "</b><br>(" + tr( "press Ctrl + d for quick access" ) + ")" );
134  mRepeatingLockDistanceButton->setToolTip( "<b>" + tr( "Continuously lock distance" ) + "</b>" );
135 
136  mRelativeAngleButton->setToolTip( "<b>" + tr( "Toggles relative angle to previous segment" ) + "</b><br>(" + tr( "press Shift + a for quick access" ) + ")" );
137  mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
138  mLockAngleButton->setToolTip( "<b>" + tr( "Lock angle" ) + "</b><br>(" + tr( "press Ctrl + a for quick access" ) + ")" );
139  mRepeatingLockAngleButton->setToolTip( "<b>" + tr( "Continuously lock angle" ) + "</b>" );
140 
141  mRelativeXButton->setToolTip( "<b>" + tr( "Toggles relative x to previous node" ) + "</b><br>(" + tr( "press Shift + x for quick access" ) + ")" );
142  mXLineEdit->setToolTip( "<b>" + tr( "X coordinate" ) + "</b><br>(" + tr( "press x for quick access" ) + ")" );
143  mLockXButton->setToolTip( "<b>" + tr( "Lock x coordinate" ) + "</b><br>(" + tr( "press Ctrl + x for quick access" ) + ")" );
144  mRepeatingLockXButton->setToolTip( "<b>" + tr( "Continuously lock x coordinate" ) + "</b>" );
145 
146  mRelativeYButton->setToolTip( "<b>" + tr( "Toggles relative y to previous node" ) + "</b><br>(" + tr( "press Shift + y for quick access" ) + ")" );
147  mYLineEdit->setToolTip( "<b>" + tr( "Y coordinate" ) + "</b><br>(" + tr( "press y for quick access" ) + ")" );
148  mLockYButton->setToolTip( "<b>" + tr( "Lock y coordinate" ) + "</b><br>(" + tr( "press Ctrl + y for quick access" ) + ")" );
149  mRepeatingLockYButton->setToolTip( "<b>" + tr( "Continuously lock y coordinate" ) + "</b>" );
150 
151 
152  updateCapacity( true );
153  connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, [ = ] { updateCapacity( true ); } );
154 
155  disable();
156 }
157 
159 {
160  // disable CAD but do not unset map event filter
161  // so it will be reactivated whenever the map tool is show again
162  setCadEnabled( false );
163 }
164 
165 void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
166 {
167  mCadEnabled = enabled;
168  mEnableAction->setChecked( enabled );
169  mCadButtons->setEnabled( enabled );
170  mInputWidgets->setEnabled( enabled );
171 
172  clear();
173  setConstructionMode( false );
174 }
175 
176 void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
177 {
178  enabled &= mCurrentMapToolSupportsCad;
179 
180  mSessionActive = enabled;
181 
182  if ( enabled && !isVisible() )
183  {
184  show();
185  }
186 
187  setCadEnabled( enabled );
188 }
189 
190 void QgsAdvancedDigitizingDockWidget::additionalConstraintClicked( bool activated )
191 {
192  if ( !activated )
193  {
194  lockAdditionalConstraint( NoConstraint );
195  }
196  if ( sender() == mParallelButton )
197  {
198  lockAdditionalConstraint( Parallel );
199  }
200  else if ( sender() == mPerpendicularButton )
201  {
202  lockAdditionalConstraint( Perpendicular );
203  }
204 }
205 
206 void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
207 {
208  if ( sender() == mRelativeAngleButton )
209  {
210  mAngleConstraint->setRelative( activate );
211  }
212  else if ( sender() == mRelativeXButton )
213  {
214  mXConstraint->setRelative( activate );
215  }
216  else if ( sender() == mRelativeYButton )
217  {
218  mYConstraint->setRelative( activate );
219  }
220 }
221 
222 void QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock( bool activate )
223 {
224  if ( sender() == mRepeatingLockDistanceButton )
225  {
226  mDistanceConstraint->setRepeatingLock( activate );
227  }
228  else if ( sender() == mRepeatingLockAngleButton )
229  {
230  mAngleConstraint->setRepeatingLock( activate );
231  }
232  else if ( sender() == mRepeatingLockXButton )
233  {
234  mXConstraint->setRepeatingLock( activate );
235  }
236  else if ( sender() == mRepeatingLockYButton )
237  {
238  mYConstraint->setRepeatingLock( activate );
239  }
240 }
241 
242 void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
243 {
244  mConstructionMode = enabled;
245  mConstructionModeButton->setChecked( enabled );
246 }
247 
248 void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction *action )
249 {
250  // common angles
251  QMap<QAction *, int>::const_iterator ica = mCommonAngleActions.constFind( action );
252  if ( ica != mCommonAngleActions.constEnd() )
253  {
254  ica.key()->setChecked( true );
255  mCommonAngleConstraint = ica.value();
256  QgsSettings().setValue( QStringLiteral( "/Cad/CommonAngle" ), ica.value() );
257  return;
258  }
259 }
260 
261 void QgsAdvancedDigitizingDockWidget::releaseLocks( bool releaseRepeatingLocks )
262 {
263  // release all locks except construction mode
264 
265  lockAdditionalConstraint( NoConstraint );
266 
267  if ( releaseRepeatingLocks || !mAngleConstraint->isRepeatingLock() )
268  mAngleConstraint->setLockMode( CadConstraint::NoLock );
269  if ( releaseRepeatingLocks || !mDistanceConstraint->isRepeatingLock() )
270  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
271  if ( releaseRepeatingLocks || !mXConstraint->isRepeatingLock() )
272  mXConstraint->setLockMode( CadConstraint::NoLock );
273  if ( releaseRepeatingLocks || !mYConstraint->isRepeatingLock() )
274  mYConstraint->setLockMode( CadConstraint::NoLock );
275 }
276 
277 #if 0
278 void QgsAdvancedDigitizingDockWidget::emit pointChanged()
279 {
280  // run a fake map mouse event to update the paint item
281  QPoint globalPos = mMapCanvas->cursor().pos();
282  QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
283  QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
284  mCurrentMapTool->canvasMoveEvent( e );
285 }
286 #endif
287 
288 QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
289 {
290  CadConstraint *constraint = nullptr;
291  if ( obj == mAngleLineEdit || obj == mLockAngleButton )
292  {
293  constraint = mAngleConstraint.get();
294  }
295  else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
296  {
297  constraint = mDistanceConstraint.get();
298  }
299  else if ( obj == mXLineEdit || obj == mLockXButton )
300  {
301  constraint = mXConstraint.get();
302  }
303  else if ( obj == mYLineEdit || obj == mLockYButton )
304  {
305  constraint = mYConstraint.get();
306  }
307  return constraint;
308 }
309 
310 double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, bool &ok ) const
311 {
312  ok = false;
313  double value = inputValue.toDouble( &ok );
314  if ( ok )
315  {
316  return value;
317  }
318  else
319  {
320  // try to evaluate expression
321  QgsExpression expr( inputValue );
322  QVariant result = expr.evaluate();
323  if ( expr.hasEvalError() )
324  ok = false;
325  else
326  value = result.toDouble( &ok );
327  return value;
328  }
329 }
330 
331 void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
332 {
333  if ( !constraint || textValue.isEmpty() )
334  {
335  return;
336  }
337 
338  if ( constraint->lockMode() == CadConstraint::NoLock )
339  return;
340 
341  bool ok;
342  double value = parseUserInput( textValue, ok );
343  if ( !ok )
344  return;
345 
346  constraint->setValue( value, convertExpression );
347  // run a fake map mouse event to update the paint item
348  emit pointChanged( mCadPointList.value( 0 ) );
349 }
350 
351 void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
352 {
353  CadConstraint *constraint = objectToConstraint( sender() );
354  if ( !constraint )
355  {
356  return;
357  }
358 
359  if ( activate )
360  {
361  QString textValue = constraint->lineEdit()->text();
362  if ( !textValue.isEmpty() )
363  {
364  bool ok;
365  double value = parseUserInput( textValue, ok );
366  if ( ok )
367  {
368  constraint->setValue( value );
369  }
370  else
371  {
372  activate = false;
373  }
374  }
375  else
376  {
377  activate = false;
378  }
379  }
380  constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
381 
382  if ( activate )
383  {
384  // deactivate perpendicular/parallel if angle has been activated
385  if ( constraint == mAngleConstraint.get() )
386  {
387  lockAdditionalConstraint( NoConstraint );
388  }
389 
390  // run a fake map mouse event to update the paint item
391  emit pointChanged( mCadPointList.value( 0 ) );
392  }
393 }
394 
395 void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
396 {
397  CadConstraint *constraint = objectToConstraint( sender() );
398  if ( !constraint )
399  {
400  return;
401  }
402 
403  updateConstraintValue( constraint, textValue, false );
404 }
405 
406 void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
407 {
408  QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
409  if ( !lineEdit )
410  return;
411 
412  CadConstraint *constraint = objectToConstraint( lineEdit );
413  if ( !constraint )
414  {
415  return;
416  }
417 
418  updateConstraintValue( constraint, lineEdit->text(), true );
419 }
420 
421 void QgsAdvancedDigitizingDockWidget::lockAdditionalConstraint( AdditionalConstraint constraint )
422 {
423  mAdditionalConstraint = constraint;
424  mPerpendicularButton->setChecked( constraint == Perpendicular );
425  mParallelButton->setChecked( constraint == Parallel );
426 }
427 
428 void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
429 {
430  CadCapacities newCapacities = nullptr;
431  // first point is the mouse point (it doesn't count)
432  if ( mCadPointList.count() > 1 )
433  {
434  newCapacities |= AbsoluteAngle | RelativeCoordinates;
435  }
436  if ( mCadPointList.count() > 2 )
437  {
438  newCapacities |= RelativeAngle;
439  }
440  if ( !updateUIwithoutChange && newCapacities == mCapacities )
441  {
442  return;
443  }
444 
445  bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
446 
447  // update the UI according to new capacities
448  // still keep the old to compare
449 
450  bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
451  bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
452  bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
453 
454  mPerpendicularButton->setEnabled( absoluteAngle && snappingEnabled );
455  mParallelButton->setEnabled( absoluteAngle && snappingEnabled );
456 
457  //update tooltips on buttons
458  if ( !snappingEnabled )
459  {
460  mPerpendicularButton->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode" ) );
461  mParallelButton->setToolTip( tr( "Snapping must be enabled to utilize parallel mode" ) );
462  }
463  else
464  {
465  mPerpendicularButton->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
466  mParallelButton->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
467  }
468 
469 
470  if ( !absoluteAngle )
471  {
472  lockAdditionalConstraint( NoConstraint );
473  }
474 
475  // absolute angle = azimuth, relative = from previous line
476  mLockAngleButton->setEnabled( absoluteAngle );
477  mRelativeAngleButton->setEnabled( relativeAngle );
478  mAngleLineEdit->setEnabled( absoluteAngle );
479  if ( !absoluteAngle )
480  {
481  mAngleConstraint->setLockMode( CadConstraint::NoLock );
482  }
483  if ( !relativeAngle )
484  {
485  mAngleConstraint->setRelative( false );
486  }
487  else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
488  {
489  // set angle mode to relative if can do and wasn't available before
490  mAngleConstraint->setRelative( true );
491  }
492 
493  // distance is always relative
494  mLockDistanceButton->setEnabled( relativeCoordinates );
495  mDistanceLineEdit->setEnabled( relativeCoordinates );
496  if ( !relativeCoordinates )
497  {
498  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
499  }
500 
501  mRelativeXButton->setEnabled( relativeCoordinates );
502  mRelativeYButton->setEnabled( relativeCoordinates );
503 
504  // update capacities
505  mCapacities = newCapacities;
506 }
507 
508 
510 {
513  constr.relative = c->relative();
514  constr.value = c->value();
515  return constr;
516 }
517 
519 {
521  context.snappingUtils = mMapCanvas->snappingUtils();
522  context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
523  context.xConstraint = _constraint( mXConstraint.get() );
524  context.yConstraint = _constraint( mYConstraint.get() );
525  context.distanceConstraint = _constraint( mDistanceConstraint.get() );
526  context.angleConstraint = _constraint( mAngleConstraint.get() );
527  context.cadPointList = mCadPointList;
528 
529  context.commonAngleConstraint.locked = true;
531  context.commonAngleConstraint.value = mCommonAngleConstraint;
532 
534 
535  bool res = output.valid;
536  QgsPointXY point = output.finalMapPoint;
537  mSnappedSegment.clear();
538  if ( output.edgeMatch.hasEdge() )
539  {
540  QgsPointXY edgePt0, edgePt1;
541  output.edgeMatch.edgePoints( edgePt0, edgePt1 );
542  mSnappedSegment << edgePt0 << edgePt1;
543  }
544  if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
545  {
546  if ( output.softLockCommonAngle != -1 )
547  {
548  mAngleConstraint->setLockMode( CadConstraint::SoftLock );
549  mAngleConstraint->setValue( output.softLockCommonAngle );
550  }
551  else
552  {
553  mAngleConstraint->setLockMode( CadConstraint::NoLock );
554  }
555  }
556 
557  // set the point coordinates in the map event
558  e->setMapPoint( point );
559 
560  // update the point list
561  updateCurrentPoint( point );
562 
563  updateUnlockedConstraintValues( point );
564 
565  if ( res )
566  {
567  emit popWarning();
568  }
569  else
570  {
571  emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
572  }
573 
574  return res;
575 }
576 
577 
578 void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPointXY &point )
579 {
580  bool previousPointExist, penulPointExist;
581  QgsPointXY previousPt = previousPoint( &previousPointExist );
582  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
583 
584  // --- angle
585  if ( !mAngleConstraint->isLocked() && previousPointExist )
586  {
587  double angle = 0.0;
588  if ( penulPointExist && mAngleConstraint->relative() )
589  {
590  // previous angle
591  angle = std::atan2( previousPt.y() - penultimatePt.y(),
592  previousPt.x() - penultimatePt.x() );
593  }
594  angle = ( std::atan2( point.y() - previousPt.y(),
595  point.x() - previousPt.x()
596  ) - angle ) * 180 / M_PI;
597  // modulus
598  angle = std::fmod( angle, 360.0 );
599  mAngleConstraint->setValue( angle );
600  }
601  // --- distance
602  if ( !mDistanceConstraint->isLocked() && previousPointExist )
603  {
604  mDistanceConstraint->setValue( std::sqrt( previousPt.sqrDist( point ) ) );
605  }
606  // --- X
607  if ( !mXConstraint->isLocked() )
608  {
609  if ( previousPointExist && mXConstraint->relative() )
610  {
611  mXConstraint->setValue( point.x() - previousPt.x() );
612  }
613  else
614  {
615  mXConstraint->setValue( point.x() );
616  }
617  }
618  // --- Y
619  if ( !mYConstraint->isLocked() )
620  {
621  if ( previousPointExist && mYConstraint->relative() )
622  {
623  mYConstraint->setValue( point.y() - previousPt.y() );
624  }
625  else
626  {
627  mYConstraint->setValue( point.y() );
628  }
629  }
630 }
631 
632 
633 QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
634 {
635  QList<QgsPointXY> segment;
636  QgsPointXY pt1, pt2;
638 
639  QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
640 
641  QgsSnappingConfig canvasConfig = snappingUtils->config();
642  QgsSnappingConfig localConfig = snappingUtils->config();
643 
644  localConfig.setMode( QgsSnappingConfig::AllLayers );
645  localConfig.setType( QgsSnappingConfig::Segment );
646  snappingUtils->setConfig( localConfig );
647 
648  match = snappingUtils->snapToMap( originalMapPoint );
649 
650  snappingUtils->setConfig( canvasConfig );
651 
652  if ( match.isValid() && match.hasEdge() )
653  {
654  match.edgePoints( pt1, pt2 );
655  segment << pt1 << pt2;
656  }
657 
658  if ( snapped )
659  {
660  *snapped = segment.count() == 2;
661  }
662 
663  return segment;
664 }
665 
667 {
668  if ( mAdditionalConstraint == NoConstraint )
669  {
670  return false;
671  }
672 
673  bool previousPointExist, penulPointExist, snappedSegmentExist;
674  QgsPointXY previousPt = previousPoint( &previousPointExist );
675  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
676  mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
677 
678  if ( !previousPointExist || !snappedSegmentExist )
679  {
680  return false;
681  }
682 
683  double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
684 
685  if ( mAngleConstraint->relative() && penulPointExist )
686  {
687  angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
688  }
689 
690  if ( mAdditionalConstraint == Perpendicular )
691  {
692  angle += M_PI_2;
693  }
694 
695  angle *= 180 / M_PI;
696 
697  mAngleConstraint->setValue( angle );
698  mAngleConstraint->setLockMode( lockMode );
699  if ( lockMode == CadConstraint::HardLock )
700  {
701  mAdditionalConstraint = NoConstraint;
702  }
703 
704  return true;
705 }
706 
708 {
709  // event on map tool
710 
711  if ( !mCadEnabled )
712  return false;
713 
714  switch ( e->key() )
715  {
716  case Qt::Key_Backspace:
717  case Qt::Key_Delete:
718  {
719  removePreviousPoint();
720  releaseLocks( false );
721  break;
722  }
723  case Qt::Key_Escape:
724  {
725  releaseLocks();
726  break;
727  }
728  default:
729  {
730  keyPressEvent( e );
731  break;
732  }
733  }
734  // for map tools, continues with key press in any case
735  return false;
736 }
737 
739 {
740  clearPoints();
741  releaseLocks();
742 }
743 
745 {
746  // event on dock (this)
747 
748  if ( !mCadEnabled )
749  return;
750 
751  switch ( e->key() )
752  {
753  case Qt::Key_Backspace:
754  case Qt::Key_Delete:
755  {
756  removePreviousPoint();
757  releaseLocks( false );
758  break;
759  }
760  case Qt::Key_Escape:
761  {
762  releaseLocks();
763  break;
764  }
765  default:
766  {
767  filterKeyPress( e );
768  break;
769  }
770  }
771 }
772 
773 void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
774 {
775  clearPoints();
776  Q_FOREACH ( const QgsPointXY &pt, points )
777  {
778  addPoint( pt );
779  }
780 }
781 
782 bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
783 {
784  // event for line edits
785  Q_UNUSED( obj );
786  if ( event->type() != QEvent::KeyPress )
787  {
788  return false;
789  }
790  QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event );
791  if ( !keyEvent )
792  {
793  return false;
794  }
795  return filterKeyPress( keyEvent );
796 }
797 
798 bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
799 {
800  switch ( e->key() )
801  {
802  case Qt::Key_X:
803  {
804  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
805  {
806  mXConstraint->toggleLocked();
807  emit pointChanged( mCadPointList.value( 0 ) );
808  }
809  else if ( e->modifiers() == Qt::ShiftModifier )
810  {
811  if ( mCapacities.testFlag( RelativeCoordinates ) )
812  {
813  mXConstraint->toggleRelative();
814  emit pointChanged( mCadPointList.value( 0 ) );
815  }
816  }
817  else
818  {
819  mXLineEdit->setFocus();
820  mXLineEdit->selectAll();
821  }
822  break;
823  }
824  case Qt::Key_Y:
825  {
826  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
827  {
828  mYConstraint->toggleLocked();
829  emit pointChanged( mCadPointList.value( 0 ) );
830  }
831  else if ( e->modifiers() == Qt::ShiftModifier )
832  {
833  if ( mCapacities.testFlag( RelativeCoordinates ) )
834  {
835  mYConstraint->toggleRelative();
836  emit pointChanged( mCadPointList.value( 0 ) );
837  }
838  }
839  else
840  {
841  mYLineEdit->setFocus();
842  mYLineEdit->selectAll();
843  }
844  break;
845  }
846  case Qt::Key_A:
847  {
848  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
849  {
850  if ( mCapacities.testFlag( AbsoluteAngle ) )
851  {
852  mAngleConstraint->toggleLocked();
853  emit pointChanged( mCadPointList.value( 0 ) );
854  }
855  }
856  else if ( e->modifiers() == Qt::ShiftModifier )
857  {
858  if ( mCapacities.testFlag( RelativeAngle ) )
859  {
860  mAngleConstraint->toggleRelative();
861  emit pointChanged( mCadPointList.value( 0 ) );
862  }
863  }
864  else
865  {
866  mAngleLineEdit->setFocus();
867  mAngleLineEdit->selectAll();
868  }
869  break;
870  }
871  case Qt::Key_D:
872  {
873  if ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier )
874  {
875  if ( mCapacities.testFlag( RelativeCoordinates ) )
876  {
877  mDistanceConstraint->toggleLocked();
878  emit pointChanged( mCadPointList.value( 0 ) );
879  }
880  }
881  else
882  {
883  mDistanceLineEdit->setFocus();
884  mDistanceLineEdit->selectAll();
885  }
886  break;
887  }
888  case Qt::Key_C:
889  {
890  setConstructionMode( !mConstructionMode );
891  break;
892  }
893  case Qt::Key_P:
894  {
895  bool parallel = mParallelButton->isChecked();
896  bool perpendicular = mPerpendicularButton->isChecked();
897 
898  if ( !parallel && !perpendicular )
899  {
900  lockAdditionalConstraint( Perpendicular );
901  }
902  else if ( perpendicular )
903  {
904  lockAdditionalConstraint( Parallel );
905  }
906  else
907  {
908  lockAdditionalConstraint( NoConstraint );
909  }
910  break;
911  }
912  default:
913  {
914  return false; // continues
915  }
916  }
917  return true; // stop the event
918 }
919 
921 {
922  connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
923  if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
924  {
925  mErrorLabel->setText( tr( "CAD tools can not be used on geographic coordinates. Change the coordinates system in the project properties." ) );
926  mErrorLabel->show();
927  mEnableAction->setEnabled( false );
928  setCadEnabled( false );
929  }
930  else
931  {
932  mEnableAction->setEnabled( true );
933  mErrorLabel->hide();
934  mCadWidget->show();
935  setMaximumHeight( 220 );
936 
937  mCurrentMapToolSupportsCad = true;
938 
939  if ( mSessionActive && !isVisible() )
940  {
941  show();
942  }
943  setCadEnabled( mSessionActive );
944  }
945 }
946 
948 {
950 
951  mEnableAction->setEnabled( false );
952  mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
953  mErrorLabel->show();
954  mCadWidget->hide();
955  setMaximumHeight( 80 );
956 
957  mCurrentMapToolSupportsCad = false;
958 
959  setCadEnabled( false );
960 }
961 
963 {
964  mCadPaintItem->update();
965 }
966 
968 {
969  if ( !pointsCount() )
970  {
971  mCadPointList << point;
972  }
973  else
974  {
975  mCadPointList.insert( 0, point );
976  }
977 
978  updateCapacity();
979 }
980 
981 void QgsAdvancedDigitizingDockWidget::removePreviousPoint()
982 {
983  if ( !pointsCount() )
984  return;
985 
986  int i = pointsCount() > 1 ? 1 : 0;
987  mCadPointList.removeAt( i );
988  updateCapacity();
989 }
990 
992 {
993  mCadPointList.clear();
994  mSnappedSegment.clear();
995  mSnappedToVertex = false;
996 
997  updateCapacity();
998 }
999 
1000 void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPointXY &point )
1001 {
1002  if ( !pointsCount() )
1003  {
1004  mCadPointList << point;
1005  updateCapacity();
1006  }
1007  else
1008  {
1009  mCadPointList[0] = point;
1010  }
1011 }
1012 
1013 
1015 {
1016  mLockMode = mode;
1017  mLockerButton->setChecked( mode == HardLock );
1018  if ( mRepeatingLockButton )
1019  {
1020  if ( mode == HardLock )
1021  {
1022  mRepeatingLockButton->setEnabled( true );
1023  }
1024  else
1025  {
1026  mRepeatingLockButton->setChecked( false );
1027  mRepeatingLockButton->setEnabled( false );
1028  }
1029  }
1030 
1031  if ( mode == NoLock )
1032  {
1033  mLineEdit->clear();
1034  }
1035 }
1036 
1038 {
1039  mRepeatingLock = repeating;
1040  if ( mRepeatingLockButton )
1041  mRepeatingLockButton->setChecked( repeating );
1042 }
1043 
1045 {
1046  mRelative = relative;
1047  if ( mRelativeButton )
1048  {
1049  mRelativeButton->setChecked( relative );
1050  }
1051 }
1052 
1053 void QgsAdvancedDigitizingDockWidget::CadConstraint::setValue( double value, bool updateWidget )
1054 {
1055  mValue = value;
1056  if ( updateWidget )
1057  mLineEdit->setText( QString::number( value, 'f' ) );
1058 }
1059 
1061 {
1062  setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1063 }
1064 
1066 {
1067  setRelative( !mRelative );
1068 }
1069 
1071 {
1072  if ( exist )
1073  *exist = pointsCount() > 0;
1074  if ( pointsCount() > 0 )
1075  return mCadPointList.value( 0 );
1076  else
1077  return QgsPointXY();
1078 }
1079 
1081 {
1082  if ( exist )
1083  *exist = pointsCount() > 1;
1084  if ( pointsCount() > 1 )
1085  return mCadPointList.value( 1 );
1086  else
1087  return QgsPointXY();
1088 }
1089 
1091 {
1092  if ( exist )
1093  *exist = pointsCount() > 2;
1094  if ( pointsCount() > 2 )
1095  return mCadPointList.value( 2 );
1096  else
1097  return QgsPointXY();
1098 }
int pointsCount() const
The number of points in the CAD point helper list.
This corresponds to distance and relative coordinates.
The CadConstraint is an abstract class for all basic constraints (angle/distance/x/y).
QgsCadUtils::AlignMapPointConstraint yConstraint
Constraint for Y coordinate.
Definition: qgscadutils.h:64
void clearPoints()
Removes all points from the CAD point list.
void snappingConfigChanged(const QgsSnappingConfig &config)
emitted whenever the configuration for snapping has changed
void focusOut()
Emitted when parent object loses focus.
A event filter for watching for focus events on a parent object.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
QgsPointXY previousPoint(bool *exists=nullptr) const
The previous point.
bool enabled() const
return if snapping is enabled
Structure with details of one constraint.
Definition: qgscadutils.h:37
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
int softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition: qgscadutils.h:99
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
bool applyConstraints(QgsMapMouseEvent *e)
apply the CAD constraints.
QgsPointXY currentPoint(bool *exists=nullptr) const
The last point.
On all vector layers.
void setRelative(bool relative)
Set if the constraint should be treated relative.
void clear()
Clear any cached previous clicks and helper lines.
void setMapPoint(const QgsPointXY &point)
Set the (snapped) point this event points to in map coordinates.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:74
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
LockMode lockMode() const
The current lock mode of this constraint.
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
QgsPointLocator::Match edgeMatch
Snapped segment - only valid if actually used for something.
Definition: qgscadutils.h:96
void releaseLocks(bool releaseRepeatingLocks=true)
unlock all constraints
bool canvasKeyPressEventFilter(QKeyEvent *e)
Filter key events to e.g.
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.cpp:68
bool locked
Whether the constraint is active, i.e. should be considered.
Definition: qgscadutils.h:46
void updateCadPaintItem()
Updates canvas item that displays constraints on the ma.
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
void setRepeatingLock(bool repeating)
Sets whether a repeating lock is set for the constraint.
double value() const
The value of the constraint.
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
Definition: qgscadutils.h:66
void addPoint(const QgsPointXY &point)
Adds point to the CAD point list.
QgsPointXY penultimatePoint(bool *exists=nullptr) const
The penultimate point.
void hideEvent(QHideEvent *) override
Disables the CAD tools when hiding the dock.
QgsDockWidget subclass with more fine-grained control over how the widget is closed or opened...
Definition: qgsdockwidget.h:31
Structure defining all constraints for alignMapPoint() method.
Definition: qgscadutils.h:54
void edgePoints(QgsPointXY &pt1, QgsPointXY &pt2) const
Only for a valid edge match - obtain endpoints of the edge.
QgsAdvancedDigitizingDockWidget(QgsMapCanvas *canvas, QWidget *parent=nullptr)
Create an advanced digitizing dock widget.
QgsCadUtils::AlignMapPointConstraint commonAngleConstraint
Constraint for soft lock to a common angle.
Definition: qgscadutils.h:70
void destinationCrsChanged()
Emitted when map CRS has changed.
bool isGeographic() const
Returns whether the CRS is a geographic CRS (using lat/lon coordinates)
void enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
QgsPointXY finalMapPoint
map point aligned according to the constraints
Definition: qgscadutils.h:93
void setPoints(const QList< QgsPointXY > &points)
Configures list of current CAD points.
double value
Numeric value of the constraint (coordinate/distance in map units or angle in degrees) ...
Definition: qgscadutils.h:50
double x
Definition: qgspointxy.h:47
Structure returned from alignMapPoint() method.
Definition: qgscadutils.h:87
double mapUnitsPerPixel
Map units/pixel ratio from map canvas. Needed for.
Definition: qgscadutils.h:59
void pointChanged(const QgsPointXY &point)
Sometimes a constraint may change the current point out of a mouse event.
void popWarning()
Remove any previously emitted warnings (if any)
void setMode(SnappingMode mode)
define the mode of snapping
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QgsPointXY originalMapPoint() const
Returns the original, unmodified map point of the mouse cursor.
bool relative() const
Is the constraint in relative mode.
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
Definition: qgscadutils.h:62
QLineEdit * lineEdit() const
The line edit that manages the value of the constraint.
QgsSnappingConfig config
QgsCadUtils::AlignMapPointConstraint angleConstraint
Constraint for angle.
Definition: qgscadutils.h:68
bool valid
Whether the combination of constraints is actually valid.
Definition: qgscadutils.h:90
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:383
void setType(SnappingType type)
define the type of snapping
This class has all the configuration of snapping and can return answers to snapping queries...
void pushWarning(const QString &message)
Push a warning.
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be null.
Definition: qgscadutils.h:57
This is a container for configuration of the snapping of the project.
static QgsCadUtils::AlignMapPointOutput alignMapPoint(const QgsPointXY &originalMapPoint, const QgsCadUtils::AlignMapPointContext &ctx)
Applies X/Y/angle/distance constraints from the given context to a map point.
Definition: qgscadutils.cpp:37
void setValue(double value, bool updateWidget=true)
Set the value of the constraint.
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr)
Snap to map according to the current configuration. Optional filter allows discarding unwanted matche...
QgsSnappingUtils * snappingUtils() const
Return snapping utility class that is associated with map canvas.
bool relative
Whether the value is relative to previous value.
Definition: qgscadutils.h:48
bool alignToSegment(QgsMapMouseEvent *e, QgsAdvancedDigitizingDockWidget::CadConstraint::LockMode lockMode=QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock)
align to segment for additional constraint.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:91
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
AdditionalConstraint
Additional constraints which can be enabled.
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (...
QList< QgsPointXY > cadPointList
List of recent CAD points in map coordinates.
Definition: qgscadutils.h:77