Skip to content

Commit acc1d55

Browse files
committed
[FEATURE] add "feature action" map tool to run actions directly when clicking on features (fix #119 and #4262)
work done for Regione Toscana-SIGTA
1 parent 1fe82b3 commit acc1d55

9 files changed

+313
-2
lines changed

src/app/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ SET(QGIS_APP_SRCS
5050
qgsmaptooldeletepart.cpp
5151
qgsmaptooldeletevertex.cpp
5252
qgsmaptooledit.cpp
53+
qgsmaptoolfeatureaction.cpp
5354
qgsmaptoolformannotation.cpp
5455
qgsmaptoolidentify.cpp
5556
qgsmaptoollabel.cpp
@@ -189,6 +190,7 @@ SET (QGIS_APP_MOC_HDRS
189190
qgsmaptooldeletepart.h
190191
qgsmaptooldeletering.h
191192
qgsmaptooldeletevertex.h
193+
qgsmaptoolfeatureaction.h
192194
qgsmaptoolidentify.h
193195
qgsmaptoolmeasureangle.h
194196
qgsmaptoolmovefeature.h

src/app/qgisapp.cpp

+64-1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205
#include "qgsmaptooldeletering.h"
206206
#include "qgsmaptooldeletepart.h"
207207
#include "qgsmaptooldeletevertex.h"
208+
#include "qgsmaptoolfeatureaction.h"
208209
#include "qgsmaptoolformannotation.h"
209210
#include "qgsmaptoolidentify.h"
210211
#include "qgsmaptoolmeasureangle.h"
@@ -640,6 +641,7 @@ QgisApp::~QgisApp()
640641
delete mMapTools.mZoomOut;
641642
delete mMapTools.mPan;
642643
delete mMapTools.mIdentify;
644+
delete mMapTools.mFeatureAction;
643645
delete mMapTools.mMeasureDist;
644646
delete mMapTools.mMeasureArea;
645647
delete mMapTools.mMeasureAngle;
@@ -814,6 +816,7 @@ void QgisApp::createActions()
814816
connect( mActionSelectRadius, SIGNAL( triggered() ), this, SLOT( selectByRadius() ) );
815817
connect( mActionDeselectAll, SIGNAL( triggered() ), this, SLOT( deselectAll() ) );
816818
connect( mActionIdentify, SIGNAL( triggered() ), this, SLOT( identify() ) );
819+
connect( mActionFeatureAction, SIGNAL( triggered() ), this, SLOT( doFeatureAction() ) );
817820
connect( mActionMeasure, SIGNAL( triggered() ), this, SLOT( measure() ) );
818821
connect( mActionMeasureArea, SIGNAL( triggered() ), this, SLOT( measureArea() ) );
819822
connect( mActionMeasureAngle, SIGNAL( triggered() ), this, SLOT( measureAngle() ) );
@@ -989,6 +992,7 @@ void QgisApp::createActionGroups()
989992
mMapToolGroup->addAction( mActionZoomIn );
990993
mMapToolGroup->addAction( mActionZoomOut );
991994
mMapToolGroup->addAction( mActionIdentify );
995+
mMapToolGroup->addAction( mActionFeatureAction );
992996
mMapToolGroup->addAction( mActionSelect );
993997
mMapToolGroup->addAction( mActionSelectRectangle );
994998
mMapToolGroup->addAction( mActionSelectPolygon );
@@ -1172,6 +1176,18 @@ void QgisApp::createToolBars()
11721176
selectAction->setObjectName( "ActionSelect" );
11731177
connect( bt, SIGNAL( triggered( QAction * ) ), this, SLOT( toolButtonActionTriggered( QAction * ) ) );
11741178

1179+
// feature action tool button
1180+
1181+
bt = new QToolButton( mAttributesToolBar );
1182+
bt->setPopupMode( QToolButton::MenuButtonPopup );
1183+
bt->setDefaultAction( mActionFeatureAction );
1184+
mFeatureActionMenu = new QMenu( bt );
1185+
connect( mFeatureActionMenu, SIGNAL( triggered( QAction * ) ), this, SLOT( updateDefaultFeatureAction( QAction * ) ) );
1186+
connect( mFeatureActionMenu, SIGNAL( aboutToShow() ), this, SLOT( refreshFeatureActions() ) );
1187+
bt->setMenu( mFeatureActionMenu );
1188+
QAction* featureActionAction = mAttributesToolBar->insertWidget( selectAction, bt );
1189+
featureActionAction->setObjectName( "ActionFeatureAction" );
1190+
11751191
// measure tool button
11761192

11771193
bt = new QToolButton( mAttributesToolBar );
@@ -1478,6 +1494,7 @@ void QgisApp::setTheme( QString theThemeName )
14781494
mActionZoomToLayer->setIcon( getThemeIcon( "/mActionZoomToLayer.png" ) );
14791495
mActionZoomActualSize->setIcon( getThemeIcon( "/mActionZoomActual.png" ) );
14801496
mActionIdentify->setIcon( getThemeIcon( "/mActionIdentify.png" ) );
1497+
mActionFeatureAction->setIcon( getThemeIcon( "/mAction.png" ) );
14811498
mActionSelect->setIcon( getThemeIcon( "/mActionSelect.png" ) );
14821499
mActionSelectRectangle->setIcon( getThemeIcon( "/mActionSelectRectangle.png" ) );
14831500
mActionSelectPolygon->setIcon( getThemeIcon( "/mActionSelectPolygon.png" ) );
@@ -1604,6 +1621,8 @@ void QgisApp::createCanvasTools()
16041621
mMapTools.mPan->setAction( mActionPan );
16051622
mMapTools.mIdentify = new QgsMapToolIdentify( mMapCanvas );
16061623
mMapTools.mIdentify->setAction( mActionIdentify );
1624+
mMapTools.mFeatureAction = new QgsMapToolFeatureAction( mMapCanvas );
1625+
mMapTools.mFeatureAction->setAction( mActionFeatureAction );
16071626
mMapTools.mMeasureDist = new QgsMeasureTool( mMapCanvas, false /* area */ );
16081627
mMapTools.mMeasureDist->setAction( mActionMeasure );
16091628
mMapTools.mMeasureArea = new QgsMeasureTool( mMapCanvas, true /* area */ );
@@ -3274,6 +3293,48 @@ void QgisApp::identify()
32743293
mMapCanvas->setMapTool( mMapTools.mIdentify );
32753294
}
32763295

3296+
void QgisApp::doFeatureAction()
3297+
{
3298+
mMapCanvas->setMapTool( mMapTools.mFeatureAction );
3299+
}
3300+
3301+
void QgisApp::updateDefaultFeatureAction( QAction *action )
3302+
{
3303+
foreach( QAction *a, mFeatureActionMenu->actions() )
3304+
{
3305+
a->setChecked( a == action );
3306+
}
3307+
3308+
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
3309+
if ( !vlayer )
3310+
return;
3311+
3312+
int index = mFeatureActionMenu->actions().indexOf( action );
3313+
vlayer->actions()->setDefaultAction( index );
3314+
3315+
doFeatureAction();
3316+
}
3317+
3318+
void QgisApp::refreshFeatureActions()
3319+
{
3320+
mFeatureActionMenu->clear();
3321+
3322+
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
3323+
if ( !vlayer )
3324+
return;
3325+
3326+
QgsAttributeAction *actions = vlayer->actions();
3327+
for ( int i = 0; i < actions->size(); i++ )
3328+
{
3329+
QAction *action = mFeatureActionMenu->addAction( actions->at( i ).name() );
3330+
action->setCheckable( true );
3331+
if ( i == actions->defaultAction() )
3332+
{
3333+
action->setChecked( true );
3334+
}
3335+
}
3336+
}
3337+
32773338
void QgisApp::measure()
32783339
{
32793340
mMapCanvas->setMapTool( mMapTools.mMeasureDist );
@@ -6019,7 +6080,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
60196080
mActionLayerProperties->setEnabled( false );
60206081
mActionLayerSubsetString->setEnabled( false );
60216082
mActionAddToOverview->setEnabled( false );
6022-
6083+
mActionFeatureAction->setEnabled( false );
60236084
mActionAddFeature->setEnabled( false );
60246085
mActionMoveFeature->setEnabled( false );
60256086
mActionNodeTool->setEnabled( false );
@@ -6077,6 +6138,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
60776138
mActionLayerSaveAs->setEnabled( true );
60786139
mActionLayerSelectionSaveAs->setEnabled( true );
60796140
mActionCopyFeatures->setEnabled( layerHasSelection );
6141+
mActionFeatureAction->setEnabled( vlayer->actions()->size() > 0 );
60806142

60816143
if ( !vlayer->isEditable() && mMapCanvas->mapTool() && mMapCanvas->mapTool()->isEditTool() )
60826144
{
@@ -6244,6 +6306,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
62446306
mActionFullHistogramStretch->setEnabled( false );
62456307
}
62466308
mActionLayerSubsetString->setEnabled( false );
6309+
mActionFeatureAction->setEnabled( false );
62476310
mActionSelect->setEnabled( false );
62486311
mActionSelectRectangle->setEnabled( false );
62496312
mActionSelectPolygon->setEnabled( false );

src/app/qgisapp.h

+11
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ class QgisApp : public QMainWindow, private Ui::MainWindow
246246
QAction *actionSelectFreehand() { return mActionSelectFreehand; }
247247
QAction *actionSelectRadius() { return mActionSelectRadius; }
248248
QAction *actionIdentify() { return mActionIdentify; }
249+
QAction *actionFeatureAction() { return mActionFeatureAction; }
249250
QAction *actionMeasure() { return mActionMeasure; }
250251
QAction *actionMeasureArea() { return mActionMeasureArea; }
251252
QAction *actionZoomFullExtent() { return mActionZoomFullExtent; }
@@ -757,6 +758,13 @@ class QgisApp : public QMainWindow, private Ui::MainWindow
757758
//! Measure angle
758759
void measureAngle();
759760

761+
//! Run the default feature action on the current layer
762+
void doFeatureAction();
763+
//! Set the default feature action for the current layer
764+
void updateDefaultFeatureAction( QAction *action );
765+
//! Refresh the list of feature actions of the current layer
766+
void refreshFeatureActions();
767+
760768
//annotations
761769
void addFormAnnotation();
762770
void addTextAnnotation();
@@ -969,6 +977,7 @@ class QgisApp : public QMainWindow, private Ui::MainWindow
969977
QgsMapTool* mZoomOut;
970978
QgsMapTool* mPan;
971979
QgsMapTool* mIdentify;
980+
QgsMapTool* mFeatureAction;
972981
QgsMapTool* mMeasureDist;
973982
QgsMapTool* mMeasureArea;
974983
QgsMapTool* mMeasureAngle;
@@ -1025,6 +1034,8 @@ class QgisApp : public QMainWindow, private Ui::MainWindow
10251034
QLabel * mOnTheFlyProjectionStatusLabel;
10261035
//! Widget in status bar used to show status of on the fly projection
10271036
QToolButton * mOnTheFlyProjectionStatusButton;
1037+
//! Menu that contains the list of actions of the selected vector layer
1038+
QMenu *mFeatureActionMenu;
10281039
//! Popup menu
10291040
QMenu * mPopupMenu;
10301041
//! Top level database menu

src/app/qgisappinterface.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ QAction *QgisAppInterface::actionSelectPolygon() { return qgis->actionSelectPoly
421421
QAction *QgisAppInterface::actionSelectFreehand() { return qgis->actionSelectFreehand(); }
422422
QAction *QgisAppInterface::actionSelectRadius() { return qgis->actionSelectRadius(); }
423423
QAction *QgisAppInterface::actionIdentify() { return qgis->actionIdentify(); }
424+
QAction *QgisAppInterface::actionFeatureAction() { return qgis->actionFeatureAction(); }
424425
QAction *QgisAppInterface::actionMeasure() { return qgis->actionMeasure(); }
425426
QAction *QgisAppInterface::actionMeasureArea() { return qgis->actionMeasureArea(); }
426427
QAction *QgisAppInterface::actionViewSeparator1() { return 0; }

src/app/qgisappinterface.h

+1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ class QgisAppInterface : public QgisInterface
269269
virtual QAction *actionSelectFreehand();
270270
virtual QAction *actionSelectRadius();
271271
virtual QAction *actionIdentify();
272+
virtual QAction *actionFeatureAction();
272273
virtual QAction *actionMeasure();
273274
virtual QAction *actionMeasureArea();
274275
virtual QAction *actionViewSeparator1();

src/app/qgsmaptoolfeatureaction.cpp

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/***************************************************************************
2+
qgsmaptoolfeatureaction.h - map tool for running feature actions
3+
---------------------
4+
begin : January 2012
5+
copyright : (C) 2012 by Giuseppe Sucameli
6+
email : brush.tyler at gmail dot 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+
#include "qgsmaptoolfeatureaction.h"
17+
18+
#include "qgsdistancearea.h"
19+
#include "qgsfeature.h"
20+
#include "qgsfield.h"
21+
#include "qgsgeometry.h"
22+
#include "qgslogger.h"
23+
#include "qgsmapcanvas.h"
24+
#include "qgsmaptopixel.h"
25+
#include "qgsmessageviewer.h"
26+
#include "qgsattributeaction.h"
27+
#include "qgscoordinatereferencesystem.h"
28+
#include "qgsvectordataprovider.h"
29+
#include "qgsvectorlayer.h"
30+
#include "qgsproject.h"
31+
#include "qgsmaplayerregistry.h"
32+
#include "qgisapp.h"
33+
34+
#include <QSettings>
35+
#include <QMessageBox>
36+
#include <QMouseEvent>
37+
#include <QStatusBar>
38+
39+
QgsMapToolFeatureAction::QgsMapToolFeatureAction( QgsMapCanvas* canvas )
40+
: QgsMapTool( canvas )
41+
{
42+
}
43+
44+
QgsMapToolFeatureAction::~QgsMapToolFeatureAction()
45+
{
46+
}
47+
48+
void QgsMapToolFeatureAction::canvasMoveEvent( QMouseEvent *e )
49+
{
50+
Q_UNUSED( e );
51+
}
52+
53+
void QgsMapToolFeatureAction::canvasPressEvent( QMouseEvent *e )
54+
{
55+
Q_UNUSED( e );
56+
}
57+
58+
void QgsMapToolFeatureAction::canvasReleaseEvent( QMouseEvent *e )
59+
{
60+
if ( !mCanvas || mCanvas->isDrawing() )
61+
{
62+
return;
63+
}
64+
65+
QgsMapLayer *layer = mCanvas->currentLayer();
66+
67+
if ( !layer || layer->type() != QgsMapLayer::VectorLayer )
68+
{
69+
QMessageBox::warning( mCanvas,
70+
tr( "No active vector layer" ),
71+
tr( "To run an action, you must choose a vector layer by clicking on its name in the legend" ) );
72+
return;
73+
}
74+
75+
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
76+
if ( vlayer->actions()->size() == 0 )
77+
{
78+
QMessageBox::warning( mCanvas,
79+
tr( "No actions available" ),
80+
tr( "The active vector layer has no defined actions" ) );
81+
return;
82+
}
83+
84+
if ( !doAction( vlayer, e->x(), e->y() ) )
85+
QgisApp::instance()->statusBar()->showMessage( tr( "No features at this position found." ) );
86+
}
87+
88+
void QgsMapToolFeatureAction::activate()
89+
{
90+
QgsMapTool::activate();
91+
}
92+
93+
void QgsMapToolFeatureAction::deactivate()
94+
{
95+
QgsMapTool::deactivate();
96+
}
97+
98+
bool QgsMapToolFeatureAction::doAction( QgsVectorLayer *layer, int x, int y )
99+
{
100+
if ( !layer )
101+
return false;
102+
103+
QgsPoint point = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
104+
105+
// load identify radius from settings
106+
QSettings settings;
107+
double identifyValue = settings.value( "/Map/identifyRadius", QGis::DEFAULT_IDENTIFY_RADIUS ).toDouble();
108+
QString ellipsoid = settings.value( "/qgis/measure/ellipsoid", "WGS84" ).toString();
109+
110+
if ( identifyValue <= 0.0 )
111+
identifyValue = QGis::DEFAULT_IDENTIFY_RADIUS;
112+
113+
QgsFeature feat;
114+
115+
// toLayerCoordinates will throw an exception for an 'invalid' point.
116+
// For example, if you project a world map onto a globe using EPSG 2163
117+
// and then click somewhere off the globe, an exception will be thrown.
118+
try
119+
{
120+
// create the search rectangle
121+
double searchRadius = mCanvas->extent().width() * ( identifyValue / 100.0 );
122+
123+
QgsRectangle r;
124+
r.setXMinimum( point.x() - searchRadius );
125+
r.setXMaximum( point.x() + searchRadius );
126+
r.setYMinimum( point.y() - searchRadius );
127+
r.setYMaximum( point.y() + searchRadius );
128+
129+
r = toLayerCoordinates( layer, r );
130+
131+
layer->select( layer->pendingAllAttributesList(), r, true, true );
132+
QgsFeature f;
133+
if ( layer->nextFeature( f ) )
134+
feat = QgsFeature( f );
135+
else
136+
return false;
137+
}
138+
catch ( QgsCsException & cse )
139+
{
140+
Q_UNUSED( cse );
141+
// catch exception for 'invalid' point and proceed with no features found
142+
QgsDebugMsg( QString( "Caught CRS exception %1" ).arg( cse.what() ) );
143+
}
144+
145+
int action = layer->actions()->defaultAction();
146+
147+
// define custom substitutions: layer id and clicked coords
148+
QMap<QString, QVariant> substitutionMap;
149+
substitutionMap.insert( "$layerid", layer->id() );
150+
point = toLayerCoordinates( layer, point );
151+
substitutionMap.insert( "$clickx", point.x() );
152+
substitutionMap.insert( "$clicky", point.y() );
153+
154+
layer->actions()->doAction( action, feat, &substitutionMap );
155+
return true;
156+
}

0 commit comments

Comments
 (0)