From a866d4917cb130dc47340632111fe8848c47eb5d Mon Sep 17 00:00:00 2001 From: Giovanni Manghi Date: Thu, 8 Nov 2018 17:35:36 +0000 Subject: [PATCH 001/266] add GRASS r.mapcalculator, remove r.mapcalc --- .../algs/grass7/description/r.mapcalc.txt | 9 --- .../processing/algs/grass7/ext/r_mapcalc.py | 65 ------------------- 2 files changed, 74 deletions(-) delete mode 100644 python/plugins/processing/algs/grass7/description/r.mapcalc.txt delete mode 100644 python/plugins/processing/algs/grass7/ext/r_mapcalc.py diff --git a/python/plugins/processing/algs/grass7/description/r.mapcalc.txt b/python/plugins/processing/algs/grass7/description/r.mapcalc.txt deleted file mode 100644 index 06751ab650f6..000000000000 --- a/python/plugins/processing/algs/grass7/description/r.mapcalc.txt +++ /dev/null @@ -1,9 +0,0 @@ -r.mapcalc -Raster map calculator. -Raster (r.*) -QgsProcessingParameterMultipleLayers|maps|Raster maps used in the calculator|3|None|True -QgsProcessingParameterString|expression|Expression to evaluate. Syntax e.g. `raster_out=raster1+raster2`. Use original filenames, without extension|None|True|True -QgsProcessingParameterFile|file|File containing expression(s) to evaluate (same rule for raster names than above)|QgsProcessingParameterFile.File|txt|None|True -QgsProcessingParameterString|seed|Integer seed for rand() function|None|False|True -*QgsProcessingParameterBoolean|-s|Generate random seed (result is non-deterministic)|False -QgsProcessingParameterFolderDestination|output_dir|Results Directory diff --git a/python/plugins/processing/algs/grass7/ext/r_mapcalc.py b/python/plugins/processing/algs/grass7/ext/r_mapcalc.py deleted file mode 100644 index 9df6f4edd690..000000000000 --- a/python/plugins/processing/algs/grass7/ext/r_mapcalc.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - r_mapcalc.py - ------------ - Date : February 2016 - Copyright : (C) 2016 by Médéric Ribreux - Email : medspx at medspx dot fr -*************************************************************************** -* * -* 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__ = 'Médéric Ribreux' -__date__ = 'February 2016' -__copyright__ = '(C) 2016, Médéric Ribreux' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -import os - - -def checkParameterValuesBeforeExecuting(alg, parameters, context): - """ Verify if we have the right parameters """ - if (alg.parameterAsString(parameters, 'expression', context) - and alg.parameterAsString(parameters, 'file', context)): - return False, alg.tr("You need to set either inline expression or a rules file!") - - return True, None - - -def processInputs(alg, parameters, context, feedback): - # We will use the same raster names than in QGIS to name the rasters in GRASS - rasters = alg.parameterAsLayerList(parameters, 'maps', context) - for idx, raster in enumerate(rasters): - rasterName = os.path.splitext( - os.path.basename(raster.source()))[0] - alg.inputLayers.append(raster) - alg.setSessionProjectionFromLayer(raster) - command = 'r.in.gdal input="{0}" output="{1}" --overwrite -o'.format( - os.path.normpath(raster.source()), - rasterName) - alg.commands.append(command) - - alg.removeParameter('maps') - alg.postInputs() - - -def processCommand(alg, parameters, context, feedback): - alg.processCommand(parameters, context, feedback, True) - - -def processOutputs(alg, parameters, context, feedback): - # We need to export every raster from the GRASSDB - alg.exportRasterLayersIntoDirectory('output_dir', - parameters, context, - wholeDB=True) From d2865983a3a3f9939ea0584a2c9d77fd9e1919e7 Mon Sep 17 00:00:00 2001 From: Giovanni Manghi Date: Thu, 8 Nov 2018 17:40:38 +0000 Subject: [PATCH 002/266] add missing file --- .../algs/grass7/description/r.mapcalculator.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 python/plugins/processing/algs/grass7/description/r.mapcalculator.txt diff --git a/python/plugins/processing/algs/grass7/description/r.mapcalculator.txt b/python/plugins/processing/algs/grass7/description/r.mapcalculator.txt new file mode 100644 index 000000000000..30144b2d743c --- /dev/null +++ b/python/plugins/processing/algs/grass7/description/r.mapcalculator.txt @@ -0,0 +1,11 @@ +r.mapcalculator +Calculate new raster map from a r.mapcalc expression. +Raster (r.) +ParameterRaster|a|Raster layer A|False +ParameterRaster|b|Raster layer B|True +ParameterRaster|c|Raster layer C|True +ParameterRaster|d|Raster layer D|True +ParameterRaster|e|Raster layer E|True +ParameterRaster|f|Raster layer F|True +ParameterString|expression|Formula|A*2 +OutputRaster|output|Calculated From fa062461b6d1da7227504f99b83b430a42e04268 Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 15 Nov 2018 09:10:00 +0100 Subject: [PATCH 003/266] pass items to delete layer activity --- python/core/auto_generated/qgsdataitem.sip.in | 3 +++ src/core/qgsdataitem.h | 4 +++ src/gui/qgsbrowserdockwidget.cpp | 1 + src/providers/ogr/qgsgeopackagedataitems.cpp | 27 ++++++++++++++----- src/providers/ogr/qgsgeopackagedataitems.h | 5 +++- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/python/core/auto_generated/qgsdataitem.sip.in b/python/core/auto_generated/qgsdataitem.sip.in index ca56eadf8fae..a7bdaf43cb76 100644 --- a/python/core/auto_generated/qgsdataitem.sip.in +++ b/python/core/auto_generated/qgsdataitem.sip.in @@ -314,6 +314,9 @@ Sets a custom sorting ``key`` for the item. Move object and all its descendants to thread %End + void setSelectedItems( const QList &selectedItems ); + QList selectedItems() const; + protected: virtual void populate( const QVector &children ); diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 4cd0872508c3..37c2f8f87a30 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -323,6 +323,9 @@ class CORE_EXPORT QgsDataItem : public QObject //! Move object and all its descendants to thread void moveToThread( QThread *targetThread ); + void setSelectedItems( const QList &selectedItems ) { mSelectedItems = selectedItems; } + QList selectedItems() const { return mSelectedItems; } + protected: virtual void populate( const QVector &children ); @@ -418,6 +421,7 @@ class CORE_EXPORT QgsDataItem : public QObject QFutureWatcher< QVector > *mFutureWatcher; // number of items currently in loading (populating) state static QgsAnimatedIcon *sPopulatingIcon; + QList mSelectedItems; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsDataItem::Capabilities ) diff --git a/src/gui/qgsbrowserdockwidget.cpp b/src/gui/qgsbrowserdockwidget.cpp index 6691878cb52a..c32dc8177041 100644 --- a/src/gui/qgsbrowserdockwidget.cpp +++ b/src/gui/qgsbrowserdockwidget.cpp @@ -211,6 +211,7 @@ void QgsBrowserDockWidget::showContextMenu( QPoint pt ) QMenu *menu = new QMenu( this ); const QList menus = item->menus( menu ); + item->setSelectedItems( selectedItems ); QList actions = item->actions( menu ); if ( !menus.isEmpty() ) diff --git a/src/providers/ogr/qgsgeopackagedataitems.cpp b/src/providers/ogr/qgsgeopackagedataitems.cpp index 52cb4969951f..94be51fa2d19 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.cpp +++ b/src/providers/ogr/qgsgeopackagedataitems.cpp @@ -77,9 +77,21 @@ QVector QgsGeoPackageRootItem::createChildren() #ifdef HAVE_GUI QList QgsGeoPackageAbstractLayerItem::actions( QWidget * ) { + //dave: decide if more than one layer is selected. if more then "Delete selected Layers" QList lst; - QAction *actionDeleteLayer = new QAction( tr( "Delete Layer '%1'…" ).arg( mName ), this ); - connect( actionDeleteLayer, &QAction::triggered, this, &QgsGeoPackageAbstractLayerItem::deleteLayer ); + + const QString deleteText = selectedItems().count() == 1 ? tr( "Delete Layer '%1'…" ).arg( mName ) + : tr( "Delete Selected Layers" ); + QAction *actionDeleteLayer = new QAction( deleteText, this ); + connect( actionDeleteLayer, &QAction::triggered, this, [ = ] + { + QList items = selectedItems(); + for ( const QgsDataItem *item : items ) + { + if ( const QgsGeoPackageAbstractLayerItem *gpkgAbstractItem = qobject_cast( item ) ) + deleteLayer( gpkgAbstractItem ); + } + } ) ; lst.append( actionDeleteLayer ); return lst; } @@ -487,14 +499,15 @@ void QgsGeoPackageCollectionItem::vacuumGeoPackageDbAction() } } -void QgsGeoPackageAbstractLayerItem::deleteLayer() +void QgsGeoPackageAbstractLayerItem::deleteLayer( const QgsGeoPackageAbstractLayerItem *item ) { + //dave: here we are - we need to find out what other layers in the gpkg are selected and delete em // Check if the layer(s) are in the registry QList layersList; const auto mapLayers( QgsProject::instance()->mapLayers() ); for ( QgsMapLayer *layer : mapLayers ) { - if ( layer->publicSource() == mUri ) + if ( layer->publicSource() == item->uri() ) { layersList << layer; } @@ -503,13 +516,13 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer() if ( ! layersList.isEmpty( ) ) { if ( QMessageBox::question( nullptr, QObject::tr( "Delete Layer" ), QObject::tr( "The layer %1 exists in the current project %2," - " do you want to remove it from the project and delete it?" ).arg( mName, layersList.at( 0 )->name() ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) + " do you want to remove it from the project and delete it?" ).arg( item->name(), layersList.at( 0 )->name() ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) { return; } } else if ( QMessageBox::question( nullptr, QObject::tr( "Delete Layer" ), - QObject::tr( "Are you sure you want to delete layer %1 from GeoPackage?" ).arg( mName ), + QObject::tr( "Are you sure you want to delete layer %1 from GeoPackage?" ).arg( item->name() ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) { return; @@ -528,7 +541,7 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer() } else { - QMessageBox::information( nullptr, tr( "Delete Layer" ), tr( "Layer %1 deleted successfully." ).arg( mName ) ); + QMessageBox::information( nullptr, tr( "Delete Layer" ), tr( "Layer %1 deleted successfully." ).arg( item->name() ) ); if ( mParent ) mParent->refreshConnections(); } diff --git a/src/providers/ogr/qgsgeopackagedataitems.h b/src/providers/ogr/qgsgeopackagedataitems.h index 76c25d43f4c0..a2375cf1bc41 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.h +++ b/src/providers/ogr/qgsgeopackagedataitems.h @@ -28,6 +28,9 @@ class QgsGeoPackageAbstractLayerItem : public QgsLayerItem { Q_OBJECT + //! Returns layer uri or empty string if layer cannot be created + QString uri() const { return mUri; } + protected: QgsGeoPackageAbstractLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType, const QString &providerKey ); @@ -39,7 +42,7 @@ class QgsGeoPackageAbstractLayerItem : public QgsLayerItem #ifdef HAVE_GUI QList actions( QWidget *menu ) override; public slots: - virtual void deleteLayer(); + virtual void deleteLayer( const QgsGeoPackageAbstractLayerItem *item ); #endif }; From be0ce0ca4ce345f413f520a6cedb11db3f0d4ecd Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 15 Nov 2018 09:30:34 +0100 Subject: [PATCH 004/266] do not pass item as argument --- src/providers/ogr/qgsgeopackagedataitems.cpp | 16 ++++++++-------- src/providers/ogr/qgsgeopackagedataitems.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/providers/ogr/qgsgeopackagedataitems.cpp b/src/providers/ogr/qgsgeopackagedataitems.cpp index 94be51fa2d19..b990200b2ab8 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.cpp +++ b/src/providers/ogr/qgsgeopackagedataitems.cpp @@ -86,10 +86,10 @@ QList QgsGeoPackageAbstractLayerItem::actions( QWidget * ) connect( actionDeleteLayer, &QAction::triggered, this, [ = ] { QList items = selectedItems(); - for ( const QgsDataItem *item : items ) + for ( QgsDataItem *item : items ) { - if ( const QgsGeoPackageAbstractLayerItem *gpkgAbstractItem = qobject_cast( item ) ) - deleteLayer( gpkgAbstractItem ); + if ( QgsGeoPackageAbstractLayerItem *gpkgAbstractItem = qobject_cast< QgsGeoPackageAbstractLayerItem *>( item ) ) + gpkgAbstractItem->deleteLayer(); } } ) ; lst.append( actionDeleteLayer ); @@ -499,7 +499,7 @@ void QgsGeoPackageCollectionItem::vacuumGeoPackageDbAction() } } -void QgsGeoPackageAbstractLayerItem::deleteLayer( const QgsGeoPackageAbstractLayerItem *item ) +void QgsGeoPackageAbstractLayerItem::deleteLayer() { //dave: here we are - we need to find out what other layers in the gpkg are selected and delete em // Check if the layer(s) are in the registry @@ -507,7 +507,7 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer( const QgsGeoPackageAbstractLay const auto mapLayers( QgsProject::instance()->mapLayers() ); for ( QgsMapLayer *layer : mapLayers ) { - if ( layer->publicSource() == item->uri() ) + if ( layer->publicSource() == mUri ) { layersList << layer; } @@ -516,13 +516,13 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer( const QgsGeoPackageAbstractLay if ( ! layersList.isEmpty( ) ) { if ( QMessageBox::question( nullptr, QObject::tr( "Delete Layer" ), QObject::tr( "The layer %1 exists in the current project %2," - " do you want to remove it from the project and delete it?" ).arg( item->name(), layersList.at( 0 )->name() ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) + " do you want to remove it from the project and delete it?" ).arg( mName, layersList.at( 0 )->name() ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) { return; } } else if ( QMessageBox::question( nullptr, QObject::tr( "Delete Layer" ), - QObject::tr( "Are you sure you want to delete layer %1 from GeoPackage?" ).arg( item->name() ), + QObject::tr( "Are you sure you want to delete layer %1 from GeoPackage?" ).arg( mName ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) { return; @@ -541,7 +541,7 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer( const QgsGeoPackageAbstractLay } else { - QMessageBox::information( nullptr, tr( "Delete Layer" ), tr( "Layer %1 deleted successfully." ).arg( item->name() ) ); + QMessageBox::information( nullptr, tr( "Delete Layer" ), tr( "Layer %1 deleted successfully." ).arg( mName ) ); if ( mParent ) mParent->refreshConnections(); } diff --git a/src/providers/ogr/qgsgeopackagedataitems.h b/src/providers/ogr/qgsgeopackagedataitems.h index a2375cf1bc41..6b34f98ac631 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.h +++ b/src/providers/ogr/qgsgeopackagedataitems.h @@ -42,7 +42,7 @@ class QgsGeoPackageAbstractLayerItem : public QgsLayerItem #ifdef HAVE_GUI QList actions( QWidget *menu ) override; public slots: - virtual void deleteLayer( const QgsGeoPackageAbstractLayerItem *item ); + virtual void deleteLayer(); #endif }; From 7acd83fd09f1b526cfd4a993c430d8187fb6e245 Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 15 Nov 2018 10:23:50 +0100 Subject: [PATCH 005/266] delete selected layers on spatialite and pg --- src/providers/postgres/qgspostgresdataitems.cpp | 14 ++++++++++++-- .../spatialite/qgsspatialitedataitems.cpp | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/providers/postgres/qgspostgresdataitems.cpp b/src/providers/postgres/qgspostgresdataitems.cpp index b1848c4d5edf..cd181e49f494 100644 --- a/src/providers/postgres/qgspostgresdataitems.cpp +++ b/src/providers/postgres/qgspostgresdataitems.cpp @@ -317,8 +317,18 @@ QList QgsPGLayerItem::actions( QWidget *parent ) connect( actionRenameLayer, &QAction::triggered, this, &QgsPGLayerItem::renameLayer ); lst.append( actionRenameLayer ); - QAction *actionDeleteLayer = new QAction( tr( "Delete %1" ).arg( typeName ), parent ); - connect( actionDeleteLayer, &QAction::triggered, this, &QgsPGLayerItem::deleteLayer ); + const QString deleteText = selectedItems().count() == 1 ? tr( "Delete %1" ).arg( typeName ) + : tr( "Delete Selected Tables" ); + QAction *actionDeleteLayer = new QAction( deleteText, parent ); + connect( actionDeleteLayer, &QAction::triggered, this, [ = ] + { + QList items = selectedItems(); + for ( QgsDataItem *item : items ) + { + if ( QgsPGLayerItem *pgLayerItem = qobject_cast< QgsPGLayerItem *>( item ) ) + pgLayerItem->deleteLayer(); + } + } ) ; lst.append( actionDeleteLayer ); if ( !mLayerProperty.isView ) diff --git a/src/providers/spatialite/qgsspatialitedataitems.cpp b/src/providers/spatialite/qgsspatialitedataitems.cpp index 18d333d58f64..2209548f5bae 100644 --- a/src/providers/spatialite/qgsspatialitedataitems.cpp +++ b/src/providers/spatialite/qgsspatialitedataitems.cpp @@ -45,8 +45,19 @@ QList QgsSLLayerItem::actions( QWidget *parent ) { QList lst; - QAction *actionDeleteLayer = new QAction( tr( "Delete Layer" ), parent ); - connect( actionDeleteLayer, &QAction::triggered, this, &QgsSLLayerItem::deleteLayer ); + const QString deleteText = selectedItems().count() == 1 ? tr( "Delete Layer '%1'…" ).arg( mName ) + : tr( "Delete Selected Layers" ); + QAction *actionDeleteLayer = new QAction( deleteText, parent ); + + connect( actionDeleteLayer, &QAction::triggered, this, [ = ] + { + QList items = selectedItems(); + for ( QgsDataItem *item : items ) + { + if ( QgsSLLayerItem *slLayerItem = qobject_cast< QgsSLLayerItem *>( item ) ) + slLayerItem->deleteLayer(); + } + } ) ; lst.append( actionDeleteLayer ); return lst; From 9dd96dab34ac9ccbf2f6c8033462a6bbea8b4d8e Mon Sep 17 00:00:00 2001 From: signedav Date: Mon, 19 Nov 2018 09:42:33 +0100 Subject: [PATCH 006/266] remove unused comments --- src/providers/ogr/qgsgeopackagedataitems.cpp | 2 -- src/providers/ogr/qgsgeopackagedataitems.h | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/providers/ogr/qgsgeopackagedataitems.cpp b/src/providers/ogr/qgsgeopackagedataitems.cpp index b990200b2ab8..07664162666b 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.cpp +++ b/src/providers/ogr/qgsgeopackagedataitems.cpp @@ -77,7 +77,6 @@ QVector QgsGeoPackageRootItem::createChildren() #ifdef HAVE_GUI QList QgsGeoPackageAbstractLayerItem::actions( QWidget * ) { - //dave: decide if more than one layer is selected. if more then "Delete selected Layers" QList lst; const QString deleteText = selectedItems().count() == 1 ? tr( "Delete Layer '%1'…" ).arg( mName ) @@ -501,7 +500,6 @@ void QgsGeoPackageCollectionItem::vacuumGeoPackageDbAction() void QgsGeoPackageAbstractLayerItem::deleteLayer() { - //dave: here we are - we need to find out what other layers in the gpkg are selected and delete em // Check if the layer(s) are in the registry QList layersList; const auto mapLayers( QgsProject::instance()->mapLayers() ); diff --git a/src/providers/ogr/qgsgeopackagedataitems.h b/src/providers/ogr/qgsgeopackagedataitems.h index 6b34f98ac631..76c25d43f4c0 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.h +++ b/src/providers/ogr/qgsgeopackagedataitems.h @@ -28,9 +28,6 @@ class QgsGeoPackageAbstractLayerItem : public QgsLayerItem { Q_OBJECT - //! Returns layer uri or empty string if layer cannot be created - QString uri() const { return mUri; } - protected: QgsGeoPackageAbstractLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType, const QString &providerKey ); From f06bb799ef5adad6dc1fbd53ec3521e83175ec72 Mon Sep 17 00:00:00 2001 From: signedav Date: Mon, 19 Nov 2018 09:58:46 +0100 Subject: [PATCH 007/266] delete/truncate selected tables for mssql-tables --- src/providers/mssql/qgsmssqldataitems.cpp | 88 ++++++++++++++--------- src/providers/mssql/qgsmssqldataitems.h | 6 ++ 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/providers/mssql/qgsmssqldataitems.cpp b/src/providers/mssql/qgsmssqldataitems.cpp index 4309507e6f8e..f5ebe7c63297 100644 --- a/src/providers/mssql/qgsmssqldataitems.cpp +++ b/src/providers/mssql/qgsmssqldataitems.cpp @@ -568,57 +568,81 @@ QgsMssqlLayerItem::QgsMssqlLayerItem( QgsDataItem *parent, const QString &name, #ifdef HAVE_GUI QList QgsMssqlLayerItem::actions( QWidget *actionParent ) { - QgsMssqlConnectionItem *connItem = qobject_cast( parent() ? parent()->parent() : nullptr ); - QList lst; // delete - QAction *actionDeleteLayer = new QAction( tr( "Delete Table" ), actionParent ); + const QString deleteText = selectedItems().count() == 1 ? tr( "Delete Table" ) + : tr( "Delete Selected Tables" ); + QAction *actionDeleteLayer = new QAction( deleteText, actionParent ); connect( actionDeleteLayer, &QAction::triggered, this, [ = ] { - if ( QMessageBox::question( nullptr, QObject::tr( "Delete Table" ), - QObject::tr( "Are you sure you want to delete [%1].[%2]?" ).arg( mLayerProperty.schemaName, mLayerProperty.tableName ), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) - return; - - QString errCause; - bool res = QgsMssqlConnection::dropTable( mUri, &errCause ); - if ( !res ) + QList items = selectedItems(); + for ( QgsDataItem *item : items ) { - QMessageBox::warning( nullptr, tr( "Delete Table" ), errCause ); - } - else - { - QMessageBox::information( nullptr, tr( "Delete Table" ), tr( "Table deleted successfully." ) ); - if ( connItem ) - connItem->refresh(); + if ( QgsMssqlLayerItem *mssqlLayerItem = qobject_cast< QgsMssqlLayerItem *>( item ) ) + mssqlLayerItem->deleteLayer(); } } ); lst.append( actionDeleteLayer ); // truncate - QAction *actionTruncateLayer = new QAction( tr( "Truncate Table" ), actionParent ); + const QString truncateText = selectedItems().count() == 1 ? tr( "Truncate Table" ) + : tr( "Truncate Selected Tables" ); + QAction *actionTruncateLayer = new QAction( truncateText, actionParent ); connect( actionTruncateLayer, &QAction::triggered, this, [ = ] { - if ( QMessageBox::question( nullptr, QObject::tr( "Truncate Table" ), - QObject::tr( "Are you sure you want to truncate [%1].[%2]?\n\nThis will delete all data within the table." ).arg( mLayerProperty.schemaName, mLayerProperty.tableName ), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) - return; - - QString errCause; - bool res = QgsMssqlConnection::truncateTable( mUri, &errCause ); - if ( !res ) + QList items = selectedItems(); + for ( QgsDataItem *item : items ) { - QMessageBox::warning( nullptr, tr( "Truncate Table" ), errCause ); - } - else - { - QMessageBox::information( nullptr, tr( "Truncate Table" ), tr( "Table truncated successfully." ) ); + if ( QgsMssqlLayerItem *mssqlLayerItem = qobject_cast< QgsMssqlLayerItem *>( item ) ) + mssqlLayerItem->truncateTable(); } } ); lst.append( actionTruncateLayer ); return lst; } + +void QgsMssqlLayerItem::deleteLayer() +{ + QgsMssqlConnectionItem *connItem = qobject_cast( parent() ? parent()->parent() : nullptr ); + + if ( QMessageBox::question( nullptr, QObject::tr( "Delete Table" ), + QObject::tr( "Are you sure you want to delete [%1].[%2]?" ).arg( mLayerProperty.schemaName, mLayerProperty.tableName ), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) + return; + + QString errCause; + bool res = QgsMssqlConnection::dropTable( mUri, &errCause ); + if ( !res ) + { + QMessageBox::warning( nullptr, tr( "Delete Table" ), errCause ); + } + else + { + QMessageBox::information( nullptr, tr( "Delete Table" ), tr( "Table deleted successfully." ) ); + if ( connItem ) + connItem->refresh(); + } +} + +void QgsMssqlLayerItem::truncateTable() +{ + if ( QMessageBox::question( nullptr, QObject::tr( "Truncate Table" ), + QObject::tr( "Are you sure you want to truncate [%1].[%2]?\n\nThis will delete all data within the table." ).arg( mLayerProperty.schemaName, mLayerProperty.tableName ), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) + return; + + QString errCause; + bool res = QgsMssqlConnection::truncateTable( mUri, &errCause ); + if ( !res ) + { + QMessageBox::warning( nullptr, tr( "Truncate Table" ), errCause ); + } + else + { + QMessageBox::information( nullptr, tr( "Truncate Table" ), tr( "Table truncated successfully." ) ); + } +} #endif QgsMssqlLayerItem *QgsMssqlLayerItem::createClone() diff --git a/src/providers/mssql/qgsmssqldataitems.h b/src/providers/mssql/qgsmssqldataitems.h index cee6c12988c4..15fcce935206 100644 --- a/src/providers/mssql/qgsmssqldataitems.h +++ b/src/providers/mssql/qgsmssqldataitems.h @@ -140,6 +140,12 @@ class QgsMssqlLayerItem : public QgsLayerItem bool disableInvalidGeometryHandling() const; + public slots: +#ifdef HAVE_GUI + void deleteLayer(); + void truncateTable(); +#endif + private: QgsMssqlLayerProperty mLayerProperty; bool mDisableInvalidGeometryHandling = false; From a1ea0ac540a63c8653e653a28a0a4ff3889fccde Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 20 Nov 2018 10:49:50 +0100 Subject: [PATCH 008/266] action deleteLayer in the dataitemproviders implemented for derived functions in: - `QgsGeoPackageAbstractLayerItem` - `QgsSLLayerItem` - `QgsPGLayerItem` - `QgsMssqlLayerItem` --- python/core/auto_generated/qgsdataitem.sip.in | 6 ++-- .../browser/qgsinbuiltdataitemproviders.cpp | 18 +++++++++++ src/app/browser/qgsinbuiltdataitemproviders.h | 1 + src/core/qgsdataitem.cpp | 5 +++ src/core/qgsdataitem.h | 4 +-- src/gui/qgsbrowserdockwidget.cpp | 1 - src/providers/mssql/qgsmssqldataitems.cpp | 31 +++---------------- src/providers/mssql/qgsmssqldataitems.h | 2 +- src/providers/ogr/qgsgeopackagedataitems.cpp | 28 +++-------------- src/providers/ogr/qgsgeopackagedataitems.h | 5 ++- .../postgres/qgspostgresdataitems.cpp | 19 ++---------- src/providers/postgres/qgspostgresdataitems.h | 2 +- .../spatialite/qgsspatialitedataitems.cpp | 27 ++-------------- .../spatialite/qgsspatialitedataitems.h | 7 +---- 14 files changed, 50 insertions(+), 106 deletions(-) diff --git a/python/core/auto_generated/qgsdataitem.sip.in b/python/core/auto_generated/qgsdataitem.sip.in index a7bdaf43cb76..a95d8e7b60b8 100644 --- a/python/core/auto_generated/qgsdataitem.sip.in +++ b/python/core/auto_generated/qgsdataitem.sip.in @@ -314,8 +314,10 @@ Sets a custom sorting ``key`` for the item. Move object and all its descendants to thread %End - void setSelectedItems( const QList &selectedItems ); - QList selectedItems() const; + virtual bool deleteLayer(); +%Docstring +Delete this layer item +%End protected: virtual void populate( const QVector &children ); diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp index 3b0089558f2f..fe1037d7bfe6 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.cpp +++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp @@ -366,6 +366,15 @@ void QgsLayerItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *men } ); menu->addAction( addAction ); + const QString deleteText = selectedItems.count() == 1 ? tr( "Delete Layer" ) + : tr( "Delete Selected Layers" ); + QAction *deleteAction = new QAction( deleteText, menu ); + connect( deleteAction, &QAction::triggered, this, [ = ] + { + deleteLayers( selectedItems ); + } ); + menu->addAction( deleteAction ); + QAction *propertiesAction = new QAction( tr( "Layer Properties…" ), menu ); connect( propertiesAction, &QAction::triggered, this, [ = ] { @@ -433,6 +442,15 @@ void QgsLayerItemGuiProvider::addLayersFromItems( const QList &it QgisApp::instance()->handleDropUriList( layerUriList ); } +void QgsLayerItemGuiProvider::deleteLayers( const QList &items ) +{ + for ( QgsDataItem *item : items ) + { + if ( !item->deleteLayer() ) + QMessageBox::information( QgisApp::instance(), tr( "Delete Layer" ), tr( "Item Layer %1 cannot be deleted." ).arg( item->name() ) ); + } +} + void QgsLayerItemGuiProvider::showPropertiesForItem( QgsLayerItem *item ) { if ( ! item ) diff --git a/src/app/browser/qgsinbuiltdataitemproviders.h b/src/app/browser/qgsinbuiltdataitemproviders.h index 2197f7d56b9d..2becbca388db 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.h +++ b/src/app/browser/qgsinbuiltdataitemproviders.h @@ -100,6 +100,7 @@ class QgsLayerItemGuiProvider : public QObject, public QgsDataItemGuiProvider void addLayersFromItems( const QList &items ); void showPropertiesForItem( QgsLayerItem *item ); + void deleteLayers( const QList &items ); }; diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp index 6a80171ec642..9d76a79e3afb 100644 --- a/src/core/qgsdataitem.cpp +++ b/src/core/qgsdataitem.cpp @@ -219,6 +219,11 @@ void QgsDataItem::moveToThread( QThread *targetThread ) QObject::moveToThread( targetThread ); } +bool QgsDataItem::deleteLayer() +{ + return false; +} + QIcon QgsDataItem::icon() { if ( state() == Populating && sPopulatingIcon ) diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 37c2f8f87a30..abde67e040e2 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -323,8 +323,8 @@ class CORE_EXPORT QgsDataItem : public QObject //! Move object and all its descendants to thread void moveToThread( QThread *targetThread ); - void setSelectedItems( const QList &selectedItems ) { mSelectedItems = selectedItems; } - QList selectedItems() const { return mSelectedItems; } + //! Delete this layer item + virtual bool deleteLayer(); protected: virtual void populate( const QVector &children ); diff --git a/src/gui/qgsbrowserdockwidget.cpp b/src/gui/qgsbrowserdockwidget.cpp index c32dc8177041..6691878cb52a 100644 --- a/src/gui/qgsbrowserdockwidget.cpp +++ b/src/gui/qgsbrowserdockwidget.cpp @@ -211,7 +211,6 @@ void QgsBrowserDockWidget::showContextMenu( QPoint pt ) QMenu *menu = new QMenu( this ); const QList menus = item->menus( menu ); - item->setSelectedItems( selectedItems ); QList actions = item->actions( menu ); if ( !menus.isEmpty() ) diff --git a/src/providers/mssql/qgsmssqldataitems.cpp b/src/providers/mssql/qgsmssqldataitems.cpp index f5ebe7c63297..18d2806ef1d0 100644 --- a/src/providers/mssql/qgsmssqldataitems.cpp +++ b/src/providers/mssql/qgsmssqldataitems.cpp @@ -570,46 +570,24 @@ QList QgsMssqlLayerItem::actions( QWidget *actionParent ) { QList lst; - // delete - const QString deleteText = selectedItems().count() == 1 ? tr( "Delete Table" ) - : tr( "Delete Selected Tables" ); - QAction *actionDeleteLayer = new QAction( deleteText, actionParent ); - connect( actionDeleteLayer, &QAction::triggered, this, [ = ] - { - QList items = selectedItems(); - for ( QgsDataItem *item : items ) - { - if ( QgsMssqlLayerItem *mssqlLayerItem = qobject_cast< QgsMssqlLayerItem *>( item ) ) - mssqlLayerItem->deleteLayer(); - } - } ); - lst.append( actionDeleteLayer ); - // truncate - const QString truncateText = selectedItems().count() == 1 ? tr( "Truncate Table" ) - : tr( "Truncate Selected Tables" ); - QAction *actionTruncateLayer = new QAction( truncateText, actionParent ); + QAction *actionTruncateLayer = new QAction( tr( "Truncate Table" ), actionParent ); connect( actionTruncateLayer, &QAction::triggered, this, [ = ] { - QList items = selectedItems(); - for ( QgsDataItem *item : items ) - { - if ( QgsMssqlLayerItem *mssqlLayerItem = qobject_cast< QgsMssqlLayerItem *>( item ) ) - mssqlLayerItem->truncateTable(); - } + truncateTable(); } ); lst.append( actionTruncateLayer ); return lst; } -void QgsMssqlLayerItem::deleteLayer() +bool QgsMssqlLayerItem::deleteLayer() { QgsMssqlConnectionItem *connItem = qobject_cast( parent() ? parent()->parent() : nullptr ); if ( QMessageBox::question( nullptr, QObject::tr( "Delete Table" ), QObject::tr( "Are you sure you want to delete [%1].[%2]?" ).arg( mLayerProperty.schemaName, mLayerProperty.tableName ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) - return; + return true; QString errCause; bool res = QgsMssqlConnection::dropTable( mUri, &errCause ); @@ -623,6 +601,7 @@ void QgsMssqlLayerItem::deleteLayer() if ( connItem ) connItem->refresh(); } + return true; } void QgsMssqlLayerItem::truncateTable() diff --git a/src/providers/mssql/qgsmssqldataitems.h b/src/providers/mssql/qgsmssqldataitems.h index 15fcce935206..4a66e5e99c63 100644 --- a/src/providers/mssql/qgsmssqldataitems.h +++ b/src/providers/mssql/qgsmssqldataitems.h @@ -142,7 +142,7 @@ class QgsMssqlLayerItem : public QgsLayerItem public slots: #ifdef HAVE_GUI - void deleteLayer(); + bool deleteLayer() override; void truncateTable(); #endif diff --git a/src/providers/ogr/qgsgeopackagedataitems.cpp b/src/providers/ogr/qgsgeopackagedataitems.cpp index 07664162666b..47d68501d432 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.cpp +++ b/src/providers/ogr/qgsgeopackagedataitems.cpp @@ -75,26 +75,6 @@ QVector QgsGeoPackageRootItem::createChildren() } #ifdef HAVE_GUI -QList QgsGeoPackageAbstractLayerItem::actions( QWidget * ) -{ - QList lst; - - const QString deleteText = selectedItems().count() == 1 ? tr( "Delete Layer '%1'…" ).arg( mName ) - : tr( "Delete Selected Layers" ); - QAction *actionDeleteLayer = new QAction( deleteText, this ); - connect( actionDeleteLayer, &QAction::triggered, this, [ = ] - { - QList items = selectedItems(); - for ( QgsDataItem *item : items ) - { - if ( QgsGeoPackageAbstractLayerItem *gpkgAbstractItem = qobject_cast< QgsGeoPackageAbstractLayerItem *>( item ) ) - gpkgAbstractItem->deleteLayer(); - } - } ) ; - lst.append( actionDeleteLayer ); - return lst; -} - void QgsGeoPackageRootItem::onConnectionsChanged() { refresh(); @@ -498,7 +478,7 @@ void QgsGeoPackageCollectionItem::vacuumGeoPackageDbAction() } } -void QgsGeoPackageAbstractLayerItem::deleteLayer() +bool QgsGeoPackageAbstractLayerItem::deleteLayer() { // Check if the layer(s) are in the registry QList layersList; @@ -516,14 +496,14 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer() if ( QMessageBox::question( nullptr, QObject::tr( "Delete Layer" ), QObject::tr( "The layer %1 exists in the current project %2," " do you want to remove it from the project and delete it?" ).arg( mName, layersList.at( 0 )->name() ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) { - return; + return true; } } else if ( QMessageBox::question( nullptr, QObject::tr( "Delete Layer" ), QObject::tr( "Are you sure you want to delete layer %1 from GeoPackage?" ).arg( mName ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) { - return; + return true; } if ( layersList.isEmpty() ) @@ -543,7 +523,7 @@ void QgsGeoPackageAbstractLayerItem::deleteLayer() if ( mParent ) mParent->refreshConnections(); } - + return true; } #endif diff --git a/src/providers/ogr/qgsgeopackagedataitems.h b/src/providers/ogr/qgsgeopackagedataitems.h index 76c25d43f4c0..f3e622f488e8 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.h +++ b/src/providers/ogr/qgsgeopackagedataitems.h @@ -36,10 +36,9 @@ class QgsGeoPackageAbstractLayerItem : public QgsLayerItem * the real deletion implementation */ virtual bool executeDeleteLayer( QString &errCause ); + #ifdef HAVE_GUI - QList actions( QWidget *menu ) override; - public slots: - virtual void deleteLayer(); + bool deleteLayer() override; #endif }; diff --git a/src/providers/postgres/qgspostgresdataitems.cpp b/src/providers/postgres/qgspostgresdataitems.cpp index cd181e49f494..8dc137084217 100644 --- a/src/providers/postgres/qgspostgresdataitems.cpp +++ b/src/providers/postgres/qgspostgresdataitems.cpp @@ -317,20 +317,6 @@ QList QgsPGLayerItem::actions( QWidget *parent ) connect( actionRenameLayer, &QAction::triggered, this, &QgsPGLayerItem::renameLayer ); lst.append( actionRenameLayer ); - const QString deleteText = selectedItems().count() == 1 ? tr( "Delete %1" ).arg( typeName ) - : tr( "Delete Selected Tables" ); - QAction *actionDeleteLayer = new QAction( deleteText, parent ); - connect( actionDeleteLayer, &QAction::triggered, this, [ = ] - { - QList items = selectedItems(); - for ( QgsDataItem *item : items ) - { - if ( QgsPGLayerItem *pgLayerItem = qobject_cast< QgsPGLayerItem *>( item ) ) - pgLayerItem->deleteLayer(); - } - } ) ; - lst.append( actionDeleteLayer ); - if ( !mLayerProperty.isView ) { QAction *actionTruncateLayer = new QAction( tr( "Truncate %1" ).arg( typeName ), parent ); @@ -348,12 +334,12 @@ QList QgsPGLayerItem::actions( QWidget *parent ) return lst; } -void QgsPGLayerItem::deleteLayer() +bool QgsPGLayerItem::deleteLayer() { if ( QMessageBox::question( nullptr, QObject::tr( "Delete Table" ), QObject::tr( "Are you sure you want to delete %1.%2?" ).arg( mLayerProperty.schemaName, mLayerProperty.tableName ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) - return; + return true; QString errCause; bool res = ::deleteLayer( mUri, errCause ); @@ -367,6 +353,7 @@ void QgsPGLayerItem::deleteLayer() if ( mParent ) mParent->refresh(); } + return true; } void QgsPGLayerItem::renameLayer() diff --git a/src/providers/postgres/qgspostgresdataitems.h b/src/providers/postgres/qgspostgresdataitems.h index d2247dc996cf..1bfe10e2adf8 100644 --- a/src/providers/postgres/qgspostgresdataitems.h +++ b/src/providers/postgres/qgspostgresdataitems.h @@ -129,7 +129,7 @@ class QgsPGLayerItem : public QgsLayerItem public slots: #ifdef HAVE_GUI - void deleteLayer(); + bool deleteLayer() override; void renameLayer(); void truncateTable(); void refreshMaterializedView(); diff --git a/src/providers/spatialite/qgsspatialitedataitems.cpp b/src/providers/spatialite/qgsspatialitedataitems.cpp index 2209548f5bae..2358b63062c9 100644 --- a/src/providers/spatialite/qgsspatialitedataitems.cpp +++ b/src/providers/spatialite/qgsspatialitedataitems.cpp @@ -41,34 +41,12 @@ QgsSLLayerItem::QgsSLLayerItem( QgsDataItem *parent, const QString &name, const } #ifdef HAVE_GUI -QList QgsSLLayerItem::actions( QWidget *parent ) -{ - QList lst; - - const QString deleteText = selectedItems().count() == 1 ? tr( "Delete Layer '%1'…" ).arg( mName ) - : tr( "Delete Selected Layers" ); - QAction *actionDeleteLayer = new QAction( deleteText, parent ); - - connect( actionDeleteLayer, &QAction::triggered, this, [ = ] - { - QList items = selectedItems(); - for ( QgsDataItem *item : items ) - { - if ( QgsSLLayerItem *slLayerItem = qobject_cast< QgsSLLayerItem *>( item ) ) - slLayerItem->deleteLayer(); - } - } ) ; - lst.append( actionDeleteLayer ); - - return lst; -} - -void QgsSLLayerItem::deleteLayer() +bool QgsSLLayerItem::deleteLayer() { if ( QMessageBox::question( nullptr, QObject::tr( "Delete Object" ), QObject::tr( "Are you sure you want to delete %1?" ).arg( mName ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) - return; + return true; QgsDataSourceUri uri( mUri ); QString errCause; @@ -82,6 +60,7 @@ void QgsSLLayerItem::deleteLayer() QMessageBox::information( nullptr, tr( "Delete Layer" ), tr( "Layer deleted successfully." ) ); mParent->refresh(); } + return true; } #endif diff --git a/src/providers/spatialite/qgsspatialitedataitems.h b/src/providers/spatialite/qgsspatialitedataitems.h index 0c07467efe67..38bf9f22bfe9 100644 --- a/src/providers/spatialite/qgsspatialitedataitems.h +++ b/src/providers/spatialite/qgsspatialitedataitems.h @@ -24,12 +24,7 @@ class QgsSLLayerItem : public QgsLayerItem QgsSLLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType ); #ifdef HAVE_GUI - QList actions( QWidget *parent ) override; -#endif - - public slots: -#ifdef HAVE_GUI - void deleteLayer(); + bool deleteLayer() override; #endif }; From 4296c77178cfb1fbd7a3f9e374e95bcb7ffb45b2 Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 20 Nov 2018 11:04:53 +0100 Subject: [PATCH 009/266] remove unused variable --- src/core/qgsdataitem.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index abde67e040e2..b6e49e196772 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -421,7 +421,6 @@ class CORE_EXPORT QgsDataItem : public QObject QFutureWatcher< QVector > *mFutureWatcher; // number of items currently in loading (populating) state static QgsAnimatedIcon *sPopulatingIcon; - QList mSelectedItems; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsDataItem::Capabilities ) From 1136aabf5b0f86b1d0804bc5e3eaf65ff5b5ff35 Mon Sep 17 00:00:00 2001 From: signedav Date: Wed, 21 Nov 2018 10:46:39 +0100 Subject: [PATCH 010/266] delete layer action only on items that have capability "delete" check if the item with the menu has this capability and only handle the selected items with this capabiltiy --- python/core/auto_generated/qgsdataitem.sip.in | 11 ++++---- .../browser/qgsinbuiltdataitemproviders.cpp | 28 +++++++++++++------ src/app/browser/qgsinbuiltdataitemproviders.h | 2 +- src/core/qgsdataitem.cpp | 10 +++---- src/core/qgsdataitem.h | 9 +++--- src/providers/mssql/qgsmssqldataitems.cpp | 1 + src/providers/ogr/qgsgeopackagedataitems.cpp | 1 + .../postgres/qgspostgresdataitems.cpp | 1 + .../spatialite/qgsspatialitedataitems.cpp | 1 + 9 files changed, 40 insertions(+), 24 deletions(-) diff --git a/python/core/auto_generated/qgsdataitem.sip.in b/python/core/auto_generated/qgsdataitem.sip.in index a95d8e7b60b8..442bb45d9df6 100644 --- a/python/core/auto_generated/qgsdataitem.sip.in +++ b/python/core/auto_generated/qgsdataitem.sip.in @@ -201,6 +201,7 @@ Items that return valid URI will be returned in mime data when dragging a select Fast, Collapse, Rename, + Delete, }; typedef QFlags Capabilities; @@ -312,11 +313,6 @@ Sets a custom sorting ``key`` for the item. void moveToThread( QThread *targetThread ); %Docstring Move object and all its descendants to thread -%End - - virtual bool deleteLayer(); -%Docstring -Delete this layer item %End protected: @@ -490,6 +486,11 @@ Returns the string representation of the given ``layerType`` Returns the icon name of the given ``layerType`` .. versionadded:: 3 +%End + + virtual bool deleteLayer(); +%Docstring +Delete this layer item %End protected: diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp index fe1037d7bfe6..5cf4dae88115 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.cpp +++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp @@ -366,14 +366,24 @@ void QgsLayerItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *men } ); menu->addAction( addAction ); - const QString deleteText = selectedItems.count() == 1 ? tr( "Delete Layer" ) - : tr( "Delete Selected Layers" ); - QAction *deleteAction = new QAction( deleteText, menu ); - connect( deleteAction, &QAction::triggered, this, [ = ] + if ( item->capabilities2() & QgsDataItem::Delete ) { - deleteLayers( selectedItems ); - } ); - menu->addAction( deleteAction ); + QList selectedDeletableItems; + for ( QgsDataItem *selectedItem : selectedItems ) + { + if ( qobject_cast( selectedItem ) && ( selectedItem->capabilities2() & QgsDataItem::Delete ) ) + selectedDeletableItems.append( qobject_cast( selectedItem ) ); + } + + const QString deleteText = selectedDeletableItems.count() == 1 ? tr( "Delete Layer" ) + : tr( "Delete Selected Layers" ); + QAction *deleteAction = new QAction( deleteText, menu ); + connect( deleteAction, &QAction::triggered, this, [ = ] + { + deleteLayers( selectedDeletableItems ); + } ); + menu->addAction( deleteAction ); + } QAction *propertiesAction = new QAction( tr( "Layer Properties…" ), menu ); connect( propertiesAction, &QAction::triggered, this, [ = ] @@ -442,9 +452,9 @@ void QgsLayerItemGuiProvider::addLayersFromItems( const QList &it QgisApp::instance()->handleDropUriList( layerUriList ); } -void QgsLayerItemGuiProvider::deleteLayers( const QList &items ) +void QgsLayerItemGuiProvider::deleteLayers( const QList &items ) { - for ( QgsDataItem *item : items ) + for ( QgsLayerItem *item : items ) { if ( !item->deleteLayer() ) QMessageBox::information( QgisApp::instance(), tr( "Delete Layer" ), tr( "Item Layer %1 cannot be deleted." ).arg( item->name() ) ); diff --git a/src/app/browser/qgsinbuiltdataitemproviders.h b/src/app/browser/qgsinbuiltdataitemproviders.h index 2becbca388db..f221144b0824 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.h +++ b/src/app/browser/qgsinbuiltdataitemproviders.h @@ -100,7 +100,7 @@ class QgsLayerItemGuiProvider : public QObject, public QgsDataItemGuiProvider void addLayersFromItems( const QList &items ); void showPropertiesForItem( QgsLayerItem *item ); - void deleteLayers( const QList &items ); + void deleteLayers( const QList &items ); }; diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp index 9d76a79e3afb..bf9d6606dbc2 100644 --- a/src/core/qgsdataitem.cpp +++ b/src/core/qgsdataitem.cpp @@ -219,11 +219,6 @@ void QgsDataItem::moveToThread( QThread *targetThread ) QObject::moveToThread( targetThread ); } -bool QgsDataItem::deleteLayer() -{ - return false; -} - QIcon QgsDataItem::icon() { if ( state() == Populating && sPopulatingIcon ) @@ -706,6 +701,11 @@ QString QgsLayerItem::iconName( QgsLayerItem::LayerType layerType ) } } +bool QgsLayerItem::deleteLayer() +{ + return false; +} + bool QgsLayerItem::equal( const QgsDataItem *other ) { //QgsDebugMsg ( mPath + " x " + other->mPath ); diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index b6e49e196772..db6f95d14484 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -210,8 +210,9 @@ class CORE_EXPORT QgsDataItem : public QObject SetCrs = 1 << 0, //!< Can set CRS on layer or group of layers Fertile = 1 << 1, //!< Can create children. Even items without this capability may have children, but cannot create them, it means that children are created by item ancestors. Fast = 1 << 2, //!< CreateChildren() is fast enough to be run in main thread when refreshing items, most root items (wms,wfs,wcs,postgres...) are considered fast because they are reading data only from QgsSettings - Collapse = 1 << 3, //!< The collapse/expand status for this items children should be ignored in order to avoid undesired network connections (wms etc.) + Collapse = 1 << 3, //!< The collapse/expand status for this items children should be ignored in order to avoid undesired network connections (wms etc.) Rename = 1 << 4, //!< Item can be renamed + Delete = 1 << 5, //!< Item can be deleted }; Q_DECLARE_FLAGS( Capabilities, Capability ) @@ -323,9 +324,6 @@ class CORE_EXPORT QgsDataItem : public QObject //! Move object and all its descendants to thread void moveToThread( QThread *targetThread ); - //! Delete this layer item - virtual bool deleteLayer(); - protected: virtual void populate( const QVector &children ); @@ -508,6 +506,9 @@ class CORE_EXPORT QgsLayerItem : public QgsDataItem */ static QString iconName( LayerType layerType ); + //! Delete this layer item + virtual bool deleteLayer(); + protected: //! The provider key diff --git a/src/providers/mssql/qgsmssqldataitems.cpp b/src/providers/mssql/qgsmssqldataitems.cpp index 18d2806ef1d0..d3aa66782fbf 100644 --- a/src/providers/mssql/qgsmssqldataitems.cpp +++ b/src/providers/mssql/qgsmssqldataitems.cpp @@ -561,6 +561,7 @@ QgsMssqlLayerItem::QgsMssqlLayerItem( QgsDataItem *parent, const QString &name, : QgsLayerItem( parent, name, path, QString(), layerType, QStringLiteral( "mssql" ) ) , mLayerProperty( layerProperty ) { + mCapabilities |= Delete; mUri = createUri(); setState( Populated ); } diff --git a/src/providers/ogr/qgsgeopackagedataitems.cpp b/src/providers/ogr/qgsgeopackagedataitems.cpp index 47d68501d432..b92d3b5e0fab 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.cpp +++ b/src/providers/ogr/qgsgeopackagedataitems.cpp @@ -699,6 +699,7 @@ bool QgsGeoPackageConnectionItem::equal( const QgsDataItem *other ) QgsGeoPackageAbstractLayerItem::QgsGeoPackageAbstractLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, QgsLayerItem::LayerType layerType, const QString &providerKey ) : QgsLayerItem( parent, name, path, uri, layerType, providerKey ) { + mCapabilities |= Delete; mToolTip = uri; setState( Populated ); // no children are expected } diff --git a/src/providers/postgres/qgspostgresdataitems.cpp b/src/providers/postgres/qgspostgresdataitems.cpp index 8dc137084217..995b8d6a19ab 100644 --- a/src/providers/postgres/qgspostgresdataitems.cpp +++ b/src/providers/postgres/qgspostgresdataitems.cpp @@ -296,6 +296,7 @@ QgsPGLayerItem::QgsPGLayerItem( QgsDataItem *parent, const QString &name, const : QgsLayerItem( parent, name, path, QString(), layerType, QStringLiteral( "postgres" ) ) , mLayerProperty( layerProperty ) { + mCapabilities |= Delete; mUri = createUri(); setState( Populated ); Q_ASSERT( mLayerProperty.size() == 1 ); diff --git a/src/providers/spatialite/qgsspatialitedataitems.cpp b/src/providers/spatialite/qgsspatialitedataitems.cpp index 2358b63062c9..eb62eeb661a7 100644 --- a/src/providers/spatialite/qgsspatialitedataitems.cpp +++ b/src/providers/spatialite/qgsspatialitedataitems.cpp @@ -37,6 +37,7 @@ QGISEXTERN bool deleteLayer( const QString &dbPath, const QString &tableName, QS QgsSLLayerItem::QgsSLLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType ) : QgsLayerItem( parent, name, path, uri, layerType, QStringLiteral( "spatialite" ) ) { + mCapabilities |= Delete; setState( Populated ); // no children are expected } From 8e223e630c7f4ec526f5e27b57a55a00327923d5 Mon Sep 17 00:00:00 2001 From: Giovanni Manghi Date: Tue, 27 Nov 2018 13:24:29 +0000 Subject: [PATCH 011/266] added missing * --- .../processing/algs/grass7/description/r.mapcalculator.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/algs/grass7/description/r.mapcalculator.txt b/python/plugins/processing/algs/grass7/description/r.mapcalculator.txt index 30144b2d743c..da148bb44aa1 100644 --- a/python/plugins/processing/algs/grass7/description/r.mapcalculator.txt +++ b/python/plugins/processing/algs/grass7/description/r.mapcalculator.txt @@ -1,6 +1,6 @@ r.mapcalculator Calculate new raster map from a r.mapcalc expression. -Raster (r.) +Raster (r.*) ParameterRaster|a|Raster layer A|False ParameterRaster|b|Raster layer B|True ParameterRaster|c|Raster layer C|True From 0494096b3df49d8599c063158f54266cd6edc178 Mon Sep 17 00:00:00 2001 From: Alexandre Neto Date: Mon, 10 Dec 2018 22:27:03 +0000 Subject: [PATCH 012/266] Renames Avoid intersection to Avoid overlap [needs-docs] --- src/app/qgssnappinglayertreemodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/qgssnappinglayertreemodel.cpp b/src/app/qgssnappinglayertreemodel.cpp index 11c34fd0fc15..7403b5269691 100644 --- a/src/app/qgssnappinglayertreemodel.cpp +++ b/src/app/qgssnappinglayertreemodel.cpp @@ -347,7 +347,7 @@ QVariant QgsSnappingLayerTreeModel::headerData( int section, Qt::Orientation ori case 3: return tr( "Units" ); case 4: - return tr( "Avoid intersection" ); + return tr( "Avoid overlap" ); default: return QVariant(); } @@ -493,7 +493,7 @@ QVariant QgsSnappingLayerTreeModel::data( const QModelIndex &idx, int role ) con } } - // avoid intersection + // avoid intersection(Overlap) if ( idx.column() == AvoidIntersectionColumn ) { if ( role == Qt::CheckStateRole && vl->geometryType() == QgsWkbTypes::PolygonGeometry ) From 55269754030c5fb85e36e6de869064e6fc70ea6f Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 11 Dec 2018 11:58:06 +0100 Subject: [PATCH 013/266] reading ogr JSON basics --- src/core/qgsfield.cpp | 2 +- src/core/qgsogrutils.cpp | 11 +++++++++++ src/providers/ogr/qgsogrprovider.cpp | 18 ++++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/core/qgsfield.cpp b/src/core/qgsfield.cpp index 69deedd2f4a4..14f8ceca42df 100644 --- a/src/core/qgsfield.cpp +++ b/src/core/qgsfield.cpp @@ -254,7 +254,7 @@ QString QgsField::displayString( const QVariant &v ) const if ( ok ) return QLocale().toString( converted ); } - else if ( d->typeName == QLatin1String( "json" ) || d->typeName == QLatin1String( "jsonb" ) ) + else if ( d->typeName == QLatin1String( "json" ) || d->typeName == QLatin1String( "jsonb" ) || d->typeName == QLatin1String( "JSON" ) ) { QJsonDocument doc = QJsonDocument::fromVariant( v ); return QString::fromUtf8( doc.toJson().data() ); diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index addcc7ee8fc4..9b586ad2ea72 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -22,6 +22,7 @@ #include #include #include +#include // Starting with GDAL 2.2, there are 2 concepts: unset fields and null fields // whereas previously there was only unset fields. For QGIS purposes, both @@ -236,6 +237,16 @@ QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField break; } + case QVariant::Map: + { + //it has to be JSON + //it's null if no json format + if ( encoding ) + value = QJsonDocument::fromJson( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant(); + else + value = QJsonDocument::fromJson( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant(); + break; + } default: Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" ); if ( ok ) diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index 7c069a51ce51..dddc22cd54ea 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -470,7 +470,8 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio << QgsVectorDataProvider::NativeType( tr( "Whole number (integer)" ), QStringLiteral( "integer" ), QVariant::Int, 0, nMaxIntLen ) << QgsVectorDataProvider::NativeType( tr( "Whole number (integer 64 bit)" ), QStringLiteral( "integer64" ), QVariant::LongLong, 0, nMaxInt64Len ) << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "double" ), QVariant::Double, 0, nMaxDoubleLen, 0, nMaxDoublePrec ) - << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 65535 ); + << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 65535 ) + << QgsVectorDataProvider::NativeType( tr( "Map (json)" ), QStringLiteral( "json" ), QVariant::Map, -1, -1, -1, -1, QVariant::String ); bool supportsDate = true; bool supportsTime = mGDALDriverName != QLatin1String( "ESRI Shapefile" ) && mGDALDriverName != QLatin1String( "GPKG" ); @@ -1009,6 +1010,7 @@ void QgsOgrProvider::loadFields() OGRFieldSubType ogrSubType = OFSTNone; QVariant::Type varType; + QVariant::Type varSubType; switch ( ogrType ) { case OFTInteger: @@ -1041,6 +1043,18 @@ void QgsOgrProvider::loadFields() break; case OFTString: + if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) + { + ogrSubType = OFSTJSON; + QgsDebugMsg( QStringLiteral( "JSON Field found" ) ); + varType = QVariant::Map; + varSubType = QVariant::String; + } + else + { + varType = QVariant::String; + } + break; default: varType = QVariant::String; // other unsupported, leave it as a string } @@ -1081,7 +1095,7 @@ void QgsOgrProvider::loadFields() #else textEncoding()->toUnicode( typeName.toStdString().c_str() ), #endif - width, prec + width, prec, QString(), varSubType ); // check if field is nullable From 607dffd1aadac054e42ee7623313649533364dd9 Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 11 Dec 2018 13:12:35 +0100 Subject: [PATCH 014/266] basic tests for json type in gpkg --- tests/src/python/test_provider_ogr_gpkg.py | 23 +++++++++++++++++++++ tests/testdata/provider/test_json.gpkg | Bin 0 -> 147456 bytes 2 files changed, 23 insertions(+) create mode 100644 tests/testdata/provider/test_json.gpkg diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 3819e879b9b5..7cddbf0e23c8 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -1230,6 +1230,29 @@ def testTransaction(self): self.assertEqual(len([f for f in vl2_external.getFeatures(QgsFeatureRequest())]), 1) del vl2_external + def testJson(self): + tmpfile = os.path.join(self.basetestpath, 'test_json.gpkg') + testdata_path = unitTestDataPath('provider') + shutil.copy(os.path.join(unitTestDataPath('provider'), 'test_json.gpkg'), tmpfile) + + vl = QgsVectorLayer('{}|layerid=0'.format(tmpfile, 'foo', 'ogr')) + self.assertTrue(vl.isValid()) + + fields = vl.dataProvider().fields() + self.assertEqual(fields.at(fields.indexFromName('json_content')).type(), QVariant.Map) + + fi = vl.getFeatures(QgsFeatureRequest()) + f = QgsFeature() + + #test dict value + while fi.nextFeature(f): + fid = vl.fields().lookupField('fid') + if fid == 1: + value = vl.fields().lookupField('json_content') + self.assertIsInstance(f.attributes()[value], dict) + self.assertEqual(f.attributes()[value_idx], {'foo': 'bar'}) + break + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/provider/test_json.gpkg b/tests/testdata/provider/test_json.gpkg new file mode 100644 index 0000000000000000000000000000000000000000..b2b280dd34c4d5ae7950b2fa924f0ab1c235f700 GIT binary patch literal 147456 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 Pdl>D}I*==1)B67aWCVI| literal 0 HcmV?d00001 From 8f5a732055992fcf090e8ceb0bc41f7eff8034ca Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 11 Dec 2018 17:40:21 +0100 Subject: [PATCH 015/266] basic writing json gpkg --- src/core/qgsogrutils.cpp | 5 +++++ src/providers/ogr/qgsogrprovider.cpp | 31 +++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index 9b586ad2ea72..f3c8e81a11c3 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -152,6 +152,11 @@ QgsFields QgsOgrUtils::readOgrFields( OGRFeatureH ogrFet, QTextCodec *encoding ) varType = QVariant::DateTime; break; case OFTString: + if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) + varType = QVariant::Map; + else + varType = QVariant::String; + break; default: varType = QVariant::String; // other unsupported, leave it as a string } diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index dddc22cd54ea..41cdb2ac73d7 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -155,6 +155,12 @@ bool QgsOgrProvider::convertField( QgsField &field, const QTextCodec &encoding ) ogrType = OFTDateTime; break; + //not sure if needed, but for consistency + case QVariant::Map: + ogrType = OFTString; + ogrSubType = OFSTJSON; + break; + default: return false; } @@ -1523,6 +1529,10 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) break; case OFTString: + if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) + { + QgsDebugMsgLevel( QStringLiteral( "Does it need to be decoded or something DAVE? JSON Stuff" ), 3 ); + } QgsDebugMsgLevel( QStringLiteral( "Writing string attribute %1 with %2, encoding %3" ) .arg( qgisAttId ) .arg( attrVal.toString(), @@ -1647,7 +1657,9 @@ bool QgsOgrProvider::addAttributeOGRLevel( const QgsField &field, bool &ignoreEr case QVariant::ByteArray: type = OFTBinary; break; - + case QVariant::Map: + type = OFTString; + break; default: pushError( tr( "type %1 for field %2 not found" ).arg( field.typeName(), field.name() ) ); ignoreErrorOut = true; @@ -1666,7 +1678,9 @@ bool QgsOgrProvider::addAttributeOGRLevel( const QgsField &field, bool &ignoreEr case QVariant::Bool: OGR_Fld_SetSubType( fielddefn.get(), OFSTBoolean ); break; - + case QVariant::Map: + OGR_Fld_SetSubType( fielddefn.get(), OFSTJSON ); + break; default: break; } @@ -2094,8 +2108,19 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ 0 ); break; case OFTString: - OGR_F_SetFieldString( of.get(), f, textEncoding()->fromUnicode( it2->toString() ).constData() ); + { + QString stringValue; + if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) + { + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toMap() ).toJson().data() ); + } + else + { + stringValue = it2->toString(); + } + OGR_F_SetFieldString( of.get(), f, textEncoding()->fromUnicode( stringValue ).constData() ); break; + } case OFTBinary: { From 7dbd7a11000c9446dcb5895806474477bd8ebcc4 Mon Sep 17 00:00:00 2001 From: signedav Date: Mon, 17 Dec 2018 16:26:06 +0100 Subject: [PATCH 016/266] basic tests for json writing --- .../editorwidgets/qgslistwidgetfactory.cpp | 2 +- src/providers/ogr/qgsogrprovider.cpp | 36 ++++++++++++++----- tests/src/python/test_provider_ogr_gpkg.py | 25 +++++++++---- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/gui/editorwidgets/qgslistwidgetfactory.cpp b/src/gui/editorwidgets/qgslistwidgetfactory.cpp index 5cddaa97a582..e55538882bef 100644 --- a/src/gui/editorwidgets/qgslistwidgetfactory.cpp +++ b/src/gui/editorwidgets/qgslistwidgetfactory.cpp @@ -44,5 +44,5 @@ QgsEditorConfigWidget *QgsListWidgetFactory::configWidget( QgsVectorLayer *vl, i unsigned int QgsListWidgetFactory::fieldScore( const QgsVectorLayer *vl, int fieldIdx ) const { const QgsField field = vl->fields().field( fieldIdx ); - return ( field.type() == QVariant::List || field.type() == QVariant::StringList || field.typeName() == QLatin1String( "json" ) || field.typeName() == QLatin1String( "jsonb" ) ) && field.subType() != QVariant::Invalid ? 20 : 0; + return ( field.type() == QVariant::List || field.type() == QVariant::StringList || field.typeName() == QLatin1String( "json" ) || field.typeName() == QLatin1String( "jsonb" ) || field.typeName() == QLatin1String( "JSON" ) ) && field.subType() != QVariant::Invalid ? 20 : 0; } diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index 41cdb2ac73d7..eab8db006e33 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -1016,7 +1016,7 @@ void QgsOgrProvider::loadFields() OGRFieldSubType ogrSubType = OFSTNone; QVariant::Type varType; - QVariant::Type varSubType; + QVariant::Type varSubType = QVariant::Invalid; switch ( ogrType ) { case OFTInteger: @@ -1529,17 +1529,29 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) break; case OFTString: - if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) + { + QString stringValue; + /* + * if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) { - QgsDebugMsgLevel( QStringLiteral( "Does it need to be decoded or something DAVE? JSON Stuff" ), 3 ); + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toMap() ).toJson().data() ); + if( stringValue == QStringLiteral( "{\n}\n" ) ) + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toList() ).toJson().data() ); + if( stringValue == QStringLiteral( "[\n]\n" ) ) + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toString() ).toJson().data() ); } + else + { + */ stringValue = attrVal.toString(); + //} + QgsDebugMsgLevel( QStringLiteral( "Writing string attribute %1 with %2, encoding %3" ) .arg( qgisAttId ) .arg( attrVal.toString(), textEncoding()->name().data() ), 3 ); - OGR_F_SetFieldString( feature.get(), ogrAttId, textEncoding()->fromUnicode( attrVal.toString() ).constData() ); + OGR_F_SetFieldString( feature.get(), ogrAttId, textEncoding()->fromUnicode( stringValue ).constData() ); break; - + } case OFTBinary: { const QByteArray ba = attrVal.toByteArray(); @@ -2110,14 +2122,20 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ case OFTString: { QString stringValue; - if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) + /* + * if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) { stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toMap() ).toJson().data() ); + if( stringValue == QStringLiteral( "{\n}\n" ) ) + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toList() ).toJson().data() ); + if( stringValue == QStringLiteral( "[\n]\n" ) ) + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toString() ).toJson().data() ); } else - { - stringValue = it2->toString(); - } + {*/ + stringValue = it2->toString(); + //} + OGR_F_SetFieldString( of.get(), f, textEncoding()->fromUnicode( stringValue ).constData() ); break; } diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 7cddbf0e23c8..2c9ae9c08bc7 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -1244,14 +1244,25 @@ def testJson(self): fi = vl.getFeatures(QgsFeatureRequest()) f = QgsFeature() - #test dict value + #test reading dict value from attribute while fi.nextFeature(f): - fid = vl.fields().lookupField('fid') - if fid == 1: - value = vl.fields().lookupField('json_content') - self.assertIsInstance(f.attributes()[value], dict) - self.assertEqual(f.attributes()[value_idx], {'foo': 'bar'}) - break + if f['fid'] == 1: + self.assertIsInstance(f['json_content'], dict) + self.assertEqual(f['json_content'], {'foo': 'bar'}) + #test changing dict value in attribute + f['json_content'] = {'foo': 'baz'} + self.assertEqual(f['json_content'], {'foo': 'baz'}) + #test changint dict to list + f['json_content'] = ['eins', 'zwei', 'drei'] + self.assertEqual(f['json_content'], ['eins', 'zwei', 'drei']) + #test changing list value in attribute + f['json_content'] = ['eins', 'zwei', 'drei', 4] + self.assertEqual(f['json_content'], ['eins', 'zwei', 'drei', 4]) + + #test adding attribute with list value + #test changing to invalid value in attribute + #test changing list ot string + #test reading string if __name__ == '__main__': From de12239b7ef0f8570089063c6965b05b09f122e4 Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 18 Dec 2018 09:33:07 +0100 Subject: [PATCH 017/266] adding and removing json attributes --- src/app/qgsaddattrdialog.cpp | 5 +++- src/providers/ogr/qgsogrprovider.cpp | 30 +++++++++---------- tests/src/python/test_provider_ogr_gpkg.py | 34 +++++++++++++++++++--- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/app/qgsaddattrdialog.cpp b/src/app/qgsaddattrdialog.cpp index eaf615e5504f..141e5fe0f4ac 100644 --- a/src/app/qgsaddattrdialog.cpp +++ b/src/app/qgsaddattrdialog.cpp @@ -111,6 +111,7 @@ void QgsAddAttrDialog::accept() QgsField QgsAddAttrDialog::field() const { + QgsDebugMsg( QStringLiteral( "idx:%1 name:%2 type:%3 typeName:%4 length:%5 prec:%6 comment:%7" ) .arg( mTypeBox->currentIndex() ) .arg( mNameEdit->text() ) @@ -126,5 +127,7 @@ QgsField QgsAddAttrDialog::field() const mTypeBox->currentData( Qt::UserRole + 1 ).toString(), mLength->value(), mPrec->value(), - mCommentEdit->text() ); + mCommentEdit->text(), + ( QVariant::Type ) mTypeBox->currentData( Qt::UserRole ).toInt() == QVariant::Map ? QVariant::String : QVariant::Invalid + ); } diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index eab8db006e33..b8be5da3cd01 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -477,7 +477,7 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio << QgsVectorDataProvider::NativeType( tr( "Whole number (integer 64 bit)" ), QStringLiteral( "integer64" ), QVariant::LongLong, 0, nMaxInt64Len ) << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "double" ), QVariant::Double, 0, nMaxDoubleLen, 0, nMaxDoublePrec ) << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 65535 ) - << QgsVectorDataProvider::NativeType( tr( "Map (json)" ), QStringLiteral( "json" ), QVariant::Map, -1, -1, -1, -1, QVariant::String ); + << QgsVectorDataProvider::NativeType( tr( "Map (JSON)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 65535, 0, 0, QVariant::String ); bool supportsDate = true; bool supportsTime = mGDALDriverName != QLatin1String( "ESRI Shapefile" ) && mGDALDriverName != QLatin1String( "GPKG" ); @@ -1531,19 +1531,18 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) case OFTString: { QString stringValue; - /* - * if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) + if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) { stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toMap() ).toJson().data() ); - if( stringValue == QStringLiteral( "{\n}\n" ) ) + if ( stringValue == QStringLiteral( "{\n}\n" ) ) stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toList() ).toJson().data() ); - if( stringValue == QStringLiteral( "[\n]\n" ) ) - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toString() ).toJson().data() ); + if ( stringValue == QStringLiteral( "[\n]\n" ) ) + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toString() ).toJson().data() ); } else { - */ stringValue = attrVal.toString(); - //} + stringValue = attrVal.toString(); + } QgsDebugMsgLevel( QStringLiteral( "Writing string attribute %1 with %2, encoding %3" ) .arg( qgisAttId ) @@ -2122,19 +2121,18 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ case OFTString: { QString stringValue; - /* - * if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) + if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) { stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toMap() ).toJson().data() ); - if( stringValue == QStringLiteral( "{\n}\n" ) ) + if ( stringValue == QStringLiteral( "{\n}\n" ) ) stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toList() ).toJson().data() ); - if( stringValue == QStringLiteral( "[\n]\n" ) ) - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toString() ).toJson().data() ); + if ( stringValue == QStringLiteral( "[\n]\n" ) ) + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toString() ).toJson().data() ); } else - {*/ - stringValue = it2->toString(); - //} + { + stringValue = it2->toString(); + } OGR_F_SetFieldString( of.get(), f, textEncoding()->fromUnicode( stringValue ).constData() ); break; diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 2c9ae9c08bc7..4438921cb26c 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -1258,11 +1258,37 @@ def testJson(self): #test changing list value in attribute f['json_content'] = ['eins', 'zwei', 'drei', 4] self.assertEqual(f['json_content'], ['eins', 'zwei', 'drei', 4]) + #test chaning to complex json structure + f['json_content'] = {'name': 'Lily', 'age': '0', 'cars': {'car1': ['fiat tipo', 'fiat punto', 'davoser schlitten'], 'car2': 'bobbycar', 'car3': 'tesla'}} + self.assertEqual(f['json_content'], {'name': 'Lily', 'age': '0', 'cars': {'car1': ['fiat tipo', 'fiat punto', 'davoser schlitten'], 'car2': 'bobbycar', 'car3': 'tesla'}}) - #test adding attribute with list value - #test changing to invalid value in attribute - #test changing list ot string - #test reading string + #test adding attribute + vl.startEditing() + self.assertTrue(vl.addAttribute(QgsField('json_content2', QVariant.Map, "JSON", 60, 0, 'no comment', QVariant.String))) + self.assertTrue(vl.commitChanges()) + + vl.startEditing() + self.assertTrue(vl.addAttribute(QgsField('json_content3', QVariant.Map, "JSON", 60, 0, 'no comment', QVariant.String))) + self.assertTrue(vl.commitChanges()) + + #test setting values to new attributes + while fi.nextFeature(f): + if f['fid'] == 2: + f['json_content'] = {'uno': 'foo'} + f['json_content2'] = ['uno', 'due', 'tre'] + f['json_content3'] = {'uno': ['uno', 'due', 'tre']} + self.assertEqual(f['json_content'], {'foo': 'baz'}) + self.assertEqual(f['json_content2'], ['uno', 'due', 'tre']) + self.assertEqual(f['json_content3'], {'uno': ['uno', 'due', 'tre']}) + + #test deleting attribute + vl.startEditing() + self.assertTrue(vl.deleteAttribute(vl.fields().indexFromName('json_content3'))) + self.assertTrue(vl.commitChanges()) + + #test if index of existent field is not -1 and the one of the deleted is -1 + self.assertNotEqual(vl.fields().indexFromName('json_content2'), -1) + self.assertEqual(vl.fields().indexFromName('json_content3'), -1) if __name__ == '__main__': From 40c6896fb976c8dc8ed1b5ec7eeef684f0534243 Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 18 Dec 2018 17:58:27 +0100 Subject: [PATCH 018/266] check for GDAL Version and exclude by ifdef --- src/core/qgsogrutils.cpp | 2 ++ src/providers/ogr/qgsogrprovider.cpp | 19 +++++++++++++++---- tests/src/python/test_provider_ogr_gpkg.py | 3 +++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index f3c8e81a11c3..58bc82a042fc 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -152,11 +152,13 @@ QgsFields QgsOgrUtils::readOgrFields( OGRFeatureH ogrFet, QTextCodec *encoding ) varType = QVariant::DateTime; break; case OFTString: +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) varType = QVariant::Map; else varType = QVariant::String; break; +#endif default: varType = QVariant::String; // other unsupported, leave it as a string } diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index b8be5da3cd01..c2a20fc1063f 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -155,12 +155,12 @@ bool QgsOgrProvider::convertField( QgsField &field, const QTextCodec &encoding ) ogrType = OFTDateTime; break; - //not sure if needed, but for consistency +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) case QVariant::Map: ogrType = OFTString; ogrSubType = OFSTJSON; break; - +#endif default: return false; } @@ -1049,6 +1049,7 @@ void QgsOgrProvider::loadFields() break; case OFTString: +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) { ogrSubType = OFSTJSON; @@ -1061,6 +1062,7 @@ void QgsOgrProvider::loadFields() varType = QVariant::String; } break; +#endif default: varType = QVariant::String; // other unsupported, leave it as a string } @@ -1531,6 +1533,8 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) case OFTString: { QString stringValue; + +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) { stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toMap() ).toJson().data() ); @@ -1543,7 +1547,9 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) { stringValue = attrVal.toString(); } - +#else + stringValue = attrVal.toString(); +#endif QgsDebugMsgLevel( QStringLiteral( "Writing string attribute %1 with %2, encoding %3" ) .arg( qgisAttId ) .arg( attrVal.toString(), @@ -1689,9 +1695,11 @@ bool QgsOgrProvider::addAttributeOGRLevel( const QgsField &field, bool &ignoreEr case QVariant::Bool: OGR_Fld_SetSubType( fielddefn.get(), OFSTBoolean ); break; +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) case QVariant::Map: OGR_Fld_SetSubType( fielddefn.get(), OFSTJSON ); break; +#endif default: break; } @@ -2121,6 +2129,7 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ case OFTString: { QString stringValue; +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) { stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toMap() ).toJson().data() ); @@ -2133,7 +2142,9 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ { stringValue = it2->toString(); } - +#else + stringValue = it2->toString(); +#endif OGR_F_SetFieldString( of.get(), f, textEncoding()->fromUnicode( stringValue ).constData() ); break; } diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 4438921cb26c..1ee230e9c763 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -1231,6 +1231,9 @@ def testTransaction(self): del vl2_external def testJson(self): + if int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 4, 0): + return + tmpfile = os.path.join(self.basetestpath, 'test_json.gpkg') testdata_path = unitTestDataPath('provider') shutil.copy(os.path.join(unitTestDataPath('provider'), 'test_json.gpkg'), tmpfile) From 35c0d24a43036cbd1388b5329a9dd58327428ae8 Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 18 Dec 2018 18:37:51 +0100 Subject: [PATCH 019/266] store plain if not valid --- src/providers/ogr/qgsogrprovider.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index c2a20fc1063f..bca78366fc6f 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -477,7 +477,7 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio << QgsVectorDataProvider::NativeType( tr( "Whole number (integer 64 bit)" ), QStringLiteral( "integer64" ), QVariant::LongLong, 0, nMaxInt64Len ) << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "double" ), QVariant::Double, 0, nMaxDoubleLen, 0, nMaxDoublePrec ) << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 65535 ) - << QgsVectorDataProvider::NativeType( tr( "Map (JSON)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 65535, 0, 0, QVariant::String ); + << QgsVectorDataProvider::NativeType( tr( "Map (JSON)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 0, 0, 0, QVariant::String ); bool supportsDate = true; bool supportsTime = mGDALDriverName != QLatin1String( "ESRI Shapefile" ) && mGDALDriverName != QLatin1String( "GPKG" ); @@ -1540,8 +1540,8 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toMap() ).toJson().data() ); if ( stringValue == QStringLiteral( "{\n}\n" ) ) stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toList() ).toJson().data() ); - if ( stringValue == QStringLiteral( "[\n]\n" ) ) - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toString() ).toJson().data() ); + if ( stringValue == QStringLiteral( "[\n]\n" ) && !attrVal.toString().isEmpty() ) + stringValue = attrVal.toString(); } else { @@ -2135,8 +2135,8 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toMap() ).toJson().data() ); if ( stringValue == QStringLiteral( "{\n}\n" ) ) stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toList() ).toJson().data() ); - if ( stringValue == QStringLiteral( "[\n]\n" ) ) - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toString() ).toJson().data() ); + if ( stringValue == QStringLiteral( "[\n]\n" ) && !it2->toString().isEmpty() ) + stringValue = it2->toString(); } else { From 03222c829a053e026a1e8ac57520d54590a5a3db Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 18 Dec 2018 22:09:34 +0100 Subject: [PATCH 020/266] fix typo --- tests/src/python/test_provider_ogr_gpkg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 1ee230e9c763..d9502d7e5713 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -1261,7 +1261,7 @@ def testJson(self): #test changing list value in attribute f['json_content'] = ['eins', 'zwei', 'drei', 4] self.assertEqual(f['json_content'], ['eins', 'zwei', 'drei', 4]) - #test chaning to complex json structure + #test changing to complex json structure f['json_content'] = {'name': 'Lily', 'age': '0', 'cars': {'car1': ['fiat tipo', 'fiat punto', 'davoser schlitten'], 'car2': 'bobbycar', 'car3': 'tesla'}} self.assertEqual(f['json_content'], {'name': 'Lily', 'age': '0', 'cars': {'car1': ['fiat tipo', 'fiat punto', 'davoser schlitten'], 'car2': 'bobbycar', 'car3': 'tesla'}}) From 9ff514b9be2c238df132fd893b9ee0c8eb71f48c Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 20 Dec 2018 09:43:59 +0100 Subject: [PATCH 021/266] beautyfications and clean up --- src/app/qgsaddattrdialog.cpp | 2 +- src/core/qgsfield.cpp | 2 +- .../editorwidgets/qgslistwidgetfactory.cpp | 2 +- src/providers/ogr/qgsogrprovider.cpp | 35 +++++++++---------- src/providers/ogr/qgsogrprovider.h | 3 ++ 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/app/qgsaddattrdialog.cpp b/src/app/qgsaddattrdialog.cpp index 141e5fe0f4ac..1cffd4da39ba 100644 --- a/src/app/qgsaddattrdialog.cpp +++ b/src/app/qgsaddattrdialog.cpp @@ -128,6 +128,6 @@ QgsField QgsAddAttrDialog::field() const mLength->value(), mPrec->value(), mCommentEdit->text(), - ( QVariant::Type ) mTypeBox->currentData( Qt::UserRole ).toInt() == QVariant::Map ? QVariant::String : QVariant::Invalid + static_cast( mTypeBox->currentData( Qt::UserRole ).toInt() ) == QVariant::Map ? QVariant::String : QVariant::Invalid ); } diff --git a/src/core/qgsfield.cpp b/src/core/qgsfield.cpp index 14f8ceca42df..466d7ea34385 100644 --- a/src/core/qgsfield.cpp +++ b/src/core/qgsfield.cpp @@ -254,7 +254,7 @@ QString QgsField::displayString( const QVariant &v ) const if ( ok ) return QLocale().toString( converted ); } - else if ( d->typeName == QLatin1String( "json" ) || d->typeName == QLatin1String( "jsonb" ) || d->typeName == QLatin1String( "JSON" ) ) + else if ( d->typeName.compare( QLatin1String( "json" ), Qt::CaseInsensitive ) == 0 || d->typeName == QLatin1String( "jsonb" ) ) { QJsonDocument doc = QJsonDocument::fromVariant( v ); return QString::fromUtf8( doc.toJson().data() ); diff --git a/src/gui/editorwidgets/qgslistwidgetfactory.cpp b/src/gui/editorwidgets/qgslistwidgetfactory.cpp index e55538882bef..49261d32a352 100644 --- a/src/gui/editorwidgets/qgslistwidgetfactory.cpp +++ b/src/gui/editorwidgets/qgslistwidgetfactory.cpp @@ -44,5 +44,5 @@ QgsEditorConfigWidget *QgsListWidgetFactory::configWidget( QgsVectorLayer *vl, i unsigned int QgsListWidgetFactory::fieldScore( const QgsVectorLayer *vl, int fieldIdx ) const { const QgsField field = vl->fields().field( fieldIdx ); - return ( field.type() == QVariant::List || field.type() == QVariant::StringList || field.typeName() == QLatin1String( "json" ) || field.typeName() == QLatin1String( "jsonb" ) || field.typeName() == QLatin1String( "JSON" ) ) && field.subType() != QVariant::Invalid ? 20 : 0; + return ( field.type() == QVariant::List || field.type() == QVariant::StringList || field.typeName().compare( QLatin1String( "json" ), Qt::CaseInsensitive ) == 0 || field.typeName() == QLatin1String( "jsonb" ) ) && field.subType() != QVariant::Invalid ? 20 : 0; } diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index bca78366fc6f..4f561b54e19a 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -476,8 +476,10 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio << QgsVectorDataProvider::NativeType( tr( "Whole number (integer)" ), QStringLiteral( "integer" ), QVariant::Int, 0, nMaxIntLen ) << QgsVectorDataProvider::NativeType( tr( "Whole number (integer 64 bit)" ), QStringLiteral( "integer64" ), QVariant::LongLong, 0, nMaxInt64Len ) << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "double" ), QVariant::Double, 0, nMaxDoubleLen, 0, nMaxDoublePrec ) - << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 65535 ) - << QgsVectorDataProvider::NativeType( tr( "Map (JSON)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 0, 0, 0, QVariant::String ); + << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 65535 ); + + if ( mGDALDriverName == QLatin1String( "GPKG" ) ) + nativeTypes << QgsVectorDataProvider::NativeType( tr( "Map (JSON)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 0, 0, 0, QVariant::String ); bool supportsDate = true; bool supportsTime = mGDALDriverName != QLatin1String( "ESRI Shapefile" ) && mGDALDriverName != QLatin1String( "GPKG" ); @@ -1053,7 +1055,6 @@ void QgsOgrProvider::loadFields() if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) { ogrSubType = OFSTJSON; - QgsDebugMsg( QStringLiteral( "JSON Field found" ) ); varType = QVariant::Map; varSubType = QVariant::String; } @@ -1407,6 +1408,16 @@ OGRGeometryH QgsOgrProvider::ConvertGeometryIfNecessary( OGRGeometryH hGeom ) return OGR_G_ForceTo( hGeom, layerGeomType, nullptr ); } +QString QgsOgrProvider::jsonStringValue( const QVariant &value ) +{ + QString stringValue = QString::fromUtf8( QJsonDocument::fromVariant( value.toMap() ).toJson().data() ); + if ( stringValue == QStringLiteral( "{\n}\n" ) ) + stringValue = QString::fromUtf8( QJsonDocument::fromVariant( value.toList() ).toJson().data() ); + if ( stringValue == QStringLiteral( "[\n]\n" ) && !value.toString().isEmpty() ) + stringValue = value.toString(); + return stringValue; +} + bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) { bool returnValue = true; @@ -1536,13 +1547,7 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) - { - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toMap() ).toJson().data() ); - if ( stringValue == QStringLiteral( "{\n}\n" ) ) - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( attrVal.toList() ).toJson().data() ); - if ( stringValue == QStringLiteral( "[\n]\n" ) && !attrVal.toString().isEmpty() ) - stringValue = attrVal.toString(); - } + stringValue = jsonStringValue( attrVal ); else { stringValue = attrVal.toString(); @@ -2131,17 +2136,9 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ QString stringValue; #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) - { - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toMap() ).toJson().data() ); - if ( stringValue == QStringLiteral( "{\n}\n" ) ) - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( it2->toList() ).toJson().data() ); - if ( stringValue == QStringLiteral( "[\n]\n" ) && !it2->toString().isEmpty() ) - stringValue = it2->toString(); - } + stringValue = jsonStringValue( it2.value() ); else - { stringValue = it2->toString(); - } #else stringValue = it2->toString(); #endif diff --git a/src/providers/ogr/qgsogrprovider.h b/src/providers/ogr/qgsogrprovider.h index 92b851727e71..a5c7f148dd1a 100644 --- a/src/providers/ogr/qgsogrprovider.h +++ b/src/providers/ogr/qgsogrprovider.h @@ -284,6 +284,9 @@ class QgsOgrProvider : public QgsVectorDataProvider mutable QStringList mSubLayerList; + //! convert from json QVariant to QString + QString jsonStringValue( const QVariant &value ); + bool addFeaturePrivate( QgsFeature &f, QgsFeatureSink::Flags flags ); //! Deletes one feature bool deleteFeature( QgsFeatureId id ); From ebb25e6b493a969e6a6590d1f27965bdff61d73c Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 20 Dec 2018 10:34:58 +0100 Subject: [PATCH 022/266] improve value conversion to json-string --- .../editorwidgets/qgslistwidgetfactory.cpp | 2 +- src/providers/ogr/qgsogrprovider.cpp | 21 +++++++++++-------- src/providers/ogr/qgsogrprovider.h | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/gui/editorwidgets/qgslistwidgetfactory.cpp b/src/gui/editorwidgets/qgslistwidgetfactory.cpp index 49261d32a352..c7a4c68e1973 100644 --- a/src/gui/editorwidgets/qgslistwidgetfactory.cpp +++ b/src/gui/editorwidgets/qgslistwidgetfactory.cpp @@ -44,5 +44,5 @@ QgsEditorConfigWidget *QgsListWidgetFactory::configWidget( QgsVectorLayer *vl, i unsigned int QgsListWidgetFactory::fieldScore( const QgsVectorLayer *vl, int fieldIdx ) const { const QgsField field = vl->fields().field( fieldIdx ); - return ( field.type() == QVariant::List || field.type() == QVariant::StringList || field.typeName().compare( QLatin1String( "json" ), Qt::CaseInsensitive ) == 0 || field.typeName() == QLatin1String( "jsonb" ) ) && field.subType() != QVariant::Invalid ? 20 : 0; + return ( field.type() == QVariant::List || field.type() == QVariant::StringList || QVariant::Map ) && field.subType() != QVariant::Invalid ? 20 : 0; } diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index 4f561b54e19a..d0eef19f9509 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -1408,14 +1408,17 @@ OGRGeometryH QgsOgrProvider::ConvertGeometryIfNecessary( OGRGeometryH hGeom ) return OGR_G_ForceTo( hGeom, layerGeomType, nullptr ); } -QString QgsOgrProvider::jsonStringValue( const QVariant &value ) +QString QgsOgrProvider::jsonStringValue( const QVariant &value, const QVariant::Type &type ) { - QString stringValue = QString::fromUtf8( QJsonDocument::fromVariant( value.toMap() ).toJson().data() ); - if ( stringValue == QStringLiteral( "{\n}\n" ) ) - stringValue = QString::fromUtf8( QJsonDocument::fromVariant( value.toList() ).toJson().data() ); - if ( stringValue == QStringLiteral( "[\n]\n" ) && !value.toString().isEmpty() ) - stringValue = value.toString(); - return stringValue; + switch ( type ) + { + case QVariant::Map: + return QString::fromUtf8( QJsonDocument::fromVariant( value.toMap() ).toJson().data() ); + case QVariant::List: + return QString::fromUtf8( QJsonDocument::fromVariant( value.toList() ).toJson().data() ); + default: + return value.toString(); + } } bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) @@ -1547,7 +1550,7 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags ) #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON ) - stringValue = jsonStringValue( attrVal ); + stringValue = jsonStringValue( attrVal, attrVal.type() ); else { stringValue = attrVal.toString(); @@ -2136,7 +2139,7 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ QString stringValue; #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0) if ( OGR_Fld_GetSubType( fd ) == OFSTJSON ) - stringValue = jsonStringValue( it2.value() ); + stringValue = jsonStringValue( it2.value(), it2->type() ); else stringValue = it2->toString(); #else diff --git a/src/providers/ogr/qgsogrprovider.h b/src/providers/ogr/qgsogrprovider.h index a5c7f148dd1a..0a4c22f634b4 100644 --- a/src/providers/ogr/qgsogrprovider.h +++ b/src/providers/ogr/qgsogrprovider.h @@ -284,8 +284,8 @@ class QgsOgrProvider : public QgsVectorDataProvider mutable QStringList mSubLayerList; - //! convert from json QVariant to QString - QString jsonStringValue( const QVariant &value ); + //! converts \a value from json QVariant to QString regarding to \a type + QString jsonStringValue( const QVariant &value, const QVariant::Type &type ); bool addFeaturePrivate( QgsFeature &f, QgsFeatureSink::Flags flags ); //! Deletes one feature From a62867db5b3e3499c0105fe65295471901e2b867 Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 20 Dec 2018 10:51:07 +0100 Subject: [PATCH 023/266] better labeling of json native type (postgres and gpkg) --- src/providers/ogr/qgsogrprovider.cpp | 2 +- src/providers/postgres/qgspostgresprovider.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index d0eef19f9509..a8c39f8b6618 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -479,7 +479,7 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 65535 ); if ( mGDALDriverName == QLatin1String( "GPKG" ) ) - nativeTypes << QgsVectorDataProvider::NativeType( tr( "Map (JSON)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 0, 0, 0, QVariant::String ); + nativeTypes << QgsVectorDataProvider::NativeType( tr( "JSON (string)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 0, 0, 0, QVariant::String ); bool supportsDate = true; bool supportsTime = mGDALDriverName != QLatin1String( "ESRI Shapefile" ) && mGDALDriverName != QLatin1String( "GPKG" ); diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 247d53aee16d..161e52916524 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -243,11 +243,11 @@ QgsPostgresProvider::QgsPostgresProvider( QString const &uri, const ProviderOpti if ( connectionRO()->pgVersion() >= 90200 ) { - nativeTypes << QgsVectorDataProvider::NativeType( tr( "Map (json)" ), QStringLiteral( "json" ), QVariant::Map, -1, -1, -1, -1, QVariant::String ); + nativeTypes << QgsVectorDataProvider::NativeType( tr( "JSON (json)" ), QStringLiteral( "json" ), QVariant::Map, -1, -1, -1, -1, QVariant::String ); if ( connectionRO()->pgVersion() >= 90400 ) { - nativeTypes << QgsVectorDataProvider::NativeType( tr( "Map (jsonb)" ), QStringLiteral( "jsonb" ), QVariant::Map, -1, -1, -1, -1, QVariant::String ); + nativeTypes << QgsVectorDataProvider::NativeType( tr( "JSON (jsonb)" ), QStringLiteral( "jsonb" ), QVariant::Map, -1, -1, -1, -1, QVariant::String ); } } setNativeTypes( nativeTypes ); From fea58316a42e57c4a20c4b4c657acc4934c4a4bd Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Thu, 20 Dec 2018 09:23:14 +0100 Subject: [PATCH 024/266] New expression function sqlite_fetch_and_increment [FEATURE] SQlite default values can only be applied on insert and not prefetched. This makes it impossible to acquire an incremented primary key via AUTO_INCREMENT before creating the row in the database. Sidenote: with postgres, this works via the option "evaluate default values". When adding new features with relations, it's really nice to be able to already add children for a parent, while the parents form is still open and hence the parent feature uncommitted. To get around this limitation, this function can be used to manage an incrementing value (or multiple sequences) in a separate table on sqlite based formats like gpkg. --- src/core/expression/qgsexpressionfunction.cpp | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 51f142500492..9e0962da9587 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -47,6 +47,7 @@ #include "qgsfieldformatter.h" #include "qgsvectorlayerfeatureiterator.h" #include "qgsproviderregistry.h" +#include "sqlite3.h" const QString QgsExpressionFunction::helpText() const { @@ -1360,6 +1361,113 @@ static QVariant fcnNumSelected( const QVariantList &values, const QgsExpressionC return layer->selectedFeatureCount(); } +template +T getParameterValue( const QString &name, const QVariantList &values, const QgsExpressionNodeFunction *nodeFunction ) +{ + T result; + QgsExpressionFunction *fd = QgsExpression::Functions().value( nodeFunction->fnIndex() ); + if ( fd ) + { + const auto ¶ms = fd->parameters(); + int idx = 0; + for ( const auto ¶m : params ) + { + if ( param.name() == name ) + { + return values.at( idx ).value(); + } + ++idx; + } + } + + return QVariant().value(); +}; + +static QVariant fcnSqliteFetchAndIncrement( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + const QString database = values.at( 0 ).toString(); + const QString table = values.at( 1 ).toString(); + const QString idColumn = values.at( 2 ).toString(); + const QString filterAttribute = values.at( 3 ).toString(); + const QVariant filterValue = values.at( 4 ).toString(); + const QVariantMap defaultValues = values.at( 5 ).toMap(); + + + // read from database + sqlite3_database_unique_ptr sqliteDb; + sqlite3_statement_unique_ptr sqliteStatement; + + if ( sqliteDb.open_v2( database, SQLITE_OPEN_READWRITE, nullptr ) != SQLITE_OK ) + { + parent->setEvalErrorString( QObject::tr( "Could not open sqlite database %1. Error %2. " ).arg( database, sqliteDb.errorMessage() ) ); + return QVariant(); + } + + QString currentValSql; + currentValSql = QStringLiteral( "SELECT %1 FROM %2" ).arg( QgsSqliteUtils::quotedIdentifier( idColumn ), QgsSqliteUtils::quotedIdentifier( table ) ); + if ( !filterAttribute.isNull() ) + { + currentValSql += QStringLiteral( " WHERE %1 = %2" ).arg( QgsSqliteUtils::quotedIdentifier( filterAttribute ), QgsSqliteUtils::quotedValue( filterValue ) ); + } + + int result; + sqliteStatement = sqliteDb.prepare( currentValSql, result ); + if ( result == SQLITE_OK ) + { + qlonglong nextId = 0; + if ( sqliteStatement.step() == SQLITE_ROW ) + { + nextId = sqliteStatement.columnAsInt64( 0 ) + 1; + } + + QString upsertSql; + upsertSql = QStringLiteral( "INSERT OR REPLACE INTO %1" ).arg( QgsSqliteUtils::quotedIdentifier( table ) ); + QStringList cols; + QStringList vals; + cols << QgsSqliteUtils::quotedIdentifier( idColumn ); + vals << QgsSqliteUtils::quotedValue( nextId ); + + if ( !filterAttribute.isNull() ) + { + cols << QgsSqliteUtils::quotedIdentifier( filterAttribute ); + vals << QgsSqliteUtils::quotedValue( filterValue ); + } + + for ( QVariantMap::const_iterator iter = defaultValues.constBegin(); iter != defaultValues.constEnd(); ++iter ) + { + cols << QgsSqliteUtils::quotedIdentifier( iter.key() ); + vals << iter.value().toString(); + } + + upsertSql += QLatin1String( " (" ) + cols.join( ',' ) + ')'; + upsertSql += QLatin1String( " VALUES " ); + upsertSql += '(' + vals.join( ',' ) + ')'; + + sqliteStatement = sqliteDb.prepare( upsertSql, result ); + + if ( result == SQLITE_OK ) + { + result = sqliteStatement.step(); + if ( result == SQLITE_DONE ) + { + return nextId; + } + else + { + parent->setEvalErrorString( QStringLiteral( "Could not increment value: SQLite error: \"%1\" (%2)." ).arg( sqliteDb.errorMessage(), QString::number( result ) ) ); + return QVariant(); + } + } + else + { + parent->setEvalErrorString( QStringLiteral( "Could not increment value: SQLite error: \"%1\" (%2)." ).arg( sqliteDb.errorMessage(), QString::number( result ) ) ); + return QVariant(); + } + } + + return QVariant(); // really? +} + static QVariant fcnConcat( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QString concat; @@ -4916,6 +5024,20 @@ const QList &QgsExpression::Functions() QSet() ); + sFunctions + << new QgsStaticExpressionFunction( + QStringLiteral( "sqlite_fetch_and_increment" ), + QgsExpressionFunction::ParameterList() + << QgsExpressionFunction::Parameter( QStringLiteral( "database" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "table" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "id_field" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "filter_attribute" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "filter_value" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "default_values" ), true ), + fcnSqliteFetchAndIncrement, + QStringLiteral( "Record and Attributes" ) + ); + // **Fields and Values** functions QgsStaticExpressionFunction *representValueFunc = new QgsStaticExpressionFunction( QStringLiteral( "represent_value" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "attribute" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "field_name" ), true ), fcnRepresentValue, QStringLiteral( "Record and Attributes" ) ); From 5d8f6371e182b7eb53f3f372aee219f31724ae20 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Thu, 20 Dec 2018 10:43:37 +0100 Subject: [PATCH 025/266] Add test for sqlite_fetch_and_increment --- tests/src/core/testqgsexpression.cpp | 33 +++++++++++++++++++++++++++ tests/testdata/humanbeings.gpkg | Bin 0 -> 71680 bytes 2 files changed, 33 insertions(+) create mode 100644 tests/testdata/humanbeings.gpkg diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 37ccc53fd411..3f90093eae88 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1556,6 +1556,39 @@ class TestQgsExpression: public QObject } } + void test_sqliteFetchAndIncrement() + { + QTemporaryDir dir; + QString testGpkgName = QStringLiteral( "humanbeings.gpkg" ); + QFile::copy( QStringLiteral( TEST_DATA_DIR ) + '/' + testGpkgName, dir.filePath( testGpkgName ) ); + + QgsExpressionContext context; + QgsExpressionContextScope *scope = new QgsExpressionContextScope(); + scope->setVariable( QStringLiteral( "test_database" ), dir.filePath( testGpkgName ) ); + context << scope; + + // Test failing + QgsExpression exp1( QStringLiteral( "sqlite_fetch_and_increment('/path/does/not/exist', 'T_KEY_OBJECT', 'T_LastUniqueId')" ) ); + + exp1.evaluate( &context ); + QCOMPARE( exp1.hasEvalError(), true ); + const QString evalErrorString = exp1.evalErrorString(); + QVERIFY2( evalErrorString.contains( "/path/does/not/exist" ), QStringLiteral( "Path not found in %1" ).arg( evalErrorString ).toUtf8().constData() ); + QVERIFY2( evalErrorString.contains( "Error" ), QStringLiteral( "\"Error\" not found in %1" ).arg( evalErrorString ).toUtf8().constData() ); + + // Test incrementation logic + QgsExpression exp( QStringLiteral( "sqlite_fetch_and_increment(@test_database, 'T_KEY_OBJECT', 'T_LastUniqueId', 'T_Key', 'T_Id', map('T_LastChange','date(''now'')','T_CreateDate','date(''now'')','T_User','''me'''))" ) ); + QVariant res = exp.evaluate( &context ); + QCOMPARE( res.toInt(), 0 ); + + res = exp.evaluate( &context ); + if ( exp.hasEvalError() ) + qDebug() << exp.evalErrorString(); + QCOMPARE( exp.hasEvalError(), false ); + + QCOMPARE( res.toInt(), 1 ); + } + void aggregate_data() { QTest::addColumn( "string" ); diff --git a/tests/testdata/humanbeings.gpkg b/tests/testdata/humanbeings.gpkg new file mode 100644 index 0000000000000000000000000000000000000000..c3040f00687f265aa876631c51c3fbb3c91e9478 GIT binary patch literal 71680 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 Date: Thu, 20 Dec 2018 14:31:26 +0100 Subject: [PATCH 026/266] Add sqlite_fetch_and increment docs --- .../json/sqlite_fetch_and_increment | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 resources/function_help/json/sqlite_fetch_and_increment diff --git a/resources/function_help/json/sqlite_fetch_and_increment b/resources/function_help/json/sqlite_fetch_and_increment new file mode 100644 index 000000000000..823c3bd6ca6d --- /dev/null +++ b/resources/function_help/json/sqlite_fetch_and_increment @@ -0,0 +1,16 @@ +{ + "name": "sqlite_fetch_and_increment", + "type": "function", + "description": "Manage autoincrementing values in sqlite databases.

