Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Flash features #5268

Merged
merged 7 commits into from
Oct 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions python/gui/qgsattributeform.sip
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading