Skip to content

Commit a373f95

Browse files
committedNov 15, 2016
[FEATURE] add functionnality to copy/move feature to move feature map tool
1 parent 9ddf78e commit a373f95

26 files changed

+3466
-40
lines changed
 

‎images/images.qrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,9 @@
583583
<file>themes/default/mIconSnappingSegment.svg</file>
584584
<file>themes/default/mIconTopologicalEditing.svg</file>
585585
<file>themes/default/mIconSnappingIntersection.svg</file>
586+
<file>themes/default/mActionMoveFeatureCopy.svg</file>
587+
<file>themes/default/mActionMoveFeatureCopyLine.svg</file>
588+
<file>themes/default/mActionMoveFeatureCopyPoint.svg</file>
586589
</qresource>
587590
<qresource prefix="/images/tips">
588591
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
-1.26 KB
Binary file not shown.

‎images/themes/default/mActionMoveFeatureCopy.svg

Lines changed: 1159 additions & 0 deletions
Loading

‎images/themes/default/mActionMoveFeatureCopyLine.svg

Lines changed: 1205 additions & 0 deletions
Loading

‎images/themes/default/mActionMoveFeatureCopyPoint.svg

Lines changed: 729 additions & 0 deletions
Loading

‎python/core/core.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
%Include qgstextrenderer.sip
140140
%Include qgstolerance.sip
141141
%Include qgstracer.sip
142+
%Include qgstrackedvectorlayertools.sip
142143
%Include qgsunittypes.sip
143144
%Include qgsvectordataprovider.sip
144145
%Include qgsvectorfilewriter.sip
@@ -148,6 +149,7 @@
148149
%Include qgsvectorlayereditpassthrough.sip
149150
%Include qgsvectorlayerimport.sip
150151
%Include qgsvectorlayerjoinbuffer.sip
152+
%Include qgsvectorlayertools.sip
151153
%Include qgsvectorlayerundocommand.sip
152154
%Include qgsvectorlayerutils.sip
153155
%Include qgsvectorsimplifymethod.sip

‎python/gui/qgstrackedvectorlayertools.sip renamed to ‎python/core/qgstrackedvectorlayertools.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class QgsTrackedVectorLayerTools : QgsVectorLayerTools
2525
bool startEditing( QgsVectorLayer* layer ) const ;
2626
bool stopEditing( QgsVectorLayer* layer, bool allowCancel ) const ;
2727
bool saveEdits( QgsVectorLayer* layer ) const ;
28+
bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request, double dx = 0, double dy = 0, QString *errorMsg = nullptr ) const;
2829

2930
/**
3031
* Set the vector layer tools that will be used to interact with the data

‎python/gui/qgsvectorlayertools.sip renamed to ‎python/core/qgsvectorlayertools.sip

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
class QgsVectorLayerTools
1+
class QgsVectorLayerTools : QObject
22
{
33
%TypeHeaderCode
44
#include <qgsvectorlayertools.h>
55
%End
66
public:
7+
QgsVectorLayerTools();
8+
9+
virtual ~QgsVectorLayerTools();
710

811
/**
912
* This method should/will be called, whenever a new feature will be added to the layer
@@ -45,4 +48,17 @@ class QgsVectorLayerTools
4548
*/
4649
virtual bool saveEdits( QgsVectorLayer* layer ) const = 0;
4750

51+
/**
52+
* Copy and move features with defined translation.
53+
*
54+
* @param layer The layer
55+
* @param request The request for the features to be moved. It will be assigned to a new feature request with the newly copied features.
56+
* @param dx The translation on x
57+
* @param dy The translation on y
58+
* @param errorMsg If given, it will contain the error message
59+
* @return True if all features could be copied.
60+
*
61+
* TODO QGIS 3: remove const qualifier
62+
*/
63+
virtual bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request /In,Out/, double dx = 0, double dy = 0, QString* errorMsg /Out/ = nullptr ) const;
4864
};

‎python/gui/gui.sip

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,10 @@
165165
%Include qgstextannotationitem.sip
166166
%Include qgstextformatwidget.sip
167167
%Include qgstextpreview.sip
168-
%Include qgstrackedvectorlayertools.sip
169168
%Include qgstreewidgetitem.sip
170169
%Include qgsunitselectionwidget.sip
171170
%Include qgsuserinputdockwidget.sip
172171
%Include qgsvariableeditorwidget.sip
173-
%Include qgsvectorlayertools.sip
174172
%Include qgsvertexmarker.sip
175173

176174
%Include attributetable/qgsattributetabledelegate.sip

‎python/testing/mocked.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ def get_iface():
6161