SQlite default values can only be applied on insert and not prefetched.

This makes it impossible to acquire an incremented primary key via AUTO_INCREMENT before creating the row in the database. Sidenote: with postgres, this works via the option "evaluate default values".

When adding new features with relations, it is really nice to be able to already add children for a parent, while the parents form is still open and hence the parent feature uncommitted.

To get around this limitation, this function can be used to manage sequence values in a separate table on sqlite based formats like gpkg.

The sequence table will be filtered for a sequence id (filter_attribute and filter_value) and the current value of the id_field will be incremented by 1 ond the incremented value returned.

If additional columns require values to be specified, the default_value map can be used for this purpose.

Note
This function modifies the target sqlite table. It is intended for usage with default value configurations for attributes.

", + "arguments": [ + {"arg":"database", "description":"Path to the sqlite file"}, + {"arg":"table", "description":"Name of the table that manages the sequences"}, + {"arg":"id_field", "description":"Name of the field that contains the current value"}, + {"arg":"filter_attribute", "description":"Name the field that contains a unique identifier for this sequence. Must have a UNIQUE index."}, + {"arg":"filter_value", "description":"Name of the sequence to use."}, + {"arg":"default_values", "description":"Map with default values for additional columns on the table. The values need to be fully quoted. Functions are allowed."} + ], + "examples": [ + { "expression":"sqlite_fetch_and_increment(layer_property(@layer, 'path'), 'sequence_table', 'last_unique_id', 'sequence_id', 'global', map('last_change','date(''now'')','user',@user_account_name))", "returns":"0"} + ] +} From fba4b25bc25e87aa6f3a45c64acd7fd243b30bfb Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Thu, 20 Dec 2018 17:16:24 +0100 Subject: [PATCH 027/266] fixes #20673 : Correct mapFromSource method when reordering columns --- .../qgsattributetablefiltermodel.cpp | 26 ++++++----- .../qgsattributetablefiltermodel.h | 1 + tests/src/app/testqgsattributetable.cpp | 44 +++++++++++++++++++ 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/gui/attributetable/qgsattributetablefiltermodel.cpp b/src/gui/attributetable/qgsattributetablefiltermodel.cpp index e3133790e670..61e0020a7419 100644 --- a/src/gui/attributetable/qgsattributetablefiltermodel.cpp +++ b/src/gui/attributetable/qgsattributetablefiltermodel.cpp @@ -247,14 +247,11 @@ void QgsAttributeTableFilterModel::setSelectedOnTop( bool selectedOnTop ) Qt::SortOrder order = sortOrder(); // set default sort values if they are not correctly set - if ( column < 0 ) - column = 0; - - if ( order != Qt::AscendingOrder && order != Qt::DescendingOrder ) - order = Qt::AscendingOrder; - - sort( column, order ); - invalidate(); + if ( column < 0 || ( order != Qt::AscendingOrder && order != Qt::DescendingOrder ) ) + { + sort( 0, Qt::AscendingOrder ); + invalidate(); + } } } @@ -382,7 +379,6 @@ void QgsAttributeTableFilterModel::selectionChanged() } else if ( mSelectedOnTop ) { - sort( sortColumn(), sortOrder() ); invalidate(); } } @@ -402,6 +398,14 @@ int QgsAttributeTableFilterModel::mapColumnToSource( int column ) const return mColumnMapping.at( column ); } +int QgsAttributeTableFilterModel::mapColumnFromSource( int column ) const +{ + if ( mColumnMapping.isEmpty() ) + return column; + else + return mColumnMapping.indexOf( column ); +} + void QgsAttributeTableFilterModel::generateListOfVisibleFeatures() { if ( !layer() ) @@ -528,7 +532,8 @@ QModelIndex QgsAttributeTableFilterModel::mapFromSource( const QModelIndex &sour if ( proxyIndex.column() < 0 ) return QModelIndex(); - int col = mapColumnToSource( proxyIndex.column() ); + int col = mapColumnFromSource( proxyIndex.column() ); + if ( col == -1 ) col = 0; @@ -544,4 +549,3 @@ Qt::ItemFlags QgsAttributeTableFilterModel::flags( const QModelIndex &index ) co QModelIndex source_index = mapToSource( index ); return masterModel()->flags( source_index ); } - diff --git a/src/gui/attributetable/qgsattributetablefiltermodel.h b/src/gui/attributetable/qgsattributetablefiltermodel.h index 83498119b36f..b5c541b91a36 100644 --- a/src/gui/attributetable/qgsattributetablefiltermodel.h +++ b/src/gui/attributetable/qgsattributetablefiltermodel.h @@ -275,6 +275,7 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, pub QgsAttributeTableConfig mConfig; QVector mColumnMapping; int mapColumnToSource( int column ) const; + int mapColumnFromSource( int column ) const; }; diff --git a/tests/src/app/testqgsattributetable.cpp b/tests/src/app/testqgsattributetable.cpp index 5dae6046918f..6969559a3d77 100644 --- a/tests/src/app/testqgsattributetable.cpp +++ b/tests/src/app/testqgsattributetable.cpp @@ -51,6 +51,7 @@ class TestQgsAttributeTable : public QObject void testNoGeom(); void testSelected(); void testSortByDisplayExpression(); + void testOrderColumn(); private: QgisApp *mQgisApp = nullptr; @@ -306,6 +307,49 @@ void TestQgsAttributeTable::testRegression15974() QCOMPARE( dlg->mMainView->filteredFeatureCount(), 3 ); } +void TestQgsAttributeTable::testOrderColumn() +{ + std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:int&field=col2:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); + QVERIFY( tempLayer->isValid() ); + + QgsFeature f1( tempLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( 0, 1 ); + f1.setAttribute( 1, 13 ); + f1.setAttribute( 2, 7 ); + QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 ) ); + + std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) ); + + // Issue https://issues.qgis.org/issues/20673 + // When we reorder column (last column becomes first column), and we select an entire row + // the currentIndex is no longer the first column, and consequently it breaks edition + + QgsAttributeTableConfig config = QgsAttributeTableConfig(); + config.update( tempLayer->dataProvider()->fields() ); + QVector columns = config.columns(); + + // move last column in first position + columns.move( 2, 0 ); + config.setColumns( columns ); + + dlg->mMainView->setAttributeTableConfig( config ); + + QgsAttributeTableFilterModel *filterModel = static_cast( dlg->mMainView->mTableView->model() ); + filterModel->sort( 0, Qt::AscendingOrder ); + + QModelIndex index = filterModel->mapToSource( filterModel->sourceModel()->index( 0, 0 ) ); + QCOMPARE( index.row(), 0 ); + QCOMPARE( index.column(), 2 ); + + index = filterModel->mapFromSource( filterModel->sourceModel()->index( 0, 0 ) ); + QCOMPARE( index.row(), 0 ); + QCOMPARE( index.column(), 1 ); + + qDebug() << filterModel->mapFromSource( filterModel->sourceModel()->index( 0, 0 ) ); + + // column 0 is indeed column 2 since we move it + QCOMPARE( filterModel->sortColumn(), 2 ); +} QGSTEST_MAIN( TestQgsAttributeTable ) #include "testqgsattributetable.moc" From ceb65a24b9a22b1dfc492c089cc82bb65c8292b9 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 09:07:04 +0100 Subject: [PATCH 028/266] Remove template class --- src/core/expression/qgsexpressionfunction.cpp | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 9e0962da9587..957c34ad7538 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -1361,28 +1361,6 @@ static QVariant fcnNumSelected( const QVariantList &values, const QgsExpressionC return layer->selectedFeatureCount(); } -template -T getParameterValue( const QString &name, const QVariantList &values, const QgsExpressionNodeFunction *nodeFunction ) -{ - T result; - QgsExpressionFunction *fd = QgsExpression::Functions().value( nodeFunction->fnIndex() ); - if ( fd ) - { - const auto ¶ms = fd->parameters(); - int idx = 0; - for ( const auto ¶m : params ) - { - if ( param.name() == name ) - { - return values.at( idx ).value(); - } - ++idx; - } - } - - return QVariant().value(); -}; - static QVariant fcnSqliteFetchAndIncrement( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { const QString database = values.at( 0 ).toString(); From e698ba877a7a9f1f24501ee4bef0cd6b0e7c7b8f Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 09:14:44 +0100 Subject: [PATCH 029/266] Fix JSON formatting --- resources/function_help/json/sqlite_fetch_and_increment | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/function_help/json/sqlite_fetch_and_increment b/resources/function_help/json/sqlite_fetch_and_increment index 823c3bd6ca6d..6ccf8badd957 100644 --- a/resources/function_help/json/sqlite_fetch_and_increment +++ b/resources/function_help/json/sqlite_fetch_and_increment @@ -1,7 +1,7 @@ { "name": "sqlite_fetch_and_increment", "type": "function", - "description": "Manage autoincrementing values in sqlite databases.

