Skip to content

Commit 3e9fa72

Browse files
committedOct 1, 2017
[FEATURE] Flash features in canvas
This adds: - API call to QgsMapCanvas to flash a set of features - A right click menu option in the attribute table to flash the clicked feature - An option in the Search by Form dialog to flash matching features When triggered, the features flash allowing easy identification without having to alter the current selection or map extent
1 parent 4374c6e commit 3e9fa72

File tree

10 files changed

+203
-0
lines changed

10 files changed

+203
-0
lines changed
 

‎python/gui/qgsattributeform.sip

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ class QgsAttributeForm : QWidget
163163
%Docstring
164164
Emitted when the user chooses to zoom to a filtered set of features.
165165
.. versionadded:: 3.0
166+
%End
167+
168+
void flashFeatures( const QString &filter );
169+
%Docstring
170+
Emitted when the user chooses to flash a filtered set of features.
171+
.. versionadded:: 3.0
166172
%End
167173

168174
public slots:

‎python/gui/qgsmapcanvas.sip

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,19 @@ Zoom to the next extent (view)
232232
void panToSelected( QgsVectorLayer *layer = 0 );
233233
%Docstring
234234
Pan to the selected features of current (vector) layer keeping same extent.
235+
%End
236+
237+
void flashFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids,
238+
const QColor &startColor = QColor( 255, 0, 0, 255 ), const QColor &endColor = QColor( 255, 0, 0, 0 ),
239+
int flashes = 3, int duration = 500 );
240+
%Docstring
241+
Causes a set of features with matching ``ids`` from a vector ``layer`` to flash
242+
within the canvas.
243+
244+
The ``startColor`` and ``endColor`` can be specified, along with the number of
245+
``flashes`` and ``duration`` of each flash (in milliseconds).
246+
247+
.. versionadded:: 3.0
235248
%End
236249

237250
void setMapTool( QgsMapTool *mapTool );

‎src/app/qgsselectbyformdialog.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ void QgsSelectByFormDialog::setMapCanvas( QgsMapCanvas *canvas )
6363
{
6464
mMapCanvas = canvas;
6565
connect( mForm, &QgsAttributeForm::zoomToFeatures, this, &QgsSelectByFormDialog::zoomToFeatures );
66+
connect( mForm, &QgsAttributeForm::flashFeatures, this, &QgsSelectByFormDialog::flashFeatures );
6667
}
6768

6869
void QgsSelectByFormDialog::zoomToFeatures( const QString &filter )
@@ -112,3 +113,35 @@ void QgsSelectByFormDialog::zoomToFeatures( const QString &filter )
112113
timeout );
113114
}
114115
}
116+
117+
void QgsSelectByFormDialog::flashFeatures( const QString &filter )
118+
{
119+
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
120+
121+
QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter )
122+
.setFlags( QgsFeatureRequest::NoGeometry )
123+
.setExpressionContext( context )
124+
.setSubsetOfAttributes( QgsAttributeList() );
125+
126+
QgsFeatureIterator features = mLayer->getFeatures( request );
127+
QgsFeature feat;
128+
QgsFeatureIds ids;
129+
while ( features.nextFeature( feat ) )
130+
{
131+
ids.insert( feat.id() );
132+
}
133+
134+
QgsSettings settings;
135+
int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
136+
if ( !ids.empty() )
137+
{
138+
mMapCanvas->flashFeatureIds( mLayer, ids );
139+
}
140+
else if ( mMessageBar )
141+
{
142+
mMessageBar->pushMessage( QString(),
143+
tr( "No matching features found" ),
144+
QgsMessageBar::INFO,
145+
timeout );
146+
}
147+
}

‎src/app/qgsselectbyformdialog.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class APP_EXPORT QgsSelectByFormDialog : public QDialog
6464
private slots:
6565

6666
void zoomToFeatures( const QString &filter );
67+
void flashFeatures( const QString &filter );
6768

6869
private:
6970