6262
canvas = QgsMapCanvas(my_iface.mainWindow())
6363
canvas.resize(QSize(400, 400))
64-
6564
my_iface.mapCanvas.return_value = canvas
6665

6766
return my_iface

‎src/app/qgisapp.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,7 @@ QgisApp::~QgisApp()
12481248
delete mMapTools.mMeasureArea;
12491249
delete mMapTools.mMeasureDist;
12501250
delete mMapTools.mMoveFeature;
1251+
delete mMapTools.mMoveFeatureCopy;
12511252
delete mMapTools.mMoveLabel;
12521253
delete mMapTools.mNodeTool;
12531254
delete mMapTools.mOffsetCurve;
@@ -1584,6 +1585,7 @@ void QgisApp::createActions()
15841585
connect( mActionCircularStringCurvePoint, SIGNAL( triggered() ), this, SLOT( circularStringCurvePoint() ) );
15851586
connect( mActionCircularStringRadius, SIGNAL( triggered() ), this, SLOT( circularStringRadius() ) );
15861587
connect( mActionMoveFeature, SIGNAL( triggered() ), this, SLOT( moveFeature() ) );
1588+
connect( mActionMoveFeatureCopy, &QAction::triggered, this, &QgisApp::moveFeatureCopy );
15871589
connect( mActionRotateFeature, SIGNAL( triggered() ), this, SLOT( rotateFeature() ) );
15881590

15891591
connect( mActionReshapeFeatures, SIGNAL( triggered() ), this, SLOT( reshapeFeatures() ) );
@@ -1876,6 +1878,7 @@ void QgisApp::createActionGroups()
18761878
mMapToolGroup->addAction( mActionCircularStringCurvePoint );
18771879
mMapToolGroup->addAction( mActionCircularStringRadius );
18781880
mMapToolGroup->addAction( mActionMoveFeature );
1881+
mMapToolGroup->addAction( mActionMoveFeatureCopy );
18791882
mMapToolGroup->addAction( mActionRotateFeature );
18801883
mMapToolGroup->addAction( mActionOffsetCurve );
18811884
mMapToolGroup->addAction( mActionReshapeFeatures );
@@ -2328,6 +2331,25 @@ void QgisApp::createToolBars()
23282331
layout->itemAt( i )->setAlignment( Qt::AlignLeft );
23292332
}
23302333

2334+
// move feature tool button
2335+
QToolButton* moveFeatureButton = new QToolButton( mDigitizeToolBar );
2336+
moveFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
2337+
moveFeatureButton->addAction( mActionMoveFeature );
2338+
moveFeatureButton->addAction( mActionMoveFeatureCopy );
2339+
QAction* defAction = mActionMoveFeature;
2340+
switch ( settings.value( QStringLiteral( "/UI/defaultMoveTool" ), 0 ).toInt() )
2341+
{
2342+
case 0:
2343+
defAction = mActionMoveFeature;
2344+
break;
2345+
case 1:
2346+
defAction = mActionMoveFeatureCopy;
2347+
break;
2348+
};
2349+
moveFeatureButton->setDefaultAction( defAction );
2350+
connect( moveFeatureButton, SIGNAL( triggered( QAction * ) ), this, SLOT( toolButtonActionTriggered( QAction * ) ) );
2351+
mDigitizeToolBar->insertWidget( mActionNodeTool, moveFeatureButton );
2352+
23312353
//circular string digitize tool button
23322354
QToolButton* tbAddCircularString = new QToolButton( mDigitizeToolBar );
23332355
tbAddCircularString->setPopupMode( QToolButton::MenuButtonPopup );
@@ -2617,6 +2639,7 @@ void QgisApp::setTheme( const QString& theThemeName )
26172639
mActionPasteFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditPaste.svg" ) ) );
26182640
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
26192641
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
2642+
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
26202643
mActionRotateFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRotateFeature.svg" ) ) );
26212644
mActionReshapeFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReshape.svg" ) ) );
26222645
mActionSplitFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSplitFeatures.svg" ) ) );
@@ -2855,8 +2878,10 @@ void QgisApp::createCanvasTools()
28552878
mMapTools.mCircularStringCurvePoint->setAction( mActionCircularStringCurvePoint );
28562879
mMapTools.mCircularStringRadius = new QgsMapToolCircularStringRadius( dynamic_cast<QgsMapToolAddFeature*>( mMapTools.mAddFeature ), mMapCanvas );
28572880
mMapTools.mCircularStringRadius->setAction( mActionCircularStringRadius );
2858-
mMapTools.mMoveFeature = new QgsMapToolMoveFeature( mMapCanvas );
2881+
mMapTools.mMoveFeature = new QgsMapToolMoveFeature( mMapCanvas, QgsMapToolMoveFeature::Move );
28592882
mMapTools.mMoveFeature->setAction( mActionMoveFeature );
2883+
mMapTools.mMoveFeatureCopy = new QgsMapToolMoveFeature( mMapCanvas, QgsMapToolMoveFeature::CopyMove );
2884+
mMapTools.mMoveFeatureCopy->setAction( mActionMoveFeatureCopy );
28602885
mMapTools.mRotateFeature = new QgsMapToolRotateFeature( mMapCanvas );
28612886
mMapTools.mRotateFeature->setAction( mActionRotateFeature );
28622887
mMapTools.mOffsetCurve = new QgsMapToolOffsetCurve( mMapCanvas );
@@ -6485,6 +6510,11 @@ void QgisApp::moveFeature()
64856510
mMapCanvas->setMapTool( mMapTools.mMoveFeature );
64866511
}
64876512

