diff --git a/images/images.qrc b/images/images.qrc
index c1f9ec46097..752e9d61041 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -583,6 +583,9 @@
themes/default/mIconSnappingSegment.svg
themes/default/mIconTopologicalEditing.svg
themes/default/mIconSnappingIntersection.svg
+ themes/default/mActionMoveFeatureCopy.svg
+ themes/default/mActionMoveFeatureCopyLine.svg
+ themes/default/mActionMoveFeatureCopyPoint.svg
qgis_tips/symbol_levels.png
diff --git a/images/themes/default/mActionMoveFeature.png b/images/themes/default/mActionMoveFeature.png
deleted file mode 100644
index 9fdea5d2234..00000000000
Binary files a/images/themes/default/mActionMoveFeature.png and /dev/null differ
diff --git a/images/themes/default/mActionMoveFeatureCopy.svg b/images/themes/default/mActionMoveFeatureCopy.svg
new file mode 100644
index 00000000000..47acc20b755
--- /dev/null
+++ b/images/themes/default/mActionMoveFeatureCopy.svg
@@ -0,0 +1,1159 @@
+
+
+
+
diff --git a/images/themes/default/mActionMoveFeatureCopyLine.svg b/images/themes/default/mActionMoveFeatureCopyLine.svg
new file mode 100644
index 00000000000..478c080854b
--- /dev/null
+++ b/images/themes/default/mActionMoveFeatureCopyLine.svg
@@ -0,0 +1,1205 @@
+
+
+
+
diff --git a/images/themes/default/mActionMoveFeatureCopyPoint.svg b/images/themes/default/mActionMoveFeatureCopyPoint.svg
new file mode 100644
index 00000000000..619ad333390
--- /dev/null
+++ b/images/themes/default/mActionMoveFeatureCopyPoint.svg
@@ -0,0 +1,729 @@
+
+
+
+
diff --git a/python/core/core.sip b/python/core/core.sip
index 6adeeb3dff1..cc14357c1b4 100644
--- a/python/core/core.sip
+++ b/python/core/core.sip
@@ -139,6 +139,7 @@
%Include qgstextrenderer.sip
%Include qgstolerance.sip
%Include qgstracer.sip
+%Include qgstrackedvectorlayertools.sip
%Include qgsunittypes.sip
%Include qgsvectordataprovider.sip
%Include qgsvectorfilewriter.sip
@@ -148,6 +149,7 @@
%Include qgsvectorlayereditpassthrough.sip
%Include qgsvectorlayerimport.sip
%Include qgsvectorlayerjoinbuffer.sip
+%Include qgsvectorlayertools.sip
%Include qgsvectorlayerundocommand.sip
%Include qgsvectorlayerutils.sip
%Include qgsvectorsimplifymethod.sip
diff --git a/python/gui/qgstrackedvectorlayertools.sip b/python/core/qgstrackedvectorlayertools.sip
similarity index 92%
rename from python/gui/qgstrackedvectorlayertools.sip
rename to python/core/qgstrackedvectorlayertools.sip
index 6efd5f519b8..7b7ba477d69 100644
--- a/python/gui/qgstrackedvectorlayertools.sip
+++ b/python/core/qgstrackedvectorlayertools.sip
@@ -25,6 +25,7 @@ class QgsTrackedVectorLayerTools : QgsVectorLayerTools
bool startEditing( QgsVectorLayer* layer ) const ;
bool stopEditing( QgsVectorLayer* layer, bool allowCancel ) const ;
bool saveEdits( QgsVectorLayer* layer ) const ;
+ bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request, double dx = 0, double dy = 0, QString *errorMsg = nullptr ) const;
/**
* Set the vector layer tools that will be used to interact with the data
diff --git a/python/gui/qgsvectorlayertools.sip b/python/core/qgsvectorlayertools.sip
similarity index 71%
rename from python/gui/qgsvectorlayertools.sip
rename to python/core/qgsvectorlayertools.sip
index bd65f42011a..fcc2c8b90a1 100644
--- a/python/gui/qgsvectorlayertools.sip
+++ b/python/core/qgsvectorlayertools.sip
@@ -1,9 +1,12 @@
-class QgsVectorLayerTools
+class QgsVectorLayerTools : QObject
{
%TypeHeaderCode
#include
%End
public:
+ QgsVectorLayerTools();
+
+ virtual ~QgsVectorLayerTools();
/**
* This method should/will be called, whenever a new feature will be added to the layer
@@ -45,4 +48,17 @@ class QgsVectorLayerTools
*/
virtual bool saveEdits( QgsVectorLayer* layer ) const = 0;
+ /**
+ * Copy and move features with defined translation.
+ *
+ * @param layer The layer
+ * @param request The request for the features to be moved. It will be assigned to a new feature request with the newly copied features.
+ * @param dx The translation on x
+ * @param dy The translation on y
+ * @param errorMsg If given, it will contain the error message
+ * @return True if all features could be copied.
+ *
+ * TODO QGIS 3: remove const qualifier
+ */
+ virtual bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request /In,Out/, double dx = 0, double dy = 0, QString* errorMsg /Out/ = nullptr ) const;
};
diff --git a/python/gui/gui.sip b/python/gui/gui.sip
index 88c27f32972..3587c165310 100644
--- a/python/gui/gui.sip
+++ b/python/gui/gui.sip
@@ -165,12 +165,10 @@
%Include qgstextannotationitem.sip
%Include qgstextformatwidget.sip
%Include qgstextpreview.sip
-%Include qgstrackedvectorlayertools.sip
%Include qgstreewidgetitem.sip
%Include qgsunitselectionwidget.sip
%Include qgsuserinputdockwidget.sip
%Include qgsvariableeditorwidget.sip
-%Include qgsvectorlayertools.sip
%Include qgsvertexmarker.sip
%Include attributetable/qgsattributetabledelegate.sip
diff --git a/python/testing/mocked.py b/python/testing/mocked.py
index 1445c7f9dba..312506dc13e 100644
--- a/python/testing/mocked.py
+++ b/python/testing/mocked.py
@@ -61,7 +61,6 @@ def get_iface():
canvas = QgsMapCanvas(my_iface.mainWindow())
canvas.resize(QSize(400, 400))
-
my_iface.mapCanvas.return_value = canvas
return my_iface
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 56931e374a3..6644b8e7c48 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -1248,6 +1248,7 @@ QgisApp::~QgisApp()
delete mMapTools.mMeasureArea;
delete mMapTools.mMeasureDist;
delete mMapTools.mMoveFeature;
+ delete mMapTools.mMoveFeatureCopy;
delete mMapTools.mMoveLabel;
delete mMapTools.mNodeTool;
delete mMapTools.mOffsetCurve;
@@ -1584,6 +1585,7 @@ void QgisApp::createActions()
connect( mActionCircularStringCurvePoint, SIGNAL( triggered() ), this, SLOT( circularStringCurvePoint() ) );
connect( mActionCircularStringRadius, SIGNAL( triggered() ), this, SLOT( circularStringRadius() ) );
connect( mActionMoveFeature, SIGNAL( triggered() ), this, SLOT( moveFeature() ) );
+ connect( mActionMoveFeatureCopy, &QAction::triggered, this, &QgisApp::moveFeatureCopy );
connect( mActionRotateFeature, SIGNAL( triggered() ), this, SLOT( rotateFeature() ) );
connect( mActionReshapeFeatures, SIGNAL( triggered() ), this, SLOT( reshapeFeatures() ) );
@@ -1876,6 +1878,7 @@ void QgisApp::createActionGroups()
mMapToolGroup->addAction( mActionCircularStringCurvePoint );
mMapToolGroup->addAction( mActionCircularStringRadius );
mMapToolGroup->addAction( mActionMoveFeature );
+ mMapToolGroup->addAction( mActionMoveFeatureCopy );
mMapToolGroup->addAction( mActionRotateFeature );
mMapToolGroup->addAction( mActionOffsetCurve );
mMapToolGroup->addAction( mActionReshapeFeatures );
@@ -2328,6 +2331,25 @@ void QgisApp::createToolBars()
layout->itemAt( i )->setAlignment( Qt::AlignLeft );
}
+ // move feature tool button
+ QToolButton* moveFeatureButton = new QToolButton( mDigitizeToolBar );
+ moveFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
+ moveFeatureButton->addAction( mActionMoveFeature );
+ moveFeatureButton->addAction( mActionMoveFeatureCopy );
+ QAction* defAction = mActionMoveFeature;
+ switch ( settings.value( QStringLiteral( "/UI/defaultMoveTool" ), 0 ).toInt() )
+ {
+ case 0:
+ defAction = mActionMoveFeature;
+ break;
+ case 1:
+ defAction = mActionMoveFeatureCopy;
+ break;
+ };
+ moveFeatureButton->setDefaultAction( defAction );
+ connect( moveFeatureButton, SIGNAL( triggered( QAction * ) ), this, SLOT( toolButtonActionTriggered( QAction * ) ) );
+ mDigitizeToolBar->insertWidget( mActionNodeTool, moveFeatureButton );
+
//circular string digitize tool button
QToolButton* tbAddCircularString = new QToolButton( mDigitizeToolBar );
tbAddCircularString->setPopupMode( QToolButton::MenuButtonPopup );
@@ -2617,6 +2639,7 @@ void QgisApp::setTheme( const QString& theThemeName )
mActionPasteFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditPaste.svg" ) ) );
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
+ mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
mActionRotateFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRotateFeature.svg" ) ) );
mActionReshapeFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReshape.svg" ) ) );
mActionSplitFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSplitFeatures.svg" ) ) );
@@ -2855,8 +2878,10 @@ void QgisApp::createCanvasTools()
mMapTools.mCircularStringCurvePoint->setAction( mActionCircularStringCurvePoint );
mMapTools.mCircularStringRadius = new QgsMapToolCircularStringRadius( dynamic_cast( mMapTools.mAddFeature ), mMapCanvas );
mMapTools.mCircularStringRadius->setAction( mActionCircularStringRadius );
- mMapTools.mMoveFeature = new QgsMapToolMoveFeature( mMapCanvas );
+ mMapTools.mMoveFeature = new QgsMapToolMoveFeature( mMapCanvas, QgsMapToolMoveFeature::Move );
mMapTools.mMoveFeature->setAction( mActionMoveFeature );
+ mMapTools.mMoveFeatureCopy = new QgsMapToolMoveFeature( mMapCanvas, QgsMapToolMoveFeature::CopyMove );
+ mMapTools.mMoveFeatureCopy->setAction( mActionMoveFeatureCopy );
mMapTools.mRotateFeature = new QgsMapToolRotateFeature( mMapCanvas );
mMapTools.mRotateFeature->setAction( mActionRotateFeature );
mMapTools.mOffsetCurve = new QgsMapToolOffsetCurve( mMapCanvas );
@@ -6485,6 +6510,11 @@ void QgisApp::moveFeature()
mMapCanvas->setMapTool( mMapTools.mMoveFeature );
}
+void QgisApp::moveFeatureCopy()
+{
+ mMapCanvas->setMapTool( mMapTools.mMoveFeatureCopy );
+}
+
void QgisApp::offsetCurve()
{
mMapCanvas->setMapTool( mMapTools.mOffsetCurve );
@@ -10526,6 +10556,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
mActionCircularStringCurvePoint->setEnabled( false );
mActionCircularStringRadius->setEnabled( false );
mActionMoveFeature->setEnabled( false );
+ mActionMoveFeatureCopy->setEnabled( false );
mActionRotateFeature->setEnabled( false );
mActionOffsetCurve->setEnabled( false );
mActionNodeTool->setEnabled( false );
@@ -10680,6 +10711,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
mActionAddPart->setEnabled( isEditable && canChangeGeometry );
mActionDeletePart->setEnabled( isEditable && canChangeGeometry );
mActionMoveFeature->setEnabled( isEditable && canChangeGeometry );
+ mActionMoveFeatureCopy->setEnabled( isEditable && canChangeGeometry );
mActionRotateFeature->setEnabled( isEditable && canChangeGeometry );
mActionNodeTool->setEnabled( isEditable && canChangeGeometry );
@@ -10690,6 +10722,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
{
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
+ mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
mActionAddRing->setEnabled( false );
mActionFillRing->setEnabled( false );
@@ -10718,6 +10751,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
{
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCaptureLine.svg" ) ) );
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureLine.svg" ) ) );
+ mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyLine.svg" ) ) );
mActionReshapeFeatures->setEnabled( isEditable && canChangeGeometry );
mActionSplitFeatures->setEnabled( isEditable && canAddFeatures );
@@ -10733,6 +10767,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
{
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePolygon.svg" ) ) );
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeature.svg" ) ) );
+ mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopy.svg" ) ) );
mActionAddRing->setEnabled( isEditable && canChangeGeometry );
mActionFillRing->setEnabled( isEditable && canChangeGeometry );
@@ -10819,6 +10854,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
mActionAddPart->setEnabled( false );
mActionNodeTool->setEnabled( false );
mActionMoveFeature->setEnabled( false );
+ mActionMoveFeatureCopy->setEnabled( false );
mActionRotateFeature->setEnabled( false );
mActionOffsetCurve->setEnabled( false );
mActionCopyFeatures->setEnabled( false );
@@ -11846,6 +11882,10 @@ void QgisApp::toolButtonActionTriggered( QAction *action )
settings.setValue( QStringLiteral( "/UI/defaultMapService" ), 0 );
else if ( action == mActionAddAmsLayer )
settings.setValue( QStringLiteral( "/UI/defaultMapService" ), 1 );
+ else if ( action == mActionMoveFeature )
+ settings.setValue( QStringLiteral( "/UI/defaultMoveTool" ), 0 );
+ else if ( action == mActionMoveFeatureCopy )
+ settings.setValue( QStringLiteral( "/UI/defaultMoveTool" ), 1 );
bt->setDefaultAction( action );
}
diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h
index d8472274d5b..cbf2a7a393d 100644
--- a/src/app/qgisapp.h
+++ b/src/app/qgisapp.h
@@ -331,6 +331,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QAction *actionDeleteSelected() { return mActionDeleteSelected; }
QAction *actionAddFeature() { return mActionAddFeature; }
QAction *actionMoveFeature() { return mActionMoveFeature; }
+ QAction *actionMoveFeatureCopy() { return mActionMoveFeatureCopy; }
QAction *actionRotateFeature() { return mActionRotateFeature;}
QAction *actionSplitFeatures() { return mActionSplitFeatures; }
QAction *actionSplitParts() { return mActionSplitParts; }
@@ -1068,6 +1069,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void circularStringRadius();
//! activates the move feature tool
void moveFeature();
+ //! activates the copy and move feature tool
+ void moveFeatureCopy();
//! activates the offset curve tool
void offsetCurve();
//! activates the reshape features tool
@@ -1634,6 +1637,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsMapTool *mCircularStringCurvePoint;
QgsMapTool *mCircularStringRadius;
QgsMapTool *mMoveFeature;
+ QgsMapTool *mMoveFeatureCopy;
QgsMapTool *mOffsetCurve;
QgsMapTool *mReshapeFeatures;
QgsMapTool *mSplitFeatures;
diff --git a/src/app/qgsguivectorlayertools.cpp b/src/app/qgsguivectorlayertools.cpp
index bcca09eacd8..1bb4d33c5ac 100644
--- a/src/app/qgsguivectorlayertools.cpp
+++ b/src/app/qgsguivectorlayertools.cpp
@@ -17,19 +17,21 @@
#include
#include "qgsguivectorlayertools.h"
-#include "qgsvectorlayer.h"
-#include "qgsvectordataprovider.h"
-#include "qgsmessagebar.h"
+
#include "qgisapp.h"
#include "qgsapplication.h"
-#include "qgsmessageviewer.h"
#include "qgsfeatureaction.h"
+#include "qgslogger.h"
#include "qgsmapcanvas.h"
+#include "qgsmessagebar.h"
#include "qgsmessagebaritem.h"
+#include "qgsmessageviewer.h"
+#include "qgsvectordataprovider.h"
+#include "qgsvectorlayer.h"
QgsGuiVectorLayerTools::QgsGuiVectorLayerTools()
- : QObject( nullptr )
+ : QgsVectorLayerTools()
{}
bool QgsGuiVectorLayerTools::addFeature( QgsVectorLayer* layer, const QgsAttributeMap& defaultValues, const QgsGeometry& defaultGeometry, QgsFeature* feat ) const
@@ -95,7 +97,6 @@ bool QgsGuiVectorLayerTools::saveEdits( QgsVectorLayer* layer ) const
return res;
}
-
bool QgsGuiVectorLayerTools::stopEditing( QgsVectorLayer* layer, bool allowCancel ) const
{
bool res = true;
diff --git a/src/app/qgsguivectorlayertools.h b/src/app/qgsguivectorlayertools.h
index da90b64b82d..ecc57ce7664 100644
--- a/src/app/qgsguivectorlayertools.h
+++ b/src/app/qgsguivectorlayertools.h
@@ -23,7 +23,7 @@
* or a feature is added.
*/
-class QgsGuiVectorLayerTools : public QObject, public QgsVectorLayerTools
+class QgsGuiVectorLayerTools : public QgsVectorLayerTools
{
Q_OBJECT
@@ -73,6 +73,7 @@ class QgsGuiVectorLayerTools : public QObject, public QgsVectorLayerTools
private:
void commitError( QgsVectorLayer* vlayer ) const;
+
};
#endif // QGSGUIVECTORLAYERTOOLS_H
diff --git a/src/app/qgsmaptoolmovefeature.cpp b/src/app/qgsmaptoolmovefeature.cpp
index e4d43d0e60e..77dfa8a73fc 100644
--- a/src/app/qgsmaptoolmovefeature.cpp
+++ b/src/app/qgsmaptoolmovefeature.cpp
@@ -13,27 +13,39 @@
* *
***************************************************************************/
-#include "qgsmaptoolmovefeature.h"
+#include "qgisapp.h"
+#include "qgsadvanceddigitizingdockwidget.h"
#include "qgsfeatureiterator.h"
#include "qgsgeometry.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
+#include "qgsmaptoolmovefeature.h"
#include "qgsrubberband.h"
-#include "qgsvectorlayer.h"
#include "qgstolerance.h"
-#include "qgisapp.h"
-#include "qgsadvanceddigitizingdockwidget.h"
+#include "qgsvectorlayer.h"
+#include "qgsvectorlayertools.h"
+
#include
#include
#include
-QgsMapToolMoveFeature::QgsMapToolMoveFeature( QgsMapCanvas* canvas )
+
+QgsMapToolMoveFeature::QgsMapToolMoveFeature( QgsMapCanvas* canvas , MoveMode mode )
: QgsMapToolAdvancedDigitizing( canvas, QgisApp::instance()->cadDockWidget() )
, mRubberBand( nullptr )
+ , mMode( mode )
{
mToolName = tr( "Move feature" );
- mCaptureMode = QgsMapToolAdvancedDigitizing::CaptureSegment;
+ switch ( mode )
+ {
+ case Move:
+ mCaptureMode = QgsMapToolAdvancedDigitizing::CaptureSegment;
+ break;
+ case CopyMove:
+ mCaptureMode = QgsMapToolAdvancedDigitizing::CaptureLine; // we copy/move several times
+ break;
+ }
}
QgsMapToolMoveFeature::~QgsMapToolMoveFeature()
@@ -138,12 +150,12 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent* e )
}
else
{
- delete mRubberBand;
- mRubberBand = nullptr;
-
+ // copy and move mode
if ( e->button() != Qt::LeftButton )
{
cadDockWidget()->clear();
+ delete mRubberBand;
+ mRubberBand = nullptr;
return;
}
@@ -152,13 +164,35 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent* e )
double dx = stopPointLayerCoords.x() - startPointLayerCoords.x();
double dy = stopPointLayerCoords.y() - startPointLayerCoords.y();
- vlayer->beginEditCommand( tr( "Feature moved" ) );
- Q_FOREACH ( QgsFeatureId id, mMovedFeatures )
+
+
+ vlayer->beginEditCommand( mMode == Move ? tr( "Feature moved" ) : tr( "Feature copied and moved" ) );
+
+
+ switch ( mMode )
{
- vlayer->translateFeature( id, dx, dy );
+ case Move:
+ Q_FOREACH ( QgsFeatureId id, mMovedFeatures )
+ {
+ vlayer->translateFeature( id, dx, dy );
+ }
+ delete mRubberBand;
+ mRubberBand = nullptr;
+ break;
+
+ case CopyMove:
+ QgsFeatureRequest request;
+ request.setFilterFids( mMovedFeatures );
+ QString* errorMsg = new QString();
+ if ( !QgisApp::instance()->vectorLayerTools()->copyMoveFeatures( vlayer, request, dx, dy, errorMsg ) )
+ {
+ emit messageEmitted( *errorMsg, QgsMessageBar::CRITICAL );
+ delete mRubberBand;
+ mRubberBand = nullptr;
+ }
+ break;
}
- delete mRubberBand;
- mRubberBand = nullptr;
+
vlayer->endEditCommand();
vlayer->triggerRepaint();
}
diff --git a/src/app/qgsmaptoolmovefeature.h b/src/app/qgsmaptoolmovefeature.h
index fd888423155..eda9c22712e 100644
--- a/src/app/qgsmaptoolmovefeature.h
+++ b/src/app/qgsmaptoolmovefeature.h
@@ -23,7 +23,14 @@ class APP_EXPORT QgsMapToolMoveFeature: public QgsMapToolAdvancedDigitizing
{
Q_OBJECT
public:
- QgsMapToolMoveFeature( QgsMapCanvas* canvas );
+ //! Mode for moving features
+ enum MoveMode
+ {
+ Move, //!< Move feature
+ CopyMove //!< Copy and move feature
+ };
+
+ QgsMapToolMoveFeature( QgsMapCanvas* canvas, MoveMode mode = Move );
virtual ~QgsMapToolMoveFeature();
virtual void cadCanvasMoveEvent( QgsMapMouseEvent* e ) override;
@@ -45,6 +52,8 @@ class APP_EXPORT QgsMapToolMoveFeature: public QgsMapToolAdvancedDigitizing
QPoint mPressPos;
+ MoveMode mMode;
+
};
#endif
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 803bbc6dd2a..ca808a861bc 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -213,6 +213,7 @@ SET(QGIS_CORE_SRCS
qgstextrenderer.cpp
qgstolerance.cpp
qgstracer.cpp
+ qgstrackedvectorlayertools.cpp
qgstransaction.cpp
qgstransactiongroup.cpp
qgsunittypes.cpp
@@ -230,6 +231,7 @@ SET(QGIS_CORE_SRCS
qgsvectorlayerlabeling.cpp
qgsvectorlayerlabelprovider.cpp
qgsvectorlayerrenderer.cpp
+ qgsvectorlayertools.cpp
qgsvectorlayerundocommand.cpp
qgsvectorlayerutils.cpp
qgsvectorsimplifymethod.cpp
@@ -491,6 +493,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsrunprocess.h
qgssnappingutils.h
qgstracer.h
+ qgstrackedvectorlayertools.h
qgstransaction.h
qgstransactiongroup.h
qgsunittypes.h
@@ -500,6 +503,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsvectorlayereditpassthrough.h
qgsvectorlayer.h
qgsvectorlayerjoinbuffer.h
+ qgsvectorlayertools.h
qgsmapthemecollection.h
qgswebpage.h
qgswebview.h
diff --git a/src/gui/qgstrackedvectorlayertools.cpp b/src/core/qgstrackedvectorlayertools.cpp
similarity index 90%
rename from src/gui/qgstrackedvectorlayertools.cpp
rename to src/core/qgstrackedvectorlayertools.cpp
index a09687d1e2d..b29bad54c2f 100644
--- a/src/gui/qgstrackedvectorlayertools.cpp
+++ b/src/core/qgstrackedvectorlayertools.cpp
@@ -17,7 +17,7 @@
#include "qgsvectorlayer.h"
QgsTrackedVectorLayerTools::QgsTrackedVectorLayerTools()
- : mBackend( nullptr )
+ : mBackend()
{
}
@@ -57,6 +57,11 @@ bool QgsTrackedVectorLayerTools::saveEdits( QgsVectorLayer* layer ) const
return mBackend->saveEdits( layer );
}
+bool QgsTrackedVectorLayerTools::copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest& request, double dx, double dy, QString* errorMsg ) const
+{
+ return mBackend->copyMoveFeatures( layer, request, dx, dy, errorMsg );
+}
+
void QgsTrackedVectorLayerTools::setVectorLayerTools( const QgsVectorLayerTools* tools )
{
mBackend = tools;
diff --git a/src/gui/qgstrackedvectorlayertools.h b/src/core/qgstrackedvectorlayertools.h
similarity index 89%
rename from src/gui/qgstrackedvectorlayertools.h
rename to src/core/qgstrackedvectorlayertools.h
index d7894e79678..32ef3a78267 100644
--- a/src/gui/qgstrackedvectorlayertools.h
+++ b/src/core/qgstrackedvectorlayertools.h
@@ -21,8 +21,9 @@
/** \ingroup gui
* \class QgsTrackedVectorLayerTools
*/
-class GUI_EXPORT QgsTrackedVectorLayerTools : public QgsVectorLayerTools
+class CORE_EXPORT QgsTrackedVectorLayerTools : public QgsVectorLayerTools
{
+ Q_OBJECT
public:
QgsTrackedVectorLayerTools();
@@ -30,6 +31,7 @@ class GUI_EXPORT QgsTrackedVectorLayerTools : public QgsVectorLayerTools
bool startEditing( QgsVectorLayer* layer ) const override;
bool stopEditing( QgsVectorLayer* layer, bool allowCancel ) const override;
bool saveEdits( QgsVectorLayer* layer ) const override;
+ bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request, double dx = 0, double dy = 0, QString *errorMsg = nullptr ) const override;
/**
* Set the vector layer tools that will be used to interact with the data
diff --git a/src/core/qgsvectorlayertools.cpp b/src/core/qgsvectorlayertools.cpp
new file mode 100644
index 00000000000..3ca7efb5e5f
--- /dev/null
+++ b/src/core/qgsvectorlayertools.cpp
@@ -0,0 +1,107 @@
+/***************************************************************************
+ qgsvectorlayertools.cpp
+ ---------------------
+ begin : 09.11.2016
+ copyright : (C) 2016 by Denis Rouzaud
+ email : denis.rouzaud@gmail.com
+ ***************************************************************************
+ * *
+ * 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 "qgsvectorlayer.h"
+#include "qgsvectorlayertools.h"
+#include "qgsfeaturerequest.h"
+#include "qgslogger.h"
+
+
+QgsVectorLayerTools::QgsVectorLayerTools()
+ : QObject( nullptr )
+{}
+
+
+QgsVectorLayerTools::~QgsVectorLayerTools()
+{}
+
+
+bool QgsVectorLayerTools::copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest& request, double dx, double dy, QString* errorMsg ) const
+{
+ bool res = false;
+ if ( !layer || !layer->isEditable() )
+ {
+ return false;
+ }
+
+ QgsFeatureIterator fi = layer->getFeatures( request );
+ QgsFeature f;
+ QgsAttributeList pkAttrList = layer->pkAttributeList();
+
+ int browsedFeatureCount = 0;
+ int couldNotWriteCount = 0;
+ int noGeometryCount = 0;
+
+ QgsFeatureIds fidList;
+
+ while ( fi.nextFeature( f ) )
+ {
+ browsedFeatureCount++;
+ // remove pkey values
+ Q_FOREACH ( auto idx, pkAttrList )
+ {
+ f.setAttribute( idx, QVariant() );
+ }
+ // translate
+ if ( f.hasGeometry() )
+ {
+ QgsGeometry geom = f.geometry();
+ geom.translate( dx, dy );
+ f.setGeometry( geom );
+#ifdef QGISDEBUG
+ const QgsFeatureId fid = f.id();
+#endif
+ // paste feature
+ if ( !layer->addFeature( f, false ) )
+ {
+ couldNotWriteCount++;
+ QgsDebugMsg( QString( "Could not add new feature. Original copied feature id: %1" ).arg( fid ) );
+ }
+ else
+ {
+ fidList.insert( f.id() );
+ }
+ }
+ else
+ {
+ noGeometryCount++;
+ }
+ }
+
+ request = QgsFeatureRequest();
+ request.setFilterFids( fidList );
+
+ if ( !couldNotWriteCount && !noGeometryCount )
+ {
+ res = true;
+ }
+ else if ( errorMsg )
+ {
+ errorMsg = new QString( QString( tr( "Only %1 out of %2 features were copied." ) )
+ .arg( browsedFeatureCount - couldNotWriteCount - noGeometryCount, browsedFeatureCount ) );
+ if ( noGeometryCount )
+ {
+ errorMsg->append( " " );
+ errorMsg->append( tr( "Some features have no geometry." ) );
+ }
+ if ( couldNotWriteCount )
+ {
+ errorMsg->append( " " );
+ errorMsg->append( tr( "Some could not be created on the layer." ) );
+ }
+ }
+ return res;
+}
diff --git a/src/gui/qgsvectorlayertools.h b/src/core/qgsvectorlayertools.h
similarity index 80%
rename from src/gui/qgsvectorlayertools.h
rename to src/core/qgsvectorlayertools.h
index 468f1d8ec42..6a9dab28eec 100644
--- a/src/gui/qgsvectorlayertools.h
+++ b/src/core/qgsvectorlayertools.h
@@ -16,9 +16,12 @@
#ifndef QGSVECTORLAYERTOOLS_H
#define QGSVECTORLAYERTOOLS_H
+#include
+
#include "qgsfeature.h"
#include "qgsgeometry.h"
+class QgsFeatureRequest;
class QgsVectorLayer;
/** \ingroup gui
@@ -30,12 +33,14 @@ class QgsVectorLayer;
* in your application.
*
*/
-class GUI_EXPORT QgsVectorLayerTools
+class CORE_EXPORT QgsVectorLayerTools : public QObject
{
- public:
- QgsVectorLayerTools() {}
+ Q_OBJECT
- virtual ~QgsVectorLayerTools() {}
+ public:
+ QgsVectorLayerTools();
+
+ virtual ~QgsVectorLayerTools();
/**
* This method should/will be called, whenever a new feature will be added to the layer
@@ -85,6 +90,20 @@ class GUI_EXPORT QgsVectorLayerTools
*/
virtual bool saveEdits( QgsVectorLayer* layer ) const = 0;
+ /**
+ * Copy and move features with defined translation.
+ *
+ * @param layer The layer
+ * @param request The request for the features to be moved. It will be assigned to a new feature request with the newly copied features.
+ * @param dx The translation on x
+ * @param dy The translation on y
+ * @param errorMsg If given, it will contain the error message
+ * @return True if all features could be copied.
+ *
+ * TODO QGIS 3: remove const qualifier
+ */
+ virtual bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request, double dx = 0, double dy = 0, QString *errorMsg = nullptr ) const;
+
};
#endif // QGSVECTORLAYERTOOLS_H
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 3dceb74b66e..fe02b7cade1 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -311,7 +311,6 @@ SET(QGIS_GUI_SRCS
qgstextannotationitem.cpp
qgstextformatwidget.cpp
qgstextpreview.cpp
- qgstrackedvectorlayertools.cpp
qgstreewidgetitem.cpp
qgsunitselectionwidget.cpp
qgsuserinputdockwidget.cpp
@@ -662,9 +661,7 @@ SET(QGIS_GUI_HDRS
qgssvgannotationitem.h
qgstablewidgetitem.h
qgstextannotationitem.h
- qgstrackedvectorlayertools.h
qgsuserinputdockwidget.h
- qgsvectorlayertools.h
qgsvertexmarker.h
qgsfiledownloader.h
diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui
index 86a9c1b33d2..f69671d8bf4 100644
--- a/src/ui/qgisapp.ui
+++ b/src/ui/qgisapp.ui
@@ -17,7 +17,7 @@
0
0
1018
- 28
+ 22
@@ -369,7 +369,6 @@
-
@@ -2564,6 +2563,21 @@ Acts on currently active editable layer
F3
+
+
+ true
+
+
+
+ :/images/themes/default/mActionMoveFeatureCopy.svg:/images/themes/default/mActionMoveFeatureCopy.svg
+
+
+ Copy and Move Feature(s)
+
+
+ Copy and Move Feature(s)
+
+
diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt
index 6f013558427..3ee40185163 100644
--- a/tests/src/python/CMakeLists.txt
+++ b/tests/src/python/CMakeLists.txt
@@ -143,6 +143,7 @@ ENDIF (WITH_DESKTOP)
IF (ENABLE_PGTEST)
ADD_PYTHON_TEST(PyQgsPostgresProvider test_provider_postgres.py)
ADD_PYTHON_TEST(PyQgsRelationEditWidget test_qgsrelationeditwidget.py)
+ ADD_PYTHON_TEST(PyQgsVectorLayerTools test_qgsvectorlayertools.py)
ENDIF (ENABLE_PGTEST)
IF (ENABLE_MSSQLTEST)
diff --git a/tests/src/python/test_qgsrelationeditwidget.py b/tests/src/python/test_qgsrelationeditwidget.py
index be4877519db..d184500b4f8 100644
--- a/tests/src/python/test_qgsrelationeditwidget.py
+++ b/tests/src/python/test_qgsrelationeditwidget.py
@@ -23,14 +23,14 @@ from qgis.core import (
QgsRelation,
QgsMapLayerRegistry,
QgsTransaction,
- QgsFeatureRequest
+ QgsFeatureRequest,
+ QgsVectorLayerTools
)
from qgis.gui import (
QgsEditorWidgetRegistry,
QgsRelationWidgetWrapper,
- QgsAttributeEditorContext,
- QgsVectorLayerTools
+ QgsAttributeEditorContext
)
from qgis.PyQt.QtCore import QTimer
diff --git a/tests/src/python/test_qgsvectorlayertools.py b/tests/src/python/test_qgsvectorlayertools.py
new file mode 100644
index 00000000000..2fcd4612897
--- /dev/null
+++ b/tests/src/python/test_qgsvectorlayertools.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+"""QGIS Unit test utils for provider tests.
+
+.. note:: 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.
+"""
+
+from builtins import str
+from builtins import object
+__author__ = 'Denis Rouzaud'
+__date__ = '2016-11-07'
+__copyright__ = 'Copyright 2015, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+from qgis.core import QgsFeatureRequest, QgsVectorLayer, QgsMapLayerRegistry, QgsVectorLayerTools
+from qgis.testing import start_app, unittest
+
+import os
+
+start_app()
+
+
+class SubQgsVectorLayerTools(QgsVectorLayerTools):
+
+ def __init__(self):
+ super().__init__()
+
+ def addFeature(self, layer):
+ pass
+
+ def startEditing(self, layer):
+ pass
+
+ def stopEditing(self, layer):
+ pass
+
+ def saveEdits(self, layer):
+ pass
+
+
+class TestQgsVectorLayerTools(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Setup the involved layers and relations for a n:m relation
+ :return:
+ """
+ cls.dbconn = 'service=\'qgis_test\''
+ if 'QGIS_PGTEST_DB' in os.environ:
+ cls.dbconn = os.environ['QGIS_PGTEST_DB']
+ # Create test layer
+ cls.vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."someData" (geom) sql=', 'layer', 'postgres')
+
+ QgsMapLayerRegistry.instance().addMapLayer(cls.vl)
+
+ cls.vltools = SubQgsVectorLayerTools()
+
+ def testCopyMoveFeature(self):
+ """ Test copy and move features"""
+ rqst = QgsFeatureRequest()
+ rqst.setFilterFid(4)
+ self.vl.startEditing()
+ (ok, rqst, msg) = self.vltools.copyMoveFeatures(self.vl, rqst, -0.1, 0.2)
+ self.assertTrue(ok)
+ for f in self.vl.getFeatures(rqst):
+ geom = f.geometry()
+ self.assertAlmostEqual(geom.asPoint().x(), -65.42)
+ self.assertAlmostEqual(geom.asPoint().y(), 78.5)
+
+
+if __name__ == '__main__':
+ unittest.main()