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