Address PR review comments and add model tests

This commit is contained in:
Alessandro Pasotti 2022-09-06 15:50:34 +02:00
parent 5a32a7558d
commit 7cab873eac
29 changed files with 370 additions and 165 deletions

View File

@ -146,7 +146,7 @@ Returns icon for point cloud layer
const QgsLayerMetadata &layerMetadata() const;
%Docstring
Returns layer's metadata, it may be a default constructed metadata
if metadata are not explicitly set.
if metadata is not explicitly set.
.. versionadded:: 3.28
%End

View File

@ -121,10 +121,6 @@ Returns the metadata standard URI (usually "http://mrcc.com/qgis.dtd")
Sets the metadata standard URI to ``standardUri``.
%End
QIcon layerTypeIcon() const;
%Docstring
Returns the icon for the layer type.
%End
};
@ -182,6 +178,8 @@ Layer metadata provider backend interface.
%End
public:
virtual ~QgsAbstractLayerMetadataProvider();
virtual QString id() const = 0;
%Docstring
Returns the id of the layer metadata provider implementation, usually the name of the data provider
@ -200,8 +198,6 @@ Searches for metadata optionally filtering by search string and geographic exten
:return: a :py:class:`QgsLayerMetadataSearchResult` object with a list of metadata and errors
%End
virtual ~QgsAbstractLayerMetadataProvider();
};
/************************************************************************

View File

@ -115,8 +115,6 @@ Constructor for Constraint.
Constructor for QgsLayerMetadata.
%End
~QgsLayerMetadata( );
QgsLayerMetadata( const QgsLayerMetadata &other );
%Docstring
Copy constructor for :py:class:`QgsLayerMetadataProviderResult` from ``other``.

View File

@ -25,6 +25,12 @@ Contains utility functions for working with icons.
static QIcon iconForWkbType( QgsWkbTypes::Type type );
%Docstring
Returns the icon for a vector layer whose geometry ``type`` is provided.
%End
static QIcon iconForGeometryType( QgsWkbTypes::GeometryType typeGroup );
%Docstring
Returns the icon for a vector layer whose geometry ``typeGroup`` is provided.
1since QGIS 3.28
%End
static QIcon iconPoint();

View File

@ -20,6 +20,9 @@ This class provides common functionality and the interface for all
source select dialogs used by data providers to configure data sources
and add layers.
The implementation is generic enough to handle other layer search and
selection widgets.
.. versionadded:: 3.0
%End

View File

@ -28,6 +28,7 @@ This is the interface for those who want to add entries to the :py:class:`QgsDat
OrderDatabaseProvider,
OrderRemoteProvider,
OrderGeoCmsProvider,
OrderSearchProvider,
OrderOtherProvider,
};

View File

@ -105,6 +105,7 @@
%Include auto_generated/qgslayermetadatasearchwidget.sip
%Include auto_generated/qgslayermetadataresultsmodel.sip
%Include auto_generated/qgslayermetadataresultsproxymodel.sip
%Include auto_generated/qgslayermetadatasourceselectprovider.sip
%Include auto_generated/qgslegendfilterbutton.sip
%Include auto_generated/qgslegendpatchshapebutton.sip
%Include auto_generated/qgslegendpatchshapewidget.sip

View File

@ -18,6 +18,8 @@
#include "qgsfeedback.h"
#include "qgsapplication.h"
#include "qgisapp.h"
#include "qgsiconutils.h"
#include <QIcon>
QgsLayerMetadataLocatorFilter::QgsLayerMetadataLocatorFilter( QObject *parent )
: QgsLocatorFilter( parent )
@ -44,7 +46,7 @@ void QgsLayerMetadataLocatorFilter::fetchResults( const QString &string, const Q
QgsLocatorResult result;
result.displayString = metadata.identifier();
result.description = metadata.title();
result.icon = metadata.layerTypeIcon();
result.icon = QgsIconUtils::iconForGeometryType( metadata.geometryType() );
result.userData = QVariant::fromValue( metadata );
emit resultFetched( result );
}
@ -69,6 +71,7 @@ void QgsLayerMetadataLocatorFilter::triggerResult( const QgsLocatorResult &resul
case QgsMapLayerType::MeshLayer:
{
QgisApp::instance()->addMeshLayer( metadataResult.uri(), metadataResult.identifier(), metadataResult.dataProviderName() );
break;
}
default: // unsupported
{

View File

@ -145,7 +145,7 @@ class CORE_EXPORT QgsLayerItem : public QgsDataItem
/**
* Returns layer's metadata, it may be a default constructed metadata
* if metadata are not explicitly set.
* if metadata is not explicitly set.
* \since QGIS 3.28
*/
const QgsLayerMetadata &layerMetadata() const;

