diff --git a/images/images.qrc b/images/images.qrc index e1b482b7aec6..c28f77291fb7 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -693,6 +693,7 @@ themes/default/mActionPanHighlightFeature.svg themes/default/mActionPrevious.svg themes/default/mActionPlay.svg + themes/default/cadtools/circlesinteresection.svg themes/default/cadtools/construction.svg themes/default/cadtools/delta.svg themes/default/cadtools/floater.svg diff --git a/images/themes/default/cadtools/circlesinteresection.svg b/images/themes/default/cadtools/circlesinteresection.svg new file mode 100644 index 000000000000..3682daa54ca3 --- /dev/null +++ b/images/themes/default/cadtools/circlesinteresection.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/python/PyQt6/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in b/python/PyQt6/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in index 874873f94a2c..5218702e566a 100644 --- a/python/PyQt6/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in @@ -204,13 +204,14 @@ Removes unit suffix from the constraint text. }; - explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = 0 ); + explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = 0, QgsUserInputWidget *userInputWidget = 0 ); %Docstring Create an advanced digitizing dock widget :param canvas: The map canvas on which the widget operates :param parent: The parent %End + ~QgsAdvancedDigitizingDockWidget(); bool canvasKeyPressEventFilter( QKeyEvent *e ); %Docstring @@ -232,6 +233,24 @@ apply the CAD constraints. The will modify the position of the map event in map %Docstring align to segment for between line constraint. If between line constraints are used, this will determine the angle to be locked depending on the snapped segment. +%End + + bool processCanvasPressEvent( QgsMapMouseEvent *e ); + bool processCanvasMoveEvent( QgsMapMouseEvent *e ); + bool processCanvasReleaseEvent( QgsMapMouseEvent *e ); + + void setTool( QgsAdvancedDigitizingTool *tool ); +%Docstring +Sets an advanced digitizing tool which will take over digitizing until the tool is close. + +.. versionadded:: 3.40 +%End + + QgsAdvancedDigitizingTool *tool() const; +%Docstring +Returns the current advanced digitizing tool. Returns ``None`` if not set. + +.. versionadded:: 3.40 %End void releaseLocks( bool releaseRepeatingLocks = true ); diff --git a/python/PyQt6/gui/auto_generated/qgsadvanceddigitizingtools.sip.in b/python/PyQt6/gui/auto_generated/qgsadvanceddigitizingtools.sip.in new file mode 100644 index 000000000000..c3511108cb71 --- /dev/null +++ b/python/PyQt6/gui/auto_generated/qgsadvanceddigitizingtools.sip.in @@ -0,0 +1,84 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsadvanceddigitizingtools.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsAdvancedDigitizingTool : QWidget +{ +%Docstring(signature="appended") +An abstract class for advanced digitizing tools. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsadvanceddigitizingtools.h" +%End + public: + + explicit QgsAdvancedDigitizingTool( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget ); +%Docstring +The advanced digitizing tool constructor. + +:param canvas: The map canvas on which the widget operates +:param cadDockWidget: The cadDockWidget to which the floater belongs +%End + + QgsMapCanvas *mapCanvas() const; +%Docstring +Returns the map canvas associated with the tool. +%End + + QgsAdvancedDigitizingDockWidget *cadDockWidget() const; +%Docstring +Returns the advanced digitizing widget associated with the tool. +%End + + virtual void paint( QPainter *painter ); +%Docstring +Paints tool content onto the advanced digitizing canvas item. +%End + + virtual bool canvasPressEvent( QgsMapMouseEvent *event ); +%Docstring +Handles canvas press event. If ``True`` is returned, the tool will have +blocked the event for propagating. +%End + + virtual bool canvasMoveEvent( QgsMapMouseEvent *event ); +%Docstring +Handles canvas press move. If ``True`` is returned, the tool will have +blocked the event for propagating. +%End + + virtual bool canvasReleaseEvent( QgsMapMouseEvent *event ); +%Docstring +Handles canvas release event. If ``True`` is returned, the tool will have +blocked the event for propagating. +%End + + signals: + + void paintRequested(); +%Docstring +Requests a new painting event to the advanced digitizing canvas item. +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsadvanceddigitizingtools.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/PyQt6/gui/gui_auto.sip b/python/PyQt6/gui/gui_auto.sip index e6bf953cc433..4e3ac7799502 100644 --- a/python/PyQt6/gui/gui_auto.sip +++ b/python/PyQt6/gui/gui_auto.sip @@ -6,6 +6,7 @@ %Include auto_generated/qgsadvanceddigitizingcanvasitem.sip %Include auto_generated/qgsadvanceddigitizingdockwidget.sip %Include auto_generated/qgsadvanceddigitizingfloater.sip +%Include auto_generated/qgsadvanceddigitizingtools.sip %Include auto_generated/qgsaggregatetoolbutton.sip %Include auto_generated/qgsalignmentcombobox.sip %Include auto_generated/qgsapplicationexitblockerinterface.sip diff --git a/python/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in b/python/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in index bb6476e19a17..6d017af61d7a 100644 --- a/python/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in +++ b/python/gui/auto_generated/qgsadvanceddigitizingdockwidget.sip.in @@ -204,13 +204,14 @@ Removes unit suffix from the constraint text. }; - explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = 0 ); + explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = 0, QgsUserInputWidget *userInputWidget = 0 ); %Docstring Create an advanced digitizing dock widget :param canvas: The map canvas on which the widget operates :param parent: The parent %End + ~QgsAdvancedDigitizingDockWidget(); bool canvasKeyPressEventFilter( QKeyEvent *e ); %Docstring @@ -232,6 +233,24 @@ apply the CAD constraints. The will modify the position of the map event in map %Docstring align to segment for between line constraint. If between line constraints are used, this will determine the angle to be locked depending on the snapped segment. +%End + + bool processCanvasPressEvent( QgsMapMouseEvent *e ); + bool processCanvasMoveEvent( QgsMapMouseEvent *e ); + bool processCanvasReleaseEvent( QgsMapMouseEvent *e ); + + void setTool( QgsAdvancedDigitizingTool *tool ); +%Docstring +Sets an advanced digitizing tool which will take over digitizing until the tool is close. + +.. versionadded:: 3.40 +%End + + QgsAdvancedDigitizingTool *tool() const; +%Docstring +Returns the current advanced digitizing tool. Returns ``None`` if not set. + +.. versionadded:: 3.40 %End void releaseLocks( bool releaseRepeatingLocks = true ); diff --git a/python/gui/auto_generated/qgsadvanceddigitizingtools.sip.in b/python/gui/auto_generated/qgsadvanceddigitizingtools.sip.in new file mode 100644 index 000000000000..c3511108cb71 --- /dev/null +++ b/python/gui/auto_generated/qgsadvanceddigitizingtools.sip.in @@ -0,0 +1,84 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsadvanceddigitizingtools.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsAdvancedDigitizingTool : QWidget +{ +%Docstring(signature="appended") +An abstract class for advanced digitizing tools. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsadvanceddigitizingtools.h" +%End + public: + + explicit QgsAdvancedDigitizingTool( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget ); +%Docstring +The advanced digitizing tool constructor. + +:param canvas: The map canvas on which the widget operates +:param cadDockWidget: The cadDockWidget to which the floater belongs +%End + + QgsMapCanvas *mapCanvas() const; +%Docstring +Returns the map canvas associated with the tool. +%End + + QgsAdvancedDigitizingDockWidget *cadDockWidget() const; +%Docstring +Returns the advanced digitizing widget associated with the tool. +%End + + virtual void paint( QPainter *painter ); +%Docstring +Paints tool content onto the advanced digitizing canvas item. +%End + + virtual bool canvasPressEvent( QgsMapMouseEvent *event ); +%Docstring +Handles canvas press event. If ``True`` is returned, the tool will have +blocked the event for propagating. +%End + + virtual bool canvasMoveEvent( QgsMapMouseEvent *event ); +%Docstring +Handles canvas press move. If ``True`` is returned, the tool will have +blocked the event for propagating. +%End + + virtual bool canvasReleaseEvent( QgsMapMouseEvent *event ); +%Docstring +Handles canvas release event. If ``True`` is returned, the tool will have +blocked the event for propagating. +%End + + signals: + + void paintRequested(); +%Docstring +Requests a new painting event to the advanced digitizing canvas item. +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsadvanceddigitizingtools.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index e6bf953cc433..4e3ac7799502 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -6,6 +6,7 @@ %Include auto_generated/qgsadvanceddigitizingcanvasitem.sip %Include auto_generated/qgsadvanceddigitizingdockwidget.sip %Include auto_generated/qgsadvanceddigitizingfloater.sip +%Include auto_generated/qgsadvanceddigitizingtools.sip %Include auto_generated/qgsaggregatetoolbutton.sip %Include auto_generated/qgsalignmentcombobox.sip %Include auto_generated/qgsapplicationexitblockerinterface.sip diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index a1aaf005a221..8e130da74b55 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1181,7 +1181,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers // Advanced Digitizing dock startProfile( tr( "Advanced digitize panel" ) ); - mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this ); + mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this, mUserInputDockWidget ); mAdvancedDigitizingDockWidget->setWindowTitle( tr( "Advanced Digitizing" ) ); mAdvancedDigitizingDockWidget->setObjectName( QStringLiteral( "AdvancedDigitizingTools" ) ); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a9a7a64db345..22f88aaba2f4 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -512,6 +512,7 @@ set(QGIS_GUI_SRCS qgsadvanceddigitizingcanvasitem.cpp qgsadvanceddigitizingdockwidget.cpp qgsadvanceddigitizingfloater.cpp + qgsadvanceddigitizingtools.cpp qgsaggregatetoolbutton.cpp qgsalignmentcombobox.cpp qgsapplicationexitblockerinterface.cpp @@ -783,6 +784,7 @@ set(QGIS_GUI_HDRS qgsadvanceddigitizingcanvasitem.h qgsadvanceddigitizingdockwidget.h qgsadvanceddigitizingfloater.h + qgsadvanceddigitizingtools.h qgsaggregatetoolbutton.h qgsalignmentcombobox.h qgsapplicationexitblockerinterface.h diff --git a/src/gui/qgsadvanceddigitizingcanvasitem.cpp b/src/gui/qgsadvanceddigitizingcanvasitem.cpp index fcc9e0dc5c85..571601be1613 100644 --- a/src/gui/qgsadvanceddigitizingcanvasitem.cpp +++ b/src/gui/qgsadvanceddigitizingcanvasitem.cpp @@ -59,6 +59,13 @@ void QgsAdvancedDigitizingCanvasItem::paint( QPainter *painter ) } } + // Draw current intersection tool + if ( mAdvancedDigitizingDockWidget->tool() ) + { + mAdvancedDigitizingDockWidget->tool()->paint( painter ); + return; + } + // Use visible polygon rather than extent to properly handle rotated maps QPolygonF mapPoly = mMapCanvas->mapSettings().visiblePolygon(); const double canvasWidth = QLineF( mapPoly[0], mapPoly[1] ).length(); diff --git a/src/gui/qgsadvanceddigitizingdockwidget.cpp b/src/gui/qgsadvanceddigitizingdockwidget.cpp index 05db8214b083..99f4eb7cdbb2 100644 --- a/src/gui/qgsadvanceddigitizingdockwidget.cpp +++ b/src/gui/qgsadvanceddigitizingdockwidget.cpp @@ -38,6 +38,7 @@ #include "qgsunittypes.h" #include "qgssettingsentryimpl.h" #include "qgssettingstree.h" +#include "qgsuserinputwidget.h" #include @@ -48,9 +49,10 @@ const QgsSettingsEntryBool *QgsAdvancedDigitizingDockWidget::settingsCadShowCons const QgsSettingsEntryBool *QgsAdvancedDigitizingDockWidget::settingsCadSnapToConstructionGuides = new QgsSettingsEntryBool( QStringLiteral( "cad-snap-to-construction-guides" ), QgsSettingsTree::sTreeDigitizing, false, tr( "Determines if points will snap to construction guides." ) ) ; -QgsAdvancedDigitizingDockWidget::QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent ) +QgsAdvancedDigitizingDockWidget::QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent, QgsUserInputWidget *userInputWidget ) : QgsDockWidget( parent ) , mMapCanvas( canvas ) + , mUserInputWidget( userInputWidget ) , mSnapIndicator( std::make_unique< QgsSnapIndicator>( canvas ) ) , mCommonAngleConstraint( QgsSettings().value( QStringLiteral( "/Cad/CommonAngle" ), 0.0 ).toDouble() ) { @@ -96,6 +98,7 @@ QgsAdvancedDigitizingDockWidget::QgsAdvancedDigitizingDockWidget( QgsMapCanvas * connect( mConstructionModeAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::setConstructionMode ); connect( mParallelAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked ); connect( mPerpendicularAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked ); + connect( mCirclesIntersectionAction, &QAction::triggered, this, &QgsAdvancedDigitizingDockWidget::circlesIntersectionClicked ); connect( mLockAngleButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint ); connect( mLockDistanceButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint ); connect( mLockXButton, &QAbstractButton::clicked, this, &QgsAdvancedDigitizingDockWidget::lockConstraint ); @@ -183,7 +186,6 @@ QgsAdvancedDigitizingDockWidget::QgsAdvancedDigitizingDockWidget( QgsMapCanvas * mCommonAngleActionsMenu->addMenu( snappingPriorityMenu ); } - for ( QList< QPair >::const_iterator it = commonAngles.constBegin(); it != commonAngles.constEnd(); ++it ) { QAction *action = new QAction( it->second, mCommonAngleActionsMenu ); @@ -420,6 +422,14 @@ QgsAdvancedDigitizingDockWidget::QgsAdvancedDigitizingDockWidget( QgsMapCanvas * disable(); } +QgsAdvancedDigitizingDockWidget::~QgsAdvancedDigitizingDockWidget() +{ + if ( mCurrentTool ) + { + mCurrentTool->deleteLater(); + } +} + QString QgsAdvancedDigitizingDockWidget::formatCommonAngleSnapping( double angle ) { if ( angle == 0 ) @@ -536,6 +546,7 @@ void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled ) mInputWidgets->setEnabled( enabled ); mFloaterAction->setEnabled( enabled ); mConstructionAction->setEnabled( enabled ); + mCirclesIntersectionAction->setEnabled( enabled ); if ( !enabled ) { @@ -545,6 +556,10 @@ void QgsAdvancedDigitizingDockWidget::setCadEnabled( bool enabled ) // will be reactivated in updateCapacities mParallelAction->setEnabled( false ); mPerpendicularAction->setEnabled( false ); + if ( mCurrentTool ) + { + mCurrentTool->deleteLater(); + } } @@ -644,6 +659,44 @@ void QgsAdvancedDigitizingDockWidget::activateCad( bool enabled ) setCadEnabled( enabled ); } +void QgsAdvancedDigitizingDockWidget::setTool( QgsAdvancedDigitizingTool *tool ) +{ + if ( mCurrentTool ) + { + mCurrentTool->deleteLater(); + mCurrentTool = nullptr; + } + + mCurrentTool = tool; + + if ( mCurrentTool ) + { + mUserInputWidget->addUserInputWidget( mCurrentTool.data() ); + connect( mCurrentTool.data(), &QgsAdvancedDigitizingTool::paintRequested, this, &QgsAdvancedDigitizingDockWidget::updateCadPaintItem ); + } +} + +QgsAdvancedDigitizingTool *QgsAdvancedDigitizingDockWidget::tool() const +{ + return mCurrentTool.data(); +} + +void QgsAdvancedDigitizingDockWidget::circlesIntersectionClicked() +{ + if ( mCurrentTool && dynamic_cast( mCurrentTool.data() ) ) + { + setTool( nullptr ); + mCirclesIntersectionAction->setChecked( false ); + } + else + { + QgsAdvancedDigitizingCirclesIntersectionTool *circlesIntersectionTool = new QgsAdvancedDigitizingCirclesIntersectionTool( mMapCanvas, this ); + connect( circlesIntersectionTool, &QObject::destroyed, this, [ = ] { mCirclesIntersectionAction->setChecked( false ); } ); + setTool( circlesIntersectionTool ); + mCirclesIntersectionAction->setChecked( true ); + } +} + void QgsAdvancedDigitizingDockWidget::betweenLineConstraintClicked( bool activated ) { if ( !activated ) @@ -1492,6 +1545,70 @@ QList QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const return segment; } +bool QgsAdvancedDigitizingDockWidget::processCanvasPressEvent( QgsMapMouseEvent *e ) +{ + if ( mCurrentTool ) + { + mCurrentTool->canvasPressEvent( e ); + } + + return constructionMode(); +} + +bool QgsAdvancedDigitizingDockWidget::processCanvasMoveEvent( QgsMapMouseEvent *e ) +{ + // perpendicular/parallel constraint + // do a soft lock when snapping to a segment + alignToSegment( e, QgsAdvancedDigitizingDockWidget::CadConstraint::SoftLock ); + + if ( mCurrentTool ) + { + mCurrentTool->canvasMoveEvent( e ); + } + + updateCadPaintItem(); + + return false; +} + +bool QgsAdvancedDigitizingDockWidget::processCanvasReleaseEvent( QgsMapMouseEvent *e ) +{ + if ( alignToSegment( e ) ) + { + return true; + } + + if ( mCurrentTool ) + { + if ( mCurrentTool->canvasReleaseEvent( e ) ) + { + return true; + } + else + { + // update the point list + QgsPoint point( e->mapPoint() ); + point.setZ( QgsMapToolEdit::defaultZValue() ); + point.setM( QgsMapToolEdit::defaultMValue() ); + + if ( mLockZButton->isChecked() ) + { + point.setZ( QLocale().toDouble( mZLineEdit->text() ) ); + } + if ( mLockMButton->isChecked() ) + { + point.setM( QLocale().toDouble( mMLineEdit->text() ) ); + } + updateCurrentPoint( point ); + } + } + + addPoint( e->mapPoint() ); + releaseLocks( false ); + + return constructionMode(); +} + bool QgsAdvancedDigitizingDockWidget::alignToSegment( QgsMapMouseEvent *e, CadConstraint::LockMode lockMode ) { if ( mBetweenLineConstraint == Qgis::BetweenLineConstraint::NoConstraint ) @@ -1996,7 +2113,6 @@ void QgsAdvancedDigitizingDockWidget::clearLockedSnapVertices( bool force ) mLockedSnapVertices.clear(); } - void QgsAdvancedDigitizingDockWidget::addPoint( const QgsPointXY &point ) { QgsPoint pt = pointXYToPoint( point ); diff --git a/src/gui/qgsadvanceddigitizingdockwidget.h b/src/gui/qgsadvanceddigitizingdockwidget.h index 13d3d7ecc3ed..12cf84ea9221 100644 --- a/src/gui/qgsadvanceddigitizingdockwidget.h +++ b/src/gui/qgsadvanceddigitizingdockwidget.h @@ -24,6 +24,7 @@ #include "ui_qgsadvanceddigitizingdockwidgetbase.h" #include "qgis_gui.h" #include "qgis_sip.h" +#include "qgsadvanceddigitizingtools.h" #include "qgsdockwidget.h" #include "qgsmessagebaritem.h" #include "qgspointxy.h" @@ -33,10 +34,12 @@ class QgsAdvancedDigitizingCanvasItem; class QgsAdvancedDigitizingFloater; +class QgsAdvancedDigitizingTool; class QgsMapCanvas; class QgsMapTool; class QgsMapToolAdvancedDigitizing; class QgsMapMouseEvent; +class QgsUserInputWidget; /** * \ingroup gui @@ -249,7 +252,8 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private * \param canvas The map canvas on which the widget operates * \param parent The parent */ - explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = nullptr ); + explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = nullptr, QgsUserInputWidget *userInputWidget = nullptr ); + ~QgsAdvancedDigitizingDockWidget(); /** * Filter key events to e.g. toggle construction mode or adapt constraints @@ -271,6 +275,22 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private */ bool alignToSegment( QgsMapMouseEvent *e, QgsAdvancedDigitizingDockWidget::CadConstraint::LockMode lockMode = QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock ); + bool processCanvasPressEvent( QgsMapMouseEvent *e ); + bool processCanvasMoveEvent( QgsMapMouseEvent *e ); + bool processCanvasReleaseEvent( QgsMapMouseEvent *e ); + + /** + * Sets an advanced digitizing tool which will take over digitizing until the tool is close. + * \since QGIS 3.40 + */ + void setTool( QgsAdvancedDigitizingTool *tool ); + + /** + * Returns the current advanced digitizing tool. Returns NULLPTR if not set. + * \since QGIS 3.40 + */ + QgsAdvancedDigitizingTool *tool() const; + /** * unlock all constraints * \param releaseRepeatingLocks set to FALSE to preserve the lock for any constraints set to repeating lock mode @@ -946,6 +966,9 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private //! Sets the between line constraint by clicking on the perpendicular/parallel buttons void betweenLineConstraintClicked( bool activated ); + //! Activate the circles intersection tool + void circlesIntersectionClicked(); + //! lock/unlock a constraint and set its value void lockConstraint( bool activate = true ); @@ -1059,6 +1082,8 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private void updateConstructionGuidesCrs(); QgsMapCanvas *mMapCanvas = nullptr; + QgsUserInputWidget *mUserInputWidget = nullptr; + QgsAdvancedDigitizingCanvasItem *mCadPaintItem = nullptr; //! Snapping indicator std::unique_ptr mSnapIndicator; @@ -1124,6 +1149,9 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private double mSoftLockY; QQueue< QgsPointLocator::Match > mLockedSnapVertices; + // Advanced digitizing tool + QPointer mCurrentTool; + #ifdef SIP_RUN //! event filter for line edits in the dock UI (angle/distance/x/y line edits) bool eventFilter( QObject *obj, QEvent *event ); diff --git a/src/gui/qgsadvanceddigitizingtools.cpp b/src/gui/qgsadvanceddigitizingtools.cpp new file mode 100644 index 000000000000..23abb12ab125 --- /dev/null +++ b/src/gui/qgsadvanceddigitizingtools.cpp @@ -0,0 +1,224 @@ +/*************************************************************************** + qgsadvanceddigitizingintersectiontools.cpp + ---------------------- + begin : May 2024 + copyright : (C) Mathieu Pellerin + email : mathieu at opengis dot ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include "qgsadvanceddigitizingtools.h" +#include "qgsapplication.h" +#include "qgsdoublespinbox.h" +#include "qgsmapcanvas.h" + +QgsAdvancedDigitizingTool::QgsAdvancedDigitizingTool( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget ) + : QWidget( canvas->viewport() ) + , mMapCanvas( canvas ) + , mCadDockWidget( cadDockWidget ) +{ +} + +QgsAdvancedDigitizingCirclesIntersectionTool::QgsAdvancedDigitizingCirclesIntersectionTool( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget ) + : QgsAdvancedDigitizingTool( canvas, cadDockWidget ) +{ + QGridLayout *layout = new QGridLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + setLayout( layout ); + + QLabel *label = new QLabel( QStringLiteral( "Circle #1" ), this ); + layout->addWidget( label, 0, 0, 1, 3 ); + + mCircle1Digitize = new QToolButton( this ); + mCircle1Digitize->setCheckable( true ); + mCircle1Digitize->setChecked( false ); + mCircle1Digitize->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) ); + layout->addWidget( mCircle1Digitize, 1, 2, 2, 1 ); + + label = new QLabel( QStringLiteral( "X" ), this ); + layout->addWidget( label, 1, 0 ); + + mCircle1X = new QgsDoubleSpinBox( this ); + mCircle1X->setMinimum( std::numeric_limits::min() ); + mCircle1X->setMaximum( std::numeric_limits::max() ); + layout->addWidget( mCircle1X, 1, 1 ); + + label = new QLabel( QStringLiteral( "Y" ), this ); + layout->addWidget( label, 2, 0 ); + + mCircle1Y = new QgsDoubleSpinBox( this ); + mCircle1Y->setMinimum( std::numeric_limits::min() ); + mCircle1Y->setMaximum( std::numeric_limits::max() ); + layout->addWidget( mCircle1Y, 2, 1 ); + + label = new QLabel( QStringLiteral( "Distance" ), this ); + layout->addWidget( label, 3, 0 ); + + mCircle1Distance = new QgsDoubleSpinBox( this ); + mCircle1Distance->setMinimum( 0 ); + mCircle1Distance->setMaximum( std::numeric_limits::max() ); + layout->addWidget( mCircle1Distance, 3, 1 ); + + label = new QLabel( QStringLiteral( "Circle #2" ), this ); + layout->addWidget( label, 4, 0, 1, 3 ); + + mCircle2Digitize = new QToolButton( this ); + mCircle2Digitize->setCheckable( true ); + mCircle2Digitize->setChecked( false ); + mCircle2Digitize->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) ); + layout->addWidget( mCircle2Digitize, 5, 2, 2, 1 ); + + label = new QLabel( QStringLiteral( "X" ), this ); + layout->addWidget( label, 5, 0 ); + + mCircle2X = new QgsDoubleSpinBox( this ); + mCircle2X->setMinimum( std::numeric_limits::min() ); + mCircle2X->setMaximum( std::numeric_limits::max() ); + layout->addWidget( mCircle2X, 5, 1 ); + + label = new QLabel( QStringLiteral( "Y" ), this ); + layout->addWidget( label, 6, 0 ); + + mCircle2Y = new QgsDoubleSpinBox( this ); + mCircle2Y->setMinimum( std::numeric_limits::min() ); + mCircle2Y->setMaximum( std::numeric_limits::max() ); + layout->addWidget( mCircle2Y, 6, 1 ); + + label = new QLabel( QStringLiteral( "Distance" ), this ); + layout->addWidget( label, 7, 0 ); + + mCircle2Distance = new QgsDoubleSpinBox( this ); + mCircle2Distance->setMinimum( 0 ); + mCircle2Distance->setMaximum( std::numeric_limits::max() ); + layout->addWidget( mCircle2Distance, 7, 1 ); + + connect( mCircle1X, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double ) { processParameters(); } ); + connect( mCircle1Y, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double ) { processParameters(); } ); + connect( mCircle1Distance, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double ) { processParameters(); } ); + connect( mCircle2X, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double ) { processParameters(); } ); + connect( mCircle2Y, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double ) { processParameters(); } ); + connect( mCircle2Distance, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double ) { processParameters(); } ); +} + +void QgsAdvancedDigitizingCirclesIntersectionTool::processParameters() +{ + mP1 = QgsPointXY(); + mP2 = QgsPointXY(); + QgsGeometryUtils::circleCircleIntersections( QgsPointXY( mCircle1X->value(), mCircle1Y->value() ), mCircle1Distance->value(), + QgsPointXY( mCircle2X->value(), mCircle2Y->value() ), mCircle2Distance->value(), + mP1, mP2 ); + emit paintRequested(); +} + +bool QgsAdvancedDigitizingCirclesIntersectionTool::canvasMoveEvent( QgsMapMouseEvent *event ) +{ + if ( mCircle1Digitize->isChecked() ) + { + mCircle1X->setValue( event->mapPoint().x() ); + mCircle1Y->setValue( event->mapPoint().y() ); + } + else if ( mCircle2Digitize->isChecked() ) + { + mCircle2X->setValue( event->mapPoint().x() ); + mCircle2Y->setValue( event->mapPoint().y() ); + } + + if ( !mP1.isEmpty() ) + { + mP1Closest = QgsGeometryUtils::distance2D( QgsPoint( mP1 ), QgsPoint( event->mapPoint() ) ) < QgsGeometryUtils::distance2D( QgsPoint( mP2 ), QgsPoint( event->mapPoint() ) ); + } + + return true; +} + +bool QgsAdvancedDigitizingCirclesIntersectionTool::canvasReleaseEvent( QgsMapMouseEvent *event ) +{ + if ( mCircle1Digitize->isChecked() ) + { + mCircle1X->setValue( event->mapPoint().x() ); + mCircle1Y->setValue( event->mapPoint().y() ); + mCircle1Digitize->setChecked( false ); + return true; + } + else if ( mCircle2Digitize->isChecked() ) + { + mCircle2X->setValue( event->mapPoint().x() ); + mCircle2Y->setValue( event->mapPoint().y() ); + mCircle2Digitize->setChecked( false ); + return true; + } + + if ( !mP1.isEmpty() ) + { + mP1Closest = QgsGeometryUtils::distance2D( QgsPoint( mP1 ), QgsPoint( event->mapPoint() ) ) < QgsGeometryUtils::distance2D( QgsPoint( mP2 ), QgsPoint( event->mapPoint() ) ); + event->setMapPoint( mP1Closest ? mP1 : mP2 ); + deleteLater(); + return false; + } + + return true; +} + +void QgsAdvancedDigitizingCirclesIntersectionTool::drawCircle( QPainter *painter, double x, double y, double distance ) +{ + painter->setPen( QPen( QColor( 0, 127, 0, 200 ), 2 ) ); + painter->setBrush( Qt::NoBrush ); + + mapCanvas()->getCoordinateTransform()->transformInPlace( x, y ); + painter->drawLine( QLineF( x - 3, y - 3, x + 3, y + 3 ) ); + painter->drawLine( QLineF( x - 3, y + 3, x + 3, y - 3 ) ); + + painter->setPen( QPen( QColor( 0, 127, 0, 150 ), 1, Qt::DashLine ) ); + distance = distance / mapCanvas()->mapSettings().mapUnitsPerPixel(); + painter->drawEllipse( QRectF( x - distance, y - distance, distance * 2, distance * 2 ) ); +} + +void QgsAdvancedDigitizingCirclesIntersectionTool::drawCandidate( QPainter *painter, double x, double y, bool closest ) +{ + if ( closest ) + { + painter->setPen( QPen( QColor( 127, 0, 0, 222 ), 4 ) ); + } + else + { + painter->setPen( QPen( QColor( 0, 127, 0, 222 ), 2 ) ); + } + + mapCanvas()->getCoordinateTransform()->transformInPlace( x, y ); + double size = closest ? 5 : 3; + painter->drawRect( QRectF( x - size, y - size, size * 2, size * 2 ) ); +} + +void QgsAdvancedDigitizingCirclesIntersectionTool::paint( QPainter *painter ) +{ + painter->save(); + + drawCircle( painter, mCircle1X->value(), mCircle1Y->value(), mCircle1Distance->value() ); + drawCircle( painter, mCircle2X->value(), mCircle2Y->value(), mCircle2Distance->value() ); + + if ( !mP1.isEmpty() ) + { + if ( mP1Closest ) + { + drawCandidate( painter, mP2.x(), mP2.y(), false ); + drawCandidate( painter, mP1.x(), mP1.y(), true ); + } + else + { + drawCandidate( painter, mP1.x(), mP1.y(), false ); + drawCandidate( painter, mP2.x(), mP2.y(), true ); + } + } + + painter->restore(); +} diff --git a/src/gui/qgsadvanceddigitizingtools.h b/src/gui/qgsadvanceddigitizingtools.h new file mode 100644 index 000000000000..d535a4b6016c --- /dev/null +++ b/src/gui/qgsadvanceddigitizingtools.h @@ -0,0 +1,147 @@ +/*************************************************************************** + qgsadvanceddigitizingtools.h + ---------------------- + begin : May 2024 + copyright : (C) Mathieu Pellerin + email : mathieu at opengis dot ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSADVANCEDDIGITIZINGTOOLS +#define QGSADVANCEDDIGITIZINGTOOLS + +#include +#include + +#include "qgsadvanceddigitizingdockwidget.h" +#include "qgis_gui.h" +#include "qgis_sip.h" +#include "qgsmapmouseevent.h" + +#include + +class QgsAdvancedDigitizingDockWidget; +class QgsDoubleSpinBox; +class QgsMapCanvas; + +/** + * \ingroup gui + * \brief An abstract class for advanced digitizing tools. + * \since QGIS 3.40 + */ +class GUI_EXPORT QgsAdvancedDigitizingTool : public QWidget +{ + Q_OBJECT + + public: + + /** + * The advanced digitizing tool constructor. + * \param canvas The map canvas on which the widget operates + * \param cadDockWidget The cadDockWidget to which the floater belongs + */ + explicit QgsAdvancedDigitizingTool( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget ); + + /** + * Returns the map canvas associated with the tool. + */ + QgsMapCanvas *mapCanvas() const { return mMapCanvas; } + + /** + * Returns the advanced digitizing widget associated with the tool. + */ + QgsAdvancedDigitizingDockWidget *cadDockWidget() const { return mCadDockWidget.data(); } + + /** + * Paints tool content onto the advanced digitizing canvas item. + */ + virtual void paint( QPainter *painter ) { Q_UNUSED( painter ) } + + /** + * Handles canvas press event. If TRUE is returned, the tool will have + * blocked the event for propagating. + */ + virtual bool canvasPressEvent( QgsMapMouseEvent *event ) + { + Q_UNUSED( event ) + return true; + } + + /** + * Handles canvas press move. If TRUE is returned, the tool will have + * blocked the event for propagating. + */ + virtual bool canvasMoveEvent( QgsMapMouseEvent *event ) + { + Q_UNUSED( event ) + return true; + } + + /** + * Handles canvas release event. If TRUE is returned, the tool will have + * blocked the event for propagating. + */ + virtual bool canvasReleaseEvent( QgsMapMouseEvent *event ) + { + Q_UNUSED( event ) + return true; + } + + signals: + + /** + * Requests a new painting event to the advanced digitizing canvas item. + */ + void paintRequested(); + + private: + + QgsMapCanvas *mMapCanvas = nullptr; + QPointer< QgsAdvancedDigitizingDockWidget > mCadDockWidget; +}; + +#ifndef SIP_RUN + +class GUI_EXPORT QgsAdvancedDigitizingCirclesIntersectionTool : public QgsAdvancedDigitizingTool +{ + Q_OBJECT + + public: + + explicit QgsAdvancedDigitizingCirclesIntersectionTool( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget ); + + void paint( QPainter *painter ) override; + + bool canvasMoveEvent( QgsMapMouseEvent *event ) override; + bool canvasReleaseEvent( QgsMapMouseEvent *event ) override; + + private: + void processParameters(); + + void drawCircle( QPainter *painter, double x, double y, double distance ); + void drawCandidate( QPainter *painter, double x, double y, bool closest ); + + QToolButton *mCircle1Digitize = nullptr; + QgsDoubleSpinBox *mCircle1X = nullptr; + QgsDoubleSpinBox *mCircle1Y = nullptr; + QgsDoubleSpinBox *mCircle1Distance = nullptr; + + QToolButton *mCircle2Digitize = nullptr; + QgsDoubleSpinBox *mCircle2X = nullptr; + QgsDoubleSpinBox *mCircle2Y = nullptr; + QgsDoubleSpinBox *mCircle2Distance = nullptr; + + QgsPointXY mP1; + QgsPointXY mP2; + bool mP1Closest = false; +}; + +#endif + +#endif // QGSADVANCEDDIGITIZINGTOOLS diff --git a/src/gui/qgsmaptooladvanceddigitizing.cpp b/src/gui/qgsmaptooladvanceddigitizing.cpp index 85fad034aa95..07a27990e123 100644 --- a/src/gui/qgsmaptooladvanceddigitizing.cpp +++ b/src/gui/qgsmaptooladvanceddigitizing.cpp @@ -33,10 +33,11 @@ void QgsMapToolAdvancedDigitizing::canvasPressEvent( QgsMapMouseEvent *e ) { if ( isAdvancedDigitizingAllowed() && mCadDockWidget->cadEnabled() ) { - mCadDockWidget->applyConstraints( e ); // updates event's map point - - if ( mCadDockWidget->constructionMode() ) - return; // decided to eat the event and not pass it to the map tool (construction mode) + mCadDockWidget->applyConstraints( e ); // updates event's map point + if ( mCadDockWidget->processCanvasPressEvent( e ) ) + { + return; // The dock widget has eaten the event + } } else if ( isAutoSnapEnabled() ) { @@ -62,20 +63,11 @@ void QgsMapToolAdvancedDigitizing::canvasReleaseEvent( QgsMapMouseEvent *e ) } else { - mCadDockWidget->applyConstraints( e ); // updates event's map point - - if ( mCadDockWidget->alignToSegment( e ) ) + mCadDockWidget->applyConstraints( e ); // updates event's map point + if ( mCadDockWidget->processCanvasReleaseEvent( e ) ) { - // Parallel or perpendicular mode and snapped to segment: do not pass the event to map tool - return; + return; // The dock widget has eaten the event } - - mCadDockWidget->addPoint( e->mapPoint() ); - - mCadDockWidget->releaseLocks( false ); - - if ( mCadDockWidget->constructionMode() ) - return; // decided to eat the event and not pass it to the map tool (construction mode) } } else if ( isAutoSnapEnabled() ) @@ -96,12 +88,11 @@ void QgsMapToolAdvancedDigitizing::canvasMoveEvent( QgsMapMouseEvent *e ) { if ( isAdvancedDigitizingAllowed() && mCadDockWidget->cadEnabled() ) { - mCadDockWidget->applyConstraints( e ); // updates event's map point - - // perpendicular/parallel constraint - // do a soft lock when snapping to a segment - mCadDockWidget->alignToSegment( e, QgsAdvancedDigitizingDockWidget::CadConstraint::SoftLock ); - mCadDockWidget->updateCadPaintItem(); + mCadDockWidget->applyConstraints( e ); // updates event's map point + if ( mCadDockWidget->processCanvasMoveEvent( e ) ) + { + return; // The dock widget has eaten the event + } } else if ( isAutoSnapEnabled() ) { diff --git a/src/ui/qgsadvanceddigitizingdockwidgetbase.ui b/src/ui/qgsadvanceddigitizingdockwidgetbase.ui index ac8e5ab24976..2b4953837e95 100644 --- a/src/ui/qgsadvanceddigitizingdockwidgetbase.ui +++ b/src/ui/qgsadvanceddigitizingdockwidgetbase.ui @@ -46,12 +46,13 @@ + + + - - @@ -605,6 +606,15 @@ + + + true + + + + :/images/themes/default/cadtools/circlesinteresection.svg:/images/themes/default/cadtools/circlesinteresection.svg + +