Merge pull request #5156 from boundlessgeo/gpkg_tab

[feature][needs-docs] Gpkg tab in source select dialog
This commit is contained in:
Alessandro Pasotti 2017-09-11 08:15:55 +02:00 committed by GitHub
commit 32dee4480c
19 changed files with 1142 additions and 268 deletions

View File

@ -565,8 +565,9 @@
<file>themes/default/mActionNewMap.svg</file>
<file>themes/default/mActionMapSettings.svg</file>
<file>themes/default/mActionLockExtent.svg</file>
<file>icons/qgis_icon.svg</file>
<file>themes/default/mGeoPackage.svg</file>
<file>themes/default/mActionAddGeoPackageLayer.svg</file>
<file>icons/qgis_icon.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -414,7 +414,14 @@ Returns provider key
static QString layerTypeAsString( const LayerType &layerType );
%Docstring
Returns the string representatio of the given ``layerType``
Returns the string representation of the given ``layerType``
.. versionadded:: 3
:rtype: str
%End
static QString iconName( const LayerType &layerType );
%Docstring
Returns the icon name of the given ``layerType``
.. versionadded:: 3
:rtype: str
%End

View File

@ -546,31 +546,7 @@ QgsLayerItem::QgsLayerItem( QgsDataItem *parent, const QString &name, const QStr
, mUri( uri )
, mLayerType( layerType )
{
switch ( layerType )
{
case Point:
mIconName = QStringLiteral( "/mIconPointLayer.svg" );
break;
case Line:
mIconName = QStringLiteral( "/mIconLineLayer.svg" );
break;
case Polygon:
mIconName = QStringLiteral( "/mIconPolygonLayer.svg" );
break;
// TODO add a new icon for generic Vector layers
case Vector :
mIconName = QStringLiteral( "/mIconPolygonLayer.svg" );
break;
case TableLayer:
mIconName = QStringLiteral( "/mIconTableLayer.svg" );
break;
case Raster:
mIconName = QStringLiteral( "/mIconRaster.svg" );
break;
default:
mIconName = QStringLiteral( "/mIconLayer.png" );
break;
}
mIconName = iconName( layerType );
}
QgsMapLayer::LayerType QgsLayerItem::mapLayerType() const
@ -588,6 +564,36 @@ QString QgsLayerItem::layerTypeAsString( const QgsLayerItem::LayerType &layerTyp
return staticMetaObject.enumerator( enumIdx ).valueToKey( layerType );
}
QString QgsLayerItem::iconName( const QgsLayerItem::LayerType &layerType )
{
switch ( layerType )
{
case Point:
return QStringLiteral( "/mIconPointLayer.svg" );
break;
case Line:
return QStringLiteral( "/mIconLineLayer.svg" );
break;
case Polygon:
return QStringLiteral( "/mIconPolygonLayer.svg" );
break;
// TODO add a new icon for generic Vector layers
case Vector :
return QStringLiteral( "/mIconPolygonLayer.svg" );
break;
case TableLayer:
case Table:
return QStringLiteral( "/mIconTableLayer.svg" );
break;
case Raster:
return QStringLiteral( "/mIconRaster.svg" );
break;
default:
return QStringLiteral( "/mIconLayer.png" );
break;
}
}
bool QgsLayerItem::equal( const QgsDataItem *other )
{
//QgsDebugMsg ( mPath + " x " + other->mPath );

View File

@ -384,11 +384,16 @@ class CORE_EXPORT QgsLayerItem : public QgsDataItem
*/
virtual QString comments() const { return QString(); }
/** Returns the string representatio of the given \a layerType
/** Returns the string representation of the given \a layerType
* \since QGIS 3
*/
static QString layerTypeAsString( const LayerType &layerType );
/** Returns the icon name of the given \a layerType
* \since QGIS 3
*/
static QString iconName( const LayerType &layerType );
protected:
//! The provider key

View File

@ -7,9 +7,11 @@ SET (OGR_SRCS
qgsogrexpressioncompiler.cpp
qgsogrsourceselect.cpp
qgsgeopackagedataitems.cpp
qgsgeopackageconnection.cpp
qgsgeopackagerasterwriter.cpp
qgsgeopackagerasterwritertask.cpp
qgsogrdbconnection.cpp
qgsogrdbsourceselect.cpp
qgsogrdbtablemodel.cpp
)
SET(OGR_MOC_HDRS
@ -18,8 +20,10 @@ SET(OGR_MOC_HDRS
qgsogrconnpool.h
qgsogrsourceselect.h
qgsgeopackagedataitems.h
qgsgeopackageconnection.h
qgsgeopackagerasterwritertask.h
qgsogrdbconnection.h
qgsogrdbsourceselect.h
qgsogrdbtablemodel.h
)
########################################################

View File

@ -16,18 +16,18 @@
#include "sqlite3.h"
#include "qgsgeopackagedataitems.h"
#include "qgsgeopackageconnection.h"
#include "qgsogrdbconnection.h"
#include "qgslogger.h"
#include "qgssettings.h"
#include "qgsproject.h"
#include "qgsvectorlayer.h"
#include "qgsrasterlayer.h"
#include "qgsogrprovider.h"
#include "qgsogrdataitems.h"
#include "qgsnewgeopackagelayerdialog.h"
#include "qgsmessageoutput.h"
#include "qgsvectorlayerexporter.h"
#include "qgsgeopackagerasterwritertask.h"
#include "gdal.h"
#include <QAction>
#include <QMessageBox>
@ -62,10 +62,10 @@ QgsGeoPackageRootItem::~QgsGeoPackageRootItem()
QVector<QgsDataItem *> QgsGeoPackageRootItem::createChildren()
{
QVector<QgsDataItem *> connections;
Q_FOREACH ( const QString &connName, QgsGeoPackageConnection::connectionList() )
const QStringList connList( QgsOgrDbConnection::connectionList( QStringLiteral( "GPKG" ) ) );
for ( const QString &connName : connList )
{
QgsGeoPackageConnection connection( connName );
QgsOgrDbConnection connection( connName, QStringLiteral( "GPKG" ) );
QgsDataItem *conn = new QgsGeoPackageConnectionItem( this, connection.name(), connection.uri().encodedUri() );
connections.append( conn );
@ -102,9 +102,10 @@ void QgsGeoPackageRootItem::connectionsChanged()
void QgsGeoPackageRootItem::newConnection()
{
// TODO use QgsFileWidget
QString path = QFileDialog::getOpenFileName( nullptr, tr( "Open GeoPackage" ), "", tr( "GeoPackage Database (*.gpkg)" ) );
storeConnection( path );
if ( QgsOgrDataCollectionItem::createConnection( QStringLiteral( "GeoPackage" ), QStringLiteral( "GeoPackage Database (*.gpkg)" ), QStringLiteral( "GPKG" ) ) )
{
refreshConnections();
}
}
@ -115,37 +116,14 @@ void QgsGeoPackageRootItem::createDatabase()
dialog.setCrs( QgsProject::instance()->defaultCrsForNewLayers() );
if ( dialog.exec() == QDialog::Accepted )
{
storeConnection( dialog.databasePath() );
if ( QgsOgrDataCollectionItem::storeConnection( dialog.databasePath(), QStringLiteral( "GPKG" ) ) )
{
refreshConnections();
}
}
}
#endif
bool QgsGeoPackageRootItem::storeConnection( const QString &path )
{
QFileInfo fileInfo( path );
QString connName = fileInfo.fileName();
if ( ! path.isEmpty() )
{
bool ok = true;
while ( ok && ! QgsGeoPackageConnection( connName ).path( ).isEmpty( ) )
{
connName = QInputDialog::getText( nullptr, tr( "Cannot add connection '%1'" ).arg( connName ),
tr( "A connection with the same name already exists,\nplease provide a new name:" ), QLineEdit::Normal,
QLatin1String( "" ), &ok );
}
if ( ok && ! connName.isEmpty() )
{
QgsGeoPackageConnection connection( connName );
connection.setPath( path );
connection.save();
refreshConnections();
return true;
}
}
return false;
}
QgsGeoPackageConnectionItem::QgsGeoPackageConnectionItem( QgsDataItem *parent, QString name, QString path )
: QgsDataCollectionItem( parent, name, path )
@ -154,133 +132,25 @@ QgsGeoPackageConnectionItem::QgsGeoPackageConnectionItem( QgsDataItem *parent, Q
mCapabilities |= Collapse;
}
QVector<QgsDataItem *> QgsGeoPackageConnectionItem::createChildren()
{
QVector<QgsDataItem *> children;
// Vector layers
QgsVectorLayer layer( mPath, QStringLiteral( "ogr_tmp" ), QStringLiteral( "ogr" ) );
if ( ! layer.isValid( ) )
const auto layers = QgsOgrLayerItem::subLayers( mPath, QStringLiteral( "GPKG" ) );
for ( const QgsOgrDbLayerInfo *info : layers )
{
QgsDebugMsgLevel( tr( "Layer is not a valid GeoPackage Vector layer %1" ).arg( mPath ), 3 );
}
else
{
// Collect mixed-geom layers
QMultiMap<int, QStringList> subLayers;
Q_FOREACH ( const QString &descriptor, layer.dataProvider()->subLayers( ) )
if ( info->layerType() == QgsLayerItem::LayerType::Raster )
{
QStringList pieces = descriptor.split( ':' );
subLayers.insert( pieces[0].toInt(), pieces );
}
int prevIdx = -1;
Q_FOREACH ( const int &idx, subLayers.keys( ) )
{
if ( idx == prevIdx )
{
continue;
}
prevIdx = idx;
QList<QStringList> values = subLayers.values( idx );
for ( int i = 0; i < values.size(); ++i )
{
QStringList pieces = values.at( i );
QString layerId = pieces[0];
QString name = pieces[1];
// QString featuresCount = pieces[2]; // Not used
QString geometryType = pieces[3];
QgsLayerItem::LayerType layerType;
layerType = layerTypeFromDb( geometryType );
// example URI for mixed-geoms geoms: '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=7|geometrytype=Point'
// example URI for mixed-geoms attr table: '/path/gdal_sample_v1.2_no_extensions.gpkg|layername=MyLayer|layerid=7'
// example URI for single geoms: '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=6'
QString uri;
// Check if it's a mixed geometry type
if ( i == 0 && values.size() > 1 )
{
uri = QStringLiteral( "%1|layerid=%2|layername=%3" ).arg( mPath, layerId, name );
QgsGeoPackageVectorLayerItem *item = new QgsGeoPackageVectorLayerItem( this, name, mPath, uri, QgsLayerItem::LayerType::TableLayer );
children.append( item );
}
if ( layerType != QgsLayerItem::LayerType::NoType )
{
if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
{
QgsDebugMsgLevel( QStringLiteral( "Layer %1 is a geometry collection: skipping %2" ).arg( name, mPath ), 3 );
}
else
{
if ( values.size() > 1 )
{
uri = QStringLiteral( "%1|layerid=%2|geometrytype=%3" ).arg( mPath, layerId, geometryType );
}
else
{
uri = QStringLiteral( "%1|layerid=%2" ).arg( mPath, layerId );
}
QgsGeoPackageVectorLayerItem *item = new QgsGeoPackageVectorLayerItem( this, name, mPath, uri, layerType );
QgsDebugMsgLevel( QStringLiteral( "Adding GeoPackage Vector item %1 %2 %3" ).arg( name, uri, geometryType ), 3 );
children.append( item );
}
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Layer type is not a supported GeoPackage Vector layer %1" ).arg( mPath ), 3 );
}
QgsDebugMsgLevel( QStringLiteral( "Adding GeoPackage Vector item %1 %2 %3" ).arg( name, uri, geometryType ), 3 );
}
}
}
// Raster layers
QgsRasterLayer rlayer( mPath, QStringLiteral( "gdal_tmp" ), QStringLiteral( "gdal" ), false );
if ( rlayer.dataProvider()->subLayers( ).size() > 0 )
{
Q_FOREACH ( const QString &uri, rlayer.dataProvider()->subLayers( ) )
{
QStringList pieces = uri.split( ':' );
QString name = pieces.value( pieces.length() - 1 );
QgsDebugMsgLevel( QStringLiteral( "Adding GeoPackage Raster item %1 %2 %3" ).arg( name, uri ), 3 );
QgsGeoPackageRasterLayerItem *item = new QgsGeoPackageRasterLayerItem( this, name, mPath, uri );
children.append( item );
}
}
else if ( rlayer.isValid( ) )
{
// Get the identifier
GDALAllRegister();
// do not print errors, but write to debug
CPLPushErrorHandler( CPLQuietErrorHandler );
CPLErrorReset();
GDALDatasetH hDS = GDALOpen( mPath.toUtf8().constData(), GA_ReadOnly );
CPLPopErrorHandler();
if ( ! hDS )
{
QgsDebugMsg( QString( "GDALOpen error # %1 : %2 " ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ) );
children.append( new QgsGeoPackageRasterLayerItem( this, info->name(), info->path(), info->uri() ) );
}
else
{
QString uri( QStringLiteral( "GPKG:%1" ).arg( mPath ) );
QString name = GDALGetMetadataItem( hDS, "IDENTIFIER", NULL );
GDALClose( hDS );
// Fallback: will not be able to delete the table
if ( name.isEmpty() )
{
name = QFileInfo( mPath ).fileName();
}
else
{
uri += QStringLiteral( ":%1" ).arg( name );
}
QgsDebugMsgLevel( QStringLiteral( "Adding GeoPackage Raster item %1 %2 %3" ).arg( name, mPath ), 3 );
QgsGeoPackageRasterLayerItem *item = new QgsGeoPackageRasterLayerItem( this, name, mPath, uri );
children.append( item );
children.append( new QgsGeoPackageVectorLayerItem( this, info->name(), info->path(), info->uri(), info->layerType( ) ) );
}
}
qDeleteAll( layers );
return children;
}
bool QgsGeoPackageConnectionItem::equal( const QgsDataItem *other )
@ -327,8 +197,8 @@ bool QgsGeoPackageConnectionItem::handleDrop( const QMimeData *data, Qt::DropAct
QStringList importResults;
bool hasError = false;
QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( data );
Q_FOREACH ( const QgsMimeDataUtils::Uri &dropUri, lst )
const auto lst = QgsMimeDataUtils::decodeUriList( data );
for ( const QgsMimeDataUtils::Uri &dropUri : lst )
{
// Check that we are not copying over self
if ( dropUri.uri.startsWith( mPath ) )
@ -369,9 +239,8 @@ bool QgsGeoPackageConnectionItem::handleDrop( const QMimeData *data, Qt::DropAct
// check if the destination layer already exists
bool exists = false;
// Q_FOREACH won't detach ...
const QVector< QgsDataItem *> c = children();
for ( const auto child : c )
const auto c( children() );
for ( const QgsDataItem *child : c )
{
if ( child->name() == dropUri.name )
{
@ -464,32 +333,6 @@ bool QgsGeoPackageConnectionItem::handleDrop( const QMimeData *data, Qt::DropAct
}
QgsLayerItem::LayerType QgsGeoPackageConnectionItem::layerTypeFromDb( const QString &geometryType )
{
if ( geometryType.contains( QStringLiteral( "Point" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Point;
}
else if ( geometryType.contains( QStringLiteral( "Polygon" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Polygon;
}
else if ( geometryType.contains( QStringLiteral( "LineString" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Line;
}
else if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Vector;
}
// To be moved in a parent class that would also work for gdal and rasters
else if ( geometryType.contains( QStringLiteral( "Raster" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Raster;
}
return QgsLayerItem::LayerType::TableLayer;
}
bool QgsGeoPackageConnectionItem::deleteGeoPackageRasterLayer( const QString uri, QString &errCause )
{
bool result = false;
@ -536,7 +379,7 @@ bool QgsGeoPackageConnectionItem::deleteGeoPackageRasterLayer( const QString uri
QStringList optionalTables;
optionalTables << QStringLiteral( "gpkg_extensions" )
<< QStringLiteral( "gpkg_metadata_reference" );
Q_FOREACH ( const QString &tableName, optionalTables )
for ( const QString &tableName : qgsAsConst( optionalTables ) )
{
char *sql = sqlite3_mprintf( "DELETE FROM %w WHERE table_name = '%q'",
tableName.toUtf8().constData(),
@ -610,7 +453,7 @@ bool QgsGeoPackageConnectionItem::deleteGeoPackageRasterLayer( const QString uri
void QgsGeoPackageConnectionItem::deleteConnection()
{
QgsGeoPackageConnection::deleteConnection( name() );
QgsOgrDbConnection::deleteConnection( name(), QStringLiteral( "GeoPackage" ) );
mParent->refreshConnections();
}
@ -620,7 +463,7 @@ void QgsGeoPackageConnectionItem::addTable()
QgsNewGeoPackageLayerDialog dialog( nullptr );
QFileInfo fileInfo( mPath );
QString connName = fileInfo.fileName();
QgsGeoPackageConnection connection( connName );
QgsOgrDbConnection connection( connName, QStringLiteral( "GeoPackage" ) );
if ( ! connection.path().isEmpty() )
{
dialog.setDatabasePath( connection.path() );
@ -652,7 +495,8 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer()
{
// Check if the layer is in the registry
const QgsMapLayer *projectLayer = nullptr;
Q_FOREACH ( const QgsMapLayer *layer, QgsProject::instance()->mapLayers() )
const auto mapLayers( QgsProject::instance()->mapLayers() );
for ( const QgsMapLayer *layer : mapLayers )
{
if ( layer->publicSource() == mUri )
{

View File

@ -19,6 +19,7 @@
#include "qgsdataitemprovider.h"
#include "qgsdataprovider.h"
/**
* \brief The QgsGeoPackageAbstractLayerItem class is the base class for GeoPackage raster and vector layers
*/
@ -69,7 +70,6 @@ class QgsGeoPackageConnectionItem : public QgsDataCollectionItem
public:
QgsGeoPackageConnectionItem( QgsDataItem *parent, QString name, QString path );
QVector<QgsDataItem *> createChildren() override;
virtual bool equal( const QgsDataItem *other ) override;
@ -116,10 +116,6 @@ class QgsGeoPackageRootItem : public QgsDataCollectionItem
void connectionsChanged();
void createDatabase();
#endif
private:
bool storeConnection( const QString &path );
};

View File

@ -14,20 +14,26 @@
***************************************************************************/
#include "qgsogrdataitems.h"
#include "qgsogrdbconnection.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include "qgssettings.h"
#include "qgsproject.h"
#include "qgsvectorlayer.h"
#include "qgsrasterlayer.h"
#include <QFileInfo>
#include <QTextStream>
#include <QAction>
#include <QMessageBox>
#include <QInputDialog>
#include <QFileDialog>
#include <ogr_srs_api.h>
#include <cpl_error.h>
#include <cpl_conv.h>
#include <gdal.h>
// these are defined in qgsogrprovider.cpp
QGISEXTERN QStringList fileExtensions();
@ -107,6 +113,156 @@ bool QgsOgrLayerItem::setCrs( const QgsCoordinateReferenceSystem &crs )
return true;
}
QgsLayerItem::LayerType QgsOgrLayerItem::layerTypeFromDb( const QString &geometryType )
{
if ( geometryType.contains( QStringLiteral( "Point" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Point;
}
else if ( geometryType.contains( QStringLiteral( "Polygon" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Polygon;
}
else if ( geometryType.contains( QStringLiteral( "LineString" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Line;
}
else if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Vector;
}
// To be moved in a parent class that would also work for gdal and rasters
else if ( geometryType.contains( QStringLiteral( "Raster" ), Qt::CaseInsensitive ) )
{
return QgsLayerItem::LayerType::Raster;
}
return QgsLayerItem::LayerType::TableLayer;
}
QList<QgsOgrDbLayerInfo *> QgsOgrLayerItem::subLayers( const QString &path, const QString &driver )
{
QList<QgsOgrDbLayerInfo *> children;
// Vector layers
QgsVectorLayer layer( path, QStringLiteral( "ogr_tmp" ), QStringLiteral( "ogr" ) );
if ( ! layer.isValid( ) )
{
QgsDebugMsgLevel( tr( "Layer is not a valid %1 Vector layer %2" ).arg( path ), 3 );
}
else
{
// Collect mixed-geom layers
QMultiMap<int, QStringList> subLayersMap;
const QStringList subLayersList( layer.dataProvider()->subLayers( ) );
for ( const QString &descriptor : subLayersList )
{
QStringList pieces = descriptor.split( ':' );
subLayersMap.insert( pieces[0].toInt(), pieces );
}
int prevIdx = -1;
for ( const int &idx : subLayersMap.keys( ) )
{
if ( idx == prevIdx )
{
continue;
}
prevIdx = idx;
QList<QStringList> values = subLayersMap.values( idx );
for ( int i = 0; i < values.size(); ++i )
{
QStringList pieces = values.at( i );
QString layerId = pieces[0];
QString name = pieces[1];
// QString featuresCount = pieces[2]; // Not used
QString geometryType = pieces[3];
QString geometryColumn = pieces[4];
QgsLayerItem::LayerType layerType;
layerType = QgsOgrLayerItem::layerTypeFromDb( geometryType );
// example URI for mixed-geoms geoms: '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=7|geometrytype=Point'
// example URI for mixed-geoms attr table: '/path/gdal_sample_v1.2_no_extensions.gpkg|layername=MyLayer|layerid=7'
// example URI for single geoms: '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=6'
QString uri;
if ( layerType != QgsLayerItem::LayerType::NoType )
{
if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
{
QgsDebugMsgLevel( QStringLiteral( "Layer %1 is a geometry collection: skipping %2" ).arg( name, path ), 3 );
}
else
{
if ( values.size() > 1 )
{
uri = QStringLiteral( "%1|layerid=%2|geometrytype=%3" ).arg( path, layerId, geometryType );
}
else
{
uri = QStringLiteral( "%1|layerid=%2" ).arg( path, layerId );
}
QgsDebugMsgLevel( QStringLiteral( "Adding %1 Vector item %2 %3 %4" ).arg( driver, name, uri, geometryType ), 3 );
children.append( new QgsOgrDbLayerInfo( path, uri, name, geometryColumn, geometryType, layerType ) );
}
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Layer type is not a supported %1 Vector layer %2" ).arg( driver, path ), 3 );
uri = QStringLiteral( "%1|layerid=%2|layername=%3" ).arg( path, layerId, name );
children.append( new QgsOgrDbLayerInfo( path, uri, name, geometryColumn, geometryType, QgsLayerItem::LayerType::TableLayer ) );
}
QgsDebugMsgLevel( QStringLiteral( "Adding %1 Vector item %2 %3 %4" ).arg( driver, name, uri, geometryType ), 3 );
}
}
}
// Raster layers
QgsRasterLayer rlayer( path, QStringLiteral( "gdal_tmp" ), QStringLiteral( "gdal" ), false );
if ( rlayer.dataProvider()->subLayers( ).size() > 0 )
{
const QStringList layers( rlayer.dataProvider()->subLayers( ) );
for ( const QString &uri : layers )
{
QStringList pieces = uri.split( ':' );
QString name = pieces.value( pieces.length() - 1 );
QgsDebugMsgLevel( QStringLiteral( "Adding GeoPackage Raster item %1 %2 %3" ).arg( name, uri ), 3 );
children.append( new QgsOgrDbLayerInfo( path, uri, name, QStringLiteral( "" ), QStringLiteral( "Raster" ), QgsLayerItem::LayerType::Raster ) );
}
}
else if ( rlayer.isValid( ) )
{
// Get the identifier
GDALAllRegister();
// do not print errors, but write to debug
CPLPushErrorHandler( CPLQuietErrorHandler );
CPLErrorReset();
GDALDatasetH hDS = GDALOpen( path.toUtf8().constData(), GA_ReadOnly );
CPLPopErrorHandler();
if ( ! hDS )
{
QgsDebugMsg( QString( "GDALOpen error # %1 : %2 " ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ) );
}
else
{
QString uri( QStringLiteral( "%1:%1" ).arg( driver, path ) );
QString name = GDALGetMetadataItem( hDS, "IDENTIFIER", NULL );
GDALClose( hDS );
// Fallback: will not be able to delete the table
if ( name.isEmpty() )
{
name = QFileInfo( path ).fileName();
}
else
{
uri += QStringLiteral( ":%1" ).arg( name );
}
QgsDebugMsgLevel( QStringLiteral( "Adding %1 Raster item %2 %3" ).arg( driver, name, path ), 3 );
children.append( new QgsOgrDbLayerInfo( path, uri, name, QStringLiteral( "" ), QStringLiteral( "Raster" ), QgsLayerItem::LayerType::Raster ) );
}
}
return children;
}
QString QgsOgrLayerItem::layerName() const
{
QFileInfo info( name() );
@ -266,6 +422,38 @@ QVector<QgsDataItem *> QgsOgrDataCollectionItem::createChildren()
return children;
}
bool QgsOgrDataCollectionItem::storeConnection( const QString &path, const QString &ogrDriverName )
{
QFileInfo fileInfo( path );
QString connName = fileInfo.fileName();
if ( ! path.isEmpty() )
{
bool ok = true;
while ( ok && ! QgsOgrDbConnection( connName, ogrDriverName ).path( ).isEmpty( ) )
{
connName = QInputDialog::getText( nullptr, tr( "Cannot add connection '%1'" ).arg( connName ),
tr( "A connection with the same name already exists,\nplease provide a new name:" ), QLineEdit::Normal,
QLatin1String( "" ), &ok );
}
if ( ok && ! connName.isEmpty() )
{
QgsOgrDbConnection connection( connName, ogrDriverName );
connection.setPath( path );
connection.save();
return true;
}
}
return false;
}
bool QgsOgrDataCollectionItem::createConnection( const QString &name, const QString &extensions, const QString &ogrDriverName )
{
QString path = QFileDialog::getOpenFileName( nullptr, tr( "Open %1" ).arg( name ), "", extensions );
return storeConnection( path, ogrDriverName );
}
// ---------------------------------------------------------------------------
QGISEXTERN int dataCapabilities()

View File

@ -20,6 +20,39 @@
#include "qgsogrprovider.h"
#include "qgsdataitemprovider.h"
/**
* Holds the information about a gpkg layer
*/
class QgsOgrDbLayerInfo
{
public:
QgsOgrDbLayerInfo( const QString &path, const QString &uri, const QString &name, const QString &theGeometryColumn, const QString &theGeometryType, const QgsLayerItem::LayerType &theLayerType )
: mPath( path )
, mUri( uri )
, mName( name )
, mGeometryColumn( theGeometryColumn )
, mGeometryType( theGeometryType )
, mLayerType( theLayerType )
{
}
const QString path() const { return mPath; }
const QString uri() const { return mUri; }
const QString name() const { return mName; }
const QString geometryColumn() const { return mGeometryColumn; }
const QString geometryType() const { return mGeometryType; }
QgsLayerItem::LayerType layerType() const { return mLayerType; }
private:
QString mPath;
QString mUri;
QString mName;
QString mGeometryColumn;
QString mGeometryType;
QgsLayerItem::LayerType mLayerType = QgsLayerItem::LayerType::NoType;
};
class QgsOgrLayerItem : public QgsLayerItem
{
Q_OBJECT
@ -29,6 +62,10 @@ class QgsOgrLayerItem : public QgsLayerItem
bool setCrs( const QgsCoordinateReferenceSystem &crs ) override;
QString layerName() const override;
//! Retrieve sub layers from a DB ogr layer \a path with the specified \a driver
static QList<QgsOgrDbLayerInfo *> subLayers( const QString &path, const QString &driver );
//! Return a LayerType from a geometry type string
static QgsLayerItem::LayerType layerTypeFromDb( const QString &geometryType );
#ifdef HAVE_GUI
QList<QAction *> actions() override;
@ -47,8 +84,21 @@ class QgsOgrDataCollectionItem : public QgsDataCollectionItem
QgsOgrDataCollectionItem( QgsDataItem *parent, QString name, QString path );
QVector<QgsDataItem *> createChildren() override;
/** Utility function to store DB connections
* \param path to the DB
* \param ogrDriverName the OGR/GDAL driver name (e.g. "GPKG")
*/
static bool storeConnection( const QString &path, const QString &ogrDriverName );
/** Utility function to create and tore a new DB connection
* \param name is the translatable name of the managed layers (e.g. "GeoPackage")
* \param extensions is a string with file extensions (e.g. "GeoPackage Database (*.gpkg *.GPKG)")
* \param ogrDriverName the OGR/GDAL driver name (e.g. "GPKG")
*/
static bool createConnection( const QString &name, const QString &extensions, const QString &ogrDriverName );
};
#endif // QGSOGRDATAITEMS_H

View File

@ -1,5 +1,5 @@
/***************************************************************************
qgsgeopackageconnection.cpp - selector for geopackage
qgsogrdbconnection.cpp
-------------------
begin : August 2017
copyright : (C) 2017 by Alessandro Pasotti
@ -18,72 +18,76 @@
#include "qgis.h"
#include "qgsdatasourceuri.h"
#include "qgssettings.h"
#include "qgsgeopackageconnection.h"
#include "qgsogrdbconnection.h"
#include "qgslogger.h"
#include <QInputDialog>
#include <QMessageBox>
const QString QgsGeoPackageConnection::SETTINGS_PREFIX = QStringLiteral( "providers/geopackage" );
QgsGeoPackageConnection::QgsGeoPackageConnection( const QString &connName )
QgsOgrDbConnection::QgsOgrDbConnection( const QString &connName, const QString &settingsKey )
: mConnName( connName )
{
mSettingsKey = settingsKey ;
QgsSettings settings;
QString key = QStringLiteral( "%1/%2/path" ).arg( connectionsPath( ), mConnName );
QString key = QStringLiteral( "%1/%2/path" ).arg( connectionsPath( settingsKey ), mConnName );
mPath = settings.value( key ).toString();
}
QgsGeoPackageConnection::~QgsGeoPackageConnection()
QgsOgrDbConnection::~QgsOgrDbConnection()
{
}
QgsDataSourceUri QgsGeoPackageConnection::uri()
QgsDataSourceUri QgsOgrDbConnection::uri()
{
QgsDataSourceUri uri;
uri.setEncodedUri( mPath );
return uri;
}
void QgsGeoPackageConnection::setPath( const QString &path )
void QgsOgrDbConnection::setPath( const QString &path )
{
mPath = path;
}
void QgsGeoPackageConnection::save( )
void QgsOgrDbConnection::save( )
{
QgsSettings settings;
settings.setValue( QStringLiteral( "%1/%2/path" ).arg( connectionsPath( ), mConnName ), mPath );
settings.setValue( QStringLiteral( "%1/%2/path" ).arg( connectionsPath( mSettingsKey ), mConnName ), mPath );
}
QString QgsGeoPackageConnection::connectionsPath()
QString QgsOgrDbConnection::fullKey( const QString &settingsKey )
{
return QStringLiteral( "%1/connections" ).arg( SETTINGS_PREFIX );
return QStringLiteral( "providers/ogr/%1" ).arg( settingsKey );
}
QStringList QgsGeoPackageConnection::connectionList()
QString QgsOgrDbConnection::connectionsPath( const QString &settingsKey )
{
return QStringLiteral( "%1/connections" ).arg( fullKey( settingsKey ) );
}
const QStringList QgsOgrDbConnection::connectionList( const QString &settingsKey )
{
QgsSettings settings;
settings.beginGroup( connectionsPath( ) );
settings.beginGroup( connectionsPath( settingsKey ) );
return settings.childGroups();
}
QString QgsGeoPackageConnection::selectedConnection()
QString QgsOgrDbConnection::selectedConnection( const QString &settingsKey )
{
QgsSettings settings;
return settings.value( QStringLiteral( "%1/selected" ).arg( SETTINGS_PREFIX ) ).toString();
return settings.value( QStringLiteral( "%1/selected" ).arg( connectionsPath( settingsKey ) ) ).toString();
}
void QgsGeoPackageConnection::setSelectedConnection( const QString &name )
void QgsOgrDbConnection::setSelectedConnection( const QString &connName, const QString &settingsKey )
{
QgsSettings settings;
settings.setValue( QStringLiteral( "%1/selected" ).arg( SETTINGS_PREFIX ), name );
settings.setValue( QStringLiteral( "%1/selected" ).arg( connectionsPath( settingsKey ) ), connName );
}
void QgsGeoPackageConnection::deleteConnection( const QString &name )
void QgsOgrDbConnection::deleteConnection( const QString &connName, const QString &settingsKey )
{
QgsSettings settings;
settings.remove( QStringLiteral( "%1/%2" ).arg( connectionsPath(), name ) );
settings.remove( QStringLiteral( "%1/%2" ).arg( connectionsPath( settingsKey ), connName ) );
}

View File

@ -1,5 +1,5 @@
/***************************************************************************
qgsgeopackageconnection.h - GeoPackage connection
qgsogrdbconnection.h - QgsOgrDbConnection
-------------------
begin : August 2017
copyright : (C) 2017 by Alessandro Pasotti
@ -15,32 +15,30 @@
* *
***************************************************************************/
#ifndef QGSGEOPACKAGECONNECTION_H
#define QGSGEOPACKAGECONNECTION_H
#ifndef QGSGOGRDBSCONNECTION_H
#define QGSGOGRDBSCONNECTION_H
#include "qgsdatasourceuri.h"
#include <QStringList>
/*!
* \brief Connections management
* \brief Generic OGR DB Connections management
*/
class QgsGeoPackageConnection : public QObject
class QgsOgrDbConnection : public QObject
{
Q_OBJECT
public:
//! Constructor
explicit QgsGeoPackageConnection( const QString &connName );
explicit QgsOgrDbConnection( const QString &connName, const QString &settingsKey );
~QgsGeoPackageConnection();
~QgsOgrDbConnection();
static QStringList connectionList();
static void deleteConnection( const QString &name );
static QString selectedConnection();
static void setSelectedConnection( const QString &name );
static const QStringList connectionList( const QString &settingsKey );
static void deleteConnection( const QString &connName, const QString &settingsKey );
static QString selectedConnection( const QString &settingsKey );
static void setSelectedConnection( const QString &connName, const QString &settingsKey );
public:
//! Return the uri
@ -54,14 +52,14 @@ class QgsGeoPackageConnection : public QObject
void setPath( const QString &path );
//! Store the connection data in the settings
void save();
const static QString SETTINGS_PREFIX;
private:
static QString connectionsPath( );
static QString fullKey( const QString &settingsKey );
static QString connectionsPath( const QString &settingsKey );
QString mConnName;
QString mPath;
QString mSettingsKey;
};
#endif // QGSGEOPACKAGECONNECTION_H
#endif // QGSGOGRDBSCONNECTION_H

View File

@ -0,0 +1,421 @@
/***************************************************************************
qgsogrdbsourceselect.cpp - QgsOgrDbSourceSelect
---------------------
begin : 5.9.2017
copyright : (C) 2017 by Alessandro Pasotti
email : apasotti at boundlessgeo dot com
based on work by : (C) 2008 by Sandro Furieri for spatialite source sel.
email : a.furieri@lqt.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 "qgsogrdbsourceselect.h"
#include "qgsogrdbconnection.h"
#include "qgsogrdataitems.h"
#include "qgsvectorlayer.h"
#include "qgsquerybuilder.h"
#include "qgssettings.h"
#include <QMessageBox>
QgsOgrDbSourceSelect::QgsOgrDbSourceSelect( const QString &theSettingsKey, const QString &theName, const QString &theExtensions, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode )
: QgsAbstractDataSourceWidget( parent, fl, theWidgetMode )
, mOgrDriverName( theSettingsKey )
, mName( theName )
, mExtension( theExtensions )
{
setupUi( this );
setupButtons( buttonBox );
connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsOgrDbSourceSelect::showHelp );
QgsSettings settings;
restoreGeometry( settings.value( QStringLiteral( "ogr/%1SourceSelect/geometry" ).arg( ogrDriverName( ) ), QgsSettings::Section::Providers ).toByteArray() );
mHoldDialogOpen->setChecked( settings.value( QStringLiteral( "ogr/%1SourceSelect/HoldDialogOpen" ).arg( ogrDriverName( ) ), false, QgsSettings::Section::Providers ).toBool() );
setWindowTitle( tr( "Add %1 Layer(s)" ).arg( name( ) ) );
btnEdit->hide(); // hide the edit button
btnSave->hide();
btnLoad->hide();
mBuildQueryButton = new QPushButton( tr( "&Set Filter" ) );
connect( mBuildQueryButton, &QAbstractButton::clicked, this, &QgsOgrDbSourceSelect::buildQuery );
mBuildQueryButton->setEnabled( false );
if ( widgetMode() != QgsProviderRegistry::WidgetMode::None )
{
mHoldDialogOpen->hide();
}
buttonBox->addButton( mBuildQueryButton, QDialogButtonBox::ActionRole );
populateConnectionList();
mSearchModeComboBox->addItem( tr( "Wildcard" ) );
mSearchModeComboBox->addItem( tr( "RegExp" ) );
mSearchColumnComboBox->addItem( tr( "All" ) );
mSearchColumnComboBox->addItem( tr( "Table" ) );
mSearchColumnComboBox->addItem( tr( "Type" ) );
mSearchColumnComboBox->addItem( tr( "Geometry column" ) );
mSearchColumnComboBox->addItem( tr( "Sql" ) );
mProxyModel.setParent( this );
mProxyModel.setFilterKeyColumn( -1 );
mProxyModel.setFilterCaseSensitivity( Qt::CaseInsensitive );
mProxyModel.setDynamicSortFilter( true );
mProxyModel.setSourceModel( &mTableModel );
mTablesTreeView->setModel( &mProxyModel );
mTablesTreeView->setSortingEnabled( true );
connect( mTablesTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsOgrDbSourceSelect::treeWidgetSelectionChanged );
//for Qt < 4.3.2, passing -1 to include all model columns
//in search does not seem to work
mSearchColumnComboBox->setCurrentIndex( 1 );
//hide the search options by default
//they will be shown when the user ticks
//the search options group box
mSearchLabel->setVisible( false );
mSearchColumnComboBox->setVisible( false );
mSearchColumnsLabel->setVisible( false );
mSearchModeComboBox->setVisible( false );
mSearchModeLabel->setVisible( false );
mSearchTableEdit->setVisible( false );
cbxAllowGeometrylessTables->setDisabled( true );
}
QgsOgrDbSourceSelect::~QgsOgrDbSourceSelect()
{
QgsSettings settings;
settings.setValue( QStringLiteral( "ogr/%1SourceSelect/geometry" ).arg( ogrDriverName( ) ), saveGeometry(), QgsSettings::Section::Providers );
settings.setValue( QStringLiteral( "ogr/%1SourceSelect/HoldDialogOpen" ).arg( ogrDriverName( ) ), mHoldDialogOpen->isChecked(), QgsSettings::Section::Providers );
}
// Remember which database is selected
void QgsOgrDbSourceSelect::on_cmbConnections_activated( int )
{
dbChanged();
}
void QgsOgrDbSourceSelect::buildQuery()
{
setSql( mTablesTreeView->currentIndex() );
}
void QgsOgrDbSourceSelect::on_cbxAllowGeometrylessTables_stateChanged( int )
{
on_btnConnect_clicked();
}
void QgsOgrDbSourceSelect::on_mTablesTreeView_clicked( const QModelIndex &index )
{
mBuildQueryButton->setEnabled( index.parent().isValid() && mTablesTreeView->currentIndex().data( Qt::UserRole + 2 ) != QStringLiteral( "Raster" ) );
}
void QgsOgrDbSourceSelect::on_mTablesTreeView_doubleClicked( const QModelIndex &index )
{
setSql( index );
}
void QgsOgrDbSourceSelect::on_mSearchGroupBox_toggled( bool checked )
{
if ( mSearchTableEdit->text().isEmpty() )
return;
on_mSearchTableEdit_textChanged( checked ? mSearchTableEdit->text() : QLatin1String( "" ) );
}
void QgsOgrDbSourceSelect::on_mSearchTableEdit_textChanged( const QString &text )
{
if ( mSearchModeComboBox->currentText() == tr( "Wildcard" ) )
{
mProxyModel._setFilterWildcard( text );
}
else if ( mSearchModeComboBox->currentText() == tr( "RegExp" ) )
{
mProxyModel._setFilterRegExp( text );
}
}
void QgsOgrDbSourceSelect::on_mSearchColumnComboBox_currentIndexChanged( const QString &text )
{
if ( text == tr( "All" ) )
{
mProxyModel.setFilterKeyColumn( -1 );
}
else if ( text == tr( "Table" ) )
{
mProxyModel.setFilterKeyColumn( 0 );
}
else if ( text == tr( "Type" ) )
{
mProxyModel.setFilterKeyColumn( 1 );
}
else if ( text == tr( "Geometry column" ) )
{
mProxyModel.setFilterKeyColumn( 2 );
}
else if ( text == tr( "Sql" ) )
{
mProxyModel.setFilterKeyColumn( 3 );
}
}
void QgsOgrDbSourceSelect::on_mSearchModeComboBox_currentIndexChanged( const QString &text )
{
Q_UNUSED( text );
on_mSearchTableEdit_textChanged( mSearchTableEdit->text() );
}
void QgsOgrDbSourceSelect::populateConnectionList()
{
cmbConnections->clear();
for ( const QString &name : QgsOgrDbConnection::connectionList( ogrDriverName( ) ) )
{
// retrieving the SQLite DB name and full path
QString text = name + tr( "@" ) + QgsOgrDbConnection( name, ogrDriverName( ) ).path();
cmbConnections->addItem( text );
}
setConnectionListPosition();
btnConnect->setDisabled( cmbConnections->count() == 0 );
btnDelete->setDisabled( cmbConnections->count() == 0 );
cmbConnections->setDisabled( cmbConnections->count() == 0 );
}
void QgsOgrDbSourceSelect::on_btnNew_clicked()
{
if ( QgsOgrDataCollectionItem::createConnection( name(), extension(), ogrDriverName() ) )
{
emit connectionsChanged();
}
}
QString QgsOgrDbSourceSelect::layerURI( const QModelIndex &index )
{
QStandardItem *item = mTableModel.itemFromIndex( index );
QString uri( item->data().toString() );
QString sql = mTableModel.itemFromIndex( index.sibling( index.row(), 3 ) )->text();
if ( ! sql.isEmpty() )
{
uri += QStringLiteral( "|subset=%1" ).arg( sql );
}
return uri;
}
// Slot for deleting an existing connection
void QgsOgrDbSourceSelect::on_btnDelete_clicked()
{
QString subKey = cmbConnections->currentText();
int idx = subKey.indexOf( '@' );
if ( idx > 0 )
subKey.truncate( idx );
QString msg = tr( "Are you sure you want to remove the %1 connection and all associated settings?" ).arg( subKey );
QMessageBox::StandardButton result =
QMessageBox::information( this, tr( "Confirm Delete" ), msg, QMessageBox::Ok | QMessageBox::Cancel );
if ( result != QMessageBox::Ok )
return;
QgsOgrDbConnection::deleteConnection( subKey, ogrDriverName() );
populateConnectionList();
emit connectionsChanged();
}
void QgsOgrDbSourceSelect::addButtonClicked()
{
typedef QPair<QString, QString> LayerInfo;
QList<LayerInfo> selectedVectors;
QList<LayerInfo> selectedRasters;
typedef QMap < int, bool >schemaInfo;
QMap < QString, schemaInfo > dbInfo;
QItemSelection selection = mTablesTreeView->selectionModel()->selection();
QModelIndexList selectedIndices = selection.indexes();
QStandardItem *currentItem = nullptr;
QModelIndexList::const_iterator selected_it = selectedIndices.constBegin();
for ( ; selected_it != selectedIndices.constEnd(); ++selected_it )
{
if ( !selected_it->parent().isValid() )
{
//top level items only contain the schema names
continue;
}
currentItem = mTableModel.itemFromIndex( mProxyModel.mapToSource( *selected_it ) );
if ( !currentItem )
{
continue;
}
QString currentSchemaName = currentItem->parent()->text();
int currentRow = currentItem->row();
if ( !dbInfo[currentSchemaName].contains( currentRow ) )
{
dbInfo[currentSchemaName][currentRow] = true;
if ( currentItem->data( Qt::UserRole + 2 ).toString().contains( QStringLiteral( "Raster" ), Qt::CaseInsensitive ) )
{
selectedRasters << LayerInfo( layerURI( mProxyModel.mapToSource( *selected_it ) ), currentItem->data( Qt::DisplayRole ).toString() );
}
else
{
selectedVectors << LayerInfo( layerURI( mProxyModel.mapToSource( *selected_it ) ), currentItem->data( Qt::DisplayRole ).toString() );
}
}
}
if ( selectedVectors.empty() && selectedRasters.empty() )
{
QMessageBox::information( this, tr( "Select Table" ), tr( "You must select a table in order to add a Layer." ) );
}
else
{
// Use OGR
for ( const LayerInfo &info : qgsAsConst( selectedVectors ) )
{
emit addVectorLayer( info.first, info.second );
}
for ( const LayerInfo &info : qgsAsConst( selectedRasters ) )
{
emit addRasterLayer( info.first, info.second, QStringLiteral( "gdal" ) );
}
if ( widgetMode() == QgsProviderRegistry::WidgetMode::None && ! mHoldDialogOpen->isChecked() )
{
accept();
}
}
}
void QgsOgrDbSourceSelect::on_btnConnect_clicked()
{
cbxAllowGeometrylessTables->setEnabled( false );
QString subKey = cmbConnections->currentText();
int idx = subKey.indexOf( '@' );
if ( idx > 0 )
subKey.truncate( idx );
QgsOgrDbConnection conn( subKey, ogrDriverName() );
mPath = conn.path();
const QList<QgsOgrDbLayerInfo *> layers = QgsOgrLayerItem::subLayers( mPath, ogrDriverName() );
QModelIndex rootItemIndex = mTableModel.indexFromItem( mTableModel.invisibleRootItem() );
mTableModel.removeRows( 0, mTableModel.rowCount( rootItemIndex ), rootItemIndex );
// populate the table list
// get the list of suitable tables and columns and populate the UI
mTableModel.setPath( mPath );
for ( const QgsOgrDbLayerInfo *table : layers )
{
if ( cbxAllowGeometrylessTables->isChecked() || table->geometryType() != QStringLiteral( "None" ) )
{
mTableModel.addTableEntry( table->layerType(), table->name(), table->uri(), table->geometryColumn(), table->geometryType(), QLatin1String( "" ) );
}
}
mTablesTreeView->sortByColumn( 0, Qt::AscendingOrder );
//expand all the toplevel items
int numTopLevelItems = mTableModel.invisibleRootItem()->rowCount();
for ( int i = 0; i < numTopLevelItems; ++i )
{
mTablesTreeView->expand( mProxyModel.mapFromSource( mTableModel.indexFromItem( mTableModel.invisibleRootItem()->child( i ) ) ) );
}
mTablesTreeView->resizeColumnToContents( 0 );
mTablesTreeView->resizeColumnToContents( 1 );
cbxAllowGeometrylessTables->setEnabled( true );
// Store selected connection
QgsOgrDbConnection::setSelectedConnection( subKey, ogrDriverName() );
qDeleteAll( layers );
}
void QgsOgrDbSourceSelect::setSql( const QModelIndex &index )
{
QModelIndex idx = mProxyModel.mapToSource( index );
QString tableName = mTableModel.itemFromIndex( idx.sibling( idx.row(), 0 ) )->text();
std::unique_ptr<QgsVectorLayer> vlayer( new QgsVectorLayer( layerURI( idx ), tableName, QStringLiteral( "ogr" ) ) );
if ( !vlayer->isValid() )
{
return;
}
// create a query builder object
std::unique_ptr<QgsQueryBuilder> gb( new QgsQueryBuilder( vlayer.get(), this ) );
if ( gb->exec() )
{
mTableModel.setSql( mProxyModel.mapToSource( index ), gb->sql() );
}
}
void QgsOgrDbSourceSelect::dbChanged()
{
// Remember which database was selected.
QgsSettings settings;
settings.setValue( QStringLiteral( "GeoPackage/connections/selected" ), cmbConnections->currentText() );
}
void QgsOgrDbSourceSelect::refresh()
{
populateConnectionList();
}
void QgsOgrDbSourceSelect::setConnectionListPosition()
{
QString toSelect = QgsOgrDbConnection::selectedConnection( ogrDriverName() );
toSelect += '@' + QgsOgrDbConnection( toSelect, ogrDriverName() ).path();
cmbConnections->setCurrentIndex( cmbConnections->findText( toSelect ) );
if ( cmbConnections->currentIndex() < 0 )
{
if ( toSelect.isNull() )
cmbConnections->setCurrentIndex( 0 );
else
cmbConnections->setCurrentIndex( cmbConnections->count() - 1 );
}
}
void QgsOgrDbSourceSelect::setSearchExpression( const QString &regexp )
{
Q_UNUSED( regexp );
}
void QgsOgrDbSourceSelect::treeWidgetSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
{
Q_UNUSED( deselected )
emit enableButtons( !selected.isEmpty() );
}
void QgsOgrDbSourceSelect::showHelp()
{
QgsHelp::openHelp( QStringLiteral( "managing_data_source/opening_data.html#GeoPackage-layers" ) );
}

View File

@ -0,0 +1,110 @@
/***************************************************************************
qgsogrdbsourceselect.h - QgsOgrDbSourceSelect
---------------------
begin : 5.9.2017
copyright : (C) 2017 by Alessandro Pasotti
based on work by : (C) 2008 by Sandro Furieri for spatialite source sel.
email : a.furieri@lqt.it
email : apasotti at boundlessgeo 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 QGSGOGRDBSOURCESELECT_H
#define QGSGOGRDBSOURCESELECT_H
#include "ui_qgsdbsourceselectbase.h"
#include "qgsguiutils.h"
#include "qgsdbfilterproxymodel.h"
#include "qgshelp.h"
#include "qgsabstractdatasourcewidget.h"
#include "qgsogrdbtablemodel.h"
/**
* @brief The QgsOgrDbSourceSelect class is a generic class for DB based OGR
* source selects.
*
*/
class QgsOgrDbSourceSelect: public QgsAbstractDataSourceWidget, private Ui::QgsDbSourceSelectBase
{
Q_OBJECT
public:
/** Construct a DB Source Select with \a theOgrDriverName specified (i.e. "GPKG", "SQLite" etc.)
* and \a theName as string for describing the layers managed by the source select (e.g. : "GeoPackage" etc.)
* The \a extensions is a string dscribing the accepted file extensions (e.g. : "GeoPackage Database (*.gpkg *.GPKG)")
*/
QgsOgrDbSourceSelect( const QString &theOgrDriverName, const QString &theName, const QString &theExtensions, QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode theWidgetMode = QgsProviderRegistry::WidgetMode::None );
~QgsOgrDbSourceSelect();
QString layerURI( const QModelIndex &index );
//! Populate the connection list combo box
void populateConnectionList();
// Store the selected database
void dbChanged();
//! Returns the QSettings key name
const QString ogrDriverName( ) { return mOgrDriverName; }
//! Returns the name of the managed layers, needs to be translatable
const QString name( ) { return mName; }
//! Returns the extensions of the managed layers, needs to be translatable
const QString extension() { return mExtension; }
//! Open file selector to add new connection
static bool newConnection( QWidget *parent );
public slots:
//! Triggered when the provider's connections need to be refreshed
void refresh() override;
void addButtonClicked() override;
/** Connects to the database using the stored connection parameters.
* Once connected, available layers are displayed.
*/
void on_btnConnect_clicked();
void buildQuery();
//! Opens the create connection dialog to build a new connection
void on_btnNew_clicked();
//! Deletes the selected connection
void on_btnDelete_clicked();
void on_mSearchGroupBox_toggled( bool );
void on_mSearchTableEdit_textChanged( const QString &text );
void on_mSearchColumnComboBox_currentIndexChanged( const QString &text );
void on_mSearchModeComboBox_currentIndexChanged( const QString &text );
void on_cbxAllowGeometrylessTables_stateChanged( int );
void setSql( const QModelIndex &index );
void on_cmbConnections_activated( int );
void on_mTablesTreeView_clicked( const QModelIndex &index );
void on_mTablesTreeView_doubleClicked( const QModelIndex &index );
void treeWidgetSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected );
//!Sets a new regular expression to the model
void setSearchExpression( const QString &regexp );
void showHelp();
private:
void setConnectionListPosition();
//! Model that acts as datasource for mTableTreeWidget
QgsOgrDbTableModel mTableModel;
QgsDatabaseFilterProxyModel mProxyModel;
QPushButton *mBuildQueryButton = nullptr;
QString mPath;
QString mOgrDriverName;
QString mName;
QString mExtension;
};
#endif // QGSGOGRDBSOURCESELECT_H

View File

@ -0,0 +1,93 @@
/***************************************************************************
qgsogrdbtablemodel.cpp - QgsOgrDbTableModel
---------------------
begin : 5.9.2017
copyright : (C) 2017 by Alessandro Pasotti
email : apasotti at boundlessgeo 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 "qgsogrdbtablemodel.h"
#include "qgsdataitem.h"
#include <QIcon>
QgsOgrDbTableModel::QgsOgrDbTableModel()
{
QStringList headerLabels;
headerLabels << tr( "Table" );
headerLabels << tr( "Type" );
headerLabels << tr( "Geometry column" );
headerLabels << tr( "Sql" );
setHorizontalHeaderLabels( headerLabels );
}
void QgsOgrDbTableModel::addTableEntry( const QgsLayerItem::LayerType &layerType, const QString &tableName, const QString &uri, const QString &geometryColName, const QString &geometryType, const QString &sql )
{
//is there already a root item ?
QStandardItem *dbItem = nullptr;
QList < QStandardItem * >dbItems = findItems( mPath, Qt::MatchExactly, 0 );
//there is already an item
if ( !dbItems.isEmpty() )
{
dbItem = dbItems.at( 0 );
}
else //create a new toplevel item
{
dbItem = new QStandardItem( mPath );
dbItem->setFlags( Qt::ItemIsEnabled );
invisibleRootItem()->setChild( invisibleRootItem()->rowCount(), dbItem );
}
QList < QStandardItem * >childItemList;
QStandardItem *typeItem = new QStandardItem( QgsApplication::getThemeIcon( QgsLayerItem::iconName( layerType ) ), geometryType );
typeItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
QStandardItem *tableItem = new QStandardItem( tableName );
tableItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
QStandardItem *geomItem = new QStandardItem( geometryColName );
geomItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
QStandardItem *sqlItem = new QStandardItem( sql );
sqlItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable );
// Add uri and geometryType information
tableItem->setData( uri, Qt::UserRole + 1 );
tableItem->setData( geometryType, Qt::UserRole + 2 );
childItemList.push_back( tableItem );
childItemList.push_back( typeItem );
childItemList.push_back( geomItem );
childItemList.push_back( sqlItem );
dbItem->appendRow( childItemList );
++mTableCount;
}
void QgsOgrDbTableModel::setSql( const QModelIndex &index, const QString &sql )
{
if ( !index.isValid() || !index.parent().isValid() )
{
return;
}
//find out table name
QModelIndex tableSibling = index.sibling( index.row(), 0 );
QModelIndex geomSibling = index.sibling( index.row(), 2 );
if ( !tableSibling.isValid() || !geomSibling.isValid() )
{
return;
}
QModelIndex sqlIndex = index.sibling( index.row(), 3 );
if ( sqlIndex.isValid() )
{
itemFromIndex( sqlIndex )->setText( sql );
}
}

View File

@ -0,0 +1,65 @@
/***************************************************************************
qgsogrdbtablemodel.h - QgsOgrDbTableModel
---------------------
begin : 5.9.2017
copyright : (C) 2017 by Alessandro Pasotti
email : apasotti at boundlessgeo 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 QGSOGRDBTABLEMODEL_H
#define QGSOGRDBTABLEMODEL_H
#include "qgis.h"
#include "qgsdataitem.h"
#include <QObject>
#include <QStandardItemModel>
#include <type_traits>
class QgsOgrDbTableModel : public QStandardItemModel
{
Q_OBJECT
public:
QgsOgrDbTableModel();
//! Set the geometry type for the table
void setGeometryTypesForTable( const QString &table, const QString &attribute, const QString &type );
//! Adds entry for one database table to the model
void addTableEntry( const QgsLayerItem::LayerType &layerType, const QString &tableName, const QString &uri, const QString &geometryColName, const QString &geometryType, const QString &sql );
//! Sets an sql statement that belongs to a cell specified by a model index
void setSql( const QModelIndex &index, const QString &sql );
//! Returns the number of tables in the model
int tableCount() const
{
return mTableCount;
}
//! Sets the DB full path
void setPath( const QString &path )
{
mPath = path;
}
private:
//! Number of tables in the model
int mTableCount;
QString mPath;
QIcon iconForType( QgsWkbTypes::Type type ) const;
QString displayStringForType( QgsWkbTypes::Type type ) const;
//! Returns qgis wkbtype from database typename
QgsWkbTypes::Type qgisTypeFromDbType( const QString &dbType ) const;
};
#endif // QGSOGRDBTABLEMODEL_H

View File

@ -39,6 +39,7 @@ email : sherman at mrcc.com
#ifdef HAVE_GUI
#include "qgssourceselectprovider.h"
#include "qgsogrsourceselect.h"
#include "qgsogrdbsourceselect.h"
#endif
#include "qgis.h"
@ -697,7 +698,6 @@ static OGRwkbGeometryType ogrWkbGeometryTypeFromName( const QString &typeName )
QStringList QgsOgrProvider::subLayers() const
{
QgsDebugMsg( "Entered." );
if ( !mValid )
{
return QStringList();
@ -710,6 +710,14 @@ QStringList QgsOgrProvider::subLayers() const
{
OGRLayerH layer = OGR_DS_GetLayer( ogrDataSource, i );
OGRFeatureDefnH fdef = OGR_L_GetLayerDefn( layer );
// Get first column name,
// TODO: add support for multiple
QString geometryColumnName;
OGRGeomFieldDefnH geomH = OGR_FD_GetGeomFieldDefn( fdef, 0 );
if ( geomH )
{
geometryColumnName = QString::fromUtf8( OGR_GFld_GetNameRef( geomH ) );
}
QString layerName = QString::fromUtf8( OGR_FD_GetName( fdef ) );
OGRwkbGeometryType layerGeomType = OGR_FD_GetGeomType( fdef );
@ -738,7 +746,7 @@ QStringList QgsOgrProvider::subLayers() const
QString geom = ogrWkbGeometryTypeName( layerGeomType );
mSubLayerList << QStringLiteral( "%1:%2:%3:%4" ).arg( i ).arg( layerName, layerFeatureCount == -1 ? tr( "Unknown" ) : QString::number( layerFeatureCount ), geom );
mSubLayerList << QStringLiteral( "%1:%2:%3:%4:%5" ).arg( i ).arg( layerName, layerFeatureCount == -1 ? tr( "Unknown" ) : QString::number( layerFeatureCount ), geom, geometryColumnName );
}
else
{
@ -792,13 +800,12 @@ QStringList QgsOgrProvider::subLayers() const
{
QString geom = ogrWkbGeometryTypeName( ( bIs25D ) ? wkbSetZ( countIt.key() ) : countIt.key() );
QString sl = QStringLiteral( "%1:%2:%3:%4" ).arg( i ).arg( layerName ).arg( fCount.value( countIt.key() ) ).arg( geom );
QString sl = QStringLiteral( "%1:%2:%3:%4:%5" ).arg( i ).arg( layerName ).arg( fCount.value( countIt.key() ) ).arg( geom, geometryColumnName );
QgsDebugMsg( "sub layer: " + sl );
mSubLayerList << sl;
}
}
}
return mSubLayerList;
}
@ -4393,12 +4400,48 @@ class QgsOgrVectorSourceSelectProvider : public QgsSourceSelectProvider
};
//! Provider for GPKG vector source select
class QgsGeoPackageSourceSelectProvider : public QgsSourceSelectProvider
{
public:
virtual QString providerKey() const override { return QStringLiteral( "ogr" ); }
virtual QString text() const override { return QObject::tr( "GeoPackage" ); }
virtual int ordering() const override { return 45; }
virtual QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddGeoPackageLayer.svg" ) ); }
virtual QgsAbstractDataSourceWidget *createDataSourceWidget( QWidget *parent = nullptr, Qt::WindowFlags fl = Qt::Widget, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::Embedded ) const override
{
return new QgsOgrDbSourceSelect( QStringLiteral( "GPKG" ), QObject::tr( "GeoPackage" ), QObject::tr( "GeoPackage Database (*.gpkg)" ), parent, fl, widgetMode );
}
};
/* This has been tested and works just fine:
//! Provider for SQLite vector source select
class QgsSpatiaLiteSourceSelectProvider : public QgsSourceSelectProvider
{
public:
virtual QString providerKey() const override { return QStringLiteral( "ogr" ); }
virtual QString text() const override { return QObject::tr( "SQLite" ); }
virtual int ordering() const override { return 46; }
virtual QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/mIconSpatialite.svg" ) ); }
virtual QgsAbstractDataSourceWidget *createDataSourceWidget( QWidget *parent = nullptr, Qt::WindowFlags fl = Qt::Widget, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::Embedded ) const override
{
return new QgsOgrDbSourceSelect( QStringLiteral( "SQLite" ), QObject::tr( "SQLite" ), QObject::tr( "SpatiaLite Database (*.db *.sqlite)" ), parent, fl, widgetMode );
}
};
//*/
QGISEXTERN QList<QgsSourceSelectProvider *> *sourceSelectProviders()
{
QList<QgsSourceSelectProvider *> *providers = new QList<QgsSourceSelectProvider *>();
*providers
<< new QgsOgrVectorSourceSelectProvider;
<< new QgsOgrVectorSourceSelectProvider
<< new QgsGeoPackageSourceSelectProvider;
// << new QgsSpatiaLiteSourceSelectProvider;
return providers;
}

View File

@ -112,7 +112,7 @@ class PyQgsOGRProvider(unittest.TestCase):
vl = QgsVectorLayer('{}|layerid=0'.format(datasource), 'test', 'ogr')
self.assertTrue(vl.isValid())
self.assertEqual(len(vl.dataProvider().subLayers()), 1)
self.assertEqual(vl.dataProvider().subLayers()[0], '0:testMixOfPolygonCurvePolygon:4:CurvePolygon')
self.assertEqual(vl.dataProvider().subLayers()[0], '0:testMixOfPolygonCurvePolygon:4:CurvePolygon:')
def testMixOfLineStringCompoundCurve(self):
@ -128,7 +128,7 @@ class PyQgsOGRProvider(unittest.TestCase):
vl = QgsVectorLayer('{}|layerid=0'.format(datasource), 'test', 'ogr')
self.assertTrue(vl.isValid())
self.assertEqual(len(vl.dataProvider().subLayers()), 1)
self.assertEqual(vl.dataProvider().subLayers()[0], '0:testMixOfLineStringCompoundCurve:5:CompoundCurve')
self.assertEqual(vl.dataProvider().subLayers()[0], '0:testMixOfLineStringCompoundCurve:5:CompoundCurve:')
def testGpxElevation(self):
# GPX without elevation data

View File

@ -84,7 +84,7 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
ds = None
vl = QgsVectorLayer('{}'.format(tmpfile), 'test', 'ogr')
self.assertEqual(vl.dataProvider().subLayers(), ['0:test:0:CurvePolygon'])
self.assertEqual(vl.dataProvider().subLayers(), ['0:test:0:CurvePolygon:geom'])
f = QgsFeature()
f.setGeometry(QgsGeometry.fromWkt('POLYGON ((0 0,0 1,1 1,0 0))'))
vl.dataProvider().addFeatures([f])