View File

@ -17,8 +17,6 @@
#include "qgsprovidermetadata.h"
#include "qgsproviderregistry.h"
#include "qgsfeedback.h"
#include "qgsiconutils.h"
#include <QIcon>
QList<QgsLayerMetadataProviderResult> QgsLayerMetadataSearchResults::metadata() const
{
@ -118,24 +116,4 @@ void QgsLayerMetadataProviderResult::setStandardUri( const QString &standardUri
mStandardUri = standardUri;
}
QIcon QgsLayerMetadataProviderResult::layerTypeIcon() const
{
if ( layerType() == QgsMapLayerType::VectorLayer )
{
switch ( geometryType() )
{
case QgsWkbTypes::NullGeometry:
return QgsIconUtils::iconTable();
case QgsWkbTypes::PointGeometry:
return QgsIconUtils::iconPoint();
case QgsWkbTypes::LineGeometry:
return QgsIconUtils::iconLine();
case QgsWkbTypes::PolygonGeometry:
return QgsIconUtils::iconPolygon();
default:
return QgsIconUtils::iconDefaultLayer();
}
}
return QgsIconUtils::iconForLayerType( layerType() );
}

View File

@ -142,10 +142,6 @@ class CORE_EXPORT QgsLayerMetadataProviderResult: public QgsLayerMetadata
*/
void setStandardUri( const QString &standardUri );
/**
* Returns the icon for the layer type.
*/
QIcon layerTypeIcon() const;
private:
@ -223,6 +219,8 @@ class CORE_EXPORT QgsAbstractLayerMetadataProvider
public:
virtual ~QgsAbstractLayerMetadataProvider() = default;
/**
* Returns the id of the layer metadata provider implementation, usually the name of the data provider
* but it may be another unique identifier.
@ -239,8 +237,6 @@ class CORE_EXPORT QgsAbstractLayerMetadataProvider
*/
virtual QgsLayerMetadataSearchResults search( const QgsMetadataSearchContext &searchContext, const QString &searchString = QString(), const QgsRectangle &geographicExtent = QgsRectangle(), QgsFeedback *feedback = nullptr ) const = 0;
virtual ~QgsAbstractLayerMetadataProvider() = default;
};
#endif // QGSABSTRACTLAYERMETADATAPROVIDER_H

View File

