QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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" ), 90 ).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 
369 #if 0
370 void QgsAdvancedDigitizingDockWidget::emit pointChanged()
371 {
372  // run a fake map mouse event to update the paint item
373  QPoint globalPos = mMapCanvas->cursor().pos();
374  QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
375  QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
376  mCurrentMapTool->canvasMoveEvent( e );
377 }
378 #endif
379 
380 QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
381 {
382  CadConstraint *constraint = nullptr;
383  if ( obj == mAngleLineEdit || obj == mLockAngleButton )
384  {
385  constraint = mAngleConstraint.get();
386  }
387  else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
388  {
389  constraint = mDistanceConstraint.get();
390  }
391  else if ( obj == mXLineEdit || obj == mLockXButton )
392  {
393  constraint = mXConstraint.get();
394  }
395  else if ( obj == mYLineEdit || obj == mLockYButton )
396  {
397  constraint = mYConstraint.get();
398  }
399  return constraint;
400 }
401 
402 double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, bool &ok ) const
403 {
404  ok = false;
405  double value = qgsPermissiveToDouble( inputValue, ok );
406  if ( ok )
407  {
408  return value;
409  }
410  else
411  {
412  // try to evaluate expression
413  QgsExpression expr( inputValue );
414  QVariant result = expr.evaluate();
415  if ( expr.hasEvalError() )
416  ok = false;
417  else
418  value = result.toDouble( &ok );
419  return value;
420  }
421 }
422 
423 void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
424 {
425  if ( !constraint || textValue.isEmpty() )
426  {
427  return;
428  }
429 
430  if ( constraint->lockMode() == CadConstraint::NoLock )
431  return;
432 
433  bool ok;
434  double value = parseUserInput( textValue, ok );
435  if ( !ok )
436  return;
437 
438  constraint->setValue( value, convertExpression );
439  // run a fake map mouse event to update the paint item
440  emit pointChanged( mCadPointList.value( 0 ) );
441 }
442 
443 void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
444 {
445  CadConstraint *constraint = objectToConstraint( sender() );
446  if ( !constraint )
447  {
448  return;
449  }
450 
451  if ( activate )
452  {
453  QString textValue = constraint->lineEdit()->text();
454  if ( !textValue.isEmpty() )
455  {
456  bool ok;
457  double value = parseUserInput( textValue, ok );
458  if ( ok )
459  {
460  constraint->setValue( value );
461  }
462  else
463  {
464  activate = false;
465  }
466  }
467  else
468  {
469  activate = false;
470  }
471  }
472  constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
473 
474  if ( constraint == mXConstraint.get() )
475  {
476  emit lockXChanged( activate );
477  }
478  else if ( constraint == mYConstraint.get() )
479  {
480  emit lockYChanged( activate );
481  }
482  else if ( constraint == mDistanceConstraint.get() )
483  {
484  emit lockDistanceChanged( activate );
485  }
486  else if ( constraint == mAngleConstraint.get() )
487  {
488  emit lockAngleChanged( activate );
489  }
490 
491  if ( activate )
492  {
493  // deactivate perpendicular/parallel if angle has been activated
494  if ( constraint == mAngleConstraint.get() )
495  {
496  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
497  }
498 
499  // run a fake map mouse event to update the paint item
500  emit pointChanged( mCadPointList.value( 0 ) );
501  }
502 }
503 
504 void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
505 {
506  CadConstraint *constraint = objectToConstraint( sender() );
507  if ( !constraint )
508  {
509  return;
510  }
511 
512  updateConstraintValue( constraint, textValue, false );
513 }
514 
515 void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
516 {
517  QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
518  if ( !lineEdit )
519  return;
520 
521  CadConstraint *constraint = objectToConstraint( lineEdit );
522  if ( !constraint )
523  {
524  return;
525  }
526 
527  updateConstraintValue( constraint, lineEdit->text(), true );
528 }
529 
530 void QgsAdvancedDigitizingDockWidget::lockAdditionalConstraint( AdditionalConstraint constraint )
531 {
532  mAdditionalConstraint = constraint;
533  mPerpendicularAction->setChecked( constraint == AdditionalConstraint::Perpendicular );
534  mParallelAction->setChecked( constraint == AdditionalConstraint::Parallel );
535 }
536 
537 void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
538 {
539  CadCapacities newCapacities = nullptr;
540  // first point is the mouse point (it doesn't count)
541  if ( mCadPointList.count() > 1 )
542  {
543  newCapacities |= AbsoluteAngle | RelativeCoordinates;
544  }
545  if ( mCadPointList.count() > 2 )
546  {
547  newCapacities |= RelativeAngle;
548  }
549  if ( !updateUIwithoutChange && newCapacities == mCapacities )
550  {
551  return;
552  }
553 
554  bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
555 
556  // update the UI according to new capacities
557  // still keep the old to compare
558 
559  bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
560  bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
561  bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
562 
563  mPerpendicularAction->setEnabled( absoluteAngle && snappingEnabled );
564  mParallelAction->setEnabled( absoluteAngle && snappingEnabled );
565 
566  //update tooltips on buttons
567  if ( !snappingEnabled )
568  {
569  mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode" ) );
570  mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode" ) );
571  }
572  else
573  {
574  mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
575  mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
576  }
577 
578 
579  if ( !absoluteAngle )
580  {
581  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
582  }
583 
584  // absolute angle = azimuth, relative = from previous line
585  mLockAngleButton->setEnabled( absoluteAngle );
586  mRelativeAngleButton->setEnabled( relativeAngle );
587  mAngleLineEdit->setEnabled( absoluteAngle );
588  emit enabledChangedAngle( absoluteAngle );
589  if ( !absoluteAngle )
590  {
591  mAngleConstraint->setLockMode( CadConstraint::NoLock );
592  }
593  if ( !relativeAngle )
594  {
595  mAngleConstraint->setRelative( false );
596  emit relativeAngleChanged( false );
597  }
598  else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
599  {
600  // set angle mode to relative if can do and wasn't available before
601  mAngleConstraint->setRelative( true );
602  emit relativeAngleChanged( true );
603  }
604 
605  // distance is always relative
606  mLockDistanceButton->setEnabled( relativeCoordinates );
607  mDistanceLineEdit->setEnabled( relativeCoordinates );
608  emit enabledChangedDistance( relativeCoordinates );
609  if ( !relativeCoordinates )
610  {
611  mDistanceConstraint->setLockMode( CadConstraint::NoLock );
612  }
613 
614  mRelativeXButton->setEnabled( relativeCoordinates );
615  mRelativeYButton->setEnabled( relativeCoordinates );
616 
617  // update capacities
618  mCapacities = newCapacities;
619 }
620 
621 
623 {
626  constr.relative = c->relative();
627  constr.value = c->value();
628  return constr;
629 }
630 
632 {
634  context.snappingUtils = mMapCanvas->snappingUtils();
635  context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
636  context.xConstraint = _constraint( mXConstraint.get() );
637  context.yConstraint = _constraint( mYConstraint.get() );
638  context.distanceConstraint = _constraint( mDistanceConstraint.get() );
639  context.angleConstraint = _constraint( mAngleConstraint.get() );
640  context.cadPointList = mCadPointList;
641 
642  context.commonAngleConstraint.locked = true;
644  context.commonAngleConstraint.value = mCommonAngleConstraint;
645 
647 
648  bool res = output.valid;
649  QgsPointXY point = output.finalMapPoint;
650  mSnappedSegment.clear();
651  if ( output.edgeMatch.hasEdge() )
652  {
653  QgsPointXY edgePt0, edgePt1;
654  output.edgeMatch.edgePoints( edgePt0, edgePt1 );
655  mSnappedSegment << edgePt0 << edgePt1;
656  }
657  if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
658  {
659  if ( output.softLockCommonAngle != -1 )
660  {
661  mAngleConstraint->setLockMode( CadConstraint::SoftLock );
662  mAngleConstraint->setValue( output.softLockCommonAngle );
663  }
664  else
665  {
666  mAngleConstraint->setLockMode( CadConstraint::NoLock );
667  }
668  }
669 
670  // set the point coordinates in the map event
671  e->setMapPoint( point );
672 
673  mSnapMatch = context.snappingUtils->snapToMap( point );
674 
675  if ( mSnapMatch.isValid() )
676  {
677  mSnapIndicator->setMatch( mSnapMatch );
678  mSnapIndicator->setVisible( true );
679  }
680  else
681  {
682  mSnapIndicator->setVisible( false );
683  }
684 
685  /*
686  * Constraints are applied in 2D, they are always called when using the tool
687  * but they do not take into account if when you snap on a vertex it has
688  * a Z value.
689  * To get the value we use the snapPoint method. However, we only apply it
690  * when the snapped point corresponds to the constrained point.
691  */
692  if ( mSnapMatch.hasVertex() && ( point == mSnapMatch.point() ) )
693  {
694  e->snapPoint();
695  }
696  // update the point list
697  updateCurrentPoint( point );
698 
699  updateUnlockedConstraintValues( point );
700 
701  if ( res )
702  {
703  emit popWarning();
704  }
705  else
706  {
707  emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
708  }
709 
710  return res;
711 }
712 
713 
714 void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPointXY &point )
715 {
716  bool previousPointExist, penulPointExist;
717  QgsPointXY previousPt = previousPoint( &previousPointExist );
718  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
719 
720  // --- angle
721  if ( !mAngleConstraint->isLocked() && previousPointExist )
722  {
723  double angle = 0.0;
724  if ( penulPointExist && mAngleConstraint->relative() )
725  {
726  // previous angle
727  angle = std::atan2( previousPt.y() - penultimatePt.y(),
728  previousPt.x() - penultimatePt.x() );
729  }
730  angle = ( std::atan2( point.y() - previousPt.y(),
731  point.x() - previousPt.x()
732  ) - angle ) * 180 / M_PI;
733  // modulus
734  angle = std::fmod( angle, 360.0 );
735  mAngleConstraint->setValue( angle );
736  }
737  // --- distance
738  if ( !mDistanceConstraint->isLocked() && previousPointExist )
739  {
740  mDistanceConstraint->setValue( std::sqrt( previousPt.sqrDist( point ) ) );
741  }
742  // --- X
743  if ( !mXConstraint->isLocked() )
744  {
745  if ( previousPointExist && mXConstraint->relative() )
746  {
747  mXConstraint->setValue( point.x() - previousPt.x() );
748  }
749  else
750  {
751  mXConstraint->setValue( point.x() );
752  }
753  }
754  // --- Y
755  if ( !mYConstraint->isLocked() )
756  {
757  if ( previousPointExist && mYConstraint->relative() )
758  {
759  mYConstraint->setValue( point.y() - previousPt.y() );
760  }
761  else
762  {
763  mYConstraint->setValue( point.y() );
764  }
765  }
766 }
767 
768 
769 QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
770 {
771  QList<QgsPointXY> segment;
772  QgsPointXY pt1, pt2;
774 
775  QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
776 
777  QgsSnappingConfig canvasConfig = snappingUtils->config();
778  QgsSnappingConfig localConfig = snappingUtils->config();
779 
780  localConfig.setMode( QgsSnappingConfig::AllLayers );
781  localConfig.setType( QgsSnappingConfig::Segment );
782  snappingUtils->setConfig( localConfig );
783 
784  match = snappingUtils->snapToMap( originalMapPoint );
785 
786  snappingUtils->setConfig( canvasConfig );
787 
788  if ( match.isValid() && match.hasEdge() )
789  {
790  match.edgePoints( pt1, pt2 );
791  segment << pt1 << pt2;
792  }
793 
794  if ( snapped )
795  {
796  *snapped = segment.count() == 2;
797  }
798 
799  return segment;
800 }
801 
803 {
804  if ( mAdditionalConstraint == AdditionalConstraint::NoConstraint )
805  {
806  return false;
807  }
808 
809  bool previousPointExist, penulPointExist, snappedSegmentExist;
810  QgsPointXY previousPt = previousPoint( &previousPointExist );
811  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
812  mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
813 
814  if ( !previousPointExist || !snappedSegmentExist )
815  {
816  return false;
817  }
818 
819  double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
820 
821  if ( mAngleConstraint->relative() && penulPointExist )
822  {
823  angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
824  }
825 
826  if ( mAdditionalConstraint == AdditionalConstraint::Perpendicular )
827  {
828  angle += M_PI_2;
829  }
830 
831  angle *= 180 / M_PI;
832 
833  mAngleConstraint->setValue( angle );
834  mAngleConstraint->setLockMode( lockMode );
835  if ( lockMode == CadConstraint::HardLock )
836  {
837  mAdditionalConstraint = AdditionalConstraint::NoConstraint;
838  }
839 
840  return true;
841 }
842 
844 {
845  // event on map tool
846 
847  if ( !mCadEnabled )
848  return false;
849 
850  switch ( e->key() )
851  {
852  case Qt::Key_Backspace:
853  case Qt::Key_Delete:
854  {
856  releaseLocks( false );
857  break;
858  }
859  case Qt::Key_Escape:
860  {
861  releaseLocks();
862  break;
863  }
864  default:
865  {
866  keyPressEvent( e );
867  break;
868  }
869  }
870  // for map tools, continues with key press in any case
871  return false;
872 }
873 
875 {
876  clearPoints();
877  releaseLocks();
878 }
879 
881 {
882  // event on dock (this)
883 
884  if ( !mCadEnabled )
885  return;
886 
887  switch ( e->key() )
888  {
889  case Qt::Key_Backspace:
890  case Qt::Key_Delete:
891  {
893  releaseLocks( false );
894  break;
895  }
896  case Qt::Key_Escape:
897  {
898  releaseLocks();
899  break;
900  }
901  default:
902  {
903  filterKeyPress( e );
904  break;
905  }
906  }
907 }
908 
909 void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
910 {
911  clearPoints();
912  const auto constPoints = points;
913  for ( const QgsPointXY &pt : constPoints )
914  {
915  addPoint( pt );
916  }
917 }
918 
919 bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
920 {
921  if ( !cadEnabled() )
922  {
923  return QgsDockWidget::eventFilter( obj, event );
924  }
925 
926  // event for line edits and map canvas
927  // we have to catch both KeyPress events and ShortcutOverride events. This is because
928  // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
929  // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
930  // us to intercept these keystrokes before they are caught by the global shortcuts
931  if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
932  {
933  if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
934  {
935  return filterKeyPress( keyEvent );
936  }
937  }
938  return QgsDockWidget::eventFilter( obj, event );
939 }
940 
941 bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
942 {
943  // we need to be careful here -- because this method is called on both KeyPress events AND
944  // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
945  // these event types for a single key press. I.e. pressing "A" may first call trigger a
946  // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
947  QEvent::Type type = e->type();
948  switch ( e->key() )
949  {
950  case Qt::Key_X:
951  {
952  // modifier+x ONLY caught for ShortcutOverride events...
953  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
954  {
955  mXConstraint->toggleLocked();
956  emit lockXChanged( mXConstraint->isLocked() );
957  emit pointChanged( mCadPointList.value( 0 ) );
958  e->accept();
959  }
960  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
961  {
962  if ( mCapacities.testFlag( RelativeCoordinates ) )
963  {
964  mXConstraint->toggleRelative();
965  emit relativeXChanged( mXConstraint->relative() );
966  emit pointChanged( mCadPointList.value( 0 ) );
967  e->accept();
968  }
969  }
970  // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
971  else if ( type == QEvent::KeyPress )
972  {
973  mXLineEdit->setFocus();
974  mXLineEdit->selectAll();
975  emit focusOnXRequested();
976  e->accept();
977  }
978  break;
979  }
980  case Qt::Key_Y:
981  {
982  // modifier+y ONLY caught for ShortcutOverride events...
983  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
984  {
985  mYConstraint->toggleLocked();
986  emit lockYChanged( mYConstraint->isLocked() );
987  emit pointChanged( mCadPointList.value( 0 ) );
988  e->accept();
989  }
990  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
991  {
992  if ( mCapacities.testFlag( RelativeCoordinates ) )
993  {
994  mYConstraint->toggleRelative();
995  emit relativeYChanged( mYConstraint->relative() );
996  emit pointChanged( mCadPointList.value( 0 ) );
997  e->accept();
998  }
999  }
1000  // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1001  else if ( type == QEvent::KeyPress )
1002  {
1003  mYLineEdit->setFocus();
1004  mYLineEdit->selectAll();
1005  emit focusOnYRequested();
1006  e->accept();
1007  }
1008  break;
1009  }
1010  case Qt::Key_A:
1011  {
1012  // modifier+a ONLY caught for ShortcutOverride events...
1013  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1014  {
1015  if ( mCapacities.testFlag( AbsoluteAngle ) )
1016  {
1017  mAngleConstraint->toggleLocked();
1018  emit lockAngleChanged( mAngleConstraint->isLocked() );
1019  emit pointChanged( mCadPointList.value( 0 ) );
1020  e->accept();
1021  }
1022  }
1023  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1024  {
1025  if ( mCapacities.testFlag( RelativeAngle ) )
1026  {
1027  mAngleConstraint->toggleRelative();
1028  emit relativeAngleChanged( mAngleConstraint->relative() );
1029  emit pointChanged( mCadPointList.value( 0 ) );
1030  e->accept();
1031  }
1032  }
1033  // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
1034  else if ( type == QEvent::KeyPress )
1035  {
1036  mAngleLineEdit->setFocus();
1037  mAngleLineEdit->selectAll();
1038  emit focusOnAngleRequested();
1039  e->accept();
1040  }
1041  break;
1042  }
1043  case Qt::Key_D:
1044  {
1045  // modifier+d ONLY caught for ShortcutOverride events...
1046  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1047  {
1048  if ( mCapacities.testFlag( RelativeCoordinates ) )
1049  {
1050  mDistanceConstraint->toggleLocked();
1051  emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1052  emit pointChanged( mCadPointList.value( 0 ) );
1053  e->accept();
1054  }
1055  }
1056  // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
1057  else if ( type == QEvent::KeyPress )
1058  {
1059  mDistanceLineEdit->setFocus();
1060  mDistanceLineEdit->selectAll();
1061  emit focusOnDistanceRequested();
1062  e->accept();
1063  }
1064  break;
1065  }
1066  case Qt::Key_C:
1067  {
1068  if ( type == QEvent::KeyPress )
1069  {
1070  setConstructionMode( !mConstructionMode );
1071  e->accept();
1072  }
1073  break;
1074  }
1075  case Qt::Key_P:
1076  {
1077  if ( type == QEvent::KeyPress )
1078  {
1079  bool parallel = mParallelAction->isChecked();
1080  bool perpendicular = mPerpendicularAction->isChecked();
1081 
1082  if ( !parallel && !perpendicular )
1083  {
1084  lockAdditionalConstraint( AdditionalConstraint::Perpendicular );
1085  }
1086  else if ( perpendicular )
1087  {
1088  lockAdditionalConstraint( AdditionalConstraint::Parallel );
1089  }
1090  else
1091  {
1092  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
1093  }
1094  e->accept();
1095  }
1096  break;
1097  }
1098  default:
1099  {
1100  return false; // continues
1101  }
1102  }
1103  return e->isAccepted();
1104 }
1105 
1107 {
1108  connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
1109  if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
1110  {
1111  mErrorLabel->setText( tr( "CAD tools can not be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1112  mErrorLabel->show();
1113  mEnableAction->setEnabled( false );
1114  setCadEnabled( false );
1115  }
1116  else
1117  {
1118  mEnableAction->setEnabled( true );
1119  mErrorLabel->hide();
1120  mCadWidget->show();
1121 
1122  mCurrentMapToolSupportsCad = true;
1123 
1124  if ( mSessionActive && !isVisible() )
1125  {
1126  show();
1127  }
1128  setCadEnabled( mSessionActive );
1129  }
1130 }
1131 
1133 {
1135 
1136  mEnableAction->setEnabled( false );
1137  mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
1138  mErrorLabel->show();
1139  mCadWidget->hide();
1140 
1141  mCurrentMapToolSupportsCad = false;
1142 
1143  setCadEnabled( false );
1144 }
1145 
1147 {
1148  mCadPaintItem->update();
1149 }
1150 
1152 {
1153  if ( !pointsCount() )
1154  {
1155  mCadPointList << point;
1156  }
1157  else
1158  {
1159  mCadPointList.insert( 0, point );
1160  }
1161 
1162  updateCapacity();
1164 }
1165 
1167 {
1168  if ( !pointsCount() )
1169  return;
1170 
1171  int i = pointsCount() > 1 ? 1 : 0;
1172  mCadPointList.removeAt( i );
1173  updateCapacity();
1175 }
1176 
1178 {
1179  mCadPointList.clear();
1180  mSnappedSegment.clear();
1181 
1182  updateCapacity();
1184 }
1185 
1186 void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPointXY &point )
1187 {
1188  if ( !pointsCount() )
1189  {
1190  mCadPointList << point;
1191  updateCapacity();
1192  }
1193  else
1194  {
1195  mCadPointList[0] = point;
1196  }
1198 }
1199 
1200 
1202 {
1203  mLockMode = mode;
1204  mLockerButton->setChecked( mode == HardLock );
1205  if ( mRepeatingLockButton )
1206  {
1207  if ( mode == HardLock )
1208  {
1209  mRepeatingLockButton->setEnabled( true );
1210  }
1211  else
1212  {
1213  mRepeatingLockButton->setChecked( false );
1214  mRepeatingLockButton->setEnabled( false );
1215  }
1216  }
1217 
1218  if ( mode == NoLock )
1219  {
1220  mLineEdit->clear();
1221  }
1222 
1223 }
1224 
1226 {
1227  mRepeatingLock = repeating;
1228  if ( mRepeatingLockButton )
1229  mRepeatingLockButton->setChecked( repeating );
1230 }
1231 
1233 {
1234  mRelative = relative;
1235  if ( mRelativeButton )
1236  {
1237  mRelativeButton->setChecked( relative );
1238  }
1239 }
1240 
1241 void QgsAdvancedDigitizingDockWidget::CadConstraint::setValue( double value, bool updateWidget )
1242 {
1243  mValue = value;
1244  if ( updateWidget )
1245  mLineEdit->setText( QLocale().toString( value, 'f', 6 ) );
1246 }
1247 
1249 {
1250  setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1251 }
1252 
1254 {
1255  setRelative( !mRelative );
1256 }
1257 
1259 {
1260  if ( exist )
1261  *exist = pointsCount() > 0;
1262  if ( pointsCount() > 0 )
1263  return mCadPointList.value( 0 );
1264  else
1265  return QgsPointXY();
1266 }
1267 
1269 {
1270  if ( exist )
1271  *exist = pointsCount() > 1;
1272  if ( pointsCount() > 1 )
1273  return mCadPointList.value( 1 );
1274  else
1275  return QgsPointXY();
1276 }
1277 
1279 {
1280  if ( exist )
1281  *exist = pointsCount() > 2;
1282  if ( pointsCount() > 2 )
1283  return mCadPointList.value( 2 );
1284  else
1285  return QgsPointXY();
1286 }
void enabledChangedAngle(bool enabled)
Emitted whenever the angle field is enabled or disabled.
int pointsCount() const
The number of points in the CAD point helper list.
This corresponds to distance and relative coordinates.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
The CadConstraint is an abstract class for all basic constraints (angle/distance/x/y).
void setAngle(const QString &value, WidgetSetMode mode)
Set the angle value on the widget.
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.
void valueAngleChanged(const QString &value)
Emitted whenever the angle value changes (either the mouse moved, or the user changed the input)...
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QgsPointXY previousPoint(bool *exists=nullptr) const
The previous point.
bool enabled() const
Returns if snapping is enabled.
void relativeAngleChanged(bool relative)
Emitted whenever the angleX parameter is toggled between absolute and relative.
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
void focusOnYRequested()
Emitted whenever the Y field should get the focus using the shortcuts (Y).
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:97
QVariant evaluate()
Evaluate the feature and return the result.
void lockYChanged(bool locked)
Emitted whenever the Y parameter is locked.
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
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 lockAngleChanged(bool locked)
Emitted whenever the angle parameter is locked.
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:73
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.
bool active()
Whether the floater is active or not.
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.h:169
bool locked
Whether the constraint is active, i.e. should be considered.
Definition: qgscadutils.h:46
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
void setX(const QString &value, WidgetSetMode mode)
Set the X value on the widget.
void updateCadPaintItem()
Updates canvas item that displays constraints on the ma.
void enabledChangedDistance(bool enabled)
Emitted whenever the distance field is enabled or disabled.
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 valueYChanged(const QString &value)
Emitted whenever the Y value changes (either the mouse moved, or the user changed the input)...
void addPoint(const QgsPointXY &point)
Adds point to the CAD point list.
QgsPointXY penultimatePoint(bool *exists=nullptr) const
The penultimate point.
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.
void focusOnXRequested()
Emitted whenever the X field should get the focus using the shortcuts (X).
void setDistance(const QString &value, WidgetSetMode mode)
Set the distance value on the widget.
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.
void valueDistanceChanged(const QString &value)
Emitted whenever the distance 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 enable()
Enables the tool (call this when an appropriate map tool is set and in the condition to make use of c...
void lockDistanceChanged(bool locked)
Emitted whenever the distance parameter is locked.
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 softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition: qgscadutils.h:99
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 setY(const QString &value, WidgetSetMode mode)
Set the Y value on the widget.
void setMode(SnappingMode mode)
define the mode of snapping
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsPointXY originalMapPoint() const
Returns the original, unmodified map point of the mouse cursor.
The QgsAdvancedDigitizingFloater class is widget that floats next to the mouse pointer, and allow interaction with the AdvancedDigitizing feature.
void setActive(bool active)
Set whether the floater should be active or not.
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords...
bool relative() const
Is the constraint in relative mode.
void focusOnDistanceRequested()
Emitted whenever the distance field should get the focus using the shortcuts (D). ...
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
Definition: qgscadutils.h:62
QLineEdit * lineEdit() const
The line edit that manages the value of the constraint.
AdditionalConstraint
Additional constraints which can be enabled.
QgsSnappingConfig config
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
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:438
void setType(SnappingType type)
define the type of snapping
This class has all the configuration of snapping and can return answers to snapping queries...
WidgetSetMode
Type of interaction to simulate when editing values from external widget.
Class that shows snapping marker on map canvas for the current snapping match.
void pushWarning(const QString &message)
Push a warning.
void focusOnAngleRequested()
Emitted whenever the angle field should get the focus using the shortcuts (A).
void relativeYChanged(bool relative)
Emitted whenever the Y parameter is toggled between absolute and relative.
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be nullptr.
Definition: qgscadutils.h:57
void valueXChanged(const QString &value)
Emitted whenever the X value changes (either the mouse moved, or the user changed the input)...
void relativeXChanged(bool relative)
Emitted whenever the X parameter is toggled between absolute and relative.
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.
void lockXChanged(bool locked)
Emitted whenever the X parameter is locked.
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
Returns snapping utility class that is associated with map canvas.
QgsPointXY snapPoint()
snapPoint will snap the points using the map canvas snapping utils configuration
bool relative
Whether the value is relative to previous value.
Definition: qgscadutils.h:48
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
bool alignToSegment(QgsMapMouseEvent *e, QgsAdvancedDigitizingDockWidget::CadConstraint::LockMode lockMode=QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock)
align to segment for additional constraint.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:99
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (...
void removePreviousPoint()
Remove previous point in the CAD point list.
QList< QgsPointXY > cadPointList
List of recent CAD points in map coordinates.
Definition: qgscadutils.h:77