Skip to content

Commit d1018cb

Browse files
authored
Merge pull request #5268 from nyalldawson/flash2
[FEATURE] Flash features
2 parents d922b55 + 5b6f02e commit d1018cb

14 files changed

+362
-105
lines changed

python/gui/qgsattributeform.sip

+6
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

+34
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,40 @@ 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+
.. note::
248+
249+
If the features or geometries are already available, flashGeometries() is much more efficient.
250+
251+
.. versionadded:: 3.0
252+
.. seealso:: flashGeometries()
253+
%End
254+
255+
void flashGeometries( const QList< QgsGeometry > &geometries, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem(),
256+
const QColor &startColor = QColor( 255, 0, 0, 255 ), const QColor &endColor = QColor( 255, 0, 0, 0 ),
257+
int flashes = 3, int duration = 500 );
258+
%Docstring
259+
Causes a set of ``geometries`` to flash within the canvas.
260+
261+
If ``crs`` is a valid coordinate reference system, the geometries will be automatically
262+
transformed from this CRS to the canvas CRS.
263+
264+
The ``startColor`` and ``endColor`` can be specified, along with the number of
265+
``flashes`` and ``duration`` of each flash (in milliseconds).
266+
267+
.. versionadded:: 3.0
268+
.. seealso:: flashFeatureIds()
235269
%End
236270

237271
void setMapTool( QgsMapTool *mapTool );

python/gui/qgsrubberband.sip

+13-3
Original file line numberDiff line numberDiff line change
@@ -216,19 +216,29 @@ for tracking the mouse while drawing polylines or polygons.
216216
\param rect rectangle in canvas coordinates
217217
%End
218218

219-
void addGeometry( const QgsGeometry &geom, QgsVectorLayer *layer );
219+
void addGeometry( const QgsGeometry &geometry, QgsVectorLayer *layer );
220220
%Docstring
221221
Adds the geometry of an existing feature to a rubberband
222222
This is useful for multi feature highlighting.
223223
As of 2.0, this method does not change the GeometryType any more. You need to set the GeometryType
224224
of the rubberband explicitly by calling reset() or setToGeometry() with appropriate arguments.
225225
setToGeometry() is also to be preferred for backwards-compatibility.
226226

227-
\param geom the geometry object. Will be treated as a collection of vertices.
227+
\param geometry the geometry object. Will be treated as a collection of vertices.
228228
\param layer the layer containing the feature, used for coord transformation to map
229229
crs. In case of 0 pointer, the coordinates are not going to be transformed.
230230
%End
231231

232+
void addGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
233+
%Docstring
234+
Adds a ``geometry`` to the rubberband.
235+
236+
If ``crs`` is specified, the geometry will be automatically reprojected from ``crs``
237+
to the canvas CRS.
238+
239+
.. versionadded:: 3.0
240+
%End
241+
232242
void setTranslationOffset( double dx, double dy );
233243
%Docstring
234244
Adds translation to original coordinates (all in map coordinates)
@@ -275,7 +285,7 @@ for tracking the mouse while drawing polylines or polygons.
275285
\param p The QPainter object
276286
%End
277287

278-
void drawShape( QPainter *p, QVector<QPointF> &pts );
288+
void drawShape( QPainter *p, const QVector<QPointF> &pts );
279289
%Docstring
280290
Draws shape of the rubber band.
281291
\param p The QPainter object

src/app/qgsselectbyformdialog.cpp

+33
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+
.setExpressionContext( context )
123+
.setSubsetOfAttributes( QgsAttributeList() );
124+
125+
QgsFeatureIterator features = mLayer->getFeatures( request );
126+
QgsFeature feat;
127+
QList< QgsGeometry > geoms;
128+
while ( features.nextFeature( feat ) )
129+
{
130+
if ( feat.hasGeometry() )
131+
geoms << feat.geometry();
132+
}
133+
134+
QgsSettings settings;
135+
int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
136+
if ( !geoms.empty() )
137+
{
138+
mMapCanvas->flashGeometries( geoms, mLayer->crs() );
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

+1
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/qgsattributetablefiltermodel.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -408,24 +408,24 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
408408
QgsRectangle rect = mCanvas->mapSettings().mapToLayerCoordinates( layer(), mCanvas->extent() );
409409
QgsRenderContext renderContext;
410410
renderContext.expressionContext().appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer() ) );
411-
QgsFeatureRenderer *renderer = layer()->renderer();
412411

