QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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
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 "qgscadutils.h"
26#include "qgsexpression.h"
27#include "qgsmapcanvas.h"
28#include "qgsmaptooledit.h"
30#include "qgsmessagebaritem.h"
31#include "qgsfocuswatcher.h"
32#include "qgssettings.h"
33#include "qgssnappingutils.h"
34#include "qgsproject.h"
35#include "qgsmapmouseevent.h"
36#include "qgsmeshlayer.h"
37
38#include <QActionGroup>
39
40
42 : QgsDockWidget( parent )
43 , mMapCanvas( canvas )
44 , mSnapIndicator( std::make_unique< QgsSnapIndicator>( canvas ) )
45 , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 0.0 ).toDouble() )
46{
47 setupUi( this );
48
49 mCadPaintItem = new QgsAdvancedDigitizingCanvasItem( canvas, this );
50
51 mAngleConstraint.reset( new CadConstraint( mAngleLineEdit, mLockAngleButton, mRelativeAngleButton, mRepeatingLockAngleButton ) );
52 mDistanceConstraint.reset( new CadConstraint( mDistanceLineEdit, mLockDistanceButton, nullptr, mRepeatingLockDistanceButton ) );
53 mXConstraint.reset( new CadConstraint( mXLineEdit, mLockXButton, mRelativeXButton, mRepeatingLockXButton ) );
54 mYConstraint.reset( new CadConstraint( mYLineEdit, mLockYButton, mRelativeYButton, mRepeatingLockYButton ) );
55 mZConstraint.reset( new CadConstraint( mZLineEdit, mLockZButton, mRelativeZButton, mRepeatingLockZButton ) );
56 mMConstraint.reset( new CadConstraint( mMLineEdit, mLockMButton, mRelativeMButton, mRepeatingLockMButton ) );
57
58 mLineExtensionConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
59 mXyVertexConstraint.reset( new CadConstraint( new QLineEdit(), new QToolButton() ) );
60
61 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
62
63 mMapCanvas->installEventFilter( this );
64 mAngleLineEdit->installEventFilter( this );
65 mDistanceLineEdit->installEventFilter( this );
66 mXLineEdit->installEventFilter( this );
67 mYLineEdit->installEventFilter( this );
68 mZLineEdit->installEventFilter( this );
69 mMLineEdit->installEventFilter( this );
70
71 // Connect the UI to the event filter to update constraints
72 connect( mEnableAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::activateCad );
73 connect( mConstructionModeAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode );
74 connect( mParallelAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
75 connect( mPerpendicularAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked );
76 connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
77 connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
78 connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
79 connect( mLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
80 connect( mLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
81 connect( mLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint );
82 connect( mRelativeAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
83 connect( mRelativeXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
84 connect( mRelativeYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
85 connect( mRelativeZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
86 connect( mRelativeMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRelative );
87 connect( mRepeatingLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
88 connect( mRepeatingLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
89 connect( mRepeatingLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
90 connect( mRepeatingLockYButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
91 connect( mRepeatingLockZButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
92 connect( mRepeatingLockMButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock );
93 connect( mAngleLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
94 connect( mDistanceLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
95 connect( mXLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
96 connect( mYLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
97 connect( mZLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
98 connect( mMLineEdit, &QLineEdit::returnPressed, this, [ = ]() { lockConstraint(); } );
99 connect( mAngleLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
100 connect( mDistanceLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
101 connect( mXLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
102 connect( mYLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
103 connect( mZLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
104 connect( mMLineEdit, &QLineEdit::textEdited, this, &QgsAdvancedDigitizingDockWidget::constraintTextEdited );
105 //also watch for focus out events on these widgets
106 QgsFocusWatcher *angleWatcher = new QgsFocusWatcher( mAngleLineEdit );
107 connect( angleWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
108 QgsFocusWatcher *distanceWatcher = new QgsFocusWatcher( mDistanceLineEdit );
109 connect( distanceWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
110 QgsFocusWatcher *xWatcher = new QgsFocusWatcher( mXLineEdit );
111 connect( xWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
112 QgsFocusWatcher *yWatcher = new QgsFocusWatcher( mYLineEdit );
113 connect( yWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
114 QgsFocusWatcher *zWatcher = new QgsFocusWatcher( mZLineEdit );
115 connect( zWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
116 QgsFocusWatcher *mWatcher = new QgsFocusWatcher( mMLineEdit );
117 connect( mWatcher, &QgsFocusWatcher::focusOut, this, &QgsAdvancedDigitizingDockWidget::constraintFocusOut );
118
119 // config menu
120 QMenu *menu = new QMenu( this );
121 // common angles
122 QActionGroup *angleButtonGroup = new QActionGroup( menu ); // actions are exclusive for common angles
123 mCommonAngleActions = QMap<QAction *, double>();
124 QList< QPair< double, QString > > commonAngles;
125 QString menuText;
126 const QList<double> anglesDouble( { 0.0, 5.0, 10.0, 15.0, 18.0, 22.5, 30.0, 45.0, 90.0} );
127 for ( QList<double>::const_iterator it = anglesDouble.constBegin(); it != anglesDouble.constEnd(); ++it )
128 {
129 if ( *it == 0 )
130 menuText = tr( "Do Not Snap to Common Angles" );
131 else
132 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 );
133 commonAngles << QPair<double, QString>( *it, menuText );
134 }
135 for ( QList< QPair<double, QString > >::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it )
136 {
137 QAction *action = new QAction( it->second, menu );
138 action->setCheckable( true );
139 action->setChecked( it->first == mCommonAngleConstraint );
140 menu->addAction( action );
141 angleButtonGroup->addAction( action );
142 mCommonAngleActions.insert( action, it->first );
143 }
144
145 qobject_cast< QToolButton *>( mToolbar->widgetForAction( mSettingsAction ) )->setPopupMode( QToolButton::InstantPopup );
146 mSettingsAction->setMenu( menu );
147 mSettingsAction->setCheckable( true );
148 mSettingsAction->setToolTip( tr( "Snap to common angles" ) );
149 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
150 connect( menu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
151
152 // Construction modes
153 QMenu *constructionMenu = new QMenu( this );
154
155 mLineExtensionAction = new QAction( "Line Extension", constructionMenu );
156 mLineExtensionAction->setCheckable( true );
157 constructionMenu->addAction( mLineExtensionAction );
158 connect( mLineExtensionAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
159
160 mXyVertexAction = new QAction( "X/Y Point", constructionMenu );
161 mXyVertexAction->setCheckable( true );
162 constructionMenu->addAction( mXyVertexAction );
163 connect( mXyVertexAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint );
164
165 auto constructionToolBar = qobject_cast< QToolButton *>( mToolbar->widgetForAction( mConstructionAction ) );
166 constructionToolBar->setPopupMode( QToolButton::InstantPopup );
167 constructionToolBar->setMenu( constructionMenu );
168 constructionToolBar->setObjectName( QStringLiteral( "ConstructionButton" ) );
169
170 mConstructionAction->setMenu( menu );
171 mConstructionAction->setCheckable( true );
172 mConstructionAction->setToolTip( tr( "Construction Tools" ) );
173// connect( constructionMenu, &QMenu::triggered, this, &QgsAdvancedDigitizingDockWidget::settingsButtonTriggered );
174
175 // set tooltips
176 mConstructionModeAction->setToolTip( "<b>" + tr( "Construction mode" ) + "</b><br>(" + tr( "press c to toggle on/off" ) + ")" );
177 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
178 mLockDistanceButton->setToolTip( "<b>" + tr( "Lock distance" ) + "</b><br>(" + tr( "press Ctrl + d for quick access" ) + ")" );
179 mRepeatingLockDistanceButton->setToolTip( "<b>" + tr( "Continuously lock distance" ) + "</b>" );
180
181 mRelativeAngleButton->setToolTip( "<b>" + tr( "Toggles relative angle to previous segment" ) + "</b><br>(" + tr( "press Shift + a for quick access" ) + ")" );
182 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
183 mLockAngleButton->setToolTip( "<b>" + tr( "Lock angle" ) + "</b><br>(" + tr( "press Ctrl + a for quick access" ) + ")" );
184 mRepeatingLockAngleButton->setToolTip( "<b>" + tr( "Continuously lock angle" ) + "</b>" );
185
186 mRelativeXButton->setToolTip( "<b>" + tr( "Toggles relative x to previous node" ) + "</b><br>(" + tr( "press Shift + x for quick access" ) + ")" );
187 mXLineEdit->setToolTip( "<b>" + tr( "X coordinate" ) + "</b><br>(" + tr( "press x for quick access" ) + ")" );
188 mLockXButton->setToolTip( "<b>" + tr( "Lock x coordinate" ) + "</b><br>(" + tr( "press Ctrl + x for quick access" ) + ")" );
189 mRepeatingLockXButton->setToolTip( "<b>" + tr( "Continuously lock x coordinate" ) + "</b>" );
190
191 mRelativeYButton->setToolTip( "<b>" + tr( "Toggles relative y to previous node" ) + "</b><br>(" + tr( "press Shift + y for quick access" ) + ")" );
192 mYLineEdit->setToolTip( "<b>" + tr( "Y coordinate" ) + "</b><br>(" + tr( "press y for quick access" ) + ")" );
193 mLockYButton->setToolTip( "<b>" + tr( "Lock y coordinate" ) + "</b><br>(" + tr( "press Ctrl + y for quick access" ) + ")" );
194 mRepeatingLockYButton->setToolTip( "<b>" + tr( "Continuously lock y coordinate" ) + "</b>" );
195
196 mRelativeZButton->setToolTip( "<b>" + tr( "Toggles relative z to previous node" ) + "</b><br>(" + tr( "press Shift + z for quick access" ) + ")" );
197 mZLineEdit->setToolTip( "<b>" + tr( "Z coordinate" ) + "</b><br>(" + tr( "press z for quick access" ) + ")" );
198 mLockZButton->setToolTip( "<b>" + tr( "Lock z coordinate" ) + "</b><br>(" + tr( "press Ctrl + z for quick access" ) + ")" );
199 mRepeatingLockZButton->setToolTip( "<b>" + tr( "Continuously lock z coordinate" ) + "</b>" );
200
201 mRelativeMButton->setToolTip( "<b>" + tr( "Toggles relative m to previous node" ) + "</b><br>(" + tr( "press Shift + m for quick access" ) + ")" );
202 mMLineEdit->setToolTip( "<b>" + tr( "M coordinate" ) + "</b><br>(" + tr( "press m for quick access" ) + ")" );
203 mLockMButton->setToolTip( "<b>" + tr( "Lock m coordinate" ) + "</b><br>(" + tr( "press Ctrl + m for quick access" ) + ")" );
204 mRepeatingLockMButton->setToolTip( "<b>" + tr( "Continuously lock m coordinate" ) + "</b>" );
205
206 // Create the slots/signals
207 connect( mXLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueXChanged );
208 connect( mYLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueYChanged );
209 connect( mZLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueZChanged );
210 connect( mMLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueMChanged );
211 connect( mDistanceLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueDistanceChanged );
212 connect( mAngleLineEdit, &QLineEdit::textChanged, this, &QgsAdvancedDigitizingDockWidget::valueAngleChanged );
213
214 // Create the floater
215 mFloater = new QgsAdvancedDigitizingFloater( canvas, this );
216 connect( mToggleFloaterAction, &QAction::triggered, mFloater, &QgsAdvancedDigitizingFloater::setActive );
217 mToggleFloaterAction->setChecked( mFloater->active() );
218
219 updateCapacity( true );
220 connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, [ = ] { updateCapacity( true ); } );
221
222 disable();
223}
224
225void QgsAdvancedDigitizingDockWidget::setX( const QString &value, WidgetSetMode mode )
226{
227 mXLineEdit->setText( value );
228 if ( mode == WidgetSetMode::ReturnPressed )
229 {
230 emit mXLineEdit->returnPressed();
231 }
232 else if ( mode == WidgetSetMode::FocusOut )
233 {
234 QEvent *e = new QEvent( QEvent::FocusOut );
235 QCoreApplication::postEvent( mXLineEdit, e );
236 }
237 else if ( mode == WidgetSetMode::TextEdited )
238 {
239 emit mXLineEdit->textEdited( value );
240 }
241}
242void QgsAdvancedDigitizingDockWidget::setY( const QString &value, WidgetSetMode mode )
243{
244 mYLineEdit->setText( value );
245 if ( mode == WidgetSetMode::ReturnPressed )
246 {
247 emit mYLineEdit->returnPressed();
248 }
249 else if ( mode == WidgetSetMode::FocusOut )
250 {
251 QEvent *e = new QEvent( QEvent::FocusOut );
252 QCoreApplication::postEvent( mYLineEdit, e );
253 }
254 else if ( mode == WidgetSetMode::TextEdited )
255 {
256 emit mYLineEdit->textEdited( value );
257 }
258}
259void QgsAdvancedDigitizingDockWidget::setZ( const QString &value, WidgetSetMode mode )
260{
261 mZLineEdit->setText( value );
262 if ( mode == WidgetSetMode::ReturnPressed )
263 {
264 emit mZLineEdit->returnPressed();
265 }
266 else if ( mode == WidgetSetMode::FocusOut )
267 {
268 QEvent *e = new QEvent( QEvent::FocusOut );
269 QCoreApplication::postEvent( mZLineEdit, e );
270 }
271 else if ( mode == WidgetSetMode::TextEdited )
272 {
273 emit mZLineEdit->textEdited( value );
274 }
275}
276void QgsAdvancedDigitizingDockWidget::setM( const QString &value, WidgetSetMode mode )
277{
278 mMLineEdit->setText( value );
279 if ( mode == WidgetSetMode::ReturnPressed )
280 {
281 emit mMLineEdit->returnPressed();
282 }
283 else if ( mode == WidgetSetMode::FocusOut )
284 {
285 QEvent *e = new QEvent( QEvent::FocusOut );
286 QCoreApplication::postEvent( mMLineEdit, e );
287 }
288 else if ( mode == WidgetSetMode::TextEdited )
289 {
290 emit mMLineEdit->textEdited( value );
291 }
292}
294{
295 mAngleLineEdit->setText( value );
296 if ( mode == WidgetSetMode::ReturnPressed )
297 {
298 emit mAngleLineEdit->returnPressed();
299 }
300 else if ( mode == WidgetSetMode::FocusOut )
301 {
302 emit mAngleLineEdit->textEdited( value );
303 }
304}
306{
307 mDistanceLineEdit->setText( value );
308 if ( mode == WidgetSetMode::ReturnPressed )
309 {
310 emit mDistanceLineEdit->returnPressed();
311 }
312 else if ( mode == WidgetSetMode::FocusOut )
313 {
314 QEvent *e = new QEvent( QEvent::FocusOut );
315 QCoreApplication::postEvent( mDistanceLineEdit, e );
316 }
317 else if ( mode == WidgetSetMode::TextEdited )
318 {
319 emit mDistanceLineEdit->textEdited( value );
320 }
321}
322
323
324void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled )
325{
326 mCadEnabled = enabled;
327 mEnableAction->setChecked( enabled );
328 mConstructionModeAction->setEnabled( enabled );
329 mSettingsAction->setEnabled( enabled );
330 mInputWidgets->setEnabled( enabled );
331 mToggleFloaterAction->setEnabled( enabled );
332 mConstructionAction->setEnabled( enabled );
333
334 if ( !enabled )
335 {
336 // uncheck at deactivation
337 mLineExtensionAction->setChecked( false );
338 mXyVertexAction->setChecked( false );
339 // will be reactivated in updateCapacities
340 mParallelAction->setEnabled( false );
341 mPerpendicularAction->setEnabled( false );
342 }
343
344
345 clear();
347 setConstructionMode( false );
348
349 switchZM();
350 emit cadEnabledChanged( enabled );
351
352 mLastSnapMatch = QgsPointLocator::Match();
353}
354
355
357{
358 bool enableZ = false;
359 bool enableM = false;
360
361 if ( QgsMapLayer *layer = targetLayer() )
362 {
363 switch ( layer->type() )
364 {
365 case Qgis::LayerType::Vector:
366 {
367 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
368 const Qgis::WkbType type = vlayer->wkbType();
369 enableZ = QgsWkbTypes::hasZ( type );
370 enableM = QgsWkbTypes::hasM( type );
371 break;
372 }
373
374 case Qgis::LayerType::Mesh:
375 {
376 QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( layer );
377 enableZ = mlayer->isEditable();
378 break;
379 }
380
381 case Qgis::LayerType::Raster:
382 case Qgis::LayerType::Plugin:
383 case Qgis::LayerType::VectorTile:
384 case Qgis::LayerType::Annotation:
385 case Qgis::LayerType::PointCloud:
386 case Qgis::LayerType::Group:
387 break;
388 }
389 }
390
391 setEnabledZ( enableZ );
392 setEnabledM( enableM );
393}
394
396{
397 mRelativeZButton->setEnabled( enable );
398 mZLabel->setEnabled( enable );
399 mZLineEdit->setEnabled( enable );
400 if ( mZLineEdit->isEnabled() )
401 mZLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultZValue(), 'f', 6 ) );
402 else
403 mZLineEdit->clear();
404 mLockZButton->setEnabled( enable );
405 emit enabledChangedZ( enable );
406}
407
409{
410 mRelativeMButton->setEnabled( enable );
411 mMLabel->setEnabled( enable );
412 mMLineEdit->setEnabled( enable );
413 if ( mMLineEdit->isEnabled() )
414 mMLineEdit->setText( QLocale().toString( QgsMapToolEdit::defaultMValue(), 'f', 6 ) );
415 else
416 mMLineEdit->clear();
417 mLockMButton->setEnabled( enable );
418 emit enabledChangedM( enable );
419}
420
421void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled )
422{
423 enabled &= mCurrentMapToolSupportsCad;
424
425 mSessionActive = enabled;
426
427 if ( enabled && !isVisible() )
428 {
429 show();
430 }
431
432 setCadEnabled( enabled );
433}
434
435void QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked( bool activated )
436{
437 if ( !activated )
438 {
439 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
440 }
441 else if ( sender() == mParallelAction )
442 {
443 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
444 }
445 else if ( sender() == mPerpendicularAction )
446 {
447 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
448 }
449}
450
451void QgsAdvancedDigitizingDockWidget::setConstraintRelative( bool activate )
452{
453 if ( sender() == mRelativeAngleButton )
454 {
455 mAngleConstraint->setRelative( activate );
456 emit relativeAngleChanged( activate );
457 }
458 else if ( sender() == mRelativeXButton )
459 {
460 mXConstraint->setRelative( activate );
461 emit relativeXChanged( activate );
462 }
463 else if ( sender() == mRelativeYButton )
464 {
465 mYConstraint->setRelative( activate );
466 emit relativeYChanged( activate );
467 }
468 else if ( sender() == mRelativeZButton )
469 {
470 mZConstraint->setRelative( activate );
471 emit relativeZChanged( activate );
472 }
473 else if ( sender() == mRelativeMButton )
474 {
475 mMConstraint->setRelative( activate );
476 emit relativeMChanged( activate );
477 }
478}
479
480void QgsAdvancedDigitizingDockWidget::setConstraintRepeatingLock( bool activate )
481{
482 if ( sender() == mRepeatingLockDistanceButton )
483 {
484 mDistanceConstraint->setRepeatingLock( activate );
485 }
486 else if ( sender() == mRepeatingLockAngleButton )
487 {
488 mAngleConstraint->setRepeatingLock( activate );
489 }
490 else if ( sender() == mRepeatingLockXButton )
491 {
492 mXConstraint->setRepeatingLock( activate );
493 }
494 else if ( sender() == mRepeatingLockYButton )
495 {
496 mYConstraint->setRepeatingLock( activate );
497 }
498 else if ( sender() == mRepeatingLockZButton )
499 {
500 mZConstraint->setRepeatingLock( activate );
501 }
502 else if ( sender() == mRepeatingLockMButton )
503 {
504 mMConstraint->setRepeatingLock( activate );
505 }
506}
507
508void QgsAdvancedDigitizingDockWidget::setConstructionMode( bool enabled )
509{
510 mConstructionMode = enabled;
511 mConstructionModeAction->setChecked( enabled );
512}
513
514void QgsAdvancedDigitizingDockWidget::settingsButtonTriggered( QAction *action )
515{
516 // common angles
517 const QMap<QAction *, double>::const_iterator ica = mCommonAngleActions.constFind( action );
518 if ( ica != mCommonAngleActions.constEnd() )
519 {
520 ica.key()->setChecked( true );
521 mCommonAngleConstraint = ica.value();
522 QgsSettings().setValue( QStringLiteral( "/Cad/CommonAngle" ), ica.value() );
523 mSettingsAction->setChecked( mCommonAngleConstraint != 0 );
524 return;
525 }
526}
527
528QgsMapLayer *QgsAdvancedDigitizingDockWidget::targetLayer() const
529{
530 if ( QgsMapToolAdvancedDigitizing *advancedTool = qobject_cast< QgsMapToolAdvancedDigitizing * >( mMapCanvas->mapTool() ) )
531 {
532 return advancedTool->layer();
533 }
534 else
535 {
536 return mMapCanvas->currentLayer();
537 }
538}
539
540void QgsAdvancedDigitizingDockWidget::releaseLocks( bool releaseRepeatingLocks )
541{
542 // release all locks except construction mode
543
544 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
545
546 if ( releaseRepeatingLocks )
547 {
548 mXyVertexAction->setChecked( false );
549 mXyVertexConstraint->setLockMode( CadConstraint::NoLock );
550 emit softLockXyChanged( false );
551
552 mLineExtensionAction->setChecked( false );
553 mLineExtensionConstraint->setLockMode( CadConstraint::NoLock );
554 emit softLockLineExtensionChanged( false );
555
557 }
558
559 if ( releaseRepeatingLocks || !mAngleConstraint->isRepeatingLock() )
560 {
561 mAngleConstraint->setLockMode( CadConstraint::NoLock );
562 emit lockAngleChanged( false );
563 }
564 if ( releaseRepeatingLocks || !mDistanceConstraint->isRepeatingLock() )
565 {
566 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
567 emit lockDistanceChanged( false );
568 }
569 if ( releaseRepeatingLocks || !mXConstraint->isRepeatingLock() )
570 {
571 mXConstraint->setLockMode( CadConstraint::NoLock );
572 emit lockXChanged( false );
573 }
574 if ( releaseRepeatingLocks || !mYConstraint->isRepeatingLock() )
575 {
576 mYConstraint->setLockMode( CadConstraint::NoLock );
577 emit lockYChanged( false );
578 }
579 if ( releaseRepeatingLocks || !mZConstraint->isRepeatingLock() )
580 {
581 mZConstraint->setLockMode( CadConstraint::NoLock );
582 emit lockZChanged( false );
583 }
584 if ( releaseRepeatingLocks || !mMConstraint->isRepeatingLock() )
585 {
586 mMConstraint->setLockMode( CadConstraint::NoLock );
587 emit lockMChanged( false );
588 }
589
590 if ( !mCadPointList.empty() )
591 {
592 if ( !mXConstraint->isLocked() && !mXConstraint->relative() )
593 {
594 mXConstraint->setValue( mCadPointList.constLast().x(), true );
595 }
596 if ( !mYConstraint->isLocked() && !mYConstraint->relative() )
597 {
598 mYConstraint->setValue( mCadPointList.constLast().y(), true );
599 }
600 if ( !mZConstraint->isLocked() && !mZConstraint->relative() )
601 {
602 mZConstraint->setValue( mCadPointList.constLast().z(), true );
603 }
604 if ( !mMConstraint->isLocked() && !mMConstraint->relative() )
605 {
606 mMConstraint->setValue( mCadPointList.constLast().m(), true );
607 }
608 }
609
610}
611
612#if 0
613void QgsAdvancedDigitizingDockWidget::emit pointChanged()
614{
615 // run a fake map mouse event to update the paint item
616 QPoint globalPos = mMapCanvas->cursor().pos();
617 QPoint pos = mMapCanvas->mapFromGlobal( globalPos );
618 QMouseEvent *e = new QMouseEvent( QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
619 mCurrentMapTool->canvasMoveEvent( e );
620}
621#endif
622
623QgsAdvancedDigitizingDockWidget::CadConstraint *QgsAdvancedDigitizingDockWidget::objectToConstraint( const QObject *obj ) const
624{
625 CadConstraint *constraint = nullptr;
626 if ( obj == mAngleLineEdit || obj == mLockAngleButton )
627 {
628 constraint = mAngleConstraint.get();
629 }
630 else if ( obj == mDistanceLineEdit || obj == mLockDistanceButton )
631 {
632 constraint = mDistanceConstraint.get();
633 }
634 else if ( obj == mXLineEdit || obj == mLockXButton )
635 {
636 constraint = mXConstraint.get();
637 }
638 else if ( obj == mYLineEdit || obj == mLockYButton )
639 {
640 constraint = mYConstraint.get();
641 }
642 else if ( obj == mZLineEdit || obj == mLockZButton )
643 {
644 constraint = mZConstraint.get();
645 }
646 else if ( obj == mMLineEdit || obj == mLockMButton )
647 {
648 constraint = mMConstraint.get();
649 }
650 else if ( obj == mLineExtensionAction )
651 {
652 constraint = mLineExtensionConstraint.get();
653 }
654 else if ( obj == mXyVertexAction )
655 {
656 constraint = mXyVertexConstraint.get();
657 }
658 return constraint;
659}
660
661double QgsAdvancedDigitizingDockWidget::parseUserInput( const QString &inputValue, bool &ok ) const
662{
663 ok = false;
664 double value = qgsPermissiveToDouble( inputValue, ok );
665 if ( ok )
666 {
667 return value;
668 }
669 else
670 {
671 // try to evaluate expression
672 QgsExpression expr( inputValue );
673 const QVariant result = expr.evaluate();
674 if ( expr.hasEvalError() )
675 {
676 ok = false;
677 QString inputValueC { inputValue };
678
679 // First: try removing group separator
680 if ( inputValue.contains( QLocale().groupSeparator() ) )
681 {
682 inputValueC.remove( QLocale().groupSeparator() );
683 QgsExpression exprC( inputValueC );
684 const QVariant resultC = exprC.evaluate();
685 if ( ! exprC.hasEvalError() )
686 {
687 value = resultC.toDouble( &ok );
688 }
689 }
690
691 // Second: be nice with non-dot locales
692 if ( !ok && QLocale().decimalPoint() != QChar( '.' ) && inputValueC.contains( QLocale().decimalPoint() ) )
693 {
694 QgsExpression exprC( inputValueC .replace( QLocale().decimalPoint(), QChar( '.' ) ) );
695 const QVariant resultC = exprC.evaluate();
696 if ( ! exprC.hasEvalError() )
697 {
698 value = resultC.toDouble( &ok );
699 }
700 }
701 }
702 else
703 {
704 value = result.toDouble( &ok );
705 }
706 return value;
707 }
708}
709
710void QgsAdvancedDigitizingDockWidget::updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression )
711{
712 if ( !constraint || textValue.isEmpty() )
713 {
714 return;
715 }
716
717 if ( constraint->lockMode() == CadConstraint::NoLock )
718 return;
719
720 bool ok;
721 const double value = parseUserInput( textValue, ok );
722 if ( !ok )
723 return;
724
725 constraint->setValue( value, convertExpression );
726 // run a fake map mouse event to update the paint item
727 emit pointChangedV2( mCadPointList.value( 0 ) );
728}
729
730void QgsAdvancedDigitizingDockWidget::lockConstraint( bool activate /* default true */ )
731{
732 CadConstraint *constraint = objectToConstraint( sender() );
733 if ( !constraint )
734 {
735 return;
736 }
737
738 if ( activate )
739 {
740 const QString textValue = constraint->lineEdit()->text();
741 if ( !textValue.isEmpty() )
742 {
743 bool ok;
744 const double value = parseUserInput( textValue, ok );
745 if ( ok )
746 {
747 constraint->setValue( value );
748 }
749 else
750 {
751 activate = false;
752 }
753 }
754 else
755 {
756 activate = false;
757 }
758 }
759 constraint->setLockMode( activate ? CadConstraint::HardLock : CadConstraint::NoLock );
760
761 if ( constraint == mXConstraint.get() )
762 {
763 emit lockXChanged( activate );
764 }
765 else if ( constraint == mYConstraint.get() )
766 {
767 emit lockYChanged( activate );
768 }
769 else if ( constraint == mZConstraint.get() )
770 {
771 emit lockZChanged( activate );
772 }
773 else if ( constraint == mMConstraint.get() )
774 {
775 emit lockMChanged( activate );
776 }
777 else if ( constraint == mDistanceConstraint.get() )
778 {
779 emit lockDistanceChanged( activate );
780 }
781 else if ( constraint == mAngleConstraint.get() )
782 {
783 emit lockAngleChanged( activate );
784 }
785
786 if ( activate )
787 {
788 // deactivate perpendicular/parallel if angle has been activated
789 if ( constraint == mAngleConstraint.get() )
790 {
791 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
792 }
793
794 // run a fake map mouse event to update the paint item
795 emit pointChangedV2( mCadPointList.value( 0 ) );
796 }
797}
798
799void QgsAdvancedDigitizingDockWidget::constraintTextEdited( const QString &textValue )
800{
801 CadConstraint *constraint = objectToConstraint( sender() );
802 if ( !constraint )
803 {
804 return;
805 }
806
807 updateConstraintValue( constraint, textValue, false );
808}
809
810void QgsAdvancedDigitizingDockWidget::constraintFocusOut()
811{
812 QLineEdit *lineEdit = qobject_cast< QLineEdit * >( sender()->parent() );
813 if ( !lineEdit )
814 return;
815
816 CadConstraint *constraint = objectToConstraint( lineEdit );
817 if ( !constraint )
818 {
819 return;
820 }
821
822 updateConstraintValue( constraint, lineEdit->text(), true );
823}
824
825void QgsAdvancedDigitizingDockWidget::lockBetweenLineConstraint( Qgis::BetweenLineConstraint constraint )
826{
827 mBetweenLineConstraint = constraint;
828 mPerpendicularAction->setChecked( constraint == Qgis::BetweenLineConstraint::Perpendicular );
829 mParallelAction->setChecked( constraint == Qgis::BetweenLineConstraint::Parallel );
830}
831
832void QgsAdvancedDigitizingDockWidget::lockParameterlessConstraint( bool activate /* default true */ )
833{
834 CadConstraint *constraint = objectToConstraint( sender() );
835 if ( !constraint )
836 {
837 return;
838 }
839
840 constraint->setLockMode( activate ? CadConstraint::SoftLock : CadConstraint::NoLock );
841
842 if ( constraint == mXyVertexConstraint.get() )
843 {
844 emit softLockXyChanged( activate );
845 }
846 else if ( constraint == mLineExtensionConstraint.get() )
847 {
848 emit softLockLineExtensionChanged( activate );
849 }
850
851 if ( activate )
852 {
853 // run a fake map mouse event to update the paint item
854 emit pointChangedV2( mCadPointList.value( 0 ) );
855 }
856
858}
859
860void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange )
861{
862 CadCapacities newCapacities = CadCapacities();
863 const bool isGeographic = mMapCanvas->mapSettings().destinationCrs().isGeographic();
864
865 // first point is the mouse point (it doesn't count)
866 if ( mCadPointList.count() > 1 )
867 {
868 newCapacities |= RelativeCoordinates;
869 if ( !isGeographic )
870 {
871 newCapacities |= AbsoluteAngle;
872 newCapacities |= Distance;
873 }
874 }
875 if ( mCadPointList.count() > 2 )
876 {
877 if ( !isGeographic )
878 newCapacities |= RelativeAngle;
879 }
880 if ( !updateUIwithoutChange && newCapacities == mCapacities )
881 {
882 return;
883 }
884
885 const bool snappingEnabled = QgsProject::instance()->snappingConfig().enabled();
886
887 // update the UI according to new capacities
888 // still keep the old to compare
889
890 const bool distance = mCadEnabled && newCapacities.testFlag( Distance );
891 const bool relativeAngle = mCadEnabled && newCapacities.testFlag( RelativeAngle );
892 const bool absoluteAngle = mCadEnabled && newCapacities.testFlag( AbsoluteAngle );
893 const bool relativeCoordinates = mCadEnabled && newCapacities.testFlag( RelativeCoordinates );
894
895 mPerpendicularAction->setEnabled( distance && snappingEnabled );
896 mParallelAction->setEnabled( distance && snappingEnabled );
897
898 mLineExtensionAction->setEnabled( snappingEnabled );
899 mXyVertexAction->setEnabled( snappingEnabled );
901
902 //update tooltips on buttons
903 if ( !snappingEnabled )
904 {
905 mPerpendicularAction->setToolTip( tr( "Snapping must be enabled to utilize perpendicular mode." ) );
906 mParallelAction->setToolTip( tr( "Snapping must be enabled to utilize parallel mode." ) );
907 mLineExtensionAction->setToolTip( tr( "Snapping must be enabled to utilize line extension mode." ) );
908 mXyVertexAction->setToolTip( tr( "Snapping must be enabled to utilize xy point mode." ) );
909 }
910 else if ( mCadPointList.count() <= 1 )
911 {
912 mPerpendicularAction->setToolTip( tr( "A first vertex should be drawn to utilize perpendicular mode." ) );
913 mParallelAction->setToolTip( tr( "A first vertex should be drawn to utilize parallel mode." ) );
914 }
915 else if ( isGeographic )
916 {
917 mPerpendicularAction->setToolTip( tr( "Perpendicular mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
918 mParallelAction->setToolTip( tr( "Parallel mode cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
919 }
920 else
921 {
922 mPerpendicularAction->setToolTip( "<b>" + tr( "Perpendicular" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
923 mParallelAction->setToolTip( "<b>" + tr( "Parallel" ) + "</b><br>(" + tr( "press p to switch between perpendicular, parallel and normal mode" ) + ")" );
924 }
925
926
927 if ( !absoluteAngle )
928 {
929 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
930 }
931
932 // absolute angle = azimuth, relative = from previous line
933 mLockAngleButton->setEnabled( absoluteAngle );
934 mRelativeAngleButton->setEnabled( relativeAngle );
935 mAngleLineEdit->setEnabled( absoluteAngle );
936 emit enabledChangedAngle( absoluteAngle );
937 if ( !absoluteAngle )
938 {
939 mAngleConstraint->setLockMode( CadConstraint::NoLock );
940 }
941 if ( !relativeAngle )
942 {
943 mAngleConstraint->setRelative( false );
944 emit relativeAngleChanged( false );
945 }
946 else if ( relativeAngle && !mCapacities.testFlag( RelativeAngle ) )
947 {
948 // set angle mode to relative if can do and wasn't available before
949 mAngleConstraint->setRelative( true );
950 emit relativeAngleChanged( true );
951 }
952
953 // distance is always relative
954 mLockDistanceButton->setEnabled( distance && relativeCoordinates );
955 mDistanceLineEdit->setEnabled( distance && relativeCoordinates );
956 emit enabledChangedDistance( distance && relativeCoordinates );
957 if ( !( distance && relativeCoordinates ) )
958 {
959 mDistanceConstraint->setLockMode( CadConstraint::NoLock );
960 }
961
962 mRelativeXButton->setEnabled( relativeCoordinates );
963 mRelativeYButton->setEnabled( relativeCoordinates );
964 mRelativeZButton->setEnabled( relativeCoordinates );
965 mRelativeMButton->setEnabled( relativeCoordinates );
966
967 // update capacities
968 mCapacities = newCapacities;
969 mCadPaintItem->updatePosition();
970}
971
972
974{
976 constr.locked = c->isLocked();
977 constr.relative = c->relative();
978 constr.value = c->value();
979 return constr;
980}
981
982void QgsAdvancedDigitizingDockWidget::toggleLockedSnapVertex( const QgsPointLocator::Match &snapMatch, QgsPointLocator::Match previouslySnap )
983{
984 // do nothing if not activated
985 if ( !mLineExtensionConstraint->isLocked() && !mXyVertexConstraint->isLocked() )
986 {
987 return;
988 }
989
990 // if the first is same actual, not toggle if previously snapped
991 const int lastIndex = mLockedSnapVertices.length() - 1;
992 for ( int i = lastIndex ; i >= 0; --i )
993 {
994 if ( mLockedSnapVertices[i].point() == snapMatch.point() )
995 {
996 if ( snapMatch.point() != previouslySnap.point() )
997 {
998 mLockedSnapVertices.removeAt( i );
999 }
1000 return;
1001 }
1002 }
1003
1004 if ( snapMatch.point() != previouslySnap.point() )
1005 {
1006 mLockedSnapVertices.enqueue( snapMatch );
1007 }
1008
1009 if ( mLockedSnapVertices.count() > 3 )
1010 {
1011 mLockedSnapVertices.dequeue();
1012 }
1013}
1014
1016{
1018 context.snappingUtils = mMapCanvas->snappingUtils();
1019 context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
1020 context.xConstraint = _constraint( mXConstraint.get() );
1021 context.yConstraint = _constraint( mYConstraint.get() );
1022 context.zConstraint = _constraint( mZConstraint.get() );
1023 context.mConstraint = _constraint( mMConstraint.get() );
1024 context.distanceConstraint = _constraint( mDistanceConstraint.get() );
1025 context.angleConstraint = _constraint( mAngleConstraint.get() );
1026
1027 context.lineExtensionConstraint = _constraint( mLineExtensionConstraint.get() );
1028 context.xyVertexConstraint = _constraint( mXyVertexConstraint.get() );
1029
1030 context.setCadPoints( mCadPointList );
1031 context.setLockedSnapVertices( mLockedSnapVertices );
1032
1035 context.commonAngleConstraint.value = mCommonAngleConstraint;
1036
1038
1039 const bool res = output.valid;
1040 QgsPoint point = pointXYToPoint( output.finalMapPoint );
1041 mSnappedSegment.clear();
1042 if ( output.snapMatch.hasEdge() )
1043 {
1044 QgsPointXY edgePt0, edgePt1;
1045 output.snapMatch.edgePoints( edgePt0, edgePt1 );
1046 mSnappedSegment << edgePt0 << edgePt1;
1047 }
1048 if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
1049 {
1050 if ( output.softLockCommonAngle != -1 )
1051 {
1052 mAngleConstraint->setLockMode( CadConstraint::SoftLock );
1053 mAngleConstraint->setValue( output.softLockCommonAngle );
1054 }
1055 else
1056 {
1057 mAngleConstraint->setLockMode( CadConstraint::NoLock );
1058 }
1059 }
1060
1061 mSoftLockLineExtension = output.softLockLineExtension;
1062 mSoftLockX = output.softLockX;
1063 mSoftLockY = output.softLockY;
1064
1065 if ( output.snapMatch.isValid() )
1066 {
1067 mSnapIndicator->setMatch( output.snapMatch );
1068 mSnapIndicator->setVisible( true );
1069 }
1070 else
1071 {
1072 mSnapIndicator->setVisible( false );
1073 }
1074
1075 /*
1076 * Ensure that Z and M are passed
1077 * It will be dropped as needed later.
1078 */
1081
1082 /*
1083 * Constraints are applied in 2D, they are always called when using the tool
1084 * but they do not take into account if when you snap on a vertex it has
1085 * a Z value.
1086 * To get the value we use the snapPoint method. However, we only apply it
1087 * when the snapped point corresponds to the constrained point or on an edge
1088 * if the topological editing is activated. Also, we don't apply it if
1089 * the point is not linked to a layer.
1090 */
1091 e->setMapPoint( point );
1092 mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
1093 if ( mSnapMatch.layer() )
1094 {
1095 if ( ( ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() ) && ( point == mSnapMatch.point() ) )
1096 || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
1097 {
1098 e->snapPoint();
1099 point = mSnapMatch.interpolatedPoint( mMapCanvas->mapSettings().destinationCrs() );
1100 }
1101 }
1102
1103 if ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() )
1104 {
1105 toggleLockedSnapVertex( mSnapMatch, mLastSnapMatch );
1106 mLastSnapMatch = mSnapMatch;
1107 }
1108 else
1109 {
1110 mLastSnapMatch = QgsPointLocator::Match();
1111 }
1112
1113 /*
1114 * And if M or Z lock button is activated get the value of the input.
1115 */
1116 if ( mLockZButton->isChecked() )
1117 {
1118 point.setZ( QLocale().toDouble( mZLineEdit->text() ) );
1119 }
1120 if ( mLockMButton->isChecked() )
1121 {
1122 point.setM( QLocale().toDouble( mMLineEdit->text() ) );
1123 }
1124
1125 // update the point list
1126 updateCurrentPoint( point );
1127
1128 updateUnlockedConstraintValues( point );
1129
1130 if ( res )
1131 {
1132 emit popWarning();
1133 }
1134 else
1135 {
1136 emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
1137 }
1138
1139 return res;
1140}
1141
1142
1143void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPoint &point )
1144{
1145 bool previousPointExist, penulPointExist;
1146 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1147 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1148
1149 // --- angle
1150 if ( !mAngleConstraint->isLocked() && previousPointExist )
1151 {
1152 double angle = 0.0;
1153 if ( penulPointExist && mAngleConstraint->relative() )
1154 {
1155 // previous angle
1156 angle = std::atan2( previousPt.y() - penultimatePt.y(),
1157 previousPt.x() - penultimatePt.x() );
1158 }
1159 angle = ( std::atan2( point.y() - previousPt.y(),
1160 point.x() - previousPt.x()
1161 ) - angle ) * 180 / M_PI;
1162 // modulus
1163 angle = std::fmod( angle, 360.0 );
1164 mAngleConstraint->setValue( angle );
1165 }
1166 // --- distance
1167 if ( !mDistanceConstraint->isLocked() && previousPointExist )
1168 {
1169 mDistanceConstraint->setValue( std::sqrt( previousPt.distanceSquared( point ) ) );
1170 }
1171 // --- X
1172 if ( !mXConstraint->isLocked() )
1173 {
1174 if ( previousPointExist && mXConstraint->relative() )
1175 {
1176 mXConstraint->setValue( point.x() - previousPt.x() );
1177 }
1178 else
1179 {
1180 mXConstraint->setValue( point.x() );
1181 }
1182 }
1183 // --- Y
1184 if ( !mYConstraint->isLocked() )
1185 {
1186 if ( previousPointExist && mYConstraint->relative() )
1187 {
1188 mYConstraint->setValue( point.y() - previousPt.y() );
1189 }
1190 else
1191 {
1192 mYConstraint->setValue( point.y() );
1193 }
1194 }
1195 // --- Z
1196 if ( !mZConstraint->isLocked() )
1197 {
1198 if ( previousPointExist && mZConstraint->relative() )
1199 {
1200 mZConstraint->setValue( point.z() - previousPt.z() );
1201 }
1202 else
1203 {
1204 mZConstraint->setValue( point.z() );
1205 }
1206 }
1207 // --- M
1208 if ( !mMConstraint->isLocked() )
1209 {
1210 if ( previousPointExist && mMConstraint->relative() )
1211 {
1212 mMConstraint->setValue( point.m() - previousPt.m() );
1213 }
1214 else
1215 {
1216 mMConstraint->setValue( point.m() );
1217 }
1218 }
1219}
1220
1221
1222QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
1223{
1224 QList<QgsPointXY> segment;
1225 QgsPointXY pt1, pt2;
1227
1228 QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
1229
1230 const QgsSnappingConfig canvasConfig = snappingUtils->config();
1231 QgsSnappingConfig localConfig = snappingUtils->config();
1232
1234 localConfig.setTypeFlag( Qgis::SnappingType::Segment );
1235 snappingUtils->setConfig( localConfig );
1236
1237 match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
1238
1239 snappingUtils->setConfig( canvasConfig );
1240
1241 if ( match.isValid() && match.hasEdge() )
1242 {
1243 match.edgePoints( pt1, pt2 );
1244 segment << pt1 << pt2;
1245 }
1246
1247 if ( snapped )
1248 {
1249 *snapped = segment.count() == 2;
1250 }
1251
1252 return segment;
1253}
1254
1256{
1257 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::NoConstraint )
1258 {
1259 return false;
1260 }
1261
1262 bool previousPointExist, penulPointExist, snappedSegmentExist;
1263 const QgsPoint previousPt = previousPointV2( &previousPointExist );
1264 const QgsPoint penultimatePt = penultimatePointV2( &penulPointExist );
1265 mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
1266
1267 if ( !previousPointExist || !snappedSegmentExist )
1268 {
1269 return false;
1270 }
1271
1272 double angle = std::atan2( mSnappedSegment[0].y() - mSnappedSegment[1].y(), mSnappedSegment[0].x() - mSnappedSegment[1].x() );
1273
1274 if ( mAngleConstraint->relative() && penulPointExist )
1275 {
1276 angle -= std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() );
1277 }
1278
1279 if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::Perpendicular )
1280 {
1281 angle += M_PI_2;
1282 }
1283
1284 angle *= 180 / M_PI;
1285
1286 mAngleConstraint->setValue( angle );
1287 mAngleConstraint->setLockMode( lockMode );
1288 if ( lockMode == CadConstraint::HardLock )
1289 {
1290 mBetweenLineConstraint = Qgis::BetweenLineConstraint::NoConstraint;
1291 }
1292
1293 return true;
1294}
1295
1297{
1298 // event on map tool
1299
1300 if ( !mCadEnabled )
1301 return false;
1302
1303 switch ( e->key() )
1304 {
1305 case Qt::Key_Backspace:
1306 case Qt::Key_Delete:
1307 {
1309 releaseLocks( false );
1310 break;
1311 }
1312 case Qt::Key_Escape:
1313 {
1314 releaseLocks();
1315 break;
1316 }
1317 default:
1318 {
1319 keyPressEvent( e );
1320 break;
1321 }
1322 }
1323 // for map tools, continues with key press in any case
1324 return false;
1325}
1326
1328{
1329 clearPoints();
1330 releaseLocks();
1331}
1332
1334{
1335 // event on dock (this)
1336
1337 if ( !mCadEnabled )
1338 return;
1339
1340 switch ( e->key() )
1341 {
1342 case Qt::Key_Backspace:
1343 case Qt::Key_Delete:
1344 {
1346 releaseLocks( false );
1347 break;
1348 }
1349 case Qt::Key_Escape:
1350 {
1351 releaseLocks();
1352 break;
1353 }
1354 default:
1355 {
1356 filterKeyPress( e );
1357 break;
1358 }
1359 }
1360}
1361
1362void QgsAdvancedDigitizingDockWidget::setPoints( const QList<QgsPointXY> &points )
1363{
1364 clearPoints();
1365 const auto constPoints = points;
1366 for ( const QgsPointXY &pt : constPoints )
1367 {
1368 addPoint( pt );
1369 }
1370}
1371
1372bool QgsAdvancedDigitizingDockWidget::eventFilter( QObject *obj, QEvent *event )
1373{
1374 if ( !cadEnabled() )
1375 {
1376 return QgsDockWidget::eventFilter( obj, event );
1377 }
1378
1379 // event for line edits and map canvas
1380 // we have to catch both KeyPress events and ShortcutOverride events. This is because
1381 // the Ctrl+D and Ctrl+A shortcuts for locking distance/angle clash with the global
1382 // "remove layer" and "select all" shortcuts. Catching ShortcutOverride events allows
1383 // us to intercept these keystrokes before they are caught by the global shortcuts
1384 if ( event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyPress )
1385 {
1386 if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
1387 {
1388 return filterKeyPress( keyEvent );
1389 }
1390 }
1391 return QgsDockWidget::eventFilter( obj, event );
1392}
1393
1394bool QgsAdvancedDigitizingDockWidget::filterKeyPress( QKeyEvent *e )
1395{
1396 // we need to be careful here -- because this method is called on both KeyPress events AND
1397 // ShortcutOverride events, we have to take care that we don't trigger the handling for BOTH
1398 // these event types for a single key press. I.e. pressing "A" may first call trigger a
1399 // ShortcutOverride event (sometimes, not always!) followed immediately by a KeyPress event.
1400 const QEvent::Type type = e->type();
1401 switch ( e->key() )
1402 {
1403 case Qt::Key_X:
1404 {
1405 // modifier+x ONLY caught for ShortcutOverride events...
1406 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1407 {
1408 mXConstraint->toggleLocked();
1409 emit lockXChanged( mXConstraint->isLocked() );
1410 emit pointChangedV2( mCadPointList.value( 0 ) );
1411 e->accept();
1412 }
1413 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1414 {
1415 if ( mCapacities.testFlag( RelativeCoordinates ) )
1416 {
1417 mXConstraint->toggleRelative();
1418 emit relativeXChanged( mXConstraint->relative() );
1419 emit pointChangedV2( mCadPointList.value( 0 ) );
1420 e->accept();
1421 }
1422 }
1423 // .. but "X" alone ONLY caught for KeyPress events (see comment at start of function)
1424 else if ( type == QEvent::KeyPress )
1425 {
1426 mXLineEdit->setFocus();
1427 mXLineEdit->selectAll();
1428 emit focusOnXRequested();
1429 e->accept();
1430 }
1431 break;
1432 }
1433 case Qt::Key_Y:
1434 {
1435 // modifier+y ONLY caught for ShortcutOverride events...
1436 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1437 {
1438 mYConstraint->toggleLocked();
1439 emit lockYChanged( mYConstraint->isLocked() );
1440 emit pointChangedV2( mCadPointList.value( 0 ) );
1441 e->accept();
1442 }
1443 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1444 {
1445 if ( mCapacities.testFlag( RelativeCoordinates ) )
1446 {
1447 mYConstraint->toggleRelative();
1448 emit relativeYChanged( mYConstraint->relative() );
1449 emit pointChangedV2( mCadPointList.value( 0 ) );
1450 e->accept();
1451 }
1452 }
1453 // .. but "y" alone ONLY caught for KeyPress events (see comment at start of function)
1454 else if ( type == QEvent::KeyPress )
1455 {
1456 mYLineEdit->setFocus();
1457 mYLineEdit->selectAll();
1458 emit focusOnYRequested();
1459 e->accept();
1460 }
1461 break;
1462 }
1463 case Qt::Key_Z:
1464 {
1465 // modifier+z ONLY caught for ShortcutOverride events...
1466 if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::AltModifier )
1467 {
1468 mZConstraint->toggleLocked();
1469 emit lockZChanged( mZConstraint->isLocked() );
1470 emit pointChangedV2( mCadPointList.value( 0 ) );
1471 e->accept();
1472 }
1473 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1474 {
1475 if ( mCapacities.testFlag( RelativeCoordinates ) )
1476 {
1477 mZConstraint->toggleRelative();
1478 emit relativeZChanged( mZConstraint->relative() );
1479 emit pointChangedV2( mCadPointList.value( 0 ) );
1480 e->accept();
1481 }
1482 }
1483 // .. but "z" alone ONLY caught for KeyPress events (see comment at start of function)
1484 else if ( type == QEvent::KeyPress )
1485 {
1486 mZLineEdit->setFocus();
1487 mZLineEdit->selectAll();
1488 emit focusOnZRequested();
1489 e->accept();
1490 }
1491 break;
1492 }
1493 case Qt::Key_M:
1494 {
1495 // modifier+m ONLY caught for ShortcutOverride events...
1496 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1497 {
1498 mMConstraint->toggleLocked();
1499 emit lockMChanged( mMConstraint->isLocked() );
1500 emit pointChangedV2( mCadPointList.value( 0 ) );
1501 e->accept();
1502 }
1503 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1504 {
1505 if ( mCapacities.testFlag( RelativeCoordinates ) )
1506 {
1507 mMConstraint->toggleRelative();
1508 emit relativeMChanged( mMConstraint->relative() );
1509 emit pointChangedV2( mCadPointList.value( 0 ) );
1510 e->accept();
1511 }
1512 }
1513 // .. but "m" alone ONLY caught for KeyPress events (see comment at start of function)
1514 else if ( type == QEvent::KeyPress )
1515 {
1516 mMLineEdit->setFocus();
1517 mMLineEdit->selectAll();
1518 emit focusOnMRequested();
1519 e->accept();
1520 }
1521 break;
1522 }
1523 case Qt::Key_A:
1524 {
1525 // modifier+a ONLY caught for ShortcutOverride events...
1526 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1527 {
1528 if ( mCapacities.testFlag( AbsoluteAngle ) )
1529 {
1530 mAngleConstraint->toggleLocked();
1531 emit lockAngleChanged( mAngleConstraint->isLocked() );
1532 emit pointChangedV2( mCadPointList.value( 0 ) );
1533 e->accept();
1534 }
1535 }
1536 else if ( type == QEvent::ShortcutOverride && e->modifiers() == Qt::ShiftModifier )
1537 {
1538 if ( mCapacities.testFlag( RelativeAngle ) )
1539 {
1540 mAngleConstraint->toggleRelative();
1541 emit relativeAngleChanged( mAngleConstraint->relative() );
1542 emit pointChangedV2( mCadPointList.value( 0 ) );
1543 e->accept();
1544 }
1545 }
1546 // .. but "a" alone ONLY caught for KeyPress events (see comment at start of function)
1547 else if ( type == QEvent::KeyPress )
1548 {
1549 mAngleLineEdit->setFocus();
1550 mAngleLineEdit->selectAll();
1551 emit focusOnAngleRequested();
1552 e->accept();
1553 }
1554 break;
1555 }
1556 case Qt::Key_D:
1557 {
1558 // modifier+d ONLY caught for ShortcutOverride events...
1559 if ( type == QEvent::ShortcutOverride && ( e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ControlModifier ) )
1560 {
1561 if ( mCapacities.testFlag( RelativeCoordinates ) && mCapacities.testFlag( Distance ) )
1562 {
1563 mDistanceConstraint->toggleLocked();
1564 emit lockDistanceChanged( mDistanceConstraint->isLocked() );
1565 emit pointChangedV2( mCadPointList.value( 0 ) );
1566 e->accept();
1567 }
1568 }
1569 // .. but "d" alone ONLY caught for KeyPress events (see comment at start of function)
1570 else if ( type == QEvent::KeyPress )
1571 {
1572 mDistanceLineEdit->setFocus();
1573 mDistanceLineEdit->selectAll();
1575 e->accept();
1576 }
1577 break;
1578 }
1579 case Qt::Key_C:
1580 {
1581 if ( type == QEvent::KeyPress )
1582 {
1583 setConstructionMode( !mConstructionMode );
1584 e->accept();
1585 }
1586 break;
1587 }
1588 case Qt::Key_P:
1589 {
1590 if ( type == QEvent::KeyPress )
1591 {
1592 const bool parallel = mParallelAction->isChecked();
1593 const bool perpendicular = mPerpendicularAction->isChecked();
1594
1595 if ( !parallel && !perpendicular )
1596 {
1597 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Perpendicular );
1598 }
1599 else if ( perpendicular )
1600 {
1601 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::Parallel );
1602 }
1603 else
1604 {
1605 lockBetweenLineConstraint( Qgis::BetweenLineConstraint::NoConstraint );
1606 }
1607 e->accept();
1608
1609 // run a fake map mouse event to update the paint item
1610 emit pointChangedV2( mCadPointList.value( 0 ) );
1611 }
1612 break;
1613 }
1614 default:
1615 {
1616 return false; // continues
1617 }
1618 }
1619 return e->isAccepted();
1620}
1621
1623{
1624 // most of theses lines can be moved to updateCapacity
1625 connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsAdvancedDigitizingDockWidget::enable, Qt::UniqueConnection );
1626 if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
1627 {
1628 mAngleLineEdit->setToolTip( tr( "Angle constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1629 mDistanceLineEdit->setToolTip( tr( "Distance constraint cannot be used on geographic coordinates. Change the coordinates system in the project properties." ) );
1630
1631 mLabelX->setText( tr( "Long" ) );
1632 mLabelY->setText( tr( "Lat" ) );
1633
1634 mXConstraint->setPrecision( 8 );
1635 mYConstraint->setPrecision( 8 );
1636 }
1637 else
1638 {
1639 mAngleLineEdit->setToolTip( "<b>" + tr( "Angle" ) + "</b><br>(" + tr( "press a for quick access" ) + ")" );
1640 mAngleLineEdit->setToolTip( QString() );
1641
1642 mDistanceLineEdit->setToolTip( "<b>" + tr( "Distance" ) + "</b><br>(" + tr( "press d for quick access" ) + ")" );
1643
1644 mLabelX->setText( tr( "x" ) );
1645 mLabelY->setText( tr( "y" ) );
1646
1647 mXConstraint->setPrecision( 6 );
1648 mYConstraint->setPrecision( 6 );
1649 }
1650
1651 updateCapacity();
1652
1653 mEnableAction->setEnabled( true );
1654 mErrorLabel->hide();
1655 mCadWidget->show();
1656
1657 mCurrentMapToolSupportsCad = true;
1658
1659 if ( mSessionActive && !isVisible() )
1660 {
1661 show();
1662 }
1663 setCadEnabled( mSessionActive );
1664}
1665
1667{
1669
1670 mEnableAction->setEnabled( false );
1671 mErrorLabel->setText( tr( "CAD tools are not enabled for the current map tool" ) );
1672 mErrorLabel->show();
1673 mCadWidget->hide();
1674
1675 mCurrentMapToolSupportsCad = false;
1676
1677 mSnapIndicator->setVisible( false );
1678
1679 setCadEnabled( false );
1680}
1681
1683{
1684 mCadPaintItem->update();
1685}
1686
1688{
1689 if ( !force && ( mLineExtensionConstraint->isLocked() || mXyVertexConstraint->isLocked() ) )
1690 {
1691 return;
1692 }
1693
1694 mLockedSnapVertices.clear();
1695}
1696
1697
1699{
1700 QgsPoint pt = pointXYToPoint( point );
1701 if ( !pointsCount() )
1702 {
1703 mCadPointList << pt;
1704 }
1705 else
1706 {
1707 mCadPointList.insert( 0, pt );
1708 }
1709
1710 updateCapacity();
1712}
1713
1715{
1716 if ( !pointsCount() )
1717 return;
1718
1719 const int i = pointsCount() > 1 ? 1 : 0;
1720 mCadPointList.removeAt( i );
1721 updateCapacity();
1723}
1724
1726{
1727 mCadPointList.clear();
1728 mSnappedSegment.clear();
1729
1730 updateCapacity();
1732}
1733
1734void QgsAdvancedDigitizingDockWidget::updateCurrentPoint( const QgsPoint &point )
1735{
1736 if ( !pointsCount() )
1737 {
1738 mCadPointList << point;
1739 updateCapacity();
1740 }
1741 else
1742 {
1743 mCadPointList[0] = point;
1744 }
1746}
1747
1748
1750{
1751 mLockMode = mode;
1752 mLockerButton->setChecked( mode == HardLock );
1753 if ( mRepeatingLockButton )
1754 {
1755 if ( mode == HardLock )
1756 {
1757 mRepeatingLockButton->setEnabled( true );
1758 }
1759 else
1760 {
1761 mRepeatingLockButton->setChecked( false );
1762 mRepeatingLockButton->setEnabled( false );
1763 }
1764 }
1765
1766 if ( mode == NoLock )
1767 {
1768 mLineEdit->clear();
1769 }
1770
1771}
1772
1774{
1775 mRepeatingLock = repeating;
1776 if ( mRepeatingLockButton )
1777 mRepeatingLockButton->setChecked( repeating );
1778}
1779
1781{
1782 mRelative = relative;
1783 if ( mRelativeButton )
1784 {
1785 mRelativeButton->setChecked( relative );
1786 }
1787}
1788
1790{
1791 mValue = value;
1792 if ( updateWidget && mLineEdit->isEnabled() )
1793 mLineEdit->setText( QLocale().toString( value, 'f', mPrecision ) );
1794}
1795
1797{
1798 setLockMode( mLockMode == HardLock ? NoLock : HardLock );
1799}
1800
1802{
1803 setRelative( !mRelative );
1804}
1805
1807{
1808 mPrecision = precision;
1809 if ( mLineEdit->isEnabled() )
1810 mLineEdit->setText( QLocale().toString( mValue, 'f', mPrecision ) );
1811}
1812
1814{
1815 if ( exist )
1816 *exist = pointsCount() > 0;
1817 if ( pointsCount() > 0 )
1818 return mCadPointList.value( 0 );
1819 else
1820 return QgsPoint();
1821}
1822
1824{
1825 if ( pointsCount() > 0 && layer )
1826 {
1827 QgsPoint res = mCadPointList.value( 0 );
1828 const QgsPointXY layerCoordinates = mMapCanvas->mapSettings().mapToLayerCoordinates( layer, res );
1829 res.setX( layerCoordinates.x() );
1830 res.setY( layerCoordinates.y() );
1831 return res;
1832 }
1833 return QgsPoint();
1834}
1835
1837{
1838 if ( exist )
1839 *exist = pointsCount() > 1;
1840 if ( pointsCount() > 1 )
1841 return mCadPointList.value( 1 );
1842 else
1843 return QgsPoint();
1844}
1845
1847{
1848 if ( exist )
1849 *exist = pointsCount() > 2;
1850 if ( pointsCount() > 2 )
1851 return mCadPointList.value( 2 );
1852 else
1853 return QgsPoint();
1854}
1855
1856QgsPoint QgsAdvancedDigitizingDockWidget::pointXYToPoint( const QgsPointXY &point ) const
1857{
1858 return QgsPoint( point.x(), point.y(), getLineZ(), getLineM() );
1859}
1860
1862{
1863 return mZLineEdit->isEnabled() ? QLocale().toDouble( mZLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
1864}
1865
1867{
1868 return mMLineEdit->isEnabled() ? QLocale().toDouble( mMLineEdit->text() ) : std::numeric_limits<double>::quiet_NaN();
1869}
@ AllLayers
On all vector layers.
BetweenLineConstraint
Between line constraints which can be enabled.
Definition: qgis.h:2354
@ NoConstraint
No additional constraint.
@ Perpendicular
Perpendicular.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:155
The QgsAdvancedDigitizingCanvasItem class draws the graphical elements of the CAD tools (.
void updatePosition() override
called on changed extent or resize event to update position of the item
The CadConstraint is an abstract class for all basic constraints (angle/distance/x/y).
void setPrecision(int precision)
Sets the numeric precision (decimal places) to show in the associated widget.
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 lockZChanged(bool locked)
Emitted whenever the Z parameter is locked.
void setEnabledZ(bool enable)
Sets whether Z is enabled.
void setPoints(const QList< QgsPointXY > &points)
Configures list of current CAD points.
void setZ(const QString &value, WidgetSetMode mode)
Set the Z value on the widget.
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.
void setEnabledM(bool enable)
Sets whether M is enabled.
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 switchZM()
Determines if Z or M will be enabled.
void relativeMChanged(bool relative)
Emitted whenever the M parameter is toggled between absolute and relative.
void lockXChanged(bool locked)
Emitted whenever the X parameter is locked.
void softLockLineExtensionChanged(bool locked)
Emitted whenever the soft line extension parameter is locked.
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).
double getLineM() const
Convenient method to get the M value from the line edit wiget.
void enabledChangedDistance(bool enabled)
Emitted whenever the distance field is enabled or disabled.
void valueZChanged(const QString &value)
Emitted whenever the Z value changes (either the mouse moved, or the user changed the input).
void clearPoints()
Removes all points from the CAD point list.
QgsPoint previousPointV2(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.
QgsPoint penultimatePointV2(bool *exists=nullptr) const
The penultimate point.
void pointChangedV2(const QgsPoint &point)
Sometimes a constraint may change the current point out of a mouse event.
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 focusOnZRequested()
Emitted whenever the Z field should get the focus using the shortcuts (Z).
void focusOnMRequested()
Emitted whenever the M field should get the focus using the shortcuts (M).
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).
double getLineZ() const
Convenient method to get the Z value from the line edit wiget.
void lockMChanged(bool locked)
Emitted whenever the M parameter is locked.
void cadEnabledChanged(bool enabled)
Signals for external widgets that need to update according to current values.
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 valueMChanged(const QString &value)
Emitted whenever the M 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...
void relativeZChanged(bool relative)
Emitted whenever the Z parameter is toggled between absolute and relative.
@ 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 enabledChangedZ(bool enabled)
Emitted whenever the Z 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 softLockXyChanged(bool locked)
Emitted whenever the soft x/y extension parameter is locked.
void enabledChangedM(bool enabled)
Emitted whenever the M field is enabled or disabled.
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 between line constraint.
void setX(const QString &value, WidgetSetMode mode)
Set the X value on the widget.
QgsPoint currentPointV2(bool *exists=nullptr) const
The last point.
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).
QgsPoint currentPointLayerCoordinates(QgsMapLayer *layer) const
Returns the last CAD point, in a map layer's coordinates.
void setM(const QString &value, WidgetSetMode mode)
Set the M value on the widget.
void pushWarning(const QString &message)
Push a warning.
void clearLockedSnapVertices(bool force=true)
Removes all points from the locked snap vertex list.
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.
Structure with details of one constraint.
Definition: qgscadutils.h:44
bool locked
Whether the constraint is active, i.e. should be considered.
Definition: qgscadutils.h:57
double value
Numeric value of the constraint (coordinate/distance in map units or angle in degrees)
Definition: qgscadutils.h:61
bool relative
Whether the value is relative to previous value.
Definition: qgscadutils.h:59
Defines constraints for the QgsCadUtils::alignMapPoint() method.
Definition: qgscadutils.h:104
QgsCadUtils::AlignMapPointConstraint xyVertexConstraint
Definition: qgscadutils.h:135
QgsCadUtils::AlignMapPointConstraint yConstraint
Constraint for Y coordinate.
Definition: qgscadutils.h:114
QgsCadUtils::AlignMapPointConstraint xConstraint
Constraint for X coordinate.
Definition: qgscadutils.h:112
double mapUnitsPerPixel
Map units/pixel ratio from map canvas.
Definition: qgscadutils.h:109
void setCadPoints(const QList< QgsPoint > &points)
Sets the list of recent CAD points (in map coordinates).
Definition: qgscadutils.h:160
void setLockedSnapVertices(const QQueue< QgsPointLocator::Match > &lockedSnapVertices)
Sets the queue of locked vertices.
Definition: qgscadutils.h:186
QgsCadUtils::AlignMapPointConstraint mConstraint
Constraint for M coordinate.
Definition: qgscadutils.h:126
QgsCadUtils::AlignMapPointConstraint distanceConstraint
Constraint for distance.
Definition: qgscadutils.h:128
QgsCadUtils::AlignMapPointConstraint zConstraint
Constraint for Z coordinate.
Definition: qgscadutils.h:120
QgsSnappingUtils * snappingUtils
Snapping utils that will be used to snap point to map. Must not be nullptr.
Definition: qgscadutils.h:107
QgsCadUtils::AlignMapPointConstraint commonAngleConstraint
Constraint for soft lock to a common angle.
Definition: qgscadutils.h:132
QgsCadUtils::AlignMapPointConstraint lineExtensionConstraint
Definition: qgscadutils.h:134
QgsCadUtils::AlignMapPointConstraint angleConstraint
Constraint for angle.
Definition: qgscadutils.h:130
Structure returned from alignMapPoint() method.
Definition: qgscadutils.h:70
Qgis::LineExtensionSide softLockLineExtension
Definition: qgscadutils.h:93
QgsPointXY finalMapPoint
map point aligned according to the constraints
Definition: qgscadutils.h:76
bool valid
Whether the combination of constraints is actually valid.
Definition: qgscadutils.h:73
QgsPointLocator::Match snapMatch
Snapped point - only valid if actually used for something.
Definition: qgscadutils.h:82
double softLockCommonAngle
Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Definition: qgscadutils.h:91
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:39
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:90
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
void destinationCrsChanged()
Emitted when map CRS has changed.
QgsMapTool * mapTool()
Returns the currently active tool.
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.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
Base class for all map layer types.
Definition: qgsmaplayer.h:73
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
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
The QgsMapToolAdvancedDigitizing class is a QgsMapTool which gives event directly in map coordinates ...
static double defaultMValue()
Returns default M value.
static double defaultZValue()
Returns default Z value.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:100
bool isEditable() const override
Returns true if the layer can be edited.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
void setX(double x) SIP_HOLDGIL
Sets the point's x-coordinate.
Definition: qgspoint.h:280
Q_GADGET double x
Definition: qgspoint.h:52
void setY(double y) SIP_HOLDGIL
Sets the point's y-coordinate.
Definition: qgspoint.h:291
void setZ(double z) SIP_HOLDGIL
Sets the point's z-coordinate.
Definition: qgspoint.h:304
double z
Definition: qgspoint.h:54
double distanceSquared(double x, double y) const SIP_HOLDGIL
Returns the Cartesian 2D squared distance between this point a specified x, y coordinate.
Definition: qgspoint.h:367
double m
Definition: qgspoint.h:55
double y
Definition: qgspoint.h:53
void setM(double m) SIP_HOLDGIL
Sets the point's m-value.
Definition: qgspoint.h:319
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:114
bool topologicalEditing
Definition: qgsproject.h:121
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Class that shows snapping marker on map canvas for the current snapping match.
This is a container for configuration of the snapping of the project.
void setTypeFlag(Qgis::SnappingTypes type)
define the type of snapping
void setMode(Qgis::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.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
static bool hasZ(Qgis::WkbType type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:977
static bool hasM(Qgis::WkbType type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1027
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)
int precision
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsPoint interpolatedPoint(const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem()) const
Convenient method to return a point on an edge with linear interpolation of the Z value.
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.