6513+
void QgisApp::moveFeatureCopy()
6514+
{
6515+
mMapCanvas->setMapTool( mMapTools.mMoveFeatureCopy );
6516+
}
6517+
64886518
void QgisApp::offsetCurve()
64896519
{
64906520
mMapCanvas->setMapTool( mMapTools.mOffsetCurve );
@@ -10526,6 +10556,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
1052610556
mActionCircularStringCurvePoint->setEnabled( false );
1052710557
mActionCircularStringRadius->setEnabled( false );
1052810558
mActionMoveFeature->setEnabled( false );
10559+
mActionMoveFeatureCopy->setEnabled( false );
1052910560
mActionRotateFeature->setEnabled( false );
1053010561
mActionOffsetCurve->setEnabled( false );
1053110562
mActionNodeTool->setEnabled( false );
@@ -10680,6 +10711,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
1068010711
mActionAddPart->setEnabled( isEditable && canChangeGeometry );
1068110712
mActionDeletePart->setEnabled( isEditable && canChangeGeometry );
1068210713
mActionMoveFeature->setEnabled( isEditable && canChangeGeometry );
10714+
mActionMoveFeatureCopy->setEnabled( isEditable && canChangeGeometry );
1068310715
mActionRotateFeature->setEnabled( isEditable && canChangeGeometry );
1068410716
mActionNodeTool->setEnabled( isEditable && canChangeGeometry );
1068510717

@@ -10690,6 +10722,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
1069010722
{
1069110723
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
1069210724
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
10725+
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
1069310726

1069410727
mActionAddRing->setEnabled( false );
1069510728
mActionFillRing->setEnabled( false );
@@ -10718,6 +10751,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
1071810751
{
1071910752
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCaptureLine.svg" ) ) );
1072010753
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureLine.svg" ) ) );
10754+
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyLine.svg" ) ) );
1072110755

1072210756
mActionReshapeFeatures->setEnabled( isEditable && canChangeGeometry );
1072310757
mActionSplitFeatures->setEnabled( isEditable && canAddFeatures );
@@ -10733,6 +10767,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
1073310767
{
1073410768
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePolygon.svg" ) ) );
1073510769
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeature.svg" ) ) );
10770+
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopy.svg" ) ) );
1073610771

1073710772
mActionAddRing->setEnabled( isEditable && canChangeGeometry );
1073810773
mActionFillRing->setEnabled( isEditable && canChangeGeometry );
@@ -10819,6 +10854,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
1081910854
mActionAddPart->setEnabled( false );
1082010855
mActionNodeTool->setEnabled( false );
1082110856
mActionMoveFeature->setEnabled( false );
10857+
mActionMoveFeatureCopy->setEnabled( false );
1082210858
mActionRotateFeature->setEnabled( false );
1082310859
mActionOffsetCurve->setEnabled( false );
1082410860
mActionCopyFeatures->setEnabled( false );
@@ -11846,6 +11882,10 @@ void QgisApp::toolButtonActionTriggered( QAction *action )
1184611882
settings.setValue( QStringLiteral( "/UI/defaultMapService" ), 0 );
1184711883
else if ( action == mActionAddAmsLayer )
1184811884
settings.setValue( QStringLiteral( "/UI/defaultMapService" ), 1 );
11885+
else if ( action == mActionMoveFeature )
11886+
settings.setValue( QStringLiteral( "/UI/defaultMoveTool" ), 0 );
11887+
else if ( action == mActionMoveFeatureCopy )
11888+
settings.setValue( QStringLiteral( "/UI/defaultMoveTool" ), 1 );
1184911889

1185011890
bt->setDefaultAction( action );
1185111891
}

‎src/app/qgisapp.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
331331
QAction *actionDeleteSelected() { return mActionDeleteSelected; }
332332
QAction *actionAddFeature() { return mActionAddFeature; }
333333
QAction *actionMoveFeature() { return mActionMoveFeature; }
334+
QAction *actionMoveFeatureCopy() { return mActionMoveFeatureCopy; }
334335
QAction *actionRotateFeature() { return mActionRotateFeature;}
335336
QAction *actionSplitFeatures() { return mActionSplitFeatures; }
336337
QAction *actionSplitParts() { return mActionSplitParts; }
@@ -1068,6 +1069,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
10681069
void circularStringRadius();
10691070
//! activates the move feature tool
10701071
void moveFeature();
1072+
//! activates the copy and move feature tool
1073+
void moveFeatureCopy();
10711074
//! activates the offset curve tool
10721075
void offsetCurve();
10731076
//! activates the reshape features tool
@@ -1634,6 +1637,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
16341637
QgsMapTool *mCircularStringCurvePoint;
16351638
QgsMapTool *mCircularStringRadius;
16361639
QgsMapTool *mMoveFeature;
1640+
QgsMapTool *mMoveFeatureCopy;
16371641
QgsMapTool *mOffsetCurve;
16381642
QgsMapTool *mReshapeFeatures;
16391643
QgsMapTool *mSplitFeatures;

‎src/app/qgsguivectorlayertools.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,21 @@
1717
#include <QToolButton>
1818

1919
#include "qgsguivectorlayertools.h"
20-
#include "qgsvectorlayer.h"
21-
#include "qgsvectordataprovider.h"
22-
#include "qgsmessagebar.h"
20+
2321
#include "qgisapp.h"
2422
#include "qgsapplication.h"
25-
#include "qgsmessageviewer.h"
2623
#include "qgsfeatureaction.h"
24+
#include "qgslogger.h"
2725
#include "qgsmapcanvas.h"
26+
#include "qgsmessagebar.h"
2827
#include "qgsmessagebaritem.h"
28+
#include "qgsmessageviewer.h"
29+
#include "qgsvectordataprovider.h"
30+
#include "qgsvectorlayer.h"
2931

3032

3133
QgsGuiVectorLayerTools::QgsGuiVectorLayerTools()
32-
: QObject( nullptr )
34+
: QgsVectorLayerTools()
3335
{}
3436

3537
bool QgsGuiVectorLayerTools::addFeature( QgsVectorLayer* layer, const QgsAttributeMap& defaultValues, const QgsGeometry& defaultGeometry, QgsFeature* feat ) const
@@ -95,7 +97,6 @@ bool QgsGuiVectorLayerTools::saveEdits( QgsVectorLayer* layer ) const
9597
return res;
9698
}
9799

98-
99100
bool QgsGuiVectorLayerTools::stopEditing( QgsVectorLayer* layer, bool allowCancel ) const
100101
{
101102
bool res = true;

‎src/app/qgsguivectorlayertools.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* or a feature is added.
2424
*/
2525

26-
class QgsGuiVectorLayerTools : public QObject, public QgsVectorLayerTools
26+
class QgsGuiVectorLayerTools : public QgsVectorLayerTools
2727
{
2828
Q_OBJECT
2929

@@ -73,6 +73,7 @@ class QgsGuiVectorLayerTools : public QObject, public QgsVectorLayerTools
7373

7474
private:
7575
void commitError( QgsVectorLayer* vlayer ) const;
76+
7677
};
7778

7879
#endif // QGSGUIVECTORLAYERTOOLS_H

‎src/app/qgsmaptoolmovefeature.cpp

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,39 @@
1313
* *
1414
***************************************************************************/
1515

16-
#include "qgsmaptoolmovefeature.h"
16+
#include "qgisapp.h"
17+
#include "qgsadvanceddigitizingdockwidget.h"
1718
#include "qgsfeatureiterator.h"
1819
#include "qgsgeometry.h"
1920
#include "qgslogger.h"
2021
#include "qgsmapcanvas.h"
22+
#include "qgsmaptoolmovefeature.h"
2123
#include "qgsrubberband.h"
22-
#include "qgsvectorlayer.h"
2324
#include "qgstolerance.h"
24-
#include "qgisapp.h"
25-
#include "qgsadvanceddigitizingdockwidget.h"
25+
#include "qgsvectorlayer.h"
26+
#include "qgsvectorlayertools.h"
27+
2628

2729
#include <QMouseEvent>
2830
#include <QSettings>
2931
#include <limits>
3032

31-
QgsMapToolMoveFeature::QgsMapToolMoveFeature( QgsMapCanvas* canvas )
33+
34+
QgsMapToolMoveFeature::QgsMapToolMoveFeature( QgsMapCanvas* canvas , MoveMode mode )
3235
: QgsMapToolAdvancedDigitizing( canvas, QgisApp::instance()->cadDockWidget() )
3336
, mRubberBand( nullptr )
37+
, mMode( mode )
3438
{
3539
mToolName = tr( "Move feature" );
36-
mCaptureMode = QgsMapToolAdvancedDigitizing::CaptureSegment;
40+
switch ( mode )
41+
{
42+
case Move:
43+
mCaptureMode = QgsMapToolAdvancedDigitizing::CaptureSegment;
44+
break;
45+
case CopyMove:
46+
mCaptureMode = QgsMapToolAdvancedDigitizing::CaptureLine; // we copy/move several times
47+
break;
48+
}
3749
}
3850

3951
QgsMapToolMoveFeature::~QgsMapToolMoveFeature()
@@ -138,12 +150,12 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent* e )
138150
}
139151
else
140152
{
141-
delete mRubberBand;
142-
mRubberBand = nullptr;
143-
153+
// copy and move mode
144154
if ( e->button() != Qt::LeftButton )
145155
{
146156
cadDockWidget()->clear();
157+
delete mRubberBand;
158+
mRubberBand = nullptr;
147159
return;
148160
}
149161

@@ -152,13 +164,35 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent* e )
152164

153165
double dx = stopPointLayerCoords.x() - startPointLayerCoords.x();
154166
double dy = stopPointLayerCoords.y() - startPointLayerCoords.y();
155-
vlayer->beginEditCommand( tr( "Feature moved" ) );
156-
Q_FOREACH ( QgsFeatureId id, mMovedFeatures )
167+
168+
169+
vlayer->beginEditCommand( mMode == Move ? tr( "Feature moved" ) : tr( "Feature copied and moved" ) );
170+
171+
172+
switch ( mMode )
157173
{
158-
vlayer->translateFeature( id, dx, dy );
174+
case Move:
175+
Q_FOREACH ( QgsFeatureId id, mMovedFeatures )
176+
{
177+
vlayer->translateFeature( id, dx, dy );
178+
}
179+
delete mRubberBand;
180+
mRubberBand = nullptr;
181+
break;
182+
183+
case CopyMove:
184+
QgsFeatureRequest request;
185+
request.setFilterFids( mMovedFeatures );
186+
QString* errorMsg = new QString();
187+
if ( !QgisApp::instance()->vectorLayerTools()->copyMoveFeatures( vlayer, request, dx, dy, errorMsg ) )
188+
{
189+
emit messageEmitted( *errorMsg, QgsMessageBar::CRITICAL );
190+
delete mRubberBand;
191+
mRubberBand = nullptr;
192+
}
193+
break;
159194
}
160-
delete mRubberBand;
161-
mRubberBand = nullptr;
195+
162196
vlayer->endEditCommand();
163197
vlayer->triggerRepaint();
164198
}

