diff --git a/python/gui/auto_generated/qgsmapcanvasutils.sip.in b/python/gui/auto_generated/qgsmapcanvasutils.sip.in new file mode 100644 index 000000000000..4f33efd5c575 --- /dev/null +++ b/python/gui/auto_generated/qgsmapcanvasutils.sip.in @@ -0,0 +1,47 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsmapcanvasutils.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsMapCanvasUtils +{ +%Docstring +Utility functions for working with QgsMapCanvas widgets. + +.. versionadded:: 3.14 +%End + +%TypeHeaderCode +#include "qgsmapcanvasutils.h" +%End + public: + + static long zoomToMatchingFeatures( QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter ); +%Docstring +Zooms a map ``canvas`` to features from the specified ``layer`` which match the given ``filter`` expression string. + +The total count of matching features will be returned. +%End + + static long flashMatchingFeatures( QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter ); +%Docstring +Flashes features from the specified ``layer`` which match the given ``filter`` expression string with a map ``canvas``. + +The total count of matching features will be returned. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsmapcanvasutils.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index f4d749a956dd..a06904a0e172 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -123,6 +123,7 @@ %Include auto_generated/qgsmapcanvasitem.sip %Include auto_generated/qgsmapcanvassnappingutils.sip %Include auto_generated/qgsmapcanvastracer.sip +%Include auto_generated/qgsmapcanvasutils.sip %Include auto_generated/qgsmaplayeractionregistry.sip %Include auto_generated/qgsmaplayercombobox.sip %Include auto_generated/qgsmaplayerconfigwidget.sip diff --git a/src/app/qgsselectbyformdialog.cpp b/src/app/qgsselectbyformdialog.cpp index cf27773dc513..dc008cf4a2c7 100644 --- a/src/app/qgsselectbyformdialog.cpp +++ b/src/app/qgsselectbyformdialog.cpp @@ -22,7 +22,7 @@ #include "qgsmessagebar.h" #include "qgsexpressioncontextutils.h" #include "qgsgui.h" - +#include "qgsmapcanvasutils.h" QgsSelectByFormDialog::QgsSelectByFormDialog( QgsVectorLayer *layer, const QgsAttributeEditorContext &context, QWidget *parent, Qt::WindowFlags fl ) : QDialog( parent, fl ) @@ -65,35 +65,11 @@ void QgsSelectByFormDialog::setMapCanvas( QgsMapCanvas *canvas ) void QgsSelectByFormDialog::zoomToFeatures( const QString &filter ) { - QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); - - QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter ) - .setExpressionContext( context ) - .setNoAttributes(); - - QgsFeatureIterator features = mLayer->getFeatures( request ); - - QgsRectangle bbox; - bbox.setMinimal(); - QgsFeature feat; - int featureCount = 0; - while ( features.nextFeature( feat ) ) - { - QgsGeometry geom = feat.geometry(); - if ( geom.isNull() || geom.constGet()->isEmpty() ) - continue; - - QgsRectangle r = mMapCanvas->mapSettings().layerExtentToOutputExtent( mLayer, geom.boundingBox() ); - bbox.combineExtentWith( r ); - featureCount++; - } - features.close(); - + const long featureCount = QgsMapCanvasUtils::zoomToMatchingFeatures( mMapCanvas, mLayer, filter ); QgsSettings settings; int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt(); if ( featureCount > 0 ) { - mMapCanvas->zoomToFeatureExtent( bbox ); if ( mMessageBar ) { mMessageBar->pushMessage( QString(), @@ -113,28 +89,10 @@ void QgsSelectByFormDialog::zoomToFeatures( const QString &filter ) void QgsSelectByFormDialog::flashFeatures( const QString &filter ) { - QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); - - QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter ) - .setExpressionContext( context ) - .setNoAttributes(); - - QgsFeatureIterator features = mLayer->getFeatures( request ); - QgsFeature feat; - QList< QgsGeometry > geoms; - while ( features.nextFeature( feat ) ) - { - if ( feat.hasGeometry() ) - geoms << feat.geometry(); - } - + const long featureCount = QgsMapCanvasUtils::flashMatchingFeatures( mMapCanvas, mLayer, filter ); QgsSettings settings; int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt(); - if ( !geoms.empty() ) - { - mMapCanvas->flashGeometries( geoms, mLayer->crs() ); - } - else if ( mMessageBar ) + if ( featureCount == 0 && mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "No matching features found" ), diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index cb48339528ee..84ea98d43085 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -450,6 +450,7 @@ SET(QGIS_GUI_SRCS qgsmapcanvasmap.cpp qgsmapcanvassnappingutils.cpp qgsmapcanvastracer.cpp + qgsmapcanvasutils.cpp qgsmaplayeractionregistry.cpp qgsmaplayercombobox.cpp qgsmaplayerconfigwidgetfactory.cpp @@ -681,6 +682,7 @@ SET(QGIS_GUI_HDRS qgsmapcanvasmap.h qgsmapcanvassnappingutils.h qgsmapcanvastracer.h + qgsmapcanvasutils.h qgsmaplayeractionregistry.h qgsmaplayercombobox.h qgsmaplayerconfigwidget.h diff --git a/src/gui/attributetable/qgsdualview.cpp b/src/gui/attributetable/qgsdualview.cpp index af7d6ee87842..9372f41f96eb 100644 --- a/src/gui/attributetable/qgsdualview.cpp +++ b/src/gui/attributetable/qgsdualview.cpp @@ -43,6 +43,7 @@ #include "qgsexpressioncontextutils.h" #include "qgsshortcutsmanager.h" #include "qgsfieldconditionalformatwidget.h" +#include "qgsmapcanvasutils.h" QgsDualView::QgsDualView( QWidget *parent ) @@ -149,6 +150,21 @@ void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const Qg connect( mAttributeForm, &QgsAttributeForm::widgetValueChanged, this, &QgsDualView::featureFormAttributeChanged ); connect( mAttributeForm, &QgsAttributeForm::modeChanged, this, &QgsDualView::formModeChanged ); connect( mAttributeForm, &QgsAttributeForm::filterExpressionSet, this, &QgsDualView::filterExpressionSet ); + connect( mAttributeForm, &QgsAttributeForm::flashFeatures, this, [ = ]( const QString & filter ) + { + if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() ) + { + QgsMapCanvasUtils::flashMatchingFeatures( canvas, mLayer, filter ); + } + } ); + connect( mAttributeForm, &QgsAttributeForm::zoomToFeatures, this, [ = ]( const QString & filter ) + { + if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() ) + { + QgsMapCanvasUtils::zoomToMatchingFeatures( canvas, mLayer, filter ); + } + } ); + connect( mMasterModel, &QgsAttributeTableModel::modelChanged, mAttributeForm, &QgsAttributeForm::refreshFeature ); connect( mFilterModel, &QgsAttributeTableFilterModel::sortColumnChanged, this, &QgsDualView::onSortColumnChanged ); diff --git a/src/gui/qgsmapcanvasutils.cpp b/src/gui/qgsmapcanvasutils.cpp new file mode 100644 index 000000000000..da4e2904bc9c --- /dev/null +++ b/src/gui/qgsmapcanvasutils.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + qgsmapcanvasutils.cpp + ------------------- + begin : June 2020 + copyright : (C) 2020 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmapcanvasutils.h" +#include "qgsmapcanvas.h" +#include "qgsvectorlayer.h" +#include "qgsexpressioncontextutils.h" + +long QgsMapCanvasUtils::zoomToMatchingFeatures( QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter ) +{ + QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) ); + + QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter ) + .setExpressionContext( context ) + .setNoAttributes(); + + QgsFeatureIterator features = layer->getFeatures( request ); + + QgsRectangle bbox; + bbox.setMinimal(); + QgsFeature feat; + int featureCount = 0; + while ( features.nextFeature( feat ) ) + { + QgsGeometry geom = feat.geometry(); + if ( geom.isNull() || geom.constGet()->isEmpty() ) + continue; + + QgsRectangle r = canvas->mapSettings().layerExtentToOutputExtent( layer, geom.boundingBox() ); + bbox.combineExtentWith( r ); + featureCount++; + } + features.close(); + + if ( featureCount > 0 ) + { + canvas->zoomToFeatureExtent( bbox ); + } + return featureCount; +} + +long QgsMapCanvasUtils::flashMatchingFeatures( QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter ) +{ + QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) ); + + QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter ) + .setExpressionContext( context ) + .setNoAttributes(); + + QgsFeatureIterator features = layer->getFeatures( request ); + QgsFeature feat; + QList< QgsGeometry > geoms; + while ( features.nextFeature( feat ) ) + { + if ( feat.hasGeometry() ) + geoms << feat.geometry(); + } + + if ( !geoms.empty() ) + { + canvas->flashGeometries( geoms, layer->crs() ); + } + return geoms.size(); +} diff --git a/src/gui/qgsmapcanvasutils.h b/src/gui/qgsmapcanvasutils.h new file mode 100644 index 000000000000..88e686b80034 --- /dev/null +++ b/src/gui/qgsmapcanvasutils.h @@ -0,0 +1,52 @@ +/*************************************************************************** + qgsmapcanvasutils.h + ------------------- + begin : June 2020 + copyright : (C) 2020 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMAPCANVASUTILS_H +#define QGSMAPCANVASUTILS_H + +#include "qgis_gui.h" + +class QgsMapCanvas; +class QgsVectorLayer; +class QString; + +/** + * \ingroup gui + * \class QgsMapCanvasUtils + * Utility functions for working with QgsMapCanvas widgets. + * \since QGIS 3.14 + */ +class GUI_EXPORT QgsMapCanvasUtils +{ + + public: + + /** + * Zooms a map \a canvas to features from the specified \a layer which match the given \a filter expression string. + * + * The total count of matching features will be returned. + */ + static long zoomToMatchingFeatures( QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter ); + + /** + * Flashes features from the specified \a layer which match the given \a filter expression string with a map \a canvas. + * + * The total count of matching features will be returned. + */ + static long flashMatchingFeatures( QgsMapCanvas *canvas, QgsVectorLayer *layer, const QString &filter ); + +}; + +#endif //QGSMAPCANVASUTILS_H