Skip to content

Commit d1018cb

Browse files
authoredOct 2, 2017
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

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: 34 additions & 0 deletions
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

Lines changed: 13 additions & 3 deletions
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

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

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

Lines changed: 6 additions & 6 deletions
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

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: 100 additions & 0 deletions
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 )

‎src/gui/qgsmapcanvas.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,38 @@ 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+
* \note If the features or geometries are already available, flashGeometries() is much more efficient.
247+
*
248+
* \since QGIS 3.0
249+
* \see flashGeometries()
250+
*/
251+
void flashFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids,
252+
const QColor &startColor = QColor( 255, 0, 0, 255 ), const QColor &endColor = QColor( 255, 0, 0, 0 ),
253+
int flashes = 3, int duration = 500 );
254+
255+
/**
256+
* Causes a set of \a geometries to flash within the canvas.
257+
*
258+
* If \a crs is a valid coordinate reference system, the geometries will be automatically
259+
* transformed from this CRS to the canvas CRS.
260+
*
261+
* The \a startColor and \a endColor can be specified, along with the number of
262+
* \a flashes and \a duration of each flash (in milliseconds).
263+
*
264+
* \since QGIS 3.0
265+
* \see flashFeatureIds()
266+
*/
267+
void flashGeometries( const QList< QgsGeometry > &geometries, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem(),
268+
const QColor &startColor = QColor( 255, 0, 0, 255 ), const QColor &endColor = QColor( 255, 0, 0, 0 ),
269+
int flashes = 3, int duration = 500 );
270+
239271
//! \brief Sets the map tool currently being used on the canvas
240272
void setMapTool( QgsMapTool *mapTool );
241273

‎src/gui/qgsrubberband.cpp

Lines changed: 82 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,21 @@ void QgsRubberBand::setToGeometry( const QgsGeometry &geom, QgsVectorLayer *laye
230230
addGeometry( geom, layer );
231231
}
232232