SQlite default values can only be applied on insert and not prefetched.

This makes it impossible to acquire an incremented primary key via AUTO_INCREMENT before creating the row in the database. Sidenote: with postgres, this works via the option "evaluate default values".

When adding new features with relations, it is really nice to be able to already add children for a parent, while the parents form is still open and hence the parent feature uncommitted.

To get around this limitation, this function can be used to manage sequence values in a separate table on sqlite based formats like gpkg.

The sequence table will be filtered for a sequence id (filter_attribute and filter_value) and the current value of the id_field will be incremented by 1 ond the incremented value returned.

If additional columns require values to be specified, the default_value map can be used for this purpose.

Note
This function modifies the target sqlite table. It is intended for usage with default value configurations for attributes.

", + "description": "Manage autoincrementing values in sqlite databases.

SQlite default values can only be applied on insert and not prefetched.

This makes it impossible to acquire an incremented primary key via AUTO_INCREMENT before creating the row in the database. Sidenote: with postgres, this works via the option evaluate default values.

When adding new features with relations, it is really nice to be able to already add children for a parent, while the parents form is still open and hence the parent feature uncommitted.

To get around this limitation, this function can be used to manage sequence values in a separate table on sqlite based formats like gpkg.

The sequence table will be filtered for a sequence id (filter_attribute and filter_value) and the current value of the id_field will be incremented by 1 ond the incremented value returned.

