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
+
+