‎src/app/qgsmaptoolmovefeature.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ class APP_EXPORT QgsMapToolMoveFeature: public QgsMapToolAdvancedDigitizing
2323
{
2424
Q_OBJECT
2525
public:
26-
QgsMapToolMoveFeature( QgsMapCanvas* canvas );
26+
//! Mode for moving features
27+
enum MoveMode
28+
{
29+
Move, //!< Move feature
30+
CopyMove //!< Copy and move feature
31+
};
32+
33+
QgsMapToolMoveFeature( QgsMapCanvas* canvas, MoveMode mode = Move );
2734
virtual ~QgsMapToolMoveFeature();
2835

2936
virtual void cadCanvasMoveEvent( QgsMapMouseEvent* e ) override;
@@ -45,6 +52,8 @@ class APP_EXPORT QgsMapToolMoveFeature: public QgsMapToolAdvancedDigitizing
4552

4653
QPoint mPressPos;
4754

55+
MoveMode mMode;
56+
4857
};
4958

5059
#endif

‎src/core/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ SET(QGIS_CORE_SRCS
213213
qgstextrenderer.cpp
214214
qgstolerance.cpp
215215
qgstracer.cpp
216+
qgstrackedvectorlayertools.cpp
216217
qgstransaction.cpp
217218
qgstransactiongroup.cpp
218219
qgsunittypes.cpp
@@ -230,6 +231,7 @@ SET(QGIS_CORE_SRCS
230231
qgsvectorlayerlabeling.cpp
231232
qgsvectorlayerlabelprovider.cpp
232233
qgsvectorlayerrenderer.cpp
234+
qgsvectorlayertools.cpp
233235
qgsvectorlayerundocommand.cpp
234236
qgsvectorlayerutils.cpp
235237
qgsvectorsimplifymethod.cpp
@@ -491,6 +493,7 @@ SET(QGIS_CORE_MOC_HDRS
491493
qgsrunprocess.h
492494
qgssnappingutils.h
493495
qgstracer.h
496+
qgstrackedvectorlayertools.h
494497
qgstransaction.h
495498
qgstransactiongroup.h
496499
qgsunittypes.h
@@ -500,6 +503,7 @@ SET(QGIS_CORE_MOC_HDRS
500503
qgsvectorlayereditpassthrough.h
501504
qgsvectorlayer.h
502505
qgsvectorlayerjoinbuffer.h
506+
qgsvectorlayertools.h
503507
qgsmapthemecollection.h
504508
qgswebpage.h
505509
qgswebview.h

‎src/gui/qgstrackedvectorlayertools.cpp renamed to ‎src/core/qgstrackedvectorlayertools.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include "qgsvectorlayer.h"
1818

1919
QgsTrackedVectorLayerTools::QgsTrackedVectorLayerTools()
20-
: mBackend( nullptr )
20+
: mBackend()
2121
{
2222
}
2323

@@ -57,6 +57,11 @@ bool QgsTrackedVectorLayerTools::saveEdits( QgsVectorLayer* layer ) const
5757
return mBackend->saveEdits( layer );
5858
}
5959

60+
bool QgsTrackedVectorLayerTools::copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest& request, double dx, double dy, QString* errorMsg ) const
61+
{
62+
return mBackend->copyMoveFeatures( layer, request, dx, dy, errorMsg );
63+
}
64+
6065
void QgsTrackedVectorLayerTools::setVectorLayerTools( const QgsVectorLayerTools* tools )
6166
{
6267
mBackend = tools;

‎src/gui/qgstrackedvectorlayertools.h renamed to ‎src/core/qgstrackedvectorlayertools.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@
2121
/** \ingroup gui
2222
* \class QgsTrackedVectorLayerTools
2323
*/
24-
class GUI_EXPORT QgsTrackedVectorLayerTools : public QgsVectorLayerTools
24+
class CORE_EXPORT QgsTrackedVectorLayerTools : public QgsVectorLayerTools
2525
{
26+
Q_OBJECT
2627
public:
2728
QgsTrackedVectorLayerTools();
2829

2930
bool addFeature( QgsVectorLayer* layer, const QgsAttributeMap& defaultValues, const QgsGeometry& defaultGeometry, QgsFeature* feature ) const override;
3031
bool startEditing( QgsVectorLayer* layer ) const override;
3132
bool stopEditing( QgsVectorLayer* layer, bool allowCancel ) const override;
3233
bool saveEdits( QgsVectorLayer* layer ) const override;
34+
bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request, double dx = 0, double dy = 0, QString *errorMsg = nullptr ) const override;
3335

3436
/**
3537
* Set the vector layer tools that will be used to interact with the data

‎src/core/qgsvectorlayertools.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/***************************************************************************
2+
qgsvectorlayertools.cpp
3+
---------------------
4+
begin : 09.11.2016
5+
copyright : (C) 2016 by Denis Rouzaud
6+
email : denis.rouzaud@gmail.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
17+
#include "qgsvectorlayer.h"
18+
#include "qgsvectorlayertools.h"
19+
#include "qgsfeaturerequest.h"
20+
#include "qgslogger.h"
21+
22+
23+
QgsVectorLayerTools::QgsVectorLayerTools()
24+
: QObject( nullptr )
25+
{}
26+
27+
28+
QgsVectorLayerTools::~QgsVectorLayerTools()
29+
{}
30+
31+
32+
bool QgsVectorLayerTools::copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest& request, double dx, double dy, QString* errorMsg ) const
33+
{
34+
bool res = false;
35+
if ( !layer || !layer->isEditable() )
36+
{
37+
return false;
38+
}
39+
40+
QgsFeatureIterator fi = layer->getFeatures( request );
41+
QgsFeature f;
42+
QgsAttributeList pkAttrList = layer->pkAttributeList();
43+
44+
int browsedFeatureCount = 0;
45+
int couldNotWriteCount = 0;
46+
int noGeometryCount = 0;
47+
48+
QgsFeatureIds fidList;
49+
50+
while ( fi.nextFeature( f ) )
51+
{
52+
browsedFeatureCount++;
53+
// remove pkey values
54+
Q_FOREACH ( auto idx, pkAttrList )
55+
{
56+
f.setAttribute( idx, QVariant() );
57+
}
58+
// translate
59+
if ( f.hasGeometry() )
60+
{
61+
QgsGeometry geom = f.geometry();
62+
geom.translate( dx, dy );
63+
f.setGeometry( geom );
64+
#ifdef QGISDEBUG
65+
const QgsFeatureId fid = f.id();
66+
#endif
67+
// paste feature
68+
if ( !layer->addFeature( f, false ) )
69+
{
70+
couldNotWriteCount++;
71+
QgsDebugMsg( QString( "Could not add new feature. Original copied feature id: %1" ).arg( fid ) );
72+
}
73+
else
74+
{
75+
fidList.insert( f.id() );
76+
}
77+
}
78+
else
79+
{
80+
noGeometryCount++;
81+
}
82+
}
83+
84+
request = QgsFeatureRequest();
85+
request.setFilterFids( fidList );
86+
87+
if ( !couldNotWriteCount && !noGeometryCount )
88+
{
89+
res = true;
90+
}
91+
else if ( errorMsg )
92+
{
93+
errorMsg = new QString( QString( tr( "Only %1 out of %2 features were copied." ) )
94+
.arg( browsedFeatureCount - couldNotWriteCount - noGeometryCount, browsedFeatureCount ) );
95+
if ( noGeometryCount )
96+
{
97+
errorMsg->append( " " );
98+
errorMsg->append( tr( "Some features have no geometry." ) );
99+
}
100+
if ( couldNotWriteCount )
101+
{
102+
errorMsg->append( " " );
103+
errorMsg->append( tr( "Some could not be created on the layer." ) );
104+
}
105+
}
106+
return res;
107+
}

‎src/gui/qgsvectorlayertools.h renamed to ‎src/core/qgsvectorlayertools.h

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
#ifndef QGSVECTORLAYERTOOLS_H
1717
#define QGSVECTORLAYERTOOLS_H
1818

19+
#include <QObject>
20+
1921
#include "qgsfeature.h"
2022
#include "qgsgeometry.h"
2123

24+
class QgsFeatureRequest;
2225
class QgsVectorLayer;
2326

2427
/** \ingroup gui
@@ -30,12 +33,14 @@ class QgsVectorLayer;
3033
* in your application.
3134
*
3235
*/
33-
class GUI_EXPORT QgsVectorLayerTools
36+
class CORE_EXPORT QgsVectorLayerTools : public QObject
3437
{
38+
Q_OBJECT
39+
3540
public:
36-
QgsVectorLayerTools() {}
41+
QgsVectorLayerTools();
3742

38-
virtual ~QgsVectorLayerTools() {}
43+
virtual ~QgsVectorLayerTools();
3944

4045
/**
4146
* This method should/will be called, whenever a new feature will be added to the layer
@@ -85,6 +90,20 @@ class GUI_EXPORT QgsVectorLayerTools
8590
*/
8691
virtual bool saveEdits( QgsVectorLayer* layer ) const = 0;
8792

93+
/**
94+
* Copy and move features with defined translation.
95+
*
96+
* @param layer The layer
97+
* @param request The request for the features to be moved. It will be assigned to a new feature request with the newly copied features.
98+
* @param dx The translation on x
99+
* @param dy The translation on y
100+
* @param errorMsg If given, it will contain the error message
101+
* @return True if all features could be copied.
102+
*
103+
* TODO QGIS 3: remove const qualifier
104+
*/
105+
virtual bool copyMoveFeatures( QgsVectorLayer* layer, QgsFeatureRequest &request, double dx = 0, double dy = 0, QString *errorMsg = nullptr ) const;
106+
88107
};
89108

90109
#endif // QGSVECTORLAYERTOOLS_H

‎src/gui/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,6 @@ SET(QGIS_GUI_SRCS
311311
qgstextannotationitem.cpp
312312
qgstextformatwidget.cpp
313313
qgstextpreview.cpp
314-
qgstrackedvectorlayertools.cpp
315314
qgstreewidgetitem.cpp
316315
qgsunitselectionwidget.cpp
317316
qgsuserinputdockwidget.cpp
@@ -662,9 +661,7 @@ SET(QGIS_GUI_HDRS
662661
qgssvgannotationitem.h
663662
qgstablewidgetitem.h
664663
qgstextannotationitem.h
665-
qgstrackedvectorlayertools.h
666664
qgsuserinputdockwidget.h
667-
qgsvectorlayertools.h
668665
qgsvertexmarker.h
669666
qgsfiledownloader.h
670667

‎src/ui/qgisapp.ui

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<x>0</x>
1818
<y>0</y>
1919
<width>1018</width>
20-
<height>28</height>
20+
<height>22</height>
2121
</rect>
2222
</property>
2323
<property name="toolTip">
@@ -369,7 +369,6 @@
369369
<addaction name="mActionToggleEditing"/>
370370
<addaction name="mActionSaveLayerEdits"/>
371371
<addaction name="mActionAddFeature"/>
372-
<addaction name="mActionMoveFeature"/>
373372
<addaction name="mActionNodeTool"/>
374373
<addaction name="mActionDeleteSelected"/>
375374
<addaction name="mActionCutFeatures"/>
@@ -2564,6 +2563,21 @@ Acts on currently active editable layer</string>
25642563
<string>F3</string>
25652564
</property>
25662565
</action>
2566+
<action name="mActionMoveFeatureCopy">
2567+
<property name="checkable">
2568+
<bool>true</bool>
2569+
</property>
2570+
<property name="icon">
2571+
<iconset resource="../../images/images.qrc">
2572+
<normaloff>:/images/themes/default/mActionMoveFeatureCopy.svg</normaloff>:/images/themes/default/mActionMoveFeatureCopy.svg</iconset>
2573+
</property>
2574+
<property name="text">
2575+
<string>Copy and Move Feature(s)</string>
2576+
</property>
2577+
<property name="toolTip">
2578+
<string>Copy and Move Feature(s)</string>
2579+
</property>
2580+
</action>
25672581
</widget>
25682582
<resources>
25692583
<include location="../../images/images.qrc"/>

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ ENDIF (WITH_DESKTOP)
143143
IF (ENABLE_PGTEST)
144144
ADD_PYTHON_TEST(PyQgsPostgresProvider test_provider_postgres.py)
145145
ADD_PYTHON_TEST(PyQgsRelationEditWidget test_qgsrelationeditwidget.py)
146+
ADD_PYTHON_TEST(PyQgsVectorLayerTools test_qgsvectorlayertools.py)
146147
ENDIF (ENABLE_PGTEST)
147148

148149
IF (ENABLE_MSSQLTEST)

‎tests/src/python/test_qgsrelationeditwidget.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@
2323
QgsRelation,
2424
QgsMapLayerRegistry,
2525
QgsTransaction,
26-
QgsFeatureRequest
26+
QgsFeatureRequest,
27+
QgsVectorLayerTools
2728
)
2829

2930
from qgis.gui import (
3031
QgsEditorWidgetRegistry,
3132
QgsRelationWidgetWrapper,
32-
QgsAttributeEditorContext,
33-
QgsVectorLayerTools
33+
QgsAttributeEditorContext
3434
)
3535

3636
from qgis.PyQt.QtCore import QTimer
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit test utils for provider tests.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
10+
from builtins import str
11+
from builtins import object
12+
__author__ = 'Denis Rouzaud'
13+
__date__ = '2016-11-07'
14+
__copyright__ = 'Copyright 2015, The QGIS Project'
15+
# This will get replaced with a git SHA1 when you do a git archive
16+
__revision__ = '$Format:%H$'
17+
18+
from qgis.core import QgsFeatureRequest, QgsVectorLayer, QgsMapLayerRegistry, QgsVectorLayerTools
19+
from qgis.testing import start_app, unittest
20+
21+
import os
22+
23+
start_app()
24+
25+
26+
class SubQgsVectorLayerTools(QgsVectorLayerTools):
27+
28+
def __init__(self):
29+
super().__init__()
30+
31+
def addFeature(self, layer):
32+
pass
33+
34+
def startEditing(self, layer):
35+
pass
36+
37+
def stopEditing(self, layer):
38+
pass
39+
40+
def saveEdits(self, layer):
41+
pass
42+
43+
44+
class TestQgsVectorLayerTools(unittest.TestCase):
45+
46+
@classmethod
47+
def setUpClass(cls):
48+
"""
49+
Setup the involved layers and relations for a n:m relation
50+
:return:
51+
"""
52+
cls.dbconn = 'service=\'qgis_test\''
53+
if 'QGIS_PGTEST_DB' in os.environ:
54+
cls.dbconn = os.environ['QGIS_PGTEST_DB']
55+
# Create test layer
56+
cls.vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."someData" (geom) sql=', 'layer', 'postgres')
57+
58+
QgsMapLayerRegistry.instance().addMapLayer(cls.vl)
59+
60+
cls.vltools = SubQgsVectorLayerTools()
61+
62+
def testCopyMoveFeature(self):
63+
""" Test copy and move features"""
64+
rqst = QgsFeatureRequest()
65+
rqst.setFilterFid(4)
66+
self.vl.startEditing()
67+
(ok, rqst, msg) = self.vltools.copyMoveFeatures(self.vl, rqst, -0.1, 0.2)
68+
self.assertTrue(ok)
69+
for f in self.vl.getFeatures(rqst):
70+
geom = f.geometry()
71+
self.assertAlmostEqual(geom.asPoint().x(), -65.42)
72+
self.assertAlmostEqual(geom.asPoint().y(), 78.5)
73+
74+
75+
if __name__ == '__main__':
76+
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.