413412
mFilteredFeatures.clear();
414-
415-
if ( !renderer )
413+
if ( !layer()->renderer() )
416414
{
417415
QgsDebugMsg( "Cannot get renderer" );
418416
return;
419417
}
420418

419+
std::unique_ptr< QgsFeatureRenderer > renderer( layer()->renderer()->clone() );
420+
421421
const QgsMapSettings &ms = mCanvas->mapSettings();
422422
if ( !layer()->isInScaleRange( ms.scale() ) )
423423
{
424424
QgsDebugMsg( "Out of scale limits" );
425425
}
426426
else
427427
{
428-
if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
428+
if ( renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
429429
{
430430
// setup scale
431431
// mapRenderer()->renderContext()->scale is not automatically updated when
@@ -436,7 +436,7 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
436436
renderContext.setRendererScale( ms.scale() );
437437
}
438438

439-
filter = renderer && renderer->capabilities() & QgsFeatureRenderer::Filter;
439+
filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
440440
}
441441

442442
renderer->startRender( renderContext, layer()->fields() );
@@ -476,7 +476,7 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
476476

477477
features.close();
478478

479-
if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
479+
if ( renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
480480
{
481481
renderer->stopRender( renderContext );
482482
}

src/gui/attributetable/qgsdualview.cpp

+18
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

+2
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

+15
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

+7
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

+100
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,106 @@ 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+
flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
1082+
}
1083+
1084+
void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
1085+
{
1086+
if ( geometries.isEmpty() )
1087+
return;
1088+
1089+
QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
1090+
QgsRubberBand *rb = new QgsRubberBand( this, geomType );
1091+
for ( const QgsGeometry &geom : geometries )
1092+
rb->addGeometry( geom, crs );
1093+
1094+
if ( geomType == QgsWkbTypes::LineGeometry || geomType == QgsWkbTypes::PointGeometry )
1095+
{
1096+
rb->setWidth( 2 );
1097+
rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
1098+
}
1099+
if ( geomType == QgsWkbTypes::PointGeometry )
1100+
rb->setIcon( QgsRubberBand::ICON_CIRCLE );
1101+
1102+
QColor startColor = color1;
1103+
if ( !startColor.isValid() )
1104+
{
1105+
if ( geomType == QgsWkbTypes::PolygonGeometry )
1106+
{
1107+
startColor = rb->fillColor();
1108+
}
1109+
else
1110+
{
1111+
startColor = rb->strokeColor();
1112+
}
1113+
startColor.setAlpha( 255 );
1114+
}
1115+
QColor endColor = color2;
1116+
if ( !endColor.isValid() )
1117+
{
1118+
endColor = startColor;
1119+
endColor.setAlpha( 0 );
1120+
}
1121+
1122+
1123+
QVariantAnimation *animation = new QVariantAnimation( this );
1124+
connect( animation, &QVariantAnimation::finished, this, [animation, rb]
1125+
{
1126+
animation->deleteLater();
1127+
delete rb;
1128+
} );
1129+
connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
1130+
{
1131+
QColor c = value.value<QColor>();
1132+
if ( geomType == QgsWkbTypes::PolygonGeometry )
1133+
{
1134+
rb->setFillColor( c );
1135+
}
1136+
else
1137+
{
1138+
rb->setStrokeColor( c );
1139+
QColor c = rb->secondaryStrokeColor();
1140+
c.setAlpha( c.alpha() );
1141+
rb->setSecondaryStrokeColor( c );
1142+
}
1143+
rb->update();
1144+
} );
1145+
1146+
animation->setDuration( duration * flashes );
1147+
animation->setStartValue( endColor );
1148+
double midStep = 0.2 / flashes;
1149+
for ( int i = 0; i < flashes; ++i )
1150+
{
1151+
double start = static_cast< double >( i ) / flashes;
1152+
animation->setKeyValueAt( start + midStep, startColor );
1153+
double end = static_cast< double >( i + 1 ) / flashes;
1154+
if ( !qgsDoubleNear( end, 1.0 ) )
1155+
animation->setKeyValueAt( end, endColor );
1156+
}
1157+
animation->setEndValue( endColor );
1158+
animation->start();
1159+
}
1160+
10611161
void QgsMapCanvas::keyPressEvent( QKeyEvent *e )
10621162
{
10631163
if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )

0 commit comments

Comments
 (0)