Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #5268 from nyalldawson/flash2
[FEATURE] Flash features
  • Loading branch information
nyalldawson committed Oct 2, 2017
2 parents d922b55 + 5b6f02e commit d1018cb
Show file tree
Hide file tree
Showing 14 changed files with 362 additions and 105 deletions.
6 changes: 6 additions & 0 deletions python/gui/qgsattributeform.sip
Expand Up @@ -163,6 +163,12 @@ class QgsAttributeForm : QWidget
%Docstring
Emitted when the user chooses to zoom to a filtered set of features.
.. versionadded:: 3.0
%End

void flashFeatures( const QString &filter );
%Docstring
Emitted when the user chooses to flash a filtered set of features.
.. versionadded:: 3.0
%End

public slots:
Expand Down
34 changes: 34 additions & 0 deletions python/gui/qgsmapcanvas.sip
Expand Up @@ -232,6 +232,40 @@ Zoom to the next extent (view)
void panToSelected( QgsVectorLayer *layer = 0 );
%Docstring
Pan to the selected features of current (vector) layer keeping same extent.
%End

void flashFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids,
const QColor &startColor = QColor( 255, 0, 0, 255 ), const QColor &endColor = QColor( 255, 0, 0, 0 ),
int flashes = 3, int duration = 500 );
%Docstring
Causes a set of features with matching ``ids`` from a vector ``layer`` to flash
within the canvas.

The ``startColor`` and ``endColor`` can be specified, along with the number of
``flashes`` and ``duration`` of each flash (in milliseconds).

.. note::

If the features or geometries are already available, flashGeometries() is much more efficient.

.. versionadded:: 3.0
.. seealso:: flashGeometries()
%End

void flashGeometries( const QList< QgsGeometry > &geometries, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem(),
const QColor &startColor = QColor( 255, 0, 0, 255 ), const QColor &endColor = QColor( 255, 0, 0, 0 ),
int flashes = 3, int duration = 500 );
%Docstring
Causes a set of ``geometries`` to flash within the canvas.

If ``crs`` is a valid coordinate reference system, the geometries will be automatically
transformed from this CRS to the canvas CRS.

The ``startColor`` and ``endColor`` can be specified, along with the number of
``flashes`` and ``duration`` of each flash (in milliseconds).

.. versionadded:: 3.0
.. seealso:: flashFeatureIds()
%End

void setMapTool( QgsMapTool *mapTool );
Expand Down
16 changes: 13 additions & 3 deletions python/gui/qgsrubberband.sip
Expand Up @@ -216,19 +216,29 @@ for tracking the mouse while drawing polylines or polygons.
\param rect rectangle in canvas coordinates
%End

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

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

void addGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
%Docstring
Adds a ``geometry`` to the rubberband.

If ``crs`` is specified, the geometry will be automatically reprojected from ``crs``
to the canvas CRS.

.. versionadded:: 3.0
%End

void setTranslationOffset( double dx, double dy );
%Docstring
Adds translation to original coordinates (all in map coordinates)
Expand Down Expand Up @@ -275,7 +285,7 @@ for tracking the mouse while drawing polylines or polygons.
\param p The QPainter object
%End

void drawShape( QPainter *p, QVector<QPointF> &pts );
void drawShape( QPainter *p, const QVector<QPointF> &pts );
%Docstring
Draws shape of the rubber band.
\param p The QPainter object
Expand Down
33 changes: 33 additions & 0 deletions src/app/qgsselectbyformdialog.cpp
Expand Up @@ -63,6 +63,7 @@ void QgsSelectByFormDialog::setMapCanvas( QgsMapCanvas *canvas )
{
mMapCanvas = canvas;
connect( mForm, &QgsAttributeForm::zoomToFeatures, this, &QgsSelectByFormDialog::zoomToFeatures );
connect( mForm, &QgsAttributeForm::flashFeatures, this, &QgsSelectByFormDialog::flashFeatures );
}

void QgsSelectByFormDialog::zoomToFeatures( const QString &filter )
Expand Down Expand Up @@ -112,3 +113,35 @@ void QgsSelectByFormDialog::zoomToFeatures( const QString &filter )
timeout );
}
}

void QgsSelectByFormDialog::flashFeatures( const QString &filter )
{
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );

QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter )
.setExpressionContext( context )
.setSubsetOfAttributes( QgsAttributeList() );

QgsFeatureIterator features = mLayer->getFeatures( request );
QgsFeature feat;
QList< QgsGeometry > geoms;
while ( features.nextFeature( feat ) )
{
if ( feat.hasGeometry() )
geoms << feat.geometry();
}

QgsSettings settings;
int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
if ( !geoms.empty() )
{
mMapCanvas->flashGeometries( geoms, mLayer->crs() );
}
else if ( mMessageBar )
{
mMessageBar->pushMessage( QString(),
tr( "No matching features found" ),
QgsMessageBar::INFO,
timeout );
}
}
1 change: 1 addition & 0 deletions src/app/qgsselectbyformdialog.h
Expand Up @@ -64,6 +64,7 @@ class APP_EXPORT QgsSelectByFormDialog : public QDialog
private slots:

void zoomToFeatures( const QString &filter );
void flashFeatures( const QString &filter );

private:

Expand Down
12 changes: 6 additions & 6 deletions src/gui/attributetable/qgsattributetablefiltermodel.cpp
Expand Up @@ -408,24 +408,24 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
QgsRectangle rect = mCanvas->mapSettings().mapToLayerCoordinates( layer(), mCanvas->extent() );
QgsRenderContext renderContext;
renderContext.expressionContext().appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer() ) );
QgsFeatureRenderer *renderer = layer()->renderer();

mFilteredFeatures.clear();

if ( !renderer )
if ( !layer()->renderer() )
{
QgsDebugMsg( "Cannot get renderer" );
return;
}

std::unique_ptr< QgsFeatureRenderer > renderer( layer()->renderer()->clone() );