If additional columns require values to be specified, the default_value map can be used for this purpose.

Note
This function modifies the target sqlite table. It is intended for usage with default value configurations for attributes.

", "arguments": [ {"arg":"database", "description":"Path to the sqlite file"}, {"arg":"table", "description":"Name of the table that manages the sequences"}, From f4d7506e73fa70de0f5d1f429e4aa963964f5e41 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 21 Dec 2018 11:47:36 +0100 Subject: [PATCH 030/266] Processing in-place do not check validity when fixing geometries Fixes #20812 --- .../processing/gui/AlgorithmExecutor.py | 4 ++++ tests/src/python/test_qgsprocessinginplace.py | 22 +++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/python/plugins/processing/gui/AlgorithmExecutor.py b/python/plugins/processing/gui/AlgorithmExecutor.py index 3dfde908456b..48f6033cb564 100644 --- a/python/plugins/processing/gui/AlgorithmExecutor.py +++ b/python/plugins/processing/gui/AlgorithmExecutor.py @@ -94,6 +94,10 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None, raise_exc if context is None: context = dataobjects.createContext(feedback) + # Ugly hack: the only invalid policy option that makes sense for fixgeometries is to not check + if alg.name() in ('fixgeometries', ): + context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck) + active_layer = parameters['INPUT'] # Run some checks and prepare the layer for in-place execution by: diff --git a/tests/src/python/test_qgsprocessinginplace.py b/tests/src/python/test_qgsprocessinginplace.py index 5e1ee4796706..f0045c1521e2 100644 --- a/tests/src/python/test_qgsprocessinginplace.py +++ b/tests/src/python/test_qgsprocessinginplace.py @@ -12,6 +12,7 @@ # This will get replaced with a git SHA1 when you do a git archive __revision__ = '$Format:%H$' +import re from qgis.PyQt.QtCore import QCoreApplication, QVariant from qgis.core import ( QgsFeature, QgsGeometry, QgsSettings, QgsApplication, QgsMemoryProviderUtils, QgsWkbTypes, QgsField, QgsFields, QgsProcessingFeatureSourceDefinition, QgsProcessingContext, QgsProcessingFeedback, QgsCoordinateReferenceSystem, QgsProject, QgsProcessingException @@ -23,7 +24,7 @@ from qgis.testing import start_app, unittest from qgis.PyQt.QtTest import QSignalSpy from qgis.analysis import QgsNativeAlgorithms -from qgis.core import QgsVectorLayerUtils +from qgis.core import QgsVectorLayerUtils, QgsFeatureRequest start_app() @@ -349,7 +350,7 @@ def test_make_features_compatible_geometry(self): self.assertEqual(len(new_features), 1) self.assertEqual(new_features[0].geometry().asWkt(), '') - def _alg_tester(self, alg_name, input_layer, parameters): + def _alg_tester(self, alg_name, input_layer, parameters, invalid_geometry_policy=QgsFeatureRequest.GeometryNoCheck): alg = self.registry.createAlgorithmById(alg_name) @@ -363,6 +364,7 @@ def _alg_tester(self, alg_name, input_layer, parameters): self.assertEqual(input_layer.selectedFeatureIds(), [old_features[0].id()], alg_name) context = QgsProcessingContext() + context.setInvalidGeometryCheck(invalid_geometry_policy) context.setProject(QgsProject.instance()) feedback = ConsoleFeedBack() @@ -611,16 +613,16 @@ def test_fix_geometries(self): polygon_layer = self._make_layer('Polygon') self.assertTrue(polygon_layer.startEditing()) - f = QgsFeature(polygon_layer.fields()) - f.setAttributes([1]) + f1 = QgsFeature(polygon_layer.fields()) + f1.setAttributes([1]) # Flake! - f.setGeometry(QgsGeometry.fromWkt('POLYGON ((0 0, 2 2, 0 2, 2 0, 0 0))')) - self.assertTrue(f.isValid()) + f1.setGeometry(QgsGeometry.fromWkt('POLYGON ((0 0, 2 2, 0 2, 2 0, 0 0))')) + self.assertTrue(f1.isValid()) f2 = QgsFeature(polygon_layer.fields()) f2.setAttributes([1]) f2.setGeometry(QgsGeometry.fromWkt('POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))')) self.assertTrue(f2.isValid()) - self.assertTrue(polygon_layer.addFeatures([f, f2])) + self.assertTrue(polygon_layer.addFeatures([f1, f2])) polygon_layer.commitChanges() polygon_layer.rollBack() self.assertEqual(polygon_layer.featureCount(), 2) @@ -631,12 +633,14 @@ def test_fix_geometries(self): 'native:fixgeometries', polygon_layer, { - } + }, + QgsFeatureRequest.GeometrySkipInvalid ) self.assertEqual(polygon_layer.featureCount(), 3) - wkt1, wkt2, _ = [f.geometry().asWkt() for f in new_features] + wkt1, wkt2, wkt3 = [f.geometry().asWkt() for f in new_features] self.assertEqual(wkt1, 'Polygon ((0 0, 1 1, 2 0, 0 0))') self.assertEqual(wkt2, 'Polygon ((1 1, 0 2, 2 2, 1 1))') + self.assertEqual(re.sub(r'0000\d+', '', wkt3), 'Polygon ((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))') # Test with Z (interpolated) polygonz_layer = self._make_layer('PolygonZ') From 40fb2ce93d000d6e4b4c4f6ee2ecbc7ee9c94b67 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 21 Dec 2018 13:20:41 +0100 Subject: [PATCH 031/266] Drop the ugly hack in favor of sourceFlags --- python/plugins/processing/gui/AlgorithmExecutor.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/python/plugins/processing/gui/AlgorithmExecutor.py b/python/plugins/processing/gui/AlgorithmExecutor.py index 48f6033cb564..dcb0b8e9d472 100644 --- a/python/plugins/processing/gui/AlgorithmExecutor.py +++ b/python/plugins/processing/gui/AlgorithmExecutor.py @@ -34,6 +34,7 @@ QgsMessageLog, QgsProcessingException, QgsProcessingFeatureSourceDefinition, + QgsProcessingFeatureSource, QgsProcessingParameters, QgsProject, QgsFeatureRequest, @@ -94,9 +95,12 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None, raise_exc if context is None: context = dataobjects.createContext(feedback) - # Ugly hack: the only invalid policy option that makes sense for fixgeometries is to not check - if alg.name() in ('fixgeometries', ): - context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck) + # Only feature based algs have sourceFlags + try: + if alg.sourceFlags() & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks: + context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck) + except AttributeError: + pass active_layer = parameters['INPUT'] From 49d8060c30c6f7b005bfe3aedb2f9c0c7cce4459 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 14:05:28 +0100 Subject: [PATCH 032/266] Add sqlite3_database_unique_ptr::exec A handy shortcut to quickly run commands on a sqlite database. --- src/core/expression/qgsexpressionfunction.cpp | 17 ++++------------- src/core/qgssqliteutils.cpp | 15 +++++++++++++++ src/core/qgssqliteutils.h | 13 ++++++++++++- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 957c34ad7538..212a856ad3ab 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -1421,24 +1421,15 @@ static QVariant fcnSqliteFetchAndIncrement( const QVariantList &values, const Qg upsertSql += QLatin1String( " VALUES " ); upsertSql += '(' + vals.join( ',' ) + ')'; - sqliteStatement = sqliteDb.prepare( upsertSql, result ); - + QString errorMessage; + result = sqliteDb.exec( upsertSql, errorMessage ); if ( result == SQLITE_OK ) { - result = sqliteStatement.step(); - if ( result == SQLITE_DONE ) - { - return nextId; - } - else - { - parent->setEvalErrorString( QStringLiteral( "Could not increment value: SQLite error: \"%1\" (%2)." ).arg( sqliteDb.errorMessage(), QString::number( result ) ) ); - return QVariant(); - } + return nextId; } else { - parent->setEvalErrorString( QStringLiteral( "Could not increment value: SQLite error: \"%1\" (%2)." ).arg( sqliteDb.errorMessage(), QString::number( result ) ) ); + parent->setEvalErrorString( QStringLiteral( "Could not increment value: SQLite error: \"%1\" (%2)." ).arg( errorMessage, QString::number( result ) ) ); return QVariant(); } } diff --git a/src/core/qgssqliteutils.cpp b/src/core/qgssqliteutils.cpp index 8dbf97f58a14..3921f30df30f 100644 --- a/src/core/qgssqliteutils.cpp +++ b/src/core/qgssqliteutils.cpp @@ -92,6 +92,21 @@ sqlite3_statement_unique_ptr sqlite3_database_unique_ptr::prepare( const QString return s; } +int sqlite3_database_unique_ptr::exec( const QString &sql, QString &errorMessage ) const +{ + char *errMsg; + + int ret = sqlite3_exec( get(), sql.toUtf8(), nullptr, nullptr, &errMsg ); + + if ( errMsg ) + { + errorMessage = QString::fromUtf8( errMsg ); + sqlite3_free( errMsg ); + } + + return ret; +} + QString QgsSqliteUtils::quotedString( const QString &value ) { if ( value.isNull() ) diff --git a/src/core/qgssqliteutils.h b/src/core/qgssqliteutils.h index 18ccd5bcb741..92c1e5bb7c57 100644 --- a/src/core/qgssqliteutils.h +++ b/src/core/qgssqliteutils.h @@ -21,6 +21,8 @@ #define SIP_NO_FILE #include "qgis_core.h" +#include "qgis_sip.h" + #include #include @@ -135,8 +137,17 @@ class CORE_EXPORT sqlite3_database_unique_ptr : public std::unique_ptr< sqlite3, * Prepares a \a sql statement, returning the result. The \a resultCode * argument will be filled with the sqlite3 result code. */ - sqlite3_statement_unique_ptr prepare( const QString &sql, int &resultCode ) const; + sqlite3_statement_unique_ptr prepare( const QString &sql, int &resultCode SIP_OUT ) const; + /** + * Executes the \a sql command in the database. Multiple sql queries can be run within + * one single command. + * Errors are reported to \a errorMessage. + * Returns SQLITE_OK in case of success or an sqlite error code. + * + * \since QGIS 3.6 + */ + int exec( const QString &sql, QString &errorMessage SIP_OUT ) const; }; /** From b4bc0720304dffd8b9064a22ace122e29a4f8b29 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 14:06:53 +0100 Subject: [PATCH 033/266] Improve function help for sqlite_fetch_and_increment --- resources/function_help/json/sqlite_fetch_and_increment | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/function_help/json/sqlite_fetch_and_increment b/resources/function_help/json/sqlite_fetch_and_increment index 6ccf8badd957..1ded3beaa895 100644 --- a/resources/function_help/json/sqlite_fetch_and_increment +++ b/resources/function_help/json/sqlite_fetch_and_increment @@ -8,9 +8,9 @@ {"arg":"id_field", "description":"Name of the field that contains the current value"}, {"arg":"filter_attribute", "description":"Name the field that contains a unique identifier for this sequence. Must have a UNIQUE index."}, {"arg":"filter_value", "description":"Name of the sequence to use."}, - {"arg":"default_values", "description":"Map with default values for additional columns on the table. The values need to be fully quoted. Functions are allowed."} + {"arg":"default_values", "description":"Map with default values for additional columns on the table. The values need to be fully quoted. Functions are allowed.", "optional": true} ], "examples": [ - { "expression":"sqlite_fetch_and_increment(layer_property(@layer, 'path'), 'sequence_table', 'last_unique_id', 'sequence_id', 'global', map('last_change','date(''now'')','user',@user_account_name))", "returns":"0"} + { "expression":"sqlite_fetch_and_increment(layer_property(@layer, 'path'), 'sequence_table', 'last_unique_id', 'sequence_id', 'global', map('last_change','date(''now'')','user','''' || @user_account_name || ''''))", "returns":"0"} ] } From 36c42d2b97e5a0628de6ade98fde6a47b278c69a Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 14:07:24 +0100 Subject: [PATCH 034/266] Check for proper error reporting in sqlite_fetch_and_increment --- tests/src/core/testqgsexpression.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 3f90093eae88..5403b3de2de4 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1565,16 +1565,24 @@ class TestQgsExpression: public QObject QgsExpressionContext context; QgsExpressionContextScope *scope = new QgsExpressionContextScope(); scope->setVariable( QStringLiteral( "test_database" ), dir.filePath( testGpkgName ) ); + scope->setVariable( QStringLiteral( "username" ), "some_username" ); context << scope; - // Test failing - QgsExpression exp1( QStringLiteral( "sqlite_fetch_and_increment('/path/does/not/exist', 'T_KEY_OBJECT', 'T_LastUniqueId')" ) ); + // Test database file does not exist + QgsExpression exp1( QStringLiteral( "sqlite_fetch_and_increment('/path/does/not/exist', 'T_KEY_OBJECT', 'T_LastUniqueId', 'T_Key', 'T_Id')" ) ); exp1.evaluate( &context ); QCOMPARE( exp1.hasEvalError(), true ); - const QString evalErrorString = exp1.evalErrorString(); - QVERIFY2( evalErrorString.contains( "/path/does/not/exist" ), QStringLiteral( "Path not found in %1" ).arg( evalErrorString ).toUtf8().constData() ); - QVERIFY2( evalErrorString.contains( "Error" ), QStringLiteral( "\"Error\" not found in %1" ).arg( evalErrorString ).toUtf8().constData() ); + const QString evalErrorString1 = exp1.evalErrorString(); + QVERIFY2( evalErrorString1.contains( "/path/does/not/exist" ), QStringLiteral( "Path not found in %1" ).arg( evalErrorString1 ).toUtf8().constData() ); + QVERIFY2( evalErrorString1.contains( "Error" ), QStringLiteral( "\"Error\" not found in %1" ).arg( evalErrorString1 ).toUtf8().constData() ); + + // Test default values are not properly quoted + QgsExpression exp2( QStringLiteral( "sqlite_fetch_and_increment(@test_database, 'T_KEY_OBJECT', 'T_LastUniqueId', 'T_Key', 'T_Id', map('T_LastChange','date(''now'')','T_CreateDate','date(''now'')','T_User', @username))" ) ); + exp2.evaluate( &context ); + QCOMPARE( exp2.hasEvalError(), true ); + const QString evalErrorString2 = exp2.evalErrorString(); + QVERIFY2( evalErrorString2.contains( "some_username" ), QStringLiteral( "'some_username' not found in '%1'" ).arg( evalErrorString2 ).toUtf8().constData() ); // Test incrementation logic QgsExpression exp( QStringLiteral( "sqlite_fetch_and_increment(@test_database, 'T_KEY_OBJECT', 'T_LastUniqueId', 'T_Key', 'T_Id', map('T_LastChange','date(''now'')','T_CreateDate','date(''now'')','T_User','''me'''))" ) ); From 308b46e921b20a155e3be53e5c85eef0d1562b3c Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 14:21:05 +0100 Subject: [PATCH 035/266] Make geometry constraint configuration size fit the contents --- src/app/qgsvectorlayerproperties.cpp | 2 + src/ui/qgsvectorlayerpropertiesbase.ui | 55 ++++++++++++++++---------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/app/qgsvectorlayerproperties.cpp b/src/app/qgsvectorlayerproperties.cpp index 5175e842f84d..3a960720bc78 100644 --- a/src/app/qgsvectorlayerproperties.cpp +++ b/src/app/qgsvectorlayerproperties.cpp @@ -429,6 +429,7 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( geometryCheckLayout->addWidget( cb ); } mGeometryValidationGroupBox->setLayout( geometryCheckLayout ); + mGeometryValidationGroupBox->setVisible( !geometryCheckFactories.isEmpty() ); QLayout *topologyCheckLayout = new QVBoxLayout(); const QList topologyCheckFactories = QgsAnalysis::instance()->geometryCheckRegistry()->geometryCheckFactories( mLayer, QgsGeometryCheck::LayerCheck, QgsGeometryCheck::Flag::AvailableInValidation ); @@ -441,6 +442,7 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( topologyCheckLayout->addWidget( cb ); } mTopologyChecksGroupBox->setLayout( topologyCheckLayout ); + mTopologyChecksGroupBox->setVisible( !topologyCheckFactories.isEmpty() ); } else { diff --git a/src/ui/qgsvectorlayerpropertiesbase.ui b/src/ui/qgsvectorlayerpropertiesbase.ui index c9bcf7dda3e0..beffa2ea2bca 100644 --- a/src/ui/qgsvectorlayerpropertiesbase.ui +++ b/src/ui/qgsvectorlayerpropertiesbase.ui @@ -433,8 +433,8 @@ 0 0 - 325 - 389 + 315 + 403 @@ -907,8 +907,8 @@ border-radius: 2px; 0 0 - 104 - 102 + 185 + 112 @@ -1504,8 +1504,8 @@ border-radius: 2px; 0 0 - 685 - 346 + 734 + 372 @@ -1961,8 +1961,8 @@ border-radius: 2px; 0 0 - 340 - 630 + 378 + 678 @@ -2367,8 +2367,8 @@ border-radius: 2px; 0 0 - 651 - 804 + 658 + 800 @@ -2377,8 +2377,15 @@ border-radius: 2px; Automatic Fixes - - + + + + + Remove duplicate nodes + + + + <html><head/><body><p>The geometry precision defines the maximum precision to of geometry coordinates that should be stored on this layer. A snap to grid algorithm will be applied on every geometry entering this layer, resulting in coordinates being rounded to multiples of this value. The operation is applied in this layer's coordinate reference system.</p></body></html> @@ -2388,14 +2395,7 @@ border-radius: 2px; - - - - Remove duplicate nodes - - - - + @@ -2436,6 +2436,19 @@ border-radius: 2px; + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -2653,6 +2666,8 @@ border-radius: 2px; + + From db77b45225f57cebe6081b02afc250c3c326ce6a Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 15:00:24 +0100 Subject: [PATCH 036/266] Toggle remove duplicate nodes when precision is changed As soon as a precision is set, duplicate nodes are removed automatically. By disabling the option and checking it in this scenario, this behavior is much more transparent to the user. --- src/app/qgsvectorlayerproperties.cpp | 21 ++++++++++++++++++++- src/app/qgsvectorlayerproperties.h | 2 ++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/app/qgsvectorlayerproperties.cpp b/src/app/qgsvectorlayerproperties.cpp index 3a960720bc78..f6148e50ff7c 100644 --- a/src/app/qgsvectorlayerproperties.cpp +++ b/src/app/qgsvectorlayerproperties.cpp @@ -408,7 +408,6 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( mGeometryPrecisionLineEdit->setEnabled( true ); mGeometryPrecisionLineEdit->setValidator( new QDoubleValidator( mGeometryPrecisionLineEdit ) ); - mRemoveDuplicateNodesCheckbox->setChecked( mLayer->geometryOptions()->removeDuplicateNodes() ); double precision( mLayer->geometryOptions()->geometryPrecision() ); bool ok = true; QString precisionStr( QLocale().toString( precision, ok ) ); @@ -416,6 +415,26 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( precisionStr = QString(); mGeometryPrecisionLineEdit->setText( precisionStr ); + mRemoveDuplicateNodesManuallyActivated = mLayer->geometryOptions()->removeDuplicateNodes(); + mRemoveDuplicateNodesCheckbox->setChecked( mRemoveDuplicateNodesManuallyActivated ); + if ( !precisionStr.isNull() ) + mRemoveDuplicateNodesCheckbox->setEnabled( false ); + connect( mGeometryPrecisionLineEdit, &QLineEdit::textChanged, this, [this] + { + if ( !mGeometryPrecisionLineEdit->text().isEmpty() ) + { + if ( mRemoveDuplicateNodesCheckbox->isEnabled() ) + mRemoveDuplicateNodesManuallyActivated = mRemoveDuplicateNodesCheckbox->isChecked(); + mRemoveDuplicateNodesCheckbox->setEnabled( false ); + mRemoveDuplicateNodesCheckbox->setChecked( true ); + } + else + { + mRemoveDuplicateNodesCheckbox->setEnabled( true ); + mRemoveDuplicateNodesCheckbox->setChecked( mRemoveDuplicateNodesManuallyActivated ); + } + } ); + mPrecisionUnitsLabel->setText( QStringLiteral( "[%1]" ).arg( QgsUnitTypes::toAbbreviatedString( mLayer->crs().mapUnits() ) ) ); QLayout *geometryCheckLayout = new QVBoxLayout(); diff --git a/src/app/qgsvectorlayerproperties.h b/src/app/qgsvectorlayerproperties.h index 352467a9fd2a..af0d5b0e841a 100644 --- a/src/app/qgsvectorlayerproperties.h +++ b/src/app/qgsvectorlayerproperties.h @@ -245,6 +245,8 @@ class APP_EXPORT QgsVectorLayerProperties : public QgsOptionsDialogBase, private QHash mGeometryCheckFactoriesGroupBoxes; + bool mRemoveDuplicateNodesManuallyActivated = false; + private slots: void openPanel( QgsPanelWidget *panel ); From 1d1a668fb0a4ff94943943b5dff965b06384fa3a Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 16:24:01 +0100 Subject: [PATCH 037/266] Add geometry check to API documentation --- doc/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 85ccd1b63351..018639ba0886 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -96,6 +96,7 @@ IF(WITH_APIDOC) ${CMAKE_SOURCE_DIR}/src/analysis/processing ${CMAKE_SOURCE_DIR}/src/analysis/raster ${CMAKE_SOURCE_DIR}/src/analysis/vector + ${CMAKE_SOURCE_DIR}/src/analysis/vector/geometry_checker ${CMAKE_SOURCE_DIR}/src/3d ${CMAKE_SOURCE_DIR}/src/3d/chunks ${CMAKE_SOURCE_DIR}/src/3d/symbols From 2ad870b95a44bd189388a8998d0532cf2ae73625 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 21 Dec 2018 18:06:40 +0100 Subject: [PATCH 038/266] Suppress api doc warnings for old classes --- tests/code_layout/acceptable_missing_doc.py | 87 ++++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index 0648b9003358..5c6ef9e7d5ae 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -463,7 +463,32 @@ "pal::PolygonCostCalculator": ['PolygonCostCalculator(LabelPosition *lp)', 'getCost()', 'getLabel()', 'update(pal::PointSet *pset)'], "pal::PriorityQueue": ['decreaseKey(int key)', 'downheap(int id)', 'getBest()', 'getId(int key)', 'getSize()', 'getSizeByPos()', 'insert(int key, double p)', 'isIn(int key)', 'print()', 'remove(int key)', 'setPriority(int key, double new_p)', 'sort()', 'upheap(int key)'], "pal::Problem": ['compareLabelArea(pal::LabelPosition *l1, pal::LabelPosition *l2)', 'compute_feature_cost(SubPart *part, int feat_id, int label_id, int *nbOverlap)', 'compute_subsolution_cost(SubPart *part, int *s, int *nbOverlap)', 'getFeatureCandidate(int fi, int ci)', 'getFeatureCandidateCount(int i)', 'getNumFeatures()', 'getSolution(bool returnInactive)', 'getStats()', 'init_sol_falp()', 'initialization()', 'popmusic_tabu(SubPart *part)', 'reduce()', 'subPart(int r, int featseed, int *isIn)'], - "pal::Util": ['unmulti(const GEOSGeometry *the_geom)'] + "pal::Util": ['unmulti(const GEOSGeometry *the_geom)'], + "QgsGeometryLineIntersectionCheck": ["QgsGeometryLineIntersectionCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)", "factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryDangleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryDegeneratePolygonCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryCheckerUtils": [], + "QgsGeometryContainedCheckError": [], + "QgsGeometryHoleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryDuplicateCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometrySelfContactCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryTypeCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryDuplicateCheckError": [], + "QgsGeometryLineLayerIntersectionCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometrySegmentLengthCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryTypeCheckError": [], + "QgsGeometrySliverPolygonCheck": [], + "QgsGeometryAreaCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryChecker": [], + "QgsGeometryFollowBoundariesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryMultipartCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometrySelfIntersectionCheckError": [], + "QgsGeometryGapCheckError": [], + "QgsGeometryPointCoveredByLineCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometrySelfIntersectionCheck": [], + "QgsGeometryContainedCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryPointInPolygonCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryDuplicateNodesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"] } ACCEPTABLE_MISSING_ADDED_NOTE = [ @@ -1333,7 +1358,40 @@ "QgsExpressionNodeLiteral", "QgsExpressionNodeUnaryOperator", "QgsStaticExpressionFunction", - "QgsExpressionNodeCondition::WhenThen" + "QgsExpressionNodeCondition::WhenThen", + "QgsGeometryChecker", + "QgsGeometryGapCheckError", + "QgsGeometrySelfIntersectionCheckError", + "QgsGeometryDangleCheck", + "QgsGeometryCheckerUtils::LayerFeatures::iterator", + "QgsGeometrySelfIntersectionCheck", + "QgsGeometryDegeneratePolygonCheck", + "QgsGeometryIsValidCheckError", + "QgsGeometryPointInPolygonCheck", + "QgsGeometryDuplicateCheckError", + "QgsGeometryOverlapCheckError", + "QgsGeometryFollowBoundariesCheck", + "QgsGeometryContainedCheck", + "QgsGeometryDuplicateNodesCheck", + "QgsGeometrySegmentLengthCheck", + "QgsGeometryGapCheck", + "QgsGeometryCheckFactoryT", + "QgsGeometryHoleCheck", + "QgsGeometryDuplicateCheck", + "QgsGeometryCheckerUtils::LayerFeature", + "QgsGeometryCheckerUtils::LayerFeatures", + "QgsGeometrySelfContactCheck", + "QgsGeometryOverlapCheck", + "QgsGeometryAngleCheck", + "QgsGeometryPointCoveredByLineCheck", + "QgsGeometryLineIntersectionCheck", + "QgsGeometryTypeCheckError", + "QgsGeometryLineLayerIntersectionCheck", + "QgsGeometrySliverPolygonCheck", + "QgsGeometryContainedCheckError", + "QgsGeometryAreaCheck", + "QgsGeometryTypeCheck", + "QgsGeometryMultipartCheck" ] ACCEPTABLE_MISSING_BRIEF = ['QgsBrushStyleComboBox', @@ -1609,7 +1667,30 @@ 'QgsCptCityBrowserModel', 'QgsSmartGroupEditorDialog', 'QgsHeatmapRendererWidget', - 'QgsStyleManagerDialog'] + 'QgsStyleManagerDialog', + 'QgsGeometrySelfIntersectionCheckError', + 'QgsGeometryDangleCheck', + 'QgsGeometrySelfIntersectionCheck', + 'QgsGeometryDegeneratePolygonCheck', + 'QgsGeometryPointInPolygonCheck', + 'QgsGeometryDuplicateCheckError', + 'QgsGeometryFollowBoundariesCheck', + 'QgsGeometryContainedCheck', + 'QgsGeometryDuplicateNodesCheck', + 'QgsGeometrySegmentLengthCheck', + 'QgsGeometryHoleCheck', + 'QgsGeometryDuplicateCheck', + 'QgsGeometrySelfContactCheck', + 'QgsGeometryAngleCheck', + 'QgsGeometryPointCoveredByLineCheck', + 'QgsGeometryLineIntersectionCheck', + 'QgsGeometryTypeCheckError', + 'QgsGeometryLineLayerIntersectionCheck', + 'QgsGeometrySliverPolygonCheck', + 'QgsGeometryContainedCheckError', + 'QgsGeometryAreaCheck', + 'QgsGeometryTypeCheck', + 'QgsGeometryMultipartCheck'] if __name__ == '__main__': From 65f6e7ba5d2970922ff540e435e86819de4df198 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 10:03:22 +0100 Subject: [PATCH 039/266] Doxymenation for gap checks --- .../qgsgeometrycheckerror.sip.in | 99 +++++++++++++++- .../geometry_checker/qgsgeometrycheckerror.h | 108 ++++++++++++++++-- .../geometry_checker/qgsgeometrygapcheck.h | 28 ++++- 3 files changed, 224 insertions(+), 11 deletions(-) diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in index 2897170b279b..01995ff753b1 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in @@ -27,8 +27,21 @@ This represents an error reported by a geometry check. #include "qgsgeometrycheckerror.h" %End public: - enum Status { StatusPending, StatusFixFailed, StatusFixed, StatusObsolete }; - enum ValueType { ValueLength, ValueArea, ValueOther }; + + enum Status + { + StatusPending, + StatusFixFailed, + StatusFixed, + StatusObsolete + }; + + enum ValueType + { + ValueLength, + ValueArea, + ValueOther + }; QgsGeometryCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, @@ -36,25 +49,98 @@ This represents an error reported by a geometry check. QgsVertexId vidx = QgsVertexId(), const QVariant &value = QVariant(), ValueType valueType = ValueOther ); +%Docstring +Create a new geometry check error with the parent ``check`` and for the +``layerFeature`` pair at the \errorLocation. Optionally the vertex can be +specified via ``vixd`` and a ``value`` with its ``value`` Type for +additional information. +%End virtual ~QgsGeometryCheckError(); const QgsGeometryCheck *check() const; +%Docstring +The geometry check that created this error. +%End + const QString &layerId() const; +%Docstring +The id of the layer on which this error has been detected. +%End + QgsFeatureId featureId() const; +%Docstring +The id of the feature on which this error has been detected. +%End + QgsGeometry geometry() const; +%Docstring +The geometry of the error in map units. +%End + virtual QgsRectangle affectedAreaBBox() const; +%Docstring +The bounding box of the affected area of the error. +%End + virtual QString description() const; +%Docstring +The error description. By default the description of the parent check +will be returned. +%End + const QgsPointXY &location() const; +%Docstring +The location of the error in map units. +%End + QVariant value() const; +%Docstring +An additional value for the error. +Lenghts and areas are provided in map units. + +.. seealso:: :py:func:`valueType` +%End + ValueType valueType() const; +%Docstring +The type of the value. + +.. seealso:: :py:func:`value` +%End + const QgsVertexId &vidx() const; +%Docstring +The id of the affected vertex. May be valid or not, depending on the +check. +%End + Status status() const; +%Docstring +The status of the error. +%End + QString resolutionMessage() const; +%Docstring +A message with details, how the error has been resolved. +%End + void setFixed( int method ); +%Docstring +Set the status to fixed and specify the ``method`` that has been used to +fix the error. +%End + void setFixFailed( const QString &reason ); +%Docstring +Set the error status to failed and specify the ``reason`` for failure. +%End + void setObsolete(); +%Docstring +Set the error status to obsolete. +%End virtual bool isEqual( QgsGeometryCheckError *other ) const; %Docstring @@ -77,6 +163,7 @@ Will be used to update existing errors whenever they are re-checked. protected: + QgsGeometryCheckError( const QgsGeometryCheck *check, const QString &layerId, QgsFeatureId featureId, @@ -85,6 +172,14 @@ Will be used to update existing errors whenever they are re-checked. QgsVertexId vidx = QgsVertexId(), const QVariant &value = QVariant(), ValueType valueType = ValueOther ); +%Docstring +Create a new geometry check error with the parent ``check`` and for the +layer with ``layerId`` and ``featureId``. +The ``geometry`` of the error and the ``errorLocation`` need to be +specified in map coordiantes. +Optionally the vertex can be specified via ``vixd`` and a ``value`` with +its ``value`` Type for additional information. +%End }; diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h index 8dd79d7bf997..8d15c30415e8 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h @@ -35,9 +35,34 @@ class QgsPointXY; class ANALYSIS_EXPORT QgsGeometryCheckError { public: - enum Status { StatusPending, StatusFixFailed, StatusFixed, StatusObsolete }; - enum ValueType { ValueLength, ValueArea, ValueOther }; + /** + * The status of an error. + */ + enum Status + { + StatusPending, //!< The error is detected and pending to be handled + StatusFixFailed, //!< A fix has been tried on the error but failed + StatusFixed, //!< The error is fixed + StatusObsolete //!< The error is obsolete because of other modifications + }; + + /** + * Describes the type of an error value. + */ + enum ValueType + { + ValueLength, //!< The value is a length + ValueArea, //!< The value is an area + ValueOther //!< The value if of another type + }; + + /** + * Create a new geometry check error with the parent \a check and for the + * \a layerFeature pair at the \errorLocation. Optionally the vertex can be + * specified via \a vixd and a \a value with its \a value Type for + * additional information. + */ QgsGeometryCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsPointXY &errorLocation, @@ -49,24 +74,85 @@ class ANALYSIS_EXPORT QgsGeometryCheckError const QgsGeometryCheckError &operator=( const QgsGeometryCheckError & ) = delete; + /** + * The geometry check that created this error. + */ const QgsGeometryCheck *check() const { return mCheck; } + + /** + * The id of the layer on which this error has been detected. + */ const QString &layerId() const { return mLayerId; } + + /** + * The id of the feature on which this error has been detected. + */ QgsFeatureId featureId() const { return mFeatureId; } - // In map units + + /** + * The geometry of the error in map units. + */ QgsGeometry geometry() const; - // In map units + + /** + * The bounding box of the affected area of the error. + */ virtual QgsRectangle affectedAreaBBox() const; + + /** + * The error description. By default the description of the parent check + * will be returned. + */ virtual QString description() const { return mCheck->description(); } - // In map units + + /** + * The location of the error in map units. + */ const QgsPointXY &location() const { return mErrorLocation; } - // Lengths, areas in map units + + /** + * An additional value for the error. + * Lenghts and areas are provided in map units. + * \see valueType() + */ QVariant value() const { return mValue; } + + /** + * The type of the value. + * \see value() + */ ValueType valueType() const { return mValueType; } + + /** + * The id of the affected vertex. May be valid or not, depending on the + * check. + */ const QgsVertexId &vidx() const { return mVidx; } + + /** + * The status of the error. + */ Status status() const { return mStatus; } + + /** + * A message with details, how the error has been resolved. + */ QString resolutionMessage() const { return mResolutionMessage; } + + /** + * Set the status to fixed and specify the \a method that has been used to + * fix the error. + */ void setFixed( int method ); + + /** + * Set the error status to failed and specify the \a reason for failure. + */ void setFixFailed( const QString &reason ); + + /** + * Set the error status to obsolete. + */ void setObsolete() { mStatus = StatusObsolete; } /** @@ -94,7 +180,15 @@ class ANALYSIS_EXPORT QgsGeometryCheckError virtual bool handleChanges( const QgsGeometryCheck::Changes &changes ) SIP_SKIP; protected: - // Users of this constructor must ensure geometry and errorLocation are in map coordinates + + /** + * Create a new geometry check error with the parent \a check and for the + * layer with \a layerId and \a featureId. + * The \a geometry of the error and the \a errorLocation need to be + * specified in map coordiantes. + * Optionally the vertex can be specified via \a vixd and a \a value with + * its \a value Type for additional information. + */ QgsGeometryCheckError( const QgsGeometryCheck *check, const QString &layerId, QgsFeatureId featureId, diff --git a/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.h b/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.h index 880bae699793..d654fb09b44f 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.h @@ -22,9 +22,22 @@ #include "qgsgeometrycheckerror.h" #include "qgsfeatureid.h" +/** + * \ingroup analysis + * An error produced by a QgsGeometryGapCheck. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError { public: + + /** + * Create a new gap check error produced by \a check on the layer \a layerId. + * The \a geometry of the gap needs to be in map coordinates. + * The \a neighbors are a map of layer ids and feature ids. + * The \a area of the gap in map units and the bounding box of the gap in map units too. + */ QgsGeometryGapCheckError( const QgsGeometryCheck *check, const QString &layerId, const QgsGeometry &geometry, @@ -36,6 +49,10 @@ class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError , mGapAreaBBox( gapAreaBBox ) { } + + /** + * A map of layers and feature ids of the neighbors of the gap. + */ const QMap &neighbors() const { return mNeighbors; } bool isEqual( QgsGeometryCheckError *other ) const override @@ -74,6 +91,13 @@ class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError QgsRectangle mGapAreaBBox; }; + +/** + * \ingroup analysis + * Checks for gaps between neighbouring polygons. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT QgsGeometryGapCheck : public QgsGeometryCheck { Q_GADGET @@ -81,8 +105,8 @@ class ANALYSIS_EXPORT QgsGeometryGapCheck : public QgsGeometryCheck //! Resolution methods for geometry gap checks enum ResolutionMethod { - MergeLongestEdge, - NoChange + MergeLongestEdge, //!< Merge the gap with the polygon with the longest shared edge. + NoChange //!< Do not handle the error. }; Q_ENUM( ResolutionMethod ) From c6420690b5f81a9b9791bfe366553259c10d3fa0 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 10:16:24 +0100 Subject: [PATCH 040/266] Skip more missing doc checks for old classes --- tests/code_layout/acceptable_missing_doc.py | 45 +++++++++++---------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index 5c6ef9e7d5ae..66943ceda907 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -465,30 +465,31 @@ "pal::Problem": ['compareLabelArea(pal::LabelPosition *l1, pal::LabelPosition *l2)', 'compute_feature_cost(SubPart *part, int feat_id, int label_id, int *nbOverlap)', 'compute_subsolution_cost(SubPart *part, int *s, int *nbOverlap)', 'getFeatureCandidate(int fi, int ci)', 'getFeatureCandidateCount(int i)', 'getNumFeatures()', 'getSolution(bool returnInactive)', 'getStats()', 'init_sol_falp()', 'initialization()', 'popmusic_tabu(SubPart *part)', 'reduce()', 'subPart(int r, int featseed, int *isIn)'], "pal::Util": ['unmulti(const GEOSGeometry *the_geom)'], "QgsGeometryLineIntersectionCheck": ["QgsGeometryLineIntersectionCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)", "factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryDangleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryDegeneratePolygonCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryCheckerUtils": [], - "QgsGeometryContainedCheckError": [], - "QgsGeometryHoleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryDuplicateCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometrySelfContactCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryTypeCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryDuplicateCheckError": [], - "QgsGeometryLineLayerIntersectionCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometrySegmentLengthCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryTypeCheckError": [], - "QgsGeometrySliverPolygonCheck": [], - "QgsGeometryAreaCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryDangleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryDangleCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryDegeneratePolygonCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryDegeneratePolygonCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryCheckerUtils": ["lineIntersections(const QgsLineString *line1, const QgsLineString *line2, double tol)", "filter1DTypes(QgsAbstractGeometry *geom)", "canDeleteVertex(const QgsAbstractGeometry *geom, int iPart, int iRing)", "polygonRings(const QgsPolygon *polygon)", "sharedEdgeLength(const QgsAbstractGeometry *geom1, const QgsAbstractGeometry *geom2, double tol)", "pointOnLine(const QgsPoint &p, const QgsLineString *line, double tol, bool excludeExtremities=false)", "getGeomPart(QgsAbstractGeometry *geom, int partIdx)", "getGeomPart(const QgsAbstractGeometry *geom, int partIdx)", "createGeomEngine(const QgsAbstractGeometry *geometry, double tolerance)"], + "QgsGeometryContainedCheckError": ["QgsGeometryContainedCheckError(const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsPointXY &errorLocation, const QgsGeometryCheckerUtils::LayerFeature &containingFeature)", "containingFeature() const"], + "QgsGeometryHoleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryHoleCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryDuplicateCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryDuplicateCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometrySelfContactCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometrySelfContactCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryTypeCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryTypeCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration, int allowedTypes)"], + "QgsGeometryDuplicateCheckError": ["QgsGeometryDuplicateCheckError(const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsPointXY &errorLocation, const QMap< QString, QgsFeaturePool * > &featurePools, const QMap< QString, QList< QgsFeatureId >> &duplicates)", "duplicates() const"], + "QgsGeometryLineLayerIntersectionCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryLineLayerIntersectionCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometrySegmentLengthCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometrySegmentLengthCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryTypeCheckError": ["QgsGeometryTypeCheckError(const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, QgsWkbTypes::Type flatType)"], + "QgsGeometryAngleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryAngleCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"] + "QgsGeometrySliverPolygonCheck": ["factoryDescription()", "factoryId()", "QgsGeometrySliverPolygonCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryAreaCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryAreaCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometryChecker": [], - "QgsGeometryFollowBoundariesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryMultipartCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], + "QgsGeometryFollowBoundariesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryFollowBoundariesCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration, QgsVectorLayer *checkLayer)"], + "QgsGeometryMultipartCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryMultipartCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometrySelfIntersectionCheckError": [], - "QgsGeometryGapCheckError": [], - "QgsGeometryPointCoveredByLineCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometrySelfIntersectionCheck": [], - "QgsGeometryContainedCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryPointInPolygonCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"], - "QgsGeometryDuplicateNodesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()"] + "QgsGeometryGapCheckError": ["QgsGeometrySelfIntersectionCheckError(const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, QgsVertexId vertexId, const QgsGeometryUtils::SelfIntersection &intersection)", "intersection() const"], + "QgsGeometryPointCoveredByLineCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryPointCoveredByLineCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometrySelfIntersectionCheck": ["QgsGeometrySelfIntersectionCheck(const QgsGeometryCheckContext *context, const QVariantMap &configuration=QVariantMap())", "ResolutionMethod"], + "QgsGeometryContainedCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryContainedCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryPointInPolygonCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryPointInPolygonCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryDuplicateNodesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryDuplicateNodesCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"] } ACCEPTABLE_MISSING_ADDED_NOTE = [ From 07ab092fb110fe44d8cad5fe1bc61a77a703aefd Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 10:35:30 +0100 Subject: [PATCH 041/266] Some doc fixes --- .../vector/geometry_checker/qgsgeometrycheckerror.sip.in | 4 ++-- src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h | 4 ++-- tests/code_layout/acceptable_missing_doc.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in index 01995ff753b1..f5e0d24d71d1 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in @@ -98,7 +98,7 @@ The location of the error in map units. QVariant value() const; %Docstring An additional value for the error. -Lenghts and areas are provided in map units. +Lengths and areas are provided in map units. .. seealso:: :py:func:`valueType` %End @@ -176,7 +176,7 @@ Will be used to update existing errors whenever they are re-checked. Create a new geometry check error with the parent ``check`` and for the layer with ``layerId`` and ``featureId``. The ``geometry`` of the error and the ``errorLocation`` need to be -specified in map coordiantes. +specified in map coordinates. Optionally the vertex can be specified via ``vixd`` and a ``value`` with its ``value`` Type for additional information. %End diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h index 8d15c30415e8..90fd037041e0 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h @@ -112,7 +112,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckError /** * An additional value for the error. - * Lenghts and areas are provided in map units. + * Lengths and areas are provided in map units. * \see valueType() */ QVariant value() const { return mValue; } @@ -185,7 +185,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckError * Create a new geometry check error with the parent \a check and for the * layer with \a layerId and \a featureId. * The \a geometry of the error and the \a errorLocation need to be - * specified in map coordiantes. + * specified in map coordinates. * Optionally the vertex can be specified via \a vixd and a \a value with * its \a value Type for additional information. */ diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index 66943ceda907..79eebad41470 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -477,7 +477,7 @@ "QgsGeometryLineLayerIntersectionCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryLineLayerIntersectionCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometrySegmentLengthCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometrySegmentLengthCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometryTypeCheckError": ["QgsGeometryTypeCheckError(const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, QgsWkbTypes::Type flatType)"], - "QgsGeometryAngleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryAngleCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"] + "QgsGeometryAngleCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryAngleCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometrySliverPolygonCheck": ["factoryDescription()", "factoryId()", "QgsGeometrySliverPolygonCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometryAreaCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryAreaCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometryChecker": [], From c8be505df88e42e77083a00bb4b718d1c7ca9f85 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 10:48:43 +0100 Subject: [PATCH 042/266] Remove unneeded methods --- .../vector/geometry_checker/qgsgeometrycontainedcheck.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h b/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h index 25965004ca4f..da092092f421 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h @@ -42,9 +42,6 @@ class ANALYSIS_EXPORT QgsGeometryContainedCheckError : public QgsGeometryCheckEr static_cast( other )->containingFeature() == containingFeature(); } - static QString factoryDescription() { return QApplication::translate( "QgsGeometryContainedCheckError", "Within feature" ); } - QString description() const override { return factoryDescription(); } - private: QPair mContainingFeature; }; From 525a3da3abf9a84d047a1db876df155b0d80d09f Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 10:57:53 +0100 Subject: [PATCH 043/266] More doxygen --- .../geometry_checker/qgsfeaturepool.sip.in | 8 ++++++-- .../qgsgeometrycheckerutils.sip.in | 4 ++++ .../qgsgeometrycheckfactory.sip.in | 2 +- .../qgsgeometrycheckregistry.sip.in | 4 ++-- .../vector/geometry_checker/qgsfeaturepool.h | 16 ++++++++++------ .../geometry_checker/qgsgeometrycheckerutils.h | 4 ++++ .../geometry_checker/qgsgeometrycheckfactory.h | 2 +- .../geometry_checker/qgsgeometrycheckregistry.h | 4 ++-- .../qgsgeometrymissingvertexcheck.h | 11 +++++++++-- .../qgsvectordataproviderfeaturepool.h | 5 +++++ .../geometry_checker/qgsvectorlayerfeaturepool.h | 4 ++++ tests/code_layout/acceptable_missing_doc.py | 2 +- 12 files changed, 49 insertions(+), 17 deletions(-) diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsfeaturepool.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsfeaturepool.sip.in index 581e602af2d9..bf1998936ff0 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsfeaturepool.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsfeaturepool.sip.in @@ -27,12 +27,16 @@ A feature pool is based on a vector layer and caches features. #include "qgsfeaturepool.h" %End public: + QgsFeaturePool( QgsVectorLayer *layer ); +%Docstring +Creates a new feature pool for ``layer``. +%End virtual ~QgsFeaturePool(); bool getFeature( QgsFeatureId id, QgsFeature &feature, QgsFeedback *feedback = 0 ); %Docstring -Retrieve the feature with the specified ``id`` into ``feature``. +Retrieves the feature with the specified ``id`` into ``feature``. It will be retrieved from the cache or from the underlying layer if unavailable. If the feature is neither available from the cache nor from the layer it will return false. If ``feedback`` is specified, the call may return if the feedback is canceled. @@ -55,7 +59,7 @@ Implementations will remove the feature from the layer or from the data provider QgsVectorLayer *layer() const; %Docstring -Get a pointer to the underlying layer. +Gets a pointer to the underlying layer. May return a ``None`` if the layer has been deleted. This must only be called from the main thread. %End diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in index abd27671a9f6..4c4d84f02b52 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in @@ -62,7 +62,11 @@ Returns the geometry of this feature. If useMapCrs was specified, it will already be reprojected into the CRS specified in the context specified in the constructor. %End + QString id() const; +%Docstring +Returns a combination of the layerId and the feature id. +%End bool operator==( const QgsGeometryCheckerUtils::LayerFeature &other ) const; bool operator!=( const QgsGeometryCheckerUtils::LayerFeature &other ) const; diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in index c2dea49db5a2..ea77c0dc44c5 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in @@ -34,7 +34,7 @@ A factory for geometry checks. virtual QgsGeometryCheck *createGeometryCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ) const = 0 /Factory/; %Docstring -Create a new geometry check with ``context`` and ``configuration``. +Creates a new geometry check with ``context`` and ``configuration``. %End virtual QString id() const = 0; diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckregistry.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckregistry.sip.in index 0e1c5f107c4c..033206e00109 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckregistry.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckregistry.sip.in @@ -50,14 +50,14 @@ Ownership is transferred to the caller. QList geometryCheckFactories( QgsVectorLayer *layer, QgsGeometryCheck::CheckType type, QgsGeometryCheck::Flags flags = 0 ) const; %Docstring -Get all geometry check factories that are compatible with ``layer`` and have all of the ``flags`` set. +Returns all geometry check factories that are compatible with ``layer`` and have all of the ``flags`` set. .. versionadded:: 3.4 %End void registerGeometryCheck( QgsGeometryCheckFactory *checkFactory /Transfer/ ); %Docstring -Register a new geometry check factory. +Registers a new geometry check factory. .. versionadded:: 3.4 %End diff --git a/src/analysis/vector/geometry_checker/qgsfeaturepool.h b/src/analysis/vector/geometry_checker/qgsfeaturepool.h index cbb58e8c40b8..889bc11c6406 100644 --- a/src/analysis/vector/geometry_checker/qgsfeaturepool.h +++ b/src/analysis/vector/geometry_checker/qgsfeaturepool.h @@ -39,11 +39,15 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT { public: + + /** + * Creates a new feature pool for \a layer. + */ QgsFeaturePool( QgsVectorLayer *layer ); virtual ~QgsFeaturePool() = default; /** - * Retrieve the feature with the specified \a id into \a feature. + * Retrieves the feature with the specified \a id into \a feature. * It will be retrieved from the cache or from the underlying layer if unavailable. * If the feature is neither available from the cache nor from the layer it will return false. * If \a feedback is specified, the call may return if the feedback is canceled. @@ -51,7 +55,7 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT bool getFeature( QgsFeatureId id, QgsFeature &feature, QgsFeedback *feedback = nullptr ); /** - * Get features for the provided \a request. No features will be fetched + * Gets features for the provided \a request. No features will be fetched * from the cache and the request is sent directly to the underlying feature source. * Results of the request are cached in the pool and the ids of all the features * are returned. This can be used to warm the cache for a particular area of interest @@ -81,7 +85,7 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT QgsFeatureIds allFeatureIds() const SIP_SKIP; /** - * Get all feature ids in the bounding box \a rect. It will use a spatial index to + * Gets all feature ids in the bounding box \a rect. It will use a spatial index to * determine the ids. * * \note not available in Python bindings @@ -89,14 +93,14 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT QgsFeatureIds getIntersects( const QgsRectangle &rect ) const SIP_SKIP; /** - * Get a pointer to the underlying layer. + * Gets a pointer to the underlying layer. * May return a ``nullptr`` if the layer has been deleted. * This must only be called from the main thread. */ QgsVectorLayer *layer() const; /** - * Get a QPointer to the underlying layer. + * Gets a QPointer to the underlying layer. * Note that access to any methods of the object * will need to be done on the main thread and * the pointer will need to be checked for validity @@ -142,7 +146,7 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT void removeFeature( const QgsFeatureId featureId ); /** - * Set all the feature ids governed by this feature pool. + * Sets all the feature ids governed by this feature pool. * Should be called by subclasses constructor and whenever * they insert a new feature. * diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h index e75461813a18..e389dff73218 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h @@ -74,6 +74,10 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils * CRS specified in the context specified in the constructor. */ const QgsGeometry &geometry() const; + + /** + * Returns a combination of the layerId and the feature id. + */ QString id() const; bool operator==( const QgsGeometryCheckerUtils::LayerFeature &other ) const; bool operator!=( const QgsGeometryCheckerUtils::LayerFeature &other ) const; diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h index b131698dec31..691b4e1c9580 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h @@ -52,7 +52,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckFactory SIP_ABSTRACT virtual ~QgsGeometryCheckFactory() = default; /** - * Create a new geometry check with \a context and \a configuration. + * Creates a new geometry check with \a context and \a configuration. */ virtual QgsGeometryCheck *createGeometryCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ) const = 0 SIP_FACTORY; diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckregistry.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckregistry.h index 0d5489a004c3..afff1dafab27 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckregistry.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckregistry.h @@ -65,14 +65,14 @@ class ANALYSIS_EXPORT QgsGeometryCheckRegistry QgsGeometryCheck *geometryCheck( const QString &checkId, QgsGeometryCheckContext *context, const QVariantMap &geometryCheckConfig ) SIP_FACTORY; /** - * Get all geometry check factories that are compatible with \a layer and have all of the \a flags set. + * Returns all geometry check factories that are compatible with \a layer and have all of the \a flags set. * * \since QGIS 3.4 */ QList geometryCheckFactories( QgsVectorLayer *layer, QgsGeometryCheck::CheckType type, QgsGeometryCheck::Flags flags = nullptr ) const; /** - * Register a new geometry check factory. + * Registers a new geometry check factory. * * \since QGIS 3.4 */ diff --git a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.h b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.h index aa10b5f05fff..801344052751 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.h @@ -37,13 +37,20 @@ class ANALYSIS_EXPORT QgsGeometryMissingVertexCheck : public QgsGeometryCheck Q_GADGET public: + + /** + * The available resolutions for missing vertex check. + */ enum ResolutionMethod { - NoChange, - AddMissingVertex + NoChange, //!< Do nothing + AddMissingVertex //! Add the missing vertex }; Q_ENUM( ResolutionMethod ) + /** + * Creates a new missing vertex geometry check with \a context and the provided \a geometryCheckConfiguration. + */ explicit QgsGeometryMissingVertexCheck( const QgsGeometryCheckContext *context, const QVariantMap &geometryCheckConfiguration ); void collectErrors( const QMap &featurePools, QList &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void fixError( const QMap &featurePools, QgsGeometryCheckError *error, int method, const QMap &mergeAttributeIndices, Changes &changes ) const override; diff --git a/src/analysis/vector/geometry_checker/qgsvectordataproviderfeaturepool.h b/src/analysis/vector/geometry_checker/qgsvectordataproviderfeaturepool.h index 7ea242a02d91..81ea12e832c2 100644 --- a/src/analysis/vector/geometry_checker/qgsvectordataproviderfeaturepool.h +++ b/src/analysis/vector/geometry_checker/qgsvectordataproviderfeaturepool.h @@ -30,6 +30,11 @@ email : matthias@opengis.ch class ANALYSIS_EXPORT QgsVectorDataProviderFeaturePool : public QgsFeaturePool { public: + + /** + * Creates a new feature pool for the data provider of \a layer. + * If \a selectedOnly is set to true, only selected features will be managed by the pool. + */ QgsVectorDataProviderFeaturePool( QgsVectorLayer *layer, bool selectedOnly = false ); bool addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags = nullptr ) override; diff --git a/src/analysis/vector/geometry_checker/qgsvectorlayerfeaturepool.h b/src/analysis/vector/geometry_checker/qgsvectorlayerfeaturepool.h index f285cf9a9e59..569e688abe49 100644 --- a/src/analysis/vector/geometry_checker/qgsvectorlayerfeaturepool.h +++ b/src/analysis/vector/geometry_checker/qgsvectorlayerfeaturepool.h @@ -32,6 +32,10 @@ class ANALYSIS_EXPORT QgsVectorLayerFeaturePool : public QObject, public QgsFeat Q_OBJECT public: + + /** + * Creates a new feature pool for \a layer. + */ QgsVectorLayerFeaturePool( QgsVectorLayer *layer ); bool addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags = nullptr ) override; diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index 79eebad41470..c8f3ceed1aea 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -483,7 +483,7 @@ "QgsGeometryChecker": [], "QgsGeometryFollowBoundariesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryFollowBoundariesCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration, QgsVectorLayer *checkLayer)"], "QgsGeometryMultipartCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryMultipartCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], - "QgsGeometrySelfIntersectionCheckError": [], + "QgsGeometrySelfIntersectionCheckError": ["QgsGeometrySelfIntersectionCheckError(const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, QgsVertexId vertexId, const QgsGeometryUtils::SelfIntersection &intersection)", "intersection() const"], "QgsGeometryGapCheckError": ["QgsGeometrySelfIntersectionCheckError(const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, QgsVertexId vertexId, const QgsGeometryUtils::SelfIntersection &intersection)", "intersection() const"], "QgsGeometryPointCoveredByLineCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryPointCoveredByLineCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometrySelfIntersectionCheck": ["QgsGeometrySelfIntersectionCheck(const QgsGeometryCheckContext *context, const QVariantMap &configuration=QVariantMap())", "ResolutionMethod"], From f2d4d877ecf92661c87b5bf7d70fcb699cc570ba Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 11:04:13 +0100 Subject: [PATCH 044/266] Overlap check documentation --- .../qgsgeometryoverlapcheck.h | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.h b/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.h index fcaa05d09cb7..b7806435d189 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.h @@ -21,7 +21,12 @@ #include "qgsgeometrycheck.h" #include "qgsgeometrycheckerror.h" - +/** + * \ingroup analysis + * An error of a QgsGeometryOverlapCheck. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckError { public: @@ -46,12 +51,22 @@ class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckErro QgsFeatureId mFeatureId; }; + /** + * Creates a new overlap check error for \a check and the \a layerFeature combination. + * The \a geometry and \a errorLocation ned to be in map coordinates. + * The \a value is the area of the overlapping area in map units. + * The \a overlappedFeature provides more details about the overlap. + */ QgsGeometryOverlapCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsGeometry &geometry, const QgsPointXY &errorLocation, const QVariant &value, const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature ); + + /** + * Returns the overlapped feature + */ const OverlappedFeature &overlappedFeature() const { return mOverlappedFeature; } bool isEqual( QgsGeometryCheckError *other ) const override @@ -90,11 +105,24 @@ class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckErro OverlappedFeature mOverlappedFeature; }; +/** + * \ingroup analysis + * Checks if geometries overlap. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT QgsGeometryOverlapCheck : public QgsGeometryCheck { public: - enum ResolutionMethod { Subtract, NoChange }; + /** + * Available resolution methods. + */ + enum ResolutionMethod + { + Subtract, //!< Subtract the overlap region from the polygon + NoChange //!< Do not change anything + }; /** * Checks for overlapping polygons. From bdedc86f0b570277c1d6af0810f6407059867caa Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 11:08:51 +0100 Subject: [PATCH 045/266] Add geometry check classes to analysis group --- .../vector/geometry_checker/qgsgeometryanglecheck.h | 3 +++ src/analysis/vector/geometry_checker/qgsgeometrychecker.h | 7 +++++++ .../vector/geometry_checker/qgsgeometrydanglecheck.h | 3 +++ .../geometry_checker/qgsgeometrydegeneratepolygoncheck.h | 3 +++ .../vector/geometry_checker/qgsgeometryduplicatecheck.h | 6 ++++++ .../geometry_checker/qgsgeometryduplicatenodescheck.h | 3 +++ .../vector/geometry_checker/qgsgeometryholecheck.h | 3 +++ .../geometry_checker/qgsgeometrylineintersectioncheck.h | 3 +++ .../qgsgeometrylinelayerintersectioncheck.h | 3 +++ .../vector/geometry_checker/qgsgeometrymultipartcheck.h | 3 +++ .../geometry_checker/qgsgeometrypointcoveredbylinecheck.h | 3 +++ .../geometry_checker/qgsgeometrypointinpolygoncheck.h | 3 +++ .../vector/geometry_checker/qgsgeometryselfcontactcheck.h | 3 +++ .../geometry_checker/qgsgeometryselfintersectioncheck.h | 6 ++++++ .../geometry_checker/qgsgeometrysliverpolygoncheck.h | 3 +++ .../vector/geometry_checker/qgsgeometrytypecheck.h | 6 ++++++ 16 files changed, 61 insertions(+) diff --git a/src/analysis/vector/geometry_checker/qgsgeometryanglecheck.h b/src/analysis/vector/geometry_checker/qgsgeometryanglecheck.h index d334881ef077..6a82f023744a 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryanglecheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryanglecheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryAngleCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrychecker.h b/src/analysis/vector/geometry_checker/qgsgeometrychecker.h index ddf1c8265462..ad33f58a6d02 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrychecker.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrychecker.h @@ -37,6 +37,13 @@ class QgsVectorLayer; class QgsFeaturePool; class QMutex; +/** + * \ingroup analysis + * + * Manages and runs a set of geometry checks. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT QgsGeometryChecker : public QObject { Q_OBJECT diff --git a/src/analysis/vector/geometry_checker/qgsgeometrydanglecheck.h b/src/analysis/vector/geometry_checker/qgsgeometrydanglecheck.h index bd537f083187..55abf03308ff 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrydanglecheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrydanglecheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryDangleCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrydegeneratepolygoncheck.h b/src/analysis/vector/geometry_checker/qgsgeometrydegeneratepolygoncheck.h index e5f1cfe8d03b..1f998bca23ba 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrydegeneratepolygoncheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrydegeneratepolygoncheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryDegeneratePolygonCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.h b/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.h index 81102f2f4df0..7dfe85edf964 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.h @@ -22,6 +22,9 @@ #include "qgsgeometrycheck.h" #include "qgsgeometrycheckerror.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryDuplicateCheckError : public QgsGeometryCheckError { public: @@ -50,6 +53,9 @@ class ANALYSIS_EXPORT QgsGeometryDuplicateCheckError : public QgsGeometryCheckEr static QString duplicatesString( const QMap &featurePools, const QMap> &duplicates ); }; +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryDuplicateCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometryduplicatenodescheck.h b/src/analysis/vector/geometry_checker/qgsgeometryduplicatenodescheck.h index f88a4dee4b26..83fa31ba1a67 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryduplicatenodescheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryduplicatenodescheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryDuplicateNodesCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometryholecheck.h b/src/analysis/vector/geometry_checker/qgsgeometryholecheck.h index b216c16a3352..e8a4dddaaa79 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryholecheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryholecheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryHoleCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrylineintersectioncheck.h b/src/analysis/vector/geometry_checker/qgsgeometrylineintersectioncheck.h index 4cf7db54541f..000ca195b215 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrylineintersectioncheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrylineintersectioncheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryLineIntersectionCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h b/src/analysis/vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h index 6bb5614a50db..7bec57145a82 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryLineLayerIntersectionCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrymultipartcheck.h b/src/analysis/vector/geometry_checker/qgsgeometrymultipartcheck.h index 80e2f319ae77..44449a0c899b 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrymultipartcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrymultipartcheck.h @@ -20,6 +20,9 @@ #include "qgssinglegeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryMultipartCheck : public QgsSingleGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrypointcoveredbylinecheck.h b/src/analysis/vector/geometry_checker/qgsgeometrypointcoveredbylinecheck.h index 8726f80c42e9..bbceafc9cd89 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrypointcoveredbylinecheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrypointcoveredbylinecheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryPointCoveredByLineCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.h b/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.h index cf6cef83bcbe..0e4dc5c85ccd 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryPointInPolygonCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometryselfcontactcheck.h b/src/analysis/vector/geometry_checker/qgsgeometryselfcontactcheck.h index 322418bcf46f..c1ff4b3111ff 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryselfcontactcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryselfcontactcheck.h @@ -20,6 +20,9 @@ #include "qgssinglegeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometrySelfContactCheck : public QgsSingleGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.h b/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.h index 09f4f550f74b..f13f364c5657 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.h @@ -21,6 +21,9 @@ #include "qgsgeometryutils.h" #include "qgssinglegeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometrySelfIntersectionCheckError : public QgsSingleGeometryCheckError { public: @@ -42,6 +45,9 @@ class ANALYSIS_EXPORT QgsGeometrySelfIntersectionCheckError : public QgsSingleGe QgsGeometryUtils::SelfIntersection mIntersection; }; +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometrySelfIntersectionCheck : public QgsSingleGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrysliverpolygoncheck.h b/src/analysis/vector/geometry_checker/qgsgeometrysliverpolygoncheck.h index 9206a231e85d..47547a786ad6 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrysliverpolygoncheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrysliverpolygoncheck.h @@ -20,6 +20,9 @@ #include "qgsgeometryareacheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometrySliverPolygonCheck : public QgsGeometryAreaCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrytypecheck.h b/src/analysis/vector/geometry_checker/qgsgeometrytypecheck.h index 07eca474a9ca..be51982dc7d6 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrytypecheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrytypecheck.h @@ -20,6 +20,9 @@ #include "qgssinglegeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryTypeCheckError : public QgsSingleGeometryCheckError { public: @@ -40,6 +43,9 @@ class ANALYSIS_EXPORT QgsGeometryTypeCheckError : public QgsSingleGeometryCheckE QgsWkbTypes::Type mFlatType; }; +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryTypeCheck : public QgsSingleGeometryCheck { public: From 265df937691856c29a688142b4425e5cc09c9c4f Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 11:26:53 +0100 Subject: [PATCH 046/266] Even more docs --- .../geometry_checker/qgsgeometryareacheck.h | 3 ++ .../qgsgeometrycheckerutils.h | 30 ++++++++++++++++++- .../qgsgeometrycontainedcheck.h | 6 ++++ .../qgsgeometryfollowboundariescheck.h | 3 ++ .../qgsgeometrysegmentlengthcheck.h | 3 ++ 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/analysis/vector/geometry_checker/qgsgeometryareacheck.h b/src/analysis/vector/geometry_checker/qgsgeometryareacheck.h index 2224cde8e154..48908502528d 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryareacheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryareacheck.h @@ -22,6 +22,9 @@ class QgsSurface; +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryAreaCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h index e389dff73218..4a3c6f5883fb 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h @@ -36,10 +36,17 @@ class QgsFeedback; * \note This class is a technology preview and unstable API. * \since QGIS 3.4 */ - class ANALYSIS_EXPORT QgsGeometryCheckerUtils { public: + /** + * \ingroup analysis + * + * A layer feature combination to uniquely identify and access a feature in + * a set of layers. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT LayerFeature { public: @@ -94,6 +101,13 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils bool mMapCrs; }; + /** + * \ingroup analysis + * + * Contains a set of layers and feature ids in those layers to pass to a geometry check. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT LayerFeatures { public: @@ -110,6 +124,13 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils const QList &geometryTypes, const QgsGeometryCheckContext *context ); + /** + * \ingroup analysis + * + * An iterator over all features in a QgsGeometryCheckerUtils::LayerFeatures. + * + * \since QGIS 3.4 + */ class iterator { public: @@ -131,7 +152,14 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils std::unique_ptr mCurrentFeature; }; + /** + * The first feature to start iterating. + */ iterator begin() const; + + /** + * One after the last feature to stop iterating. + */ iterator end() const; #endif diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h b/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h index da092092f421..b35ecd320095 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.h @@ -22,6 +22,9 @@ #include "qgsvectorlayer.h" #include "qgsgeometrycheckerror.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryContainedCheckError : public QgsGeometryCheckError { public: @@ -46,6 +49,9 @@ class ANALYSIS_EXPORT QgsGeometryContainedCheckError : public QgsGeometryCheckEr QPair mContainingFeature; }; +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryContainedCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.h b/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.h index d6fa8278b395..dcf9b1ca7f35 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.h @@ -23,6 +23,9 @@ class QgsSpatialIndex; +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometryFollowBoundariesCheck : public QgsGeometryCheck { public: diff --git a/src/analysis/vector/geometry_checker/qgsgeometrysegmentlengthcheck.h b/src/analysis/vector/geometry_checker/qgsgeometrysegmentlengthcheck.h index 8a62bb02ed78..6cb04a2f026c 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrysegmentlengthcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrysegmentlengthcheck.h @@ -20,6 +20,9 @@ #include "qgsgeometrycheck.h" +/** + * \ingroup analysis + */ class ANALYSIS_EXPORT QgsGeometrySegmentLengthCheck : public QgsGeometryCheck { public: From d3f64deb80ffd9af7a1177d090f8b9d994705a74 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 11:54:43 +0100 Subject: [PATCH 047/266] Further doc improvements --- .../geometry_checker/qgsgeometrycheck.sip.in | 4 +++ .../qgsgeometrycheckerutils.sip.in | 15 +++++++- .../geometry_checker/qgsgeometrycheck.h | 34 +++++++++++++++++++ .../qgsgeometrycheckerutils.h | 3 +- .../qgsgeometryisvalidcheck.h | 12 +++++++ tests/code_layout/acceptable_missing_doc.py | 3 +- 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheck.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheck.sip.in index 9e28feaba7d0..de83a1d32310 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheck.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheck.sip.in @@ -164,6 +164,10 @@ Returns the context protected: + + + + }; /************************************************************************ diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in index 4c4d84f02b52..a1c57694a1cf 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in @@ -10,7 +10,6 @@ - class QgsGeometryCheckerUtils { %Docstring @@ -28,8 +27,16 @@ Contains utilities required for geometry checks. #include "qgsgeometrycheckerutils.h" %End public: + class LayerFeature { +%Docstring + +A layer feature combination to uniquely identify and access a feature in +a set of layers. + +.. versionadded:: 3.4 +%End %TypeHeaderCode #include "qgsgeometrycheckerutils.h" @@ -79,6 +86,12 @@ Returns if the geometry is reprojected to the map CRS or not. class LayerFeatures { +%Docstring + +Contains a set of layers and feature ids in those layers to pass to a geometry check. + +.. versionadded:: 3.4 +%End %TypeHeaderCode #include "qgsgeometrycheckerutils.h" diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheck.h b/src/analysis/vector/geometry_checker/qgsgeometrycheck.h index 97db5300cd40..d193402b55e8 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheck.h @@ -253,14 +253,48 @@ class ANALYSIS_EXPORT QgsGeometryCheck const QgsGeometryCheckContext *context() const { return mContext; } protected: + + /** + * Returns all layers and feature ids. + * + * \since QGIS 3.4 + * \note Not available in Python bindings + */ QMap allLayerFeatureIds( const QMap &featurePools ) const SIP_SKIP; + + /** + * Replaces a part in a feature geometry. + * + * \since QGIS 3.4 + * \note Not available in Python bindings + */ void replaceFeatureGeometryPart( const QMap &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, QgsAbstractGeometry *newPartGeom, Changes &changes ) const SIP_SKIP; + + /** + * Deletes a part of a feature geometry. + * + * \since QGIS 3.4 + * \note Not available in Python bindings + */ void deleteFeatureGeometryPart( const QMap &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, Changes &changes ) const SIP_SKIP; + + /** + * Deletes a ring in a feature geometry. + * + * \since QGIS 3.4 + * \note Not available in Python bindings + */ void deleteFeatureGeometryRing( const QMap &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, int ringIdx, Changes &changes ) const SIP_SKIP; const QgsGeometryCheckContext *mContext; QVariantMap mConfiguration; + /** + * Determin the scale factor of a layer to the map coordiante reference system. + * + * \since QGIS 3.4 + * \note Not available in Python bindings + */ double scaleFactor( const QPointer &layer ) const SIP_SKIP; }; diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h index 4a3c6f5883fb..9a91b4e60e9b 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h @@ -39,6 +39,7 @@ class QgsFeedback; class ANALYSIS_EXPORT QgsGeometryCheckerUtils { public: + /** * \ingroup analysis * @@ -128,7 +129,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils * \ingroup analysis * * An iterator over all features in a QgsGeometryCheckerUtils::LayerFeatures. - * + * * \since QGIS 3.4 */ class iterator diff --git a/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h b/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h index bbfc10d87a43..27b8594b5ef9 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h @@ -20,6 +20,14 @@ email : matthias@opengis.ch #include "qgssinglegeometrycheck.h" +/** + * \ingroup analysis + * + * An error for a QgsGeometryIsValid check. + * The description is delivered by the underlying check engine, either GEOS or QGIS internal. + * + * \since QGIS 3.4 + */ class ANALYSIS_EXPORT QgsGeometryIsValidCheckError : public QgsSingleGeometryCheckError { public: @@ -40,6 +48,10 @@ class ANALYSIS_EXPORT QgsGeometryIsValidCheckError : public QgsSingleGeometryChe class ANALYSIS_EXPORT QgsGeometryIsValidCheck : public QgsSingleGeometryCheck { public: + + /** + * Creates a new is valid check with the provided \a context. No options are supported in \a configuration. + */ explicit QgsGeometryIsValidCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ); QList compatibleGeometryTypes() const override; diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index c8f3ceed1aea..21fe479dd1e8 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -489,7 +489,8 @@ "QgsGeometrySelfIntersectionCheck": ["QgsGeometrySelfIntersectionCheck(const QgsGeometryCheckContext *context, const QVariantMap &configuration=QVariantMap())", "ResolutionMethod"], "QgsGeometryContainedCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryContainedCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], "QgsGeometryPointInPolygonCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryPointInPolygonCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], - "QgsGeometryDuplicateNodesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryDuplicateNodesCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"] + "QgsGeometryDuplicateNodesCheck": ["factoryDescription()", "factoryId()", "factoryCompatibleGeometryTypes()", "factoryIsCompatible(QgsVectorLayer *layer)", "ResolutionMethod", "factoryCheckType()", "QgsGeometryDuplicateNodesCheck(QgsGeometryCheckContext *context, const QVariantMap &configuration)"], + "QgsGeometryChecker": ["getContext() const", "featurePools() const", "setMergeAttributeIndices(const QMap< QString, int > &mergeAttributeIndices)", "progressValue(int value)", "getChecks() const", "errorAdded(QgsGeometryCheckError *error)", "QgsGeometryChecker(const QList< QgsGeometryCheck * > &checks, QgsGeometryCheckContext *context, const QMap< QString, QgsFeaturePool * > &featurePools)", "fixError(QgsGeometryCheckError *error, int method, bool triggerRepaint=false)", "errorUpdated(QgsGeometryCheckError *error, bool statusChanged)", "execute(int *totalSteps=nullptr)", "getMessages() const"] } ACCEPTABLE_MISSING_ADDED_NOTE = [ From 929df96e207cc3d0ec6a33b6999859eb8b750ba6 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 12:17:13 +0100 Subject: [PATCH 048/266] Remaining docs --- .../qgsgeometrycheckfactory.sip.in | 7 +++++ .../qgssinglegeometrycheck.sip.in | 8 +++++ .../geometry_checker/qgsgeometrycheck.h | 15 ++++++--- .../qgsgeometrycheckerutils.h | 31 ++++++++++++++++++- .../qgsgeometrycheckfactory.h | 5 +++ .../qgsgeometryisvalidcheck.h | 6 ++++ .../geometry_checker/qgssinglegeometrycheck.h | 8 +++++ 7 files changed, 74 insertions(+), 6 deletions(-) diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in index ea77c0dc44c5..f3bd002848e8 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckfactory.sip.in @@ -66,6 +66,13 @@ The type of this check. template class QgsGeometryCheckFactoryT : QgsGeometryCheckFactory { +%Docstring +Template to create a factory for a geometry check. + +.. note:: + + Not available in Python bindings. +%End %TypeHeaderCode #include "qgsgeometrycheckfactory.h" diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in index 9c50a9accf66..33faabca6084 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in @@ -28,7 +28,11 @@ An error from a QgsSingleGeometryCheck. #include "qgssinglegeometrycheck.h" %End public: + QgsSingleGeometryCheckError( const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, const QgsVertexId &vertexId = QgsVertexId() ); +%Docstring +Creates a new single geometry check error. +%End virtual ~QgsSingleGeometryCheckError(); @@ -93,7 +97,11 @@ The single error can be obtained via singleError. #include "qgssinglegeometrycheck.h" %End public: + QgsGeometryCheckErrorSingle( QgsSingleGeometryCheckError *singleError, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ); +%Docstring +Creates a new error for a :py:class:`QgsSingleGeometryCheck`. +%End QgsSingleGeometryCheckError *singleError() const; %Docstring diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheck.h b/src/analysis/vector/geometry_checker/qgsgeometrycheck.h index d193402b55e8..8cbff8af7d69 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheck.h @@ -173,6 +173,11 @@ class ANALYSIS_EXPORT QgsGeometryCheck virtual ~QgsGeometryCheck() = default; #ifndef SIP_RUN + + /** + * Returns the configuration value with the \a name, saved in the QGIS settings for + * this geometry check. If no configuration could be found, \a defaultValue is returned. + */ template T configurationValue( const QString &name, const QVariant &defaultValue = QVariant() ) { @@ -265,24 +270,24 @@ class ANALYSIS_EXPORT QgsGeometryCheck /** * Replaces a part in a feature geometry. * - * \since QGIS 3.4 * \note Not available in Python bindings + * \since QGIS 3.4 */ void replaceFeatureGeometryPart( const QMap &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, QgsAbstractGeometry *newPartGeom, Changes &changes ) const SIP_SKIP; /** * Deletes a part of a feature geometry. * - * \since QGIS 3.4 * \note Not available in Python bindings + * \since QGIS 3.4 */ void deleteFeatureGeometryPart( const QMap &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, Changes &changes ) const SIP_SKIP; /** * Deletes a ring in a feature geometry. * - * \since QGIS 3.4 * \note Not available in Python bindings + * \since QGIS 3.4 */ void deleteFeatureGeometryRing( const QMap &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, int ringIdx, Changes &changes ) const SIP_SKIP; @@ -290,10 +295,10 @@ class ANALYSIS_EXPORT QgsGeometryCheck QVariantMap mConfiguration; /** - * Determin the scale factor of a layer to the map coordiante reference system. + * Determines the scale factor of a layer to the map coordinate reference system. * - * \since QGIS 3.4 * \note Not available in Python bindings + * \since QGIS 3.4 */ double scaleFactor( const QPointer &layer ) const SIP_SKIP; }; diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h index 9a91b4e60e9b..6a5a47a11ade 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h @@ -113,6 +113,10 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils { public: #ifndef SIP_RUN + + /** + * Creates a new set of layer and features. + */ LayerFeatures( const QMap &featurePools, const QMap &featureIds, const QList &geometryTypes, @@ -120,6 +124,9 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils const QgsGeometryCheckContext *context, bool useMapCrs = false ); + /** + * Creates a new set of layer and features. + */ LayerFeatures( const QMap &featurePools, const QList &layerIds, const QgsRectangle &extent, const QList &geometryTypes, @@ -135,11 +142,33 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils class iterator { public: + + /** + * Creates a new iterator. + */ iterator( const QList::const_iterator &layerIt, const LayerFeatures *parent ); + + /** + * Copies the iterator \a rh. + */ iterator( const iterator &rh ); ~iterator(); + + /** + * Increments the item the iterator currently points to by one and + * returns the new iterator. + */ const iterator &operator++(); - iterator operator++( int ); + + /** + * Increments the item the iterator currently points to by \a n and + * returns the new iterator. + */ + iterator operator++( int n ); + + /** + * Dereferences the item at the current iterator location. + */ const QgsGeometryCheckerUtils::LayerFeature &operator*() const; bool operator!=( const iterator &other ); diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h index 691b4e1c9580..8b1eb3302a07 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h @@ -82,6 +82,11 @@ class ANALYSIS_EXPORT QgsGeometryCheckFactory SIP_ABSTRACT virtual QgsGeometryCheck::CheckType checkType() const = 0; }; +/** + * Template to create a factory for a geometry check. + * + * \note Not available in Python bindings. + */ template class QgsGeometryCheckFactoryT : public QgsGeometryCheckFactory { diff --git a/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h b/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h index 27b8594b5ef9..9a63a943fc45 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometryisvalidcheck.h @@ -31,6 +31,10 @@ email : matthias@opengis.ch class ANALYSIS_EXPORT QgsGeometryIsValidCheckError : public QgsSingleGeometryCheckError { public: + + /** + * Creates a new is valid check error. + */ QgsGeometryIsValidCheckError( const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, const QString &errorDescription ); QString description() const override; @@ -40,6 +44,8 @@ class ANALYSIS_EXPORT QgsGeometryIsValidCheckError : public QgsSingleGeometryChe }; /** + * \ingroup analysis + * * Checks if geometries are valid using the backend configured in the QGIS settings. * This does not offer any fixes but makes sure that all geometries are valid. * diff --git a/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h b/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h index 461ca9605594..4263b1ecdaef 100644 --- a/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h +++ b/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h @@ -39,6 +39,10 @@ class QgsSingleGeometryCheck; class ANALYSIS_EXPORT QgsSingleGeometryCheckError { public: + + /** + * Creates a new single geometry check error. + */ QgsSingleGeometryCheckError( const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, const QgsVertexId &vertexId = QgsVertexId() ) : mCheck( check ) , mGeometry( geometry ) @@ -111,6 +115,10 @@ class ANALYSIS_EXPORT QgsSingleGeometryCheckError class ANALYSIS_EXPORT QgsGeometryCheckErrorSingle : public QgsGeometryCheckError { public: + + /** + * Creates a new error for a QgsSingleGeometryCheck. + */ QgsGeometryCheckErrorSingle( QgsSingleGeometryCheckError *singleError, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ); /** From cb766d7f72a15f06ab8dd4493f608b2ca402804d Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 15:29:26 +0100 Subject: [PATCH 049/266] Make travis happy --- .../vector/geometry_checker/qgssinglegeometrycheck.sip.in | 4 ++++ src/analysis/vector/geometry_checker/qgsgeometrycheck.h | 2 +- .../vector/geometry_checker/qgsgeometrycheckfactory.h | 1 + src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h | 4 ++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in index 33faabca6084..9692cd38a22b 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in @@ -127,8 +127,12 @@ Subclasses need to implement the processGeometry method. #include "qgssinglegeometrycheck.h" %End public: + QgsSingleGeometryCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ); +%Docstring +Creates a new single geometry check. +%End virtual void collectErrors( const QMap &featurePools, QList &errors, diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheck.h b/src/analysis/vector/geometry_checker/qgsgeometrycheck.h index 8cbff8af7d69..252fc744d9a1 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheck.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheck.h @@ -262,8 +262,8 @@ class ANALYSIS_EXPORT QgsGeometryCheck /** * Returns all layers and feature ids. * - * \since QGIS 3.4 * \note Not available in Python bindings + * \since QGIS 3.4 */ QMap allLayerFeatureIds( const QMap &featurePools ) const SIP_SKIP; diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h index 8b1eb3302a07..2a9829ca559c 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckfactory.h @@ -83,6 +83,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckFactory SIP_ABSTRACT }; /** + * \ingroup analysis * Template to create a factory for a geometry check. * * \note Not available in Python bindings. diff --git a/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h b/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h index 4263b1ecdaef..3ede02b296c8 100644 --- a/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h +++ b/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h @@ -145,6 +145,10 @@ class ANALYSIS_EXPORT QgsGeometryCheckErrorSingle : public QgsGeometryCheckError class ANALYSIS_EXPORT QgsSingleGeometryCheck : public QgsGeometryCheck { public: + + /** + * Creates a new single geometry check. + */ QgsSingleGeometryCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ) : QgsGeometryCheck( context, configuration ) From 0b39a2ea474b0df1fc113465abc39ddc91c007fd Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 16:01:59 +0100 Subject: [PATCH 050/266] Optimize space on CRS selection tab --- src/ui/qgsdatumtransformtablewidgetbase.ui | 43 +++++++++++++++++ src/ui/qgsprojectpropertiesbase.ui | 55 +++++++++++++++++----- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/src/ui/qgsdatumtransformtablewidgetbase.ui b/src/ui/qgsdatumtransformtablewidgetbase.ui index e5654038bc72..514b936fe489 100644 --- a/src/ui/qgsdatumtransformtablewidgetbase.ui +++ b/src/ui/qgsdatumtransformtablewidgetbase.ui @@ -14,6 +14,18 @@ Form + + 0 + + + 0 + + + 0 + + + 0 + @@ -67,6 +79,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/qgsprojectpropertiesbase.ui b/src/ui/qgsprojectpropertiesbase.ui index ab3ea2f3586b..87442aff998a 100644 --- a/src/ui/qgsprojectpropertiesbase.ui +++ b/src/ui/qgsprojectpropertiesbase.ui @@ -236,7 +236,7 @@ - 4 + 2 @@ -265,8 +265,8 @@ 0 0 - 563 - 833 + 680 + 883 @@ -863,8 +863,8 @@ 0 0 - 547 - 152 + 694 + 778 @@ -884,7 +884,7 @@ - + Datum Transformations @@ -938,8 +938,8 @@ 0 0 - 271 - 597 + 291 + 615 @@ -1514,8 +1514,8 @@ 0 0 - 157 - 59 + 159 + 49 @@ -1576,8 +1576,8 @@ 0 0 - 603 - 2666 + 632 + 2867 @@ -2961,6 +2961,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b87798cd8fabb6bd3a065d789b15b9ab3b5246de Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 22 Dec 2018 16:38:32 +0100 Subject: [PATCH 051/266] Make travis even happier --- .../geometry_checker/qgsgeometrycheckerror.sip.in | 4 ++-- .../geometry_checker/qgssinglegeometrycheck.sip.in | 2 +- .../vector/geometry_checker/qgsgeometrycheckerror.h | 4 ++-- .../geometry_checker/qgsgeometrycheckerutils.h | 1 - .../vector/geometry_checker/qgsgeometrygapcheck.cpp | 2 ++ .../qgsgeometrymissingvertexcheck.cpp | 2 ++ .../geometry_checker/qgsgeometryoverlapcheck.cpp | 12 +++++++----- .../qgsgeometryselfintersectioncheck.cpp | 2 ++ .../vector/geometry_checker/qgssinglegeometrycheck.h | 2 +- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in index f5e0d24d71d1..643049ec0428 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in @@ -51,7 +51,7 @@ This represents an error reported by a geometry check. ValueType valueType = ValueOther ); %Docstring Create a new geometry check error with the parent ``check`` and for the -``layerFeature`` pair at the \errorLocation. Optionally the vertex can be +``layerFeature`` pair at the ``errorLocation``. Optionally the vertex can be specified via ``vixd`` and a ``value`` with its ``value`` Type for additional information. %End @@ -157,7 +157,7 @@ If this returns true, it can be used to update existing errors after re-checking virtual void update( const QgsGeometryCheckError *other ); %Docstring -Update this error with the information from \other. +Update this error with the information from ``other``. Will be used to update existing errors whenever they are re-checked. %End diff --git a/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in b/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in index 9692cd38a22b..b78e0e58902d 100644 --- a/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in +++ b/python/analysis/auto_generated/vector/geometry_checker/qgssinglegeometrycheck.sip.in @@ -38,7 +38,7 @@ Creates a new single geometry check error. virtual void update( const QgsSingleGeometryCheckError *other ); %Docstring -Update this error with the information from \other. +Update this error with the information from ``other``. Will be used to update existing errors whenever they are re-checked. %End diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h index 90fd037041e0..0922c826171a 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h @@ -59,7 +59,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckError /** * Create a new geometry check error with the parent \a check and for the - * \a layerFeature pair at the \errorLocation. Optionally the vertex can be + * \a layerFeature pair at the \a errorLocation. Optionally the vertex can be * specified via \a vixd and a \a value with its \a value Type for * additional information. */ @@ -169,7 +169,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckError virtual bool closeMatch( QgsGeometryCheckError * /*other*/ ) const; /** - * Update this error with the information from \other. + * Update this error with the information from \a other. * Will be used to update existing errors whenever they are re-checked. */ virtual void update( const QgsGeometryCheckError *other ); diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h index 6a5a47a11ade..d16d5ce3621d 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h @@ -221,7 +221,6 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils /** * Returns the number of points in a polyline, accounting for duplicate start and end point if the polyline is closed - * \param polyLine The polyline * \returns The number of distinct points of the polyline */ static inline int polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing, bool *isClosed = nullptr ) diff --git a/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp index e20d124fa3ac..df4e46078725 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp @@ -256,6 +256,7 @@ QgsGeometryCheck::Flags QgsGeometryGapCheck::flags() const return factoryFlags(); } +///@cond private QString QgsGeometryGapCheck::factoryDescription() { return tr( "Gap" ); @@ -285,3 +286,4 @@ QgsGeometryCheck::CheckType QgsGeometryGapCheck::factoryCheckType() { return QgsGeometryCheck::LayerCheck; } +///@endcond private diff --git a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp index fbc7d4e0fddd..39434252575e 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp @@ -210,6 +210,7 @@ QList QgsGeometryMissingVertexCheck::factoryCompatibl return {QgsWkbTypes::PolygonGeometry}; } +///@cond private bool QgsGeometryMissingVertexCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); @@ -234,3 +235,4 @@ QgsGeometryCheck::CheckType QgsGeometryMissingVertexCheck::factoryCheckType() { return QgsGeometryCheck::LayerCheck; } +///@endcond private diff --git a/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp index 741d1ef04a57..6c44231bc2df 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp @@ -227,6 +227,12 @@ QString QgsGeometryOverlapCheck::factoryDescription() return tr( "Overlap" ); } +///@cond private +QgsGeometryCheck::CheckType QgsGeometryOverlapCheck::factoryCheckType() +{ + return QgsGeometryCheck::LayerCheck; +} + QString QgsGeometryOverlapCheck::factoryId() { return QStringLiteral( "QgsGeometryOverlapCheck" ); @@ -247,6 +253,7 @@ bool QgsGeometryOverlapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_S return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } +///@endcond private QgsGeometryOverlapCheckError::QgsGeometryOverlapCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsGeometry &geometry, const QgsPointXY &errorLocation, const QVariant &value, const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature ) : QgsGeometryCheckError( check, layerFeature.layer()->id(), layerFeature.feature().id(), geometry, errorLocation, QgsVertexId(), value, ValueArea ) , mOverlappedFeature( OverlappedFeature( overlappedFeature.layer(), overlappedFeature.feature().id() ) ) @@ -258,8 +265,3 @@ QString QgsGeometryOverlapCheckError::description() const { return QCoreApplication::translate( "QgsGeometryTypeCheckError", "Overlap with %1 at feature %2" ).arg( mOverlappedFeature.layerName(), QString::number( mOverlappedFeature.featureId() ) ); } - -QgsGeometryCheck::CheckType QgsGeometryOverlapCheck::factoryCheckType() -{ - return QgsGeometryCheck::LayerCheck; -} diff --git a/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp index 1f77529e7741..f6976ae0689b 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp @@ -333,6 +333,7 @@ QList QgsGeometrySelfIntersectionCheck::processGe return errors; } +///@cond private QList QgsGeometrySelfIntersectionCheck::factoryCompatibleGeometryTypes() { return {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry}; @@ -362,3 +363,4 @@ QgsGeometryCheck::CheckType QgsGeometrySelfIntersectionCheck::factoryCheckType() { return QgsGeometryCheck::FeatureNodeCheck; } +///@endcond private diff --git a/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h b/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h index 3ede02b296c8..def8d3bb6a32 100644 --- a/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h +++ b/src/analysis/vector/geometry_checker/qgssinglegeometrycheck.h @@ -53,7 +53,7 @@ class ANALYSIS_EXPORT QgsSingleGeometryCheckError virtual ~QgsSingleGeometryCheckError() = default; /** - * Update this error with the information from \other. + * Update this error with the information from \a other. * Will be used to update existing errors whenever they are re-checked. */ virtual void update( const QgsSingleGeometryCheckError *other ); From 908eaf5d3944e54259fa47f409f79bb8aaf7bb5e Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sun, 23 Dec 2018 09:52:25 +0100 Subject: [PATCH 052/266] Make more space available on CRS selection dialog --- src/gui/qgsprojectionselectiontreewidget.cpp | 27 +- src/ui/qgsprojectionselectorbase.ui | 315 +++++++++---------- 2 files changed, 153 insertions(+), 189 deletions(-) diff --git a/src/gui/qgsprojectionselectiontreewidget.cpp b/src/gui/qgsprojectionselectiontreewidget.cpp index ffe1d2830e7a..c1f46927fa94 100644 --- a/src/gui/qgsprojectionselectiontreewidget.cpp +++ b/src/gui/qgsprojectionselectiontreewidget.cpp @@ -277,7 +277,6 @@ void QgsProjectionSelectionTreeWidget::applySelection( int column, QString value lstCoordinateSystems->clearSelection(); lstRecent->clearSelection(); teProjection->clear(); - teSelected->clear(); } } @@ -772,7 +771,6 @@ void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( // Found a real CRS emit crsSelected(); - teSelected->setText( selectedName() ); updateBoundsPreview(); QList nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn ); @@ -790,10 +788,9 @@ void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( } else { - // Not an CRS - remove the highlight so the user doesn't get too confused + // Not a CRS - remove the highlight so the user doesn't get too confused current->setSelected( false ); teProjection->clear(); - teSelected->clear(); lstRecent->clearSelection(); } } @@ -872,7 +869,6 @@ void QgsProjectionSelectionTreeWidget::updateFilter() { ( *itr )->setSelected( false ); teProjection->clear(); - teSelected->clear(); } } else if ( ( *itr )->text( NameColumn ).contains( re ) @@ -996,6 +992,7 @@ void QgsProjectionSelectionTreeWidget::updateBoundsPreview() return; QgsRectangle rect = currentCrs.bounds(); + QString extentString = tr( "Extent not known" ); if ( !qgsDoubleNear( rect.area(), 0.0 ) ) { QgsGeometry geom; @@ -1017,22 +1014,22 @@ void QgsProjectionSelectionTreeWidget::updateBoundsPreview() mAreaCanvas->setExtent( extent ); mAreaCanvas->refresh(); mPreviewBand->show(); - QString extentString = tr( "Extent: %1, %2, %3, %4" ) - .arg( rect.xMinimum(), 0, 'f', 2 ) - .arg( rect.yMinimum(), 0, 'f', 2 ) - .arg( rect.xMaximum(), 0, 'f', 2 ) - .arg( rect.yMaximum(), 0, 'f', 2 ); - QString proj4String = tr( "Proj4: %1" ).arg( selectedProj4String() ); - teProjection->setText( extentString + "\n" + proj4String ); + extentString = QStringLiteral( "%1, %2, %3, %4" ) + .arg( rect.xMinimum(), 0, 'f', 2 ) + .arg( rect.yMinimum(), 0, 'f', 2 ) + .arg( rect.xMaximum(), 0, 'f', 2 ) + .arg( rect.yMaximum(), 0, 'f', 2 ); + } else { mPreviewBand->hide(); mAreaCanvas->zoomToFullExtent(); - QString extentString = tr( "Extent: Extent not known" ); - QString proj4String = tr( "Proj4: %1" ).arg( selectedProj4String() ); - teProjection->setText( extentString + "\n" + proj4String ); } + + QString extentHtml = QStringLiteral( "
%1
%2
" ).arg( tr( "Extent" ), extentString ); + QString proj4String = tr( "
%1
%2
" ).arg( tr( "Proj4" ), selectedProj4String() ); + teProjection->setText( QStringLiteral( "

%1

" ).arg( selectedName() ) + extentHtml + proj4String + QLatin1String( "
" ) ); } QStringList QgsProjectionSelectionTreeWidget::authorities() diff --git a/src/ui/qgsprojectionselectorbase.ui b/src/ui/qgsprojectionselectorbase.ui index 56bc0179a5f5..93adba34ed59 100644 --- a/src/ui/qgsprojectionselectorbase.ui +++ b/src/ui/qgsprojectionselectorbase.ui @@ -36,6 +36,16 @@ 0 + + + + Use this option to treat all coordinates as Cartesian coordinates in an unknown reference system. + + + No projection (or unknown/non-Earth projection) + + + @@ -57,168 +67,7 @@ 0 - - - - Qt::Vertical - - - true - - - - - 0 - 0 - - - - - 0 - 105 - - - - - 16777215 - 200 - - - - true - - - false - - - true - - - 3 - - - - Coordinate Reference System - - - - - Authority ID - - - - - ID - - - - - - - 0 - - - - - 0 - - - - - - 75 - true - - - - Coordinate reference systems of the world - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 9 - - - - Hide deprecated CRSs - - - - - - - - - true - - - true - - - 3 - - - - Coordinate Reference System - - - - - Authority ID - - - - - ID - - - - - - - - - - - 75 - true - - - - Selected CRS - - - - - - - true - - - - - - - - - - + @@ -232,7 +81,7 @@ - + @@ -245,6 +94,135 @@ + + + + + 0 + 0 + + + + + 0 + 105 + + + + + 16777215 + 200 + + + + true + + + false + + + true + + + 3 + + + + Coordinate Reference System + + + + + Authority ID + + + + + ID + + + + + + + + 0 + + + + + true + + + true + + + 3 + + + + Coordinate Reference System + + + + + Authority ID + + + + + ID + + + + + + + + 0 + + + + + + 75 + true + + + + Coordinate reference systems of the world + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 9 + + + + Hide deprecated CRSs + + + + + + + @@ -318,16 +296,6 @@ - - - - Use this option to treat all coordinates as Cartesian coordinates in an unknown reference system. - - - No projection (or unknown/non-Earth projection) - - -
@@ -349,7 +317,6 @@ lstRecent cbxHideDeprecated lstCoordinateSystems - teSelected From 59cfa5025d4c4051b07b9783b98b26b6c5e6d0d8 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sun, 23 Dec 2018 10:05:00 +0100 Subject: [PATCH 053/266] Limit size of projection preview map --- src/ui/qgsprojectionselectorbase.ui | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ui/qgsprojectionselectorbase.ui b/src/ui/qgsprojectionselectorbase.ui index 93adba34ed59..66f0497c799a 100644 --- a/src/ui/qgsprojectionselectorbase.ui +++ b/src/ui/qgsprojectionselectorbase.ui @@ -231,7 +231,7 @@ - + 0 0 @@ -242,12 +242,6 @@ 0 - - - 16777215 - 16777215 - - 0 @@ -270,7 +264,7 @@ - + 0 0 From f301f944bd2ada224c60c1cc5b4c83d2406871c3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Dec 2018 15:55:02 +1000 Subject: [PATCH 054/266] Revert QgsBlockingNetworkRequest Too many issues... I'm unsure if this is even possible now... --- .../qgsblockingnetworkrequest.sip.in | 146 ------- .../auto_generated/qgsnetworkreply.sip.in | 115 ------ python/core/core_auto.sip | 2 - src/core/CMakeLists.txt | 4 - src/core/qgsblockingnetworkrequest.cpp | 391 ------------------ src/core/qgsblockingnetworkrequest.h | 228 ---------- src/core/qgsnetworkreply.cpp | 75 ---- src/core/qgsnetworkreply.h | 148 ------- src/providers/arcgisrest/qgsafsprovider.cpp | 8 + .../arcgisrest/qgsarcgisrestutils.cpp | 62 ++- tests/src/python/CMakeLists.txt | 1 - .../python/test_qgsblockingnetworkrequest.py | 104 ----- 12 files changed, 55 insertions(+), 1229 deletions(-) delete mode 100644 python/core/auto_generated/qgsblockingnetworkrequest.sip.in delete mode 100644 python/core/auto_generated/qgsnetworkreply.sip.in delete mode 100644 src/core/qgsblockingnetworkrequest.cpp delete mode 100644 src/core/qgsblockingnetworkrequest.h delete mode 100644 src/core/qgsnetworkreply.cpp delete mode 100644 src/core/qgsnetworkreply.h delete mode 100644 tests/src/python/test_qgsblockingnetworkrequest.py diff --git a/python/core/auto_generated/qgsblockingnetworkrequest.sip.in b/python/core/auto_generated/qgsblockingnetworkrequest.sip.in deleted file mode 100644 index f4c231aa90a2..000000000000 --- a/python/core/auto_generated/qgsblockingnetworkrequest.sip.in +++ /dev/null @@ -1,146 +0,0 @@ -/************************************************************************ - * This file has been generated automatically from * - * * - * src/core/qgsblockingnetworkrequest.h * - * * - * Do not edit manually ! Edit header and run scripts/sipify.pl again * - ************************************************************************/ - - - -class QgsBlockingNetworkRequest : QObject -{ -%Docstring -A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy -and authentication settings. - -This class should be used whenever a blocking network request is required. Unlike implementations -which rely on QApplication.processEvents() or creation of a QEventLoop, this class is completely -thread safe and can be used on either the main thread or background threads without issue. - -Redirects are automatically handled by the class. - -After completion of a request, the reply content should be retrieved by calling getReplyContent(). -This method returns a QgsNetworkReplyContent container, which is safe and cheap to copy and pass -between threads without issue. - -.. versionadded:: 3.6 -%End - -%TypeHeaderCode -#include "qgsblockingnetworkrequest.h" -%End - public: - - enum ErrorCode - { - NoError, - NetworkError, - TimeoutError, - ServerExceptionError, - }; - - explicit QgsBlockingNetworkRequest(); -%Docstring -Constructor for QgsBlockingNetworkRequest -%End - - ~QgsBlockingNetworkRequest(); - - ErrorCode get( QNetworkRequest &request, bool forceRefresh = false, QgsFeedback *feedback = 0 ); -%Docstring -Performs a "get" operation on the specified ``request``. - -If ``forceRefresh`` is false then previously cached replies may be used for the request. If -it is set to true then a new query is always performed. - -If an authCfg() has been set, then any authentication configuration required will automatically be applied to -``request``. There is no need to manually apply the authentication to the request prior to calling -this method. - -The optional ``feedback`` argument can be used to abort ongoing requests. - -The method will return NoError if the get operation was successful. The contents of the reply can be retrieved -by calling reply(). - -If an error was encountered then a specific ErrorCode will be returned, and a detailed error message -can be retrieved by calling errorMessage(). - -.. seealso:: :py:func:`post` -%End - - ErrorCode post( QNetworkRequest &request, const QByteArray &data, bool forceRefresh = false, QgsFeedback *feedback = 0 ); -%Docstring -Performs a "post" operation on the specified ``request``, using the given ``data``. - -If ``forceRefresh`` is false then previously cached replies may be used for the request. If -it is set to true then a new query is always performed. - -If an authCfg() has been set, then any authentication configuration required will automatically be applied to -``request``. There is no need to manually apply the authentication to the request prior to calling -this method. - -The optional ``feedback`` argument can be used to abort ongoing requests. - -The method will return NoError if the get operation was successful. The contents of the reply can be retrieved -by calling reply(). - -If an error was encountered then a specific ErrorCode will be returned, and a detailed error message -can be retrieved by calling errorMessage(). - -.. seealso:: :py:func:`get` -%End - - QString errorMessage() const; -%Docstring -Returns the error message string, after a get() or post() request has been made.\ -%End - - QgsNetworkReplyContent reply() const; -%Docstring -Returns the content of the network reply, after a get() or post() request has been made. -%End - - QString authCfg() const; -%Docstring -Returns the authentication config id which will be used during the request. - -.. seealso:: :py:func:`setAuthCfg` -%End - - void setAuthCfg( const QString &authCfg ); -%Docstring -Sets the authentication config id which should be used during the request. - -.. seealso:: :py:func:`authCfg` -%End - - public slots: - - void abort(); -%Docstring -Aborts the network request immediately. -%End - - signals: - - void downloadProgress( qint64, qint64 ); -%Docstring -Emitted when when data arrives during a request. -%End - - void downloadFinished(); -%Docstring -Emitted once a request has finished downloading. -%End - -}; - - -/************************************************************************ - * This file has been generated automatically from * - * * - * src/core/qgsblockingnetworkrequest.h * - * * - * Do not edit manually ! Edit header and run scripts/sipify.pl again * - ************************************************************************/ diff --git a/python/core/auto_generated/qgsnetworkreply.sip.in b/python/core/auto_generated/qgsnetworkreply.sip.in deleted file mode 100644 index 5edcb1b62f25..000000000000 --- a/python/core/auto_generated/qgsnetworkreply.sip.in +++ /dev/null @@ -1,115 +0,0 @@ -/************************************************************************ - * This file has been generated automatically from * - * * - * src/core/qgsnetworkreply.h * - * * - * Do not edit manually ! Edit header and run scripts/sipify.pl again * - ************************************************************************/ - - - -class QgsNetworkReplyContent -{ -%Docstring -Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between threads. - -.. versionadded:: 3.6 -%End - -%TypeHeaderCode -#include "qgsnetworkreply.h" -%End - public: - - QgsNetworkReplyContent(); -%Docstring -Default constructor for an empty reply. -%End - - explicit QgsNetworkReplyContent( QNetworkReply *reply ); -%Docstring -Constructor for QgsNetworkReplyContent, populated from the specified ``reply``. -%End - - void clear(); -%Docstring -Clears the reply, resetting it back to a default, empty reply. -%End - - QVariant attribute( QNetworkRequest::Attribute code ) const; -%Docstring -Returns the attribute associated with the ``code``. If the attribute has not been set, it returns an -invalid QVariant. - -You can expect the default values listed in QNetworkRequest.Attribute to be -applied to the values returned by this function. - -.. seealso:: :py:func:`attributes` -%End - - - QByteArray content() const; -%Docstring -Returns the raw reply content. -%End - - QNetworkReply::NetworkError error() const; -%Docstring -Returns the reply's error message, or QNetworkReply.NoError if no -error was encountered. - -.. seealso:: :py:func:`errorString` -%End - - QString errorString() const; -%Docstring -Returns the error text for the reply, or an empty string if no -error was encountered. - -.. seealso:: :py:func:`error` -%End - - - bool hasRawHeader( const QByteArray &headerName ) const; -%Docstring -Returns true if the reply contains a header with the specified ``headerName``. - -.. seealso:: :py:func:`rawHeaderPairs` - -.. seealso:: :py:func:`rawHeaderList` - -.. seealso:: :py:func:`rawHeader` -%End - - QList rawHeaderList() const; -%Docstring -Returns a list of raw header names contained within the reply. - -.. seealso:: :py:func:`rawHeaderPairs` - -.. seealso:: :py:func:`hasRawHeader` - -.. seealso:: :py:func:`rawHeader` -%End - - QByteArray rawHeader( const QByteArray &headerName ) const; -%Docstring -Returns the content of the header with the specified ``headerName``, or an -empty QByteArray if the specified header was not found in the reply. - -.. seealso:: :py:func:`rawHeaderPairs` - -.. seealso:: :py:func:`hasRawHeader` - -.. seealso:: :py:func:`rawHeaderList` -%End - -}; - -/************************************************************************ - * This file has been generated automatically from * - * * - * src/core/qgsnetworkreply.h * - * * - * Do not edit manually ! Edit header and run scripts/sipify.pl again * - ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index f2dc3f5b04f3..a1abcd32cb14 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -80,7 +80,6 @@ %Include auto_generated/qgsmargins.sip %Include auto_generated/qgsmimedatautils.sip %Include auto_generated/qgsmultirenderchecker.sip -%Include auto_generated/qgsnetworkreply.sip %Include auto_generated/qgsobjectcustomproperties.sip %Include auto_generated/qgsogcutils.sip %Include auto_generated/qgsoptional.sip @@ -313,7 +312,6 @@ %Include auto_generated/qgsactionscoperegistry.sip %Include auto_generated/qgsanimatedicon.sip %Include auto_generated/qgsauxiliarystorage.sip -%Include auto_generated/qgsblockingnetworkrequest.sip %Include auto_generated/qgsbrowsermodel.sip %Include auto_generated/qgsbrowserproxymodel.sip %Include auto_generated/qgscoordinatereferencesystem.sip diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4c0dffe0f8ae..d1b53c57906d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -152,7 +152,6 @@ SET(QGIS_CORE_SRCS qgsattributeeditorelement.cpp qgsauxiliarystorage.cpp qgsbearingutils.cpp - qgsblockingnetworkrequest.cpp qgsbrowsermodel.cpp qgsbrowserproxymodel.cpp qgscachedfeatureiterator.cpp @@ -258,7 +257,6 @@ SET(QGIS_CORE_SRCS qgsnetworkcontentfetcher.cpp qgsnetworkcontentfetcherregistry.cpp qgsnetworkcontentfetchertask.cpp - qgsnetworkreply.cpp qgsnetworkreplyparser.cpp qgsobjectcustomproperties.cpp qgsofflineediting.cpp @@ -596,7 +594,6 @@ SET(QGIS_CORE_MOC_HDRS qgsactionscoperegistry.h qgsanimatedicon.h qgsauxiliarystorage.h - qgsblockingnetworkrequest.h qgsbrowsermodel.h qgsbrowserproxymodel.h qgscoordinatereferencesystem.h @@ -911,7 +908,6 @@ SET(QGIS_CORE_HDRS qgsmargins.h qgsmimedatautils.h qgsmultirenderchecker.h - qgsnetworkreply.h qgsobjectcustomproperties.h qgsogcutils.h qgsoptional.h diff --git a/src/core/qgsblockingnetworkrequest.cpp b/src/core/qgsblockingnetworkrequest.cpp deleted file mode 100644 index 2b0cd6f4cfea..000000000000 --- a/src/core/qgsblockingnetworkrequest.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/*************************************************************************** - qgsblockingnetworkrequest.cpp - ----------------------------- - begin : November 2018 - copyright : (C) 2018 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 "qgsblockingnetworkrequest.h" -#include "qgslogger.h" -#include "qgsapplication.h" -#include "qgsnetworkaccessmanager.h" -#include "qgsauthmanager.h" -#include "qgsmessagelog.h" -#include "qgsfeedback.h" -#include -#include -#include -#include -#include -#include -#include - -const qint64 READ_BUFFER_SIZE_HINT = 1024 * 1024; - -QgsBlockingNetworkRequest::QgsBlockingNetworkRequest() -{ - connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::requestTimedOut, this, &QgsBlockingNetworkRequest::requestTimedOut ); -} - -QgsBlockingNetworkRequest::~QgsBlockingNetworkRequest() -{ - abort(); -} - -void QgsBlockingNetworkRequest::requestTimedOut( QNetworkReply *reply ) -{ - if ( reply == mReply ) - mTimedout = true; -} - -QString QgsBlockingNetworkRequest::authCfg() const -{ - return mAuthCfg; -} - -void QgsBlockingNetworkRequest::setAuthCfg( const QString &authCfg ) -{ - mAuthCfg = authCfg; -} - -QgsBlockingNetworkRequest::ErrorCode QgsBlockingNetworkRequest::get( QNetworkRequest &request, bool forceRefresh, QgsFeedback *feedback ) -{ - return doRequest( Get, request, forceRefresh, feedback ); -} - -QgsBlockingNetworkRequest::ErrorCode QgsBlockingNetworkRequest::post( QNetworkRequest &request, const QByteArray &data, bool forceRefresh, QgsFeedback *feedback ) -{ - mPostData = data; - return doRequest( Post, request, forceRefresh, feedback ); -} - -QgsBlockingNetworkRequest::ErrorCode QgsBlockingNetworkRequest::doRequest( QgsBlockingNetworkRequest::Method method, QNetworkRequest &request, bool forceRefresh, QgsFeedback *feedback ) -{ - mMethod = method; - mFeedback = feedback; - - abort(); // cancel previous - mIsAborted = false; - mTimedout = false; - mGotNonEmptyResponse = false; - - mErrorMessage.clear(); - mErrorCode = NoError; - mForceRefresh = forceRefresh; - mReplyContent.clear(); - - if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg ) ) - { - mErrorCode = NetworkError; - mErrorMessage = errorMessageFailedAuth(); - QgsMessageLog::logMessage( mErrorMessage, tr( "Network" ) ); - return NetworkError; - } - - QgsDebugMsgLevel( QStringLiteral( "Calling: %1" ).arg( request.url().toString() ), 2 ); - - request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, forceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache ); - request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); - - // QWaitCondition based producer/consumer problem - // taken from http://doc.qt.io/qt-5/qtcore-threads-waitconditions-example.html - // in this case the buffer has size 1 and potentially contains authentication requests - // from the producer (downloader thread), which the consumer (main thread) needs to - // handle - QWaitCondition authRequestBufferNotEmpty; - QWaitCondition authRequestBufferNotFull; - QMutex waitConditionMutex; - - bool threadFinished = false; - bool success = false; - - if ( mFeedback ) - connect( mFeedback, &QgsFeedback::canceled, this, &QgsBlockingNetworkRequest::abort ); - - std::function downloaderFunction = [ this, request, &waitConditionMutex, &authRequestBufferNotEmpty, &authRequestBufferNotFull, &threadFinished, &success ]() - { - if ( QThread::currentThread() != QgsApplication::instance()->thread() ) - QgsNetworkAccessManager::instance( Qt::DirectConnection ); - - success = true; - - switch ( mMethod ) - { - case Get: - mReply = QgsNetworkAccessManager::instance()->get( request ); - break; - - case Post: - mReply = QgsNetworkAccessManager::instance()->post( request, mPostData ); - break; - }; - - mReply->setReadBufferSize( READ_BUFFER_SIZE_HINT ); - if ( mFeedback ) - connect( mFeedback, &QgsFeedback::canceled, mReply, &QNetworkReply::abort ); - - if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkReply( mReply, mAuthCfg ) ) - { - mErrorCode = NetworkError; - mErrorMessage = errorMessageFailedAuth(); - QgsMessageLog::logMessage( mErrorMessage, tr( "Network" ) ); - authRequestBufferNotEmpty.wakeAll(); - success = false; - } - else - { - // We are able to use direct connection here, because we - // * either run on the thread mReply lives in, so DirectConnection is standard and safe anyway - // * or the owner thread of mReply is currently not doing anything because it's blocked in future.waitForFinished() (if it is the main thread) - connect( mReply, &QNetworkReply::finished, this, &QgsBlockingNetworkRequest::replyFinished, Qt::DirectConnection ); - connect( mReply, &QNetworkReply::downloadProgress, this, &QgsBlockingNetworkRequest::replyProgress, Qt::DirectConnection ); - - auto resumeMainThread = [&waitConditionMutex, &authRequestBufferNotEmpty, &authRequestBufferNotFull ]() - { - // when this method is called we have "produced" a single authentication request -- so the buffer is now full - // and it's time for the "consumer" (main thread) to do its part - waitConditionMutex.lock(); - authRequestBufferNotFull.wait( &waitConditionMutex ); - waitConditionMutex.unlock(); - - waitConditionMutex.lock(); - authRequestBufferNotEmpty.wakeAll(); - waitConditionMutex.unlock(); - }; - - connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::authenticationRequired, this, resumeMainThread, Qt::DirectConnection ); - connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::proxyAuthenticationRequired, this, resumeMainThread, Qt::DirectConnection ); - -#ifndef QT_NO_SSL - connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::sslErrors, this, resumeMainThread, Qt::DirectConnection ); -#endif - QEventLoop loop; - connect( this, &QgsBlockingNetworkRequest::downloadFinished, &loop, &QEventLoop::quit, Qt::DirectConnection ); - loop.exec(); - } - waitConditionMutex.lock(); - threadFinished = true; - authRequestBufferNotEmpty.wakeAll(); - waitConditionMutex.unlock(); - }; - - if ( QThread::currentThread() == QApplication::instance()->thread() ) - { - std::unique_ptr downloaderThread = qgis::make_unique( downloaderFunction ); - downloaderThread->start(); - - while ( true ) - { - waitConditionMutex.lock(); - if ( threadFinished ) - { - waitConditionMutex.unlock(); - break; - } - authRequestBufferNotEmpty.wait( &waitConditionMutex ); - - // If the downloader thread wakes us (the main thread) up and is not yet finished - // then it has "produced" an authentication request which we need to now "consume". - // The processEvents() call gives the auth manager the chance to show a dialog and - // once done with that, we can wake the downloaderThread again and continue the download. - if ( !threadFinished ) - { - waitConditionMutex.unlock(); - - QgsApplication::instance()->processEvents(); - waitConditionMutex.lock(); - authRequestBufferNotFull.wakeAll(); - waitConditionMutex.unlock(); - } - else - { - waitConditionMutex.unlock(); - } - } - // wait for thread to gracefully exit - downloaderThread->wait(); - } - else - { - downloaderFunction(); - } - return mErrorCode; -} - -void QgsBlockingNetworkRequest::abort() -{ - mIsAborted = true; - if ( mReply ) - { - mReply->deleteLater(); - mReply = nullptr; - } -} - -void QgsBlockingNetworkRequest::replyProgress( qint64 bytesReceived, qint64 bytesTotal ) -{ - QgsDebugMsgLevel( QStringLiteral( "%1 of %2 bytes downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QStringLiteral( "unknown number of" ) : QString::number( bytesTotal ) ), 2 ); - - if ( bytesReceived != 0 ) - mGotNonEmptyResponse = true; - - if ( !mIsAborted && mReply && ( !mFeedback || !mFeedback->isCanceled() ) ) - { - if ( mReply->error() == QNetworkReply::NoError ) - { - QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute ); - if ( !redirect.isNull() ) - { - // We don't want to emit downloadProgress() for a redirect - return; - } - } - } - - emit downloadProgress( bytesReceived, bytesTotal ); -} - -void QgsBlockingNetworkRequest::replyFinished() -{ - if ( !mIsAborted && mReply ) - { - if ( mReply->error() == QNetworkReply::NoError && ( !mFeedback || !mFeedback->isCanceled() ) ) - { - QgsDebugMsgLevel( QStringLiteral( "reply OK" ), 2 ); - QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute ); - if ( !redirect.isNull() ) - { - QgsDebugMsgLevel( QStringLiteral( "Request redirected." ), 2 ); - - const QUrl &toUrl = redirect.toUrl(); - mReply->request(); - if ( toUrl == mReply->url() ) - { - mErrorMessage = tr( "Redirect loop detected: %1" ).arg( toUrl.toString() ); - QgsMessageLog::logMessage( mErrorMessage, tr( "Network" ) ); - mReplyContent.clear(); - } - else - { - QNetworkRequest request( toUrl ); - - if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg ) ) - { - mReplyContent.clear(); - mErrorMessage = errorMessageFailedAuth(); - mErrorCode = NetworkError; - QgsMessageLog::logMessage( mErrorMessage, tr( "Network" ) ); - emit downloadFinished(); - return; - } - - request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, mForceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache ); - request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); - - mReply->deleteLater(); - mReply = nullptr; - - QgsDebugMsgLevel( QStringLiteral( "redirected: %1 forceRefresh=%2" ).arg( redirect.toString() ).arg( mForceRefresh ), 2 ); - switch ( mMethod ) - { - case Get: - mReply = QgsNetworkAccessManager::instance()->get( request ); - break; - - case Post: - mReply = QgsNetworkAccessManager::instance()->post( request, mPostData ); - break; - }; - - mReply->setReadBufferSize( READ_BUFFER_SIZE_HINT ); - - if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkReply( mReply, mAuthCfg ) ) - { - mReplyContent.clear(); - mErrorMessage = errorMessageFailedAuth(); - mErrorCode = NetworkError; - QgsMessageLog::logMessage( mErrorMessage, tr( "Network" ) ); - emit downloadFinished(); - return; - } - - connect( mReply, &QNetworkReply::finished, this, &QgsBlockingNetworkRequest::replyFinished, Qt::DirectConnection ); - connect( mReply, &QNetworkReply::downloadProgress, this, &QgsBlockingNetworkRequest::replyProgress, Qt::DirectConnection ); - return; - } - } - else - { - const QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance(); - - if ( nam->cache() ) - { - QNetworkCacheMetaData cmd = nam->cache()->metaData( mReply->request().url() ); - - QNetworkCacheMetaData::RawHeaderList hl; - Q_FOREACH ( const QNetworkCacheMetaData::RawHeader &h, cmd.rawHeaders() ) - { - if ( h.first != "Cache-Control" ) - hl.append( h ); - } - cmd.setRawHeaders( hl ); - - QgsDebugMsgLevel( QStringLiteral( "expirationDate:%1" ).arg( cmd.expirationDate().toString() ), 2 ); - if ( cmd.expirationDate().isNull() ) - { - cmd.setExpirationDate( QDateTime::currentDateTime().addSecs( mExpirationSec ) ); - } - - nam->cache()->updateMetaData( cmd ); - } - else - { - QgsDebugMsgLevel( QStringLiteral( "No cache!" ), 2 ); - } - -#ifdef QGISDEBUG - bool fromCache = mReply->attribute( QNetworkRequest::SourceIsFromCacheAttribute ).toBool(); - QgsDebugMsgLevel( QStringLiteral( "Reply was cached: %1" ).arg( fromCache ), 2 ); -#endif - - mReplyContent = QgsNetworkReplyContent( mReply ); - if ( mReplyContent.content().isEmpty() && !mGotNonEmptyResponse ) - { - mErrorMessage = tr( "empty response: %1" ).arg( mReply->errorString() ); - mErrorCode = ServerExceptionError; - QgsMessageLog::logMessage( mErrorMessage, tr( "Network" ) ); - } - } - } - else - { - mErrorMessage = mReply->errorString(); - mErrorCode = ServerExceptionError; - QgsMessageLog::logMessage( mErrorMessage, tr( "Network" ) ); - mReplyContent.clear(); - } - } - if ( mTimedout ) - mErrorCode = TimeoutError; - - if ( mReply ) - { - mReply->deleteLater(); - mReply = nullptr; - } - - emit downloadFinished(); -} - -QString QgsBlockingNetworkRequest::errorMessageFailedAuth() -{ - return tr( "network request update failed for authentication config" ); -} diff --git a/src/core/qgsblockingnetworkrequest.h b/src/core/qgsblockingnetworkrequest.h deleted file mode 100644 index 72185110d73f..000000000000 --- a/src/core/qgsblockingnetworkrequest.h +++ /dev/null @@ -1,228 +0,0 @@ -/*************************************************************************** - qgsblockingnetworkrequest.h - --------------------------- - begin : November 2018 - copyright : (C) 2018 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 QGSBLOCKINGNETWORKREQUEST_H -#define QGSBLOCKINGNETWORKREQUEST_H - -#include "qgis_core.h" -#include "qgsnetworkreply.h" -#include "qgsfeedback.h" -#include -#include -#include -#include - -class QNetworkRequest; -class QNetworkReply; - -/** - * A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy - * and authentication settings. - * - * This class should be used whenever a blocking network request is required. Unlike implementations - * which rely on QApplication::processEvents() or creation of a QEventLoop, this class is completely - * thread safe and can be used on either the main thread or background threads without issue. - * - * Redirects are automatically handled by the class. - * - * After completion of a request, the reply content should be retrieved by calling getReplyContent(). - * This method returns a QgsNetworkReplyContent container, which is safe and cheap to copy and pass - * between threads without issue. - * - * \ingroup core - * \since QGIS 3.6 - */ -class CORE_EXPORT QgsBlockingNetworkRequest : public QObject -{ - Q_OBJECT - public: - - //! Error codes - enum ErrorCode - { - NoError, //!< No error was encountered - NetworkError, //!< A network error occurred - TimeoutError, //!< Timeout was reached before a reply was received - ServerExceptionError, //!< An exception was raised by the server - }; - - //! Constructor for QgsBlockingNetworkRequest - explicit QgsBlockingNetworkRequest(); - - ~QgsBlockingNetworkRequest() override; - - /** - * Performs a "get" operation on the specified \a request. - * - * If \a forceRefresh is false then previously cached replies may be used for the request. If - * it is set to true then a new query is always performed. - * - * If an authCfg() has been set, then any authentication configuration required will automatically be applied to - * \a request. There is no need to manually apply the authentication to the request prior to calling - * this method. - * - * The optional \a feedback argument can be used to abort ongoing requests. - * - * The method will return NoError if the get operation was successful. The contents of the reply can be retrieved - * by calling reply(). - * - * If an error was encountered then a specific ErrorCode will be returned, and a detailed error message - * can be retrieved by calling errorMessage(). - * - * \see post() - */ - ErrorCode get( QNetworkRequest &request, bool forceRefresh = false, QgsFeedback *feedback = nullptr ); - - /** - * Performs a "post" operation on the specified \a request, using the given \a data. - * - * If \a forceRefresh is false then previously cached replies may be used for the request. If - * it is set to true then a new query is always performed. - * - * If an authCfg() has been set, then any authentication configuration required will automatically be applied to - * \a request. There is no need to manually apply the authentication to the request prior to calling - * this method. - * - * The optional \a feedback argument can be used to abort ongoing requests. - * - * The method will return NoError if the get operation was successful. The contents of the reply can be retrieved - * by calling reply(). - * - * If an error was encountered then a specific ErrorCode will be returned, and a detailed error message - * can be retrieved by calling errorMessage(). - * - * \see get() - */ - ErrorCode post( QNetworkRequest &request, const QByteArray &data, bool forceRefresh = false, QgsFeedback *feedback = nullptr ); - - /** - * Returns the error message string, after a get() or post() request has been made.\ - */ - QString errorMessage() const { return mErrorMessage; } - - /** - * Returns the content of the network reply, after a get() or post() request has been made. - */ - QgsNetworkReplyContent reply() const { return mReplyContent; } - - /** - * Returns the authentication config id which will be used during the request. - * \see setAuthCfg() - */ - QString authCfg() const; - - /** - * Sets the authentication config id which should be used during the request. - * \see authCfg() - */ - void setAuthCfg( const QString &authCfg ); - - public slots: - - /** - * Aborts the network request immediately. - */ - void abort(); - - signals: - - /** - * Emitted when when data arrives during a request. - */ - void downloadProgress( qint64, qint64 ); - - /** - * Emitted once a request has finished downloading. - */ - void downloadFinished(); - - private slots: - void replyProgress( qint64, qint64 ); - void replyFinished(); - void requestTimedOut( QNetworkReply *reply ); - - private : - - enum Method - { - Get, - Post - }; - - //! The reply to the request - QNetworkReply *mReply = nullptr; - - Method mMethod = Get; - QByteArray mPostData; - - //! Authentication configuration ID - QString mAuthCfg; - - //! The error message associated with the last error. - QString mErrorMessage; - - //! Error code - ErrorCode mErrorCode = NoError; - - QgsNetworkReplyContent mReplyContent; - - //! Whether the request is aborted. - bool mIsAborted = false; - - //! Whether to force refresh (i.e. issue a network request and not use cache) - bool mForceRefresh = false; - - //! Whether the request has timed-out - bool mTimedout = false; - - //! Whether we already received bytes - bool mGotNonEmptyResponse = false; - - int mExpirationSec = 30; - - QPointer< QgsFeedback > mFeedback; - - ErrorCode doRequest( Method method, QNetworkRequest &request, bool forceRefresh, QgsFeedback *feedback = nullptr ); - - QString errorMessageFailedAuth(); - -}; - -///@cond PRIVATE -#ifndef SIP_RUN - -class DownloaderThread : public QThread -{ - Q_OBJECT - - public: - DownloaderThread( const std::function &function, QObject *parent = nullptr ) - : QThread( parent ) - , mFunction( function ) - { - } - - void run() override - { - mFunction(); - } - - private: - std::function mFunction; -}; - -#endif -///@endcond - -#endif // QGSBLOCKINGNETWORKREQUEST_H diff --git a/src/core/qgsnetworkreply.cpp b/src/core/qgsnetworkreply.cpp deleted file mode 100644 index 3aef4427439e..000000000000 --- a/src/core/qgsnetworkreply.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/*************************************************************************** - qgsnetworkreply.cpp - ------------------- - begin : November 2018 - copyright : (C) 2018 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 "qgsnetworkreply.h" -#include - -QgsNetworkReplyContent::QgsNetworkReplyContent( QNetworkReply *reply ) - : mContent( reply->readAll() ) - , mError( reply->error() ) - , mErrorString( reply->errorString() ) - , mRawHeaderPairs( reply->rawHeaderPairs() ) -{ - int maxAttribute = static_cast< int >( QNetworkRequest::RedirectPolicyAttribute ); -#if QT_VERSION >= QT_VERSION_CHECK( 5, 11, 0 ) - maxAttribute = static_cast< int >( QNetworkRequest::Http2DirectAttribute ); -#endif - for ( int i = 0; i <= maxAttribute; ++i ) - { - if ( reply->attribute( static_cast< QNetworkRequest::Attribute>( i ) ).isValid() ) - mAttributes[ static_cast< QNetworkRequest::Attribute>( i ) ] = reply->attribute( static_cast< QNetworkRequest::Attribute>( i ) ); - } -} - -void QgsNetworkReplyContent::clear() -{ - *this = QgsNetworkReplyContent(); -} - -QVariant QgsNetworkReplyContent::attribute( QNetworkRequest::Attribute code ) const -{ - return mAttributes.value( code ); -} - -bool QgsNetworkReplyContent::hasRawHeader( const QByteArray &headerName ) const -{ - for ( auto &header : mRawHeaderPairs ) - { - if ( header.first == headerName ) - return true; - } - return false; -} - -QList QgsNetworkReplyContent::rawHeaderList() const -{ - QList< QByteArray > res; - res.reserve( mRawHeaderPairs.length() ); - for ( auto &header : mRawHeaderPairs ) - { - res << header.first; - } - return res; -} - -QByteArray QgsNetworkReplyContent::rawHeader( const QByteArray &headerName ) const -{ - for ( auto &header : mRawHeaderPairs ) - { - if ( header.first == headerName ) - return header.second; - } - return QByteArray(); -} diff --git a/src/core/qgsnetworkreply.h b/src/core/qgsnetworkreply.h deleted file mode 100644 index e233025125f9..000000000000 --- a/src/core/qgsnetworkreply.h +++ /dev/null @@ -1,148 +0,0 @@ -/*************************************************************************** - qgsnetworkreply.h - ----------------- - begin : November 2018 - copyright : (C) 2018 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 QGSNETWORKREPLY_H -#define QGSNETWORKREPLY_H - -#include "qgis_core.h" - -#include - -/** - * Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between threads. - * \ingroup core - * \since QGIS 3.6 - */ -class CORE_EXPORT QgsNetworkReplyContent -{ - public: - - /** - * Default constructor for an empty reply. - */ - QgsNetworkReplyContent() = default; - - /** - * Constructor for QgsNetworkReplyContent, populated from the specified \a reply. - */ - explicit QgsNetworkReplyContent( QNetworkReply *reply ); - - /** - * Clears the reply, resetting it back to a default, empty reply. - */ - void clear(); - - /** - * Returns the attribute associated with the \a code. If the attribute has not been set, it returns an - * invalid QVariant. - * - * You can expect the default values listed in QNetworkRequest::Attribute to be - * applied to the values returned by this function. - * - * \see attributes() - */ - QVariant attribute( QNetworkRequest::Attribute code ) const; - -#ifndef SIP_RUN - - /** - * Returns a list of valid attributes received in the reply. - * - * \see attribute() - * \note Not available in Python bindings - */ - QMap< QNetworkRequest::Attribute, QVariant > attributes() const { return mAttributes; } -#endif - - /** - * Returns the raw reply content. - */ - QByteArray content() const - { - return mContent; - } - - /** - * Returns the reply's error message, or QNetworkReply::NoError if no - * error was encountered. - * - * \see errorString() - */ - QNetworkReply::NetworkError error() const - { - return mError; - } - - /** - * Returns the error text for the reply, or an empty string if no - * error was encountered. - * - * \see error() - */ - QString errorString() const - { - return mErrorString; - } - -#ifndef SIP_RUN - typedef QPair RawHeaderPair; - - /** - * Returns the list of raw header pairs in the reply. - * \see hasRawHeader() - * \see rawHeaderList() - * \see rawHeader() - * \note Not available in Python bindings - */ - const QList &rawHeaderPairs() const - { - return mRawHeaderPairs; - } -#endif - - /** - * Returns true if the reply contains a header with the specified \a headerName. - * \see rawHeaderPairs() - * \see rawHeaderList() - * \see rawHeader() - */ - bool hasRawHeader( const QByteArray &headerName ) const; - - /** - * Returns a list of raw header names contained within the reply. - * \see rawHeaderPairs() - * \see hasRawHeader() - * \see rawHeader() - */ - QList rawHeaderList() const; - - /** - * Returns the content of the header with the specified \a headerName, or an - * empty QByteArray if the specified header was not found in the reply. - * \see rawHeaderPairs() - * \see hasRawHeader() - * \see rawHeaderList() - */ - QByteArray rawHeader( const QByteArray &headerName ) const; - - private: - - QByteArray mContent; - QNetworkReply::NetworkError mError = QNetworkReply::NoError; - QString mErrorString; - QList mRawHeaderPairs; - QMap< QNetworkRequest::Attribute, QVariant > mAttributes; -}; - -#endif // QGSNETWORKREPLY_H diff --git a/src/providers/arcgisrest/qgsafsprovider.cpp b/src/providers/arcgisrest/qgsafsprovider.cpp index 661917d277ae..38ca8e154aee 100644 --- a/src/providers/arcgisrest/qgsafsprovider.cpp +++ b/src/providers/arcgisrest/qgsafsprovider.cpp @@ -21,6 +21,8 @@ #include "qgsdatasourceuri.h" #include "qgsafsdataitems.h" #include "qgslogger.h" +#include "geometry/qgsgeometry.h" +#include "qgsnetworkaccessmanager.h" #include "qgsdataitemprovider.h" #ifdef HAVE_GUI @@ -28,6 +30,12 @@ #include "qgssourceselectprovider.h" #endif +#include +#include +#include +#include + + static const QString TEXT_PROVIDER_KEY = QStringLiteral( "arcgisfeatureserver" ); static const QString TEXT_PROVIDER_DESCRIPTION = QStringLiteral( "ArcGIS Feature Server data provider" ); diff --git a/src/providers/arcgisrest/qgsarcgisrestutils.cpp b/src/providers/arcgisrest/qgsarcgisrestutils.cpp index c8ca14deea01..f8fe27034ae2 100644 --- a/src/providers/arcgisrest/qgsarcgisrestutils.cpp +++ b/src/providers/arcgisrest/qgsarcgisrestutils.cpp @@ -42,8 +42,10 @@ #include "qgscategorizedsymbolrenderer.h" #include "qgsvectorlayerlabeling.h" +#include #include -#include "qgsblockingnetworkrequest.h" +#include +#include #include #include @@ -469,27 +471,57 @@ QList QgsArcGisRestUtils::getObjectIdsByExtent( const QString &layerurl QByteArray QgsArcGisRestUtils::queryService( const QUrl &u, const QString &authcfg, QString &errorTitle, QString &errorText, QgsFeedback *feedback ) { + QEventLoop loop; QUrl url = parseUrl( u ); QNetworkRequest request( url ); - QgsBlockingNetworkRequest networkRequest; - networkRequest.setAuthCfg( authcfg ); - const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback ); - if ( feedback && feedback->isCanceled() ) - return QByteArray(); - - // Handle network errors - if ( error != QgsBlockingNetworkRequest::NoError ) + if ( !authcfg.isEmpty() ) { - QgsDebugMsg( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) ); - errorTitle = QStringLiteral( "Network error" ); - errorText = networkRequest.errorMessage(); - return QByteArray(); + QgsApplication::authManager()->updateNetworkRequest( request, authcfg ); } - const QgsNetworkReplyContent content = networkRequest.reply(); - return content.content(); + QNetworkReply *reply = nullptr; + QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance(); + + // Request data, handling redirects + while ( true ) + { + reply = nam->get( request ); + QObject::connect( reply, &QNetworkReply::finished, &loop, &QEventLoop::quit ); + if ( feedback ) + { + QObject::connect( feedback, &QgsFeedback::canceled, reply, &QNetworkReply::abort ); + } + + loop.exec( QEventLoop::ExcludeUserInputEvents ); + + reply->deleteLater(); + + if ( feedback && feedback->isCanceled() ) + return QByteArray(); + + // Handle network errors + if ( reply->error() != QNetworkReply::NoError ) + { + QgsDebugMsg( QStringLiteral( "Network error: %1" ).arg( reply->errorString() ) ); + errorTitle = QStringLiteral( "Network error" ); + errorText = reply->errorString(); + return QByteArray(); + } + + // Handle HTTP redirects + QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ); + if ( redirect.isNull() ) + { + break; + } + + QgsDebugMsg( "redirecting to " + redirect.toUrl().toString() ); + request.setUrl( redirect.toUrl() ); + } + QByteArray result = reply->readAll(); + return result; } QVariantMap QgsArcGisRestUtils::queryServiceJSON( const QUrl &url, const QString &authcfg, QString &errorTitle, QString &errorText, QgsFeedback *feedback ) diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index b95569073449..b0bd61f9ae9c 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -23,7 +23,6 @@ ADD_PYTHON_TEST(PyQgsAuthenticationSystem test_qgsauthsystem.py) ADD_PYTHON_TEST(PyQgsBearingUtils test_qgsbearingutils.py) ADD_PYTHON_TEST(PyQgsBinaryWidget test_qgsbinarywidget.py) ADD_PYTHON_TEST(PyQgsBlendModes test_qgsblendmodes.py) -ADD_PYTHON_TEST(PyQgsBlockingNetworkRequest test_qgsblockingnetworkrequest.py) ADD_PYTHON_TEST(PyQgsBox3d test_qgsbox3d.py) ADD_PYTHON_TEST(PyQgsCategorizedSymbolRenderer test_qgscategorizedsymbolrenderer.py) ADD_PYTHON_TEST(PyQgsCheckableComboBox test_qgscheckablecombobox.py) diff --git a/tests/src/python/test_qgsblockingnetworkrequest.py b/tests/src/python/test_qgsblockingnetworkrequest.py deleted file mode 100644 index 47983d3c8522..000000000000 --- a/tests/src/python/test_qgsblockingnetworkrequest.py +++ /dev/null @@ -1,104 +0,0 @@ -# -*- coding: utf-8 -*- -"""QGIS Unit tests for QgsBlockingNetworkRequest - -.. 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. -""" - -from builtins import chr -from builtins import str -__author__ = 'Nyall Dawson' -__date__ = '12/11/2018' -__copyright__ = 'Copyright 2018, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' - -import qgis # NOQA - -import os -from qgis.testing import unittest, start_app -from qgis.core import QgsBlockingNetworkRequest -from utilities import unitTestDataPath -from qgis.PyQt.QtCore import QUrl -from qgis.PyQt.QtTest import QSignalSpy -from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest -import socketserver -import threading -import http.server - -app = start_app() - - -class TestQgsBlockingNetworkRequest(unittest.TestCase): - - @classmethod - def setUpClass(cls): - # Bring up a simple HTTP server - os.chdir(unitTestDataPath() + '') - handler = http.server.SimpleHTTPRequestHandler - - cls.httpd = socketserver.TCPServer(('localhost', 0), handler) - cls.port = cls.httpd.server_address[1] - - cls.httpd_thread = threading.Thread(target=cls.httpd.serve_forever) - cls.httpd_thread.setDaemon(True) - cls.httpd_thread.start() - - def testFetchEmptyUrl(self): - request = QgsBlockingNetworkRequest() - spy = QSignalSpy(request.downloadFinished) - err = request.get(QNetworkRequest(QUrl())) - self.assertEqual(len(spy), 1) - self.assertEqual(err, QgsBlockingNetworkRequest.ServerExceptionError) - self.assertEqual(request.errorMessage(), 'Protocol "" is unknown') - reply = request.reply() - self.assertFalse(reply.content()) - - def testFetchBadUrl(self): - request = QgsBlockingNetworkRequest() - spy = QSignalSpy(request.downloadFinished) - err = request.get(QNetworkRequest(QUrl('http://x'))) - self.assertEqual(len(spy), 1) - self.assertEqual(err, QgsBlockingNetworkRequest.ServerExceptionError) - self.assertEqual(request.errorMessage(), 'Host x not found') - reply = request.reply() - self.assertFalse(reply.content()) - - def testFetchBadUrl2(self): - request = QgsBlockingNetworkRequest() - spy = QSignalSpy(request.downloadFinished) - err = request.get(QNetworkRequest(QUrl('http://localhost:' + str(TestQgsBlockingNetworkRequest.port) + '/ffff'))) - self.assertEqual(len(spy), 1) - self.assertEqual(err, QgsBlockingNetworkRequest.ServerExceptionError) - self.assertIn('File not found', request.errorMessage()) - reply = request.reply() - self.assertFalse(reply.content()) - self.assertEqual(reply.rawHeaderList(), []) - - def testGet(self): - request = QgsBlockingNetworkRequest() - spy = QSignalSpy(request.downloadFinished) - err = request.get(QNetworkRequest(QUrl('http://localhost:' + str(TestQgsBlockingNetworkRequest.port) + '/qgis_local_server/index.html'))) - self.assertEqual(len(spy), 1) - self.assertEqual(err, QgsBlockingNetworkRequest.NoError) - self.assertEqual(request.errorMessage(), '') - reply = request.reply() - self.assertEqual(reply.error(), QNetworkReply.NoError) - self.assertEqual(reply.content(), '\n\n\n\t\n\tLocal QGIS Server Default Index\n\n\n

Web Server Working

\n\n\n') - self.assertEqual(reply.rawHeaderList(), [b'Server', - b'Date', - b'Content-type', - b'Content-Length', - b'Last-Modified']) - self.assertEqual(reply.rawHeader(b'Content-type'), 'text/html') - self.assertEqual(reply.rawHeader(b'xxxxxxxxx'), '') - self.assertEqual(reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), 200) - self.assertEqual(reply.attribute(QNetworkRequest.HttpReasonPhraseAttribute), 'OK') - self.assertEqual(reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), 200) - self.assertEqual(reply.attribute(QNetworkRequest.RedirectionTargetAttribute), None) - - -if __name__ == "__main__": - unittest.main() From de12688f39c0eff8fd71bdffd83cf7d2903ee2c2 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sun, 23 Dec 2018 18:09:38 +0100 Subject: [PATCH 055/266] Tame doxygen --- src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h | 2 +- .../vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h index d16d5ce3621d..3ca252c04ae1 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h @@ -146,7 +146,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils /** * Creates a new iterator. */ - iterator( const QList::const_iterator &layerIt, const LayerFeatures *parent ); + iterator( const QStringList::const_iterator &layerIt, const LayerFeatures *parent ); /** * Copies the iterator \a rh. diff --git a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp index 39434252575e..c4f47dd3d9a3 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp @@ -205,12 +205,12 @@ QgsGeometryCheck::CheckType QgsGeometryMissingVertexCheck::checkType() const return factoryCheckType(); } +///@cond private QList QgsGeometryMissingVertexCheck::factoryCompatibleGeometryTypes() { return {QgsWkbTypes::PolygonGeometry}; } -///@cond private bool QgsGeometryMissingVertexCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); From add1a9228af84d9e9135429f80ce8d15fac673e0 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Sun, 23 Dec 2018 21:44:22 +0100 Subject: [PATCH 056/266] debian packaging: include qgis.processing and qgis.testing (fixes #20856) --- debian/python-qgis-common.install | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/python-qgis-common.install b/debian/python-qgis-common.install index 40aec0b0b1bf..7a2577709de8 100644 --- a/debian/python-qgis-common.install +++ b/debian/python-qgis-common.install @@ -1,2 +1,4 @@ usr/share/qgis/python/* +usr/lib/python*/*-packages/qgis/processing/* +usr/lib/python*/*-packages/qgis/testing/* usr/lib/python*/*-packages/PyQt?/uic/widget-plugins/qgis_customwidgets.py From 304771a583a792117d5e7b35c5f3d22714cc54a0 Mon Sep 17 00:00:00 2001 From: nirvn Date: Mon, 24 Dec 2018 11:20:54 +0700 Subject: [PATCH 057/266] [FEATURE] A new grayscale theme, "blend of gray" --- .../themes/Blend of Gray/icons/arrow-down.svg | 1 + .../themes/Blend of Gray/icons/arrow-up.svg | 1 + .../themes/Blend of Gray/icons/caret-down.svg | 1 + .../Blend of Gray/icons/caret-right.svg | 1 + .../themes/Blend of Gray/icons/close.svg | 1 + .../Blend of Gray/icons/qcheckbox-checked.svg | 1 + .../icons/qcheckbox-unchecked.svg | 1 + .../Blend of Gray/icons/qradiobox-checked.svg | 1 + .../icons/qradiobox-unchecked.svg | 1 + resources/themes/Blend of Gray/style.qss | 637 ++++++++++++++++++ resources/themes/Blend of Gray/variables.qss | 9 + 11 files changed, 655 insertions(+) create mode 100644 resources/themes/Blend of Gray/icons/arrow-down.svg create mode 100644 resources/themes/Blend of Gray/icons/arrow-up.svg create mode 100644 resources/themes/Blend of Gray/icons/caret-down.svg create mode 100644 resources/themes/Blend of Gray/icons/caret-right.svg create mode 100644 resources/themes/Blend of Gray/icons/close.svg create mode 100644 resources/themes/Blend of Gray/icons/qcheckbox-checked.svg create mode 100644 resources/themes/Blend of Gray/icons/qcheckbox-unchecked.svg create mode 100644 resources/themes/Blend of Gray/icons/qradiobox-checked.svg create mode 100644 resources/themes/Blend of Gray/icons/qradiobox-unchecked.svg create mode 100644 resources/themes/Blend of Gray/style.qss create mode 100644 resources/themes/Blend of Gray/variables.qss diff --git a/resources/themes/Blend of Gray/icons/arrow-down.svg b/resources/themes/Blend of Gray/icons/arrow-down.svg new file mode 100644 index 000000000000..c66a52859e7f --- /dev/null +++ b/resources/themes/Blend of Gray/icons/arrow-down.svg @@ -0,0 +1 @@ + diff --git a/resources/themes/Blend of Gray/icons/arrow-up.svg b/resources/themes/Blend of Gray/icons/arrow-up.svg new file mode 100644 index 000000000000..3c991d18fd59 --- /dev/null +++ b/resources/themes/Blend of Gray/icons/arrow-up.svg @@ -0,0 +1 @@ + diff --git a/resources/themes/Blend of Gray/icons/caret-down.svg b/resources/themes/Blend of Gray/icons/caret-down.svg new file mode 100644 index 000000000000..159b85175e6a --- /dev/null +++ b/resources/themes/Blend of Gray/icons/caret-down.svg @@ -0,0 +1 @@ + diff --git a/resources/themes/Blend of Gray/icons/caret-right.svg b/resources/themes/Blend of Gray/icons/caret-right.svg new file mode 100644 index 000000000000..dcaec276801a --- /dev/null +++ b/resources/themes/Blend of Gray/icons/caret-right.svg @@ -0,0 +1 @@ + diff --git a/resources/themes/Blend of Gray/icons/close.svg b/resources/themes/Blend of Gray/icons/close.svg new file mode 100644 index 000000000000..7acc0a7804c6 --- /dev/null +++ b/resources/themes/Blend of Gray/icons/close.svg @@ -0,0 +1 @@ +image/svg+xml