diff --git a/images/images.qrc b/images/images.qrc index 2a2bbfe8bb4..aa79f2e192d 100755 --- a/images/images.qrc +++ b/images/images.qrc @@ -704,6 +704,7 @@ themes/default/mIconGPU.svg themes/default/mAddToProject.svg themes/default/mDockify.svg + themes/default/mActionReverseLine.svg themes/default/mActionAdd3DMap.svg diff --git a/images/themes/default/mActionReverseLine.svg b/images/themes/default/mActionReverseLine.svg new file mode 100644 index 00000000000..e40cab70bf6 --- /dev/null +++ b/images/themes/default/mActionReverseLine.svg @@ -0,0 +1,142 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 7d273efcd48..c2a5a59dd1c 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -98,6 +98,7 @@ SET(QGIS_APP_SRCS qgsmaptooloffsetpointsymbol.cpp qgsmaptoolpointsymbol.cpp qgsmaptoolreshape.cpp + qgsmaptoolreverseline.cpp qgsmaptoolrotatefeature.cpp qgsmaptoolrotatelabel.cpp qgsmaptoolrotatepointsymbols.cpp @@ -320,6 +321,7 @@ SET (QGIS_APP_MOC_HDRS qgsmaptooloffsetpointsymbol.h qgsmaptoolpointsymbol.h qgsmaptoolreshape.h + qgsmaptoolreverseline.h qgsmaptoolrotatefeature.h qgsmaptoolrotatelabel.h qgsmaptoolrotatepointsymbols.h diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index aa2d010c7cf..4245e25261e 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -403,6 +403,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsmaptoolmovelabel.h" #include "qgsmaptoolrotatelabel.h" #include "qgsmaptoolchangelabelproperties.h" +#include "qgsmaptoolreverseline.h" #include "vertextool/qgsvertextool.h" @@ -1466,6 +1467,7 @@ QgisApp::~QgisApp() delete mMapTools.mOffsetCurve; delete mMapTools.mPinLabels; delete mMapTools.mReshapeFeatures; + delete mMapTools.mReverseLine; delete mMapTools.mRotateFeature; delete mMapTools.mRotateLabel; delete mMapTools.mRotatePointSymbolsTool; @@ -2063,6 +2065,7 @@ void QgisApp::createActions() connect( mActionOffsetPointSymbol, &QAction::triggered, this, &QgisApp::offsetPointSymbol ); connect( mActionSnappingOptions, &QAction::triggered, this, &QgisApp::snappingOptions ); connect( mActionOffsetCurve, &QAction::triggered, this, &QgisApp::offsetCurve ); + connect( mActionReverseLine, &QAction::triggered, this, &QgisApp::reverseLine ); // View Menu Items connect( mActionPan, &QAction::triggered, this, &QgisApp::pan ); @@ -2354,6 +2357,7 @@ void QgisApp::createActionGroups() mMapToolGroup->addAction( mActionMoveLabel ); mMapToolGroup->addAction( mActionRotateLabel ); mMapToolGroup->addAction( mActionChangeLabelProperties ); + mMapToolGroup->addAction( mActionReverseLine ); // // Preview Modes Group @@ -3307,6 +3311,7 @@ void QgisApp::setTheme( const QString &themeName ) mActionDecorationNorthArrow->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/north_arrow.png" ) ) ); mActionDecorationScaleBar->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleBar.svg" ) ) ); mActionDecorationGrid->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) ); + mActionReverseLine->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReverseLine.svg" ) ) ); emit currentThemeChanged( themeName ); } @@ -3518,6 +3523,8 @@ void QgisApp::createCanvasTools() mMapTools.mOffsetCurve->setAction( mActionOffsetCurve ); mMapTools.mReshapeFeatures = new QgsMapToolReshape( mMapCanvas ); mMapTools.mReshapeFeatures->setAction( mActionReshapeFeatures ); + mMapTools.mReverseLine = new QgsMapToolReverseLine( mMapCanvas ); + mMapTools.mReverseLine->setAction( mActionReverseLine ); mMapTools.mSplitFeatures = new QgsMapToolSplitFeatures( mMapCanvas ); mMapTools.mSplitFeatures->setAction( mActionSplitFeatures ); mMapTools.mSplitParts = new QgsMapToolSplitParts( mMapCanvas ); @@ -7605,6 +7612,11 @@ void QgisApp::deletePart() mMapCanvas->setMapTool( mMapTools.mDeletePart ); } +void QgisApp::reverseLine() +{ + mMapCanvas->setMapTool( mMapTools.mReverseLine ); +} + QgsGeometry QgisApp::unionGeometries( const QgsVectorLayer *vl, QgsFeatureList &featureList, bool &canceled ) { canceled = false; @@ -12140,6 +12152,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer ) mActionPasteStyle->setEnabled( false ); mActionCopyLayer->setEnabled( false ); mActionPasteLayer->setEnabled( false ); + mActionReverseLine->setEnabled( false ); mUndoDock->widget()->setEnabled( false ); mActionUndo->setEnabled( false ); @@ -12214,6 +12227,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer ) mActionZoomToSelected->setEnabled( isSpatial ); mActionLabeling->setEnabled( isSpatial ); mActionDiagramProperties->setEnabled( isSpatial ); + mActionReverseLine->setEnabled( false ); mActionSelectFeatures->setEnabled( isSpatial ); mActionSelectPolygon->setEnabled( isSpatial ); @@ -12357,6 +12371,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer ) mActionSplitParts->setEnabled( isEditable && canChangeGeometry && isMultiPart ); mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry ); mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes ); + mActionReverseLine->setEnabled( isEditable && canChangeGeometry ); mActionAddRing->setEnabled( false ); mActionFillRing->setEnabled( false ); diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 17a729165e7..44d2d5823e2 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -1399,6 +1399,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void rotatePointSymbols(); //! activates the offset point symbol tool void offsetPointSymbol(); + //! activates the reverse line tool + void reverseLine(); //! activates the tool void setMapTool( QgsMapTool *tool, bool clean = false ); @@ -2056,6 +2058,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsMapTool *mRotateFeature = nullptr; QgsMapTool *mRotateLabel = nullptr; QgsMapTool *mChangeLabelProperties = nullptr; + QgsMapTool *mReverseLine = nullptr ; } mMapTools; QgsMapTool *mNonEditMapTool = nullptr; diff --git a/src/app/qgsmaptoolreverseline.cpp b/src/app/qgsmaptoolreverseline.cpp new file mode 100644 index 00000000000..2b5d2ec2dd4 --- /dev/null +++ b/src/app/qgsmaptoolreverseline.cpp @@ -0,0 +1,182 @@ +/*************************************************************************** + qgsmaptoolreverseline.cpp - reverse a line geometry + --------------------- + begin : April 2018 + copyright : (C) 2018 by Loïc Bartoletti + email : loic dot bartoletti at oslandia dot 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 "qgsmaptoolreverseline.h" + +#include "qgsfeatureiterator.h" +#include "qgsmapcanvas.h" +#include "qgsvertexmarker.h" +#include "qgsvectorlayer.h" +#include "qgsgeometry.h" +#include "qgsrubberband.h" +#include "qgssnappingutils.h" +#include "qgstolerance.h" +#include "qgisapp.h" +#include "qgslinestring.h" +#include "qgsmultilinestring.h" + +#include + +QgsMapToolReverseLine::QgsMapToolReverseLine( QgsMapCanvas *canvas ) + : QgsMapToolEdit( canvas ) +{ + mToolName = tr( "Reverse line geometry" ); +} + +QgsMapToolReverseLine::~QgsMapToolReverseLine() +{ +} + +void QgsMapToolReverseLine::canvasMoveEvent( QgsMapMouseEvent *e ) +{ + Q_UNUSED( e ); + //nothing to do +} + +void QgsMapToolReverseLine::canvasPressEvent( QgsMapMouseEvent *e ) +{ + mPressedFid = -1; + mPressedPartNum = -1; + + QgsMapLayer *currentLayer = mCanvas->currentLayer(); + if ( !currentLayer ) + return; + + vlayer = qobject_cast( currentLayer ); + if ( !vlayer ) + { + notifyNotVectorLayer(); + return; + } + + if ( !vlayer->isEditable() ) + { + notifyNotEditableLayer(); + return; + } + + QgsGeometry geomPart = partUnderPoint( e->pos(), mPressedFid, mPressedPartNum ); + + if ( mPressedFid != -1 ) + { + mRubberBand.reset( createRubberBand( vlayer->geometryType() ) ); + + mRubberBand->setToGeometry( geomPart, vlayer ); + mRubberBand->show(); + } + +} + +void QgsMapToolReverseLine::canvasReleaseEvent( QgsMapMouseEvent *e ) +{ + Q_UNUSED( e ); + + if ( !vlayer || !vlayer->isEditable() ) + { + return; + } + + if ( mPressedFid == -1 ) + return; + + QgsFeature f; + vlayer->getFeatures( QgsFeatureRequest().setFilterFid( mPressedFid ) ).nextFeature( f ); + QgsGeometry geom; + + if ( f.hasGeometry() ) + { + if ( f.geometry().isMultipart() ) + { + std::unique_ptr line_reversed( static_cast( f.geometry().constGet()->clone() ) ); + std::unique_ptr line_part( static_cast( line_reversed->geometryN( mPressedPartNum )->clone() ) ); + std::unique_ptr line_part_reversed( line_part->reversed() ); + line_reversed->removeGeometry( mPressedPartNum ); + line_reversed->insertGeometry( line_part_reversed.release(), mPressedPartNum ); + + geom = QgsGeometry( line_reversed.release() ); + + } + else + { + + geom = QgsGeometry( static_cast< const QgsCurve * >( f.geometry().constGet() )->reversed() ); + + } + + if ( geom ) + { + vlayer->beginEditCommand( tr( "Reverse line" ) ); + vlayer->changeGeometry( f.id(), geom ); + vlayer->endEditCommand(); + vlayer->triggerRepaint(); + emit messageEmitted( tr( "Line reversed." ) ); + } + else + { + emit messageEmitted( tr( "Couldn't reverse the selected part." ) ); + } + } +} + +QgsGeometry QgsMapToolReverseLine::partUnderPoint( QPoint point, QgsFeatureId &fid, int &partNum ) +{ + QgsFeature f; + QgsGeometry geomPart; + + switch ( vlayer->geometryType() ) + { + case QgsWkbTypes::LineGeometry: + { + QgsPointLocator::Match match = mCanvas->snappingUtils()->snapToCurrentLayer( point, QgsPointLocator::Types( QgsPointLocator::Vertex | QgsPointLocator::Edge ) ); + if ( !match.isValid() ) + return geomPart; + + int snapVertex = match.vertexIndex(); + vlayer->getFeatures( QgsFeatureRequest().setFilterFid( match.featureId() ) ).nextFeature( f ); + QgsGeometry g = f.geometry(); + if ( !g.isMultipart() ) + { + fid = match.featureId(); + return g; + } + else if ( QgsWkbTypes::geometryType( g.wkbType() ) == QgsWkbTypes::LineGeometry ) + { + QgsMultiPolylineXY mline = g.asMultiPolyline(); + for ( int part = 0; part < mline.count(); part++ ) + { + if ( snapVertex < mline[part].count() ) + { + fid = match.featureId(); + partNum = part; + return QgsGeometry::fromPolylineXY( mline[part] ); + } + snapVertex -= mline[part].count(); + } + } + break; + } + default: + { + break; + } + } + return geomPart; +} + +void QgsMapToolReverseLine::deactivate() +{ + QgsMapTool::deactivate(); +} + diff --git a/src/app/qgsmaptoolreverseline.h b/src/app/qgsmaptoolreverseline.h new file mode 100644 index 00000000000..2e034ac6c51 --- /dev/null +++ b/src/app/qgsmaptoolreverseline.h @@ -0,0 +1,56 @@ +/*************************************************************************** + qgsmaptoolreverseline.h - reverse a line geometry + --------------------- + begin : April 2018 + copyright : (C) 2018 by Loïc Bartoletti + email : loic dot bartoletti at oslandia dot 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. * + * * + ***************************************************************************/ + +#ifndef QGSMAPTOOLREVERSELINE_H +#define QGSMAPTOOLREVERSELINE_H + +#include "qgsmaptooledit.h" +#include "qgis_app.h" + +class QgsVertexMarker; + +//! Map tool to delete vertices from line/polygon features +class APP_EXPORT QgsMapToolReverseLine: public QgsMapToolEdit +{ + Q_OBJECT + + public: + QgsMapToolReverseLine( QgsMapCanvas *canvas ); + ~QgsMapToolReverseLine() override; + + void canvasMoveEvent( QgsMapMouseEvent *e ) override; + + void canvasPressEvent( QgsMapMouseEvent *e ) override; + + void canvasReleaseEvent( QgsMapMouseEvent *e ) override; + + //! called when map tool is being deactivated + void deactivate() override; + + private: + QgsVectorLayer *vlayer = nullptr; + + QgsGeometry partUnderPoint( QPoint p, QgsFeatureId &fid, int &partNum ); + + /* Rubberband that shows the part being reversed*/ + std::unique_ptrmRubberBand; + + //The feature and part where the mouse cursor was pressed + //This is used to check whether we are still in the same part at cursor release + QgsFeatureId mPressedFid = 0; + int mPressedPartNum = 0; +}; + +#endif // QGSMAPTOOLREVERSELINE_H diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index aea900f5df4..68bd085446f 100755 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -362,6 +362,7 @@ + @@ -468,6 +469,7 @@ + @@ -1047,6 +1049,18 @@ Offset Point Symbol + + + true + + + + :/images/themes/default/mActionReverseLine.svg:/images/themes/default/mActionReverseLine.svg + + + Reverse line + + &Snapping Options… diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 7a5bf565beb..fa74a667b8a 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -108,3 +108,5 @@ ADD_QGIS_TEST(maptoolregularpolygontest testqgsmaptoolregularpolygon.cpp) ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp) ADD_QGIS_TEST(vertextool testqgsvertextool.cpp) ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp) +ADD_QGIS_TEST(maptoolreverselinetest testqgsmaptoolreverseline.cpp) + diff --git a/tests/src/app/testqgsmaptoolreverseline.cpp b/tests/src/app/testqgsmaptoolreverseline.cpp new file mode 100644 index 00000000000..a893c127cb2 --- /dev/null +++ b/tests/src/app/testqgsmaptoolreverseline.cpp @@ -0,0 +1,190 @@ +/*************************************************************************** + TestQgsMapToolReverseLine.cpp + -------------------------------- + Date : May 2018 + Copyright : (C) 2018 by Loïc Bartoletti + Email : loic dot bartoletti at oslandia dot 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 "qgstest.h" + +#include "qgisapp.h" +#include "qgsgeometry.h" +#include "qgsmapcanvas.h" +#include "qgssettings.h" +#include "qgsvectorlayer.h" +#include "qgsmaptooladdfeature.h" + +#include "testqgsmaptoolutils.h" +#include "qgsmaptoolreverseline.h" + + +class TestQgsMapToolReverseLine : public QObject +{ + Q_OBJECT + + public: + TestQgsMapToolReverseLine(); + + private slots: + void initTestCase(); + void cleanupTestCase(); + + void testReverseCurve(); + void testReverseLineString(); + void testReverseMultiLineString(); + + private: + QgisApp *mQgisApp = nullptr; + QgsMapCanvas *mCanvas = nullptr; +}; + +TestQgsMapToolReverseLine::TestQgsMapToolReverseLine() = default; + + +//runs before all tests +void TestQgsMapToolReverseLine::initTestCase() +{ + QgsApplication::init(); + QgsApplication::initQgis(); + + mQgisApp = new QgisApp(); + + mCanvas = new QgsMapCanvas(); + mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) ); + +} + +void TestQgsMapToolReverseLine::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsMapToolReverseLine::testReverseCurve() +{ + //create a temporary layer + std::unique_ptr< QgsVectorLayer > memoryLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); + QVERIFY( memoryLayer->isValid() ); + QgsFeature curve( memoryLayer->dataProvider()->fields(), 1 ); + + curve.setAttribute( QStringLiteral( "pk" ), 1 ); + curve.setGeometry( QgsGeometry::fromWkt( QStringLiteral( + "CircularString(10 10, 5 5)" ) ) ); + + memoryLayer->dataProvider()->addFeatures( QgsFeatureList() << curve ); + + mCanvas->setLayers( QList() << memoryLayer.get() ); + mCanvas->setCurrentLayer( memoryLayer.get() ); + + std::unique_ptr< QgsMapToolReverseLine > tool( new QgsMapToolReverseLine( mCanvas ) ); + + memoryLayer->startEditing(); + QgsPointXY mapPoint = mCanvas->getCoordinateTransform()->transform( 5, 5 ); + std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + QPoint( mapPoint.x(), mapPoint.y() ) + ) ); + // trigger mouseRelease handler + tool->canvasPressEvent( event.get() ); + tool->canvasReleaseEvent( event.get() ); + QgsFeature f = memoryLayer->getFeature( 1 ); + + QString wkt = "CircularString (5 5, 10 10)"; + QCOMPARE( f.geometry().asWkt(), wkt ); + memoryLayer->rollBack(); + +} + +void TestQgsMapToolReverseLine::testReverseLineString() +{ + //create a temporary layer + std::unique_ptr< QgsVectorLayer > memoryLayer( new QgsVectorLayer( QStringLiteral( "LineStringZ?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); + QVERIFY( memoryLayer->isValid() ); + QgsFeature line( memoryLayer->dataProvider()->fields(), 1 ); + + line.setAttribute( QStringLiteral( "pk" ), 1 ); + line.setGeometry( QgsGeometry::fromWkt( QStringLiteral( + "LineStringZ(0 0 0, 10 10 10, 5 5 5)" ) ) ); + + memoryLayer->dataProvider()->addFeatures( QgsFeatureList() << line ); + mCanvas->setLayers( QList() << memoryLayer.get() ); + mCanvas->setCurrentLayer( memoryLayer.get() ); + + + std::unique_ptr< QgsMapToolReverseLine > tool( new QgsMapToolReverseLine( mCanvas ) ); + memoryLayer->startEditing(); + QgsPointXY mapPoint = mCanvas->getCoordinateTransform()->transform( 6, 6 ); + std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + QPoint( mapPoint.x(), mapPoint.y() ) + ) ); + // trigger mouseRelease handler + tool->canvasPressEvent( event.get() ); + tool->canvasReleaseEvent( event.get() ); + + QgsFeature f = memoryLayer->getFeature( 1 ); + + QString wkt = "LineStringZ (5 5 5, 10 10 10, 0 0 0)"; + QCOMPARE( f.geometry().asWkt(), wkt ); + + memoryLayer->rollBack(); +} + +void TestQgsMapToolReverseLine::testReverseMultiLineString() +{ + //create a temporary layer + std::unique_ptr< QgsVectorLayer > memoryLayer( new QgsVectorLayer( QStringLiteral( "MultiLineStringZ?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); + QVERIFY( memoryLayer->isValid() ); + QgsFeature multi( memoryLayer->dataProvider()->fields(), 1 ); + + multi.setAttribute( QStringLiteral( "pk" ), 1 ); + multi.setGeometry( QgsGeometry::fromWkt( QStringLiteral( + "MultiLineStringZ((0 0 0, 10 10 10, 5 5 5), (100 100 100, 120 120 120))" ) ) ); + + memoryLayer->dataProvider()->addFeatures( QgsFeatureList() << multi ); + mCanvas->setLayers( QList() << memoryLayer.get() ); + mCanvas->setCurrentLayer( memoryLayer.get() ); + + std::unique_ptr< QgsMapToolReverseLine > tool( new QgsMapToolReverseLine( mCanvas ) ); + + memoryLayer->startEditing(); + QgsPointXY mapPoint = mCanvas->getCoordinateTransform()->transform( 6, 6 ); + std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + QPoint( mapPoint.x(), mapPoint.y() ) + ) ); + // trigger mouseRelease handler + tool->canvasPressEvent( event.get() ); + tool->canvasReleaseEvent( event.get() ); + QgsFeature f = memoryLayer->getFeature( 1 ); + + QString wkt = "MultiLineStringZ ((5 5 5, 10 10 10, 0 0 0),(100 100 100, 120 120 120))"; + QCOMPARE( f.geometry().asWkt(), wkt ); + + mapPoint = mCanvas->getCoordinateTransform()->transform( 110, 110 ); + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + QPoint( mapPoint.x(), mapPoint.y() ) + ) ); + // trigger mouseRelease handler + tool->canvasPressEvent( event.get() ); + tool->canvasReleaseEvent( event.get() ); + f = memoryLayer->getFeature( 1 ); + + wkt = "MultiLineStringZ ((5 5 5, 10 10 10, 0 0 0),(120 120 120, 100 100 100))"; + QCOMPARE( f.geometry().asWkt(), wkt ); + memoryLayer->rollBack(); +} +QGSTEST_MAIN( TestQgsMapToolReverseLine ) +#include "testqgsmaptoolreverseline.moc"