‎src/gui/attributetable/qgsdualview.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ void QgsDualView::viewWillShowContextMenu( QMenu *menu, const QModelIndex &atInd
540540
{
541541
menu->addAction( tr( "Zoom to feature" ), this, SLOT( zoomToCurrentFeature() ) );
542542
menu->addAction( tr( "Pan to feature" ), this, SLOT( panToCurrentFeature() ) );
543+
menu->addAction( tr( "Flash feature" ), this, SLOT( flashCurrentFeature() ) );
543544
}
544545

545546
//add user-defined actions to context menu
@@ -780,6 +781,23 @@ void QgsDualView::panToCurrentFeature()
780781
}
781782
}
782783

784+
void QgsDualView::flashCurrentFeature()
785+
{
786+
QModelIndex currentIndex = mTableView->currentIndex();
787+
if ( !currentIndex.isValid() )
788+
{
789+
return;
790+
}
791+
792+
QgsFeatureIds ids;
793+
ids.insert( mFilterModel->rowToId( currentIndex ) );
794+
QgsMapCanvas *canvas = mFilterModel->mapCanvas();
795+
if ( canvas )
796+
{
797+
canvas->flashFeatureIds( mLayer, ids );
798+
}
799+
}
800+
783801
void QgsDualView::rebuildFullLayerCache()
784802
{
785803
connect( mLayerCache, &QgsVectorLayerCache::progress, this, &QgsDualView::progress, Qt::UniqueConnection );

‎src/gui/attributetable/qgsdualview.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
328328
//! Pans to the active feature
329329
void panToCurrentFeature();
330330

331+
void flashCurrentFeature();
332+
331333
void rebuildFullLayerCache();
332334

333335
private:

‎src/gui/qgsattributeform.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,15 @@ void QgsAttributeForm::searchZoomTo()
410410
emit zoomToFeatures( filter );
411411
}
412412