@ -428,11 +428,6 @@ bool QgsLayerMetadata::operator==( const QgsLayerMetadata &other ) const
bool QgsLayerMetadata::contains( const QString &searchString ) const
{
if ( searchString.isEmpty() )
{
return true;
}
if ( title().contains( searchString, Qt::CaseInsensitive ) ||
identifier().contains( searchString, Qt::CaseInsensitive ) ||
abstract().contains( searchString, Qt::CaseInsensitive ) )

View File

@ -172,11 +172,6 @@ class CORE_EXPORT QgsLayerMetadata : public QgsAbstractMetadataBase
*/
QgsLayerMetadata() = default;
/**
* Destructor.
*/
~QgsLayerMetadata( ) = default;
/**
* Copy constructor for QgsLayerMetadataProviderResult from \a other.
* \since QGIS 3.28

View File

@ -25,7 +25,12 @@
QIcon QgsIconUtils::iconForWkbType( QgsWkbTypes::Type type )
{
const QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( QgsWkbTypes::Type( type ) );
switch ( geomType )
return iconForGeometryType( geomType );
}
QIcon QgsIconUtils::iconForGeometryType( QgsWkbTypes::GeometryType typeGroup )
{
switch ( typeGroup )
{
case QgsWkbTypes::NullGeometry:
return iconTable();

View File

@ -40,6 +40,12 @@ class CORE_EXPORT QgsIconUtils
*/
static QIcon iconForWkbType( QgsWkbTypes::Type type );
/**
* Returns the icon for a vector layer whose geometry \a typeGroup is provided.
* 1since QGIS 3.28
*/
static QIcon iconForGeometryType( QgsWkbTypes::GeometryType typeGroup );
/**
* Returns an icon representing point geometries.
*/

View File

@ -530,6 +530,7 @@ set(QGIS_GUI_SRCS
qgskeyvaluewidget.cpp
qgslayermetadatasearchwidget.cpp
qgslayermetadataresultsmodel.cpp
qgslayermetadatasourceselectprovider.cpp
qgslayermetadataresultsproxymodel.cpp
qgslistwidget.cpp
qgslegendfilterbutton.cpp
@ -795,6 +796,7 @@ set(QGIS_GUI_HDRS
qgslayermetadatasearchwidget.h
qgslayermetadataresultsmodel.h
qgslayermetadataresultsproxymodel.h
qgslayermetadatasourceselectprovider.h
qgslegendfilterbutton.h
qgslegendpatchshapebutton.h
qgslegendpatchshapewidget.h

View File

@ -38,6 +38,10 @@ class QgsBrowserModel;
* This class provides common functionality and the interface for all
* source select dialogs used by data providers to configure data sources
* and add layers.
*
* The implementation is generic enough to handle other layer search and
* selection widgets.
*
* \since QGIS 3.0
*/
class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog

View File

@ -67,43 +67,7 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsBrowserGuiModel *brow
connect( mBrowserWidget, &QgsBrowserDockWidget::connectionsChanged, this, &QgsDataSourceManagerDialog::connectionsChanged );
connect( this, &QgsDataSourceManagerDialog::updateProjectHome, mBrowserWidget->browserWidget(), &QgsBrowserWidget::updateProjectHome );
// METADATA Add the metadata search widget as the second stacked widget
mLayerMetadataSearchWidget = new QgsLayerMetadataSearchWidget( mMapCanvas, this );
connect( mLayerMetadataSearchWidget, &QgsLayerMetadataSearchWidget::rejected, this, &QgsDataSourceManagerDialog::reject );
ui->mOptionsStackedWidget->addWidget( mLayerMetadataSearchWidget );
mPageNames.append( QStringLiteral( "metadata" ) );
connect( mLayerMetadataSearchWidget, &QgsLayerMetadataSearchWidget::addLayers, this, [ = ]( const QList< QgsLayerMetadataProviderResult > &metadataResults )
{
for ( const QgsLayerMetadataProviderResult &metadata : std::as_const( metadataResults ) )
{
switch ( metadata.layerType() )
{
case QgsMapLayerType::VectorLayer:
{
emit addVectorLayer( metadata.uri(), metadata.identifier(), metadata.dataProviderName() );
break;
};
case QgsMapLayerType::RasterLayer:
{
emit addRasterLayer( metadata.uri(), metadata.identifier(), metadata.dataProviderName() );
break;
}
case QgsMapLayerType::MeshLayer:
{
emit addMeshLayer( metadata.uri(), metadata.identifier(), metadata.dataProviderName() );
break;
}
default: // Unsupported!
{
// Ignore
break;
}
}
}
} );
// Add provider dialogs
// Add registered source select dialogs
const QList<QgsSourceSelectProvider *> sourceSelectProviders = QgsGui::sourceSelectProviderRegistry()->providers( );
for ( QgsSourceSelectProvider *provider : sourceSelectProviders )
{

View File

@ -59,6 +59,7 @@
#include "qgsmaptoolshaperegistry.h"
#include "qgssettingsregistrygui.h"
#include "qgshistoryproviderregistry.h"
#include "qgslayermetadatasourceselectprovider.h"
#include <QPushButton>
#include <QToolButton>
@ -293,6 +294,7 @@ QgsGui::QgsGui()
mProjectStorageGuiRegistry->initializeFromProviderGuiRegistry( mProviderGuiRegistry );
mDataItemGuiProviderRegistry->initializeFromProviderGuiRegistry( mProviderGuiRegistry );
mSourceSelectProviderRegistry->initializeFromProviderGuiRegistry( mProviderGuiRegistry );
mSourceSelectProviderRegistry->addProvider( new QgsLayerMetadataSourceSelectProvider() );
mSubsetStringEditorProviderRegistry->initializeFromProviderGuiRegistry( mProviderGuiRegistry );
mProviderSourceWidgetProviderRegistry->initializeFromProviderGuiRegistry( mProviderGuiRegistry );

View File

@ -18,6 +18,9 @@
#include "qgsapplication.h"
#include "qgslayermetadataproviderregistry.h"
#include "qgslayermetadataformatter.h"
#include "qgsiconutils.h"
#include "qgsproviderregistry.h"
#include "qgsprovidermetadata.h"
#include <QIcon>
QgsLayerMetadataResultsModel::QgsLayerMetadataResultsModel( const QgsMetadataSearchContext &searchContext, QObject *parent )
@ -60,7 +63,11 @@ QVariant QgsLayerMetadataResultsModel::data( const QModelIndex &index, int role
case Sections::Abstract:
return mResult.metadata().at( index.row() ).abstract();
case Sections::DataProviderName:
return mResult.metadata().at( index.row() ).dataProviderName();
{
const QString providerName { mResult.metadata().at( index.row() ).dataProviderName() };
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( providerName ) };
return md ? md->description() : providerName;
}
case Sections::GeometryType:
return QgsWkbTypes::geometryDisplayString( mResult.metadata().at( index.row() ).geometryType() );
default:
@ -80,7 +87,7 @@ QVariant QgsLayerMetadataResultsModel::data( const QModelIndex &index, int role
{
if ( index.column() == 0 )
{
return mResult.metadata().at( index.row() ).layerTypeIcon();
return QgsIconUtils::iconForGeometryType( mResult.metadata().at( index.row() ).geometryType() );
}
break;
}

View File

@ -20,18 +20,20 @@
#include "qgsmapcanvas.h"
#include "qgsprojectviewsettings.h"
QgsLayerMetadataSearchWidget::QgsLayerMetadataSearchWidget( const QgsMapCanvas *mapCanvas, QWidget *parent )
: QWidget( parent )
, mMapCanvas( mapCanvas )
QgsLayerMetadataSearchWidget::QgsLayerMetadataSearchWidget( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode )
: QgsAbstractDataSourceWidget( parent, fl, widgetMode )
{
setupUi( this );
setupButtons( mButtonBox );
QgsMetadataSearchContext searchContext;
searchContext.transformContext = QgsProject::instance()->transformContext();
QgsLayerMetadataResultsModel *sourceModel = new QgsLayerMetadataResultsModel( searchContext, this );
mSourceModel = new QgsLayerMetadataResultsModel( searchContext, this );
mProxyModel = new QgsLayerMetadataResultsProxyModel( this );
mProxyModel->setSourceModel( sourceModel );
mProxyModel->setSourceModel( mSourceModel );
mMetadataTableView->setModel( mProxyModel );
mMetadataTableView->setSortingEnabled( true );
mMetadataTableView->sortByColumn( 0, Qt::SortOrder::AscendingOrder );
@ -61,8 +63,8 @@ QgsLayerMetadataSearchWidget::QgsLayerMetadataSearchWidget( const QgsMapCanvas *
}
};
connect( sourceModel, &QgsLayerMetadataResultsModel::progressChanged, mProgressBar, &QProgressBar::setValue );
connect( sourceModel, &QgsLayerMetadataResultsModel::progressChanged, this, [ = ]( int progress )
connect( mSourceModel, &QgsLayerMetadataResultsModel::progressChanged, mProgressBar, &QProgressBar::setValue );
connect( mSourceModel, &QgsLayerMetadataResultsModel::progressChanged, this, [ = ]( int progress )
{
if ( progress == 100 )
{
@ -71,66 +73,29 @@ QgsLayerMetadataSearchWidget::QgsLayerMetadataSearchWidget( const QgsMapCanvas *
}
} );
connect( mAbortPushButton, &QPushButton::clicked, sourceModel, [ = ]( bool )
connect( mAbortPushButton, &QPushButton::clicked, mSourceModel, [ = ]( bool )
{
if ( ! mIsLoading )
{
mIsLoading = true;
sourceModel->reloadAsync( );
mSourceModel->reloadAsync( );
}
else
{
sourceModel->cancel();
mSourceModel->cancel();
mIsLoading = false;
}
updateLoadBtn();
} );
// Setup buttons
mButtonBox->setStandardButtons( QDialogButtonBox::Apply | QDialogButtonBox::Close | QDialogButtonBox::Help );
#ifdef Q_OS_MACX
mButtonBox->setStyleSheet( "* { button-layout: 2 }" );
#endif
mAddButton = mButtonBox->button( QDialogButtonBox::Apply );
mAddButton->setText( tr( "&Add" ) );
mAddButton->setToolTip( tr( "Add selected layers to map" ) );
mAddButton->setEnabled( false );
connect( mAddButton, &QPushButton::clicked, this, [ = ]( bool )
{
const QModelIndexList &selectedIndexes { mMetadataTableView->selectionModel()->selectedRows() };
if ( ! selectedIndexes.isEmpty() )
{
QList< QgsLayerMetadataProviderResult > layersToAdd;
for ( const auto &selectedIndex : std::as_const( selectedIndexes ) )
{
layersToAdd.push_back( sourceModel->data( mProxyModel->mapToSource( selectedIndex ), QgsLayerMetadataResultsModel::Roles::Metadata ).value<QgsLayerMetadataProviderResult>() );
}
emit addLayers( layersToAdd );
}
} );
connect( mMetadataTableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [ = ]( const QItemSelection &, const QItemSelection & )
{
mAddButton->setEnabled( mMetadataTableView->selectionModel()->hasSelection() );
emit enableButtons( mMetadataTableView->selectionModel()->hasSelection() );
} );
QPushButton *closeButton = mButtonBox->button( QDialogButtonBox::Close );
closeButton->setToolTip( tr( "Close this dialog without adding any layer" ) );
connect( closeButton, &QPushButton::clicked, this, &QgsLayerMetadataSearchWidget::rejected );
connect( mSearchFilterLineEdit, &QLineEdit::textEdited, mProxyModel, &QgsLayerMetadataResultsProxyModel::setFilterString );
connect( mExtentFilterComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsLayerMetadataSearchWidget::updateExtentFilter );
if ( mMapCanvas )
{
connect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, [ = ]
{
updateExtentFilter( mExtentFilterComboBox->currentIndex() );
} );
}
connect( QgsProject::instance(), &QgsProject::layersAdded, this, [ = ]( const QList<QgsMapLayer *> & )
{
updateExtentFilter( mExtentFilterComboBox->currentIndex() );
@ -142,18 +107,30 @@ QgsLayerMetadataSearchWidget::QgsLayerMetadataSearchWidget( const QgsMapCanvas *
} );
// Start loading metadata in the model
sourceModel->reloadAsync();
mSourceModel->reloadAsync();
mIsLoading = true;
}
void QgsLayerMetadataSearchWidget::setMapCanvas( QgsMapCanvas *newMapCanvas )
{
if ( newMapCanvas && mapCanvas() != newMapCanvas )
{
connect( newMapCanvas, &QgsMapCanvas::extentsChanged, this, [ = ]
{
updateExtentFilter( mExtentFilterComboBox->currentIndex() );
} );
}
QgsAbstractDataSourceWidget::setMapCanvas( newMapCanvas );
}
void QgsLayerMetadataSearchWidget::updateExtentFilter( int index )
{
if ( index == 1 && mMapCanvas )
if ( index == 1 && mapCanvas() )
{
QgsCoordinateTransform ct( mMapCanvas->mapSettings().destinationCrs(), QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsProject::instance()->transformContext() );
QgsCoordinateTransform ct( mapCanvas()->mapSettings().destinationCrs(), QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsProject::instance()->transformContext() );
ct.setBallparkTransformsAreAppropriate( true );
mProxyModel->setFilterExtent( ct.transformBoundingBox( mMapCanvas->extent() ) );
mProxyModel->setFilterExtent( ct.transformBoundingBox( mapCanvas()->extent() ) );
}
else if ( index == 2 )
{
@ -167,3 +144,48 @@ void QgsLayerMetadataSearchWidget::updateExtentFilter( int index )
mProxyModel->setFilterExtent( QgsRectangle( ) );
}
}
void QgsLayerMetadataSearchWidget::refresh()
{
mSourceModel->reloadAsync();
}
void QgsLayerMetadataSearchWidget::addButtonClicked()
{
const QModelIndexList &selectedIndexes { mMetadataTableView->selectionModel()->selectedRows() };
if ( ! selectedIndexes.isEmpty() )
{
for ( const auto &selectedIndex : std::as_const( selectedIndexes ) )
{
const QgsLayerMetadataProviderResult metadataResult { mSourceModel->data( mProxyModel->mapToSource( selectedIndex ), QgsLayerMetadataResultsModel::Roles::Metadata ).value<QgsLayerMetadataProviderResult>() };
switch ( metadataResult.layerType() )
{
case QgsMapLayerType::RasterLayer:
{
emit addRasterLayer( metadataResult.uri(), metadataResult.identifier(), metadataResult.dataProviderName() );
break;
}
case QgsMapLayerType::VectorLayer:
{
emit addVectorLayer( metadataResult.uri(), metadataResult.identifier(), metadataResult.dataProviderName() );
break;
}
case QgsMapLayerType::MeshLayer:
{
emit addMeshLayer( metadataResult.uri(), metadataResult.identifier(), metadataResult.dataProviderName() );
}
default: // unsupported
{
// Ignore
break;
}
}
}
}
}
void QgsLayerMetadataSearchWidget::reset()
{
mSearchFilterLineEdit->clear();
mExtentFilterComboBox->setCurrentIndex( 0 );
}

View File

@ -21,9 +21,11 @@
#include "ui_qgslayermetadatasearchwidget.h"
#include "qgsfeedback.h"
#include "qgsabstractlayermetadataprovider.h"
#include "qgsabstractdatasourcewidget.h"
class QgsMapCanvas;
class QgsLayerMetadataResultsProxyModel;
class QgsLayerMetadataResultsModel;
/**
* \ingroup gui
@ -31,7 +33,7 @@ class QgsLayerMetadataResultsProxyModel;
* It is designed to be embedded in the data source manager dialog.
* \since QGIS 3.28
*/
class GUI_EXPORT QgsLayerMetadataSearchWidget : public QWidget, private Ui::QgsLayerMetadataSearchWidget
class GUI_EXPORT QgsLayerMetadataSearchWidget : public QgsAbstractDataSourceWidget, private Ui::QgsLayerMetadataSearchWidget
{
Q_OBJECT
public:
@ -41,28 +43,24 @@ class GUI_EXPORT QgsLayerMetadataSearchWidget : public QWidget, private Ui::QgsL
* \param mapCanvas optional map canvas
* \param parent optional parent
*/
explicit QgsLayerMetadataSearchWidget( const QgsMapCanvas *mapCanvas = nullptr, QWidget *parent = nullptr );
explicit QgsLayerMetadataSearchWidget( QWidget *parent = nullptr, Qt::WindowFlags fl = Qt::WindowFlags(), QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None );
signals:
//! Emitted when the close button is clicked.
void rejected();
//! Emitted when layers have been selected for addition
void addLayers( const QList< QgsLayerMetadataProviderResult > &metadataResults );
void setMapCanvas( QgsMapCanvas *mapCanvas ) override;
public slots:
//! Updates the extent filter based on the combo box current item \a index.
void updateExtentFilter( int index );
void refresh() override;
void addButtonClicked() override;
void reset() override;
private:
const QgsMapCanvas *mMapCanvas = nullptr;
QgsLayerMetadataResultsProxyModel *mProxyModel = nullptr;
bool mIsLoading = false;
QPushButton *mAddButton = nullptr;
QgsLayerMetadataResultsModel *mSourceModel = nullptr;
};

View File

@ -0,0 +1,50 @@
/***************************************************************************
qgslayermetadatasourceselectprovider.cpp - QgsLayerMetadataSourceSelectProvider
---------------------
begin : 6.9.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* 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 "qgslayermetadatasourceselectprovider.h"
#include "qgslayermetadatasearchwidget.h"
#include "qgsapplication.h"
QgsLayerMetadataSourceSelectProvider::QgsLayerMetadataSourceSelectProvider()
: QgsSourceSelectProvider()
{
}
QString QgsLayerMetadataSourceSelectProvider::providerKey() const
{
return QStringLiteral( "layermetadata" );
}
QString QgsLayerMetadataSourceSelectProvider::text() const
{
return QObject::tr( "Layer Metadata" );
}
QIcon QgsLayerMetadataSourceSelectProvider::icon() const
{
return QgsApplication::getThemeIcon( QStringLiteral( "search.svg" ) );
}
QgsAbstractDataSourceWidget *QgsLayerMetadataSourceSelectProvider::createDataSourceWidget( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) const
{
return new QgsLayerMetadataSearchWidget( parent, fl, widgetMode );
}
int QgsLayerMetadataSourceSelectProvider::ordering() const
{
return OrderSearchProvider;
}

View File

@ -0,0 +1,43 @@
/***************************************************************************
qgslayermetadatasourceselectprovider.h - QgsLayerMetadataSourceSelectProvider
---------------------
begin : 6.9.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* 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 QGSLAYERMETADATASOURCESELECTPROVIDER_H
#define QGSLAYERMETADATASOURCESELECTPROVIDER_H
#include "qgssourceselectprovider.h"
#include "qgis_gui.h"
/**
* \ingroup gui
* \brief Source select provider for layer metadata.
*
* \since QGIS 3.28
*/
class GUI_EXPORT QgsLayerMetadataSourceSelectProvider : public QgsSourceSelectProvider
{
public:
QgsLayerMetadataSourceSelectProvider();
// QgsSourceSelectProvider interface
public:
QString providerKey() const override;
QString text() const override;
QIcon icon() const override;
QgsAbstractDataSourceWidget *createDataSourceWidget( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) const override;
int ordering() const override;
};
#endif // QGSLAYERMETADATASOURCESELECTPROVIDER_H

View File

@ -43,7 +43,8 @@ class GUI_EXPORT QgsSourceSelectProvider
OrderDatabaseProvider = 1000, //!< Starting point for database providers (e.g. Postgres)
OrderRemoteProvider = 2000, //!< Starting point for remote (online) providers (e.g. WMS)
OrderGeoCmsProvider = 3000, //!< Starting point for GeoCMS type providers (e.g. GeoNode)
OrderOtherProvider = 4000, //!< Starting point for other providers (e.g. plugin based providers)
OrderSearchProvider = 4000, //!< Starting point for search providers (e.g. Layer Metadata)
OrderOtherProvider = 5000, //!< Starting point for other providers (e.g. plugin based providers)
};
virtual ~QgsSourceSelectProvider() = default;

View File

@ -116,15 +116,6 @@
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
</property>
</item>
<item>
<property name="text">
<string>Metadata</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/propertyicons/metadata.svg</normaloff>:/images/themes/default/propertyicons/metadata.svg</iconset>
</property>
</item>
</widget>
</item>
</layout>

View File

@ -24,7 +24,7 @@
</widget>
</item>
<item>
<widget class="QLineEdit" name="mSearchFilterLineEdit"/>
<widget class="QgsFilterLineEdit" name="mSearchFilterLineEdit"/>
</item>
<item>
<widget class="QLabel" name="label_2">
@ -74,12 +74,19 @@
<item>
<widget class="QDialogButtonBox" name="mButtonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Help</set>
<set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Help</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsFilterLineEdit</class>
<extends>QLineEdit</extends>
<header>qgsfilterlineedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -145,6 +145,7 @@ ADD_PYTHON_TEST(PyQgsLabelThinningSettings test_qgslabelthinningsettings.py)
ADD_PYTHON_TEST(PyQgsLayerMetadata test_qgslayermetadata.py)
ADD_PYTHON_TEST(PyQgsLayerMetadataProviderPython test_qgslayermetadataprovider_python.py)
ADD_PYTHON_TEST(PyQgsLayerMetadataProviderOgr test_qgslayermetadataprovider_ogr.py)
ADD_PYTHON_TEST(PyQgsLayerMetadataResultsModel test_qgslayermetadataresultsmodel.py)
ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.py)
ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py)
ADD_PYTHON_TEST(PyQgsLayerTreeView test_qgslayertreeview.py)

View File

@ -0,0 +1,130 @@
# coding=utf-8
""""Base test for layer metadata models
.. note:: 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.
"""
__author__ = 'elpaso@itopen.it'
__date__ = '2022-08-19'
__copyright__ = 'Copyright 2022, ItOpen'
import os
from osgeo import ogr
from qgis.core import (
QgsVectorLayer,
QgsProviderRegistry,
QgsWkbTypes,
QgsLayerMetadata,
QgsProviderMetadata,
QgsBox3d,
QgsRectangle,
QgsMetadataSearchContext,
)
from qgis.gui import (
QgsLayerMetadataResultsModel,
QgsLayerMetadataResultsProxyModel,
)
from qgis.PyQt.QtTest import QAbstractItemModelTester
from qgis.PyQt.QtCore import QCoreApplication, QTemporaryDir, QVariant, Qt
from utilities import compareWkt, unitTestDataPath
from qgis.testing import start_app, TestCase
import unittest
QGIS_APP = start_app()
NUM_LAYERS = 20
class TestQgsLayerMetadataResultModels(TestCase):
"""Base test for layer metadata provider models
"""
@classmethod
def setUpClass(cls):
"""Run before all tests"""
QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain(cls.__name__)
QCoreApplication.setApplicationName(cls.__name__)
def setUp(self):
super().setUp()
self.temp_dir = QTemporaryDir()
self.temp_path = self.temp_dir.path()
self.temp_gpkg = os.path.join(self.temp_path, 'test.gpkg')
ds = ogr.GetDriverByName('GPKG').CreateDataSource(self.temp_gpkg)
md = QgsProviderRegistry.instance().providerMetadata('ogr')
self.assertIsNotNone(md)
self.assertTrue(bool(md.providerCapabilities() & QgsProviderMetadata.ProviderCapability.SaveLayerMetadata))
self.conn = md.createConnection(self.temp_gpkg, {})
self.conn.store('test_conn')
for i in range(NUM_LAYERS):
lyr = ds.CreateLayer("layer_%s" % i, geom_type=ogr.wkbPoint, options=['SPATIAL_INDEX=NO'])
lyr.CreateField(ogr.FieldDefn('text_field', ogr.OFTString))
f = ogr.Feature(lyr.GetLayerDefn())
f['text_field'] = 'foo'
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(%s %s)' % (i, i + 0.01)))
lyr.CreateFeature(f)
f = ogr.Feature(lyr.GetLayerDefn())
f['text_field'] = 'bar'
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(%s %s)' % (i + 0.03, i + 0.04)))
lyr.CreateFeature(f)
f = None
ds = None
for t in self.conn.tables():
layer_uri = self.conn.tableUri('', t.tableName())
vl = QgsVectorLayer(layer_uri, t.tableName(), 'ogr')
self.assertTrue(vl.isValid())
metadata = vl.metadata()
ext = QgsLayerMetadata.Extent()
spatial_ext = QgsLayerMetadata.SpatialExtent()
spatial_ext.bounds = QgsBox3d(vl.extent())
spatial_ext.crs = vl.crs()
ext.setSpatialExtents([spatial_ext])
metadata.setExtent(ext)
metadata.setIdentifier(t.tableName())
self.assertTrue(md.saveLayerMetadata(layer_uri, metadata)[0])
def testModels(self):
search_context = QgsMetadataSearchContext()
model = QgsLayerMetadataResultsModel(search_context)
proxy_model = QgsLayerMetadataResultsProxyModel()
proxy_model.setSourceModel(model)
tester = QAbstractItemModelTester(proxy_model, QAbstractItemModelTester.FailureReportingMode.Fatal)
model.reload()
proxy_model.setFilterString('_1')
self.assertEqual(proxy_model.rowCount(), 11)
proxy_model.setFilterString('_11')
self.assertEqual(proxy_model.rowCount(), 1)
metadata = proxy_model.data(proxy_model.index(0, 0), QgsLayerMetadataResultsModel.Roles.Metadata)
self.assertEqual(metadata.identifier(), 'layer_11')
proxy_model.setFilterString('')
self.assertEqual(proxy_model.rowCount(), 20)
proxy_model.setFilterExtent(QgsRectangle(0, 0, 2, 2.001))
self.assertEqual(proxy_model.rowCount(), 2)
model.reload()
self.assertEqual(proxy_model.rowCount(), 2)
proxy_model.setFilterExtent(QgsRectangle())
metadata = proxy_model.data(proxy_model.index(0, 0), QgsLayerMetadataResultsModel.Roles.Metadata)
self.assertEqual(metadata.identifier(), 'layer_0')
proxy_model.sort(0, Qt.DescendingOrder)
metadata = proxy_model.data(proxy_model.index(0, 0), QgsLayerMetadataResultsModel.Roles.Metadata)
self.assertEqual(metadata.identifier(), 'layer_9')
if __name__ == '__main__':
unittest.main()