QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 <QMenu>
17 #include <QEvent>
18 #include <QCoreApplication>
19 
20 #include <cmath>
21 
25 #include "qgsapplication.h"
26 #include "qgscadutils.h"
27 #include "qgsexpression.h"
28 #include "qgslogger.h"
29 #include "qgsmapcanvas.h"
30 #include "qgsmaptoolcapture.h"
32 #include "qgsmessagebaritem.h"
33 #include "qgslinestring.h"
34 #include "qgsfocuswatcher.h"
35 #include "qgssettings.h"
36 #include "qgssnappingutils.h"
37 #include "qgsproject.h"
38 #include "qgsmapmouseevent.h"
39 #include "qgsmessagelog.h"
40 
41 
43  : QgsDockWidget( parent )
44  , mMapCanvas( canvas )
45  , mSnapIndicator( qgis::make_unique< QgsSnapIndicator>( canvas ) )
46  , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 0.0 ).toDouble() )
47 {
48  setupUi( this );
49 
50  mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this );
51 
52  mAngleConstraint.reset( new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton, mRepeatingLockAngleButton ) );
53  mDistanceConstraint.reset( new CadConstraint( mDistanceLineEdit, mLockDistanceButton, nullptr, mRepeatingLockDistanceButton ) );
54  mXConstraint.reset( new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton, mRepeatingLockXButton ) );
55  mYConstraint.reset( new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton, mRepeatingLockYButton ) );
56  mAdditionalConstraint = AdditionalConstraint::NoConstraint;
57 
58  mMapCanvas->installEventFilter( this );
59  mAngleLineEdit->installEventFilter( this );
60  mDistanceLineEdit->installEventFilter( this );
61  mXLineEdit->installEventFilter( this );
62  mYLineEdit->installEventFilter( this );
63 
64  // Connect the UI to the event filter to update constraints
65  connect( mEnableAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::activateCad );
66  connect( mConstructionModeAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode );
67  connect( mParallelAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::additionalConstraintClicked );
68  connect( mPerpendicularAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::additionalConstraintClicked );
69  connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
70  connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
71  connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
72  connect( mLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
73  connect( mRelativeAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
74  connect( mRelativeXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
75  connect( mRelativeYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
76  connect( mRepeatingLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
77  connect( mRepeatingLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
78  connect( mRepeatingLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
79  connect( mRepeatingLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
80  connect( mAngleLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
81  connect( mDistanceLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
82  connect( mXLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
83  connect( mYLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
84  connect( mAngleLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
85  connect( mDistanceLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
86  connect( mXLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
87  connect( mYLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
88  //also watch for focus out events on these widgets
89  QgsFocusWatcher *angleWatcher = new QgsFocusWatcher( mAngleLineEdit );
90  connect( angleWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
91  QgsFocusWatcher *distanceWatcher = new QgsFocusWatcher( mDistanceLineEdit );
92  connect( distanceWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
93  QgsFocusWatcher *xWatcher = new QgsFocusWatcher( mXLineEdit );
94  connect( xWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
95  QgsFocusWatcher *yWatcher = new QgsFocusWatcher( mYLineEdit );
96  connect( yWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
97 
98  // config menu
99  QMenu *menu = new QMenu( this );
100  // common angles
101  QActionGroup *angleButtonGroup = new QActionGroup( menu ); // actions are exclusive for common angles
102  mCommonAngleActions = QMap<QAction *, double>();
103  QList< QPair< double, QString > > commonAngles;
104  QString menuText;
105  QList<double> anglesDouble( { 0.0, 5.0, 10.0, 15.0, 18.0, 22.5, 30.0, 45.0, 90.0} );
106  for ( QList<double>::const_iterator it = anglesDouble.constBegin(); it != anglesDouble.constEnd(); ++it )
107  {
108  if ( *it == 0 )
109  menuText = tr( "Do Not Snap to Common Angles" );
110  else
111  menuText = QString( tr( "%1, %2, %3, %4°…" ) ).arg( *it, 0, 'f', 1 ).arg( *it * 2, 0, 'f', 1 ).arg( *it * 3, 0, 'f', 1 ).arg( *it * 4, 0, 'f', 1 );
112  commonAngles << QPair<double, QString>( *it, menuText );
113  }
114  for ( QList< QPair<double, QString > >::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it )
115  {
116  QAction *action = new QAction( it->second, menu );
117  action->setCheckable( true );
118  action->setChecked( it->first == mCommonAngleConstraint );
119  menu->addAction( action );
120  angleButtonGroup->addAction( action );
121  mCommonAngleActions.insert( action, it->first );
122  }
123 
124  qobject_cast< QToolButton *>( mToolbar->widgetForAction( mSettingsAction ) )->setPopupMode( QToolButton::InstantPopup );
125  mSettingsAction->setMenu( menu );
126  connect( menu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
127 
128  // set tooltips
129  mConstructionModeAction->setToolTip( "<b>" + tr( "Construction mode" ) + "</b><br>(" + tr( "press c to toggle on/off" ) + ")" );
130  mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
131  mLockDistanceButton->setToolTip( "<b>" + tr( "Lock distance" ) + "</b><br>(" + tr( "press Ctrl + d for quick access" ) + ")" );
132  mRepeatingLockDistanceButton->setToolTip( "<b>" + tr( "Continuously lock distance" ) + "</b>" );
133 
134  mRelativeAngleButton->setToolTip( "<b>" + tr( "Toggles relative angle to previous segment" ) + "</b><br>(" + tr( "press Shift + a for quick access" ) + ")" );
135  mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
136  mLockAngleButton->setToolTip( "<b>" + tr( "Lock angle" ) + "</b><br>(" + tr( "press Ctrl + a for quick access" ) + ")" );
137  mRepeatingLockAngleButton->setToolTip( "<b>" + tr( "Continuously lock angle" ) + "</b>" );
138 
139  mRelativeXButton->setToolTip( "<b>" + tr( "Toggles relative x to previous node" ) + "</b><br>(" + tr( "press Shift + x for quick access" ) + ")" );
140  mXLineEdit->setToolTip( "<b>" + tr( "X coordinate" ) + "</b><br>(" + tr( "press x for quick access" ) + ")" );
141  mLockXButton->setToolTip( "<b>" + tr( "Lock x coordinate" ) + "</b><br>(" + tr( "press Ctrl + x for quick access" ) + ")" );
142  mRepeatingLockXButton->setToolTip( "<b>" + tr( "Continuously lock x coordinate" ) + "</b>" );
143 
144  mRelativeYButton->setToolTip( "<b>" + tr( "Toggles relative y to previous node" ) + "</b><br>(" + tr( "press Shift + y for quick access" ) + ")" );
145  mYLineEdit->setToolTip( "<b>" + tr( "Y coordinate" ) + "</b><br>(" + tr( "press y for quick access" ) + ")" );
146  mLockYButton->setToolTip( "<b>" + tr( "Lock y coordinate" ) + "</b><br>(" + tr( "press Ctrl + y for quick access" ) + ")" );
147  mRepeatingLockYButton->setToolTip( "<b>" + tr( "Continuously lock y coordinate" ) + "</b>" );
148 
149  // Create the slots/signals
150  connect( mXLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueXChanged );
151  connect( mYLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueYChanged );
152  connect( mDistanceLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueDistanceChanged );
153  connect( mAngleLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueAngleChanged );
154 
155  // Create the floater
156  mFloater = new QgsAdvancedDigitizingFloater( canvas, this );
157  connect( mToggleFloaterAction, &QAction::triggered, mFloater, &QgsAdvancedDigitizingFloater::setActive );
158  mToggleFloaterAction->setChecked( mFloater->active() );
159 
160  updateCapacity( true );
161  connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, [ = ] { updateCapacity( true ); } );
162 
163  disable();
164 }
165 
166 void QgsAdvancedDigitizingDockWidget::setX( const QString &value, WidgetSetMode mode )
167 {
168  mXLineEdit->setText( value );
169  if ( mode == WidgetSetMode::ReturnPressed )
170  {
171  mXLineEdit->returnPressed();
172  }
173  else if ( mode == WidgetSetMode::FocusOut )
174  {
175  QEvent *e = new QEvent( QEvent::FocusOut );
176  QCoreApplication::postEvent( mXLineEdit, e );
177  }
178  else if ( mode == WidgetSetMode::TextEdited )
179  {
180  mXLineEdit->textEdited( value );
181  }
182 }
183 void QgsAdvancedDigitizingDockWidget::setY( const QString &value, WidgetSetMode mode )
184 {
185  mYLineEdit->setText( value );
186  if ( mode == WidgetSetMode::ReturnPressed )
187  {
188  mYLineEdit->returnPressed();
189  }
190  else if ( mode == WidgetSetMode::FocusOut )
191  {
192  QEvent *e = new QEvent( QEvent::FocusOut );
193  QCoreApplication::postEvent( mYLineEdit, e );
194  }
195  else if ( mode == WidgetSetMode::TextEdited )
196  {
197  mYLineEdit->textEdited( value );
198  }
199 }
201 {
202  mAngleLineEdit->setText( value );
203  if ( mode == WidgetSetMode::ReturnPressed )
204  {
205  mAngleLineEdit->returnPressed();
206  }
207  else if ( mode == WidgetSetMode::FocusOut )
208  {
209  QEvent *e = new QEvent( QEvent::FocusOut );
210  QCoreApplication::postEvent( mAngleLineEdit, e );
211  }
212  else if ( mode == WidgetSetMode::TextEdited )
213  {
214  mAngleLineEdit->textEdited( value );
215  }
216 }
218 {
219  mDistanceLineEdit->setText( value );
220  if ( mode == WidgetSetMode::ReturnPressed )
221  {
222  mDistanceLineEdit->returnPressed();
223  }
224  else if ( mode == WidgetSetMode::FocusOut )
225  {
226  QEvent *e = new QEvent( QEvent::FocusOut );
227  QCoreApplication::postEvent( mDistanceLineEdit, e );
228  }
229  else if ( mode == WidgetSetMode::TextEdited )
230  {
231  mDistanceLineEdit->textEdited( value );
232  }
233 }
234 
235 
236 void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
237 {
238  mCadEnabled = enabled;
239  mEnableAction->setChecked( enabled );
240  mConstructionModeAction->setEnabled( enabled );
241  mParallelAction->setEnabled( enabled );
242  mPerpendicularAction->setEnabled( enabled );
243  mSettingsAction->setEnabled( enabled );
244  mInputWidgets->setEnabled( enabled );
245  mToggleFloaterAction->setEnabled( enabled );
246 
247  clear();
248  setConstructionMode( false );
249 
250  emit cadEnabledChanged( enabled );
251 }
252 
253 void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
254 {
255  enabled &= mCurrentMapToolSupportsCad;
256 
257  mSessionActive = enabled;
258 
259  if ( enabled && !isVisible() )
260  {
261  show();
262  }
263 
264  setCadEnabled( enabled );
265 }
266 
267 void QgsAdvancedDigitizingDockWidget::additionalConstraintClicked( bool activated )
268 {
269  if ( !activated )
270  {
271  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
272  }
273  if ( sender() == mParallelAction )
274  {
275  lockAdditionalConstraint( AdditionalConstraint::Parallel );
276  }
277  else if ( sender() == mPerpendicularAction )
278  {
279  lockAdditionalConstraint( AdditionalConstraint::Perpendicular );
280  }
281 }
282 
283 void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
284 {
285  if ( sender() == mRelativeAngleButton )
286  {
287  mAngleConstraint->setRelative( activate );
288  emit relativeAngleChanged( activate );
289  }
290  else if ( sender() == mRelativeXButton )
291  {
292  mXConstraint->setRelative( activate );
293  emit relativeXChanged( activate );
294  }
295  else if ( sender() == mRelativeYButton )
296  {
297  mYConstraint->setRelative( activate );
298  emit relativeYChanged( activate );
299  }
300 }
301 
302 void QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock( bool activate )
303 {
304  if ( sender() == mRepeatingLockDistanceButton )
305  {
306  mDistanceConstraint->setRepeatingLock( activate );
307  }
308  else if ( sender() == mRepeatingLockAngleButton )
309  {
310  mAngleConstraint->setRepeatingLock( activate );
311  }
312  else if ( sender() == mRepeatingLockXButton )
313  {
314  mXConstraint->setRepeatingLock( activate );
315  }
316  else if ( sender() == mRepeatingLockYButton )
317  {
318  mYConstraint->setRepeatingLock( activate );
319  }
320 }
321 
322 void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
323 {
324  mConstructionMode = enabled;
325  mConstructionModeAction->setChecked( enabled );
326 }
327 
328 void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction *action )
329 {
330  // common angles
331  QMap<QAction *, double>::const_iterator ica = mCommonAngleActions.constFind( action );
332  if ( ica != mCommonAngleActions.constEnd() )
333  {
334  ica.key()->setChecked( true );
335  mCommonAngleConstraint = ica.value();
336  QgsSettings().setValue( QStringLiteral( "/Cad/CommonAngle" ), ica.value() );
337  return;
338  }
339 }
340 
341 void QgsAdvancedDigitizingDockWidget::releaseLocks( bool releaseRepeatingLocks )
342 {
343  // release all locks except construction mode
344 
345  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
346 
347  if ( releaseRepeatingLocks || !mAngleConstraint->isRepeatingLock() )
348  {
349  mAngleConstraint->setLockMode( CadConstraint::NoLock );
350  emit lockAngleChanged( false );
351  }
352  if ( releaseRepeatingLocks || !mDistanceConstraint->isRepeatingLock() )
353  {
354  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
355  emit lockDistanceChanged( false );
356  }
357  if ( releaseRepeatingLocks || !mXConstraint->isRepeatingLock() )
358  {
359  mXConstraint->setLockMode( CadConstraint::NoLock );
360  emit lockXChanged( false );
361  }
362  if ( releaseRepeatingLocks || !mYConstraint->isRepeatingLock() )
363  {
364  mYConstraint->setLockMode( CadConstraint::NoLock );
365  emit lockYChanged( false );
366  }
367 
368  if ( !mCadPointList.empty() )
369  {
370  if ( !mXConstraint->isLocked() && !mXConstraint->relative() )
371  {
372  mXConstraint->setValue( mCadPointList.constLast().x(), true );
373  }
374  if ( !mYConstraint->isLocked() && !mYConstraint->relative() )
375  {
376  mYConstraint->setValue( mCadPointList.constLast().y(), true );
377  }
378  }
379 
380 }
381 
382 #if 0
383 void QgsAdvancedDigitizingDockWidget::emit pointChanged()
384 {
385  // run a fake map mouse event to update the paint item
386  QPoint globalPos = mMapCanvas->cursor().pos();
387  QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
388  QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
389  mCurrentMapTool->canvasMoveEvent( e );
390 }
391 #endif
392 
393 QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
394 {
395  CadConstraint *constraint = nullptr;
396  if ( obj == mAngleLineEdit || obj == mLockAngleButton )
397  {
398  constraint = mAngleConstraint.get();
399  }
400  else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
401  {
402  constraint = mDistanceConstraint.get();
403  }
404  else if ( obj == mXLineEdit || obj == mLockXButton )
405  {
406  constraint = mXConstraint.get();
407  }
408  else if ( obj == mYLineEdit || obj == mLockYButton )
409  {
410  constraint = mYConstraint.get();
411  }
412  return constraint;
413 }
414 
415 double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, bool &ok ) const
416 {
417  ok = false;
418  double value = qgsPermissiveToDouble( inputValue, ok );
419  if ( ok )
420  {
421  return value;
422  }
423  else
424  {
425  // try to evaluate expression
426  QgsExpression expr( inputValue );
427  QVariant result = expr.evaluate();
428  if ( expr.hasEvalError() )
429  ok = false;
430  else
431  value = result.toDouble( &ok );
432  return value;
433  }
434 }
435 
436 void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
437 {
438  if ( !constraint || textValue.isEmpty() )
439  {
440  return;
441  }
442 
443  if ( constraint->lockMode() == CadConstraint::NoLock )
444  return;
445 
446  bool ok;
447  double value = parseUserInput( textValue, ok );
448  if ( !ok )
449  return;
450 
451  constraint->setValue( value, convertExpression );
452  // run a fake map mouse event to update the paint item
453  emit pointChanged( mCadPointList.value( 0 ) );
454 }
455 
456 void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
457 {
458  CadConstraint *constraint = objectToConstraint( sender() );
459  if ( !constraint )
460  {
461  return;
462  }
463 
464  if ( activate )
465  {
466  QString textValue = constraint->lineEdit()->text();
467  if ( !textValue.isEmpty() )
468  {
469  bool ok;
470  double value = parseUserInput( textValue, ok );
471  if ( ok )
472  {
473  constraint->setValue( value );
474  }
475  else
476  {
477  activate = false;
478  }
479  }
480  else
481  {
482  activate = false;
483  }
484  }
485  constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
486 
487  if ( constraint == mXConstraint.get() )
488  {
489  emit lockXChanged( activate );
490  }
491  else if ( constraint == mYConstraint.get() )
492  {
493  emit lockYChanged( activate );
494  }
495  else if ( constraint == mDistanceConstraint.get() )
496  {
497  emit lockDistanceChanged( activate );
498  }
499  else if ( constraint == mAngleConstraint.get() )
500  {
501  emit lockAngleChanged( activate );
502  }
503 
504  if ( activate )
505  {
506  // deactivate perpendicular/parallel if angle has been activated
507  if ( constraint == mAngleConstraint.get() )
508  {
509  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
510  }
511 
512  // run a fake map mouse event to update the paint item
513  emit pointChanged( mCadPointList.value( 0 ) );
514  }
515 }
516 
517 void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
518 {
519  CadConstraint *constraint = objectToConstraint( sender() );
520  if ( !constraint )
521  {
522  return;
523  }
524 
525  updateConstraintValue( constraint, textValue, false );
526 }
527 
528 void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
529 {
530  QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
531  if ( !lineEdit )
532  return;
533 
534  CadConstraint *constraint = objectToConstraint( lineEdit );
535  if ( !constraint )
536  {
537  return;
538  }
539 
540  updateConstraintValue( constraint, lineEdit->text(), true );
541 }
542 
543 void QgsAdvancedDigitizingDockWidget::lockAdditionalConstraint( AdditionalConstraint constraint )
544 {
545  mAdditionalConstraint = constraint;
546  mPerpendicularAction->setChecked( constraint == AdditionalConstraint::Perpendicular );
547  mParallelAction->setChecked( constraint == AdditionalConstraint::Parallel );
548 }
549 
550 void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
551 {
552  CadCapacities newCapacities = CadCapacities();
553  // first point is the mouse point (it doesn't count)
554  if ( mCadPointList.count() > 1 )
555  {
556  newCapacities |= AbsoluteAngle | RelativeCoordinates;
557  }
558  if ( mCadPointList.count() > 2 )
559  {
560  newCapacities |= RelativeAngle;
561  }
562  if ( !updateUIwithoutChange && newCapacities == mCapacities )
563  {
564  return;
565  }
566 
567  bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
568 
569  // update the UI according to new capacities
570  // still keep the old to compare
571 
572  bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
573  bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
574  bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
575 
576  mPerpendicularAction->setEnabled( absoluteAngle && snappingEnabled );
577  mParallelAction->setEnabled( absoluteAngle && snappingEnabled );
578 
579  //update tooltips on buttons
580  if ( !snappingEnabled )
581  {
582  mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode" ) );
583  mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode" ) );
584  }
585  else
586  {
587  mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
588  mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
589  }
590 
591 
592  if ( !absoluteAngle )
593  {
594  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
595  }
596 
597  // absolute angle = azimuth, relative = from previous line
598  mLockAngleButton->setEnabled( absoluteAngle );
599  mRelativeAngleButton->setEnabled( relativeAngle );
600  mAngleLineEdit->setEnabled( absoluteAngle );
601  emit enabledChangedAngle( absoluteAngle );
602  if ( !absoluteAngle )
603  {
604  mAngleConstraint->setLockMode( CadConstraint::NoLock );
605  }
606  if ( !relativeAngle )
607  {
608  mAngleConstraint->setRelative( false );
609  emit relativeAngleChanged( false );
610  }
611  else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
612  {
613  // set angle mode to relative if can do and wasn't available before
614  mAngleConstraint->setRelative( true );
615  emit relativeAngleChanged( true );
616  }
617 
618  // distance is always relative
619  mLockDistanceButton->setEnabled( relativeCoordinates );
620  mDistanceLineEdit->setEnabled( relativeCoordinates );
621  emit enabledChangedDistance( relativeCoordinates );
622  if ( !relativeCoordinates )
623  {
624  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
625  }
626 
627  mRelativeXButton->setEnabled( relativeCoordinates );
628  mRelativeYButton->setEnabled( relativeCoordinates );
629 
630  // update capacities
631  mCapacities = newCapacities;
632 }
633 
634 
636 {
639  constr.relative = c->relative();
640  constr.value = c->value();
641  return constr;
642 }
643 
645 {
647  context.snappingUtils = mMapCanvas->snappingUtils();
648  context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
649  context.xConstraint = _constraint( mXConstraint.get() );
650  context.yConstraint = _constraint( mYConstraint.get() );
651  context.distanceConstraint = _constraint( mDistanceConstraint.get() );
652  context.angleConstraint = _constraint( mAngleConstraint.get() );
653  context.cadPointList = mCadPointList;
654 
655  context.commonAngleConstraint.locked = true;
657  context.commonAngleConstraint.value = mCommonAngleConstraint;
658 
660 
661  bool res = output.valid;
662  QgsPointXY point = output.finalMapPoint;
663  mSnappedSegment.clear();
664  if ( output.snapMatch.hasEdge() )
665  {
666  QgsPointXY edgePt0, edgePt1;
667  output.snapMatch.edgePoints( edgePt0, edgePt1 );
668  mSnappedSegment << edgePt0 << edgePt1;
669  }
670  if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
671  {
672  if ( output.softLockCommonAngle != -1 )
673  {
674  mAngleConstraint->setLockMode( CadConstraint::SoftLock );
675  mAngleConstraint->setValue( output.softLockCommonAngle );
676  }
677  else
678  {
679  mAngleConstraint->setLockMode( CadConstraint::NoLock );
680  }
681  }
682 
683  if ( output.snapMatch.isValid() )
684  {
685  mSnapIndicator->setMatch( output.snapMatch );
686  mSnapIndicator->setVisible( true );
687  }
688  else
689  {
690  mSnapIndicator->setVisible( false );
691  }
692 
693  /*
694  * Constraints are applied in 2D, they are always called when using the tool
695  * but they do not take into account if when you snap on a vertex it has
696  * a Z value.
697  * To get the value we use the snapPoint method. However, we only apply it
698  * when the snapped point corresponds to the constrained point or on an edge
699  * if the topological editing is activated.
700  */
701  e->setMapPoint( point );
702  mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
703  if ( ( mSnapMatch.hasVertex() && ( point == mSnapMatch.point() ) ) || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
704  {
705  e->snapPoint();
706  }
707  // update the point list
708  updateCurrentPoint( point );
709 
710  updateUnlockedConstraintValues( point );
711 
712  if ( res )
713  {
714  emit popWarning();
715  }
716  else
717  {
718  emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
719  }
720 
721  return res;
722 }
723 
724 
725 void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPointXY &point )
726 {
727  bool previousPointExist, penulPointExist;
728  QgsPointXY previousPt = previousPoint( &previousPointExist );
729  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
730 
731  // --- angle
732  if ( !mAngleConstraint->isLocked() && previousPointExist )
733  {
734  double angle = 0.0;
735  if ( penulPointExist && mAngleConstraint->relative() )
736  {
737  // previous angle
738  angle = std::atan2( previousPt.y() - penultimatePt.y(),
739  previousPt.x() - penultimatePt.x() );
740  }
741  angle = ( std::atan2( point.y() - previousPt.y(),
742  point.x() - previousPt.x()
743  ) - angle ) * 180 / M_PI;
744  // modulus
745  angle = std::fmod( angle, 360.0 );
746  mAngleConstraint->setValue( angle );
747  }
748  // --- distance
749  if ( !mDistanceConstraint->isLocked() && previousPointExist )
750  {
751  mDistanceConstraint->setValue( std::sqrt( previousPt.sqrDist( point ) ) );
752  }
753  // --- X
754  if ( !mXConstraint->isLocked() )
755  {
756  if ( previousPointExist && mXConstraint->relative() )
757  {
758  mXConstraint->setValue( point.x() - previousPt.x() );
759  }
760  else
761  {
762  mXConstraint->setValue( point.x() );
763  }
764  }
765  // --- Y
766  if ( !mYConstraint->isLocked() )
767  {
768  if ( previousPointExist && mYConstraint->relative() )
769  {
770  mYConstraint->setValue( point.y() - previousPt.y() );
771  }
772  else
773  {
774  mYConstraint->setValue( point.y() );
775  }
776  }
777 }
778 
779 
780 QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
781 {
782  QList<QgsPointXY> segment;
783  QgsPointXY pt1, pt2;
785 
786  QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
787 
788  QgsSnappingConfig canvasConfig = snappingUtils->config();
789  QgsSnappingConfig localConfig = snappingUtils->config();
790 
791  localConfig.setMode( QgsSnappingConfig::AllLayers );
793  snappingUtils->setConfig( localConfig );
794 
795  match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
796 
797  snappingUtils->setConfig( canvasConfig );
798 
799  if ( match.isValid() && match.hasEdge() )
800  {
801  match.edgePoints( pt1, pt2 );
802  segment << pt1 << pt2;
803  }
804 
805  if ( snapped )
806  {
807  *snapped = segment.count() == 2;
808  }
809 
810  return segment;
811 }
812 
814 {
815  if ( mAdditionalConstraint == AdditionalConstraint::NoConstraint )
816  {
817  return false;
818  }
819 
820  bool previousPointExist, penulPointExist, snappedSegmentExist;
821  QgsPointXY previousPt = previousPoint( &previousPointExist );
822  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
823  mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
824 
825  if ( !previousPointExist || !snappedSegmentExist )
826  {
827  return false;
828  }
829 
830  double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
831 
832  if ( mAngleConstraint->relative() && penulPointExist )
833  {
834  angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
835  }
836 
837  if ( mAdditionalConstraint == AdditionalConstraint::Perpendicular )
838  {
839  angle += M_PI_2;
840  }
841 
842  angle *= 180 / M_PI;
843 
844  mAngleConstraint->setValue( angle );
845  mAngleConstraint->setLockMode( lockMode );
846  if ( lockMode == CadConstraint::HardLock )
847  {
848  mAdditionalConstraint = AdditionalConstraint::NoConstraint;
849  }
850 
851  return true;
852 }
853 
855 {
856  // event on map tool
857 
858  if ( !mCadEnabled )
859  return false;
860 
861  switch ( e->key() )
862  {
863  case Qt::Key_Backspace:
864  case Qt::Key_Delete:
865  {
867  releaseLocks( false );
868  break;
869  }
870  case Qt::Key_Escape:
871  {
872  releaseLocks();
873  break;
874  }
875  default:
876  {
877  keyPressEvent( e );
878  break;
879  }
880  }
881  // for map tools, continues with key press in any case
882  return false;
883 }
884 
886 {
887  clearPoints();
888  releaseLocks();
889 }
890 
892 {
893  // event on dock (this)
894 
895  if ( !mCadEnabled )
896  return;
897 
898  switch ( e->key() )
899  {
900  case Qt::Key_Backspace:
901  case Qt::Key_Delete:
902  {
904  releaseLocks( false );
905  break;
906  }
907  case Qt::Key_Escape:
908  {
909  releaseLocks();
910  break;
911  }
912  default:
913  {
914  filterKeyPress( e );
915  break;
916  }
917  }
918 }
919 
920 void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
921 {
922  clearPoints();
923  const auto constPoints = points;
924  for ( const QgsPointXY &pt : constPoints )
925  {
926  addPoint( pt );
927  }
928 }
929 
930 bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
931 {
932  if ( !cadEnabled() )
933  {
934  return QgsDockWidget::eventFilter( obj, event );
935  }
936 
937  // event for line edits and map canvas
938  // we have to catch both KeyPress events and ShortcutOverride events. This is because
939  // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
940  // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
941  // us to intercept these keystrokes before they are caught by the global shortcuts
942  if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
943  {
944  if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
945  {
946  return filterKeyPress( keyEvent );
947  }
948  }
949  return QgsDockWidget::eventFilter( obj, event );
950 }
951 
952 bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
953 {
954  // we need to be careful here -- because this method is called on both KeyPress events AND
955  // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
956  // these event types for a single key press. I.e. pressing "A" may first call trigger a
957  // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
958  QEvent::Type type = e->type();
959  switch ( e->key() )
960  {
961  case Qt::Key_X:
962  {
963  // modifier+x ONLY caught for ShortcutOverride events...
964  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
965  {
966  mXConstraint->toggleLocked();
967  emit lockXChanged( mXConstraint->isLocked() );
968  emit pointChanged( mCadPointList.value( 0 ) );
969  e->accept();
970  }
971  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
972  {
973  if ( mCapacities.testFlag( RelativeCoordinates ) )
974  {
975  mXConstraint->toggleRelative();
976  emit relativeXChanged( mXConstraint->relative() );
977  emit pointChanged( mCadPointList.value( 0 ) );
978  e->accept();
979  }
980  }
981  // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
982  else if ( type == QEvent::KeyPress )
983  {
984  mXLineEdit->setFocus();
985  mXLineEdit->selectAll();
986  emit focusOnXRequested();
987  e->accept();
988  }
989  break;
990  }
991  case Qt::Key_Y:
992  {
993  // modifier+y ONLY caught for ShortcutOverride events...
994  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
995  {
996  mYConstraint->toggleLocked();
997  emit lockYChanged( mYConstraint->isLocked() );
998  emit pointChanged( mCadPointList.value( 0 ) );
999  e->accept();
1000  }
1001  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1002  {
1003  if ( mCapacities.testFlag( RelativeCoordinates ) )
1004  {
1005  mYConstraint->toggleRelative();
1006  emit relativeYChanged( mYConstraint->relative() );
1007  emit pointChanged( mCadPointList.value( 0 ) );
1008  e->accept();
1009  }
1010  }
1011  // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1012  else if ( type == QEvent::KeyPress )
1013  {
1014  mYLineEdit->setFocus();
1015  mYLineEdit->selectAll();
1016  emit focusOnYRequested();
1017  e->accept();
1018  }
1019  break;
1020  }
1021  case Qt::Key_A:
1022  {
1023  // modifier+a ONLY caught for ShortcutOverride events...
1024  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1025  {
1026  if ( mCapacities.testFlag( AbsoluteAngle ) )
1027  {
1028  mAngleConstraint->toggleLocked();
1029  emit lockAngleChanged( mAngleConstraint->isLocked() );
1030  emit pointChanged( mCadPointList.value( 0 ) );
1031  e->accept();
1032  }
1033  }
1034  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1035  {
1036  if ( mCapacities.testFlag( RelativeAngle ) )
1037  {
1038  mAngleConstraint->toggleRelative();
1039  emit relativeAngleChanged( mAngleConstraint->relative() );
1040  emit pointChanged( mCadPointList.value( 0 ) );
1041  e->accept();
1042  }
1043  }
1044  // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
1045  else if ( type == QEvent::KeyPress )
1046  {
1047  mAngleLineEdit->setFocus();
1048  mAngleLineEdit->selectAll();
1049  emit focusOnAngleRequested();
1050  e->accept();
1051  }
1052  break;
1053  }
1054  case Qt::Key_D:
1055  {
1056  // modifier+d ONLY caught for ShortcutOverride events...
1057  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1058  {
1059  if ( mCapacities.testFlag( RelativeCoordinates ) )
1060  {
1061  mDistanceConstraint->toggleLocked();
1062  emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1063  emit pointChanged( mCadPointList.value( 0 ) );
1064  e->accept();
1065  }
1066  }
1067  // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
1068  else if ( type == QEvent::KeyPress )
1069  {
1070  mDistanceLineEdit->setFocus();
1071  mDistanceLineEdit->selectAll();
1072  emit focusOnDistanceRequested();
1073  e->accept();
1074  }
1075  break;
1076  }
1077  case Qt::Key_C:
1078  {
1079  if ( type == QEvent::KeyPress )
1080  {
1081  setConstructionMode( !mConstructionMode );
1082  e->accept();
1083  }
1084  break;
1085  }
1086  case Qt::Key_P:
1087  {
1088  if ( type == QEvent::KeyPress )
1089  {
1090  bool parallel = mParallelAction->isChecked();
1091  bool perpendicular = mPerpendicularAction->isChecked();
1092 
1093  if ( !parallel && !perpendicular )
1094  {
1095  lockAdditionalConstraint( AdditionalConstraint::Perpendicular );
1096  }
1097  else if ( perpendicular )
1098  {
1099  lockAdditionalConstraint( AdditionalConstraint::Parallel );
1100  }
1101  else
1102  {
1103  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
1104  }
1105  e->accept();
1106  }
1107  break;
1108  }
1109  default:
1110  {
1111  return false; // continues
1112  }
1113  }
1114  return e->isAccepted();
1115 }
1116 
1118 {
1119  connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
1120  if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
1121  {
1122  mErrorLabel->setText( tr( "CAD tools can not be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1123  mErrorLabel->show();
1124  mEnableAction->setEnabled( false );
1125  setCadEnabled( false );
1126  }
1127  else
1128  {
1129  mEnableAction->setEnabled( true );
1130  mErrorLabel->hide();
1131  mCadWidget->show();
1132 
1133  mCurrentMapToolSupportsCad = true;
1134 
1135  if ( mSessionActive && !isVisible() )
1136  {
1137  show();
1138  }
1139  setCadEnabled( mSessionActive );
1140  }
1141 }
1142 
1144 {
1146 
1147  mEnableAction->setEnabled( false );
1148  mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
1149  mErrorLabel->show();
1150  mCadWidget->hide();
1151 
1152  mCurrentMapToolSupportsCad = false;
1153 
1154  setCadEnabled( false );
1155 }
1156 
1158 {
1159  mCadPaintItem->update();
1160 }
1161 
1163 {
1164  if ( !pointsCount() )
1165  {
1166  mCadPointList << point;
1167  }
1168  else
1169  {
1170  mCadPointList.insert( 0, point );
1171  }
1172 
1173  updateCapacity();
1175 }
1176 
1178 {
1179  if ( !pointsCount() )
1180  return;
1181 
1182  int i = pointsCount() > 1 ? 1 : 0;
1183  mCadPointList.removeAt( i );
1184  updateCapacity();
1186 }
1187 
1189 {
1190  mCadPointList.clear();
1191  mSnappedSegment.clear();
1192 
1193  updateCapacity();
1195 }
1196 
1197 void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPointXY &point )
1198 {
1199  if ( !pointsCount() )
1200  {
1201  mCadPointList << point;
1202  updateCapacity();
1203  }
1204  else
1205  {
1206  mCadPointList[0] = point;
1207  }
1209 }
1210 
1211 
1213 {
1214  mLockMode = mode;
1215  mLockerButton->setChecked( mode == HardLock );
1216  if ( mRepeatingLockButton )
1217  {
1218  if ( mode == HardLock )
1219  {
1220  mRepeatingLockButton->setEnabled( true );
1221  }
1222  else
1223  {
1224  mRepeatingLockButton->setChecked( false );
1225  mRepeatingLockButton->setEnabled( false );
1226  }
1227  }
1228 
1229  if ( mode == NoLock )
1230  {
1231  mLineEdit->clear();
1232  }
1233 
1234 }
1235 
1237 {
1238  mRepeatingLock = repeating;
1239  if ( mRepeatingLockButton )
1240  mRepeatingLockButton->setChecked( repeating );
1241 }
1242 
1244 {
1245  mRelative = relative;
1246  if ( mRelativeButton )
1247  {
1248  mRelativeButton->setChecked( relative );
1249  }
1250 }
1251 
1252 void QgsAdvancedDigitizingDockWidget::CadConstraint::setValue( double value, bool updateWidget )
1253 {
1254  mValue = value;
1255  if ( updateWidget )
1256  mLineEdit->setText( QLocale().toString( value, 'f', 6 ) );
1257 }
1258 
1260 {
1261  setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1262 }
1263 
1265 {
1266  setRelative( !mRelative );
1267 }
1268 
1270 {
1271  if ( exist )
1272  *exist = pointsCount() > 0;
1273  if ( pointsCount() > 0 )
1274  return mCadPointList.value( 0 );
1275  else
1276  return QgsPointXY();
1277 }
1278 
1280 {
1281  if ( exist )
1282  *exist = pointsCount() > 1;
1283  if ( pointsCount() > 1 )
1284  return mCadPointList.value( 1 );
1285  else
1286  return QgsPointXY();
1287 }
1288 
1290 {
1291  if ( exist )
1292  *exist = pointsCount() > 2;
1293  if ( pointsCount() > 2 )
1294  return mCadPointList.value( 2 );
1295  else
1296  return QgsPointXY();
1297 }
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (.
The CadConstraint is an abstract class for all basic constraints (angle/distance/x/y).
void setRepeatingLock(bool repeating)
Sets whether a repeating lock is set for the constraint.
void setRelative(bool relative)
Set if the constraint should be treated relative.
void setValue(double value, bool updateWidget=true)
Set the value of the constraint.
void valueDistanceChanged(const QString &value)
Emitted whenever the distance value changes (either the mouse moved, or the user changed the input).
void setPoints(const QList< QgsPointXY > &points)
Configures list of current CAD points.
QgsPointXY penultimatePoint(bool *exists=nullptr) const
The penultimate point.
void setY(const QString &value, WidgetSetMode mode)
Set the Y value on the widget.
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
bool applyConstraints(QgsMapMouseEvent *e)
apply the CAD constraints.
int pointsCount() const
The number of points in the CAD point helper list.
void addPoint(const QgsPointXY &point)
Adds point to the CAD point list.
void releaseLocks(bool releaseRepeatingLocks=true)
unlock all constraints
void lockXChanged(bool locked)
Emitted whenever the X parameter is locked.
QgsPointXY currentPoint(bool *exists=nullptr) const
The last point.
void focusOnXRequested()
Emitted whenever the X field should get the focus using the shortcuts (X).
void valueYChanged(const QString &value)
Emitted whenever the Y value changes (either the mouse moved, or the user changed the input).
void focusOnYRequested()
Emitted whenever the Y field should get the focus using the shortcuts (Y).
void enabledChangedDistance(bool enabled)
Emitted whenever the distance field is enabled or disabled.
void clearPoints()
Removes all points from the CAD point list.
QgsPointXY previousPoint(bool *exists=nullptr) const
The previous point.
void lockAngleChanged(bool locked)
Emitted whenever the angle parameter is locked.
void updateCadPaintItem()
Updates canvas item that displays constraints on the ma.
void removePreviousPoint()
Remove previous point in the CAD point list.
void relativeXChanged(bool relative)
Emitted whenever the X parameter is toggled between absolute and relative.
void focusOnAngleRequested()
Emitted whenever the angle field should get the focus using the shortcuts (A).
WidgetSetMode
Type of interaction to simulate when editing values from external widget.
void popWarning()
Remove any previously emitted warnings (if any)
void valueXChanged(const QString &value)
Emitted whenever the X value changes (either the mouse moved, or the user changed the input).
void cadEnabledChanged(bool enabled)
Signals for external widgets that need to update according to current values.
void pointChanged(const QgsPointXY &point)
Sometimes a constraint may change the current point out of a mouse event.
void lockYChanged(bool locked)
Emitted whenever the Y parameter is locked.
void valueAngleChanged(const QString &value)
Emitted whenever the angle value changes (either the mouse moved, or the user changed the input).
void enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
@ RelativeAngle
Also for parallel and perpendicular.
@ RelativeCoordinates
This corresponds to distance and relative coordinates.
bool canvasKeyPressEventFilter(QKeyEvent *e)
Filter key events to e.g.
void setAngle(const QString &value, WidgetSetMode mode)
Set the angle value on the widget.
void enabledChangedAngle(bool enabled)
Emitted whenever the angle field is enabled or disabled.
void lockDistanceChanged(bool locked)
Emitted whenever the distance parameter is locked.
void relativeAngleChanged(bool relative)
Emitted whenever the angleX parameter is toggled between absolute and relative.
void setDistance(const QString &value, WidgetSetMode mode)
Set the distance value on the widget.
bool alignToSegment(QgsMapMouseEvent *e, QgsAdvancedDigitizingDockWidget::CadConstraint::LockMode lockMode=QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock)
align to segment for additional constraint.
void setX(const QString &value, WidgetSetMode mode)
Set the X value on the widget.
QgsAdvancedDigitizingDockWidget(QgsMapCanvas *canvas, QWidget *parent=nullptr)
Create an advanced digitizing dock widget.
void clear()
Clear any cached previous clicks and helper lines.
void focusOnDistanceRequested()
Emitted whenever the distance field should get the focus using the shortcuts (D).
void pushWarning(const QString &message)
Push a warning.
void relativeYChanged(bool relative)
Emitted whenever the Y parameter is toggled between absolute and relative.
The QgsAdvancedDigitizingFloater class is widget that floats next to the mouse pointer,...
void setActive(bool active)
Set whether the floater should be active or not.
bool active()
Whether the floater is active or not.
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
QgsDockWidget subclass with more fine-grained control over how the widget is closed or opened.
Definition: qgsdockwidget.h:32
Class for parsing and evaluation of expressions (formerly called "search strings").
A event filter for watching for focus events on a parent object.
void focusOut()
Emitted when parent object loses focus.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:86
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
void destinationCrsChanged()
Emitted when map CRS has changed.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
QgsPointXY originalMapPoint() const
Returns the original, unmodified map point of the mouse cursor.
void setMapPoint(const QgsPointXY &point)
Set the (snapped) point this event points to in map coordinates.
QgsPointXY snapPoint()
snapPoint will snap the points using the map canvas snapping utils configuration
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
A class to represent a 2D point.
Definition: qgspointxy.h:44
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:175
double y
Definition: qgspointxy.h:48
Q_GADGET double x
Definition: qgspointxy.h:47
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:501
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:108
bool topologicalEditing
Definition: qgsproject.h:115
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Class that shows snapping marker on map canvas for the current snapping match.
This is a container for configuration of the snapping of the project.
@ AllLayers
On all vector layers.
@ SegmentFlag
On segments.
void setTypeFlag(QgsSnappingConfig::SnappingTypeFlag type)
define the type of snapping
void setMode(SnappingMode mode)
define the mode of snapping
bool enabled() const
Returns if snapping is enabled.
This class has all the configuration of snapping and can return answers to snapping queries.
QgsSnappingConfig config
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Snap to map according to the current configuration.
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
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
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
double qgsPermissiveToDouble(QString string, bool &ok)
Converts a string to a double in a permissive way, e.g., allowing for incorrect numbers of digits bet...
Definition: qgis.cpp:65
Structure with details of one constraint.
Definition: qgscadutils.h:38
bool locked
Whether the constraint is active, i.e. should be considered.
Definition: qgscadutils.h:46
double value
Numeric value of the constraint (coordinate/distance in map units or angle in degrees)
Definition: qgscadutils.h:50
bool relative
Whether the value is relative to previous value.
Definition: qgscadutils.h:48
Structure defining all constraints for alignMapPoint() method.
Definition: qgscadutils.h:55
QgsCadUtils::AlignMapPointConstraint yConstraint
Constraint for Y coordinate.
Definition: qgscadutils.h:64
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
Definition: qgscadutils.h:62
double mapUnitsPerPixel
Map units/pixel ratio from map canvas. Needed for.
Definition: qgscadutils.h:59
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
Definition: qgscadutils.h:66
QList< QgsPointXY > cadPointList
List of recent CAD points in map coordinates.
Definition: qgscadutils.h:77
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be nullptr.
Definition: qgscadutils.h:57
QgsCadUtils::AlignMapPointConstraint commonAngleConstraint
Constraint for soft lock to a common angle.
Definition: qgscadutils.h:70
QgsCadUtils::AlignMapPointConstraint angleConstraint
Constraint for angle.
Definition: qgscadutils.h:68
Structure returned from alignMapPoint() method.
Definition: qgscadutils.h:88
QgsPointXY finalMapPoint
map point aligned according to the constraints
Definition: qgscadutils.h:93
bool valid
Whether the combination of constraints is actually valid.
Definition: qgscadutils.h:90
QgsPointLocator::Match snapMatch
Snapped point - only valid if actually used for something.
Definition: qgscadutils.h:99
double softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition: qgscadutils.h:108
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords,...
bool hasEdge() const
Returns true if the Match is an edge.
void edgePoints(QgsPointXY &pt1, QgsPointXY &pt2) const
Only for a valid edge match - obtain endpoints of the edge.
bool hasVertex() const
Returns true if the Match is a vertex.