233-
void QgsRubberBand::addGeometry( const QgsGeometry &geom, QgsVectorLayer *layer )
233+
void QgsRubberBand::addGeometry( const QgsGeometry &geometry, QgsVectorLayer *layer )
234234
{
235-
if ( geom.isNull() )
235+
QgsGeometry geom = geometry;
236+
if ( layer )
237+
{
238+
QgsCoordinateTransform ct = mMapCanvas->mapSettings().layerTransform( layer );
239+
geom.transform( ct );
240+
}
241+
242+
addGeometry( geom );
243+
}
244+
245+
void QgsRubberBand::addGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &crs )
246+
{
247+
if ( geometry.isEmpty() )
236248
{
237249
return;
238250
}
@@ -242,21 +254,20 @@ void QgsRubberBand::addGeometry( const QgsGeometry &geom, QgsVectorLayer *layer
242254

243255
int idx = mPoints.size();
244256

245-
switch ( geom.wkbType() )
257+
QgsGeometry geom = geometry;
258+
if ( crs.isValid() )
259+
{
260+
QgsCoordinateTransform ct( crs, ms.destinationCrs() );
261+
geom.transform( ct );
262+
}
263+
264+
switch ( QgsWkbTypes::flatType( geom.wkbType() ) )
246265
{
247266

248267
case QgsWkbTypes::Point:
249268
case QgsWkbTypes::Point25D:
250269
{
251-
QgsPointXY pt;
252-
if ( layer )
253-
{
254-
pt = ms.layerToMapCoordinates( layer, geom.asPoint() );
255-
}
256-
else
257-
{
258-
pt = geom.asPoint();
259-
}
270+
QgsPointXY pt = geom.asPoint();
260271
addPoint( pt, false, idx );
261272
removeLastPoint( idx, false );
262273
}
@@ -265,38 +276,23 @@ void QgsRubberBand::addGeometry( const QgsGeometry &geom, QgsVectorLayer *layer
265276
case QgsWkbTypes::MultiPoint:
266277
case QgsWkbTypes::MultiPoint25D:
267278
{
268-
QgsMultiPoint mpt = geom.asMultiPoint();
269-
for ( int i = 0; i < mpt.size(); ++i, ++idx )
279+
const QgsMultiPoint mpt = geom.asMultiPoint();
280+
for ( QgsPointXY pt : mpt )
270281
{
271-
QgsPointXY pt = mpt[i];
272-
if ( layer )
273-
{
274-
addPoint( ms.layerToMapCoordinates( layer, pt ), false, idx );
275-
removeLastPoint( idx, false );
276-
}
277-
else
278-
{
279-
addPoint( pt, false, idx );
280-
removeLastPoint( idx, false );
281-
}
282+
addPoint( pt, false, idx );
283+
removeLastPoint( idx, false );
284+
idx++;
282285
}
283286
}
284287
break;
285288

286289
case QgsWkbTypes::LineString:
287290
case QgsWkbTypes::LineString25D:
288291
{
289-
QgsPolyline line = geom.asPolyline();
290-
for ( int i = 0; i < line.count(); i++ )
292+
const QgsPolyline line = geom.asPolyline();
293+
for ( QgsPointXY pt : line )
291294
{
292-
if ( layer )
293-
{
294-
addPoint( ms.layerToMapCoordinates( layer, line[i] ), false, idx );
295-
}
296-
else
297-
{
298-
addPoint( line[i], false, idx );
299-
}
295+
addPoint( pt, false, idx );
300296
}
301297
}
302298
break;
@@ -305,46 +301,31 @@ void QgsRubberBand::addGeometry( const QgsGeometry &geom, QgsVectorLayer *layer
305301
case QgsWkbTypes::MultiLineString25D:
306302
{
307303

308-
QgsMultiPolyline mline = geom.asMultiPolyline();
309-
for ( int i = 0; i < mline.size(); ++i, ++idx )
304+
const QgsMultiPolyline mline = geom.asMultiPolyline();
305+
for ( const QgsPolyline &line : mline )
310306
{
311-
QgsPolyline line = mline[i];
312-
313307
if ( line.isEmpty() )
314308
{
315-
--idx;
309+
continue;
316310
}
317311

318-
for ( int j = 0; j < line.size(); ++j )
312+
for ( QgsPointXY pt : line )
319313
{
320-
if ( layer )
321-
{
322-
addPoint( ms.layerToMapCoordinates( layer, line[j] ), false, idx );
323-
}
324-
else
325-
{
326-
addPoint( line[j], false, idx );
327-
}
314+
addPoint( pt, false, idx );
328315
}
316+
idx++;
329317
}
330318
}
331319
break;
332320

333321
case QgsWkbTypes::Polygon:
334322
case QgsWkbTypes::Polygon25D:
335323
{
336-
QgsPolygon poly = geom.asPolygon();
337-
QgsPolyline line = poly[0];
338-
for ( int i = 0; i < line.count(); i++ )
324+
const QgsPolygon poly = geom.asPolygon();
325+
const QgsPolyline line = poly.at( 0 );
326+
for ( QgsPointXY pt : line )
339327
{
340-
if ( layer )
341-
{
342-
addPoint( ms.layerToMapCoordinates( layer, line[i] ), false, idx );
343-
}
344-
else
345-
{
346-
addPoint( line[i], false, idx );
347-
}
328+
addPoint( pt, false, idx );
348329
}
349330
}
350331
break;
@@ -353,22 +334,18 @@ void QgsRubberBand::addGeometry( const QgsGeometry &geom, QgsVectorLayer *layer
353334
case QgsWkbTypes::MultiPolygon25D:
354335
{
355336

356-
QgsMultiPolygon multipoly = geom.asMultiPolygon();
357-
for ( int i = 0; i < multipoly.size(); ++i, ++idx )
337+
const QgsMultiPolygon multipoly = geom.asMultiPolygon();
338+
for ( const QgsPolygon &poly : multipoly )
358339
{
359-
QgsPolygon poly = multipoly[i];
360-
QgsPolyline line = poly[0];
361-
for ( int j = 0; j < line.count(); ++j )
340+
if ( poly.empty() )
341+
continue;
342+
343+
const QgsPolyline line = poly.at( 0 );
344+
for ( QgsPointXY pt : line )
362345
{
363-
if ( layer )
364-
{
365-
addPoint( ms.layerToMapCoordinates( layer, line[j] ), false, idx );
366-
}
367-
else
368-
{
369-
addPoint( line[j], false, idx );
370-
}
346+
addPoint( pt, false, idx );
371347
}
348+
idx++;
372349
}
373350
}
374351
break;
@@ -405,35 +382,47 @@ void QgsRubberBand::setToCanvasRectangle( QRect rect )
405382

406383
void QgsRubberBand::paint( QPainter *p )
407384
{
408-
if ( !mPoints.isEmpty() )
385+
if ( mPoints.isEmpty() )
386+
return;
387+
388+
QVector< QVector<QPointF> > shapes;
389+
for ( const QList<QgsPointXY> &line : qgsAsConst( mPoints ) )
409390
{
410-
Q_FOREACH ( const QList<QgsPointXY> &line, mPoints )
391+
QVector<QPointF> pts;
392+
for ( const QgsPointXY &pt : line )
411393
{
412-
QVector<QPointF> pts;
413-
Q_FOREACH ( const QgsPointXY &pt, line )
414-
{
415-
const QPointF cur = toCanvasCoordinates( QgsPointXY( pt.x() + mTranslationOffsetX, pt.y() + mTranslationOffsetY ) ) - pos();
416-
if ( pts.empty() || std::abs( pts.back().x() - cur.x() ) > 1 || std::abs( pts.back().y() - cur.y() ) > 1 )
417-
pts.append( cur );
418-
}
419-
420-
if ( mSecondaryPen.color().isValid() )
421-
{
422-
mSecondaryPen.setWidth( mPen.width() + 2 );
423-
424-
p->setBrush( Qt::NoBrush );
425-
p->setPen( mSecondaryPen );
426-
drawShape( p, pts );
427-
}
394+
const QPointF cur = toCanvasCoordinates( QgsPointXY( pt.x() + mTranslationOffsetX, pt.y() + mTranslationOffsetY ) ) - pos();
395+
if ( pts.empty() || std::abs( pts.back().x() - cur.x() ) > 1 || std::abs( pts.back().y() - cur.y() ) > 1 )
396+
pts.append( cur );
397+
}
398+
shapes << pts;
399+
}
428400

401+
int iterations = mSecondaryPen.color().isValid() ? 2 : 1;
402+
for ( int i = 0; i < iterations; ++i )
403+
{
404+
if ( i == 0 && iterations > 1 )
405+
{
406+
// first iteration with multi-pen painting, so use secondary pen
407+
mSecondaryPen.setWidth( mPen.width() + 2 );
408+
p->setBrush( Qt::NoBrush );
409+
p->setPen( mSecondaryPen );
410+
}
411+
else
412+
{
413+
// "top" layer, use primary pen/brush
429414
p->setBrush( mBrush );
430415
p->setPen( mPen );
431-
drawShape( p, pts );
416+
}
417+
418+
for ( const QVector<QPointF> &shape : qgsAsConst( shapes ) )
419+
{
420+
drawShape( p, shape );
432421
}
433422
}
434423
}
435424

436-
void QgsRubberBand::drawShape( QPainter *p, QVector<QPointF> &pts )
425+
void QgsRubberBand::drawShape( QPainter *p, const QVector<QPointF> &pts )
437426
{
438427
switch ( mGeometryType )
439428
{

‎src/gui/qgsrubberband.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,21 @@ class GUI_EXPORT QgsRubberBand: public QgsMapCanvasItem
257257
* of the rubberband explicitly by calling reset() or setToGeometry() with appropriate arguments.
258258
* setToGeometry() is also to be preferred for backwards-compatibility.
259259
*
260-
* \param geom the geometry object. Will be treated as a collection of vertices.
260+
* \param geometry the geometry object. Will be treated as a collection of vertices.
261261
* \param layer the layer containing the feature, used for coord transformation to map
262262
* crs. In case of 0 pointer, the coordinates are not going to be transformed.
263263
*/
264-
void addGeometry( const QgsGeometry &geom, QgsVectorLayer *layer );
264+
void addGeometry( const QgsGeometry &geometry, QgsVectorLayer *layer );
265+
266+
/**
267+
* Adds a \a geometry to the rubberband.
268+
*
269+
* If \a crs is specified, the geometry will be automatically reprojected from \a crs
270+
* to the canvas CRS.
271+
*
272+
* \since QGIS 3.0
273+
*/
274+
void addGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
265275

266276
/**
267277
* Adds translation to original coordinates (all in map coordinates)
@@ -310,7 +320,7 @@ class GUI_EXPORT QgsRubberBand: public QgsMapCanvasItem
310320
* \param p The QPainter object
311321
* \param pts A list of points used to draw the shape
312322
*/
313-
void drawShape( QPainter *p, QVector<QPointF> &pts );
323+
void drawShape( QPainter *p, const QVector<QPointF> &pts );
314324

315325
//! Recalculates needed rectangle
316326
void updateRect();

0 commit comments

Comments
 (0)
Please sign in to comment.