diff --git a/images/themes/default/mActionRotatePointSymbols.png b/images/themes/default/mActionRotatePointSymbols.png new file mode 100644 index 00000000000..258093dac76 Binary files /dev/null and b/images/themes/default/mActionRotatePointSymbols.png differ diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 2459b95bd71..0b8ef990c38 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -40,6 +40,7 @@ SET(QGIS_APP_SRCS qgsmaptoolmovevertex.cpp qgsmaptoolnodetool.cpp qgsmaptoolreshape.cpp + qgsmaptoolrotatepointsymbols.cpp qgsmaptoolselect.cpp qgsmaptoolsimplify.cpp qgsmaptoolsplitfeatures.cpp @@ -52,6 +53,7 @@ SET(QGIS_APP_SRCS qgsogrsublayersdialog.cpp qgsoptions.cpp qgspastetransformations.cpp + qgspointrotationitem.cpp qgspluginitem.cpp qgspluginmanager.cpp qgspluginmetadata.cpp diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 2c739c75fbd..112841ebb25 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -172,6 +172,7 @@ #include "qgsmaptoolpan.h" #include "qgsmaptoolselect.h" #include "qgsmaptoolreshape.h" +#include "qgsmaptoolrotatepointsymbols.h" #include "qgsmaptoolsplitfeatures.h" #include "qgsmaptoolvertexedit.h" #include "qgsmaptoolzoom.h" @@ -740,6 +741,12 @@ void QgisApp::createActions() connect( mActionNodeTool, SIGNAL( triggered() ), this, SLOT( nodeTool() ) ); mActionNodeTool->setEnabled( false ); + mActionRotatePointSymbols = new QAction( getThemeIcon( "mActionRotatePointSymbols.png" ), tr( "Rotate Point Symbols" ), this ); + shortcuts->registerAction( mActionRotatePointSymbols ); + mActionRotatePointSymbols->setStatusTip( tr( "Rotate Point Symbols" ) ); + connect( mActionRotatePointSymbols, SIGNAL( triggered() ), this, SLOT( rotatePointSymbols() ) ); + mActionRotatePointSymbols->setEnabled( false ); + // View Menu Items mActionPan = new QAction( getThemeIcon( "mActionPan.png" ), tr( "Pan Map" ), this ); @@ -1094,7 +1101,8 @@ void QgisApp::createActionGroups() mMapToolGroup->addAction( mActionMergeFeatures ); mActionNodeTool->setCheckable( true ); mMapToolGroup->addAction( mActionNodeTool ); - + mActionRotatePointSymbols->setCheckable( true ); + mMapToolGroup->addAction( mActionRotatePointSymbols ); } void QgisApp::createMenus() @@ -1189,6 +1197,7 @@ void QgisApp::createMenus() mEditMenu->addAction( mActionSplitFeatures ); mEditMenu->addAction( mActionMergeFeatures ); mEditMenu->addAction( mActionNodeTool ); + mEditMenu->addAction( mActionRotatePointSymbols ); if ( layout == QDialogButtonBox::GnomeLayout || layout == QDialogButtonBox::MacLayout ) { @@ -1398,6 +1407,7 @@ void QgisApp::createToolBars() mAdvancedDigitizeToolBar->addAction( mActionSplitFeatures ); mAdvancedDigitizeToolBar->addAction( mActionMergeFeatures ); mAdvancedDigitizeToolBar->addAction( mActionNodeTool ); + mAdvancedDigitizeToolBar->addAction( mActionRotatePointSymbols ); mToolbarMenu->addAction( mAdvancedDigitizeToolBar->toggleViewAction() ); @@ -1788,6 +1798,8 @@ void QgisApp::createCanvas() mMapTools.mDeletePart->setAction( mActionDeletePart ); mMapTools.mNodeTool = new QgsMapToolNodeTool( mMapCanvas ); mMapTools.mNodeTool->setAction( mActionNodeTool ); + mMapTools.mRotatePointSymbolsTool = new QgsMapToolRotatePointSymbols( mMapCanvas ); + mMapTools.mRotatePointSymbolsTool->setAction( mActionRotatePointSymbols ); //ensure that non edit tool is initialised or we will get crashes in some situations mNonEditMapTool = mMapTools.mPan; } @@ -4298,6 +4310,11 @@ void QgisApp::nodeTool() mMapCanvas->setMapTool( mMapTools.mNodeTool ); } +void QgisApp::rotatePointSymbols() +{ + mMapCanvas->setMapTool( mMapTools.mRotatePointSymbolsTool ); +} + void QgisApp::splitFeatures() { mMapCanvas->setMapTool( mMapTools.mSplitFeatures ); @@ -5639,11 +5656,19 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionSplitFeatures->setEnabled( false ); mActionSimplifyFeature->setEnabled( false ); mActionDeleteRing->setEnabled( false ); + mActionRotatePointSymbols->setEnabled( false ); if ( vlayer->isEditable() && dprovider->capabilities() & QgsVectorDataProvider::ChangeGeometries ) { mActionMoveVertex->setEnabled( true ); } + if ( vlayer->isEditable() && dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues ) + { + if ( QgsMapToolRotatePointSymbols::layerIsRotatable( vlayer ) ) + { + mActionRotatePointSymbols->setEnabled( true ); + } + } return; } else if ( vlayer->geometryType() == QGis::Line ) @@ -5752,6 +5777,9 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionCopyFeatures->setEnabled( false ); mActionCutFeatures->setEnabled( false ); mActionPasteFeatures->setEnabled( false ); + mActionRotatePointSymbols->setEnabled( false ); + mActionNodeTool->setEnabled( false ); + mActionDeletePart->setEnabled( false ); //NOTE: This check does not really add any protection, as it is called on load not on layer select/activate //If you load a layer with a provider and idenitfy ability then load another without, the tool would be disabled for both diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index a0f9613bfab..b00937c162f 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -523,6 +523,8 @@ class QgisApp : public QMainWindow void mergeSelectedFeatures(); //! provides operations with nodes void nodeTool(); + //! activates the rotate points tool + void rotatePointSymbols(); //! activates the selection tool void select(); @@ -742,6 +744,7 @@ class QgisApp : public QMainWindow QAction *mActionDeletePart; QAction *mActionMergeFeatures; QAction *mActionNodeTool; + QAction *mActionRotatePointSymbols; QAction *mActionEditSeparator3; QAction *mActionPan; @@ -864,6 +867,7 @@ class QgisApp : public QMainWindow QgsMapTool* mDeleteRing; QgsMapTool* mDeletePart; QgsMapTool* mNodeTool; + QgsMapTool* mRotatePointSymbolsTool; } mMapTools; QgsMapTool *mNonEditMapTool; diff --git a/src/app/qgsmaptoolrotatepointsymbols.cpp b/src/app/qgsmaptoolrotatepointsymbols.cpp new file mode 100644 index 00000000000..ae8d4931307 --- /dev/null +++ b/src/app/qgsmaptoolrotatepointsymbols.cpp @@ -0,0 +1,250 @@ +/*************************************************************************** + qgsmaptoolrotatepointsymbols.cpp + -------------------------------- + begin : September 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco at hugis dot net + *************************************************************************** + * * + * 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 "qgsmaptoolrotatepointsymbols.h" +#include "qgsapplication.h" +#include "qgsmapcanvas.h" +#include "qgspointrotationitem.h" +#include "qgsrenderer.h" +#include "qgssymbol.h" +#include "qgsvectorlayer.h" +#include +#include +#include + +QgsMapToolRotatePointSymbols::QgsMapToolRotatePointSymbols( QgsMapCanvas* canvas ): QgsMapToolEdit( canvas ), \ + mActiveLayer( 0 ), mFeatureNumber( 0 ), mCurrentMouseAzimut( 0.0 ), mCurrentRotationFeature( 0.0 ), mRotating( false ), mRotationItem( 0 ) +{ + +} + +QgsMapToolRotatePointSymbols::~QgsMapToolRotatePointSymbols() +{ + delete mRotationItem; +} + +bool QgsMapToolRotatePointSymbols::layerIsRotatable( QgsMapLayer* ml ) +{ + if ( !ml ) + { + return false; + } + + //a vector layer + QgsVectorLayer* vLayer = dynamic_cast( ml ); + if ( !vLayer ) + { + return false; + } + + //does it have point or multipoint type? + if ( vLayer->geometryType() != QGis::Point ) + { + return false; + } + + //does it have a least one rotation attribute? + QList rotationAttributes; + layerRotationAttributes( vLayer, rotationAttributes ); + if ( rotationAttributes.size() < 1 ) + { + return false; + } + return true; +} + +void QgsMapToolRotatePointSymbols::canvasPressEvent( QMouseEvent * e ) +{ + if ( !mCanvas ) + { + return; + } + + mActiveLayer = currentVectorLayer(); + if ( !mActiveLayer ) + { + return; + } + + if ( mActiveLayer->geometryType() != QGis::Point || !mActiveLayer->isEditable() ) + { + return; + } + + //find the closest feature to the pressed position + QgsMapCanvasSnapper canvasSnapper( mCanvas ); + QList snapResults; + if ( canvasSnapper.snapToCurrentLayer( e->pos(), snapResults, QgsSnapper::SnapToVertex, -1 ) != 0 || snapResults.size() < 1 ) + { + QMessageBox::critical( 0, tr( "No point feature" ), tr( "No point feature was detected at the clicked position. Please click closer to the feature or enhance the search tolerance under Settings->Options->Digitizing->Serch radius for vertex edits" ) ); + return; //error during snapping + } + + mFeatureNumber = snapResults.at( 0 ).snappedAtGeometry; + + //get list with renderer rotation attributes + if ( layerRotationAttributes( mActiveLayer, mCurrentRotationAttributes ) != 0 ) + { + return; + } + + if ( mCurrentRotationAttributes.size() < 1 ) + { + QMessageBox::critical( 0, tr( "No rotation Attributes" ), tr( "The active point layer does not have a rotation attribute" ) ); + return; + } + + mSnappedPoint = toCanvasCoordinates( snapResults.at( 0 ).snappedVertex ); + + //find out initial arrow direction + QgsFeature pointFeature; + if ( !mActiveLayer->featureAtId( mFeatureNumber, pointFeature, false, true ) ) + { + return; + } + const QgsAttributeMap pointFeatureAttributes = pointFeature.attributeMap(); + const QgsAttributeMap::const_iterator attIt = pointFeatureAttributes.find( mCurrentRotationAttributes.at( 0 ) ); + if ( attIt == pointFeatureAttributes.constEnd() ) + { + return; + } + + mCurrentRotationFeature = attIt.value().toDouble(); + createPixmapItem(); + if ( mRotationItem ) + { + mRotationItem->setPointLocation( snapResults.at( 0 ).snappedVertex ); + } + mCurrentMouseAzimut = calculateAzimut( e->pos() ); + setPixmapItemRotation( mCurrentMouseAzimut ); + mRotating = true; +} + +void QgsMapToolRotatePointSymbols::canvasMoveEvent( QMouseEvent * e ) +{ + if ( !mRotating ) + { + return; + } + + double azimut = calculateAzimut( e->pos() ); + double azimutDiff = azimut - mCurrentMouseAzimut; + + //assign new feature rotation, making sure to respect the 0 - 360 degree range + mCurrentRotationFeature += azimutDiff; + if ( mCurrentRotationFeature < 0 ) + { + mCurrentRotationFeature = 360 - mCurrentRotationFeature; + } + else if ( mCurrentRotationFeature >= 360 ) + { + mCurrentRotationFeature -= 360; + } + mCurrentMouseAzimut = azimut; + if ( mCurrentMouseAzimut < 0 ) + { + mCurrentMouseAzimut = 360 - mCurrentMouseAzimut; + } + else if ( mCurrentMouseAzimut >= 360 ) + { + mCurrentMouseAzimut -= 360; + } + setPixmapItemRotation( mCurrentRotationFeature ); +} + +void QgsMapToolRotatePointSymbols::canvasReleaseEvent( QMouseEvent * e ) +{ + if ( mRotating && mActiveLayer ) + { + mActiveLayer->beginEditCommand( tr( "Rotate symbol" ) ); + bool rotateSuccess = true; + + //write mCurrentRotationFeature to all rotation attributes of feature (mFeatureNumber) + QList::const_iterator it = mCurrentRotationAttributes.constBegin(); + for ( ; it != mCurrentRotationAttributes.constEnd(); ++it ) + { + if ( !mActiveLayer->changeAttributeValue( mFeatureNumber, *it, mCurrentRotationFeature, true ) ) + { + rotateSuccess = false; + } + } + + if ( rotateSuccess ) + { + mActiveLayer->endEditCommand(); + } + else + { + mActiveLayer->destroyEditCommand(); + } + } + mRotating = false; + delete mRotationItem; + mRotationItem = 0; + mCanvas->refresh(); +} + +int QgsMapToolRotatePointSymbols::layerRotationAttributes( const QgsVectorLayer* vl, QList& attList ) +{ + attList.clear(); + if ( !vl ) + { + return 1; + } + + //get renderer + const QgsRenderer* layerRenderer = vl->renderer(); + if ( !layerRenderer ) + { + return 2; + } + + //get renderer symbols + const QList rendererSymbols = layerRenderer->symbols(); + int currentRotationAttribute; + + QList::const_iterator symbolIt = rendererSymbols.constBegin(); + for ( ; symbolIt != rendererSymbols.constEnd(); ++symbolIt ) + { + currentRotationAttribute = ( *symbolIt )->rotationClassificationField(); + if ( currentRotationAttribute >= 0 ) + { + attList.push_back( currentRotationAttribute ); + } + } + return 0; +} + +double QgsMapToolRotatePointSymbols::calculateAzimut( const QPoint& mousePos ) +{ + int dx = mousePos.x() - mSnappedPoint.x(); + int dy = mousePos.y() - mSnappedPoint.y(); + return 180 - atan2( dx, dy ) * 180.0 / M_PI; +} + +void QgsMapToolRotatePointSymbols::createPixmapItem() +{ + delete mRotationItem; + mRotationItem = new QgsPointRotationItem( mCanvas ); + mRotationItem->setSymbol( QgsApplication::defaultThemePath() + "mActionArrowUp.png" ); + mCanvas->scene()->addItem( mRotationItem ); +} + +void QgsMapToolRotatePointSymbols::setPixmapItemRotation( double rotation ) +{ + mRotationItem->setSymbolRotation( rotation ); + mRotationItem->update(); +} + diff --git a/src/app/qgsmaptoolrotatepointsymbols.h b/src/app/qgsmaptoolrotatepointsymbols.h new file mode 100644 index 00000000000..435958ce4cf --- /dev/null +++ b/src/app/qgsmaptoolrotatepointsymbols.h @@ -0,0 +1,68 @@ +/*************************************************************************** + qgsmaptoolrotatepointsymbols.h + --------------------- + begin : September 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco at hugis dot net + *************************************************************************** + * * + * 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 QGSMAPTOOLROTATEPOINTSYMBOLS_H +#define QGSMAPTOOLROTATEPOINTSYMBOLS_H + +#include "qgsmaptooledit.h" + +class QgsPointRotationItem; + +/**A class that allows to interactively manipulate the value of the rotation field(s) for point layers*/ +class QgsMapToolRotatePointSymbols: public QgsMapToolEdit +{ + public: + QgsMapToolRotatePointSymbols( QgsMapCanvas* canvas ); + ~QgsMapToolRotatePointSymbols(); + + void canvasPressEvent( QMouseEvent * e ); + void canvasMoveEvent( QMouseEvent * e ); + void canvasReleaseEvent( QMouseEvent * e ); + + bool isEditTool() {return true;} + + /**Returns true if the symbols of a maplayer can be rotated. This means the layer \ + is a vector layer, has type point or multipoint and has at least one rotation attribute in the renderer*/ + static bool layerIsRotatable( QgsMapLayer* ml ); + + private: + QgsVectorLayer* mActiveLayer; + int mFeatureNumber; + /**Last azimut between mouse and edited point*/ + double mCurrentMouseAzimut; + /**Last feature rotation*/ + double mCurrentRotationFeature; + bool mRotating; + QList mCurrentRotationAttributes; + /**Screen coordinate of the snaped feature*/ + QPoint mSnappedPoint; + /**Item that displays rotation during mouse move*/ + QgsPointRotationItem* mRotationItem; + + /**Finds out the rotation attributes of mActiveLayers + @param vl the point vector layer + @param attList out: the list containing the rotation indices + @return 0 in case of success*/ + static int layerRotationAttributes( const QgsVectorLayer* vl, QList& attList ); + void drawArrow( double azimut ) const; + /**Calculates the azimut between mousePos and mSnappedPoint*/ + double calculateAzimut( const QPoint& mousePos ); + /**Create item that shows rotation to the user*/ + void createPixmapItem(); + /**Sets the rotation of the pixmap item*/ + void setPixmapItemRotation( double rotation ); +}; + +#endif // QGSMAPTOOLROTATEPOINTSYMBOLS_H diff --git a/src/app/qgspointrotationitem.cpp b/src/app/qgspointrotationitem.cpp new file mode 100644 index 00000000000..a15fdb4a389 --- /dev/null +++ b/src/app/qgspointrotationitem.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + qgspointrotationitem.cpp + ------------------------ + begin : September 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco at hugis dot net + *************************************************************************** + * * + * 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 "qgspointrotationitem.h" +#include + +#ifndef Q_OS_MACX +#include +#else +#include +#endif + +QgsPointRotationItem::QgsPointRotationItem( QgsMapCanvas* canvas ): QgsMapCanvasItem( canvas ), mRotation( 0.0 ) +{ + //setup font + mFont.setPointSize( 12 ); + mFont.setBold( true ); +} + +QgsPointRotationItem::QgsPointRotationItem(): QgsMapCanvasItem( 0 ), mRotation( 0.0 ) +{ + +} + +QgsPointRotationItem::~QgsPointRotationItem() +{ + +} + +void QgsPointRotationItem::paint( QPainter * painter ) +{ + if ( !painter ) + { + return; + } + painter->save(); + + //do a bit of trigonometry to find out how to transform a rotated item such that the center point is at the point feature + double x = 0; + double y = 0; + double h, dAngel; + if ( mPixmap.width() > 0 && mPixmap.height() > 0 ) + { + h = sqrt( mPixmap.width() * mPixmap.width() + mPixmap.height() * mPixmap.height() ) / 2; //the half of the item diagonal + dAngel = acos( mPixmap.width() / ( h * 2 ) ) * 180 / M_PI; //the diagonal angel of the original rect + x = h * cos(( mRotation - dAngel ) * M_PI / 180 ); + y = h * sin(( mRotation - dAngel ) * M_PI / 180 ); + } + + //painter->translate(-mPixmap.width() / 2.0, -mPixmap.width() / 2.0); + painter->rotate( mRotation ); + painter->translate( x - mPixmap.width() / 2.0, -y - mPixmap.height() / 2.0 ); + painter->drawPixmap( 0, 0, mPixmap ); + + //draw numeric value beside the symbol + painter->restore(); + QFontMetricsF fm( mFont ); + painter->fillRect( mPixmap.width(), 0, mItemSize.width() - mPixmap.width(), mItemSize.height(), QColor( Qt::white ) ); + painter->setFont( mFont ); + painter->drawText( mPixmap.width(), mPixmap.height() / 2.0 + fm.height() / 2.0, QString::number( mRotation, 'f', 2 ) ); +} + +void QgsPointRotationItem::setPointLocation( const QgsPoint& p ) +{ + QPointF transformedPoint = toCanvasCoordinates( p ); + setPos( transformedPoint.x() - mPixmap.width() / 2.0, transformedPoint.y() - mPixmap.height() / 2.0 ); +} + +void QgsPointRotationItem::setSymbol( const QString& symbolPath ) +{ + mPixmap = QPixmap( symbolPath ); + QFontMetricsF fm( mFont ); + mItemSize.setWidth( mPixmap.width() + fm.width( "360.99" ) ); + double pixmapHeight = mPixmap.height(); + double fontHeight = fm.height(); + if ( pixmapHeight >= fontHeight ) + { + mItemSize.setHeight( mPixmap.height() ); + } + else + { + mItemSize.setHeight( fm.height() ); + } +} + diff --git a/src/app/qgspointrotationitem.h b/src/app/qgspointrotationitem.h new file mode 100644 index 00000000000..a6ad5acaed0 --- /dev/null +++ b/src/app/qgspointrotationitem.h @@ -0,0 +1,51 @@ +/*************************************************************************** + qgspointrotationitem.h + ---------------------- + begin : September 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco at hugis dot net + *************************************************************************** + * * + * 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 QGSPOINTROTATIONITEM_H +#define QGSPOINTROTATIONITEM_H + +#include "qgsmapcanvasitem.h" +#include +#include + +/**An item that shows a rotated point symbol (e.g. arrow) centered to a map location together with a text displaying the rotation value*/ +class QgsPointRotationItem: public QgsMapCanvasItem +{ + public: + QgsPointRotationItem( QgsMapCanvas* canvas ); + ~QgsPointRotationItem(); + + void paint( QPainter * painter ); + + /**Sets the center point of the rotation symbol (in map coordinates)*/ + void setPointLocation( const QgsPoint& p ); + + /**Sets the rotation of the symbol and displays the new rotation number. \ + Units are degrees, starting from north direction, clockwise direction*/ + void setSymbolRotation( double r ) {mRotation = r;} + + /**Sets a symbol from image file*/ + void setSymbol( const QString& symbolPath ); + + private: + QgsPointRotationItem(); + /**Font to display the numerical rotation values*/ + QFont mFont; + /**Symboll pixmap*/ + QPixmap mPixmap; + double mRotation; +}; + +#endif // QGSPOINTROTATIONITEM_H