const QgsMapSettings &ms = mCanvas->mapSettings();
if ( !layer()->isInScaleRange( ms.scale() ) )
{
QgsDebugMsg( "Out of scale limits" );
}
else
{
if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
if ( renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
{
// setup scale
// mapRenderer()->renderContext()->scale is not automatically updated when
Expand All @@ -436,7 +436,7 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
renderContext.setRendererScale( ms.scale() );
}

filter = renderer && renderer->capabilities() & QgsFeatureRenderer::Filter;
filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
}

renderer->startRender( renderContext, layer()->fields() );
Expand Down Expand Up @@ -476,7 +476,7 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()

features.close();

if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
if ( renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
{
renderer->stopRender( renderContext );
}
Expand Down
18 changes: 18 additions & 0 deletions src/gui/attributetable/qgsdualview.cpp
Expand Up @@ -540,6 +540,7 @@ void QgsDualView::viewWillShowContextMenu( QMenu *menu, const QModelIndex &atInd
{
menu->addAction( tr( "Zoom to feature" ), this, SLOT( zoomToCurrentFeature() ) );
menu->addAction( tr( "Pan to feature" ), this, SLOT( panToCurrentFeature() ) );
menu->addAction( tr( "Flash feature" ), this, SLOT( flashCurrentFeature() ) );
}

//add user-defined actions to context menu
Expand Down Expand Up @@ -780,6 +781,23 @@ void QgsDualView::panToCurrentFeature()
}
}

void QgsDualView::flashCurrentFeature()
{
QModelIndex currentIndex = mTableView->currentIndex();
if ( !currentIndex.isValid() )
{
return;
}

QgsFeatureIds ids;
ids.insert( mFilterModel->rowToId( currentIndex ) );
QgsMapCanvas *canvas = mFilterModel->mapCanvas();
if ( canvas )
{
canvas->flashFeatureIds( mLayer, ids );
}
}

void QgsDualView::rebuildFullLayerCache()
{
connect( mLayerCache, &QgsVectorLayerCache::progress, this, &QgsDualView::progress, Qt::UniqueConnection );
Expand Down
2 changes: 2 additions & 0 deletions src/gui/attributetable/qgsdualview.h
Expand Up @@ -328,6 +328,8 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
//! Pans to the active feature
void panToCurrentFeature();

void flashCurrentFeature();

void rebuildFullLayerCache();

private:
Expand Down
15 changes: 15 additions & 0 deletions src/gui/qgsattributeform.cpp
Expand Up @@ -410,6 +410,15 @@ void QgsAttributeForm::searchZoomTo()
emit zoomToFeatures( filter );
}

void QgsAttributeForm::searchFlash()
{
QString filter = createFilterExpression();
if ( filter.isEmpty() )
return;

emit flashFeatures( filter );
}

void QgsAttributeForm::filterAndTriggered()
{
QString filter = createFilterExpression();
Expand Down Expand Up @@ -1353,6 +1362,12 @@ void QgsAttributeForm::init()
boxLayout->addWidget( clearButton );
boxLayout->addStretch( 1 );

QPushButton *flashButton = new QPushButton();
flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
flashButton->setText( tr( "&Flash features" ) );
connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
boxLayout->addWidget( flashButton );

QPushButton *zoomButton = new QPushButton();
zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
zoomButton->setText( tr( "&Zoom to features" ) );
Expand Down
7 changes: 7 additions & 0 deletions src/gui/qgsattributeform.h
Expand Up @@ -202,6 +202,12 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
*/
void zoomToFeatures( const QString &filter );

/**
* Emitted when the user chooses to flash a filtered set of features.
* \since QGIS 3.0
*/
void flashFeatures( const QString &filter );

public slots:

/**
Expand Down Expand Up @@ -263,6 +269,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
void filterTriggered();

void searchZoomTo();
void searchFlash();
void searchSetSelection();
void searchAddToSelection();
void searchRemoveFromSelection();
Expand Down
100 changes: 100 additions & 0 deletions src/gui/qgsmapcanvas.cpp
Expand Up @@ -1058,6 +1058,106 @@ void QgsMapCanvas::panToSelected( QgsVectorLayer *layer )
refresh();
}

void QgsMapCanvas::flashFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids,
const QColor &color1, const QColor &color2,
int flashes, int duration )
{
if ( !layer )
{
return;
}

QList< QgsGeometry > geoms;

QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setSubsetOfAttributes( QgsAttributeList() ) );
QgsFeature fet;
while ( it.nextFeature( fet ) )
{
if ( !fet.hasGeometry() )
continue;
geoms << fet.geometry();
}

flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
}

void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
{
if ( geometries.isEmpty() )
return;

QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
QgsRubberBand *rb = new QgsRubberBand( this, geomType );
for ( const QgsGeometry &geom : geometries )
rb->addGeometry( geom, crs );

if ( geomType == QgsWkbTypes::LineGeometry || geomType == QgsWkbTypes::PointGeometry )
{
rb->setWidth( 2 );
rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
}
if ( geomType == QgsWkbTypes::PointGeometry )
rb->setIcon( QgsRubberBand::ICON_CIRCLE );

QColor startColor = color1;
if ( !startColor.isValid() )
{
if ( geomType == QgsWkbTypes::PolygonGeometry )
{
startColor = rb->fillColor();
}
else
{
startColor = rb->strokeColor();
}
startColor.setAlpha( 255 );
}
QColor endColor = color2;
if ( !endColor.isValid() )
{
endColor = startColor;
endColor.setAlpha( 0 );
}


QVariantAnimation *animation = new QVariantAnimation( this );
connect( animation, &QVariantAnimation::finished, this, [animation, rb]
{
animation->deleteLater();
delete rb;
} );
connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
{
QColor c = value.value<QColor>();
if ( geomType == QgsWkbTypes::PolygonGeometry )
{
rb->setFillColor( c );
}
else
{
rb->setStrokeColor( c );
QColor c = rb->secondaryStrokeColor();
c.setAlpha( c.alpha() );
rb->setSecondaryStrokeColor( c );
}
rb->update();
} );

animation->setDuration( duration * flashes );
animation->setStartValue( endColor );
double midStep = 0.2 / flashes;
for ( int i = 0; i < flashes; ++i )
{
double start = static_cast< double >( i ) / flashes;
animation->setKeyValueAt( start + midStep, startColor );
double end = static_cast< double >( i + 1 ) / flashes;
if ( !qgsDoubleNear( end, 1.0 ) )
animation->setKeyValueAt( end, endColor );
}
animation->setEndValue( endColor );
animation->start();
}

void QgsMapCanvas::keyPressEvent( QKeyEvent *e )
{
if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
Expand Down

0 comments on commit d1018cb

Please sign in to comment.