QGIS API Documentation  3.13.0-Master (5a3b1ced84)
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, nullptr, true );
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 or on an edge
691  * if the topological editing is activated.
692  */
693  if ( ( mSnapMatch.hasVertex() && ( point == mSnapMatch.point() ) ) || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
694  {
695  e->snapPoint();
696  }
697  // update the point list
698  updateCurrentPoint( point );
699 
700  updateUnlockedConstraintValues( point );
701 
702  if ( res )
703  {
704  emit popWarning();
705  }
706  else
707  {
708  emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
709  }
710 
711  return res;
712 }
713 
714 
715 void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPointXY &point )
716 {
717  bool previousPointExist, penulPointExist;
718  QgsPointXY previousPt = previousPoint( &previousPointExist );
719  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
720 
721  // --- angle
722  if ( !mAngleConstraint->isLocked() && previousPointExist )
723  {
724  double angle = 0.0;
725  if ( penulPointExist && mAngleConstraint->relative() )
726  {
727  // previous angle
728  angle = std::atan2( previousPt.y() - penultimatePt.y(),
729  previousPt.x() - penultimatePt.x() );
730  }
731  angle = ( std::atan2( point.y() - previousPt.y(),
732  point.x() - previousPt.x()
733  ) - angle ) * 180 / M_PI;
734  // modulus
735  angle = std::fmod( angle, 360.0 );
736  mAngleConstraint->setValue( angle );
737  }
738  // --- distance
739  if ( !mDistanceConstraint->isLocked() && previousPointExist )
740  {
741  mDistanceConstraint->setValue( std::sqrt( previousPt.sqrDist( point ) ) );
742  }
743  // --- X
744  if ( !mXConstraint->isLocked() )
745  {
746  if ( previousPointExist && mXConstraint->relative() )
747  {
748  mXConstraint->setValue( point.x() - previousPt.x() );
749  }
750  else
751  {
752  mXConstraint->setValue( point.x() );
753  }
754  }
755  // --- Y
756  if ( !mYConstraint->isLocked() )
757  {
758  if ( previousPointExist && mYConstraint->relative() )
759  {
760  mYConstraint->setValue( point.y() - previousPt.y() );
761  }
762  else
763  {
764  mYConstraint->setValue( point.y() );
765  }
766  }
767 }
768 
769 
770 QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
771 {
772  QList<QgsPointXY> segment;
773  QgsPointXY pt1, pt2;
775 
776  QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
777 
778  QgsSnappingConfig canvasConfig = snappingUtils->config();
779  QgsSnappingConfig localConfig = snappingUtils->config();
780 
781  localConfig.setMode( QgsSnappingConfig::AllLayers );
783  snappingUtils->setConfig( localConfig );
784 
785  match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
786 
787  snappingUtils->setConfig( canvasConfig );
788 
789  if ( match.isValid() && match.hasEdge() )
790  {
791  match.edgePoints( pt1, pt2 );
792  segment << pt1 << pt2;
793  }
794 
795  if ( snapped )
796  {
797  *snapped = segment.count() == 2;
798  }
799 
800  return segment;
801 }
802 
804 {
805  if ( mAdditionalConstraint == AdditionalConstraint::NoConstraint )
806  {
807  return false;
808  }
809 
810  bool previousPointExist, penulPointExist, snappedSegmentExist;
811  QgsPointXY previousPt = previousPoint( &previousPointExist );
812  QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
813  mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
814 
815  if ( !previousPointExist || !snappedSegmentExist )
816  {
817  return false;
818  }
819 
820  double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
821 
822  if ( mAngleConstraint->relative() && penulPointExist )
823  {
824  angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
825  }
826 
827  if ( mAdditionalConstraint == AdditionalConstraint::Perpendicular )
828  {
829  angle += M_PI_2;
830  }
831 
832  angle *= 180 / M_PI;
833 
834  mAngleConstraint->setValue( angle );
835  mAngleConstraint->setLockMode( lockMode );
836  if ( lockMode == CadConstraint::HardLock )
837  {
838  mAdditionalConstraint = AdditionalConstraint::NoConstraint;
839  }
840 
841  return true;
842 }
843 
845 {
846  // event on map tool
847 
848  if ( !mCadEnabled )
849  return false;
850 
851  switch ( e->key() )
852  {
853  case Qt::Key_Backspace:
854  case Qt::Key_Delete:
855  {
857  releaseLocks( false );
858  break;
859  }
860  case Qt::Key_Escape:
861  {
862  releaseLocks();
863  break;
864  }
865  default:
866  {
867  keyPressEvent( e );
868  break;
869  }
870  }
871  // for map tools, continues with key press in any case
872  return false;
873 }
874 
876 {
877  clearPoints();
878  releaseLocks();
879 }
880 
882 {
883  // event on dock (this)
884 
885  if ( !mCadEnabled )
886  return;
887 
888  switch ( e->key() )
889  {
890  case Qt::Key_Backspace:
891  case Qt::Key_Delete:
892  {
894  releaseLocks( false );
895  break;
896  }
897  case Qt::Key_Escape:
898  {
899  releaseLocks();
900  break;
901  }
902  default:
903  {
904  filterKeyPress( e );
905  break;
906  }
907  }
908 }
909 
910 void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
911 {
912  clearPoints();
913  const auto constPoints = points;
914  for ( const QgsPointXY &pt : constPoints )
915  {
916  addPoint( pt );
917  }
918 }
919 
920 bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
921 {
922  if ( !cadEnabled() )
923  {
924  return QgsDockWidget::eventFilter( obj, event );
925  }
926 
927  // event for line edits and map canvas
928  // we have to catch both KeyPress events and ShortcutOverride events. This is because
929  // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
930  // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
931  // us to intercept these keystrokes before they are caught by the global shortcuts
932  if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
933  {
934  if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
935  {
936  return filterKeyPress( keyEvent );
937  }
938  }
939  return QgsDockWidget::eventFilter( obj, event );
940 }
941 
942 bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
943 {
944  // we need to be careful here -- because this method is called on both KeyPress events AND
945  // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
946  // these event types for a single key press. I.e. pressing "A" may first call trigger a
947  // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
948  QEvent::Type type = e->type();
949  switch ( e->key() )
950  {
951  case Qt::Key_X:
952  {
953  // modifier+x ONLY caught for ShortcutOverride events...
954  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
955  {
956  mXConstraint->toggleLocked();
957  emit lockXChanged( mXConstraint->isLocked() );
958  emit pointChanged( mCadPointList.value( 0 ) );
959  e->accept();
960  }
961  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
962  {
963  if ( mCapacities.testFlag( RelativeCoordinates ) )
964  {
965  mXConstraint->toggleRelative();
966  emit relativeXChanged( mXConstraint->relative() );
967  emit pointChanged( mCadPointList.value( 0 ) );
968  e->accept();
969  }
970  }
971  // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
972  else if ( type == QEvent::KeyPress )
973  {
974  mXLineEdit->setFocus();
975  mXLineEdit->selectAll();
976  emit focusOnXRequested();
977  e->accept();
978  }
979  break;
980  }
981  case Qt::Key_Y:
982  {
983  // modifier+y ONLY caught for ShortcutOverride events...
984  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
985  {
986  mYConstraint->toggleLocked();
987  emit lockYChanged( mYConstraint->isLocked() );
988  emit pointChanged( mCadPointList.value( 0 ) );
989  e->accept();
990  }
991  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
992  {
993  if ( mCapacities.testFlag( RelativeCoordinates ) )
994  {
995  mYConstraint->toggleRelative();
996  emit relativeYChanged( mYConstraint->relative() );
997  emit pointChanged( mCadPointList.value( 0 ) );
998  e->accept();
999  }
1000  }
1001  // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1002  else if ( type == QEvent::KeyPress )
1003  {
1004  mYLineEdit->setFocus();
1005  mYLineEdit->selectAll();
1006  emit focusOnYRequested();
1007  e->accept();
1008  }
1009  break;
1010  }
1011  case Qt::Key_A:
1012  {
1013  // modifier+a ONLY caught for ShortcutOverride events...
1014  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1015  {
1016  if ( mCapacities.testFlag( AbsoluteAngle ) )
1017  {
1018  mAngleConstraint->toggleLocked();
1019  emit lockAngleChanged( mAngleConstraint->isLocked() );
1020  emit pointChanged( mCadPointList.value( 0 ) );
1021  e->accept();
1022  }
1023  }
1024  else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1025  {
1026  if ( mCapacities.testFlag( RelativeAngle ) )
1027  {
1028  mAngleConstraint->toggleRelative();
1029  emit relativeAngleChanged( mAngleConstraint->relative() );
1030  emit pointChanged( mCadPointList.value( 0 ) );
1031  e->accept();
1032  }
1033  }
1034  // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
1035  else if ( type == QEvent::KeyPress )
1036  {
1037  mAngleLineEdit->setFocus();
1038  mAngleLineEdit->selectAll();
1039  emit focusOnAngleRequested();
1040  e->accept();
1041  }
1042  break;
1043  }
1044  case Qt::Key_D:
1045  {
1046  // modifier+d ONLY caught for ShortcutOverride events...
1047  if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1048  {
1049  if ( mCapacities.testFlag( RelativeCoordinates ) )
1050  {
1051  mDistanceConstraint->toggleLocked();
1052  emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1053  emit pointChanged( mCadPointList.value( 0 ) );
1054  e->accept();
1055  }
1056  }
1057  // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
1058  else if ( type == QEvent::KeyPress )
1059  {
1060  mDistanceLineEdit->setFocus();
1061  mDistanceLineEdit->selectAll();
1062  emit focusOnDistanceRequested();
1063  e->accept();
1064  }
1065  break;
1066  }
1067  case Qt::Key_C:
1068  {
1069  if ( type == QEvent::KeyPress )
1070  {
1071  setConstructionMode( !mConstructionMode );
1072  e->accept();
1073  }
1074  break;
1075  }
1076  case Qt::Key_P:
1077  {
1078  if ( type == QEvent::KeyPress )
1079  {
1080  bool parallel = mParallelAction->isChecked();
1081  bool perpendicular = mPerpendicularAction->isChecked();
1082 
1083  if ( !parallel && !perpendicular )
1084  {
1085  lockAdditionalConstraint( AdditionalConstraint::Perpendicular );
1086  }
1087  else if ( perpendicular )
1088  {
1089  lockAdditionalConstraint( AdditionalConstraint::Parallel );
1090  }
1091  else
1092  {
1093  lockAdditionalConstraint( AdditionalConstraint::NoConstraint );
1094  }
1095  e->accept();
1096  }
1097  break;
1098  }
1099  default:
1100  {
1101  return false; // continues
1102  }
1103  }
1104  return e->isAccepted();
1105 }
1106 
1108 {
1109  connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
1110  if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
1111  {
1112  mErrorLabel->setText( tr( "CAD tools can not be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1113  mErrorLabel->show();
1114  mEnableAction->setEnabled( false );
1115  setCadEnabled( false );
1116  }
1117  else
1118  {
1119  mEnableAction->setEnabled( true );
1120  mErrorLabel->hide();
1121  mCadWidget->show();
1122 
1123  mCurrentMapToolSupportsCad = true;
1124 
1125  if ( mSessionActive && !isVisible() )
1126  {
1127  show();
1128  }
1129  setCadEnabled( mSessionActive );
1130  }
1131 }
1132 
1134 {
1136 
1137  mEnableAction->setEnabled( false );
1138  mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
1139  mErrorLabel->show();
1140  mCadWidget->hide();
1141 
1142  mCurrentMapToolSupportsCad = false;
1143 
1144  setCadEnabled( false );
1145 }
1146 
1148 {
1149  mCadPaintItem->update();
1150 }
1151 
1153 {
1154  if ( !pointsCount() )
1155  {
1156  mCadPointList << point;
1157  }
1158  else
1159  {
1160  mCadPointList.insert( 0, point );
1161  }
1162 
1163  updateCapacity();
1165 }
1166 
1168 {
1169  if ( !pointsCount() )
1170  return;
1171 
1172  int i = pointsCount() > 1 ? 1 : 0;
1173  mCadPointList.removeAt( i );
1174  updateCapacity();
1176 }
1177 
1179 {
1180  mCadPointList.clear();
1181  mSnappedSegment.clear();
1182 
1183  updateCapacity();
1185 }
1186 
1187 void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPointXY &point )
1188 {
1189  if ( !pointsCount() )
1190  {
1191  mCadPointList << point;
1192  updateCapacity();
1193  }
1194  else
1195  {
1196  mCadPointList[0] = point;
1197  }
1199 }
1200 
1201 
1203 {
1204  mLockMode = mode;
1205  mLockerButton->setChecked( mode == HardLock );
1206  if ( mRepeatingLockButton )
1207  {
1208  if ( mode == HardLock )
1209  {
1210  mRepeatingLockButton->setEnabled( true );
1211  }
1212  else
1213  {
1214  mRepeatingLockButton->setChecked( false );
1215  mRepeatingLockButton->setEnabled( false );
1216  }
1217  }
1218 
1219  if ( mode == NoLock )
1220  {
1221  mLineEdit->clear();
1222  }
1223 
1224 }
1225 
1227 {
1228  mRepeatingLock = repeating;
1229  if ( mRepeatingLockButton )
1230  mRepeatingLockButton->setChecked( repeating );
1231 }
1232 
1234 {
1235  mRelative = relative;
1236  if ( mRelativeButton )
1237  {
1238  mRelativeButton->setChecked( relative );
1239  }
1240 }
1241 
1242 void QgsAdvancedDigitizingDockWidget::CadConstraint::setValue( double value, bool updateWidget )
1243 {
1244  mValue = value;
1245  if ( updateWidget )
1246  mLineEdit->setText( QLocale().toString( value, 'f', 6 ) );
1247 }
1248 
1250 {
1251  setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1252 }
1253 
1255 {
1256  setRelative( !mRelative );
1257 }
1258 
1260 {
1261  if ( exist )
1262  *exist = pointsCount() > 0;
1263  if ( pointsCount() > 0 )
1264  return mCadPointList.value( 0 );
1265  else
1266  return QgsPointXY();
1267 }
1268 
1270 {
1271  if ( exist )
1272  *exist = pointsCount() > 1;
1273  if ( pointsCount() > 1 )
1274  return mCadPointList.value( 1 );
1275  else
1276  return QgsPointXY();
1277 }
1278 
1280 {
1281  if ( exist )
1282  *exist = pointsCount() > 2;
1283  if ( pointsCount() > 2 )
1284  return mCadPointList.value( 2 );
1285  else
1286  return QgsPointXY();
1287 }
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:61
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:65
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.
void setTypeFlag(QgsSnappingConfig::SnappingTypeFlag type)
define the type of snapping
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.
bool hasEdge() const
Returns true if the Match is an edge.
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:78
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:175
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
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Snap to map according to the current configuration.
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.
bool hasVertex() const
Returns true if the Match is a vertex.
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.
bool topologicalEditing() const
Convenience function to query topological editing status.
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:457
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.
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:102
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