413+
void QgsAttributeForm::searchFlash()
414+
{
415+
QString filter = createFilterExpression();
416+
if ( filter.isEmpty() )
417+
return;
418+
419+
emit flashFeatures( filter );
420+
}
421+
413422
void QgsAttributeForm::filterAndTriggered()
414423
{
415424
QString filter = createFilterExpression();
@@ -1353,6 +1362,12 @@ void QgsAttributeForm::init()
13531362
boxLayout->addWidget( clearButton );
13541363
boxLayout->addStretch( 1 );
13551364

1365+
QPushButton *flashButton = new QPushButton();
1366+
flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1367+
flashButton->setText( tr( "&Flash features" ) );
1368+
connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
1369+
boxLayout->addWidget( flashButton );
1370+
13561371
QPushButton *zoomButton = new QPushButton();
13571372
zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
13581373
zoomButton->setText( tr( "&Zoom to features" ) );

‎src/gui/qgsattributeform.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
202202
*/
203203
void zoomToFeatures( const QString &filter );
204204

205+
/**
206+
* Emitted when the user chooses to flash a filtered set of features.
207+
* \since QGIS 3.0
208+
*/
209+
void flashFeatures( const QString &filter );
210+
205211
public slots:
206212

207213
/**
@@ -263,6 +269,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
263269
void filterTriggered();
264270

265271
void searchZoomTo();
272+
void searchFlash();
266273
void searchSetSelection();
267274
void searchAddToSelection();
268275
void searchRemoveFromSelection();

‎src/gui/qgsmapcanvas.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,101 @@ void QgsMapCanvas::panToSelected( QgsVectorLayer *layer )
10581058
refresh();
10591059
}
10601060

1061+
void QgsMapCanvas::flashFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids,
1062+
const QColor &color1, const QColor &color2,
1063+
int flashes, int duration )
1064+
{
1065+
if ( !layer )
1066+
{
1067+
return;
1068+
}
1069+
1070+
QList< QgsGeometry > geoms;
1071+
1072+
QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setSubsetOfAttributes( QgsAttributeList() ) );
1073+
QgsFeature fet;
1074+
while ( it.nextFeature( fet ) )
1075+
{
1076+
if ( !fet.hasGeometry() )
1077+
continue;
1078+
geoms << fet.geometry();
1079+
}
1080+
1081+
if ( geoms.isEmpty() )
1082+
return;
1083+
1084+
QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( layer->wkbType() );
1085+
QgsRubberBand *rb = new QgsRubberBand( this, geomType );
1086+
for ( const QgsGeometry &geom : qgsAsConst( geoms ) )
1087+
rb->addGeometry( geom, layer );
1088+
1089+
if ( geomType == QgsWkbTypes::LineGeometry || geomType == QgsWkbTypes::PointGeometry )
1090+
{
1091+
rb->setWidth( 2 );
1092+
rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
1093+
}
1094+
if ( geomType == QgsWkbTypes::PointGeometry )
1095+
rb->setIcon( QgsRubberBand::ICON_CIRCLE );
1096+
1097+
QColor startColor = color1;
1098+
if ( !startColor.isValid() )
1099+
{
1100+
if ( geomType == QgsWkbTypes::PolygonGeometry )
1101+
{
1102+
startColor = rb->fillColor();
1103+
}
1104+
else
1105+
{
1106+
startColor = rb->strokeColor();
1107+
}
1108+
startColor.setAlpha( 255 );
1109+
}
1110+
QColor endColor = color2;
1111+
if ( !endColor.isValid() )
1112+
{
1113+
endColor = startColor;
1114+
endColor.setAlpha( 0 );
1115+
}
1116+
1117+
1118+
QVariantAnimation *animation = new QVariantAnimation( this );
1119+
connect( animation, &QVariantAnimation::finished, this, [animation, rb]
1120+
{
1121+
animation->deleteLater();
1122+
delete rb;
1123+
} );
1124+
connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
1125+
{
1126+
QColor c = value.value<QColor>();
1127+
if ( geomType == QgsWkbTypes::PolygonGeometry )
1128+
{
1129+
rb->setFillColor( c );
1130+
}
1131+
else
1132+
{
1133+
rb->setStrokeColor( c );
1134+
QColor c = rb->secondaryStrokeColor();
1135+
c.setAlpha( c.alpha() );
1136+
rb->setSecondaryStrokeColor( c );
1137+
}
1138+
rb->update();
1139+
} );
1140+
1141+
animation->setDuration( duration * flashes );
1142+
animation->setStartValue( endColor );
1143+
double midStep = 0.2 / flashes;
1144+
for ( int i = 0; i < flashes; ++i )
1145+
{
1146+
double start = static_cast< double >( i ) / flashes;
1147+
animation->setKeyValueAt( start + midStep, startColor );
1148+
double end = static_cast< double >( i + 1 ) / flashes;
1149+
if ( !qgsDoubleNear( end, 1.0 ) )
1150+
animation->setKeyValueAt( end, endColor );
1151+
}
1152+
animation->setEndValue( endColor );
1153+
animation->start();
1154+
}
1155+
10611156
void QgsMapCanvas::keyPressEvent( QKeyEvent *e )
10621157
{
10631158
if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )

‎src/gui/qgsmapcanvas.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,19 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
236236
//! Pan to the selected features of current (vector) layer keeping same extent.
237237
void panToSelected( QgsVectorLayer *layer = nullptr );
238238

239+
/**
240+
* Causes a set of features with matching \a ids from a vector \a layer to flash
241+
* within the canvas.
242+
*
243+
* The \a startColor and \a endColor can be specified, along with the number of
244+
* \a flashes and \a duration of each flash (in milliseconds).
245+
*
246+
* \since QGIS 3.0
247+
*/
248+
void flashFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids,
249+
const QColor &startColor = QColor( 255, 0, 0, 255 ), const QColor &endColor = QColor( 255, 0, 0, 0 ),
250+
int flashes = 3, int duration = 500 );
251+
239252
//! \brief Sets the map tool currently being used on the canvas
240253
void setMapTool( QgsMapTool *mapTool );
241254

0 commit comments

Comments
 (0)
Please sign in to comment.