From 2d531e5814c1b43a23c36d09ace9a5f5729e5bb4 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Fri, 9 Jun 2017 17:29:37 +0200 Subject: [PATCH 001/266] [FEATURE] First experiment with preloading After main map canvas render is complete, render a separate image for each adjacent map "tile" (in the background). These are shown when panning the map to display a preview of what will be visible when the panning operation ends. --- src/gui/qgsmapcanvas.cpp | 67 +++++++++++++++++++++++++++++++++++++ src/gui/qgsmapcanvas.h | 8 +++++ src/gui/qgsmapcanvasmap.cpp | 35 ++++++++++++++++++- src/gui/qgsmapcanvasmap.h | 7 ++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index 831b0ffcc30..159b9e11590 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -503,6 +503,7 @@ void QgsMapCanvas::refreshMap() QgsDebugMsgLevel( "CANVAS refresh!", 3 ); stopRendering(); // if any... + stopPreviewJobs(); //build the expression context QgsExpressionContext expressionContext; @@ -624,6 +625,7 @@ void QgsMapCanvas::rendererJobFinished() p.end(); mMap->setContent( img, imageRect( img, mSettings ) ); + startPreviewJobs(); } // now we are in a slot called from mJob - do not delete it immediately @@ -634,6 +636,20 @@ void QgsMapCanvas::rendererJobFinished() emit mapCanvasRefreshed(); } +void QgsMapCanvas::previewJobFinished() +{ + QgsMapRendererQImageJob *job = qobject_cast( sender() ); + if ( !job ) + { + return; + } + + if ( mMap ) + { + mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() ); + } +} + QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings ) { // This is a hack to pass QgsMapCanvasItem::setRect what it @@ -2109,3 +2125,54 @@ const QgsLabelingEngineSettings &QgsMapCanvas::labelingEngineSettings() const { return mSettings.labelingEngineSettings(); } + +void QgsMapCanvas::startPreviewJobs() +{ + stopPreviewJobs(); //just in case still running + + QgsRectangle mapRect = mSettings.visibleExtent(); + + for ( int j = 0; j < 3; ++j ) + { + for ( int i = 0; i < 3; ++i ) + { + if ( i == 1 && j == 1 ) + { + continue; + } + + + //copy settings, only update extent + QgsMapSettings jobSettings = mSettings; + + double dx = ( i - 1 ) * mapRect.width(); + double dy = ( 1 - j ) * mapRect.height(); + QgsRectangle jobExtent = mapRect; + jobExtent.setXMaximum( jobExtent.xMaximum() + dx ); + jobExtent.setXMinimum( jobExtent.xMinimum() + dx ); + jobExtent.setYMaximum( jobExtent.yMaximum() + dy ); + jobExtent.setYMinimum( jobExtent.yMinimum() + dy ); + + jobSettings.setExtent( jobExtent ); + + QgsMapRendererQImageJob *job = new QgsMapRendererParallelJob( jobSettings ); + mPreviewJobs.append( job ); + connect( job, SIGNAL( finished() ), this, SLOT( previewJobFinished() ) ); + job->start(); + } + } +} + +void QgsMapCanvas::stopPreviewJobs() +{ + QList< QgsMapRendererQImageJob * >::iterator it = mPreviewJobs.begin(); + for ( ; it != mPreviewJobs.end(); ++it ) + { + if ( *it ) + { + ( *it )->cancel(); + } + delete ( *it ); + } + mPreviewJobs.clear(); +} diff --git a/src/gui/qgsmapcanvas.h b/src/gui/qgsmapcanvas.h index 2d3ed7304fd..5398a9c692c 100644 --- a/src/gui/qgsmapcanvas.h +++ b/src/gui/qgsmapcanvas.h @@ -586,6 +586,9 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView //! called when a renderer job has finished successfully or when it was canceled void rendererJobFinished(); + //! called when a preview job has been finished + void previewJobFinished(); + void mapUpdateTimeout(); void refreshMap(); @@ -820,6 +823,8 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView QgsSnappingUtils *mSnappingUtils = nullptr; + QList< QgsMapRendererQImageJob * > mPreviewJobs; + //! lock the scale, so zooming can be performed using magnication bool mScaleLocked; @@ -868,6 +873,9 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView void setLayersPrivate( const QList &layers ); + void startPreviewJobs(); + void stopPreviewJobs(); + friend class TestQgsMapCanvas; }; // class QgsMapCanvas diff --git a/src/gui/qgsmapcanvasmap.cpp b/src/gui/qgsmapcanvasmap.cpp index b31eea7ad1b..1c8c1674890 100644 --- a/src/gui/qgsmapcanvasmap.cpp +++ b/src/gui/qgsmapcanvasmap.cpp @@ -16,7 +16,9 @@ #include "qgslogger.h" #include "qgsmapcanvas.h" #include "qgsmapcanvasmap.h" +#include "qgsmaprendererjob.h" #include "qgsmapsettings.h" +#include "qgsmaplayer.h" #include @@ -30,6 +32,8 @@ QgsMapCanvasMap::QgsMapCanvasMap( QgsMapCanvas *canvas ) void QgsMapCanvasMap::setContent( const QImage &image, const QgsRectangle &rect ) { + mPreviewImages.clear(); + mImage = image; // For true retro fans: this is approximately how the graphics looked like in 1990 @@ -40,9 +44,23 @@ void QgsMapCanvasMap::setContent( const QImage &image, const QgsRectangle &rect setRect( rect ); } +void QgsMapCanvasMap::addPreviewImage( const QImage &image, const QgsRectangle &rect ) +{ + mPreviewImages.append( qMakePair( image, rect ) ); + update(); +} + +QRectF QgsMapCanvasMap::boundingRect() const +{ + double width = mItemSize.width(); + double height = mItemSize.height(); + + return QRectF( -width, -height, 3 * width, 3 * height ); +} + void QgsMapCanvasMap::paint( QPainter *painter ) { - int w = qRound( boundingRect().width() ) - 2, h = qRound( boundingRect().height() ) - 2; // setRect() makes the size +2 :-( + int w = qRound( mItemSize.width() ) - 2, h = qRound( mItemSize.height() ) - 2; // setRect() makes the size +2 :-( if ( mImage.size() != QSize( w, h ) ) { QgsDebugMsg( QString( "map paint DIFFERENT SIZE: img %1,%2 item %3,%4" ).arg( mImage.width() ).arg( mImage.height() ).arg( w ).arg( h ) ); @@ -50,6 +68,21 @@ void QgsMapCanvasMap::paint( QPainter *painter ) // the renderer has completed } + /*Offset between 0/0 and mRect.xMinimum/mRect.yMinimum. + We need to consider the offset, because mRect is not updated yet and there might be an offset*/ + QgsPoint pt = toMapCoordinates( QPoint( 0, 0 ) ); + double offsetX = pt.x() - mRect.xMinimum(); + double offsetY = pt.y() - mRect.yMaximum(); + + //draw preview images first + QMap< QgsRectangle, QImage >::const_iterator previewIt = mPreviewImages.constBegin(); + for ( ; previewIt != mPreviewImages.constEnd(); ++previewIt ) + { + QPointF ul = toCanvasCoordinates( QgsPoint( previewIt.key().xMinimum() + offsetX, previewIt.key().yMaximum() + offsetY ) ); + QPointF lr = toCanvasCoordinates( QgsPoint( previewIt.key().xMaximum() + offsetX, previewIt.key().yMinimum() + offsetY ) ); + painter->drawImage( QRectF( ul.x(), ul.y(), lr.x() - ul.x(), lr.y() - ul.y() ), previewIt.value(), QRect( 0, 0, previewIt.value().width(), previewIt.value().height() ) ); + } + painter->drawImage( QRect( 0, 0, w, h ), mImage ); // For debugging: diff --git a/src/gui/qgsmapcanvasmap.h b/src/gui/qgsmapcanvasmap.h index 1b2f7789308..8794fc4f305 100644 --- a/src/gui/qgsmapcanvasmap.h +++ b/src/gui/qgsmapcanvasmap.h @@ -45,9 +45,16 @@ class QgsMapCanvasMap : public QgsMapCanvasItem virtual void paint( QPainter *painter ) override; + void addPreviewImage( const QImage &image, const QgsRectangle &rect ); + + QRectF boundingRect() const; + private: QImage mImage; + + //! Preview images for panning. Usually cover area around the rendered image + QList< QPair< QImage, QgsRectangle > > mPreviewImages; }; /// @endcond From 494b1947b0c7b75b53d8b809946f692a96d2b4c0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 26 Jun 2017 09:57:20 +1000 Subject: [PATCH 002/266] Fix build --- src/gui/qgsmapcanvasmap.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/qgsmapcanvasmap.cpp b/src/gui/qgsmapcanvasmap.cpp index 1c8c1674890..55d666a1047 100644 --- a/src/gui/qgsmapcanvasmap.cpp +++ b/src/gui/qgsmapcanvasmap.cpp @@ -70,17 +70,17 @@ void QgsMapCanvasMap::paint( QPainter *painter ) /*Offset between 0/0 and mRect.xMinimum/mRect.yMinimum. We need to consider the offset, because mRect is not updated yet and there might be an offset*/ - QgsPoint pt = toMapCoordinates( QPoint( 0, 0 ) ); + QgsPointXY pt = toMapCoordinates( QPoint( 0, 0 ) ); double offsetX = pt.x() - mRect.xMinimum(); double offsetY = pt.y() - mRect.yMaximum(); //draw preview images first - QMap< QgsRectangle, QImage >::const_iterator previewIt = mPreviewImages.constBegin(); - for ( ; previewIt != mPreviewImages.constEnd(); ++previewIt ) + QList< QPair< QImage, QgsRectangle > >::const_iterator imIt = mPreviewImages.constBegin(); + for ( ; imIt != mPreviewImages.constEnd(); ++imIt ) { - QPointF ul = toCanvasCoordinates( QgsPoint( previewIt.key().xMinimum() + offsetX, previewIt.key().yMaximum() + offsetY ) ); - QPointF lr = toCanvasCoordinates( QgsPoint( previewIt.key().xMaximum() + offsetX, previewIt.key().yMinimum() + offsetY ) ); - painter->drawImage( QRectF( ul.x(), ul.y(), lr.x() - ul.x(), lr.y() - ul.y() ), previewIt.value(), QRect( 0, 0, previewIt.value().width(), previewIt.value().height() ) ); + QPointF ul = toCanvasCoordinates( QgsPoint( imIt->second.xMinimum() + offsetX, imIt->second.yMaximum() + offsetY ) ); + QPointF lr = toCanvasCoordinates( QgsPoint( imIt->second.xMaximum() + offsetX, imIt->second.yMinimum() + offsetY ) ); + painter->drawImage( QRectF( ul.x(), ul.y(), lr.x() - ul.x(), lr.y() - ul.y() ), imIt->first, QRect( 0, 0, imIt->first.width(), imIt->first.height() ) ); } painter->drawImage( QRect( 0, 0, w, h ), mImage ); From 14560c59f41fd27712fde0dbcb6c8d8b6c59ca41 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 26 Jun 2017 09:57:32 +1000 Subject: [PATCH 003/266] New style connects --- src/gui/qgsmapcanvas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index 159b9e11590..3dc0c8df89a 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -2157,7 +2157,7 @@ void QgsMapCanvas::startPreviewJobs() QgsMapRendererQImageJob *job = new QgsMapRendererParallelJob( jobSettings ); mPreviewJobs.append( job ); - connect( job, SIGNAL( finished() ), this, SLOT( previewJobFinished() ) ); + connect( job, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished ); job->start(); } } From d0fd38f6e481ceb7bcfc2bc9bbbdab010e9bb4dc Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 26 Jun 2017 09:57:37 +1000 Subject: [PATCH 004/266] Don't label preview tiles Labeling can be expensive, so don't do this for tiles which are only going to be used as a preview image --- src/gui/qgsmapcanvas.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index 3dc0c8df89a..08aa8e52231 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -2154,6 +2154,7 @@ void QgsMapCanvas::startPreviewJobs() jobExtent.setYMinimum( jobExtent.yMinimum() + dy ); jobSettings.setExtent( jobExtent ); + jobSettings.setFlag( QgsMapSettings::DrawLabeling, false ); QgsMapRendererQImageJob *job = new QgsMapRendererParallelJob( jobSettings ); mPreviewJobs.append( job ); From 306c5b33b2501e879054f2ce95c0b8ba67bb8ea0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 26 Jun 2017 10:14:36 +1000 Subject: [PATCH 005/266] Cancel preview jobs without blocking Makes for more reponsive map updates --- src/gui/qgsmapcanvas.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index 08aa8e52231..f88e47a333e 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -2171,9 +2171,10 @@ void QgsMapCanvas::stopPreviewJobs() { if ( *it ) { - ( *it )->cancel(); + disconnect( *it, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished ); + connect( *it, &QgsMapRendererQImageJob::finished, *it, &QgsMapRendererQImageJob::deleteLater ); + ( *it )->cancelWithoutBlocking(); } - delete ( *it ); } mPreviewJobs.clear(); } From 4c432a1905afdf5dc70eca630234465123bfd3c8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 26 Jun 2017 16:40:58 +1000 Subject: [PATCH 006/266] Add missing override --- src/gui/qgsmapcanvasmap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qgsmapcanvasmap.h b/src/gui/qgsmapcanvasmap.h index 8794fc4f305..f9cd8df37b3 100644 --- a/src/gui/qgsmapcanvasmap.h +++ b/src/gui/qgsmapcanvasmap.h @@ -47,7 +47,7 @@ class QgsMapCanvasMap : public QgsMapCanvasItem void addPreviewImage( const QImage &image, const QgsRectangle &rect ); - QRectF boundingRect() const; + QRectF boundingRect() const override; private: From 1e145b3ab2fc86561c85bf57130a60ff0fdc3da4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 14 Jul 2017 20:48:38 +1000 Subject: [PATCH 007/266] Minor code improvements --- src/gui/qgsmapcanvas.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index f88e47a333e..0cce4cba6d1 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -639,10 +639,7 @@ void QgsMapCanvas::rendererJobFinished() void QgsMapCanvas::previewJobFinished() { QgsMapRendererQImageJob *job = qobject_cast( sender() ); - if ( !job ) - { - return; - } + Q_ASSERT( job ); if ( mMap ) { @@ -2166,8 +2163,8 @@ void QgsMapCanvas::startPreviewJobs() void QgsMapCanvas::stopPreviewJobs() { - QList< QgsMapRendererQImageJob * >::iterator it = mPreviewJobs.begin(); - for ( ; it != mPreviewJobs.end(); ++it ) + QList< QgsMapRendererQImageJob * >::const_iterator it = mPreviewJobs.constBegin(); + for ( ; it != mPreviewJobs.constEnd(); ++it ) { if ( *it ) { From f926033699d8dd52e3aacbf2b4d449157d0e9938 Mon Sep 17 00:00:00 2001 From: rldhont Date: Mon, 3 Jul 2017 17:58:47 +0200 Subject: [PATCH 008/266] [Server] WMS GetFeatureInfo refactoring --- python/server/qgsserverprojectutils.sip | 56 +++ src/server/qgsserverprojectutils.cpp | 71 +++ src/server/qgsserverprojectutils.h | 42 ++ src/server/services/wms/qgswmsparameters.cpp | 163 ++++++- src/server/services/wms/qgswmsparameters.h | 115 ++++- src/server/services/wms/qgswmsrenderer.cpp | 438 +++++++++--------- src/server/services/wms/qgswmsrenderer.h | 2 +- .../python/test_qgsserver_accesscontrol.py | 54 +++ tests/src/python/test_qgsserver_wms.py | 15 +- .../wms_getfeatureinfo-text-xml.txt | 14 + 10 files changed, 735 insertions(+), 235 deletions(-) create mode 100644 tests/testdata/qgis_server/wms_getfeatureinfo-text-xml.txt diff --git a/python/server/qgsserverprojectutils.sip b/python/server/qgsserverprojectutils.sip index 6c3b35eceeb..4fcdc58e1e5 100644 --- a/python/server/qgsserverprojectutils.sip +++ b/python/server/qgsserverprojectutils.sip @@ -145,6 +145,62 @@ namespace QgsServerProjectUtils :rtype: bool %End + bool wmsFeatureInfoAddWktGeometry( const QgsProject &project ); +%Docstring + Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request. + \param project the QGIS project + :return: if the geometry is displayed as Well Known Text in GetFeatureInfo request. + :rtype: bool +%End + + bool wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project ); +%Docstring + Returns if the geometry has to be segmentize in GetFeatureInfo request. + \param project the QGIS project + :return: if the geometry has to be segmentize in GetFeatureInfo request. + :rtype: bool +%End + + int wmsFeatureInfoPrecision( const QgsProject &project ); +%Docstring + Returns the geometry precision for GetFeatureInfo request. + \param project the QGIS project + :return: the geometry precision for GetFeatureInfo request. + :rtype: int +%End + + QString wmsFeatureInfoDocumentElement( const QgsProject &project ); +%Docstring + Returns the document element name for XML GetFeatureInfo request. + \param project the QGIS project + :return: the document element name for XML GetFeatureInfo request. + :rtype: str +%End + + QString wmsFeatureInfoDocumentElementNs( const QgsProject &project ); +%Docstring + Returns the document element namespace for XML GetFeatureInfo request. + \param project the QGIS project + :return: the document element namespace for XML GetFeatureInfo request. + :rtype: str +%End + + QString wmsFeatureInfoSchema( const QgsProject &project ); +%Docstring + Returns the schema URL for XML GetFeatureInfo request. + \param project the QGIS project + :return: the schema URL for XML GetFeatureInfo request. + :rtype: str +%End + + QHash wmsFeatureInfoLayerAliasMap( const QgsProject &project ); +%Docstring + Returns the mapping between layer name and wms layer name for GetFeatureInfo request. + \param project the QGIS project + :return: the mapping between layer name and wms layer name for GetFeatureInfo request. + :rtype: QHash +%End + bool wmsInspireActivate( const QgsProject &project ); %Docstring Returns if Inspire is activated. diff --git a/src/server/qgsserverprojectutils.cpp b/src/server/qgsserverprojectutils.cpp index de00e8a3cea..e5a71034d29 100644 --- a/src/server/qgsserverprojectutils.cpp +++ b/src/server/qgsserverprojectutils.cpp @@ -117,6 +117,77 @@ bool QgsServerProjectUtils::wmsInfoFormatSia2045( const QgsProject &project ) return false; } +bool QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( const QgsProject &project ) +{ + QString wktGeom = project.readEntry( QStringLiteral( "WMSAddWktGeometry" ), QStringLiteral( "/" ), "" ); + + if ( wktGeom.compare( QLatin1String( "enabled" ), Qt::CaseInsensitive ) == 0 + || wktGeom.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 ) + { + return true; + } + return false; +} + +bool QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project ) +{ + QString segmGeom = project.readEntry( QStringLiteral( "WMSSegmentizeFeatureInfoGeometry" ), QStringLiteral( "/" ), "" ); + + if ( segmGeom.compare( QLatin1String( "enabled" ), Qt::CaseInsensitive ) == 0 + || segmGeom.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 ) + { + return true; + } + return false; +} + +int QgsServerProjectUtils::wmsFeatureInfoPrecision( const QgsProject &project ) +{ + return project.readNumEntry( QStringLiteral( "WMSPrecision" ), QStringLiteral( "/" ), 6 ); +} + +QString QgsServerProjectUtils::wmsFeatureInfoDocumentElement( const QgsProject &project ) +{ + return project.readEntry( QStringLiteral( "WMSFeatureInfoDocumentElement" ), QStringLiteral( "/" ), "" ); +} + +QString QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( const QgsProject &project ) +{ + return project.readEntry( QStringLiteral( "WMSFeatureInfoDocumentElementNS" ), QStringLiteral( "/" ), "" ); +} + +QString QgsServerProjectUtils::wmsFeatureInfoSchema( const QgsProject &project ) +{ + return project.readEntry( QStringLiteral( "WMSFeatureInfoSchema" ), QStringLiteral( "/" ), "" ); +} + +QHash QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( const QgsProject &project ) +{ + QHash aliasMap; + + //WMSFeatureInfoAliasLayers + QStringList aliasLayerStringList = project.readListEntry( QStringLiteral( "WMSFeatureInfoAliasLayers" ), QStringLiteral( "/value" ), QStringList() ); + if ( aliasLayerStringList.isEmpty() ) + { + return aliasMap; + } + + //WMSFeatureInfoLayerAliases + QStringList layerAliasStringList = project.readListEntry( QStringLiteral( "WMSFeatureInfoLayerAliases" ), QStringLiteral( "/value" ), QStringList() ); + if ( layerAliasStringList.isEmpty() ) + { + return aliasMap; + } + + int nMapEntries = qMin( aliasLayerStringList.size(), layerAliasStringList.size() ); + for ( int i = 0; i < nMapEntries; ++i ) + { + aliasMap.insert( aliasLayerStringList.at( i ), layerAliasStringList.at( i ) ); + } + + return aliasMap; +} + bool QgsServerProjectUtils::wmsInspireActivate( const QgsProject &project ) { return project.readBoolEntry( QStringLiteral( "WMSInspire" ), QStringLiteral( "/activated" ) ); diff --git a/src/server/qgsserverprojectutils.h b/src/server/qgsserverprojectutils.h index 8eaf1074f4b..f550a44dad0 100644 --- a/src/server/qgsserverprojectutils.h +++ b/src/server/qgsserverprojectutils.h @@ -132,6 +132,48 @@ namespace QgsServerProjectUtils */ SERVER_EXPORT bool wmsInfoFormatSia2045( const QgsProject &project ); + /** Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request. + * \param project the QGIS project + * \returns if the geometry is displayed as Well Known Text in GetFeatureInfo request. + */ + SERVER_EXPORT bool wmsFeatureInfoAddWktGeometry( const QgsProject &project ); + + /** Returns if the geometry has to be segmentize in GetFeatureInfo request. + * \param project the QGIS project + * \returns if the geometry has to be segmentize in GetFeatureInfo request. + */ + SERVER_EXPORT bool wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project ); + + /** Returns the geometry precision for GetFeatureInfo request. + * \param project the QGIS project + * \returns the geometry precision for GetFeatureInfo request. + */ + SERVER_EXPORT int wmsFeatureInfoPrecision( const QgsProject &project ); + + /** Returns the document element name for XML GetFeatureInfo request. + * \param project the QGIS project + * \returns the document element name for XML GetFeatureInfo request. + */ + SERVER_EXPORT QString wmsFeatureInfoDocumentElement( const QgsProject &project ); + + /** Returns the document element namespace for XML GetFeatureInfo request. + * \param project the QGIS project + * \returns the document element namespace for XML GetFeatureInfo request. + */ + SERVER_EXPORT QString wmsFeatureInfoDocumentElementNs( const QgsProject &project ); + + /** Returns the schema URL for XML GetFeatureInfo request. + * \param project the QGIS project + * \returns the schema URL for XML GetFeatureInfo request. + */ + SERVER_EXPORT QString wmsFeatureInfoSchema( const QgsProject &project ); + + /** Returns the mapping between layer name and wms layer name for GetFeatureInfo request. + * \param project the QGIS project + * \returns the mapping between layer name and wms layer name for GetFeatureInfo request. + */ + SERVER_EXPORT QHash wmsFeatureInfoLayerAliasMap( const QgsProject &project ); + /** Returns if Inspire is activated. * \param project the QGIS project * \returns if Inspire is activated. diff --git a/src/server/services/wms/qgswmsparameters.cpp b/src/server/services/wms/qgswmsparameters.cpp index 90549ea7844..bcd905e2b27 100644 --- a/src/server/services/wms/qgswmsparameters.cpp +++ b/src/server/services/wms/qgswmsparameters.cpp @@ -191,6 +191,41 @@ namespace QgsWms }; save( pFormat ); + const Parameter pInfoFormat = { ParameterName::INFO_FORMAT, + QVariant::String, + QVariant( "" ), + QVariant() + }; + save( pInfoFormat ); + + const Parameter pI = { ParameterName::I, + QVariant::Int, + QVariant( -1 ), + QVariant() + }; + save( pI ); + + const Parameter pJ = { ParameterName::J, + QVariant::Int, + QVariant( -1 ), + QVariant() + }; + save( pJ ); + + const Parameter pX = { ParameterName::X, + QVariant::Int, + QVariant( -1 ), + QVariant() + }; + save( pX ); + + const Parameter pY = { ParameterName::Y, + QVariant::Int, + QVariant( -1 ), + QVariant() + }; + save( pY ); + const Parameter pRule = { ParameterName::RULE, QVariant::String, QVariant( "" ), @@ -254,6 +289,20 @@ namespace QgsWms }; save( pLayers ); + const Parameter pQueryLayers = { ParameterName::QUERY_LAYERS, + QVariant::String, + QVariant( "" ), + QVariant() + }; + save( pQueryLayers ); + + const Parameter pFeatureCount = { ParameterName::FEATURE_COUNT, + QVariant::Int, + QVariant( 1 ), + QVariant() + }; + save( pFeatureCount ); + const Parameter pLayerTitle = { ParameterName::LAYERTITLE, QVariant::Bool, QVariant( true ), @@ -324,12 +373,26 @@ namespace QgsWms }; save( pFilter ); + const Parameter pFilterGeom = { ParameterName::FILTER_GEOM, + QVariant::String, + QVariant( "" ), + QVariant() + }; + save( pFilterGeom ); + const Parameter pSelection = { ParameterName::SELECTION, QVariant::String, QVariant( "" ), QVariant() }; save( pSelection ); + + const Parameter pWmsPrecision = { ParameterName::WMS_PRECISION, + QVariant::Int, + QVariant( -1 ), + QVariant() + }; + save( pWmsPrecision ); } QgsWmsParameters::QgsWmsParameters( const QgsServerRequest::Parameters ¶meters ) @@ -341,6 +404,7 @@ namespace QgsWms { mRequestParameters = parameters; + log( "load WMS Request parameters:" ); const QMetaEnum metaEnum( QMetaEnum::fromType() ); foreach ( QString key, parameters.keys() ) { @@ -351,6 +415,7 @@ namespace QgsWms if ( value.canConvert( mParameters[name].mType ) ) { mParameters[name].mValue = value; + log( " - " + key + " : " + parameters[key] ); } else { @@ -628,9 +693,12 @@ namespace QgsWms QgsWmsParameters::Format QgsWmsParameters::format() const { - Format f = Format::PNG; QString fStr = formatAsString(); + if ( fStr.isEmpty() ) + return Format::NONE; + + Format f = Format::PNG; if ( fStr.compare( QLatin1String( "jpg" ), Qt::CaseInsensitive ) == 0 || fStr.compare( QLatin1String( "jpeg" ), Qt::CaseInsensitive ) == 0 || fStr.compare( QLatin1String( "image/jpeg" ), Qt::CaseInsensitive ) == 0 ) @@ -639,6 +707,69 @@ namespace QgsWms return f; } + QString QgsWmsParameters::infoFormatAsString() const + { + return value( ParameterName::INFO_FORMAT ).toString(); + } + + QgsWmsParameters::Format QgsWmsParameters::infoFormat() const + { + QString fStr = infoFormatAsString(); + + if ( fStr.isEmpty() ) + return Format::NONE; + + Format f = Format::TEXT; + if ( fStr.startsWith( QLatin1String( "text/xml" ), Qt::CaseInsensitive ) ) + f = Format::XML; + else if ( fStr.startsWith( QLatin1String( "text/html" ), Qt::CaseInsensitive ) ) + f = Format::HTML; + else if ( fStr.startsWith( QLatin1String( "application/vnd.ogc.gml" ), Qt::CaseInsensitive ) ) + f = Format::GML; + + return f; + } + + QString QgsWmsParameters::i() const + { + return value( ParameterName::I ).toString(); + } + + QString QgsWmsParameters::j() const + { + return value( ParameterName::J ).toString(); + } + + int QgsWmsParameters::iAsInt() const + { + return toInt( ParameterName::I ); + } + + int QgsWmsParameters::jAsInt() const + { + return toInt( ParameterName::J ); + } + + QString QgsWmsParameters::x() const + { + return value( ParameterName::X ).toString(); + } + + QString QgsWmsParameters::y() const + { + return value( ParameterName::Y ).toString(); + } + + int QgsWmsParameters::xAsInt() const + { + return toInt( ParameterName::X ); + } + + int QgsWmsParameters::yAsInt() const + { + return toInt( ParameterName::Y ); + } + QString QgsWmsParameters::rule() const { return value( ParameterName::RULE ).toString(); @@ -674,6 +805,16 @@ namespace QgsWms return toBool( ParameterName::SHOWFEATURECOUNT ); } + QString QgsWmsParameters::featureCount() const + { + return value( ParameterName::FEATURE_COUNT ).toString(); + } + + int QgsWmsParameters::featureCountAsInt() const + { + return toInt( ParameterName::FEATURE_COUNT ); + } + QString QgsWmsParameters::boxSpace() const { return value( ParameterName::BOXSPACE ).toString(); @@ -960,6 +1101,16 @@ namespace QgsWms return toFloatList( highlightLabelBufferSize(), ParameterName::HIGHLIGHT_LABELBUFFERSIZE ); } + QString QgsWmsParameters::wmsPrecision() const + { + return value( ParameterName::WMS_PRECISION ).toString(); + } + + int QgsWmsParameters::wmsPrecisionAsInt() const + { + return toInt( ParameterName::WMS_PRECISION ); + } + QString QgsWmsParameters::sld() const { return value( ParameterName::SLD ).toString(); @@ -970,6 +1121,11 @@ namespace QgsWms return toStringList( ParameterName::FILTER, ';' ); } + QString QgsWmsParameters::filterGeom() const + { + return value( ParameterName::FILTER_GEOM ).toString(); + } + QStringList QgsWmsParameters::selections() const { return toStringList( ParameterName::SELECTION ); @@ -992,6 +1148,11 @@ namespace QgsWms return layer << layers; } + QStringList QgsWmsParameters::queryLayersNickname() const + { + return toStringList( ParameterName::QUERY_LAYERS ); + } + QStringList QgsWmsParameters::allStyles() const { QStringList style = value( ParameterName::STYLE ).toString().split( ",", QString::SkipEmptyParts ); diff --git a/src/server/services/wms/qgswmsparameters.h b/src/server/services/wms/qgswmsparameters.h index 57bb800f4fa..ecb49e8dadd 100644 --- a/src/server/services/wms/qgswmsparameters.h +++ b/src/server/services/wms/qgswmsparameters.h @@ -86,6 +86,8 @@ namespace QgsWms LAYERS, LAYERSPACE, LAYERTITLESPACE, + QUERY_LAYERS, + FEATURE_COUNT, SHOWFEATURECOUNT, STYLE, STYLES, @@ -95,7 +97,13 @@ namespace QgsWms OPACITIES, SLD, FILTER, + FILTER_GEOM, FORMAT, + INFO_FORMAT, + I, + J, + X, + Y, RULE, RULELABEL, SCALE, @@ -108,7 +116,8 @@ namespace QgsWms HIGHLIGHT_LABELWEIGHT, HIGHLIGHT_LABELCOLOR, HIGHLIGHT_LABELBUFFERCOLOR, - HIGHLIGHT_LABELBUFFERSIZE + HIGHLIGHT_LABELBUFFERSIZE, + WMS_PRECISION }; Q_ENUM( ParameterName ) @@ -116,7 +125,11 @@ namespace QgsWms { NONE, JPG, - PNG + PNG, + TEXT, + XML, + HTML, + GML }; struct Parameter @@ -203,6 +216,11 @@ namespace QgsWms */ QStringList filters() const; + /** Returns the filter geometry found in FILTER_GEOM parameter. + * \returns the filter geometry as Well Known Text. + */ + QString filterGeom() const; + /** Returns the list of opacities found in OPACITIES parameter. * \returns the list of opacities in string */ @@ -221,6 +239,11 @@ namespace QgsWms */ QStringList allLayersNickname() const; + /** Returns nickname of layers found in QUERY_LAYERS parameter. + * \returns nickname of layers + */ + QStringList queryLayersNickname() const; + /** Returns styles found in STYLE and STYLES parameters. * \returns name of styles */ @@ -242,6 +265,69 @@ namespace QgsWms */ Format format() const; + /** Returns INFO_FORMAT parameter as a string. + * \returns INFO_FORMAT parameter as string + */ + QString infoFormatAsString() const; + + /** Returns infoFormat. If the INFO_FORMAT parameter is not used, then the + * default value is text/plain. + * \returns infoFormat + */ + Format infoFormat() const; + + /** Returns I parameter or an empty string if not defined. + * \returns i parameter + */ + QString i() const; + + /** Returns I parameter as an int or its default value if not + * defined. An exception is raised if I is defined and cannot be + * converted. + * \returns i parameter + * \throws QgsBadRequestException + */ + int iAsInt() const; + + /** Returns J parameter or an empty string if not defined. + * \returns j parameter + */ + QString j() const; + + /** Returns J parameter as an int or its default value if not + * defined. An exception is raised if J is defined and cannot be + * converted. + * \returns j parameter + * \throws QgsBadRequestException + */ + int jAsInt() const; + + /** Returns X parameter or an empty string if not defined. + * \returns x parameter + */ + QString x() const; + + /** Returns X parameter as an int or its default value if not + * defined. An exception is raised if X is defined and cannot be + * converted. + * \returns x parameter + * \throws QgsBadRequestException + */ + int xAsInt() const; + + /** Returns Y parameter or an empty string if not defined. + * \returns y parameter + */ + QString y() const; + + /** Returns Y parameter as an int or its default value if not + * defined. An exception is raised if Y is defined and cannot be + * converted. + * \returns j parameter + * \throws QgsBadRequestException + */ + int yAsInt() const; + /** Returns RULE parameter or an empty string if none is defined * \returns RULE parameter or an empty string if none is defined */ @@ -271,6 +357,18 @@ namespace QgsWms */ bool showFeatureCountAsBool() const; + /** Returns FEATURE_COUNT parameter or an empty string if none is defined + * \returns FEATURE_COUNT parameter or an empty string if none is defined + */ + QString featureCount() const; + + /** Returns FEATURE_COUNT as an integer. An exception is raised if an invalid + * parameter is found. + * \returns FeatureCount + * \throws QgsBadRequestException + */ + int featureCountAsInt() const; + /** Returns SCALE parameter or an empty string if none is defined * \returns SCALE parameter or an empty string if none is defined */ @@ -595,6 +693,19 @@ namespace QgsWms */ QList highlightLabelBufferColorAsColor() const; + /** Returns WMS_PRECISION parameter or an empty string if not defined. + * \returns wms precision parameter + */ + QString wmsPrecision() const; + + /** Returns WMS_PRECISION parameter as an int or its default value if not + * defined. An exception is raised if WMS_PRECISION is defined and cannot be + * converted. + * \returns wms precision parameter + * \throws QgsBadRequestException + */ + int wmsPrecisionAsInt() const; + private: QString name( ParameterName name ) const; void raiseError( ParameterName name ) const; diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index d31a938451f..cdcbdf93218 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -644,131 +644,161 @@ namespace QgsWms QDomDocument QgsRenderer::getFeatureInfo( const QString &version ) { - if ( !mConfigParser ) + // Verifying Mandatory parameters + // The INFO_FORMAT parameter is Mandatory + if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::NONE ) { - throw QgsException( QStringLiteral( "No config parser" ) ); + throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ), + QStringLiteral( "INFO_FORMAT parameter is required for GetFeatureInfo" ) ); } - QDomDocument result; - QStringList layersList, stylesList; - bool conversionSuccess; - - for ( auto it = mParameters.constBegin(); it != mParameters.constEnd(); ++it ) + // The QUERY_LAYERS parameter is Mandatory + QStringList queryLayers = mWmsParameters.queryLayersNickname(); + if ( queryLayers.isEmpty() ) { - QgsMessageLog::logMessage( QStringLiteral( "%1 // %2" ).arg( it.key(), it.value() ) ); + throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ), + QStringLiteral( "QUERY_LAYERS parameter is required for GetFeatureInfo" ) ); } - readLayersAndStyles( mParameters, layersList, stylesList ); - initializeSLDParser( layersList, stylesList ); + // The I/J parameters are Mandatory if they are not replaced by X/Y or FILTER or FILTER_GEOM + bool ijDefined = false; + if ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() ) + ijDefined = true; + bool xyDefined = false; + if ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() ) + xyDefined = true; + + bool filtersDefined = false; + if ( !mWmsParameters.filters().isEmpty() ) + filtersDefined = true; + + bool filterGeomDefined = false; + if ( !mWmsParameters.filterGeom().isEmpty() ) + filterGeomDefined = true; + + if ( !ijDefined && !xyDefined && !filtersDefined && !filterGeomDefined ) + { + throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ), + QStringLiteral( "I/J parameters are required for GetFeatureInfo" ) ); + } + + // get layers parameters + QList layers; + QList params = mWmsParameters.layersParameters(); + + // init layer restorer before doing anything + std::unique_ptr restorer; + restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) ); + + // init stylized layers according to LAYERS/STYLES or SLD + QString sld = mWmsParameters.sld(); + if ( !sld.isEmpty() ) + layers = sldStylizedLayers( sld ); + else + layers = stylizedLayers( params ); + + // create the mapSettings and the output image QgsMapSettings mapSettings; std::unique_ptr outputImage( createImage() ); + // configure map settings (background, DPI, ...) configureMapSettings( outputImage.get(), mapSettings ); + QgsMessageLog::logMessage( "mapSettings.destinationCrs(): " + mapSettings.destinationCrs().authid() ); QgsMessageLog::logMessage( "mapSettings.extent(): " + mapSettings.extent().toString() ); QgsMessageLog::logMessage( QStringLiteral( "mapSettings width = %1 height = %2" ).arg( mapSettings.outputSize().width() ).arg( mapSettings.outputSize().height() ) ); QgsMessageLog::logMessage( QStringLiteral( "mapSettings.mapUnitsPerPixel() = %1" ).arg( mapSettings.mapUnitsPerPixel() ) ); - //find out the current scale denominator and set it to the SLD parser QgsScaleCalculator scaleCalc( ( outputImage->logicalDpiX() + outputImage->logicalDpiY() ) / 2, mapSettings.destinationCrs().mapUnits() ); QgsRectangle mapExtent = mapSettings.extent(); double scaleDenominator = scaleCalc.calculate( mapExtent, outputImage->width() ); - mConfigParser->setScaleDenominator( scaleDenominator ); - //read FEATURE_COUNT - int featureCount = 1; - if ( mParameters.contains( QStringLiteral( "FEATURE_COUNT" ) ) ) + // remove unwanted layers (restricted layers, ...) + removeUnwantedLayers( layers, scaleDenominator ); + // remove non identifiable layers + QStringList nonIdentifiableLayers = mProject->nonIdentifiableLayers(); + if ( !nonIdentifiableLayers.isEmpty() ) { - featureCount = mParameters[ QStringLiteral( "FEATURE_COUNT" )].toInt( &conversionSuccess ); - if ( !conversionSuccess ) + QList wantedLayers; + + Q_FOREACH ( QgsMapLayer *layer, layers ) { - featureCount = 1; + if ( nonIdentifiableLayers.contains( layer->id() ) ) + continue; + + wantedLayers.append( layer ); + } + + layers = wantedLayers; + } + + Q_FOREACH ( QgsMapLayer *layer, layers ) + { + Q_FOREACH ( QgsWmsParametersLayer param, params ) + { + if ( param.mNickname == layerNickname( *layer ) ) + { + checkLayerReadPermissions( layer ); + + setLayerFilter( layer, param.mFilter ); + + setLayerAccessControlFilter( layer ); + + break; + } } } - //read QUERY_LAYERS - if ( !mParameters.contains( QStringLiteral( "QUERY_LAYERS" ) ) ) + // add layers to map settings (revert order for the rendering) + std::reverse( layers.begin(), layers.end() ); + mapSettings.setLayers( layers ); + + int featureCount = mWmsParameters.featureCountAsInt(); + if ( featureCount < 1 ) { - throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ), QStringLiteral( "No QUERY_LAYERS" ) ); + featureCount = 1; } - QStringList queryLayerList = mParameters[ QStringLiteral( "QUERY_LAYERS" )].split( ',', QString::SkipEmptyParts ); - if ( queryLayerList.isEmpty() ) + int i = mWmsParameters.iAsInt(); + int j = mWmsParameters.jAsInt(); + if ( xyDefined && !ijDefined ) { - throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ), QStringLiteral( "Malformed QUERY_LAYERS" ) ); + i = mWmsParameters.xAsInt(); + j = mWmsParameters.yAsInt(); + } + int width = mWmsParameters.widthAsInt(); + int height = mWmsParameters.heightAsInt(); + if ( ( i != -1 && j != -1 && width != 0 && height != 0 ) && ( width != outputImage->width() || height != outputImage->height() ) ) + { + i *= ( outputImage->width() / ( double )width ); + j *= ( outputImage->height() / ( double )height ); } - //read I,J resp. X,Y - QString iString = mParameters.value( QStringLiteral( "I" ), mParameters.value( QStringLiteral( "X" ) ) ); - int i = iString.toInt( &conversionSuccess ); - if ( !conversionSuccess ) - { - i = -1; - } - - QString jString = mParameters.value( QStringLiteral( "J" ), mParameters.value( QStringLiteral( "Y" ) ) ); - int j = jString.toInt( &conversionSuccess ); - if ( !conversionSuccess ) - { - j = -1; - } - - //read FILTER_GEOM - std::unique_ptr filterGeom; - if ( mParameters.contains( QStringLiteral( "FILTER_GEOM" ) ) ) - { - filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mParameters.value( QStringLiteral( "FILTER_GEOM" ) ) ) ) ); - } - - //In case the output image is distorted (WIDTH/HEIGHT ratio not equal to BBOX width/height), I and J need to be adapted as well - int widthParam = mParameters.value( "WIDTH", "-1" ).toInt(); - int heightParam = mParameters.value( "HEIGHT", "-1" ).toInt(); - if ( ( i != -1 && j != -1 && widthParam != -1 && heightParam != -1 ) && ( widthParam != outputImage->width() || heightParam != outputImage->height() ) ) - { - i *= ( outputImage->width() / ( double )widthParam ); - j *= ( outputImage->height() / ( double )heightParam ); - } - - //Normally, I/J or X/Y are mandatory parameters. - //However, in order to make attribute only queries via the FILTER parameter, it is allowed to skip them if the FILTER parameter is there - + // init search variables std::unique_ptr featuresRect; + std::unique_ptr filterGeom; std::unique_ptr infoPoint; - if ( i == -1 || j == -1 ) - { - if ( mParameters.contains( QStringLiteral( "FILTER" ) ) ) - { - featuresRect.reset( new QgsRectangle() ); - } - else if ( !filterGeom.get() ) - { - throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ), - QStringLiteral( "I/J parameters are required for GetFeatureInfo" ) ); - } - } - else + if ( i != -1 && j != -1 ) { infoPoint.reset( new QgsPointXY() ); infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings ); } + else if ( filtersDefined ) + { + featuresRect.reset( new QgsRectangle() ); + } + else if ( filterGeomDefined ) + { + filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mWmsParameters.filterGeom() ) ) ); + } - //get the layer registered in QgsMapLayerRegistry and apply possible filters - ( void )layerSet( layersList, stylesList, mapSettings.destinationCrs() ); - - //scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope - //there's LOTS of potential exit paths here, so we avoid having to restore the filters manually - std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer( mAccessControl ) ); - applyRequestedLayerFilters( layersList, mapSettings, filterRestorer->originalFilters() ); - -#ifdef HAVE_SERVER_PYTHON_PLUGINS - applyAccessControlLayersFilters( layersList, filterRestorer->originalFilters() ); -#endif + QDomDocument result; QDomElement getFeatureInfoElement; - QString infoFormat = mParameters.value( QStringLiteral( "INFO_FORMAT" ) ); - if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) ) + QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat(); + if ( infoFormat == QgsWmsParameters::Format::GML ) { getFeatureInfoElement = result.createElement( QStringLiteral( "wfs:FeatureCollection" ) ); getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:wfs" ), QStringLiteral( "http://www.opengis.net/wfs" ) ); @@ -782,18 +812,22 @@ namespace QgsWms } else { - QString featureInfoElemName = mConfigParser->featureInfoDocumentElement( QStringLiteral( "GetFeatureInfoResponse" ) ); - QString featureInfoElemNS = mConfigParser->featureInfoDocumentElementNS(); - if ( featureInfoElemNS.isEmpty() ) + QString featureInfoElemName = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( *mProject ); + if ( featureInfoElemName.isEmpty() ) + { + featureInfoElemName = QStringLiteral( "GetFeatureInfoResponse" ); + } + QString featureInfoElemNs = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( *mProject ); + if ( featureInfoElemNs.isEmpty() ) { getFeatureInfoElement = result.createElement( featureInfoElemName ); } else { - getFeatureInfoElement = result.createElementNS( featureInfoElemNS, featureInfoElemName ); + getFeatureInfoElement = result.createElementNS( featureInfoElemNs, featureInfoElemName ); } //feature info schema - QString featureInfoSchema = mConfigParser->featureInfoSchema(); + QString featureInfoSchema = QgsServerProjectUtils::wmsFeatureInfoSchema( *mProject ); if ( !featureInfoSchema.isEmpty() ) { getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); @@ -802,122 +836,92 @@ namespace QgsWms } result.appendChild( getFeatureInfoElement ); - QStringList nonIdentifiableLayers = mConfigParser->identifyDisabledLayers(); - //Render context is needed to determine feature visibility for vector layers QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings ); - bool sia2045 = mConfigParser->featureInfoFormatSIA2045(); + bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *mProject ); //layers can have assigned a different name for GetCapabilities - QHash layerAliasMap = mConfigParser->featureInfoLayerAliasMap(); + QHash layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject ); - QList layerList; - QgsMapLayer *currentLayer = nullptr; - for ( auto layerIt = queryLayerList.constBegin(); layerIt != queryLayerList.constEnd(); ++layerIt ) + Q_FOREACH ( QString queryLayer, queryLayers ) { - //create maplayers from sld parser (several layers are possible in case of feature info on a group) - layerList = mConfigParser->mapLayerFromStyle( *layerIt, QLatin1String( "" ) ); - for ( auto layerListIt = layerList.begin() ; layerListIt != layerList.end(); ++layerListIt ) + Q_FOREACH ( QgsMapLayer *layer, layers ) { - currentLayer = *layerListIt; - if ( !currentLayer || nonIdentifiableLayers.contains( currentLayer->id() ) ) + if ( queryLayer == layerNickname( *layer ) ) { - continue; - } - QgsMapLayer *registeredMapLayer = QgsProject::instance()->mapLayer( currentLayer->id() ); - if ( registeredMapLayer ) - { - currentLayer = registeredMapLayer; - } - -#ifdef HAVE_SERVER_PYTHON_PLUGINS - if ( !mAccessControl->layerReadPermission( currentLayer ) ) - { - throw QgsSecurityException( QStringLiteral( "You are not allowed to access to the layer: %1" ).arg( currentLayer->name() ) ); - } -#endif - - //skip layer if not visible at current map scale - if ( scaleDenominator > 0 && !currentLayer->isInScaleRange( scaleDenominator ) ) - { - continue; - } - - //switch depending on vector or raster - QgsVectorLayer *vectorLayer = qobject_cast( currentLayer ); - - QDomElement layerElement; - if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) ) - { - layerElement = getFeatureInfoElement; - } - else - { - layerElement = result.createElement( QStringLiteral( "Layer" ) ); - QString layerName = currentLayer->name(); - if ( mConfigParser->useLayerIds() ) - layerName = currentLayer->id(); - else if ( !currentLayer->shortName().isEmpty() ) - layerName = currentLayer->shortName(); - - //check if the layer is given a different name for GetFeatureInfo output - QHash::const_iterator layerAliasIt = layerAliasMap.find( layerName ); - if ( layerAliasIt != layerAliasMap.constEnd() ) + QDomElement layerElement; + if ( infoFormat == QgsWmsParameters::Format::GML ) { - layerName = layerAliasIt.value(); + layerElement = getFeatureInfoElement; } - layerElement.setAttribute( QStringLiteral( "name" ), layerName ); - getFeatureInfoElement.appendChild( layerElement ); - if ( sia2045 ) //the name might not be unique after alias replacement + else { - layerElement.setAttribute( QStringLiteral( "id" ), currentLayer->id() ); - } - } + layerElement = result.createElement( QStringLiteral( "Layer" ) ); + QString layerName = queryLayer; - if ( vectorLayer ) - { - if ( !featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, infoFormat, featuresRect.get(), filterGeom.get() ) ) - { - continue; - } - } - else //raster layer - { - if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) ) - { - layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ ); - getFeatureInfoElement.appendChild( layerElement ); - } - - QgsRasterLayer *rasterLayer = qobject_cast( currentLayer ); - if ( rasterLayer ) - { - if ( !infoPoint ) + //check if the layer is given a different name for GetFeatureInfo output + QHash::const_iterator layerAliasIt = layerAliasMap.find( layerName ); + if ( layerAliasIt != layerAliasMap.constEnd() ) { - continue; + layerName = layerAliasIt.value(); } - QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( currentLayer, *( infoPoint.get() ) ); - if ( !featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version, infoFormat ) ) + + layerElement.setAttribute( QStringLiteral( "name" ), layerName ); + getFeatureInfoElement.appendChild( layerElement ); + if ( sia2045 ) //the name might not be unique after alias replacement { - continue; + layerElement.setAttribute( QStringLiteral( "id" ), layer->id() ); + } + } + + if ( layer->type() == QgsMapLayer::VectorLayer ) + { + QgsVectorLayer *vectorLayer = qobject_cast( layer ); + if ( vectorLayer ) + { + if ( !featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, mWmsParameters.infoFormatAsString(), featuresRect.get(), filterGeom.get() ) ) + { + break; + } + break; } } else { - continue; + if ( infoFormat == QgsWmsParameters::Format::GML ) + { + layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ ); + getFeatureInfoElement.appendChild( layerElement ); + } + + QgsRasterLayer *rasterLayer = qobject_cast( layer ); + if ( rasterLayer ) + { + if ( !infoPoint ) + { + break; + } + QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) ); + if ( !featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version, mWmsParameters.infoFormatAsString() ) ) + { + break; + } + break; + } } + break; } } } if ( featuresRect ) { - if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) ) + if ( infoFormat == QgsWmsParameters::Format::GML ) { QDomElement bBoxElem = result.createElement( QStringLiteral( "gml:boundedBy" ) ); QDomElement boxElem; - int gmlVersion = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; + int gmlVersion = mWmsParameters.infoFormatAsString().startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; if ( gmlVersion < 3 ) { boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 ); @@ -947,16 +951,11 @@ namespace QgsWms } } - if ( sia2045 && infoFormat.compare( QLatin1String( "text/xml" ), Qt::CaseInsensitive ) == 0 ) + if ( sia2045 && infoFormat == QgsWmsParameters::Format::XML ) { convertFeatureInfoToSIA2045( result ); } - //force restoration of original filters - filterRestorer.reset(); - - QgsProject::instance()->removeAllMapLayers(); - return result; } @@ -1288,8 +1287,8 @@ namespace QgsWms int featureCounter = 0; layer->updateFields(); const QgsFields &fields = layer->pendingFields(); - bool addWktGeometry = mConfigParser && mConfigParser->featureInfoWithWktGeometry(); - bool segmentizeWktGeometry = mConfigParser && mConfigParser->segmentizeFeatureInfoWktGeometry(); + bool addWktGeometry = QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ); + bool segmentizeWktGeometry = QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( *mProject ); const QSet &excludedAttributes = layer->excludeAttributesWms(); QgsFeatureRequest fReq; @@ -1385,17 +1384,13 @@ namespace QgsWms outputCrs = mapSettings.destinationCrs(); } - if ( infoFormat == QLatin1String( "application/vnd.ogc.gml" ) ) + if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML ) { bool withGeom = layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry; - int version = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; - QString typeName = layer->name(); - if ( mConfigParser && mConfigParser->useLayerIds() ) - typeName = layer->id(); - else if ( !layer->shortName().isEmpty() ) - typeName = layer->shortName(); + int gmlVersion = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; + QString typeName = layerNickname( *layer ); QDomElement elem = createFeatureGML( - &feature, layer, infoDocument, outputCrs, mapSettings, typeName, withGeom, version + &feature, layer, infoDocument, outputCrs, mapSettings, typeName, withGeom, gmlVersion #ifdef HAVE_SERVER_PYTHON_PLUGINS , &attributes #endif @@ -1453,14 +1448,14 @@ namespace QgsWms } //append feature bounding box to feature info xml - if ( layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry && mConfigParser ) + if ( layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry ) { QDomElement bBoxElem = infoDocument.createElement( QStringLiteral( "BoundingBox" ) ); bBoxElem.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", outputCrs.authid() ); - bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( box.xMinimum(), getWMSPrecision( 8 ) ) ); - bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( box.xMaximum(), getWMSPrecision( 8 ) ) ); - bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( box.yMinimum(), getWMSPrecision( 8 ) ) ); - bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( box.yMaximum(), getWMSPrecision( 8 ) ) ); + bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( box.xMinimum(), getWMSPrecision() ) ); + bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( box.xMaximum(), getWMSPrecision() ) ); + bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( box.yMinimum(), getWMSPrecision() ) ); + bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( box.yMaximum(), getWMSPrecision() ) ); featureElement.appendChild( bBoxElem ); } @@ -1484,14 +1479,14 @@ namespace QgsWms { if ( QgsWkbTypes::isCurvedType( abstractGeom->wkbType() ) ) { - QgsAbstractGeometry *segmentizedGeom = abstractGeom-> segmentize(); + QgsAbstractGeometry *segmentizedGeom = abstractGeom->segmentize(); geom.setGeometry( segmentizedGeom ); } } } QDomElement geometryElement = infoDocument.createElement( QStringLiteral( "Attribute" ) ); geometryElement.setAttribute( QStringLiteral( "name" ), QStringLiteral( "geometry" ) ); - geometryElement.setAttribute( QStringLiteral( "value" ), geom.exportToWkt( getWMSPrecision( 8 ) ) ); + geometryElement.setAttribute( QStringLiteral( "value" ), geom.exportToWkt( getWMSPrecision() ) ); geometryElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "derived" ) ); featureElement.appendChild( geometryElement ); } @@ -1540,7 +1535,7 @@ namespace QgsWms attributes = layer->dataProvider()->identify( *infoPoint, QgsRaster::IdentifyFormatValue, mapSettings.extent(), mapSettings.outputSize().width(), mapSettings.outputSize().height() ).results(); } - if ( infoFormat == QLatin1String( "application/vnd.ogc.gml" ) ) + if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML ) { QgsFeature feature; QgsFields fields; @@ -1554,14 +1549,10 @@ namespace QgsWms feature.setFields( fields ); QgsCoordinateReferenceSystem layerCrs = layer->crs(); - int version = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; - QString typeName = layer->name(); - if ( mConfigParser && mConfigParser->useLayerIds() ) - typeName = layer->id(); - else if ( !layer->shortName().isEmpty() ) - typeName = layer->shortName(); + int gmlVersion = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; + QString typeName = layerNickname( *layer ); QDomElement elem = createFeatureGML( - &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, version, nullptr ); + &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr ); layerElement.appendChild( elem ); } else @@ -2263,11 +2254,11 @@ namespace QgsWms QDomElement boxElem; if ( version < 3 ) { - boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, 8 ); + boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, getWMSPrecision() ); } else { - boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, 8 ); + boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, getWMSPrecision() ); } if ( crs.isValid() ) @@ -2291,11 +2282,11 @@ namespace QgsWms QDomElement gmlElem; if ( version < 3 ) { - gmlElem = QgsOgcUtils::geometryToGML( geom, doc, 8 ); + gmlElem = QgsOgcUtils::geometryToGML( geom, doc, getWMSPrecision() ); } else { - gmlElem = QgsOgcUtils::geometryToGML( geom, doc, QStringLiteral( "GML3" ), 8 ); + gmlElem = QgsOgcUtils::geometryToGML( geom, doc, QStringLiteral( "GML3" ), getWMSPrecision() ); } if ( !gmlElem.isNull() ) @@ -2388,27 +2379,18 @@ namespace QgsWms return imageQuality; } - int QgsRenderer::getWMSPrecision( int defaultValue = 8 ) const + int QgsRenderer::getWMSPrecision() const { - // First taken from QGIS project - int WMSPrecision = mConfigParser->wmsPrecision(); + // First taken from QGIS project and the default value is 6 + int WMSPrecision = QgsServerProjectUtils::wmsFeatureInfoPrecision( *mProject ); // Then checks if a parameter is given, if so use it instead - if ( mParameters.contains( QStringLiteral( "WMS_PRECISION" ) ) ) - { - bool conversionSuccess; - int WMSPrecisionParameter; - WMSPrecisionParameter = mParameters[ QStringLiteral( "WMS_PRECISION" )].toInt( &conversionSuccess ); - if ( conversionSuccess ) - { - WMSPrecision = WMSPrecisionParameter; - } - } - if ( WMSPrecision == -1 ) - { - WMSPrecision = defaultValue; - } - return WMSPrecision; + int WMSPrecisionParameter = mWmsParameters.wmsPrecisionAsInt(); + + if ( WMSPrecisionParameter > -1 ) + return WMSPrecisionParameter; + else + return WMSPrecision; } QgsRectangle QgsRenderer::featureInfoSearchRect( QgsVectorLayer *ml, const QgsMapSettings &mapSettings, const QgsRenderContext &rct, const QgsPointXY &infoPoint ) const diff --git a/src/server/services/wms/qgswmsrenderer.h b/src/server/services/wms/qgswmsrenderer.h index 70be8a01e99..19f83f4a3a2 100644 --- a/src/server/services/wms/qgswmsrenderer.h +++ b/src/server/services/wms/qgswmsrenderer.h @@ -334,7 +334,7 @@ namespace QgsWms int getImageQuality() const; //! Return precision to use for GetFeatureInfo request - int getWMSPrecision( int defaultValue ) const; + int getWMSPrecision() const; }; diff --git a/tests/src/python/test_qgsserver_accesscontrol.py b/tests/src/python/test_qgsserver_accesscontrol.py index 4f01a715743..6299c50d617 100644 --- a/tests/src/python/test_qgsserver_accesscontrol.py +++ b/tests/src/python/test_qgsserver_accesscontrol.py @@ -453,6 +453,33 @@ class TestQgsServerAccessControl(unittest.TestCase): str(response).find("red") != -1, # spellok "No color in result of GetFeatureInfo\n%s" % response) + response, headers = self._get_restricted(query_string) + self.assertEqual( + headers.get("Content-Type"), "text/xml; charset=utf-8", + "Content type for GetFeatureInfo is wrong: %s" % headers.get("Content-Type")) + self.assertTrue( + str(response).find('') != -1, + "Not allowed do a GetFeatureInfo on Country" + ) + + query_string = "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectPath), + "SERVICE": "WMS", + "VERSION": "1.1.1", + "REQUEST": "GetFeatureInfo", + "LAYERS": "Hello", + "QUERY_LAYERS": "Hello", + "STYLES": "", + "FORMAT": "image/png", + "BBOX": "-16817707,-6318936.5,5696513,16195283.5", + "HEIGHT": "500", + "WIDTH": "500", + "SRS": "EPSG:3857", + "FEATURE_COUNT": "10", + "INFO_FORMAT": "application/vnd.ogc.gml", + "X": "56", + "Y": "144" + }.items())]) response, headers = self._get_restricted(query_string) self.assertTrue( str(response).find("1") != -1, @@ -1053,6 +1080,33 @@ class TestQgsServerAccessControl(unittest.TestCase): str(response).find("1") != -1, "No good result in GetFeatureInfo Hello/1\n%s" % response) + response, headers = self._get_restricted(query_string) + self.assertEqual( + headers.get("Content-Type"), "text/xml; charset=utf-8", + "Content type for GetFeatureInfo is wrong: %s" % headers.get("Content-Type")) + self.assertTrue( + str(response).find('') != -1, + "Not allowed do a GetFeatureInfo on Country" + ) + + query_string = "&".join(["%s=%s" % i for i in list({ + "SERVICE": "WMS", + "VERSION": "1.1.1", + "REQUEST": "GetFeatureInfo", + "LAYERS": "Hello_SubsetString", + "QUERY_LAYERS": "Hello_SubsetString", + "STYLES": "", + "FORMAT": "image/png", + "BBOX": "-16817707,-6318936.5,5696513,16195283.5", + "HEIGHT": "500", + "WIDTH": "500", + "SRS": "EPSG:3857", + "FEATURE_COUNT": "10", + "INFO_FORMAT": "application/vnd.ogc.gml", + "X": "56", + "Y": "144", + "MAP": urllib.parse.quote(self.projectPath) + }.items())]) response, headers = self._get_restricted(query_string) self.assertTrue( str(response).find("") != -1, diff --git a/tests/src/python/test_qgsserver_wms.py b/tests/src/python/test_qgsserver_wms.py index fbc08d6a23d..eec680b4ed3 100644 --- a/tests/src/python/test_qgsserver_wms.py +++ b/tests/src/python/test_qgsserver_wms.py @@ -69,7 +69,16 @@ class TestQgsServerWMS(QgsServerTestBase): for request in ('GetCapabilities', 'GetProjectSettings', 'GetContext'): self.wms_request_compare(request) - # Test getfeatureinfo response + # Test getfeatureinfo response xml (default info_format) + self.wms_request_compare('GetFeatureInfo', + '&layers=testlayer%20%C3%A8%C3%A9&styles=&' + + 'info_format=text%2Fxml&transparent=true&' + + 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' + + '5606005.488876367%2C913235.426296057%2C5606035.347090538&' + + 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320', + 'wms_getfeatureinfo-text-xml') + + # Test getfeatureinfo response html self.wms_request_compare('GetFeatureInfo', '&layers=testlayer%20%C3%A8%C3%A9&styles=&' + 'info_format=text%2Fhtml&transparent=true&' + @@ -78,10 +87,10 @@ class TestQgsServerWMS(QgsServerTestBase): 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320', 'wms_getfeatureinfo-text-html') - # Test getfeatureinfo default info_format + # Test getfeatureinfo response text self.wms_request_compare('GetFeatureInfo', '&layers=testlayer%20%C3%A8%C3%A9&styles=&' + - 'transparent=true&' + + 'info_format=text%2Fplain&transparent=true&' + 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' + '5606005.488876367%2C913235.426296057%2C5606035.347090538&' + 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320', diff --git a/tests/testdata/qgis_server/wms_getfeatureinfo-text-xml.txt b/tests/testdata/qgis_server/wms_getfeatureinfo-text-xml.txt new file mode 100644 index 00000000000..591eea56f80 --- /dev/null +++ b/tests/testdata/qgis_server/wms_getfeatureinfo-text-xml.txt @@ -0,0 +1,14 @@ +***** +Content-Type: text/xml; charset=utf-8 + + + + + + + + + + + + From 349c996ad24e783ba110c897fcb4c7cbfad56b29 Mon Sep 17 00:00:00 2001 From: rldhont Date: Mon, 17 Jul 2017 15:47:55 +0200 Subject: [PATCH 009/266] [Server] WMS GetFeatureInfo refactoring cleanup --- .../services/wms/qgswmsgetfeatureinfo.cpp | 138 +--- src/server/services/wms/qgswmsparameters.cpp | 20 +- src/server/services/wms/qgswmsparameters.h | 6 + src/server/services/wms/qgswmsrenderer.cpp | 603 +++++++++++------- src/server/services/wms/qgswmsrenderer.h | 20 +- tests/src/python/test_qgsserver_wms.py | 6 +- 6 files changed, 420 insertions(+), 373 deletions(-) diff --git a/src/server/services/wms/qgswmsgetfeatureinfo.cpp b/src/server/services/wms/qgswmsgetfeatureinfo.cpp index 42d72a659cd..ced448dd88a 100644 --- a/src/server/services/wms/qgswmsgetfeatureinfo.cpp +++ b/src/server/services/wms/qgswmsgetfeatureinfo.cpp @@ -25,136 +25,6 @@ namespace QgsWms { - void writeInfoResponse( QDomDocument &infoDoc, QgsServerResponse &response, const QString &infoFormat ) - { - QByteArray ba; - QgsMessageLog::logMessage( "Info format is:" + infoFormat ); - - if ( infoFormat == QLatin1String( "text/xml" ) || infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) ) - { - ba = infoDoc.toByteArray(); - } - else if ( infoFormat == QLatin1String( "text/plain" ) || infoFormat == QLatin1String( "text/html" ) ) - { - //create string - QString featureInfoString; - - if ( infoFormat == QLatin1String( "text/plain" ) ) - { - featureInfoString.append( "GetFeatureInfo results\n" ); - featureInfoString.append( "\n" ); - } - else if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "\n" ); - featureInfoString.append( " GetFeatureInfo results \n" ); - featureInfoString.append( "\n" ); - featureInfoString.append( "\n" ); - featureInfoString.append( "\n" ); - } - - QDomNodeList layerList = infoDoc.elementsByTagName( QStringLiteral( "Layer" ) ); - - //layer loop - for ( int i = 0; i < layerList.size(); ++i ) - { - QDomElement layerElem = layerList.at( i ).toElement(); - if ( infoFormat == QLatin1String( "text/plain" ) ) - { - featureInfoString.append( "Layer '" + layerElem.attribute( QStringLiteral( "name" ) ) + "'\n" ); - } - else if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "\n" ); - featureInfoString.append( "\n" ); - featureInfoString.append( "
" ); - } - - //feature loop (for vector layers) - QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) ); - QDomElement currentFeatureElement; - - if ( featureNodeList.isEmpty() ) //raster layer? - { - QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) ); - for ( int j = 0; j < attributeNodeList.size(); ++j ) - { - QDomElement attributeElement = attributeNodeList.at( j ).toElement(); - if ( infoFormat == QLatin1String( "text/plain" ) ) - { - featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" + - attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" ); - } - else if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "\n" ); - } - } - } - else //vector layer - { - for ( int j = 0; j < featureNodeList.size(); ++j ) - { - QDomElement featureElement = featureNodeList.at( j ).toElement(); - if ( infoFormat == QLatin1String( "text/plain" ) ) - { - featureInfoString.append( "Feature " + featureElement.attribute( QStringLiteral( "id" ) ) + "\n" ); - } - else if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "
Layer" + layerElem.attribute( QStringLiteral( "name" ) ) + "
" + attributeElement.attribute( QStringLiteral( "name" ) ) + "" + - attributeElement.attribute( QStringLiteral( "value" ) ) + "
\n" ); - featureInfoString.append( "\n" ); - } - //attribute loop - QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) ); - for ( int k = 0; k < attributeNodeList.size(); ++k ) - { - QDomElement attributeElement = attributeNodeList.at( k ).toElement(); - if ( infoFormat == QLatin1String( "text/plain" ) ) - { - featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" + - attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" ); - } - else if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "\n" ); - } - } - - if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "
Feature" + featureElement.attribute( QStringLiteral( "id" ) ) + "
" + attributeElement.attribute( QStringLiteral( "name" ) ) + "" + attributeElement.attribute( QStringLiteral( "value" ) ) + "
\n
\n" ); - } - } - } - if ( infoFormat == QLatin1String( "text/plain" ) ) - { - featureInfoString.append( "\n" ); - } - else if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "\n

\n" ); - - } - } - if ( infoFormat == QLatin1String( "text/html" ) ) - { - featureInfoString.append( "\n" ); - } - ba = featureInfoString.toUtf8(); - } - else //unsupported format, set exception - { - throw QgsServiceException( QStringLiteral( "InvalidFormat" ), - QString( "Feature info format '%1' is not supported. Possibilities are 'text/plain', 'text/html' or 'text/xml'." ).arg( infoFormat ) ); - } - - response.setHeader( QStringLiteral( "Content-Type" ), infoFormat + QStringLiteral( "; charset=utf-8" ) ); - response.write( ba ); - } - - void writeGetFeatureInfo( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response ) @@ -163,9 +33,11 @@ namespace QgsWms QgsServerRequest::Parameters params = request.parameters(); QgsRenderer renderer( serverIface, project, params, getConfigParser( serverIface ) ); - QDomDocument doc = renderer.getFeatureInfo( version ); - QString outputFormat = params.value( QStringLiteral( "INFO_FORMAT" ), QStringLiteral( "text/plain" ) ); - writeInfoResponse( doc, response, outputFormat ); + std::unique_ptr result( renderer.getFeatureInfo( version ) ); + QString infoFormat = params.value( QStringLiteral( "INFO_FORMAT" ), QStringLiteral( "text/plain" ) ); + + response.setHeader( QStringLiteral( "Content-Type" ), infoFormat + QStringLiteral( "; charset=utf-8" ) ); + response.write( *result ); } diff --git a/src/server/services/wms/qgswmsparameters.cpp b/src/server/services/wms/qgswmsparameters.cpp index bcd905e2b27..9c6e827f174 100644 --- a/src/server/services/wms/qgswmsparameters.cpp +++ b/src/server/services/wms/qgswmsparameters.cpp @@ -404,7 +404,6 @@ namespace QgsWms { mRequestParameters = parameters; - log( "load WMS Request parameters:" ); const QMetaEnum metaEnum( QMetaEnum::fromType() ); foreach ( QString key, parameters.keys() ) { @@ -415,7 +414,6 @@ namespace QgsWms if ( value.canConvert( mParameters[name].mType ) ) { mParameters[name].mValue = value; - log( " - " + key + " : " + parameters[key] ); } else { @@ -716,10 +714,10 @@ namespace QgsWms { QString fStr = infoFormatAsString(); - if ( fStr.isEmpty() ) - return Format::NONE; - Format f = Format::TEXT; + if ( fStr.isEmpty() ) + return f; + if ( fStr.startsWith( QLatin1String( "text/xml" ), Qt::CaseInsensitive ) ) f = Format::XML; else if ( fStr.startsWith( QLatin1String( "text/html" ), Qt::CaseInsensitive ) ) @@ -730,6 +728,18 @@ namespace QgsWms return f; } + int QgsWmsParameters::infoFormatVersion() const + { + if ( infoFormat() != Format::GML ) + return -1; + + QString fStr = infoFormatAsString(); + if ( fStr.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ), Qt::CaseInsensitive ) ) + return 3; + else + return 2; + } + QString QgsWmsParameters::i() const { return value( ParameterName::I ).toString(); diff --git a/src/server/services/wms/qgswmsparameters.h b/src/server/services/wms/qgswmsparameters.h index ecb49e8dadd..6bc373fec5d 100644 --- a/src/server/services/wms/qgswmsparameters.h +++ b/src/server/services/wms/qgswmsparameters.h @@ -276,6 +276,12 @@ namespace QgsWms */ Format infoFormat() const; + /** Returns the infoFormat version for GML. If the INFO_FORMAT is not GML, + * then the default value is -1. + * \returns infoFormat version + */ + int infoFormatVersion() const; + /** Returns I parameter or an empty string if not defined. * \returns i parameter */ diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index cdcbdf93218..41545eafd32 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -642,16 +642,9 @@ namespace QgsWms infoPoint->setY( mapSettings.extent().yMaximum() - j * yRes - yRes / 2.0 ); } - QDomDocument QgsRenderer::getFeatureInfo( const QString &version ) + QByteArray *QgsRenderer::getFeatureInfo( const QString &version ) { // Verifying Mandatory parameters - // The INFO_FORMAT parameter is Mandatory - if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::NONE ) - { - throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ), - QStringLiteral( "INFO_FORMAT parameter is required for GetFeatureInfo" ) ); - } - // The QUERY_LAYERS parameter is Mandatory QStringList queryLayers = mWmsParameters.queryLayersNickname(); if ( queryLayers.isEmpty() ) @@ -717,21 +710,7 @@ namespace QgsWms // remove unwanted layers (restricted layers, ...) removeUnwantedLayers( layers, scaleDenominator ); // remove non identifiable layers - QStringList nonIdentifiableLayers = mProject->nonIdentifiableLayers(); - if ( !nonIdentifiableLayers.isEmpty() ) - { - QList wantedLayers; - - Q_FOREACH ( QgsMapLayer *layer, layers ) - { - if ( nonIdentifiableLayers.contains( layer->id() ) ) - continue; - - wantedLayers.append( layer ); - } - - layers = wantedLayers; - } + removeNonIdentifiableLayers( layers ); Q_FOREACH ( QgsMapLayer *layer, layers ) { @@ -754,209 +733,20 @@ namespace QgsWms std::reverse( layers.begin(), layers.end() ); mapSettings.setLayers( layers ); - int featureCount = mWmsParameters.featureCountAsInt(); - if ( featureCount < 1 ) - { - featureCount = 1; - } + QDomDocument result = featureInfoDocument( layers, mapSettings, outputImage.get(), version ); - int i = mWmsParameters.iAsInt(); - int j = mWmsParameters.jAsInt(); - if ( xyDefined && !ijDefined ) - { - i = mWmsParameters.xAsInt(); - j = mWmsParameters.yAsInt(); - } - int width = mWmsParameters.widthAsInt(); - int height = mWmsParameters.heightAsInt(); - if ( ( i != -1 && j != -1 && width != 0 && height != 0 ) && ( width != outputImage->width() || height != outputImage->height() ) ) - { - i *= ( outputImage->width() / ( double )width ); - j *= ( outputImage->height() / ( double )height ); - } + QByteArray *ba = nullptr; + ba = new QByteArray(); - // init search variables - std::unique_ptr featuresRect; - std::unique_ptr filterGeom; - std::unique_ptr infoPoint; - - if ( i != -1 && j != -1 ) - { - infoPoint.reset( new QgsPointXY() ); - infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings ); - } - else if ( filtersDefined ) - { - featuresRect.reset( new QgsRectangle() ); - } - else if ( filterGeomDefined ) - { - filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mWmsParameters.filterGeom() ) ) ); - } - - QDomDocument result; - - QDomElement getFeatureInfoElement; QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat(); - if ( infoFormat == QgsWmsParameters::Format::GML ) - { - getFeatureInfoElement = result.createElement( QStringLiteral( "wfs:FeatureCollection" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:wfs" ), QStringLiteral( "http://www.opengis.net/wfs" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ows" ), QStringLiteral( "http://www.opengis.net/ows" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:qgs" ), QStringLiteral( "http://qgis.org/gml" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://qgis.org/gml" ) ); - } + if ( infoFormat == QgsWmsParameters::Format::TEXT ) + *ba = convertFeatureInfoToText( result ); + else if ( infoFormat == QgsWmsParameters::Format::HTML ) + *ba = convertFeatureInfoToHtml( result ); else - { - QString featureInfoElemName = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( *mProject ); - if ( featureInfoElemName.isEmpty() ) - { - featureInfoElemName = QStringLiteral( "GetFeatureInfoResponse" ); - } - QString featureInfoElemNs = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( *mProject ); - if ( featureInfoElemNs.isEmpty() ) - { - getFeatureInfoElement = result.createElement( featureInfoElemName ); - } - else - { - getFeatureInfoElement = result.createElementNS( featureInfoElemNs, featureInfoElemName ); - } - //feature info schema - QString featureInfoSchema = QgsServerProjectUtils::wmsFeatureInfoSchema( *mProject ); - if ( !featureInfoSchema.isEmpty() ) - { - getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); - getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), featureInfoSchema ); - } - } - result.appendChild( getFeatureInfoElement ); + *ba = result.toByteArray(); - //Render context is needed to determine feature visibility for vector layers - QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings ); - - bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *mProject ); - - //layers can have assigned a different name for GetCapabilities - QHash layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject ); - - Q_FOREACH ( QString queryLayer, queryLayers ) - { - Q_FOREACH ( QgsMapLayer *layer, layers ) - { - if ( queryLayer == layerNickname( *layer ) ) - { - QDomElement layerElement; - if ( infoFormat == QgsWmsParameters::Format::GML ) - { - layerElement = getFeatureInfoElement; - } - else - { - layerElement = result.createElement( QStringLiteral( "Layer" ) ); - QString layerName = queryLayer; - - //check if the layer is given a different name for GetFeatureInfo output - QHash::const_iterator layerAliasIt = layerAliasMap.find( layerName ); - if ( layerAliasIt != layerAliasMap.constEnd() ) - { - layerName = layerAliasIt.value(); - } - - layerElement.setAttribute( QStringLiteral( "name" ), layerName ); - getFeatureInfoElement.appendChild( layerElement ); - if ( sia2045 ) //the name might not be unique after alias replacement - { - layerElement.setAttribute( QStringLiteral( "id" ), layer->id() ); - } - } - - if ( layer->type() == QgsMapLayer::VectorLayer ) - { - QgsVectorLayer *vectorLayer = qobject_cast( layer ); - if ( vectorLayer ) - { - if ( !featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, mWmsParameters.infoFormatAsString(), featuresRect.get(), filterGeom.get() ) ) - { - break; - } - break; - } - } - else - { - if ( infoFormat == QgsWmsParameters::Format::GML ) - { - layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ ); - getFeatureInfoElement.appendChild( layerElement ); - } - - QgsRasterLayer *rasterLayer = qobject_cast( layer ); - if ( rasterLayer ) - { - if ( !infoPoint ) - { - break; - } - QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) ); - if ( !featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version, mWmsParameters.infoFormatAsString() ) ) - { - break; - } - break; - } - } - break; - } - } - } - - if ( featuresRect ) - { - if ( infoFormat == QgsWmsParameters::Format::GML ) - { - QDomElement bBoxElem = result.createElement( QStringLiteral( "gml:boundedBy" ) ); - QDomElement boxElem; - int gmlVersion = mWmsParameters.infoFormatAsString().startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; - if ( gmlVersion < 3 ) - { - boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 ); - } - else - { - boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect.get(), result, 8 ); - } - - QgsCoordinateReferenceSystem crs = mapSettings.destinationCrs(); - if ( crs.isValid() ) - { - boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() ); - } - bBoxElem.appendChild( boxElem ); - getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child - } - else - { - QDomElement bBoxElem = result.createElement( QStringLiteral( "BoundingBox" ) ); - bBoxElem.setAttribute( QStringLiteral( "CRS" ), mapSettings.destinationCrs().authid() ); - bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( featuresRect->xMinimum(), 8 ) ); - bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( featuresRect->xMaximum(), 8 ) ); - bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( featuresRect->yMinimum(), 8 ) ); - bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( featuresRect->yMaximum(), 8 ) ); - getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child - } - } - - if ( sia2045 && infoFormat == QgsWmsParameters::Format::XML ) - { - convertFeatureInfoToSIA2045( result ); - } - - return result; + return ba; } QImage *QgsRenderer::initializeRendering( QStringList &layersList, QStringList &stylesList, QStringList &layerIdList, QgsMapSettings &mapSettings ) @@ -1242,6 +1032,224 @@ namespace QgsWms } } + QDomDocument QgsRenderer::featureInfoDocument( QList &layers, const QgsMapSettings &mapSettings, + const QImage *outputImage, const QString &version ) const + { + QStringList queryLayers = mWmsParameters.queryLayersNickname(); + + bool ijDefined = ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() ); + + bool xyDefined = ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() ); + + bool filtersDefined = !mWmsParameters.filters().isEmpty(); + + bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty(); + + int featureCount = mWmsParameters.featureCountAsInt(); + if ( featureCount < 1 ) + { + featureCount = 1; + } + + int i = mWmsParameters.iAsInt(); + int j = mWmsParameters.jAsInt(); + if ( xyDefined && !ijDefined ) + { + i = mWmsParameters.xAsInt(); + j = mWmsParameters.yAsInt(); + } + int width = mWmsParameters.widthAsInt(); + int height = mWmsParameters.heightAsInt(); + if ( ( i != -1 && j != -1 && width != 0 && height != 0 ) && ( width != outputImage->width() || height != outputImage->height() ) ) + { + i *= ( outputImage->width() / ( double )width ); + j *= ( outputImage->height() / ( double )height ); + } + + // init search variables + std::unique_ptr featuresRect; + std::unique_ptr filterGeom; + std::unique_ptr infoPoint; + + if ( i != -1 && j != -1 ) + { + infoPoint.reset( new QgsPointXY() ); + infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings ); + } + else if ( filtersDefined ) + { + featuresRect.reset( new QgsRectangle() ); + } + else if ( filterGeomDefined ) + { + filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mWmsParameters.filterGeom() ) ) ); + } + + QDomDocument result; + + QDomElement getFeatureInfoElement; + QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat(); + if ( infoFormat == QgsWmsParameters::Format::GML ) + { + getFeatureInfoElement = result.createElement( QStringLiteral( "wfs:FeatureCollection" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:wfs" ), QStringLiteral( "http://www.opengis.net/wfs" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ows" ), QStringLiteral( "http://www.opengis.net/ows" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:qgs" ), QStringLiteral( "http://qgis.org/gml" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://qgis.org/gml" ) ); + } + else + { + QString featureInfoElemName = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( *mProject ); + if ( featureInfoElemName.isEmpty() ) + { + featureInfoElemName = QStringLiteral( "GetFeatureInfoResponse" ); + } + QString featureInfoElemNs = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( *mProject ); + if ( featureInfoElemNs.isEmpty() ) + { + getFeatureInfoElement = result.createElement( featureInfoElemName ); + } + else + { + getFeatureInfoElement = result.createElementNS( featureInfoElemNs, featureInfoElemName ); + } + //feature info schema + QString featureInfoSchema = QgsServerProjectUtils::wmsFeatureInfoSchema( *mProject ); + if ( !featureInfoSchema.isEmpty() ) + { + getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); + getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), featureInfoSchema ); + } + } + result.appendChild( getFeatureInfoElement ); + + //Render context is needed to determine feature visibility for vector layers + QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings ); + + bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *mProject ); + + //layers can have assigned a different name for GetCapabilities + QHash layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject ); + + Q_FOREACH ( QString queryLayer, queryLayers ) + { + Q_FOREACH ( QgsMapLayer *layer, layers ) + { + if ( queryLayer == layerNickname( *layer ) ) + { + QDomElement layerElement; + if ( infoFormat == QgsWmsParameters::Format::GML ) + { + layerElement = getFeatureInfoElement; + } + else + { + layerElement = result.createElement( QStringLiteral( "Layer" ) ); + QString layerName = queryLayer; + + //check if the layer is given a different name for GetFeatureInfo output + QHash::const_iterator layerAliasIt = layerAliasMap.find( layerName ); + if ( layerAliasIt != layerAliasMap.constEnd() ) + { + layerName = layerAliasIt.value(); + } + + layerElement.setAttribute( QStringLiteral( "name" ), layerName ); + getFeatureInfoElement.appendChild( layerElement ); + if ( sia2045 ) //the name might not be unique after alias replacement + { + layerElement.setAttribute( QStringLiteral( "id" ), layer->id() ); + } + } + + if ( layer->type() == QgsMapLayer::VectorLayer ) + { + QgsVectorLayer *vectorLayer = qobject_cast( layer ); + if ( vectorLayer ) + { + if ( !featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, featuresRect.get(), filterGeom.get() ) ) + { + break; + } + break; + } + } + else + { + if ( infoFormat == QgsWmsParameters::Format::GML ) + { + layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ ); + getFeatureInfoElement.appendChild( layerElement ); + } + + QgsRasterLayer *rasterLayer = qobject_cast( layer ); + if ( rasterLayer ) + { + if ( !infoPoint ) + { + break; + } + QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) ); + if ( !featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version ) ) + { + break; + } + break; + } + } + break; + } + } + } + + if ( featuresRect ) + { + if ( infoFormat == QgsWmsParameters::Format::GML ) + { + QDomElement bBoxElem = result.createElement( QStringLiteral( "gml:boundedBy" ) ); + QDomElement boxElem; + int gmlVersion = mWmsParameters.infoFormatVersion(); + if ( gmlVersion < 3 ) + { + boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 ); + } + else + { + boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect.get(), result, 8 ); + } + + QgsCoordinateReferenceSystem crs = mapSettings.destinationCrs(); + if ( crs.isValid() ) + { + boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() ); + } + bBoxElem.appendChild( boxElem ); + getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child + } + else + { + QDomElement bBoxElem = result.createElement( QStringLiteral( "BoundingBox" ) ); + bBoxElem.setAttribute( QStringLiteral( "CRS" ), mapSettings.destinationCrs().authid() ); + bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( featuresRect->xMinimum(), 8 ) ); + bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( featuresRect->xMaximum(), 8 ) ); + bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( featuresRect->yMinimum(), 8 ) ); + bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( featuresRect->yMaximum(), 8 ) ); + getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child + } + } + + if ( sia2045 && infoFormat == QgsWmsParameters::Format::XML ) + { + convertFeatureInfoToSia2045( result ); + } + + return result; + } + bool QgsRenderer::featureInfoFromVectorLayer( QgsVectorLayer *layer, const QgsPointXY *infoPoint, int nFeatures, @@ -1250,7 +1258,6 @@ namespace QgsWms const QgsMapSettings &mapSettings, QgsRenderContext &renderContext, const QString &version, - const QString &infoFormat, QgsRectangle *featureBBox, QgsGeometry *filterGeom ) const { @@ -1387,7 +1394,7 @@ namespace QgsWms if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML ) { bool withGeom = layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry; - int gmlVersion = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; + int gmlVersion = mWmsParameters.infoFormatVersion(); QString typeName = layerNickname( *layer ); QDomElement elem = createFeatureGML( &feature, layer, infoDocument, outputCrs, mapSettings, typeName, withGeom, gmlVersion @@ -1506,8 +1513,7 @@ namespace QgsWms const QgsPointXY *infoPoint, QDomDocument &infoDocument, QDomElement &layerElement, - const QString &version, - const QString &infoFormat ) const + const QString &version ) const { Q_UNUSED( version ); @@ -1549,7 +1555,7 @@ namespace QgsWms feature.setFields( fields ); QgsCoordinateReferenceSystem layerCrs = layer->crs(); - int gmlVersion = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2; + int gmlVersion = mWmsParameters.infoFormatVersion(); QString typeName = layerNickname( *layer ); QDomElement elem = createFeatureGML( &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr ); @@ -2088,7 +2094,7 @@ namespace QgsWms return true; } - void QgsRenderer::convertFeatureInfoToSIA2045( QDomDocument &doc ) + void QgsRenderer::convertFeatureInfoToSia2045( QDomDocument &doc ) const { QDomDocument SIAInfoDoc; QDomElement infoDocElement = doc.documentElement(); @@ -2203,6 +2209,130 @@ namespace QgsWms doc = SIAInfoDoc; } + QByteArray QgsRenderer::convertFeatureInfoToHtml( const QDomDocument &doc ) const + { + QString featureInfoString; + + //the HTML head + featureInfoString.append( "\n" ); + featureInfoString.append( " GetFeatureInfo results \n" ); + featureInfoString.append( "\n" ); + featureInfoString.append( "\n" ); + + //start the html body + featureInfoString.append( "\n" ); + + QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) ); + + //layer loop + for ( int i = 0; i < layerList.size(); ++i ) + { + QDomElement layerElem = layerList.at( i ).toElement(); + + featureInfoString.append( "\n" ); + featureInfoString.append( "\n" ); + featureInfoString.append( "
" ); + + //feature loop (for vector layers) + QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) ); + QDomElement currentFeatureElement; + + if ( !featureNodeList.isEmpty() ) //vector layer + { + for ( int j = 0; j < featureNodeList.size(); ++j ) + { + QDomElement featureElement = featureNodeList.at( j ).toElement(); + featureInfoString.append( "
Layer" + layerElem.attribute( QStringLiteral( "name" ) ) + "
\n" ); + featureInfoString.append( "\n" ); + + //attribute loop + QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) ); + for ( int k = 0; k < attributeNodeList.size(); ++k ) + { + QDomElement attributeElement = attributeNodeList.at( k ).toElement(); + featureInfoString.append( "\n" ); + } + + featureInfoString.append( "
Feature" + featureElement.attribute( QStringLiteral( "id" ) ) + + "
" + attributeElement.attribute( QStringLiteral( "name" ) ) + + "" + attributeElement.attribute( QStringLiteral( "value" ) ) + "
\n
\n" ); + } + } + else //raster layer + { + QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) ); + for ( int j = 0; j < attributeNodeList.size(); ++j ) + { + QDomElement attributeElement = attributeNodeList.at( j ).toElement(); + featureInfoString.append( "" + attributeElement.attribute( QStringLiteral( "name" ) ) + + "" + attributeElement.attribute( QStringLiteral( "value" ) ) + "\n" ); + } + } + + featureInfoString.append( "\n

\n" ); + } + + //start the html body + featureInfoString.append( "\n" ); + + return featureInfoString.toUtf8(); + } + + QByteArray QgsRenderer::convertFeatureInfoToText( const QDomDocument &doc ) const + { + QString featureInfoString; + + //the Text head + featureInfoString.append( "GetFeatureInfo results\n" ); + featureInfoString.append( "\n" ); + + QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) ); + + //layer loop + for ( int i = 0; i < layerList.size(); ++i ) + { + QDomElement layerElem = layerList.at( i ).toElement(); + + featureInfoString.append( "Layer '" + layerElem.attribute( QStringLiteral( "name" ) ) + "'\n" ); + + //feature loop (for vector layers) + QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) ); + QDomElement currentFeatureElement; + + if ( !featureNodeList.isEmpty() ) //vector layer + { + for ( int j = 0; j < featureNodeList.size(); ++j ) + { + QDomElement featureElement = featureNodeList.at( j ).toElement(); + featureInfoString.append( "Feature " + featureElement.attribute( QStringLiteral( "id" ) ) + "\n" ); + + //attribute loop + QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) ); + for ( int k = 0; k < attributeNodeList.size(); ++k ) + { + QDomElement attributeElement = attributeNodeList.at( k ).toElement(); + featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" + + attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" ); + } + } + } + else //raster layer + { + QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) ); + for ( int j = 0; j < attributeNodeList.size(); ++j ) + { + QDomElement attributeElement = attributeNodeList.at( j ).toElement(); + featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" + + attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" ); + } + } + + featureInfoString.append( "\n" ); + } + + return featureInfoString.toUtf8(); + } + QDomElement QgsRenderer::createFeatureGML( QgsFeature *feat, QgsVectorLayer *layer, @@ -2911,6 +3041,25 @@ namespace QgsWms layers = wantedLayers; } + void QgsRenderer::removeNonIdentifiableLayers( QList &layers ) const + { + QStringList nonIdentifiableLayers = mProject->nonIdentifiableLayers(); + if ( !nonIdentifiableLayers.isEmpty() ) + { + QList wantedLayers; + + Q_FOREACH ( QgsMapLayer *layer, layers ) + { + if ( nonIdentifiableLayers.contains( layer->id() ) ) + continue; + + wantedLayers.append( layer ); + } + + layers = wantedLayers; + } + } + QgsLayerTreeModel *QgsRenderer::buildLegendTreeModel( const QList &layers, double scaleDenominator, QgsLayerTree &rootGroup ) { // get params diff --git a/src/server/services/wms/qgswmsrenderer.h b/src/server/services/wms/qgswmsrenderer.h index 19f83f4a3a2..aba143014de 100644 --- a/src/server/services/wms/qgswmsrenderer.h +++ b/src/server/services/wms/qgswmsrenderer.h @@ -116,7 +116,7 @@ namespace QgsWms /** Creates an xml document that describes the result of the getFeatureInfo request. * May throw an exception */ - QDomDocument getFeatureInfo( const QString &version = "1.3.0" ); + QByteArray *getFeatureInfo( const QString &version = "1.3.0" ); private: @@ -140,6 +140,9 @@ namespace QgsWms // Remove unwanted layers (restricted, not visible, etc) void removeUnwantedLayers( QList &layers, double scaleDenominator = -1 ) const; + // Remove non identifiable layers (restricted, not visible, etc) + void removeNonIdentifiableLayers( QList &layers ) const; + // Rendering step for layers QPainter *layersRendering( const QgsMapSettings &mapSettings, QImage &image, HitTest *hitTest = nullptr ) const; @@ -211,6 +214,9 @@ namespace QgsWms */ void initializeSLDParser( QStringList &layersList, QStringList &stylesList ); + QDomDocument featureInfoDocument( QList &layers, const QgsMapSettings &mapSettings, + const QImage *outputImage, const QString &version ) const; + /** Appends feature info xml for the layer to the layer element of the feature info dom document \param featureBBox the bounding box of the selected features in output CRS \returns true in case of success*/ @@ -222,7 +228,6 @@ namespace QgsWms const QgsMapSettings &mapSettings, QgsRenderContext &renderContext, const QString &version, - const QString &infoFormat, QgsRectangle *featureBBox = nullptr, QgsGeometry *filterGeom = nullptr ) const; //! Appends feature info xml for the layer to the layer element of the dom document @@ -231,8 +236,7 @@ namespace QgsWms const QgsPointXY *infoPoint, QDomDocument &infoDocument, QDomElement &layerElement, - const QString &version, - const QString &infoFormat ) const; + const QString &version ) const; /** Creates a layer set and returns a stringlist with layer ids that can be passed to a renderer. Usually used in conjunction with readLayersAndStyles \param scaleDenominator Filter out layer if scale based visibility does not match (or use -1 if no scale restriction)*/ @@ -291,7 +295,13 @@ namespace QgsWms bool checkMaximumWidthHeight() const; //! Converts a feature info xml document to SIA2045 norm - void convertFeatureInfoToSIA2045( QDomDocument &doc ); + void convertFeatureInfoToSia2045( QDomDocument &doc ) const; + + //! Converts a feature info xml document to HTML + QByteArray convertFeatureInfoToHtml( const QDomDocument &doc ) const; + + //! Converts a feature info xml document to Text + QByteArray convertFeatureInfoToText( const QDomDocument &doc ) const; QDomElement createFeatureGML( QgsFeature *feat, diff --git a/tests/src/python/test_qgsserver_wms.py b/tests/src/python/test_qgsserver_wms.py index eec680b4ed3..e41840866a7 100644 --- a/tests/src/python/test_qgsserver_wms.py +++ b/tests/src/python/test_qgsserver_wms.py @@ -69,7 +69,7 @@ class TestQgsServerWMS(QgsServerTestBase): for request in ('GetCapabilities', 'GetProjectSettings', 'GetContext'): self.wms_request_compare(request) - # Test getfeatureinfo response xml (default info_format) + # Test getfeatureinfo response xml self.wms_request_compare('GetFeatureInfo', '&layers=testlayer%20%C3%A8%C3%A9&styles=&' + 'info_format=text%2Fxml&transparent=true&' + @@ -87,10 +87,10 @@ class TestQgsServerWMS(QgsServerTestBase): 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320', 'wms_getfeatureinfo-text-html') - # Test getfeatureinfo response text + # Test getfeatureinfo default info_format self.wms_request_compare('GetFeatureInfo', '&layers=testlayer%20%C3%A8%C3%A9&styles=&' + - 'info_format=text%2Fplain&transparent=true&' + + 'transparent=true&' + 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' + '5606005.488876367%2C913235.426296057%2C5606035.347090538&' + 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320', From bae6d56388d8f5dabec3d5df723e96dd90c47f25 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Mon, 17 Jul 2017 22:22:49 +0200 Subject: [PATCH 010/266] consider datum transformation when pasting features (fixes #16846) --- doc/api_break.dox | 1 + python/core/qgsdatumtransformstore.sip | 5 +++-- python/gui/qgsmapcanvas.sip | 1 + src/app/qgsclipboard.cpp | 25 ++++++++++++++++++++++++- src/app/qgsclipboard.h | 5 +++++ src/core/qgsdatumtransformstore.cpp | 8 +++++--- src/core/qgsdatumtransformstore.h | 5 +++-- 7 files changed, 42 insertions(+), 8 deletions(-) diff --git a/doc/api_break.dox b/doc/api_break.dox index fb3bb150f11..22c35ec849a 100644 --- a/doc/api_break.dox +++ b/doc/api_break.dox @@ -960,6 +960,7 @@ QgsDatumTransformStore {#qgis_api_break_3_0_QgsDatumTransformStore} - transformation() now returns a QgsCoordinateTransform object, not a pointer. An invalid QgsCoordinateTransform will be returned in place of a null pointer. +- transformation() also optional source and destination authid parameters QgsDiagram {#qgis_api_break_3_0_QgsDiagram} diff --git a/python/core/qgsdatumtransformstore.sip b/python/core/qgsdatumtransformstore.sip index 54a306d1baa..fe7936e5c01 100644 --- a/python/core/qgsdatumtransformstore.sip +++ b/python/core/qgsdatumtransformstore.sip @@ -36,12 +36,13 @@ class QgsDatumTransformStore :rtype: bool %End - QgsCoordinateTransform transformation( const QgsMapLayer *layer ) const; + QgsCoordinateTransform transformation( const QgsMapLayer *layer, QString srcAuthId = QString(), QString dstAuthId = QString() ) const; %Docstring Will return transform from layer's CRS to current destination CRS. - Will emit datumTransformInfoRequested signal if the layer has no entry. :return: transformation associated with layer, or an invalid QgsCoordinateTransform if no transform is associated with the layer + \param srcAuthId source CRS (defaults to layer crs) + \param dstAuthId destination CRS (defaults to store's crs) :rtype: QgsCoordinateTransform %End diff --git a/python/gui/qgsmapcanvas.sip b/python/gui/qgsmapcanvas.sip index ad8d1199000..2fff0b77c91 100644 --- a/python/gui/qgsmapcanvas.sip +++ b/python/gui/qgsmapcanvas.sip @@ -853,6 +853,7 @@ called when panning is in action, reset indicates end of panning + void updateDatumTransformEntries(); %Docstring Make sure the datum transform store is properly populated diff --git a/src/app/qgsclipboard.cpp b/src/app/qgsclipboard.cpp index f217fa9f0e7..9299b871821 100644 --- a/src/app/qgsclipboard.cpp +++ b/src/app/qgsclipboard.cpp @@ -36,6 +36,8 @@ #include "qgsogrutils.h" #include "qgsjsonutils.h" #include "qgssettings.h" +#include "qgisapp.h" +#include "qgsmapcanvas.h" QgsClipboard::QgsClipboard() : QObject() @@ -59,6 +61,9 @@ void QgsClipboard::replaceWithCopyOf( QgsVectorLayer *src ) mFeatureFields = src->fields(); mFeatureClipboard = src->selectedFeatures(); mCRS = src->crs(); + layerDestroyed(); + mSrcLayer = src; + connect( mSrcLayer, &QObject::destroyed, this, &QgsClipboard::layerDestroyed ); QgsDebugMsg( "replaced QGis clipboard." ); @@ -73,11 +78,19 @@ void QgsClipboard::replaceWithCopyOf( QgsFeatureStore &featureStore ) mFeatureFields = featureStore.fields(); mFeatureClipboard = featureStore.features(); mCRS = featureStore.crs(); + disconnect( mSrcLayer, &QObject::destroyed, this, &QgsClipboard::layerDestroyed ); + mSrcLayer = nullptr; setSystemClipboard(); mUseSystemClipboard = false; emit changed(); } +void QgsClipboard::layerDestroyed() +{ + disconnect( mSrcLayer, &QObject::destroyed, this, &QgsClipboard::layerDestroyed ); + mSrcLayer = nullptr; +} + QString QgsClipboard::generateClipboardText() const { QgsSettings settings; @@ -264,7 +277,17 @@ bool QgsClipboard::isEmpty() const QgsFeatureList QgsClipboard::transformedCopyOf( const QgsCoordinateReferenceSystem &destCRS, const QgsFields &fields ) const { QgsFeatureList featureList = copyOf( fields ); - QgsCoordinateTransform ct( crs(), destCRS ); + + QgsCoordinateTransform ct; + if ( mSrcLayer ) + { + QgisApp::instance()->mapCanvas()->getDatumTransformInfo( mSrcLayer, crs().authid(), destCRS.authid() ); + ct = QgisApp::instance()->mapCanvas()->mapSettings().datumTransformStore().transformation( mSrcLayer, crs().authid(), destCRS.authid() ); + } + else + { + ct = QgsCoordinateTransform( crs(), destCRS ); + } QgsDebugMsg( "transforming clipboard." ); for ( QgsFeatureList::iterator iter = featureList.begin(); iter != featureList.end(); ++iter ) diff --git a/src/app/qgsclipboard.h b/src/app/qgsclipboard.h index 2539a126809..598399d341f 100644 --- a/src/app/qgsclipboard.h +++ b/src/app/qgsclipboard.h @@ -148,6 +148,10 @@ class APP_EXPORT QgsClipboard : public QObject //! Emitted when content changed void changed(); + private slots: + //! source layer destroyed + void layerDestroyed(); + private: /** @@ -180,6 +184,7 @@ class APP_EXPORT QgsClipboard : public QObject QgsFeatureList mFeatureClipboard; QgsFields mFeatureFields; QgsCoordinateReferenceSystem mCRS; + QgsVectorLayer *mSrcLayer = nullptr; //! True when the data from the system clipboard should be read bool mUseSystemClipboard; diff --git a/src/core/qgsdatumtransformstore.cpp b/src/core/qgsdatumtransformstore.cpp index 3d3ad0baa28..38574cb1974 100644 --- a/src/core/qgsdatumtransformstore.cpp +++ b/src/core/qgsdatumtransformstore.cpp @@ -50,13 +50,15 @@ bool QgsDatumTransformStore::hasEntryForLayer( QgsMapLayer *layer ) const return mEntries.contains( layer->id() ); } -QgsCoordinateTransform QgsDatumTransformStore::transformation( const QgsMapLayer *layer ) const +QgsCoordinateTransform QgsDatumTransformStore::transformation( const QgsMapLayer *layer, QString srcAuthId, QString dstAuthId ) const { if ( !layer ) return QgsCoordinateTransform(); - QString srcAuthId = layer->crs().authid(); - QString dstAuthId = mDestCRS.authid(); + if ( srcAuthId.isEmpty() ) + srcAuthId = layer->crs().authid(); + if ( dstAuthId.isEmpty() ) + dstAuthId = mDestCRS.authid(); if ( srcAuthId == dstAuthId ) { diff --git a/src/core/qgsdatumtransformstore.h b/src/core/qgsdatumtransformstore.h index 0dfe43c7486..1adb91046ee 100644 --- a/src/core/qgsdatumtransformstore.h +++ b/src/core/qgsdatumtransformstore.h @@ -45,11 +45,12 @@ class CORE_EXPORT QgsDatumTransformStore /** * Will return transform from layer's CRS to current destination CRS. - * Will emit datumTransformInfoRequested signal if the layer has no entry. * \returns transformation associated with layer, or an invalid QgsCoordinateTransform * if no transform is associated with the layer + * \param srcAuthId source CRS (defaults to layer crs) + * \param dstAuthId destination CRS (defaults to store's crs) */ - QgsCoordinateTransform transformation( const QgsMapLayer *layer ) const; + QgsCoordinateTransform transformation( const QgsMapLayer *layer, QString srcAuthId = QString(), QString dstAuthId = QString() ) const; void readXml( const QDomNode &parentNode ); From 205b7051dc44d7460df7b9909c918a9b60f6366c Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Mon, 17 Jul 2017 22:24:18 +0200 Subject: [PATCH 011/266] don't close database selection dialogs --- src/gui/qgsmapcanvas.h | 1 + src/providers/db2/qgsdb2sourceselect.cpp | 2 +- src/providers/mssql/qgsmssqlsourceselect.cpp | 2 +- .../oracle/qgsoraclesourceselect.cpp | 20 +++++++++++-------- src/providers/postgres/qgspgsourceselect.cpp | 1 - .../spatialite/qgsspatialitesourceselect.cpp | 2 +- src/providers/wfs/qgswfssourceselect.cpp | 3 +-- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/gui/qgsmapcanvas.h b/src/gui/qgsmapcanvas.h index 2d3ed7304fd..b62b5d3e6b5 100644 --- a/src/gui/qgsmapcanvas.h +++ b/src/gui/qgsmapcanvas.h @@ -729,6 +729,7 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView */ void connectNotify( const char *signal ) override; #endif + //! Make sure the datum transform store is properly populated void updateDatumTransformEntries(); diff --git a/src/providers/db2/qgsdb2sourceselect.cpp b/src/providers/db2/qgsdb2sourceselect.cpp index 666367f95e1..159dfdef166 100644 --- a/src/providers/db2/qgsdb2sourceselect.cpp +++ b/src/providers/db2/qgsdb2sourceselect.cpp @@ -454,7 +454,7 @@ void QgsDb2SourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "DB2" ) ); - if ( !mHoldDialogOpen->isChecked() ) + if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/mssql/qgsmssqlsourceselect.cpp b/src/providers/mssql/qgsmssqlsourceselect.cpp index 58ec85e410f..068913582d8 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.cpp +++ b/src/providers/mssql/qgsmssqlsourceselect.cpp @@ -454,7 +454,7 @@ void QgsMssqlSourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "mssql" ) ); - if ( !mHoldDialogOpen->isChecked() ) + if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/oracle/qgsoraclesourceselect.cpp b/src/providers/oracle/qgsoraclesourceselect.cpp index 68b9a45cd87..545775a9c08 100644 --- a/src/providers/oracle/qgsoraclesourceselect.cpp +++ b/src/providers/oracle/qgsoraclesourceselect.cpp @@ -174,9 +174,10 @@ QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags f { setupUi( this ); - if ( mWidgetMode == QgsProviderRegistry::WidgetMode::Embedded ) + if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) { - buttonBox->button( QDialogButtonBox::Close )->hide(); + buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); + mHoldDialogOpen->hide(); } else { @@ -190,11 +191,14 @@ QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags f mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); - connect( mAddButton, SIGNAL( clicked() ), this, SLOT( addTables() ) ); + if ( mWidgetMode != QgsProviderRegistry::WidgetMode::Manager ) + { + buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); + connect( mAddButton, &QAbstractButton::clicked, this, &QgsOracleSourceSelect::addTables ); - buttonBox->addButton( mBuildQueryButton, QDialogButtonBox::ActionRole ); - connect( mBuildQueryButton, SIGNAL( clicked() ), this, SLOT( buildQuery() ) ); + buttonBox->addButton( mBuildQueryButton, QDialogButtonBox::ActionRole ); + connect( mBuildQueryButton, &QAbstractButton::clicked, this, &QgsOracleSourceSelect::buildQuery ); + } mSearchModeComboBox->addItem( tr( "Wildcard" ) ); mSearchModeComboBox->addItem( tr( "RegExp" ) ); @@ -220,7 +224,7 @@ QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags f mTablesTreeView->setEditTriggers( QAbstractItemView::CurrentChanged ); mTablesTreeView->setItemDelegate( mTablesTreeDelegate ); - connect( mTablesTreeView->selectionModel(), SIGNAL( selectionChanged( const QItemSelection &, const QItemSelection & ) ), this, SLOT( treeWidgetSelectionChanged( const QItemSelection &, const QItemSelection & ) ) ); + connect( mTablesTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsOracleSourceSelect::treeWidgetSelectionChanged ); QgsSettings settings; mTablesTreeView->setSelectionMode( settings.value( "qgis/addOracleDC", false ).toBool() ? @@ -490,7 +494,7 @@ void QgsOracleSourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, "oracle" ); - if ( !mHoldDialogOpen->isChecked() ) + if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/postgres/qgspgsourceselect.cpp b/src/providers/postgres/qgspgsourceselect.cpp index 16a5b0ef780..fc9b5d55c11 100644 --- a/src/providers/postgres/qgspgsourceselect.cpp +++ b/src/providers/postgres/qgspgsourceselect.cpp @@ -204,7 +204,6 @@ QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsPr if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); - mHoldDialogOpen->setHidden( true ); mHoldDialogOpen->hide(); } else diff --git a/src/providers/spatialite/qgsspatialitesourceselect.cpp b/src/providers/spatialite/qgsspatialitesourceselect.cpp index 1728225ce98..62b9214de6d 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.cpp +++ b/src/providers/spatialite/qgsspatialitesourceselect.cpp @@ -418,7 +418,7 @@ void QgsSpatiaLiteSourceSelect::addTables() else { emit addDatabaseLayers( m_selectedTables, QStringLiteral( "spatialite" ) ); - if ( mWidgetMode == QgsProviderRegistry::WidgetMode::None && ! mHoldDialogOpen->isChecked() ) + if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/wfs/qgswfssourceselect.cpp b/src/providers/wfs/qgswfssourceselect.cpp index 38989f85aec..f60aa507d38 100644 --- a/src/providers/wfs/qgswfssourceselect.cpp +++ b/src/providers/wfs/qgswfssourceselect.cpp @@ -61,7 +61,6 @@ QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs // For some obscure reason hiding does not work! // buttonBox->button( QDialogButtonBox::Close )->hide(); buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); - mHoldDialogOpen->setHidden( true ); mHoldDialogOpen->hide(); } @@ -405,7 +404,7 @@ void QgsWFSSourceSelect::addLayer() emit addWfsLayer( mUri, layerName ); } - if ( ! mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) { accept(); } From 9bae83275368d09b14a4b25951724b1a30b363b7 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Mon, 17 Jul 2017 23:57:04 +0200 Subject: [PATCH 012/266] add (unemitted) signals to provider source selectors to silence data source manager connection warnings --- python/gui/qgsowssourceselect.sip | 2 -- src/app/qgisapp.h | 2 +- src/app/qgsclipboard.cpp | 10 ---------- src/app/qgsclipboard.h | 7 ++----- src/gui/qgsdatasourcemanagerdialog.cpp | 4 ++-- src/gui/qgsdatasourcemanagerdialog.h | 2 +- src/gui/qgsowssourceselect.h | 2 -- src/providers/arcgisrest/qgsamssourceselect.h | 5 +++++ src/providers/db2/qgsdb2sourceselect.h | 2 ++ src/providers/mssql/qgsmssqlsourceselect.h | 2 ++ src/providers/spatialite/qgsspatialitesourceselect.h | 2 ++ 11 files changed, 17 insertions(+), 23 deletions(-) diff --git a/python/gui/qgsowssourceselect.sip b/python/gui/qgsowssourceselect.sip index f8873c864db..a05129b202c 100644 --- a/python/gui/qgsowssourceselect.sip +++ b/python/gui/qgsowssourceselect.sip @@ -106,8 +106,6 @@ Stores the selected datasource whenerver it is changed Add some default wms servers to the list %End - void on_mDialogButtonBox_helpRequested(); - signals: void addRasterLayer( const QString &rasterLayerPath, const QString &baseName, diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 62eb08381a1..08e316be521 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -805,7 +805,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow */ bool addVectorLayers( const QStringList &layerQStringList, const QString &enc, const QString &dataSourceType ); - /** Overloaded vesion of the private addRasterLayer() + /** Overloaded version of the private addRasterLayer() Method that takes a list of file names instead of prompting user with a dialog. \returns true if successfully added layer(s) diff --git a/src/app/qgsclipboard.cpp b/src/app/qgsclipboard.cpp index 9299b871821..b9337334424 100644 --- a/src/app/qgsclipboard.cpp +++ b/src/app/qgsclipboard.cpp @@ -61,10 +61,7 @@ void QgsClipboard::replaceWithCopyOf( QgsVectorLayer *src ) mFeatureFields = src->fields(); mFeatureClipboard = src->selectedFeatures(); mCRS = src->crs(); - layerDestroyed(); mSrcLayer = src; - connect( mSrcLayer, &QObject::destroyed, this, &QgsClipboard::layerDestroyed ); - QgsDebugMsg( "replaced QGis clipboard." ); setSystemClipboard(); @@ -78,19 +75,12 @@ void QgsClipboard::replaceWithCopyOf( QgsFeatureStore &featureStore ) mFeatureFields = featureStore.fields(); mFeatureClipboard = featureStore.features(); mCRS = featureStore.crs(); - disconnect( mSrcLayer, &QObject::destroyed, this, &QgsClipboard::layerDestroyed ); mSrcLayer = nullptr; setSystemClipboard(); mUseSystemClipboard = false; emit changed(); } -void QgsClipboard::layerDestroyed() -{ - disconnect( mSrcLayer, &QObject::destroyed, this, &QgsClipboard::layerDestroyed ); - mSrcLayer = nullptr; -} - QString QgsClipboard::generateClipboardText() const { QgsSettings settings; diff --git a/src/app/qgsclipboard.h b/src/app/qgsclipboard.h index 598399d341f..7237aa6cbd7 100644 --- a/src/app/qgsclipboard.h +++ b/src/app/qgsclipboard.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "qgsfields.h" #include "qgsfeature.h" @@ -148,10 +149,6 @@ class APP_EXPORT QgsClipboard : public QObject //! Emitted when content changed void changed(); - private slots: - //! source layer destroyed - void layerDestroyed(); - private: /** @@ -184,7 +181,7 @@ class APP_EXPORT QgsClipboard : public QObject QgsFeatureList mFeatureClipboard; QgsFields mFeatureFields; QgsCoordinateReferenceSystem mCRS; - QgsVectorLayer *mSrcLayer = nullptr; + QPointer mSrcLayer; //! True when the data from the system clipboard should be read bool mUseSystemClipboard; diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 5663c0b7c77..8bd37891f3c 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -168,12 +168,12 @@ void QgsDataSourceManagerDialog::setPreviousPage() void QgsDataSourceManagerDialog::rasterLayerAdded( const QString &uri, const QString &baseName, const QString &providerKey ) { - emit( addRasterLayer( uri, baseName, providerKey ) ); + emit addRasterLayer( uri, baseName, providerKey ); } void QgsDataSourceManagerDialog::vectorLayerAdded( const QString &vectorLayerPath, const QString &baseName, const QString &providerKey ) { - emit( addVectorLayer( vectorLayerPath, baseName, providerKey ) ); + emit addVectorLayer( vectorLayerPath, baseName, providerKey ); } void QgsDataSourceManagerDialog::vectorLayersAdded( const QStringList &layerQStringList, const QString &enc, const QString &dataSourceType ) diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index 63bcb1df7c7..5fe10138e0e 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -79,7 +79,7 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva //! Emitted when a raster layer was selected for addition: for signal forwarding to QgisApp void addRasterLayer( QString const &uri, QString const &baseName, QString const &providerKey ); //! Emitted when the user wants to select a raster layer: for signal forwarding to QgisApp - void addRasterLayer( ); + void addRasterLayer(); //! Emitted when a vector layer was selected for addition: for signal forwarding to QgisApp void addVectorLayer( const QString &vectorLayerPath, const QString &baseName, const QString &providerKey ); //! Replace the selected layer by a vector layer defined by uri, layer name, data source uri diff --git a/src/gui/qgsowssourceselect.h b/src/gui/qgsowssourceselect.h index 7c37bfa722e..69e84cc2dbd 100644 --- a/src/gui/qgsowssourceselect.h +++ b/src/gui/qgsowssourceselect.h @@ -106,8 +106,6 @@ class GUI_EXPORT QgsOWSSourceSelect : public QDialog, protected Ui::QgsOWSSource //! Add some default wms servers to the list void on_mAddDefaultButton_clicked(); - void on_mDialogButtonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); } - signals: void addRasterLayer( const QString &rasterLayerPath, const QString &baseName, diff --git a/src/providers/arcgisrest/qgsamssourceselect.h b/src/providers/arcgisrest/qgsamssourceselect.h index d0f3c2eda0c..42bdcd9d556 100644 --- a/src/providers/arcgisrest/qgsamssourceselect.h +++ b/src/providers/arcgisrest/qgsamssourceselect.h @@ -30,6 +30,11 @@ class QgsAmsSourceSelect: public QgsSourceSelectDialog public: QgsAmsSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + signals: + void addRasterLayer( QString const &rasterLayerPath, + QString const &baseName, + QString const &providerKey ); + protected: bool connectToService( const QgsOwsConnection &connection ) override; QString getLayerURI( const QgsOwsConnection &connection, diff --git a/src/providers/db2/qgsdb2sourceselect.h b/src/providers/db2/qgsdb2sourceselect.h index b780db39814..762dbf0724c 100644 --- a/src/providers/db2/qgsdb2sourceselect.h +++ b/src/providers/db2/qgsdb2sourceselect.h @@ -111,6 +111,8 @@ class QgsDb2SourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); void connectionsChanged(); void addGeometryColumn( QgsDb2LayerProperty ); + void progress( int, int ); + void progressMessage( QString ); public slots: //! Determines the tables the user selected and closes the dialog diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h index 737d326220a..680ddb3aa03 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.h +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -81,6 +81,8 @@ class QgsMssqlSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); void connectionsChanged(); void addGeometryColumn( const QgsMssqlLayerProperty & ); + void progress( int, int ); + void progressMessage( QString ); public slots: //! Determines the tables the user selected and closes the dialog diff --git a/src/providers/spatialite/qgsspatialitesourceselect.h b/src/providers/spatialite/qgsspatialitesourceselect.h index 6c6432dde03..17fbc745601 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.h +++ b/src/providers/spatialite/qgsspatialitesourceselect.h @@ -97,6 +97,8 @@ class QgsSpatiaLiteSourceSelect: public QDialog, private Ui::QgsDbSourceSelectBa signals: void connectionsChanged(); void addDatabaseLayers( QStringList const &paths, QString const &providerKey ); + void progress( int, int ); + void progressMessage( QString ); private: enum Columns From f405b96ddd812fbb63327be7e0f18731e3e82b1a Mon Sep 17 00:00:00 2001 From: Sandro Mani Date: Mon, 17 Jul 2017 14:28:16 +0200 Subject: [PATCH 013/266] Demote translation loading warning to debug msg (forward port from Sourcepole's fork) --- src/app/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index 583bf63dd4a..959bbbcbb27 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -1034,7 +1034,7 @@ int main( int argc, char *argv[] ) } else { - qWarning( "loading of qgis translation failed [%s]", QStringLiteral( "%1/qgis_%2" ).arg( i18nPath, myTranslationCode ).toLocal8Bit().constData() ); + QgsDebugMsg( QStringLiteral( "loading of qgis translation failed %1/qgis_%2" ).arg( i18nPath, myTranslationCode ) ); } /* Translation file for Qt. @@ -1048,7 +1048,7 @@ int main( int argc, char *argv[] ) } else { - qWarning( "loading of qt translation failed [%s]", QStringLiteral( "%1/qt_%2" ).arg( QLibraryInfo::location( QLibraryInfo::TranslationsPath ), myTranslationCode ).toLocal8Bit().constData() ); + QgsDebugMsg( QStringLiteral( "loading of qt translation failed %1/qt_%2" ).arg( QLibraryInfo::location( QLibraryInfo::TranslationsPath ), myTranslationCode ) ); } } From 514e43057a5e663f967d25212914e9f6f6d9363f Mon Sep 17 00:00:00 2001 From: Sandro Mani Date: Mon, 17 Jul 2017 15:11:49 +0200 Subject: [PATCH 014/266] Re-use timer in QgsMapCanvas::refresh to ensure multiple refresh requests get compressed Forward port from Sourcepole's fork --- src/gui/qgsmapcanvas.cpp | 6 +++++- src/gui/qgsmapcanvas.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index 831b0ffcc30..80e8bc27ca4 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -133,6 +133,10 @@ QgsMapCanvas::QgsMapCanvas( QWidget *parent ) mResizeTimer->setSingleShot( true ); connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh ); + mRefreshTimer = new QTimer( this ); + mRefreshTimer->setSingleShot( true ); + connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap ); + // create map canvas item which will show the map mMap = new QgsMapCanvasMap( this ); @@ -493,7 +497,7 @@ void QgsMapCanvas::refresh() QgsDebugMsg( "CANVAS refresh scheduling" ); // schedule a refresh - QTimer::singleShot( 1, this, SLOT( refreshMap() ) ); + mRefreshTimer->start( 1 ); } // refresh void QgsMapCanvas::refreshMap() diff --git a/src/gui/qgsmapcanvas.h b/src/gui/qgsmapcanvas.h index b62b5d3e6b5..b4f47faa409 100644 --- a/src/gui/qgsmapcanvas.h +++ b/src/gui/qgsmapcanvas.h @@ -814,6 +814,7 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView QgsMapRendererCache *mCache = nullptr; QTimer *mResizeTimer = nullptr; + QTimer *mRefreshTimer = nullptr; QgsPreviewEffect *mPreviewEffect = nullptr; From b2b35dd08453908cbd1b011ab8ed24be3989f3a9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 13:53:14 +1000 Subject: [PATCH 015/266] Add a context class for layouts Stores information relating to the current context (such as associated feature and layer) and rendering settings for a layout. --- python/core/core_auto.sip | 1 + python/core/layout/qgslayoutcontext.sip | 111 ++++++++++++++++++++ src/core/CMakeLists.txt | 2 + src/core/layout/qgslayoutcontext.cpp | 56 ++++++++++ src/core/layout/qgslayoutcontext.h | 123 ++++++++++++++++++++++ tests/src/core/CMakeLists.txt | 1 + tests/src/core/testqgslayoutcontext.cpp | 130 ++++++++++++++++++++++++ 7 files changed, 424 insertions(+) create mode 100644 python/core/layout/qgslayoutcontext.sip create mode 100644 src/core/layout/qgslayoutcontext.cpp create mode 100644 src/core/layout/qgslayoutcontext.h create mode 100644 tests/src/core/testqgslayoutcontext.cpp diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 222dd725031..8af0c5dfa5b 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -378,6 +378,7 @@ %Include gps/qgsgpsdetector.sip %Include gps/qgsnmeaconnection.sip %Include gps/qgsgpsdconnection.sip +%Include layout/qgslayoutcontext.sip %Include layout/qgslayoutitem.sip %Include layout/qgslayoutitemregistry.sip %Include layout/qgslayoutobject.sip diff --git a/python/core/layout/qgslayoutcontext.sip b/python/core/layout/qgslayoutcontext.sip new file mode 100644 index 00000000000..691ca8f9990 --- /dev/null +++ b/python/core/layout/qgslayoutcontext.sip @@ -0,0 +1,111 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutcontext.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsLayoutContext +{ +%Docstring + Stores information relating to the current context and rendering settings for a layout. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutcontext.h" +%End + public: + + enum Flag + { + FlagDebug, + FlagOutlineOnly, + FlagAntialiasing, + FlagUseAdvancedEffects, + }; + typedef QFlags Flags; + + + QgsLayoutContext(); + + void setFlags( const QgsLayoutContext::Flags flags ); +%Docstring + Sets the combination of ``flags`` that will be used for rendering the layout. +.. seealso:: setFlag() +.. seealso:: flags() +.. seealso:: testFlag() +%End + + void setFlag( const QgsLayoutContext::Flag flag, const bool on = true ); +%Docstring + Enables or disables a particular rendering ``flag`` for the layout. Other existing + flags are not affected. +.. seealso:: setFlags() +.. seealso:: flags() +.. seealso:: testFlag() +%End + + QgsLayoutContext::Flags flags() const; +%Docstring + Returns the current combination of flags used for rendering the layout. +.. seealso:: setFlags() +.. seealso:: setFlag() +.. seealso:: testFlag() + :rtype: QgsLayoutContext.Flags +%End + + bool testFlag( const Flag flag ) const; +%Docstring + Check whether a particular rendering ``flag`` is enabled for the layout. +.. seealso:: setFlags() +.. seealso:: setFlag() +.. seealso:: flags() + :rtype: bool +%End + + void setFeature( const QgsFeature &feature ); +%Docstring + Sets the current ``feature`` for evaluating the layout. This feature may + be used for altering an item's content and appearance for a report + or atlas layout. +.. seealso:: feature() +%End + + QgsFeature feature() const; +%Docstring + Returns the current feature for evaluating the layout. This feature may + be used for altering an item's content and appearance for a report + or atlas layout. +.. seealso:: setFeature() + :rtype: QgsFeature +%End + + QgsVectorLayer *layer() const; +%Docstring + Returns the vector layer associated with the layout's context. +.. seealso:: setLayer() + :rtype: QgsVectorLayer +%End + + void setLayer( QgsVectorLayer *layer ); +%Docstring + Sets the vector ``layer`` associated with the layout's context. +.. seealso:: layer() +%End + +}; + + + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutcontext.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index bd92bfc7cad..adf75ef0d17 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -350,6 +350,7 @@ SET(QGIS_CORE_SRCS dxf/qgsdxfpallabeling.cpp layout/qgslayout.cpp + layout/qgslayoutcontext.cpp layout/qgslayoutitem.cpp layout/qgslayoutitemregistry.cpp layout/qgslayoutmeasurement.cpp @@ -671,6 +672,7 @@ SET(QGIS_CORE_MOC_HDRS gps/qgsgpsdconnection.h layout/qgslayout.h + layout/qgslayoutcontext.h layout/qgslayoutitem.h layout/qgslayoutitemregistry.h layout/qgslayoutobject.h diff --git a/src/core/layout/qgslayoutcontext.cpp b/src/core/layout/qgslayoutcontext.cpp new file mode 100644 index 00000000000..de99d699bf7 --- /dev/null +++ b/src/core/layout/qgslayoutcontext.cpp @@ -0,0 +1,56 @@ +/*************************************************************************** + qgslayoutcontext.cpp + -------------------- + begin : July 2017 + copyright : (C) 2017 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 "qgslayoutcontext.h" +#include "qgsfeature.h" + + +QgsLayoutContext::QgsLayoutContext() + : mFlags( FlagAntialiasing | FlagUseAdvancedEffects ) +{} + +void QgsLayoutContext::setFlags( const QgsLayoutContext::Flags flags ) +{ + mFlags = flags; +} + +void QgsLayoutContext::setFlag( const QgsLayoutContext::Flag flag, const bool on ) +{ + if ( on ) + mFlags |= flag; + else + mFlags &= ~flag; +} + +QgsLayoutContext::Flags QgsLayoutContext::flags() const +{ + return mFlags; +} + +bool QgsLayoutContext::testFlag( const QgsLayoutContext::Flag flag ) const +{ + return mFlags.testFlag( flag ); +} + +QgsVectorLayer *QgsLayoutContext::layer() const +{ + return mLayer; +} + +void QgsLayoutContext::setLayer( QgsVectorLayer *layer ) +{ + mLayer = layer; +} diff --git a/src/core/layout/qgslayoutcontext.h b/src/core/layout/qgslayoutcontext.h new file mode 100644 index 00000000000..f79a0d85552 --- /dev/null +++ b/src/core/layout/qgslayoutcontext.h @@ -0,0 +1,123 @@ +/*************************************************************************** + qgslayoutcontext.h + ------------------- + begin : July 2017 + copyright : (C) 2017 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 QGSLAYOUTCONTEXT_H +#define QGSLAYOUTCONTEXT_H + +#include "qgis_core.h" +#include "qgsfeature.h" +#include "qgsvectorlayer.h" +#include + +class QgsFeature; +class QgsVectorLayer; + +/** + * \ingroup Layout + * \class QgsLayoutContext + * \brief Stores information relating to the current context and rendering settings for a layout. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutContext +{ + + public: + + //! Flags for controlling how a layout is rendered + enum Flag + { + FlagDebug = 1 << 1, //!< Debug/testing mode, items are drawn as solid rectangles. + FlagOutlineOnly = 1 << 2, //!< Render items as outlines only. + FlagAntialiasing = 1 << 3, //!< Use antialiasing when drawing items. + FlagUseAdvancedEffects = 1 << 4, //!< Enable advanced effects such as blend modes. + }; + Q_DECLARE_FLAGS( Flags, Flag ) + + QgsLayoutContext(); + + /** + * Sets the combination of \a flags that will be used for rendering the layout. + * \see setFlag() + * \see flags() + * \see testFlag() + */ + void setFlags( const QgsLayoutContext::Flags flags ); + + /** + * Enables or disables a particular rendering \a flag for the layout. Other existing + * flags are not affected. + * \see setFlags() + * \see flags() + * \see testFlag() + */ + void setFlag( const QgsLayoutContext::Flag flag, const bool on = true ); + + /** + * Returns the current combination of flags used for rendering the layout. + * \see setFlags() + * \see setFlag() + * \see testFlag() + */ + QgsLayoutContext::Flags flags() const; + + /** + * Check whether a particular rendering \a flag is enabled for the layout. + * \see setFlags() + * \see setFlag() + * \see flags() + */ + bool testFlag( const Flag flag ) const; + + /** + * Sets the current \a feature for evaluating the layout. This feature may + * be used for altering an item's content and appearance for a report + * or atlas layout. + * \see feature() + */ + void setFeature( const QgsFeature &feature ) { mFeature = feature; } + + /** + * Returns the current feature for evaluating the layout. This feature may + * be used for altering an item's content and appearance for a report + * or atlas layout. + * \see setFeature() + */ + QgsFeature feature() const { return mFeature; } + + /** + * Returns the vector layer associated with the layout's context. + * \see setLayer() + */ + QgsVectorLayer *layer() const; + + /** + * Sets the vector \a layer associated with the layout's context. + * \see layer() + */ + void setLayer( QgsVectorLayer *layer ); + + private: + + Flags mFlags = 0; + + QgsFeature mFeature; + QPointer< QgsVectorLayer > mLayer; + +}; + +#endif //QGSLAYOUTCONTEXT_H + + + diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 8991fe6dbba..32c62b090ff 100755 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -127,6 +127,7 @@ SET(TESTS testqgslabelingengine.cpp testqgslayertree.cpp testqgslayout.cpp + testqgslayoutcontext.cpp testqgslayoutitem.cpp testqgslayoutobject.cpp testqgslayoutunits.cpp diff --git a/tests/src/core/testqgslayoutcontext.cpp b/tests/src/core/testqgslayoutcontext.cpp new file mode 100644 index 00000000000..c6797fcdb0b --- /dev/null +++ b/tests/src/core/testqgslayoutcontext.cpp @@ -0,0 +1,130 @@ +/*************************************************************************** + testqgslayoutcontext.cpp + ------------------------ + begin : November 2014 + copyright : (C) 2014 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 "qgslayoutcontext.h" +#include "qgis.h" +#include "qgsfeature.h" +#include "qgsvectorlayer.h" +#include +#include "qgstest.h" + +class TestQgsLayoutContext: public QObject +{ + Q_OBJECT + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init();// will be called before each testfunction is executed. + void cleanup();// will be called after every testfunction. + void creation(); //test creation of QgsLayout + void flags(); //test QgsLayout flags + void feature(); + void layer(); + + private: + QString mReport; + +}; + +void TestQgsLayoutContext::initTestCase() +{ + mReport = "

Layout Context Tests

\n"; +} + +void TestQgsLayoutContext::cleanupTestCase() +{ + QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html"; + QFile myFile( myReportFile ); + if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) ) + { + QTextStream myQTextStream( &myFile ); + myQTextStream << mReport; + myFile.close(); + } +} + +void TestQgsLayoutContext::init() +{ + +} + +void TestQgsLayoutContext::cleanup() +{ + +} + +void TestQgsLayoutContext::creation() +{ + QgsLayoutContext *context = new QgsLayoutContext(); + QVERIFY( context ); + delete context; +} + +void TestQgsLayoutContext::flags() +{ + QgsLayoutContext context; + //test getting and setting flags + context.setFlags( QgsLayoutContext::Flags( QgsLayoutContext::FlagAntialiasing | QgsLayoutContext::FlagUseAdvancedEffects ) ); + QVERIFY( context.flags() == ( QgsLayoutContext::FlagAntialiasing | QgsLayoutContext::FlagUseAdvancedEffects ) ); + QVERIFY( context.testFlag( QgsLayoutContext::FlagAntialiasing ) ); + QVERIFY( context.testFlag( QgsLayoutContext::FlagUseAdvancedEffects ) ); + QVERIFY( ! context.testFlag( QgsLayoutContext::FlagDebug ) ); + context.setFlag( QgsLayoutContext::FlagDebug ); + QVERIFY( context.testFlag( QgsLayoutContext::FlagDebug ) ); + context.setFlag( QgsLayoutContext::FlagDebug, false ); + QVERIFY( ! context.testFlag( QgsLayoutContext::FlagDebug ) ); +} + +void TestQgsLayoutContext::feature() +{ + QgsLayoutContext context; + + //test removing feature + context.setFeature( QgsFeature() ); + QVERIFY( !context.feature().isValid() ); + + //test setting/getting feature + QgsFeature testFeature; + testFeature.initAttributes( 1 ); + testFeature.setAttribute( 0, "Test" ); + context.setFeature( testFeature ); + QCOMPARE( context.feature().attribute( 0 ), testFeature.attribute( 0 ) ); +} + +void TestQgsLayoutContext::layer() +{ + QgsLayoutContext context; + + //test clearing layer + context.setLayer( nullptr ); + QVERIFY( !context.layer() ); + + //test setting/getting layer + QgsVectorLayer *layer = new QgsVectorLayer( "Point?field=id_a:integer", "A", "memory" ); + context.setLayer( layer ); + QCOMPARE( context.layer(), layer ); + + //clear layer + context.setLayer( nullptr ); + QVERIFY( !context.layer() ); + + delete layer; +} + +QGSTEST_MAIN( TestQgsLayoutContext ) +#include "testqgslayoutcontext.moc" From cd380f616c05602cfff99743e89e179a852513eb Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 14:26:02 +1000 Subject: [PATCH 016/266] Add measurement converter and dpi to layout context --- python/core/layout/qgslayoutcontext.sip | 22 +++++++++++++++++++ src/core/CMakeLists.txt | 3 +-- src/core/layout/qgslayoutcontext.cpp | 10 +++++++++ src/core/layout/qgslayoutcontext.h | 28 +++++++++++++++++++++++++ tests/src/core/testqgslayoutcontext.cpp | 9 ++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/python/core/layout/qgslayoutcontext.sip b/python/core/layout/qgslayoutcontext.sip index 691ca8f9990..7f9ccd3d7a7 100644 --- a/python/core/layout/qgslayoutcontext.sip +++ b/python/core/layout/qgslayoutcontext.sip @@ -97,6 +97,28 @@ class QgsLayoutContext .. seealso:: layer() %End + void setDpi( double dpi ); +%Docstring + Sets the ``dpi`` for outputting the layout. This also sets the + corresponding DPI for the context's measurementConverter(). +.. seealso:: dpi() +%End + + double dpi() const; +%Docstring + Returns the ``dpi`` for outputting the layout. +.. seealso:: setDpi() + :rtype: float +%End + + + QgsLayoutMeasurementConverter &measurementConverter(); +%Docstring + Returns the layout measurement converter to be used in the layout. This converter is used + for translating between other measurement units and the layout's native unit. + :rtype: QgsLayoutMeasurementConverter +%End + }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index adf75ef0d17..3f3e49ba55a 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -672,7 +672,6 @@ SET(QGIS_CORE_MOC_HDRS gps/qgsgpsdconnection.h layout/qgslayout.h - layout/qgslayoutcontext.h layout/qgslayoutitem.h layout/qgslayoutitemregistry.h layout/qgslayoutobject.h @@ -918,7 +917,7 @@ SET(QGIS_CORE_HDRS composer/qgscomposertexttable.h composer/qgspaperitem.h - layout/qgslayout.h + layout/qgslayoutcontext.h layout/qgslayoutmeasurement.h layout/qgslayoutmeasurementconverter.h layout/qgspagesizeregistry.h diff --git a/src/core/layout/qgslayoutcontext.cpp b/src/core/layout/qgslayoutcontext.cpp index de99d699bf7..43782b0b47e 100644 --- a/src/core/layout/qgslayoutcontext.cpp +++ b/src/core/layout/qgslayoutcontext.cpp @@ -54,3 +54,13 @@ void QgsLayoutContext::setLayer( QgsVectorLayer *layer ) { mLayer = layer; } + +void QgsLayoutContext::setDpi( double dpi ) +{ + mMeasurementConverter.setDpi( dpi ); +} + +double QgsLayoutContext::dpi() const +{ + return mMeasurementConverter.dpi(); +} diff --git a/src/core/layout/qgslayoutcontext.h b/src/core/layout/qgslayoutcontext.h index f79a0d85552..13055a46e8b 100644 --- a/src/core/layout/qgslayoutcontext.h +++ b/src/core/layout/qgslayoutcontext.h @@ -19,6 +19,7 @@ #include "qgis_core.h" #include "qgsfeature.h" #include "qgsvectorlayer.h" +#include "qgslayoutmeasurementconverter.h" #include class QgsFeature; @@ -108,6 +109,31 @@ class CORE_EXPORT QgsLayoutContext */ void setLayer( QgsVectorLayer *layer ); + /** + * Sets the \a dpi for outputting the layout. This also sets the + * corresponding DPI for the context's measurementConverter(). + * \see dpi() + */ + void setDpi( double dpi ); + + /** + * Returns the \a dpi for outputting the layout. + * \see setDpi() + */ + double dpi() const; + + /** + * Returns the layout measurement converter to be used in the layout. This converter is used + * for translating between other measurement units and the layout's native unit. + */ + SIP_SKIP const QgsLayoutMeasurementConverter &measurementConverter() const { return mMeasurementConverter; } + + /** + * Returns the layout measurement converter to be used in the layout. This converter is used + * for translating between other measurement units and the layout's native unit. + */ + QgsLayoutMeasurementConverter &measurementConverter() { return mMeasurementConverter; } + private: Flags mFlags = 0; @@ -115,6 +141,8 @@ class CORE_EXPORT QgsLayoutContext QgsFeature mFeature; QPointer< QgsVectorLayer > mLayer; + QgsLayoutMeasurementConverter mMeasurementConverter; + }; #endif //QGSLAYOUTCONTEXT_H diff --git a/tests/src/core/testqgslayoutcontext.cpp b/tests/src/core/testqgslayoutcontext.cpp index c6797fcdb0b..4b97681e106 100644 --- a/tests/src/core/testqgslayoutcontext.cpp +++ b/tests/src/core/testqgslayoutcontext.cpp @@ -35,6 +35,7 @@ class TestQgsLayoutContext: public QObject void flags(); //test QgsLayout flags void feature(); void layer(); + void dpi(); private: QString mReport; @@ -126,5 +127,13 @@ void TestQgsLayoutContext::layer() delete layer; } +void TestQgsLayoutContext::dpi() +{ + QgsLayoutContext context; + context.setDpi( 600 ); + QCOMPARE( context.dpi(), 600.0 ); + QCOMPARE( context.measurementConverter().dpi(), 600.0 ); +} + QGSTEST_MAIN( TestQgsLayoutContext ) #include "testqgslayoutcontext.moc" From ab79b1560cd5971582afb4c7ab1a20b0cb2f54c6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 14:43:35 +1000 Subject: [PATCH 017/266] [layout] Add measurement unit handling to QgsLayout Allows layouts to convert from various units to their own native units Also added a QgsLayoutContext to QgsLayout. --- python/core/layout/qgslayout.sip | 78 ++++++++++++++++++++++++++++++ src/core/layout/qgslayout.cpp | 30 ++++++++++++ src/core/layout/qgslayout.h | 81 ++++++++++++++++++++++++++++++++ tests/src/core/testqgslayout.cpp | 49 +++++++++++++++++++ 4 files changed, 238 insertions(+) diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index cf616caf22f..c3874f57f95 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -26,6 +26,84 @@ class QgsLayout : QGraphicsScene QgsLayout(); + void setUnits( QgsUnitTypes::LayoutUnit units ); +%Docstring + Sets the native measurement ``units`` for the layout. These also form the default unit + for measurements for the layout. +.. seealso:: units() +.. seealso:: convertToLayoutUnits() +%End + + QgsUnitTypes::LayoutUnit units() const; +%Docstring + Returns the native units for the layout. +.. seealso:: setUnits() +.. seealso:: convertToLayoutUnits() + :rtype: QgsUnitTypes.LayoutUnit +%End + + double convertToLayoutUnits( const QgsLayoutMeasurement &measurement ) const; +%Docstring + Converts a measurement into the layout's native units. + :return: length of measurement in layout units +.. seealso:: convertFromLayoutUnits() +.. seealso:: units() + :rtype: float +%End + + QSizeF convertToLayoutUnits( const QgsLayoutSize &size ) const; +%Docstring + Converts a size into the layout's native units. + :return: size of measurement in layout units +.. seealso:: convertFromLayoutUnits() +.. seealso:: units() + :rtype: QSizeF +%End + + QPointF convertToLayoutUnits( const QgsLayoutPoint &point ) const; +%Docstring + Converts a ``point`` into the layout's native units. + :return: point in layout units +.. seealso:: convertFromLayoutUnits() +.. seealso:: units() + :rtype: QPointF +%End + + QgsLayoutMeasurement convertFromLayoutUnits( const double length, const QgsUnitTypes::LayoutUnit unit ) const; +%Docstring + Converts a ``length`` measurement from the layout's native units to a specified target ``unit``. + :return: length of measurement in specified units +.. seealso:: convertToLayoutUnits() +.. seealso:: units() + :rtype: QgsLayoutMeasurement +%End + + QgsLayoutSize convertFromLayoutUnits( const QSizeF &size, const QgsUnitTypes::LayoutUnit unit ) const; +%Docstring + Converts a ``size`` from the layout's native units to a specified target ``unit``. + :return: size of measurement in specified units +.. seealso:: convertToLayoutUnits() +.. seealso:: units() + :rtype: QgsLayoutSize +%End + + QgsLayoutPoint convertFromLayoutUnits( const QPointF &point, const QgsUnitTypes::LayoutUnit unit ) const; +%Docstring + Converts a ``point`` from the layout's native units to a specified target ``unit``. + :return: point in specified units +.. seealso:: convertToLayoutUnits() +.. seealso:: units() + :rtype: QgsLayoutPoint +%End + + QgsLayoutContext &context(); +%Docstring + Returns a reference to the layout's context, which stores information relating to the + current context and rendering settings for the layout. + :rtype: QgsLayoutContext +%End + + }; diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index fd810e25185..5516499311a 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -21,3 +21,33 @@ QgsLayout::QgsLayout() { } + +double QgsLayout::convertToLayoutUnits( const QgsLayoutMeasurement &measurement ) const +{ + return mContext.measurementConverter().convert( measurement, mUnits ).length(); +} + +QSizeF QgsLayout::convertToLayoutUnits( const QgsLayoutSize &size ) const +{ + return mContext.measurementConverter().convert( size, mUnits ).toQSizeF(); +} + +QPointF QgsLayout::convertToLayoutUnits( const QgsLayoutPoint &point ) const +{ + return mContext.measurementConverter().convert( point, mUnits ).toQPointF(); +} + +QgsLayoutMeasurement QgsLayout::convertFromLayoutUnits( const double length, const QgsUnitTypes::LayoutUnit unit ) const +{ + return mContext.measurementConverter().convert( QgsLayoutMeasurement( length, mUnits ), unit ); +} + +QgsLayoutSize QgsLayout::convertFromLayoutUnits( const QSizeF &size, const QgsUnitTypes::LayoutUnit unit ) const +{ + return mContext.measurementConverter().convert( QgsLayoutSize( size.width(), size.height(), mUnits ), unit ); +} + +QgsLayoutPoint QgsLayout::convertFromLayoutUnits( const QPointF &point, const QgsUnitTypes::LayoutUnit unit ) const +{ + return mContext.measurementConverter().convert( QgsLayoutPoint( point.x(), point.y(), mUnits ), unit ); +} diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index f023735f2d9..278c9ef226d 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -18,6 +18,7 @@ #include "qgis_core.h" #include +#include "qgslayoutcontext.h" /** * \ingroup core @@ -39,6 +40,86 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene QgsLayout(); + /** + * Sets the native measurement \a units for the layout. These also form the default unit + * for measurements for the layout. + * \see units() + * \see convertToLayoutUnits() + */ + void setUnits( QgsUnitTypes::LayoutUnit units ) { mUnits = units; } + + /** + * Returns the native units for the layout. + * \see setUnits() + * \see convertToLayoutUnits() + */ + QgsUnitTypes::LayoutUnit units() const { return mUnits; } + + /** + * Converts a measurement into the layout's native units. + * \returns length of measurement in layout units + * \see convertFromLayoutUnits() + * \see units() + */ + double convertToLayoutUnits( const QgsLayoutMeasurement &measurement ) const; + + /** + * Converts a size into the layout's native units. + * \returns size of measurement in layout units + * \see convertFromLayoutUnits() + * \see units() + */ + QSizeF convertToLayoutUnits( const QgsLayoutSize &size ) const; + + /** + * Converts a \a point into the layout's native units. + * \returns point in layout units + * \see convertFromLayoutUnits() + * \see units() + */ + QPointF convertToLayoutUnits( const QgsLayoutPoint &point ) const; + + /** + * Converts a \a length measurement from the layout's native units to a specified target \a unit. + * \returns length of measurement in specified units + * \see convertToLayoutUnits() + * \see units() + */ + QgsLayoutMeasurement convertFromLayoutUnits( const double length, const QgsUnitTypes::LayoutUnit unit ) const; + + /** + * Converts a \a size from the layout's native units to a specified target \a unit. + * \returns size of measurement in specified units + * \see convertToLayoutUnits() + * \see units() + */ + QgsLayoutSize convertFromLayoutUnits( const QSizeF &size, const QgsUnitTypes::LayoutUnit unit ) const; + + /** + * Converts a \a point from the layout's native units to a specified target \a unit. + * \returns point in specified units + * \see convertToLayoutUnits() + * \see units() + */ + QgsLayoutPoint convertFromLayoutUnits( const QPointF &point, const QgsUnitTypes::LayoutUnit unit ) const; + + /** + * Returns a reference to the layout's context, which stores information relating to the + * current context and rendering settings for the layout. + */ + QgsLayoutContext &context() { return mContext; } + + /** + * Returns a reference to the layout's context, which stores information relating to the + * current context and rendering settings for the layout. + */ + SIP_SKIP const QgsLayoutContext &context() const { return mContext; } + + private: + + QgsUnitTypes::LayoutUnit mUnits = QgsUnitTypes::LayoutMillimeters; + QgsLayoutContext mContext; + }; #endif //QGSLAYOUT_H diff --git a/tests/src/core/testqgslayout.cpp b/tests/src/core/testqgslayout.cpp index cd77e11bb85..9362d84792d 100644 --- a/tests/src/core/testqgslayout.cpp +++ b/tests/src/core/testqgslayout.cpp @@ -28,6 +28,7 @@ class TestQgsLayout: public QObject void init();// will be called before each testfunction is executed. void cleanup();// will be called after every testfunction. void creation(); //test creation of QgsLayout + void units(); private: QString mReport; @@ -68,6 +69,54 @@ void TestQgsLayout::creation() delete layout; } +void TestQgsLayout::units() +{ + QgsLayout layout; + layout.setUnits( QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( layout.units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutMeasurement( 10.0, QgsUnitTypes::LayoutMillimeters ) ), 1.0 ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutSize( 10.0, 20.0, QgsUnitTypes::LayoutMillimeters ) ), QSizeF( 1.0, 2.0 ) ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutPoint( 10.0, 20.0, QgsUnitTypes::LayoutMillimeters ) ), QPointF( 1.0, 2.0 ) ); + QCOMPARE( layout.convertFromLayoutUnits( 1.0, QgsUnitTypes::LayoutMillimeters ), QgsLayoutMeasurement( 10.0, QgsUnitTypes::LayoutMillimeters ) ); + QCOMPARE( layout.convertFromLayoutUnits( QSizeF( 1.0, 2.0 ), QgsUnitTypes::LayoutMillimeters ), QgsLayoutSize( 10.0, 20.0, QgsUnitTypes::LayoutMillimeters ) ); + QCOMPARE( layout.convertFromLayoutUnits( QPointF( 1.0, 2.0 ), QgsUnitTypes::LayoutMillimeters ), QgsLayoutPoint( 10.0, 20.0, QgsUnitTypes::LayoutMillimeters ) ); + + //check with dpi conversion + layout.setUnits( QgsUnitTypes::LayoutInches ); + layout.context().setDpi( 96.0 ); + QCOMPARE( layout.context().dpi(), 96.0 ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutMeasurement( 96, QgsUnitTypes::LayoutPixels ) ), 1.0 ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutSize( 96, 96, QgsUnitTypes::LayoutPixels ) ), QSizeF( 1.0, 1.0 ) ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutPoint( 96, 96, QgsUnitTypes::LayoutPixels ) ), QPointF( 1.0, 1.0 ) ); + QgsLayoutMeasurement result = layout.convertFromLayoutUnits( 1.0, QgsUnitTypes::LayoutPixels ); + QCOMPARE( result.units(), QgsUnitTypes::LayoutPixels ); + QCOMPARE( result.length(), 96.0 ); + QgsLayoutSize sizeResult = layout.convertFromLayoutUnits( QSizeF( 1.0, 1.0 ), QgsUnitTypes::LayoutPixels ); + QCOMPARE( sizeResult.units(), QgsUnitTypes::LayoutPixels ); + QCOMPARE( sizeResult.width(), 96.0 ); + QCOMPARE( sizeResult.height(), 96.0 ); + QgsLayoutPoint pointResult = layout.convertFromLayoutUnits( QPointF( 1.0, 1.0 ), QgsUnitTypes::LayoutPixels ); + QCOMPARE( pointResult.units(), QgsUnitTypes::LayoutPixels ); + QCOMPARE( pointResult.x(), 96.0 ); + QCOMPARE( pointResult.y(), 96.0 ); + + layout.setUnits( QgsUnitTypes::LayoutPixels ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) ), 96.0 ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutSize( 1, 2, QgsUnitTypes::LayoutInches ) ), QSizeF( 96.0, 192.0 ) ); + QCOMPARE( layout.convertToLayoutUnits( QgsLayoutPoint( 1, 2, QgsUnitTypes::LayoutInches ) ), QPointF( 96.0, 192.0 ) ); + result = layout.convertFromLayoutUnits( 96.0, QgsUnitTypes::LayoutInches ); + QCOMPARE( result.units(), QgsUnitTypes::LayoutInches ); + QCOMPARE( result.length(), 1.0 ); + sizeResult = layout.convertFromLayoutUnits( QSizeF( 96.0, 192.0 ), QgsUnitTypes::LayoutInches ); + QCOMPARE( sizeResult.units(), QgsUnitTypes::LayoutInches ); + QCOMPARE( sizeResult.width(), 1.0 ); + QCOMPARE( sizeResult.height(), 2.0 ); + pointResult = layout.convertFromLayoutUnits( QPointF( 96.0, 192.0 ), QgsUnitTypes::LayoutInches ); + QCOMPARE( pointResult.units(), QgsUnitTypes::LayoutInches ); + QCOMPARE( pointResult.x(), 1.0 ); + QCOMPARE( pointResult.y(), 2.0 ); +} + QGSTEST_MAIN( TestQgsLayout ) #include "testqgslayout.moc" From a6156d9221d2355ebb6aea618203b76d2b0df8a7 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 15:04:03 +1000 Subject: [PATCH 018/266] Layouts have a name --- python/core/layout/qgslayout.sip | 13 +++++++++++++ src/core/layout/qgslayout.h | 14 ++++++++++++++ tests/src/core/testqgslayout.cpp | 9 +++++++++ 3 files changed, 36 insertions(+) diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index c3874f57f95..e3798f0c418 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -26,6 +26,19 @@ class QgsLayout : QGraphicsScene QgsLayout(); + QString name() const; +%Docstring + Returns the layout's name. +.. seealso:: setName() + :rtype: str +%End + + void setName( const QString &name ); +%Docstring + Sets the layout's name. +.. seealso:: name() +%End + void setUnits( QgsUnitTypes::LayoutUnit units ); %Docstring Sets the native measurement ``units`` for the layout. These also form the default unit diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index 278c9ef226d..9c2a83bac36 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -40,6 +40,18 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene QgsLayout(); + /** + * Returns the layout's name. + * \see setName() + */ + QString name() const { return mName; } + + /** + * Sets the layout's name. + * \see name() + */ + void setName( const QString &name ) { mName = name; } + /** * Sets the native measurement \a units for the layout. These also form the default unit * for measurements for the layout. @@ -117,6 +129,8 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene private: + QString mName; + QgsUnitTypes::LayoutUnit mUnits = QgsUnitTypes::LayoutMillimeters; QgsLayoutContext mContext; diff --git a/tests/src/core/testqgslayout.cpp b/tests/src/core/testqgslayout.cpp index 9362d84792d..1e764c3a388 100644 --- a/tests/src/core/testqgslayout.cpp +++ b/tests/src/core/testqgslayout.cpp @@ -29,6 +29,7 @@ class TestQgsLayout: public QObject void cleanup();// will be called after every testfunction. void creation(); //test creation of QgsLayout void units(); + void name(); private: QString mReport; @@ -117,6 +118,14 @@ void TestQgsLayout::units() QCOMPARE( pointResult.y(), 2.0 ); } +void TestQgsLayout::name() +{ + QgsLayout layout; + QString layoutName = "test name"; + layout.setName( layoutName ); + QCOMPARE( layout.name(), layoutName ); +} + QGSTEST_MAIN( TestQgsLayout ) #include "testqgslayout.moc" From 15b65fa6d455c99914f5aa924bf1badaf5854def Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 15:36:36 +1000 Subject: [PATCH 019/266] Port some basic functionality from QgsComposerObject --- python/core/layout/qgslayoutobject.sip | 115 ++++++++++++++++++++++ src/core/layout/qgslayoutobject.cpp | 85 ++++++++++++++++ src/core/layout/qgslayoutobject.h | 128 +++++++++++++++++++++++++ tests/src/core/testqgslayoutobject.cpp | 30 ++++++ 4 files changed, 358 insertions(+) diff --git a/python/core/layout/qgslayoutobject.sip b/python/core/layout/qgslayoutobject.sip index f98934c4c4e..225c74fcd6e 100644 --- a/python/core/layout/qgslayoutobject.sip +++ b/python/core/layout/qgslayoutobject.sip @@ -21,6 +21,63 @@ class QgsLayoutObject: QObject %End public: + enum DataDefinedProperty + { + NoProperty, + AllProperties, + TestProperty, + //composer + PresetPaperSize, + PaperWidth, + PaperHeight, + NumPages, + PaperOrientation, + //general + PageNumber, + PositionX, + PositionY, + ItemWidth, + ItemHeight, + ItemRotation, + Transparency, + Opacity, + BlendMode, + ExcludeFromExports, + FrameColor, + BackgroundColor, + //composer + MapRotation, + MapScale, + MapXMin, + MapYMin, + MapXMax, + MapYMax, + MapAtlasMargin, + MapLayers, + MapStylePreset, + //composer + PictureSource, + PictureSvgBackgroundColor, + PictureSvgStrokeColor, + PictureSvgStrokeWidth, + //html + SourceUrl, + //legend + LegendTitle, + LegendColumnCount, + //scalebar + ScalebarFillColor, + ScalebarFillColor2, + ScalebarLineColor, + ScalebarLineWidth, + }; + + static const QgsPropertiesDefinition &propertyDefinitions(); +%Docstring + Returns the layout object property definitions. + :rtype: QgsPropertiesDefinition +%End + QgsLayoutObject( QgsLayout *layout ); %Docstring Constructor for QgsLayoutObject, with the specified parent ``layout``. @@ -38,9 +95,67 @@ class QgsLayoutObject: QObject :rtype: QgsLayout %End + QgsPropertyCollection &dataDefinedProperties(); +%Docstring + Returns a reference to the object's property collection, used for data defined overrides. +.. seealso:: setDataDefinedProperties() + :rtype: QgsPropertyCollection +%End + + + void setDataDefinedProperties( const QgsPropertyCollection &collection ); +%Docstring + Sets the objects's property collection, used for data defined overrides. + \param collection property collection. Existing properties will be replaced. +.. seealso:: dataDefinedProperties() +%End + + + void setCustomProperty( const QString &key, const QVariant &value ); +%Docstring + Set a custom property for the object. + \param key property key. If a property with the same key already exists it will be overwritten. + \param value property value +.. seealso:: customProperty() +.. seealso:: removeCustomProperty() +.. seealso:: customProperties() +%End + + QVariant customProperty( const QString &key, const QVariant &defaultValue = QVariant() ) const; +%Docstring + Read a custom property from the object. + \param key property key + \param defaultValue default value to return if property with matching key does not exist + :return: value of matching property +.. seealso:: setCustomProperty() +.. seealso:: removeCustomProperty() +.. seealso:: customProperties() + :rtype: QVariant +%End + + void removeCustomProperty( const QString &key ); +%Docstring + Remove a custom property from the object. + \param key property key +.. seealso:: setCustomProperty() +.. seealso:: customProperty() +.. seealso:: customProperties() +%End + + QStringList customProperties() const; +%Docstring + Return list of keys stored in custom properties for the object. +.. seealso:: setCustomProperty() +.. seealso:: customProperty() +.. seealso:: removeCustomProperty() + :rtype: list of str +%End + protected: + + }; /************************************************************************ diff --git a/src/core/layout/qgslayoutobject.cpp b/src/core/layout/qgslayoutobject.cpp index 188186729c9..1ee14df8d55 100644 --- a/src/core/layout/qgslayoutobject.cpp +++ b/src/core/layout/qgslayoutobject.cpp @@ -20,8 +20,93 @@ #include "qgslayout.h" #include "qgslayoutobject.h" + +QgsPropertiesDefinition QgsLayoutObject::sPropertyDefinitions; + +void QgsLayoutObject::initPropertyDefinitions() +{ + if ( !sPropertyDefinitions.isEmpty() ) + return; + + sPropertyDefinitions = QgsPropertiesDefinition + { + { QgsLayoutObject::TestProperty, QgsPropertyDefinition( "dataDefinedProperty", QgsPropertyDefinition::DataTypeString, "invalid property", QString() ) }, + { + QgsLayoutObject::PresetPaperSize, QgsPropertyDefinition( "dataDefinedPaperSize", QgsPropertyDefinition::DataTypeString, QObject::tr( "Paper size" ), QObject::tr( "string " ) + QLatin1String( "[A5|A4|A3|A2|A1|A0" + "B5|B4|B3|B2|B1|B0" + "Legal|Ansi A|Ansi B|Ansi C|Ansi D|Ansi E" + "Arch A|Arch B|Arch C|Arch D|Arch E|Arch E1]" + ) ) + }, + { QgsLayoutObject::PaperWidth, QgsPropertyDefinition( "dataDefinedPaperWidth", QObject::tr( "Page width" ), QgsPropertyDefinition::DoublePositive ) }, + { QgsLayoutObject::PaperHeight, QgsPropertyDefinition( "dataDefinedPaperHeight", QObject::tr( "Page height" ), QgsPropertyDefinition::DoublePositive ) }, + { QgsLayoutObject::NumPages, QgsPropertyDefinition( "dataDefinedNumPages", QObject::tr( "Number of pages" ), QgsPropertyDefinition::IntegerPositive ) }, + { QgsLayoutObject::PaperOrientation, QgsPropertyDefinition( "dataDefinedPaperOrientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), QObject::tr( "string " ) + QLatin1String( "[portrait|landscape]" ) ) }, + { QgsLayoutObject::PageNumber, QgsPropertyDefinition( "dataDefinedPageNumber", QObject::tr( "Page number" ), QgsPropertyDefinition::IntegerPositive ) }, + { QgsLayoutObject::PositionX, QgsPropertyDefinition( "dataDefinedPositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double ) }, + { QgsLayoutObject::PositionY, QgsPropertyDefinition( "dataDefinedPositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double ) }, + { QgsLayoutObject::ItemWidth, QgsPropertyDefinition( "dataDefinedWidth", QObject::tr( "Width" ), QgsPropertyDefinition::DoublePositive ) }, + { QgsLayoutObject::ItemHeight, QgsPropertyDefinition( "dataDefinedHeight", QObject::tr( "Height" ), QgsPropertyDefinition::DoublePositive ) }, + { QgsLayoutObject::ItemRotation, QgsPropertyDefinition( "dataDefinedRotation", QObject::tr( "Rotation angle" ), QgsPropertyDefinition::Rotation ) }, + { QgsLayoutObject::Transparency, QgsPropertyDefinition( "dataDefinedTransparency", QObject::tr( "Transparency" ), QgsPropertyDefinition::Opacity ) }, + { QgsLayoutObject::Opacity, QgsPropertyDefinition( "dataDefinedOpacity", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity ) }, + { QgsLayoutObject::BlendMode, QgsPropertyDefinition( "dataDefinedBlendMode", QObject::tr( "Blend mode" ), QgsPropertyDefinition::BlendMode ) }, + { QgsLayoutObject::ExcludeFromExports, QgsPropertyDefinition( "dataDefinedExcludeExports", QObject::tr( "Exclude item from exports" ), QgsPropertyDefinition::Boolean ) }, + { QgsLayoutObject::FrameColor, QgsPropertyDefinition( "dataDefinedFrameColor", QObject::tr( "Frame color" ), QgsPropertyDefinition::ColorWithAlpha ) }, + { QgsLayoutObject::BackgroundColor, QgsPropertyDefinition( "dataDefinedBackgroundColor", QObject::tr( "Background color" ), QgsPropertyDefinition::ColorWithAlpha ) }, + { QgsLayoutObject::MapRotation, QgsPropertyDefinition( "dataDefinedMapRotation", QObject::tr( "Map rotation" ), QgsPropertyDefinition::Rotation ) }, + { QgsLayoutObject::MapScale, QgsPropertyDefinition( "dataDefinedMapScale", QObject::tr( "Map scale" ), QgsPropertyDefinition::DoublePositive ) }, + { QgsLayoutObject::MapXMin, QgsPropertyDefinition( "dataDefinedMapXMin", QObject::tr( "Extent minimum X" ), QgsPropertyDefinition::Double ) }, + { QgsLayoutObject::MapYMin, QgsPropertyDefinition( "dataDefinedMapYMin", QObject::tr( "Extent minimum Y" ), QgsPropertyDefinition::Double ) }, + { QgsLayoutObject::MapXMax, QgsPropertyDefinition( "dataDefinedMapXMax", QObject::tr( "Extent maximum X" ), QgsPropertyDefinition::Double ) }, + { QgsLayoutObject::MapYMax, QgsPropertyDefinition( "dataDefinedMapYMax", QObject::tr( "Extent maximum Y" ), QgsPropertyDefinition::Double ) }, + { QgsLayoutObject::MapAtlasMargin, QgsPropertyDefinition( "dataDefinedMapAtlasMargin", QObject::tr( "Atlas margin" ), QgsPropertyDefinition::DoublePositive ) }, + { QgsLayoutObject::MapLayers, QgsPropertyDefinition( "dataDefinedMapLayers", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), tr( "list of map layer names separated by | characters" ) ) }, + { QgsLayoutObject::MapStylePreset, QgsPropertyDefinition( "dataDefinedMapStylePreset", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), tr( "list of map layer names separated by | characters" ) ) }, + { QgsLayoutObject::PictureSource, QgsPropertyDefinition( "dataDefinedSource", QObject::tr( "Picture source (URL)" ), QgsPropertyDefinition::String ) }, + { QgsLayoutObject::SourceUrl, QgsPropertyDefinition( "dataDefinedSourceUrl", QObject::tr( "Source URL" ), QgsPropertyDefinition::String ) }, + { QgsLayoutObject::PictureSvgBackgroundColor, QgsPropertyDefinition( "dataDefinedSvgBackgroundColor", QObject::tr( "SVG background color" ), QgsPropertyDefinition::ColorWithAlpha ) }, + { QgsLayoutObject::PictureSvgStrokeColor, QgsPropertyDefinition( "dataDefinedSvgStrokeColor", QObject::tr( "SVG stroke color" ), QgsPropertyDefinition::ColorWithAlpha ) }, + { QgsLayoutObject::PictureSvgStrokeWidth, QgsPropertyDefinition( "dataDefinedSvgStrokeWidth", QObject::tr( "SVG stroke width" ), QgsPropertyDefinition::StrokeWidth ) }, + { QgsLayoutObject::LegendTitle, QgsPropertyDefinition( "dataDefinedLegendTitle", QObject::tr( "Legend title" ), QgsPropertyDefinition::String ) }, + { QgsLayoutObject::LegendColumnCount, QgsPropertyDefinition( "dataDefinedLegendColumns", QObject::tr( "Number of columns" ), QgsPropertyDefinition::IntegerPositiveGreaterZero ) }, + { QgsLayoutObject::ScalebarFillColor, QgsPropertyDefinition( "dataDefinedScalebarFill", QObject::tr( "Fill color" ), QgsPropertyDefinition::ColorWithAlpha ) }, + { QgsLayoutObject::ScalebarFillColor2, QgsPropertyDefinition( "dataDefinedScalebarFill2", QObject::tr( "Secondary fill color" ), QgsPropertyDefinition::ColorWithAlpha ) }, + { QgsLayoutObject::ScalebarLineColor, QgsPropertyDefinition( "dataDefinedScalebarLineColor", QObject::tr( "Line color" ), QgsPropertyDefinition::ColorWithAlpha ) }, + { QgsLayoutObject::ScalebarLineWidth, QgsPropertyDefinition( "dataDefinedScalebarLineWidth", QObject::tr( "Line width" ), QgsPropertyDefinition::StrokeWidth ) }, + }; +} + +const QgsPropertiesDefinition &QgsLayoutObject::propertyDefinitions() +{ + QgsLayoutObject::initPropertyDefinitions(); + return sPropertyDefinitions; +} + QgsLayoutObject::QgsLayoutObject( QgsLayout *layout ) : QObject( nullptr ) , mLayout( layout ) { + initPropertyDefinitions(); +} + + +void QgsLayoutObject::setCustomProperty( const QString &key, const QVariant &value ) +{ + mCustomProperties.setValue( key, value ); +} + +QVariant QgsLayoutObject::customProperty( const QString &key, const QVariant &defaultValue ) const +{ + return mCustomProperties.value( key, defaultValue ); +} + +void QgsLayoutObject::removeCustomProperty( const QString &key ) +{ + mCustomProperties.remove( key ); +} + +QStringList QgsLayoutObject::customProperties() const +{ + return mCustomProperties.keys(); } diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h index b9d26483b00..bdedcbb418e 100644 --- a/src/core/layout/qgslayoutobject.h +++ b/src/core/layout/qgslayoutobject.h @@ -19,6 +19,8 @@ #include "qgis_core.h" #include "qgis_sip.h" +#include "qgspropertycollection.h" +#include "qgsobjectcustomproperties.h" #include #include #include @@ -36,6 +38,64 @@ class CORE_EXPORT QgsLayoutObject: public QObject Q_OBJECT public: + /** Data defined properties for different item types + */ + enum DataDefinedProperty + { + NoProperty = 0, //!< No property + AllProperties, //!< All properties for item + TestProperty, //!< Dummy property with no effect on item + //composer page properties + PresetPaperSize, //!< Preset paper size for composition + PaperWidth, //!< Paper width + PaperHeight, //!< Paper height + NumPages, //!< Number of pages in composition + PaperOrientation, //!< Paper orientation + //general composer item properties + PageNumber, //!< Page number for item placement + PositionX, //!< X position on page + PositionY, //!< Y position on page + ItemWidth, //!< Width of item + ItemHeight, //!< Height of item + ItemRotation, //!< Rotation of item + Transparency, //!< Item transparency (deprecated) + Opacity, //!< Item opacity + BlendMode, //!< Item blend mode + ExcludeFromExports, //!< Exclude item from exports + FrameColor, //!< Item frame color + BackgroundColor, //!< Item background color + //composer map + MapRotation, //!< Map rotation + MapScale, //!< Map scale + MapXMin, //!< Map extent x minimum + MapYMin, //!< Map extent y minimum + MapXMax, //!< Map extent x maximum + MapYMax, //!< Map extent y maximum + MapAtlasMargin, //!< Map atlas margin + MapLayers, //!< Map layer set + MapStylePreset, //!< Layer and style map theme + //composer picture + PictureSource, //!< Picture source url + PictureSvgBackgroundColor, //!< SVG background color + PictureSvgStrokeColor, //!< SVG stroke color + PictureSvgStrokeWidth, //!< SVG stroke width + //html item + SourceUrl, //!< Html source url + //legend item + LegendTitle, //!< Legend title + LegendColumnCount, //!< Legend column count + //scalebar item + ScalebarFillColor, //!< Scalebar fill color + ScalebarFillColor2, //!< Scalebar secondary fill color + ScalebarLineColor, //!< Scalebar line color + ScalebarLineWidth, //!< Scalebar line width + }; + + /** + * Returns the layout object property definitions. + */ + static const QgsPropertiesDefinition &propertyDefinitions(); + /** * Constructor for QgsLayoutObject, with the specified parent \a layout. * \note While ownership of a QgsLayoutObject is not passed to the layout, @@ -54,12 +114,80 @@ class CORE_EXPORT QgsLayoutObject: public QObject */ QgsLayout *layout() { return mLayout; } + /** + * Returns a reference to the object's property collection, used for data defined overrides. + * \see setDataDefinedProperties() + */ + QgsPropertyCollection &dataDefinedProperties() { return mDataDefinedProperties; } + + /** + * Returns a reference to the object's property collection, used for data defined overrides. + * \see setDataDefinedProperties() + */ + const QgsPropertyCollection &dataDefinedProperties() const { return mDataDefinedProperties; } SIP_SKIP + + /** + * Sets the objects's property collection, used for data defined overrides. + * \param collection property collection. Existing properties will be replaced. + * \see dataDefinedProperties() + */ + void setDataDefinedProperties( const QgsPropertyCollection &collection ) { mDataDefinedProperties = collection; } + + + /** + * Set a custom property for the object. + * \param key property key. If a property with the same key already exists it will be overwritten. + * \param value property value + * \see customProperty() + * \see removeCustomProperty() + * \see customProperties() + */ + void setCustomProperty( const QString &key, const QVariant &value ); + + /** + * Read a custom property from the object. + * \param key property key + * \param defaultValue default value to return if property with matching key does not exist + * \returns value of matching property + * \see setCustomProperty() + * \see removeCustomProperty() + * \see customProperties() + */ + QVariant customProperty( const QString &key, const QVariant &defaultValue = QVariant() ) const; + + /** + * Remove a custom property from the object. + * \param key property key + * \see setCustomProperty() + * \see customProperty() + * \see customProperties() + */ + void removeCustomProperty( const QString &key ); + + /** + * Return list of keys stored in custom properties for the object. + * \see setCustomProperty() + * \see customProperty() + * \see removeCustomProperty() + */ + QStringList customProperties() const; + protected: QgsLayout *mLayout = nullptr; + QgsPropertyCollection mDataDefinedProperties; + + //! Custom properties for object + QgsObjectCustomProperties mCustomProperties; + private: + //! Property definitions + static QgsPropertiesDefinition sPropertyDefinitions; + + static void initPropertyDefinitions(); + friend class TestQgsLayoutObject; }; diff --git a/tests/src/core/testqgslayoutobject.cpp b/tests/src/core/testqgslayoutobject.cpp index efc268ce4c9..927ca58a717 100644 --- a/tests/src/core/testqgslayoutobject.cpp +++ b/tests/src/core/testqgslayoutobject.cpp @@ -30,6 +30,7 @@ class TestQgsLayoutObject: public QObject void cleanup();// will be called after every testfunction. void creation(); //test creation of QgsLayoutObject void layout(); //test fetching layout from QgsLayoutObject + void customProperties(); private: QgsLayout *mLayout = nullptr; @@ -81,5 +82,34 @@ void TestQgsLayoutObject::layout() delete object; } +void TestQgsLayoutObject::customProperties() +{ + QgsLayoutObject *object = new QgsLayoutObject( mLayout ); + + QCOMPARE( object->customProperty( "noprop", "defaultval" ).toString(), QString( "defaultval" ) ); + QVERIFY( object->customProperties().isEmpty() ); + object->setCustomProperty( QStringLiteral( "testprop" ), "testval" ); + QCOMPARE( object->customProperty( "testprop", "defaultval" ).toString(), QString( "testval" ) ); + QCOMPARE( object->customProperties().length(), 1 ); + QCOMPARE( object->customProperties().at( 0 ), QString( "testprop" ) ); + + //test no crash + object->removeCustomProperty( QStringLiteral( "badprop" ) ); + + object->removeCustomProperty( QStringLiteral( "testprop" ) ); + QVERIFY( object->customProperties().isEmpty() ); + QCOMPARE( object->customProperty( "noprop", "defaultval" ).toString(), QString( "defaultval" ) ); + + object->setCustomProperty( QStringLiteral( "testprop1" ), "testval1" ); + object->setCustomProperty( QStringLiteral( "testprop2" ), "testval2" ); + QStringList keys = object->customProperties(); + QCOMPARE( keys.length(), 2 ); + QVERIFY( keys.contains( "testprop1" ) ); + QVERIFY( keys.contains( "testprop2" ) ); + + delete object; +} + + QGSTEST_MAIN( TestQgsLayoutObject ) #include "testqgslayoutobject.moc" From dd370373beb089096f3177165ceb79792656dd9c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 16:37:19 +1000 Subject: [PATCH 020/266] Port a bunch of low-level methods to layouts Relating to expression contexts and variables --- python/core/layout/qgslayout.sip | 69 ++++++++++++++++++- python/core/layout/qgslayoutobject.sip | 10 ++- python/core/qgsexpressioncontext.sip | 31 +++++++++ src/core/layout/qgslayout.cpp | 46 ++++++++++++- src/core/layout/qgslayout.h | 70 ++++++++++++++++++- src/core/layout/qgslayoutobject.cpp | 12 ++++ src/core/layout/qgslayoutobject.h | 9 ++- src/core/qgsexpressioncontext.cpp | 75 +++++++++++++++++++++ src/core/qgsexpressioncontext.h | 34 +++++++++- tests/src/core/testqgslayout.cpp | 93 +++++++++++++++++++++++++- tests/src/core/testqgslayoutitem.cpp | 10 +-- tests/src/core/testqgslayoutobject.cpp | 44 +++++++++--- tests/src/gui/testqgslayoutview.cpp | 7 +- 13 files changed, 483 insertions(+), 27 deletions(-) diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index e3798f0c418..1661e0c22fa 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -7,7 +7,7 @@ ************************************************************************/ -class QgsLayout : QGraphicsScene +class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator { %Docstring Base class for layouts, which can contain items such as maps, labels, scalebars, etc. @@ -24,7 +24,17 @@ class QgsLayout : QGraphicsScene ZMapTool, }; - QgsLayout(); + QgsLayout( QgsProject *project ); +%Docstring + Construct a new layout linked to the specified ``project``. +%End + + QgsProject *project() const; +%Docstring + The project associated with the layout. Used to get access to layers, map themes, + relations and various other bits. It is never null. + :rtype: QgsProject +%End QString name() const; %Docstring @@ -117,6 +127,61 @@ class QgsLayout : QGraphicsScene %End + virtual QgsExpressionContext createExpressionContext() const; + +%Docstring + Creates an expression context relating to the layout's current state. The context includes + scopes for global, project, layout and layout context properties. + :rtype: QgsExpressionContext +%End + + void setCustomProperty( const QString &key, const QVariant &value ); +%Docstring + Set a custom property for the layout. + \param key property key. If a property with the same key already exists it will be overwritten. + \param value property value +.. seealso:: customProperty() +.. seealso:: removeCustomProperty() +.. seealso:: customProperties() +%End + + QVariant customProperty( const QString &key, const QVariant &defaultValue = QVariant() ) const; +%Docstring + Read a custom property from the layout. + \param key property key + \param defaultValue default value to return if property with matching key does not exist + :return: value of matching property +.. seealso:: setCustomProperty() +.. seealso:: removeCustomProperty() +.. seealso:: customProperties() + :rtype: QVariant +%End + + void removeCustomProperty( const QString &key ); +%Docstring + Remove a custom property from the layout. + \param key property key +.. seealso:: setCustomProperty() +.. seealso:: customProperty() +.. seealso:: customProperties() +%End + + QStringList customProperties() const; +%Docstring + Return list of keys stored in custom properties for the layout. +.. seealso:: setCustomProperty() +.. seealso:: customProperty() +.. seealso:: removeCustomProperty() + :rtype: list of str +%End + + signals: + + void variablesChanged(); +%Docstring + Emitted whenever the expression variables stored in the layout have been changed. +%End + }; diff --git a/python/core/layout/qgslayoutobject.sip b/python/core/layout/qgslayoutobject.sip index 225c74fcd6e..bb500cc9df5 100644 --- a/python/core/layout/qgslayoutobject.sip +++ b/python/core/layout/qgslayoutobject.sip @@ -9,7 +9,7 @@ -class QgsLayoutObject: QObject +class QgsLayoutObject: QObject, QgsExpressionContextGenerator { %Docstring A base class for objects which belong to a layout. @@ -151,6 +151,14 @@ class QgsLayoutObject: QObject :rtype: list of str %End + virtual QgsExpressionContext createExpressionContext() const; + +%Docstring + Creates an expression context relating to the objects' current state. The context includes + scopes for global, project and layout properties. + :rtype: QgsExpressionContext +%End + protected: diff --git a/python/core/qgsexpressioncontext.sip b/python/core/qgsexpressioncontext.sip index 81f9c3283b7..0258f358e7f 100644 --- a/python/core/qgsexpressioncontext.sip +++ b/python/core/qgsexpressioncontext.sip @@ -854,6 +854,37 @@ class QgsExpressionContextUtils \param variables new set of layer variables .. seealso:: setCompositionVariable() .. seealso:: compositionScope() +%End + + static QgsExpressionContextScope *layoutScope( const QgsLayout *layout ) /Factory/; +%Docstring + Creates a new scope which contains variables and functions relating to a QgsLayout ``layout``. + For instance, number of pages and page sizes. +.. versionadded:: 3.0 + :rtype: QgsExpressionContextScope +%End + + static void setLayoutVariable( QgsLayout *layout, const QString &name, const QVariant &value ); +%Docstring + Sets a layout context variable. This variable will be contained within scopes retrieved via + layoutScope(). + \param layout target layout + \param name variable name + \param value variable value +.. seealso:: setLayoutVariables() +.. seealso:: layoutScope() +.. versionadded:: 3.0 +%End + + static void setLayoutVariables( QgsLayout *layout, const QVariantMap &variables ); +%Docstring + Sets all layout context variables. Existing layout variables will be removed and replaced + with the variables specified. + \param layout target layout + \param variables new set of layer variables +.. seealso:: setLayoutVariable() +.. seealso:: layoutScope() +.. versionadded:: 3.0 %End static QgsExpressionContextScope *atlasScope( const QgsAtlasComposition *atlas ) /Factory/; diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index 5516499311a..4fdd13544c8 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -16,10 +16,14 @@ #include "qgslayout.h" -QgsLayout::QgsLayout() +QgsLayout::QgsLayout( QgsProject *project ) : QGraphicsScene() -{ + , mProject( project ) +{} +QgsProject *QgsLayout::project() const +{ + return mProject; } double QgsLayout::convertToLayoutUnits( const QgsLayoutMeasurement &measurement ) const @@ -51,3 +55,41 @@ QgsLayoutPoint QgsLayout::convertFromLayoutUnits( const QPointF &point, const Qg { return mContext.measurementConverter().convert( QgsLayoutPoint( point.x(), point.y(), mUnits ), unit ); } + +QgsExpressionContext QgsLayout::createExpressionContext() const +{ + QgsExpressionContext context = QgsExpressionContext(); + context.appendScope( QgsExpressionContextUtils::globalScope() ); + context.appendScope( QgsExpressionContextUtils::projectScope( mProject ) ); + context.appendScope( QgsExpressionContextUtils::layoutScope( this ) ); +#if 0 //TODO + if ( mAtlasComposition.enabled() ) + { + context.appendScope( QgsExpressionContextUtils::atlasScope( &mAtlasComposition ) ); + } +#endif + return context; +} + +void QgsLayout::setCustomProperty( const QString &key, const QVariant &value ) +{ + mCustomProperties.setValue( key, value ); + + if ( key.startsWith( QLatin1String( "variable" ) ) ) + emit variablesChanged(); +} + +QVariant QgsLayout::customProperty( const QString &key, const QVariant &defaultValue ) const +{ + return mCustomProperties.value( key, defaultValue ); +} + +void QgsLayout::removeCustomProperty( const QString &key ) +{ + mCustomProperties.remove( key ); +} + +QStringList QgsLayout::customProperties() const +{ + return mCustomProperties.keys(); +} diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index 9c2a83bac36..481209e3f8d 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -19,6 +19,7 @@ #include "qgis_core.h" #include #include "qgslayoutcontext.h" +#include "qgsexpressioncontextgenerator.h" /** * \ingroup core @@ -26,7 +27,7 @@ * \brief Base class for layouts, which can contain items such as maps, labels, scalebars, etc. * \since QGIS 3.0 */ -class CORE_EXPORT QgsLayout : public QGraphicsScene +class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContextGenerator { Q_OBJECT @@ -38,7 +39,17 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene ZMapTool = 10000, //!< Z-Value for temporary map tool items }; - QgsLayout(); + /** + * Construct a new layout linked to the specified \a project. + */ + QgsLayout( QgsProject *project ); + + /** + * The project associated with the layout. Used to get access to layers, map themes, + * relations and various other bits. It is never null. + * + */ + QgsProject *project() const; /** * Returns the layout's name. @@ -127,10 +138,65 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene */ SIP_SKIP const QgsLayoutContext &context() const { return mContext; } + /** + * Creates an expression context relating to the layout's current state. The context includes + * scopes for global, project, layout and layout context properties. + */ + QgsExpressionContext createExpressionContext() const override; + + /** + * Set a custom property for the layout. + * \param key property key. If a property with the same key already exists it will be overwritten. + * \param value property value + * \see customProperty() + * \see removeCustomProperty() + * \see customProperties() + */ + void setCustomProperty( const QString &key, const QVariant &value ); + + /** + * Read a custom property from the layout. + * \param key property key + * \param defaultValue default value to return if property with matching key does not exist + * \returns value of matching property + * \see setCustomProperty() + * \see removeCustomProperty() + * \see customProperties() + */ + QVariant customProperty( const QString &key, const QVariant &defaultValue = QVariant() ) const; + + /** + * Remove a custom property from the layout. + * \param key property key + * \see setCustomProperty() + * \see customProperty() + * \see customProperties() + */ + void removeCustomProperty( const QString &key ); + + /** + * Return list of keys stored in custom properties for the layout. + * \see setCustomProperty() + * \see customProperty() + * \see removeCustomProperty() + */ + QStringList customProperties() const; + + signals: + + /** + * Emitted whenever the expression variables stored in the layout have been changed. + */ + void variablesChanged(); + private: + QgsProject *mProject = nullptr; + QString mName; + QgsObjectCustomProperties mCustomProperties; + QgsUnitTypes::LayoutUnit mUnits = QgsUnitTypes::LayoutMillimeters; QgsLayoutContext mContext; diff --git a/src/core/layout/qgslayoutobject.cpp b/src/core/layout/qgslayoutobject.cpp index 1ee14df8d55..d13163771c0 100644 --- a/src/core/layout/qgslayoutobject.cpp +++ b/src/core/layout/qgslayoutobject.cpp @@ -110,3 +110,15 @@ QStringList QgsLayoutObject::customProperties() const { return mCustomProperties.keys(); } + +QgsExpressionContext QgsLayoutObject::createExpressionContext() const +{ + if ( mLayout ) + { + return mLayout->createExpressionContext(); + } + else + { + return QgsExpressionContext() << QgsExpressionContextUtils::globalScope(); + } +} diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h index bdedcbb418e..eddace21288 100644 --- a/src/core/layout/qgslayoutobject.h +++ b/src/core/layout/qgslayoutobject.h @@ -21,6 +21,7 @@ #include "qgis_sip.h" #include "qgspropertycollection.h" #include "qgsobjectcustomproperties.h" +#include "qgsexpressioncontextgenerator.h" #include #include #include @@ -33,7 +34,7 @@ class QPainter; * A base class for objects which belong to a layout. * \since QGIS 3.0 */ -class CORE_EXPORT QgsLayoutObject: public QObject +class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGenerator { Q_OBJECT public: @@ -172,6 +173,12 @@ class CORE_EXPORT QgsLayoutObject: public QObject */ QStringList customProperties() const; + /** + * Creates an expression context relating to the objects' current state. The context includes + * scopes for global, project and layout properties. + */ + QgsExpressionContext createExpressionContext() const override; + protected: QgsLayout *mLayout = nullptr; diff --git a/src/core/qgsexpressioncontext.cpp b/src/core/qgsexpressioncontext.cpp index 11be136b859..56936aebdd7 100644 --- a/src/core/qgsexpressioncontext.cpp +++ b/src/core/qgsexpressioncontext.cpp @@ -31,6 +31,7 @@ #include "qgsmaplayerlistutils.h" #include "qgsprocessingcontext.h" #include "qgsprocessingalgorithm.h" +#include "qgslayout.h" #include #include @@ -1025,6 +1026,80 @@ void QgsExpressionContextUtils::setCompositionVariables( QgsComposition *composi composition->setCustomProperty( QStringLiteral( "variableValues" ), variableValues ); } +QgsExpressionContextScope *QgsExpressionContextUtils::layoutScope( const QgsLayout *layout ) +{ + std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope( QObject::tr( "Layout" ) ) ); + if ( !layout ) + return scope.release(); + + //add variables defined in layout properties + QStringList variableNames = layout->customProperty( QStringLiteral( "variableNames" ) ).toStringList(); + QStringList variableValues = layout->customProperty( QStringLiteral( "variableValues" ) ).toStringList(); + + int varIndex = 0; + Q_FOREACH ( const QString &variableName, variableNames ) + { + if ( varIndex >= variableValues.length() ) + { + break; + } + + QVariant varValue = variableValues.at( varIndex ); + varIndex++; + scope->setVariable( variableName, varValue ); + } + + //add known layout context variables + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_name" ), layout->name(), true ) ); +#if 0 //TODO + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_numpages" ), composition->numPages(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pageheight" ), composition->paperHeight(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pagewidth" ), composition->paperWidth(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_dpi" ), layout->context().dpi(), true ) ); +#endif + +#if 0 //TODO + scope->addFunction( QStringLiteral( "item_variables" ), new GetComposerItemVariables( composition ) ); +#endif + + return scope.release(); +} + +void QgsExpressionContextUtils::setLayoutVariable( QgsLayout *layout, const QString &name, const QVariant &value ) +{ + if ( !layout ) + return; + + //write variable to composition + QStringList variableNames = layout->customProperty( QStringLiteral( "variableNames" ) ).toStringList(); + QStringList variableValues = layout->customProperty( QStringLiteral( "variableValues" ) ).toStringList(); + + variableNames << name; + variableValues << value.toString(); + + layout->setCustomProperty( QStringLiteral( "variableNames" ), variableNames ); + layout->setCustomProperty( QStringLiteral( "variableValues" ), variableValues ); +} + +void QgsExpressionContextUtils::setLayoutVariables( QgsLayout *layout, const QVariantMap &variables ) +{ + if ( !layout ) + return; + + QStringList variableNames; + QStringList variableValues; + + QVariantMap::const_iterator it = variables.constBegin(); + for ( ; it != variables.constEnd(); ++it ) + { + variableNames << it.key(); + variableValues << it.value().toString(); + } + + layout->setCustomProperty( QStringLiteral( "variableNames" ), variableNames ); + layout->setCustomProperty( QStringLiteral( "variableValues" ), variableValues ); +} + QgsExpressionContextScope *QgsExpressionContextUtils::atlasScope( const QgsAtlasComposition *atlas ) { QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Atlas" ) ); diff --git a/src/core/qgsexpressioncontext.h b/src/core/qgsexpressioncontext.h index 83bfe150116..b9a9e7e173a 100644 --- a/src/core/qgsexpressioncontext.h +++ b/src/core/qgsexpressioncontext.h @@ -31,6 +31,7 @@ class QgsExpression; class QgsExpressionNodeFunction; class QgsMapLayer; +class QgsLayout; class QgsComposition; class QgsComposerItem; class QgsAtlasComposition; @@ -776,7 +777,8 @@ class CORE_EXPORT QgsExpressionContextUtils */ static void setCompositionVariable( QgsComposition *composition, const QString &name, const QVariant &value ); - /** Sets all composition context variables. Existing composition variables will be removed and replaced + /** + * Sets all composition context variables. Existing composition variables will be removed and replaced * with the variables specified. * \param composition target composition * \param variables new set of layer variables @@ -785,6 +787,36 @@ class CORE_EXPORT QgsExpressionContextUtils */ static void setCompositionVariables( QgsComposition *composition, const QVariantMap &variables ); + /** + * Creates a new scope which contains variables and functions relating to a QgsLayout \a layout. + * For instance, number of pages and page sizes. + * \since QGIS 3.0 + */ + static QgsExpressionContextScope *layoutScope( const QgsLayout *layout ) SIP_FACTORY; + + /** + * Sets a layout context variable. This variable will be contained within scopes retrieved via + * layoutScope(). + * \param layout target layout + * \param name variable name + * \param value variable value + * \see setLayoutVariables() + * \see layoutScope() + * \since QGIS 3.0 + */ + static void setLayoutVariable( QgsLayout *layout, const QString &name, const QVariant &value ); + + /** + * Sets all layout context variables. Existing layout variables will be removed and replaced + * with the variables specified. + * \param layout target layout + * \param variables new set of layer variables + * \see setLayoutVariable() + * \see layoutScope() + * \since QGIS 3.0 + */ + static void setLayoutVariables( QgsLayout *layout, const QVariantMap &variables ); + /** Creates a new scope which contains variables and functions relating to a QgsAtlasComposition. * For instance, current page name and number. * \param atlas source atlas. If null, a set of default atlas variables will be added to the scope. diff --git a/tests/src/core/testqgslayout.cpp b/tests/src/core/testqgslayout.cpp index 1e764c3a388..a8ddc12e433 100644 --- a/tests/src/core/testqgslayout.cpp +++ b/tests/src/core/testqgslayout.cpp @@ -17,6 +17,7 @@ #include "qgslayout.h" #include "qgstest.h" +#include "qgsproject.h" class TestQgsLayout: public QObject { @@ -30,6 +31,9 @@ class TestQgsLayout: public QObject void creation(); //test creation of QgsLayout void units(); void name(); + void customProperties(); + void variablesEdited(); + void scope(); private: QString mReport; @@ -65,14 +69,17 @@ void TestQgsLayout::cleanup() void TestQgsLayout::creation() { - QgsLayout *layout = new QgsLayout(); + QgsProject p; + QgsLayout *layout = new QgsLayout( &p ); QVERIFY( layout ); + QCOMPARE( layout->project(), &p ); delete layout; } void TestQgsLayout::units() { - QgsLayout layout; + QgsProject p; + QgsLayout layout( &p ); layout.setUnits( QgsUnitTypes::LayoutCentimeters ); QCOMPARE( layout.units(), QgsUnitTypes::LayoutCentimeters ); QCOMPARE( layout.convertToLayoutUnits( QgsLayoutMeasurement( 10.0, QgsUnitTypes::LayoutMillimeters ) ), 1.0 ); @@ -120,12 +127,92 @@ void TestQgsLayout::units() void TestQgsLayout::name() { - QgsLayout layout; + QgsProject p; + QgsLayout layout( &p ); QString layoutName = "test name"; layout.setName( layoutName ); QCOMPARE( layout.name(), layoutName ); } +void TestQgsLayout::customProperties() +{ + QgsProject p; + QgsLayout *layout = new QgsLayout( &p ); + + QCOMPARE( layout->customProperty( "noprop", "defaultval" ).toString(), QString( "defaultval" ) ); + QVERIFY( layout->customProperties().isEmpty() ); + layout->setCustomProperty( QStringLiteral( "testprop" ), "testval" ); + QCOMPARE( layout->customProperty( "testprop", "defaultval" ).toString(), QString( "testval" ) ); + QCOMPARE( layout->customProperties().length(), 1 ); + QCOMPARE( layout->customProperties().at( 0 ), QString( "testprop" ) ); + + //test no crash + layout->removeCustomProperty( QStringLiteral( "badprop" ) ); + + layout->removeCustomProperty( QStringLiteral( "testprop" ) ); + QVERIFY( layout->customProperties().isEmpty() ); + QCOMPARE( layout->customProperty( "noprop", "defaultval" ).toString(), QString( "defaultval" ) ); + + layout->setCustomProperty( QStringLiteral( "testprop1" ), "testval1" ); + layout->setCustomProperty( QStringLiteral( "testprop2" ), "testval2" ); + QStringList keys = layout->customProperties(); + QCOMPARE( keys.length(), 2 ); + QVERIFY( keys.contains( "testprop1" ) ); + QVERIFY( keys.contains( "testprop2" ) ); + + delete layout; +} + +void TestQgsLayout::variablesEdited() +{ + QgsProject p; + QgsLayout l( &p ); + QSignalSpy spyVariablesChanged( &l, &QgsLayout::variablesChanged ); + + l.setCustomProperty( QStringLiteral( "not a variable" ), "1" ); + QVERIFY( spyVariablesChanged.count() == 0 ); + l.setCustomProperty( QStringLiteral( "variableNames" ), "1" ); + QVERIFY( spyVariablesChanged.count() == 1 ); + l.setCustomProperty( QStringLiteral( "variableValues" ), "1" ); + QVERIFY( spyVariablesChanged.count() == 2 ); +} + +void TestQgsLayout::scope() +{ + QgsProject p; + QgsLayout l( &p ); + + // no crash + std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::layoutScope( nullptr ) ); + l.setName( "test" ); + scope.reset( QgsExpressionContextUtils::layoutScope( &l ) ); + QCOMPARE( scope->variable( "layout_name" ).toString(), QStringLiteral( "test" ) ); + + QgsExpressionContextUtils::setLayoutVariable( &l, "new_var", 5 ); + QgsExpressionContextUtils::setLayoutVariable( &l, "new_var2", 15 ); + scope.reset( QgsExpressionContextUtils::layoutScope( &l ) ); + QCOMPARE( scope->variable( "layout_name" ).toString(), QStringLiteral( "test" ) ); + QCOMPARE( scope->variable( "new_var" ).toInt(), 5 ); + QCOMPARE( scope->variable( "new_var2" ).toInt(), 15 ); + + QVariantMap newVars; + newVars.insert( "new_var3", 17 ); + QgsExpressionContextUtils::setLayoutVariables( &l, newVars ); + scope.reset( QgsExpressionContextUtils::layoutScope( &l ) ); + QCOMPARE( scope->variable( "layout_name" ).toString(), QStringLiteral( "test" ) ); + QVERIFY( !scope->hasVariable( "new_var" ) ); + QVERIFY( !scope->hasVariable( "new_var2" ) ); + QCOMPARE( scope->variable( "new_var3" ).toInt(), 17 ); + + p.setTitle( "my title" ); + QgsExpressionContext c = l.createExpressionContext(); + // should contain project variables + QCOMPARE( c.variable( "project_title" ).toString(), QStringLiteral( "my title" ) ); + // and layout variables + QCOMPARE( c.variable( "new_var3" ).toInt(), 17 ); + +} + QGSTEST_MAIN( TestQgsLayout ) #include "testqgslayout.moc" diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index ea70cf70439..42772c9550a 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -20,6 +20,7 @@ #include "qgslayout.h" #include "qgsmultirenderchecker.h" #include "qgstest.h" +#include "qgsproject.h" #include #include #include @@ -62,7 +63,6 @@ class TestQgsLayoutItem: public QObject } }; - QgsLayout *mLayout = nullptr; QString mReport; bool renderCheck( QString testName, QImage &image, int mismatchCount ); @@ -71,14 +71,11 @@ class TestQgsLayoutItem: public QObject void TestQgsLayoutItem::initTestCase() { - mLayout = new QgsLayout(); mReport = "

Layout Item Tests

\n"; } void TestQgsLayoutItem::cleanupTestCase() { - delete mLayout; - QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html"; QFile myFile( myReportFile ); if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) ) @@ -101,9 +98,12 @@ void TestQgsLayoutItem::cleanup() void TestQgsLayoutItem::creation() { - TestItem *item = new TestItem( mLayout ); + QgsProject p; + QgsLayout *layout = new QgsLayout( &p ); + TestItem *item = new TestItem( layout ); QVERIFY( item ); delete item; + delete layout; } void TestQgsLayoutItem::registry() diff --git a/tests/src/core/testqgslayoutobject.cpp b/tests/src/core/testqgslayoutobject.cpp index 927ca58a717..ff0ec46836b 100644 --- a/tests/src/core/testqgslayoutobject.cpp +++ b/tests/src/core/testqgslayoutobject.cpp @@ -18,6 +18,7 @@ #include "qgslayoutobject.h" #include "qgslayout.h" #include "qgstest.h" +#include "qgsproject.h" class TestQgsLayoutObject: public QObject { @@ -31,23 +32,20 @@ class TestQgsLayoutObject: public QObject void creation(); //test creation of QgsLayoutObject void layout(); //test fetching layout from QgsLayoutObject void customProperties(); + void context(); private: - QgsLayout *mLayout = nullptr; QString mReport; }; void TestQgsLayoutObject::initTestCase() { - mLayout = new QgsLayout(); mReport = "

Layout Object Tests

\n"; } void TestQgsLayoutObject::cleanupTestCase() { - delete mLayout; - QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html"; QFile myFile( myReportFile ); if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) ) @@ -70,21 +68,29 @@ void TestQgsLayoutObject::cleanup() void TestQgsLayoutObject::creation() { - QgsLayoutObject *object = new QgsLayoutObject( mLayout ); + QgsProject p; + QgsLayout *layout = new QgsLayout( &p ); + QgsLayoutObject *object = new QgsLayoutObject( layout ); QVERIFY( object ); delete object; + delete layout; } void TestQgsLayoutObject::layout() { - QgsLayoutObject *object = new QgsLayoutObject( mLayout ); - QCOMPARE( object->layout(), mLayout ); + QgsProject p; + QgsLayout *layout = new QgsLayout( &p ); + QgsLayoutObject *object = new QgsLayoutObject( layout ); + QCOMPARE( object->layout(), layout ); delete object; + delete layout; } void TestQgsLayoutObject::customProperties() { - QgsLayoutObject *object = new QgsLayoutObject( mLayout ); + QgsProject p; + QgsLayout *layout = new QgsLayout( &p ); + QgsLayoutObject *object = new QgsLayoutObject( layout ); QCOMPARE( object->customProperty( "noprop", "defaultval" ).toString(), QString( "defaultval" ) ); QVERIFY( object->customProperties().isEmpty() ); @@ -108,6 +114,28 @@ void TestQgsLayoutObject::customProperties() QVERIFY( keys.contains( "testprop2" ) ); delete object; + delete layout; +} + +void TestQgsLayoutObject::context() +{ + QgsProject p; + p.setTitle( "my title" ); + QgsLayout l( &p ); + l.setName( "my layout" ); + + QgsLayoutObject *object = new QgsLayoutObject( nullptr ); + // no crash + QgsExpressionContext c = object->createExpressionContext(); + delete object; + + object = new QgsLayoutObject( &l ); + c = object->createExpressionContext(); + // should contain project variables + QCOMPARE( c.variable( "project_title" ).toString(), QStringLiteral( "my title" ) ); + // and layout variables + QCOMPARE( c.variable( "layout_name" ).toString(), QStringLiteral( "my layout" ) ); + delete object; } diff --git a/tests/src/gui/testqgslayoutview.cpp b/tests/src/gui/testqgslayoutview.cpp index e56cec1bdbe..abc0d5bfa3c 100644 --- a/tests/src/gui/testqgslayoutview.cpp +++ b/tests/src/gui/testqgslayoutview.cpp @@ -23,6 +23,7 @@ #include "qgslayoutitemregistry.h" #include "qgslayoutitemguiregistry.h" #include "qgstestutils.h" +#include "qgsproject.h" #include "qgsgui.h" #include @@ -63,7 +64,8 @@ void TestQgsLayoutView::cleanup() void TestQgsLayoutView::basic() { - QgsLayout *layout = new QgsLayout(); + QgsProject p; + QgsLayout *layout = new QgsLayout( &p ); QgsLayoutView *view = new QgsLayoutView(); QSignalSpy spyLayoutChanged( view, &QgsLayoutView::layoutSet ); @@ -180,8 +182,9 @@ class LoggingTool : public QgsLayoutViewTool void TestQgsLayoutView::events() { + QgsProject p; QgsLayoutView *view = new QgsLayoutView(); - QgsLayout *layout = new QgsLayout(); + QgsLayout *layout = new QgsLayout( &p ); view->setCurrentLayout( layout ); layout->setSceneRect( 0, 0, 1000, 1000 ); view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); From 498c4cda160e022cface3ab4b793bfad114f8ba8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Jul 2017 12:36:47 +1000 Subject: [PATCH 021/266] Add some rendering checks for layout items --- src/core/layout/qgslayoutitem.cpp | 25 ++++- src/core/layout/qgslayoutitem.h | 2 + tests/src/core/testqgslayoutitem.cpp | 90 ++++++++++++++++++ .../expected_layoutitem_draw.png | Bin 1164 -> 0 bytes .../expected_layoutitem_debugrect.png | Bin 1157 -> 1141 bytes .../expected_layoutitem_draw.png | Bin 0 -> 1141 bytes 6 files changed, 116 insertions(+), 1 deletion(-) delete mode 100644 tests/testdata/control_images/expected_layoutitem_draw/expected_layoutitem_draw.png rename tests/testdata/control_images/{ => layouts}/expected_layoutitem_debugrect/expected_layoutitem_debugrect.png (56%) create mode 100644 tests/testdata/control_images/layouts/expected_layoutitem_draw/expected_layoutitem_draw.png diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 5bb978945a2..4c835ef43ec 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -36,7 +36,14 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it painter->save(); preparePainter( painter ); - draw( painter, itemStyle, pWidget ); + if ( shouldDrawDebugRect() ) + { + drawDebugRect( painter ); + } + else + { + draw( painter, itemStyle, pWidget ); + } painter->restore(); } @@ -62,4 +69,20 @@ void QgsLayoutItem::preparePainter( QPainter *painter ) { return; } + + painter->setRenderHint( QPainter::Antialiasing, shouldDrawAntialiased() ); +} + +bool QgsLayoutItem::shouldDrawAntialiased() const +{ + if ( !mLayout ) + { + return true; + } + return mLayout->context().testFlag( QgsLayoutContext::FlagAntialiasing ) && !mLayout->context().testFlag( QgsLayoutContext::FlagDebug ); +} + +bool QgsLayoutItem::shouldDrawDebugRect() const +{ + return mLayout && mLayout->context().testFlag( QgsLayoutContext::FlagDebug ); } diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 957b436fc45..b8bcf9dcaa1 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -66,6 +66,8 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt //! Prepares a painter by setting rendering flags void preparePainter( QPainter *painter ); + bool shouldDrawAntialiased() const; + bool shouldDrawDebugRect() const; friend class TestQgsLayoutItem; }; diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 42772c9550a..7ea4ab45fbc 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -37,6 +37,11 @@ class TestQgsLayoutItem: public QObject void cleanup();// will be called after every testfunction. void creation(); //test creation of QgsLayoutItem void registry(); + void shouldDrawDebug(); + void shouldDrawAntialiased(); + void preparePainter(); + void debugRect(); + void draw(); private: @@ -160,6 +165,90 @@ void TestQgsLayoutItem::registry() QVERIFY( !reg2.populate() ); } +void TestQgsLayoutItem::shouldDrawDebug() +{ + QgsProject p; + QgsLayout l( &p ); + TestItem *item = new TestItem( &l ); + l.context().setFlag( QgsLayoutContext::FlagDebug, true ); + QVERIFY( item->shouldDrawDebugRect() ); + l.context().setFlag( QgsLayoutContext::FlagDebug, false ); + QVERIFY( !item->shouldDrawDebugRect() ); + delete item; +} + +void TestQgsLayoutItem::shouldDrawAntialiased() +{ + QgsProject p; + QgsLayout l( &p ); + TestItem *item = new TestItem( &l ); + l.context().setFlag( QgsLayoutContext::FlagAntialiasing, false ); + QVERIFY( !item->shouldDrawAntialiased() ); + l.context().setFlag( QgsLayoutContext::FlagAntialiasing, true ); + QVERIFY( item->shouldDrawAntialiased() ); + delete item; +} + +void TestQgsLayoutItem::preparePainter() +{ + QgsProject p; + QgsLayout l( &p ); + TestItem *item = new TestItem( &l ); + //test with no painter + item->preparePainter( nullptr ); + + //test antialiasing correctly set for painter + QImage image( QSize( 100, 100 ), QImage::Format_ARGB32 ); + QPainter painter; + painter.begin( &image ); + l.context().setFlag( QgsLayoutContext::FlagAntialiasing, false ); + item->preparePainter( &painter ); + QVERIFY( !( painter.renderHints() & QPainter::Antialiasing ) ); + l.context().setFlag( QgsLayoutContext::FlagAntialiasing, true ); + item->preparePainter( &painter ); + QVERIFY( painter.renderHints() & QPainter::Antialiasing ); + delete item; +} + +void TestQgsLayoutItem::debugRect() +{ + QgsProject p; + QgsLayout l( &p ); + TestItem *item = new TestItem( &l ); + l.addItem( item ); + item->setPos( 100, 100 ); + item->setRect( 0, 0, 200, 200 ); + l.setSceneRect( 0, 0, 400, 400 ); + l.context().setFlag( QgsLayoutContext::FlagDebug, true ); + QImage image( l.sceneRect().size().toSize(), QImage::Format_ARGB32 ); + image.fill( 0 ); + QPainter painter( &image ); + l.render( &painter ); + painter.end(); + + bool result = renderCheck( "layoutitem_debugrect", image, 0 ); + QVERIFY( result ); +} + +void TestQgsLayoutItem::draw() +{ + QgsProject p; + QgsLayout l( &p ); + TestItem *item = new TestItem( &l ); + l.addItem( item ); + item->setPos( 100, 100 ); + item->setRect( 0, 0, 200, 200 ); + l.setSceneRect( 0, 0, 400, 400 ); + l.context().setFlag( QgsLayoutContext::FlagAntialiasing, false ); //disable antialiasing to limit cross platform differences + QImage image( l.sceneRect().size().toSize(), QImage::Format_ARGB32 ); + image.fill( 0 ); + QPainter painter( &image ); + l.render( &painter ); + painter.end(); + bool result = renderCheck( "layoutitem_draw", image, 0 ); + QVERIFY( result ); +} + bool TestQgsLayoutItem::renderCheck( QString testName, QImage &image, int mismatchCount ) { mReport += "

" + testName + "

\n"; @@ -167,6 +256,7 @@ bool TestQgsLayoutItem::renderCheck( QString testName, QImage &image, int mismat QString myFileName = myTmpDir + testName + ".png"; image.save( myFileName, "PNG" ); QgsRenderChecker myChecker; + myChecker.setControlPathPrefix( "layouts" ); myChecker.setControlName( "expected_" + testName ); myChecker.setRenderedImage( myFileName ); bool myResultFlag = myChecker.compareImages( testName, mismatchCount ); diff --git a/tests/testdata/control_images/expected_layoutitem_draw/expected_layoutitem_draw.png b/tests/testdata/control_images/expected_layoutitem_draw/expected_layoutitem_draw.png deleted file mode 100644 index 3ee7fce79fbc98ae97fd85a05b78a3b496802f62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1164 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD}uylV=DA5Y%v_bO8CB1s;*b z3=DinK$vl=HlH+5jh?5AV@SoEw^t1X4>>R}98|Xz3TW>a`PgWslAw^<_HEYr^KZ0v zygLTeKUl$z{^M^L{?&f}o?pg%=j!VpHtFUCH`CY|HmET(Di{hd2#r#sK`>-e!C=j0 dKzB_mXR_GEI^oL)XS|IwmUW@rDSx9lG`O=Mz82?Rwl36Q@orV3j&hzh*J> z536^NrS3A+m*zdU+tqm8%!;AGQgX8*<07WX1&o3dlcr28U>7&o%f88*b;6a@jk6el Nz|+;wWt~$(69B}3DCGbE diff --git a/tests/testdata/control_images/layouts/expected_layoutitem_draw/expected_layoutitem_draw.png b/tests/testdata/control_images/layouts/expected_layoutitem_draw/expected_layoutitem_draw.png new file mode 100644 index 0000000000000000000000000000000000000000..714ce9933ae1fbb13065ce7d5101071d8a24f962 GIT binary patch literal 1141 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~teM`SSr1K$x4W}K?cC(XdX zqVDP97*a9k?NvkG1_y=%4i2Jq7h2_G4thlF{xkWG=8J2oj6kh}6wEkoW!13$x5c`P zyaubk+I+vryJy=)MutC=8yO}@a4>X^QlmjIWKzK(&0`>E?f=fOHJf$9&;LD7L0nH) KKbLh*2~7auOWUFV literal 0 HcmV?d00001 From d0c844ed67fe94867b946ccea2ae1facfb1c5e74 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Jul 2017 13:06:27 +1000 Subject: [PATCH 022/266] Implement item size and positioning using layout units --- python/core/layout/qgslayoutitem.sip | 120 ++++++++++- python/core/layout/qgslayoutobject.sip | 9 +- src/core/layout/qgslayoutitem.cpp | 139 ++++++++++++ src/core/layout/qgslayoutitem.h | 130 +++++++++++- src/core/layout/qgslayoutobject.h | 9 +- tests/src/core/testqgslayoutitem.cpp | 282 +++++++++++++++++++++++++ 6 files changed, 685 insertions(+), 4 deletions(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 9d3db060187..504b8ef35bf 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -21,7 +21,20 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem %End public: - QgsLayoutItem( QgsLayout *layout ); + enum ReferencePoint + { + UpperLeft, + UpperMiddle, + UpperRight, + MiddleLeft, + Middle, + MiddleRight, + LowerLeft, + LowerMiddle, + LowerRight, + }; + + explicit QgsLayoutItem( QgsLayout *layout ); %Docstring Constructor for QgsLayoutItem, with the specified parent ``layout``. %End @@ -34,6 +47,72 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem the pure virtual method QgsLayoutItem.draw. %End + void setReferencePoint( const ReferencePoint &point ); +%Docstring + Sets the reference ``point`` for positioning of the layout item. This point is also + fixed during resizing of the item, and any size changes will be performed + so that the position of the reference point within the layout remains unchanged. +.. seealso:: referencePoint() +%End + + ReferencePoint referencePoint() const; +%Docstring + Returns the reference point for positioning of the layout item. This point is also + fixed during resizing of the item, and any size changes will be performed + so that the position of the reference point within the layout remains unchanged. +.. seealso:: setReferencePoint() + :rtype: ReferencePoint +%End + + QgsLayoutSize fixedSize() const; +%Docstring + Returns the fixed size of the item, if applicable, or an empty size if item can be freely + resized. +.. seealso:: setFixedSize() +.. seealso:: minimumSize() + :rtype: QgsLayoutSize +%End + + virtual QgsLayoutSize minimumSize() const; +%Docstring + Returns the minimum allowed size of the item, if applicable, or an empty size if item can be freely + resized. +.. seealso:: setMinimumSize() +.. seealso:: fixedSize() + :rtype: QgsLayoutSize +%End + + virtual void attemptResize( const QgsLayoutSize &size ); +%Docstring + Attempts to resize the item to a specified target ``size``. Note that the final size of the + item may not match the specified target size, as items with a fixed or minimum + size will place restrictions on the allowed item size. Data defined item size overrides + will also override the specified target size. +.. seealso:: minimumSize() +.. seealso:: fixedSize() +.. seealso:: attemptMove() +%End + + virtual void attemptMove( const QgsLayoutPoint &point ); +%Docstring + Attempts to move the item to a specified ``point``. This method respects the item's + reference point, in that the item will be moved so that its current reference + point is placed at the specified target point. + Note that the final position of the item may not match the specified target position, + as data defined item position may override the specified value. +.. seealso:: attemptResize() +.. seealso:: referencePoint() +%End + + public slots: + + virtual void refresh(); + +%Docstring + Refreshes the item, causing a recalculation of any property overrides and + recalculation of its position and size. +%End + protected: virtual void drawDebugRect( QPainter *painter ); @@ -48,6 +127,45 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem Draws the item's contents on a specified ``painter``. %End + virtual void setFixedSize( const QgsLayoutSize &size ); +%Docstring + Sets a fixed ``size`` for the layout item, which prevents it from being freely + resized. Set an empty size if item can be freely resized. +.. seealso:: fixedSize() +.. seealso:: setMinimumSize() +%End + + virtual void setMinimumSize( const QgsLayoutSize &size ); +%Docstring + Sets the minimum allowed ``size`` for the layout item. Set an empty size if item can be freely + resized. +.. seealso:: minimumSize() +.. seealso:: setFixedSize() +%End + + void refreshItemSize(); +%Docstring + Refreshes an item's size by rechecking it against any possible item fixed + or minimum sizes. +.. seealso:: setFixedSize() +.. seealso:: setMinimumSize() +.. seealso:: refreshItemPosition() +%End + + void refreshItemPosition(); +%Docstring + Refreshes an item's position by rechecking it against any possible overrides + such as data defined positioning. +.. seealso:: refreshItemSize() +%End + + QPointF adjustPointForReferencePosition( const QPointF &point, const QSizeF &size ) const; +%Docstring + Adjusts the specified ``point`` at which the reference position of the item + sits and returns the top left corner of the item. + :rtype: QPointF +%End + }; diff --git a/python/core/layout/qgslayoutobject.sip b/python/core/layout/qgslayoutobject.sip index bb500cc9df5..e60d5673ddf 100644 --- a/python/core/layout/qgslayoutobject.sip +++ b/python/core/layout/qgslayoutobject.sip @@ -78,7 +78,7 @@ class QgsLayoutObject: QObject, QgsExpressionContextGenerator :rtype: QgsPropertiesDefinition %End - QgsLayoutObject( QgsLayout *layout ); + explicit QgsLayoutObject( QgsLayout *layout ); %Docstring Constructor for QgsLayoutObject, with the specified parent ``layout``. .. note:: @@ -159,6 +159,13 @@ class QgsLayoutObject: QObject, QgsExpressionContextGenerator :rtype: QgsExpressionContext %End + public slots: + + virtual void refresh(); +%Docstring + Refreshes the object, causing a recalculation of any property overrides. +%End + protected: diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 4c835ef43ec..c28df2e1397 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -23,6 +23,13 @@ QgsLayoutItem::QgsLayoutItem( QgsLayout *layout ) , QGraphicsRectItem( 0 ) { setCacheMode( QGraphicsItem::DeviceCoordinateCache ); + + //record initial position + QgsUnitTypes::LayoutUnit initialUnits = layout ? layout->units() : QgsUnitTypes::LayoutMillimeters; + mItemPosition = QgsLayoutPoint( scenePos().x(), scenePos().y(), initialUnits ); + mItemSize = QgsLayoutSize( rect().width(), rect().height(), initialUnits ); + + initConnectionsToLayout(); } void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) @@ -48,6 +55,62 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it painter->restore(); } +void QgsLayoutItem::setReferencePoint( const QgsLayoutItem::ReferencePoint &point ) +{ + mReferencePoint = point; +} + +void QgsLayoutItem::attemptResize( const QgsLayoutSize &size ) +{ + if ( !mLayout ) + { + mItemSize = size; + setRect( 0, 0, size.width(), size.height() ); + return; + } + + QSizeF targetSizeLayoutUnits = mLayout->convertToLayoutUnits( size ); + QSizeF actualSizeLayoutUnits = applyMinimumSize( targetSizeLayoutUnits ); + actualSizeLayoutUnits = applyFixedSize( actualSizeLayoutUnits ); + + if ( actualSizeLayoutUnits == rect().size() ) + { + return; + } + + QgsLayoutSize actualSizeTargetUnits = mLayout->convertFromLayoutUnits( actualSizeLayoutUnits, size.units() ); + mItemSize = actualSizeTargetUnits; + + setRect( 0, 0, actualSizeLayoutUnits.width(), actualSizeLayoutUnits.height() ); +} + +void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) +{ + if ( !mLayout ) + { + mItemPosition = point; + setPos( point.toQPointF() ); + return; + } + + QPointF targetPointLayoutUnits = mLayout->convertToLayoutUnits( point ); + //TODO - apply data defined position here + targetPointLayoutUnits = adjustPointForReferencePosition( targetPointLayoutUnits, rect().size() ); + QPointF actualPointLayoutUnits = targetPointLayoutUnits; + + QgsLayoutPoint actualPointTargetUnits = mLayout->convertFromLayoutUnits( actualPointLayoutUnits, point.units() ); + mItemPosition = actualPointTargetUnits; + + setPos( targetPointLayoutUnits ); +} + +void QgsLayoutItem::refresh() +{ + QgsLayoutObject::refresh(); + refreshItemSize(); + refreshItemPosition(); +} + void QgsLayoutItem::drawDebugRect( QPainter *painter ) { if ( !painter ) @@ -63,6 +126,62 @@ void QgsLayoutItem::drawDebugRect( QPainter *painter ) painter->restore(); } +void QgsLayoutItem::setFixedSize( const QgsLayoutSize &size ) +{ + mFixedSize = size; + refreshItemSize(); +} + +void QgsLayoutItem::setMinimumSize( const QgsLayoutSize &size ) +{ + mMinimumSize = size; + refreshItemSize(); +} + +void QgsLayoutItem::refreshItemSize() +{ + attemptResize( mItemSize ); +} + +void QgsLayoutItem::refreshItemPosition() +{ + attemptMove( mItemPosition ); +} + +QPointF QgsLayoutItem::adjustPointForReferencePosition( const QPointF &position, const QSizeF &size ) const +{ + switch ( mReferencePoint ) + { + case UpperMiddle: + return QPointF( position.x() - size.width() / 2.0, position.y() ); + case UpperRight: + return QPointF( position.x() - size.width(), position.y() ); + case MiddleLeft: + return QPointF( position.x(), position.y() - size.height() / 2.0 ); + case Middle: + return QPointF( position.x() - size.width() / 2.0, position.y() - size.height() / 2.0 ); + case MiddleRight: + return QPointF( position.x() - size.width(), position.y() - size.height() / 2.0 ); + case LowerLeft: + return QPointF( position.x(), position.y() - size.height() ); + case LowerMiddle: + return QPointF( position.x() - size.width() / 2.0, position.y() - size.height() ); + case LowerRight: + return QPointF( position.x() - size.width(), position.y() - size.height() ); + case UpperLeft: + return position; + } + // no warnings + return position; +} + +void QgsLayoutItem::initConnectionsToLayout() +{ + if ( !mLayout ) + return; + +} + void QgsLayoutItem::preparePainter( QPainter *painter ) { if ( !painter || !painter->device() ) @@ -86,3 +205,23 @@ bool QgsLayoutItem::shouldDrawDebugRect() const { return mLayout && mLayout->context().testFlag( QgsLayoutContext::FlagDebug ); } + +QSizeF QgsLayoutItem::applyMinimumSize( const QSizeF &targetSize ) +{ + if ( !mLayout || minimumSize().isEmpty() ) + { + return targetSize; + } + QSizeF minimumSizeLayoutUnits = mLayout->convertToLayoutUnits( minimumSize() ); + return targetSize.expandedTo( minimumSizeLayoutUnits ); +} + +QSizeF QgsLayoutItem::applyFixedSize( const QSizeF &targetSize ) +{ + if ( !mLayout || fixedSize().isEmpty() ) + { + return targetSize; + } + QSizeF fixedSizeLayoutUnits = mLayout->convertToLayoutUnits( fixedSize() ); + return targetSize.expandedTo( fixedSizeLayoutUnits ); +} diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index b8bcf9dcaa1..5ba52d8fbfb 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -19,6 +19,8 @@ #include "qgis_core.h" #include "qgslayoutobject.h" +#include "qgslayoutsize.h" +#include "qgslayoutpoint.h" #include class QgsLayout; @@ -37,10 +39,24 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt public: + //! Fixed position reference point + enum ReferencePoint + { + UpperLeft, //!< Upper left corner of item + UpperMiddle, //!< Upper center of item + UpperRight, //!< Upper right corner of item + MiddleLeft, //!< Middle left of item + Middle, //!< Center of item + MiddleRight, //!< Middle right of item + LowerLeft, //!< Lower left corner of item + LowerMiddle, //!< Lower center of item + LowerRight, //!< Lower right corner of item + }; + /** * Constructor for QgsLayoutItem, with the specified parent \a layout. */ - QgsLayoutItem( QgsLayout *layout ); + explicit QgsLayoutItem( QgsLayout *layout ); /** * Handles preparing a paint surface for the layout item and painting the item's @@ -49,6 +65,68 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) override; + /** + * Sets the reference \a point for positioning of the layout item. This point is also + * fixed during resizing of the item, and any size changes will be performed + * so that the position of the reference point within the layout remains unchanged. + * \see referencePoint() + */ + void setReferencePoint( const ReferencePoint &point ); + + /** + * Returns the reference point for positioning of the layout item. This point is also + * fixed during resizing of the item, and any size changes will be performed + * so that the position of the reference point within the layout remains unchanged. + * \see setReferencePoint() + */ + ReferencePoint referencePoint() const { return mReferencePoint; } + + /** + * Returns the fixed size of the item, if applicable, or an empty size if item can be freely + * resized. + * \see setFixedSize() + * \see minimumSize() + */ + QgsLayoutSize fixedSize() const { return mFixedSize; } + + /** + * Returns the minimum allowed size of the item, if applicable, or an empty size if item can be freely + * resized. + * \see setMinimumSize() + * \see fixedSize() + */ + virtual QgsLayoutSize minimumSize() const { return mMinimumSize; } + + /** + * Attempts to resize the item to a specified target \a size. Note that the final size of the + * item may not match the specified target size, as items with a fixed or minimum + * size will place restrictions on the allowed item size. Data defined item size overrides + * will also override the specified target size. + * \see minimumSize() + * \see fixedSize() + * \see attemptMove() + */ + virtual void attemptResize( const QgsLayoutSize &size ); + + /** + * Attempts to move the item to a specified \a point. This method respects the item's + * reference point, in that the item will be moved so that its current reference + * point is placed at the specified target point. + * Note that the final position of the item may not match the specified target position, + * as data defined item position may override the specified value. + * \see attemptResize() + * \see referencePoint() + */ + virtual void attemptMove( const QgsLayoutPoint &point ); + + public slots: + + /** + * Refreshes the item, causing a recalculation of any property overrides and + * recalculation of its position and size. + */ + void refresh() override; + protected: /** Draws a debugging rectangle of the item's current bounds within the specified @@ -62,13 +140,63 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ virtual void draw( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) = 0; + /** + * Sets a fixed \a size for the layout item, which prevents it from being freely + * resized. Set an empty size if item can be freely resized. + * \see fixedSize() + * \see setMinimumSize() + */ + virtual void setFixedSize( const QgsLayoutSize &size ); + + /** + * Sets the minimum allowed \a size for the layout item. Set an empty size if item can be freely + * resized. + * \see minimumSize() + * \see setFixedSize() + */ + virtual void setMinimumSize( const QgsLayoutSize &size ); + + /** + * Refreshes an item's size by rechecking it against any possible item fixed + * or minimum sizes. + * \see setFixedSize() + * \see setMinimumSize() + * \see refreshItemPosition() + */ + void refreshItemSize(); + + /** + * Refreshes an item's position by rechecking it against any possible overrides + * such as data defined positioning. + * \see refreshItemSize() + */ + void refreshItemPosition(); + + /** + * Adjusts the specified \a point at which the reference position of the item + * sits and returns the top left corner of the item. + */ + QPointF adjustPointForReferencePosition( const QPointF &point, const QSizeF &size ) const; + private: + ReferencePoint mReferencePoint = UpperLeft; + QgsLayoutSize mFixedSize; + QgsLayoutSize mMinimumSize; + + QgsLayoutSize mItemSize; + QgsLayoutPoint mItemPosition; + + void initConnectionsToLayout(); + //! Prepares a painter by setting rendering flags void preparePainter( QPainter *painter ); bool shouldDrawAntialiased() const; bool shouldDrawDebugRect() const; + QSizeF applyMinimumSize( const QSizeF &targetSize ); + QSizeF applyFixedSize( const QSizeF &targetSize ); + friend class TestQgsLayoutItem; }; diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h index eddace21288..4064d216722 100644 --- a/src/core/layout/qgslayoutobject.h +++ b/src/core/layout/qgslayoutobject.h @@ -103,7 +103,7 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe * classes which are derived from QgsLayoutObject (such as QgsLayoutItem) * may transfer their ownership to a layout upon construction. */ - QgsLayoutObject( QgsLayout *layout ); + explicit QgsLayoutObject( QgsLayout *layout ); /** * Returns the layout the object is attached to. @@ -179,6 +179,13 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe */ QgsExpressionContext createExpressionContext() const override; + public slots: + + /** + * Refreshes the object, causing a recalculation of any property overrides. + */ + virtual void refresh() {} + protected: QgsLayout *mLayout = nullptr; diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 7ea4ab45fbc..4c5a2697ba4 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -42,6 +42,11 @@ class TestQgsLayoutItem: public QObject void preparePainter(); void debugRect(); void draw(); + void resize(); + void referencePoint(); + void fixedSize(); + void minSize(); + void move(); private: @@ -68,6 +73,53 @@ class TestQgsLayoutItem: public QObject } }; + //item with minimum size + class MinSizedItem : public TestItem + { + public: + MinSizedItem( QgsLayout *layout ) : TestItem( layout ) + { + setMinimumSize( QgsLayoutSize( 5.0, 10.0, QgsUnitTypes::LayoutCentimeters ) ); + } + + void updateMinSize( QgsLayoutSize size ) + { + setMinimumSize( size ); + } + + ~MinSizedItem() {} + }; + + //item with fixed size + class FixedSizedItem : public TestItem + { + public: + + FixedSizedItem( QgsLayout *layout ) : TestItem( layout ) + { + setFixedSize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutInches ) ); + } + + void updateFixedSize( QgsLayoutSize size ) + { + setFixedSize( size ); + } + ~FixedSizedItem() {} + }; + + //item with both conflicting fixed and minimum size + class FixedMinSizedItem : public TestItem + { + public: + + FixedMinSizedItem( QgsLayout *layout ) : TestItem( layout ) + { + setFixedSize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) ); + setMinimumSize( QgsLayoutSize( 5.0, 9.0, QgsUnitTypes::LayoutCentimeters ) ); + } + ~FixedMinSizedItem() {} + }; + QString mReport; bool renderCheck( QString testName, QImage &image, int mismatchCount ); @@ -264,5 +316,235 @@ bool TestQgsLayoutItem::renderCheck( QString testName, QImage &image, int mismat return myResultFlag; } +void TestQgsLayoutItem::resize() +{ + QgsProject p; + QgsLayout l( &p ); + + //resize test item (no restrictions), same units as layout + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + TestItem *item = new TestItem( &l ); + item->setRect( 0, 0, 55, 45 ); + item->setPos( 27, 29 ); + item->attemptResize( QgsLayoutSize( 100.0, 200.0, QgsUnitTypes::LayoutMillimeters ) ); + QCOMPARE( item->rect().width(), 100.0 ); + QCOMPARE( item->rect().height(), 200.0 ); + QCOMPARE( item->pos().x(), 27.0 ); //item should not move + QCOMPARE( item->pos().y(), 29.0 ); + + //test conversion of units + l.setUnits( QgsUnitTypes::LayoutCentimeters ); + item->setRect( 0, 0, 100, 200 ); + item->attemptResize( QgsLayoutSize( 0.30, 0.45, QgsUnitTypes::LayoutMeters ) ); + QCOMPARE( item->rect().width(), 30.0 ); + QCOMPARE( item->rect().height(), 45.0 ); + + //test pixel -> page conversion + l.setUnits( QgsUnitTypes::LayoutInches ); + l.context().setDpi( 100.0 ); + item->refresh(); + item->setRect( 0, 0, 1, 2 ); + item->attemptResize( QgsLayoutSize( 140, 280, QgsUnitTypes::LayoutPixels ) ); + QCOMPARE( item->rect().width(), 1.4 ); + QCOMPARE( item->rect().height(), 2.8 ); + //changing the dpi should resize the item + l.context().setDpi( 200.0 ); + item->refresh(); + QCOMPARE( item->rect().width(), 0.7 ); + QCOMPARE( item->rect().height(), 1.4 ); + + //test page -> pixel conversion + l.setUnits( QgsUnitTypes::LayoutPixels ); + l.context().setDpi( 100.0 ); + item->refresh(); + item->setRect( 0, 0, 2, 2 ); + item->attemptResize( QgsLayoutSize( 1, 3, QgsUnitTypes::LayoutInches ) ); + QCOMPARE( item->rect().width(), 100.0 ); + QCOMPARE( item->rect().height(), 300.0 ); + //changing dpi results in item resize + l.context().setDpi( 200.0 ); + item->refresh(); + QCOMPARE( item->rect().width(), 200.0 ); + QCOMPARE( item->rect().height(), 600.0 ); + + l.setUnits( QgsUnitTypes::LayoutMillimeters ); +} + +void TestQgsLayoutItem::referencePoint() +{ + QgsProject p; + QgsLayout l( &p ); + + //test setting/getting reference point + TestItem *item = new TestItem( &l ); + item->setReferencePoint( QgsLayoutItem::LowerMiddle ); + QCOMPARE( item->referencePoint(), QgsLayoutItem::LowerMiddle ); + + //test that setting item position is done relative to reference point + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + item->attemptResize( QgsLayoutSize( 2, 4 ) ); + item->setReferencePoint( QgsLayoutItem::UpperLeft ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), 1.0 ); + QCOMPARE( item->pos().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::UpperMiddle ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), 0.0 ); + QCOMPARE( item->pos().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::UpperRight ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), -1.0 ); + QCOMPARE( item->pos().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleLeft ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), 1.0 ); + QCOMPARE( item->pos().y(), 0.0 ); + item->setReferencePoint( QgsLayoutItem::Middle ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), 0.0 ); + QCOMPARE( item->pos().y(), 0.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleRight ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), -1.0 ); + QCOMPARE( item->pos().y(), 0.0 ); + item->setReferencePoint( QgsLayoutItem::LowerLeft ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), 1.0 ); + QCOMPARE( item->pos().y(), -2.0 ); + item->setReferencePoint( QgsLayoutItem::LowerMiddle ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), 0.0 ); + QCOMPARE( item->pos().y(), -2.0 ); + item->setReferencePoint( QgsLayoutItem::LowerRight ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->pos().x(), -1.0 ); + QCOMPARE( item->pos().y(), -2.0 ); + + //test that resizing is done relative to reference point + +} + +void TestQgsLayoutItem::fixedSize() +{ + QgsProject p; + QgsLayout l( &p ); + + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + FixedSizedItem *item = new FixedSizedItem( &l ); + QCOMPARE( item->fixedSize().width(), 2.0 ); + QCOMPARE( item->fixedSize().height(), 4.0 ); + QCOMPARE( item->fixedSize().units(), QgsUnitTypes::LayoutInches ); + + item->setRect( 0, 0, 5.0, 6.0 ); //temporarily set rect to random size + item->attemptResize( QgsLayoutSize( 7.0, 8.0, QgsUnitTypes::LayoutPoints ) ); + //check size matches fixed item size converted to mm + QVERIFY( qgsDoubleNear( item->rect().width(), 2.0 * 25.4 ) ); + QVERIFY( qgsDoubleNear( item->rect().height(), 4.0 * 25.4 ) ); + + //check that setting a fixed size applies this size immediately + item->updateFixedSize( QgsLayoutSize( 150, 250, QgsUnitTypes::LayoutMillimeters ) ); + QVERIFY( qgsDoubleNear( item->rect().width(), 150.0 ) ); + QVERIFY( qgsDoubleNear( item->rect().height(), 250.0 ) ); +} + +void TestQgsLayoutItem::minSize() +{ + QgsProject p; + QgsLayout l( &p ); + + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + MinSizedItem *item = new MinSizedItem( &l ); + QCOMPARE( item->minimumSize().width(), 5.0 ); + QCOMPARE( item->minimumSize().height(), 10.0 ); + QCOMPARE( item->minimumSize().units(), QgsUnitTypes::LayoutCentimeters ); + + item->setRect( 0, 0, 9.0, 6.0 ); //temporarily set rect to random size + //try to resize to less than minimum size + item->attemptResize( QgsLayoutSize( 1.0, 0.5, QgsUnitTypes::LayoutPoints ) ); + //check size matches min item size converted to mm + QVERIFY( qgsDoubleNear( item->rect().width(), 50.0 ) ); + QVERIFY( qgsDoubleNear( item->rect().height(), 100.0 ) ); + + //check that resize to larger than min size works + item->attemptResize( QgsLayoutSize( 0.1, 0.2, QgsUnitTypes::LayoutMeters ) ); + QVERIFY( qgsDoubleNear( item->rect().width(), 100.0 ) ); + QVERIFY( qgsDoubleNear( item->rect().height(), 200.0 ) ); + + //check that setting a minimum size applies this size immediately + item->updateMinSize( QgsLayoutSize( 150, 250, QgsUnitTypes::LayoutMillimeters ) ); + QVERIFY( qgsDoubleNear( item->rect().width(), 150.0 ) ); + QVERIFY( qgsDoubleNear( item->rect().height(), 250.0 ) ); + + //also need check that fixed size trumps min size + FixedMinSizedItem *fixedMinItem = new FixedMinSizedItem( &l ); + QCOMPARE( fixedMinItem->minimumSize().width(), 5.0 ); + QCOMPARE( fixedMinItem->minimumSize().height(), 9.0 ); + QCOMPARE( fixedMinItem->minimumSize().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( fixedMinItem->fixedSize().width(), 2.0 ); + QCOMPARE( fixedMinItem->fixedSize().height(), 4.0 ); + QCOMPARE( fixedMinItem->fixedSize().units(), QgsUnitTypes::LayoutCentimeters ); + //try to resize to less than minimum size + fixedMinItem->attemptResize( QgsLayoutSize( 1.0, 0.5, QgsUnitTypes::LayoutPoints ) ); + //check size matches fixed item size, not minimum size (converted to mm) + QVERIFY( qgsDoubleNear( fixedMinItem->rect().width(), 50.0 ) ); + QVERIFY( qgsDoubleNear( fixedMinItem->rect().height(), 90.0 ) ); +} + +void TestQgsLayoutItem::move() +{ + QgsProject p; + QgsLayout l( &p ); + + //move test item, same units as layout + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + TestItem *item = new TestItem( &l ); + item->setRect( 0, 0, 55, 45 ); + item->setPos( 27, 29 ); + item->attemptMove( QgsLayoutPoint( 60.0, 15.0, QgsUnitTypes::LayoutMillimeters ) ); + QCOMPARE( item->rect().width(), 55.0 ); //size should not change + QCOMPARE( item->rect().height(), 45.0 ); + QCOMPARE( item->pos().x(), 60.0 ); + QCOMPARE( item->pos().y(), 15.0 ); + + //test conversion of units + l.setUnits( QgsUnitTypes::LayoutCentimeters ); + item->setPos( 100, 200 ); + item->attemptMove( QgsLayoutPoint( 0.30, 0.45, QgsUnitTypes::LayoutMeters ) ); + QCOMPARE( item->pos().x(), 30.0 ); + QCOMPARE( item->pos().y(), 45.0 ); + + //test pixel -> page conversion + l.setUnits( QgsUnitTypes::LayoutInches ); + l.context().setDpi( 100.0 ); + item->refresh(); + item->setPos( 1, 2 ); + item->attemptMove( QgsLayoutPoint( 140, 280, QgsUnitTypes::LayoutPixels ) ); + QCOMPARE( item->pos().x(), 1.4 ); + QCOMPARE( item->pos().y(), 2.8 ); + //changing the dpi should move the item + l.context().setDpi( 200.0 ); + item->refresh(); + QCOMPARE( item->pos().x(), 0.7 ); + QCOMPARE( item->pos().y(), 1.4 ); + + //test page -> pixel conversion + l.setUnits( QgsUnitTypes::LayoutPixels ); + l.context().setDpi( 100.0 ); + item->refresh(); + item->setPos( 2, 2 ); + item->attemptMove( QgsLayoutPoint( 1, 3, QgsUnitTypes::LayoutInches ) ); + QCOMPARE( item->pos().x(), 100.0 ); + QCOMPARE( item->pos().y(), 300.0 ); + //changing dpi results in item move + l.context().setDpi( 200.0 ); + item->refresh(); + QCOMPARE( item->pos().x(), 200.0 ); + QCOMPARE( item->pos().y(), 600.0 ); + + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + + //TODO - reference points +} + QGSTEST_MAIN( TestQgsLayoutItem ) #include "testqgslayoutitem.moc" From 3f0a0cb063986053b2ff76e08c7b777c93a839e2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Jul 2017 13:14:34 +1000 Subject: [PATCH 023/266] Ensure that moving/resizing items respects reference point --- python/core/layout/qgslayoutitem.sip | 10 ++ src/core/layout/qgslayoutitem.cpp | 15 +++ src/core/layout/qgslayoutitem.h | 9 ++ tests/src/core/testqgslayoutitem.cpp | 144 ++++++++++++++++++++++++++- 4 files changed, 176 insertions(+), 2 deletions(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 504b8ef35bf..ccb11099abe 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -104,6 +104,16 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem .. seealso:: referencePoint() %End + QgsLayoutPoint positionWithUnits() const; +%Docstring + Returns the item's current position, including units. The position returned + is the position of the item's reference point, which may not necessarily be the top + left corner of the item. +.. seealso:: attemptMove() +.. seealso:: referencePoint() + :rtype: QgsLayoutPoint +%End + public slots: virtual void refresh(); diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index c28df2e1397..2dcda3d13ed 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -57,7 +57,16 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it void QgsLayoutItem::setReferencePoint( const QgsLayoutItem::ReferencePoint &point ) { + if ( point == mReferencePoint ) + { + return; + } + mReferencePoint = point; + + //also need to adjust stored position + QPointF positionReferencePointLayoutUnits = adjustPointForReferencePosition( pos(), QSizeF( -rect().width(), -rect().height() ) ); + mItemPosition = mLayout->convertFromLayoutUnits( positionReferencePointLayoutUnits, mItemPosition.units() ); } void QgsLayoutItem::attemptResize( const QgsLayoutSize &size ) @@ -82,6 +91,7 @@ void QgsLayoutItem::attemptResize( const QgsLayoutSize &size ) mItemSize = actualSizeTargetUnits; setRect( 0, 0, actualSizeLayoutUnits.width(), actualSizeLayoutUnits.height() ); + refreshItemPosition(); } void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) @@ -98,6 +108,11 @@ void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) targetPointLayoutUnits = adjustPointForReferencePosition( targetPointLayoutUnits, rect().size() ); QPointF actualPointLayoutUnits = targetPointLayoutUnits; + if ( actualPointLayoutUnits == pos() ) + { + return; + } + QgsLayoutPoint actualPointTargetUnits = mLayout->convertFromLayoutUnits( actualPointLayoutUnits, point.units() ); mItemPosition = actualPointTargetUnits; diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 5ba52d8fbfb..964aff66085 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -119,6 +119,15 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ virtual void attemptMove( const QgsLayoutPoint &point ); + /** + * Returns the item's current position, including units. The position returned + * is the position of the item's reference point, which may not necessarily be the top + * left corner of the item. + * \see attemptMove() + * \see referencePoint() + */ + QgsLayoutPoint positionWithUnits() const { return mItemPosition; } + public slots: /** diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 4c5a2697ba4..be5f1eb0d8c 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -44,9 +44,11 @@ class TestQgsLayoutItem: public QObject void draw(); void resize(); void referencePoint(); + void adjustPointForReference(); void fixedSize(); void minSize(); void move(); + void positionWithUnits(); private: @@ -316,6 +318,22 @@ bool TestQgsLayoutItem::renderCheck( QString testName, QImage &image, int mismat return myResultFlag; } +void TestQgsLayoutItem::positionWithUnits() +{ + QgsProject p; + QgsLayout l( &p ); + + TestItem *item = new TestItem( &l ); + item->attemptMove( QgsLayoutPoint( 60.0, 15.0, QgsUnitTypes::LayoutMillimeters ) ); + QCOMPARE( item->positionWithUnits().x(), 60.0 ); + QCOMPARE( item->positionWithUnits().y(), 15.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutMillimeters ); + item->attemptMove( QgsLayoutPoint( 50.0, 100.0, QgsUnitTypes::LayoutPixels ) ); + QCOMPARE( item->positionWithUnits().x(), 50.0 ); + QCOMPARE( item->positionWithUnits().y(), 100.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutPixels ); +} + void TestQgsLayoutItem::resize() { QgsProject p; @@ -325,7 +343,7 @@ void TestQgsLayoutItem::resize() l.setUnits( QgsUnitTypes::LayoutMillimeters ); TestItem *item = new TestItem( &l ); item->setRect( 0, 0, 55, 45 ); - item->setPos( 27, 29 ); + item->attemptMove( QgsLayoutPoint( 27, 29 ) ); item->attemptResize( QgsLayoutSize( 100.0, 200.0, QgsUnitTypes::LayoutMillimeters ) ); QCOMPARE( item->rect().width(), 100.0 ); QCOMPARE( item->rect().height(), 200.0 ); @@ -380,6 +398,44 @@ void TestQgsLayoutItem::referencePoint() item->setReferencePoint( QgsLayoutItem::LowerMiddle ); QCOMPARE( item->referencePoint(), QgsLayoutItem::LowerMiddle ); + //test that setting reference point results in positionWithUnits returning position at new reference + //point + item->setReferencePoint( QgsLayoutItem::UpperLeft ); + item->attemptResize( QgsLayoutSize( 2, 4 ) ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + QCOMPARE( item->positionWithUnits().x(), 1.0 ); + QCOMPARE( item->positionWithUnits().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::UpperLeft ); + QCOMPARE( item->positionWithUnits().x(), 1.0 ); + QCOMPARE( item->positionWithUnits().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::UpperMiddle ); + QCOMPARE( item->positionWithUnits().x(), 2.0 ); + QCOMPARE( item->positionWithUnits().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::UpperRight ); + QCOMPARE( item->positionWithUnits().x(), 3.0 ); + QCOMPARE( item->positionWithUnits().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleLeft ); + QCOMPARE( item->positionWithUnits().x(), 1.0 ); + QCOMPARE( item->positionWithUnits().y(), 4.0 ); + item->setReferencePoint( QgsLayoutItem::Middle ); + QCOMPARE( item->positionWithUnits().x(), 2.0 ); + QCOMPARE( item->positionWithUnits().y(), 4.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleRight ); + QCOMPARE( item->positionWithUnits().x(), 3.0 ); + QCOMPARE( item->positionWithUnits().y(), 4.0 ); + item->setReferencePoint( QgsLayoutItem::LowerLeft ); + QCOMPARE( item->positionWithUnits().x(), 1.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + item->setReferencePoint( QgsLayoutItem::LowerMiddle ); + QCOMPARE( item->positionWithUnits().x(), 2.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + item->setReferencePoint( QgsLayoutItem::LowerRight ); + QCOMPARE( item->positionWithUnits().x(), 3.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + + delete item; + item = new TestItem( &l ); + //test that setting item position is done relative to reference point l.setUnits( QgsUnitTypes::LayoutMillimeters ); item->attemptResize( QgsLayoutSize( 2, 4 ) ); @@ -420,8 +476,92 @@ void TestQgsLayoutItem::referencePoint() QCOMPARE( item->pos().x(), -1.0 ); QCOMPARE( item->pos().y(), -2.0 ); - //test that resizing is done relative to reference point + delete item; + item = new TestItem( &l ); + //test that resizing is done relative to reference point + item->attemptResize( QgsLayoutSize( 2, 4 ) ); + item->setReferencePoint( QgsLayoutItem::UpperLeft ); + item->attemptMove( QgsLayoutPoint( 1, 2 ) ); + item->attemptResize( QgsLayoutSize( 4, 6 ) ); + QCOMPARE( item->pos().x(), 1.0 ); + QCOMPARE( item->pos().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::UpperMiddle ); + item->attemptResize( QgsLayoutSize( 6, 4 ) ); + QCOMPARE( item->pos().x(), 0.0 ); + QCOMPARE( item->pos().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::UpperRight ); + item->attemptResize( QgsLayoutSize( 4, 6 ) ); + QCOMPARE( item->pos().x(), 2.0 ); + QCOMPARE( item->pos().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleLeft ); + item->attemptResize( QgsLayoutSize( 6, 4 ) ); + QCOMPARE( item->pos().x(), 2.0 ); + QCOMPARE( item->pos().y(), 3.0 ); + item->setReferencePoint( QgsLayoutItem::Middle ); + item->attemptResize( QgsLayoutSize( 4, 6 ) ); + QCOMPARE( item->pos().x(), 3.0 ); + QCOMPARE( item->pos().y(), 2.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleRight ); + item->attemptResize( QgsLayoutSize( 6, 4 ) ); + QCOMPARE( item->pos().x(), 1.0 ); + QCOMPARE( item->pos().y(), 3.0 ); + item->setReferencePoint( QgsLayoutItem::LowerLeft ); + item->attemptResize( QgsLayoutSize( 4, 6 ) ); + QCOMPARE( item->pos().x(), 1.0 ); + QCOMPARE( item->pos().y(), 1.0 ); + item->setReferencePoint( QgsLayoutItem::LowerMiddle ); + item->attemptResize( QgsLayoutSize( 6, 4 ) ); + QCOMPARE( item->pos().x(), 0.0 ); + QCOMPARE( item->pos().y(), 3.0 ); + item->setReferencePoint( QgsLayoutItem::LowerRight ); + item->attemptResize( QgsLayoutSize( 4, 6 ) ); + QCOMPARE( item->pos().x(), 2.0 ); + QCOMPARE( item->pos().y(), 1.0 ); +} + +void TestQgsLayoutItem::adjustPointForReference() +{ + QgsProject p; + QgsLayout l( &p ); + + TestItem *item = new TestItem( &l ); + item->setReferencePoint( QgsLayoutItem::UpperLeft ); + QPointF result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 5.0 ); + QCOMPARE( result.y(), 7.0 ); + item->setReferencePoint( QgsLayoutItem::UpperMiddle ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 4.0 ); + QCOMPARE( result.y(), 7.0 ); + item->setReferencePoint( QgsLayoutItem::UpperRight ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 3.0 ); + QCOMPARE( result.y(), 7.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleLeft ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 5.0 ); + QCOMPARE( result.y(), 5.0 ); + item->setReferencePoint( QgsLayoutItem::Middle ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 4.0 ); + QCOMPARE( result.y(), 5.0 ); + item->setReferencePoint( QgsLayoutItem::MiddleRight ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 3.0 ); + QCOMPARE( result.y(), 5.0 ); + item->setReferencePoint( QgsLayoutItem::LowerLeft ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 5.0 ); + QCOMPARE( result.y(), 3.0 ); + item->setReferencePoint( QgsLayoutItem::LowerMiddle ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 4.0 ); + QCOMPARE( result.y(), 3.0 ); + item->setReferencePoint( QgsLayoutItem::LowerRight ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 3.0 ); + QCOMPARE( result.y(), 3.0 ); } void TestQgsLayoutItem::fixedSize() From 6fd06983a64248bfeb46e83b4c42758a9e41dc4b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Jul 2017 13:39:28 +1000 Subject: [PATCH 024/266] Port data defined item position and size --- python/core/layout/qgslayoutitem.sip | 19 ++ src/core/layout/qgslayoutitem.cpp | 68 +++++-- src/core/layout/qgslayoutitem.h | 20 ++ tests/src/core/testqgslayoutitem.cpp | 272 +++++++++++++++++++++++++++ 4 files changed, 368 insertions(+), 11 deletions(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index ccb11099abe..979c900dbd0 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -91,6 +91,7 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem .. seealso:: minimumSize() .. seealso:: fixedSize() .. seealso:: attemptMove() +.. seealso:: sizeWithUnits() %End virtual void attemptMove( const QgsLayoutPoint &point ); @@ -102,6 +103,7 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem as data defined item position may override the specified value. .. seealso:: attemptResize() .. seealso:: referencePoint() +.. seealso:: positionWithUnits() %End QgsLayoutPoint positionWithUnits() const; @@ -111,9 +113,18 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem left corner of the item. .. seealso:: attemptMove() .. seealso:: referencePoint() +.. seealso:: sizeWithUnits() :rtype: QgsLayoutPoint %End + QgsLayoutSize sizeWithUnits() const; +%Docstring + Returns the item's current size, including units. +.. seealso:: attemptResize() +.. seealso:: positionWithUnits() + :rtype: QgsLayoutSize +%End + public slots: virtual void refresh(); @@ -123,6 +134,14 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem recalculation of its position and size. %End + virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ); +%Docstring + Refreshes a data defined ``property`` for the item by reevaluating the property's value + and redrawing the item with this new value. If ``property`` is set to + QgsLayoutObject.AllProperties then all data defined properties for the item will be + refreshed. +%End + protected: virtual void drawDebugRect( QPainter *painter ); diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 2dcda3d13ed..b9217fad4dc 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -67,6 +67,7 @@ void QgsLayoutItem::setReferencePoint( const QgsLayoutItem::ReferencePoint &poin //also need to adjust stored position QPointF positionReferencePointLayoutUnits = adjustPointForReferencePosition( pos(), QSizeF( -rect().width(), -rect().height() ) ); mItemPosition = mLayout->convertFromLayoutUnits( positionReferencePointLayoutUnits, mItemPosition.units() ); + refreshItemPosition(); } void QgsLayoutItem::attemptResize( const QgsLayoutSize &size ) @@ -78,7 +79,8 @@ void QgsLayoutItem::attemptResize( const QgsLayoutSize &size ) return; } - QSizeF targetSizeLayoutUnits = mLayout->convertToLayoutUnits( size ); + QgsLayoutSize evaluatedSize = applyDataDefinedSize( size ); + QSizeF targetSizeLayoutUnits = mLayout->convertToLayoutUnits( evaluatedSize ); QSizeF actualSizeLayoutUnits = applyMinimumSize( targetSizeLayoutUnits ); actualSizeLayoutUnits = applyFixedSize( actualSizeLayoutUnits ); @@ -103,27 +105,71 @@ void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) return; } - QPointF targetPointLayoutUnits = mLayout->convertToLayoutUnits( point ); - //TODO - apply data defined position here - targetPointLayoutUnits = adjustPointForReferencePosition( targetPointLayoutUnits, rect().size() ); - QPointF actualPointLayoutUnits = targetPointLayoutUnits; - - if ( actualPointLayoutUnits == pos() ) + QgsLayoutPoint evaluatedPoint = applyDataDefinedPosition( point ); + QPointF evaluatedPointLayoutUnits = mLayout->convertToLayoutUnits( evaluatedPoint ); + QPointF topLeftPointLayoutUnits = adjustPointForReferencePosition( evaluatedPointLayoutUnits, rect().size() ); + if ( topLeftPointLayoutUnits == pos() && point.units() == mItemPosition.units() ) { + //TODO - add test for second condition return; } - QgsLayoutPoint actualPointTargetUnits = mLayout->convertFromLayoutUnits( actualPointLayoutUnits, point.units() ); - mItemPosition = actualPointTargetUnits; + QgsLayoutPoint referencePointTargetUnits = mLayout->convertFromLayoutUnits( evaluatedPointLayoutUnits, point.units() ); + mItemPosition = referencePointTargetUnits; - setPos( targetPointLayoutUnits ); + setPos( topLeftPointLayoutUnits ); } +QgsLayoutPoint QgsLayoutItem::applyDataDefinedPosition( const QgsLayoutPoint &position ) +{ + if ( !mLayout ) + { + return position; + } + + QgsExpressionContext context = createExpressionContext(); + double evaluatedX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PositionX, context, position.x() ); + double evaluatedY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PositionY, context, position.y() ); + return QgsLayoutPoint( evaluatedX, evaluatedY, position.units() ); +} + +QgsLayoutSize QgsLayoutItem::applyDataDefinedSize( const QgsLayoutSize &size ) +{ + if ( !mLayout ) + { + return size; + } + + QgsExpressionContext context = createExpressionContext(); + double evaluatedWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemWidth, context, size.width() ); + double evaluatedHeight = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemHeight, context, size.height() ); + return QgsLayoutSize( evaluatedWidth, evaluatedHeight, size.units() ); +} + +void QgsLayoutItem::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property ) +{ + //update data defined properties and update item to match + + //evaluate width and height first, since they may affect position if non-top-left reference point set + if ( property == QgsLayoutObject::ItemWidth || property == QgsLayoutObject::ItemHeight || + property == QgsLayoutObject::AllProperties ) + { + refreshItemSize(); + } + if ( property == QgsLayoutObject::PositionX || property == QgsLayoutObject::PositionY || + property == QgsLayoutObject::AllProperties ) + { + refreshItemPosition(); + } +} + + void QgsLayoutItem::refresh() { QgsLayoutObject::refresh(); refreshItemSize(); - refreshItemPosition(); + + refreshDataDefinedProperty(); } void QgsLayoutItem::drawDebugRect( QPainter *painter ) diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 964aff66085..c7109dd25e3 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -105,6 +105,7 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt * \see minimumSize() * \see fixedSize() * \see attemptMove() + * \see sizeWithUnits() */ virtual void attemptResize( const QgsLayoutSize &size ); @@ -116,6 +117,7 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt * as data defined item position may override the specified value. * \see attemptResize() * \see referencePoint() + * \see positionWithUnits() */ virtual void attemptMove( const QgsLayoutPoint &point ); @@ -125,9 +127,17 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt * left corner of the item. * \see attemptMove() * \see referencePoint() + * \see sizeWithUnits() */ QgsLayoutPoint positionWithUnits() const { return mItemPosition; } + /** + * Returns the item's current size, including units. + * \see attemptResize() + * \see positionWithUnits() + */ + QgsLayoutSize sizeWithUnits() const { return mItemSize; } + public slots: /** @@ -136,6 +146,14 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ void refresh() override; + /** + * Refreshes a data defined \a property for the item by reevaluating the property's value + * and redrawing the item with this new value. If \a property is set to + * QgsLayoutObject::AllProperties then all data defined properties for the item will be + * refreshed. + */ + virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ); + protected: /** Draws a debugging rectangle of the item's current bounds within the specified @@ -205,6 +223,8 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt QSizeF applyMinimumSize( const QSizeF &targetSize ); QSizeF applyFixedSize( const QSizeF &targetSize ); + QgsLayoutPoint applyDataDefinedPosition( const QgsLayoutPoint &position ); + QgsLayoutSize applyDataDefinedSize( const QgsLayoutSize &size ); friend class TestQgsLayoutItem; }; diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index be5f1eb0d8c..83a0be1827f 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -49,6 +49,10 @@ class TestQgsLayoutItem: public QObject void minSize(); void move(); void positionWithUnits(); + void sizeWithUnits(); + void dataDefinedPosition(); + void dataDefinedSize(); + void combinedDataDefinedPositionAndSize(); private: @@ -334,6 +338,274 @@ void TestQgsLayoutItem::positionWithUnits() QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutPixels ); } +void TestQgsLayoutItem::sizeWithUnits() +{ + QgsProject p; + QgsLayout l( &p ); + + TestItem *item = new TestItem( &l ); + item->attemptResize( QgsLayoutSize( 60.0, 15.0, QgsUnitTypes::LayoutMillimeters ) ); + QCOMPARE( item->sizeWithUnits().width(), 60.0 ); + QCOMPARE( item->sizeWithUnits().height(), 15.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutMillimeters ); + item->attemptResize( QgsLayoutSize( 50.0, 100.0, QgsUnitTypes::LayoutPixels ) ); + QCOMPARE( item->sizeWithUnits().width(), 50.0 ); + QCOMPARE( item->sizeWithUnits().height(), 100.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutPixels ); + + delete item; +} + +void TestQgsLayoutItem::dataDefinedPosition() +{ + QgsProject p; + QgsLayout l( &p ); + + //test setting data defined position + TestItem *item = new TestItem( &l ); + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); + item->attemptResize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) ); + + // position x + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+7" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::PositionX ); + QCOMPARE( item->positionWithUnits().x(), 11.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 110.0 ); //mm + + //position y + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+3" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::PositionY ); + QCOMPARE( item->positionWithUnits().y(), 5.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().y(), 50.0 ); //mm + + //refreshPosition should also respect data defined positioning + item->setPos( 0, 0 ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + item->refreshItemPosition(); + QCOMPARE( item->positionWithUnits().x(), 12.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 120.0 ); //mm + QCOMPARE( item->pos().y(), 60.0 ); //mm + + //also check that data defined position overrides when attempting to move + item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); + QCOMPARE( item->positionWithUnits().x(), 12.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 120.0 ); //mm + QCOMPARE( item->pos().y(), 60.0 ); //mm + //restriction only for x position + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty() ); + item->attemptMove( QgsLayoutPoint( 6.0, 1.5, QgsUnitTypes::LayoutCentimeters ) ); + QCOMPARE( item->positionWithUnits().x(), 12.0 ); + QCOMPARE( item->positionWithUnits().y(), 1.5 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 120.0 ); //mm + QCOMPARE( item->pos().y(), 15.0 ); //mm + //restriction only for y position + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty() ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + item->attemptMove( QgsLayoutPoint( 7.0, 1.5, QgsUnitTypes::LayoutCentimeters ) ); + QCOMPARE( item->positionWithUnits().x(), 7.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 70.0 ); //mm + QCOMPARE( item->pos().y(), 60.0 ); //mm + + //check change of units should apply to data defined position + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + //first set to same as existing position, but with different units + item->attemptMove( QgsLayoutPoint( 120.0, 60.0, QgsUnitTypes::LayoutMillimeters ) ); + //data defined position should utilize new units + QCOMPARE( item->positionWithUnits().x(), 12.0 ); //mm + QCOMPARE( item->positionWithUnits().y(), 6.0 ); //mm + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutMillimeters ); + QCOMPARE( item->pos().x(), 12.0 ); //mm + QCOMPARE( item->pos().y(), 6.0 ); //mm + + //test that data defined position applies to item's reference point + item->attemptMove( QgsLayoutPoint( 12.0, 6.0, QgsUnitTypes::LayoutCentimeters ) ); + item->setReferencePoint( QgsLayoutItem::LowerRight ); + QCOMPARE( item->positionWithUnits().x(), 12.0 ); //cm + QCOMPARE( item->positionWithUnits().y(), 6.0 ); //cm + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 100.0 ); //mm + QCOMPARE( item->pos().y(), 20.0 ); //mm + + //also check setting data defined position AFTER setting reference point + item->setPos( 0, 0 ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "6+10" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+6" ) ) ); + item->refreshItemPosition(); + QCOMPARE( item->positionWithUnits().x(), 16.0 ); //cm + QCOMPARE( item->positionWithUnits().y(), 8.0 ); //cm + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 140.0 ); //mm + QCOMPARE( item->pos().y(), 40.0 ); //mm + + delete item; +} + +void TestQgsLayoutItem::dataDefinedSize() +{ + QgsProject p; + QgsLayout l( &p ); + + //test setting data defined size + TestItem *item = new TestItem( &l ); + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); + item->attemptResize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) ); + + //width + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+7" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::ItemWidth ); + QCOMPARE( item->sizeWithUnits().width(), 11.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->rect().width(), 110.0 ); //mm + + //height + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+3" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::ItemHeight ); + QCOMPARE( item->sizeWithUnits().height(), 5.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->rect().height(), 50.0 ); //mm + + //refreshSize should also respect data defined size + item->setRect( 0.0, 0.0, 9.0, 8.0 ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + item->refreshItemSize(); + QCOMPARE( item->sizeWithUnits().width(), 12.0 ); + QCOMPARE( item->sizeWithUnits().height(), 6.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->rect().width(), 120.0 ); //mm + QCOMPARE( item->rect().height(), 60.0 ); //mm + + //also check that data defined size overrides when attempting to resize + item->attemptResize( QgsLayoutSize( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); + QCOMPARE( item->sizeWithUnits().width(), 12.0 ); + QCOMPARE( item->sizeWithUnits().height(), 6.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->rect().width(), 120.0 ); //mm + QCOMPARE( item->rect().height(), 60.0 ); //mm + //restriction only for width + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() ); + item->attemptResize( QgsLayoutSize( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); + QCOMPARE( item->sizeWithUnits().width(), 12.0 ); + QCOMPARE( item->sizeWithUnits().height(), 1.5 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->rect().width(), 120.0 ); //mm + QCOMPARE( item->rect().height(), 15.0 ); //mm + //restriction only for y position + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + item->attemptResize( QgsLayoutSize( 7.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); + QCOMPARE( item->sizeWithUnits().width(), 7.0 ); + QCOMPARE( item->sizeWithUnits().height(), 6.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->rect().width(), 70.0 ); //mm + QCOMPARE( item->rect().height(), 60.0 ); //mm + + //check change of units should apply to data defined size + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + //first set to same as existing size, but with different units + item->attemptResize( QgsLayoutSize( 120.0, 60.0, QgsUnitTypes::LayoutMillimeters ) ); + //data defined size should utilize new units + QCOMPARE( item->sizeWithUnits().width(), 12.0 ); //mm + QCOMPARE( item->sizeWithUnits().height(), 6.0 ); //mm + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutMillimeters ); + QCOMPARE( item->rect().width(), 12.0 ); //mm + QCOMPARE( item->rect().height(), 6.0 ); //mm + + //test that data defined size applies to item's reference point + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() ); + item->attemptResize( QgsLayoutSize( 10.0, 5.0, QgsUnitTypes::LayoutMillimeters ) ); + item->attemptMove( QgsLayoutPoint( 20.0, 10.0, QgsUnitTypes::LayoutMillimeters ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "5" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "6" ) ) ); + item->setReferencePoint( QgsLayoutItem::LowerRight ); + item->refreshItemSize(); + QCOMPARE( item->pos().x(), 25.0 ); //mm + QCOMPARE( item->pos().y(), 9.0 ); //mm + + //test that data defined size applied after setting item's reference point respects reference + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() ); + item->setReferencePoint( QgsLayoutItem::UpperLeft ); + item->attemptResize( QgsLayoutSize( 10.0, 5.0, QgsUnitTypes::LayoutMillimeters ) ); + item->attemptMove( QgsLayoutPoint( 20.0, 10.0, QgsUnitTypes::LayoutMillimeters ) ); + item->setReferencePoint( QgsLayoutItem::LowerRight ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "7" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "9" ) ) ); + item->refreshItemSize(); + QCOMPARE( item->pos().x(), 23.0 ); //mm + QCOMPARE( item->pos().y(), 6.0 ); //mm + + delete item; +} + +void TestQgsLayoutItem::combinedDataDefinedPositionAndSize() +{ + QgsProject p; + QgsLayout l( &p ); + + //test setting data defined size + TestItem *item = new TestItem( &l ); + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); + item->attemptResize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) ); + + //test item with all of data defined x, y, width, height set + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+7" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+3" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+9" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::AllProperties ); + QCOMPARE( item->positionWithUnits().x(), 11.0 ); + QCOMPARE( item->positionWithUnits().y(), 5.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->sizeWithUnits().width(), 13.0 ); + QCOMPARE( item->sizeWithUnits().height(), 6.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 110.0 ); //mm + QCOMPARE( item->pos().y(), 50.0 ); //mm + QCOMPARE( item->rect().width(), 130.0 ); //mm + QCOMPARE( item->rect().height(), 60.0 ); //mm + + //also try with reference point set + item->setReferencePoint( QgsLayoutItem::Middle ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "3+7" ) ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "1+3" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::AllProperties ); + QCOMPARE( item->positionWithUnits().x(), 12.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->sizeWithUnits().width(), 10.0 ); + QCOMPARE( item->sizeWithUnits().height(), 4.0 ); + QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); + QCOMPARE( item->pos().x(), 70.0 ); //mm + QCOMPARE( item->pos().y(), 40.0 ); //mm + QCOMPARE( item->rect().width(), 100.0 ); //mm + QCOMPARE( item->rect().height(), 40.0 ); //mm + + delete item; +} + +//TODO rotation + void TestQgsLayoutItem::resize() { QgsProject p; From c3456133dc124362b3e94aee9220eeb12288b798 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Jul 2017 14:02:51 +1000 Subject: [PATCH 025/266] Add QgsLayoutUtils --- python/core/core_auto.sip | 5 +- python/core/layout/qgslayoututils.sip | 39 +++++++++ src/core/CMakeLists.txt | 2 + src/core/layout/qgslayoutitem.cpp | 1 + src/core/layout/qgslayoututils.cpp | 33 ++++++++ src/core/layout/qgslayoututils.h | 40 +++++++++ tests/src/core/CMakeLists.txt | 1 + tests/src/core/testqgslayoututils.cpp | 116 ++++++++++++++++++++++++++ 8 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 python/core/layout/qgslayoututils.sip create mode 100644 src/core/layout/qgslayoututils.cpp create mode 100644 src/core/layout/qgslayoututils.h create mode 100644 tests/src/core/testqgslayoututils.cpp diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 8af0c5dfa5b..e958891c4a9 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -153,12 +153,13 @@ %Include composer/qgscomposermultiframecommand.sip %Include composer/qgscomposertexttable.sip %Include composer/qgspaperitem.sip -%Include layout/qgslayout.sip +%Include layout/qgslayoutcontext.sip %Include layout/qgslayoutmeasurement.sip %Include layout/qgslayoutmeasurementconverter.sip %Include layout/qgspagesizeregistry.sip %Include layout/qgslayoutpoint.sip %Include layout/qgslayoutsize.sip +%Include layout/qgslayoututils.sip %Include metadata/qgslayermetadata.sip %Include metadata/qgslayermetadatavalidator.sip %Include processing/qgsprocessing.sip @@ -378,7 +379,7 @@ %Include gps/qgsgpsdetector.sip %Include gps/qgsnmeaconnection.sip %Include gps/qgsgpsdconnection.sip -%Include layout/qgslayoutcontext.sip +%Include layout/qgslayout.sip %Include layout/qgslayoutitem.sip %Include layout/qgslayoutitemregistry.sip %Include layout/qgslayoutobject.sip diff --git a/python/core/layout/qgslayoututils.sip b/python/core/layout/qgslayoututils.sip new file mode 100644 index 00000000000..a8a0346f88b --- /dev/null +++ b/python/core/layout/qgslayoututils.sip @@ -0,0 +1,39 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoututils.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsLayoutUtils +{ +%Docstring + Utilities for layouts. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoututils.h" +%End + public: + + static double normalizedAngle( const double angle, const bool allowNegative = false ); +%Docstring + Ensures that an ``angle`` (in degrees) is in the range 0 <= angle < 360. + If ``allowNegative`` is true then angles between (-360, 360) are allowed. If false, + angles are converted to positive angles in the range [0, 360). + :rtype: float +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoututils.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3f3e49ba55a..9c1ea975222 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -356,6 +356,7 @@ SET(QGIS_CORE_SRCS layout/qgslayoutmeasurement.cpp layout/qgslayoutmeasurementconverter.cpp layout/qgslayoutobject.cpp + layout/qgslayoututils.cpp layout/qgspagesizeregistry.cpp layout/qgslayoutpoint.cpp layout/qgslayoutsize.cpp @@ -923,6 +924,7 @@ SET(QGIS_CORE_HDRS layout/qgspagesizeregistry.h layout/qgslayoutpoint.h layout/qgslayoutsize.h + layout/qgslayoututils.h metadata/qgslayermetadata.h metadata/qgslayermetadatavalidator.h diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index b9217fad4dc..31f644f4703 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -16,6 +16,7 @@ #include "qgslayoutitem.h" #include "qgslayout.h" +#include "qgslayoututils.h" #include QgsLayoutItem::QgsLayoutItem( QgsLayout *layout ) diff --git a/src/core/layout/qgslayoututils.cpp b/src/core/layout/qgslayoututils.cpp new file mode 100644 index 00000000000..18ecc20ecd0 --- /dev/null +++ b/src/core/layout/qgslayoututils.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + qgslayoututils.cpp + ------------------ + begin : July 2017 + copyright : (C) 2017 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 "qgslayoututils.h" +#include + +double QgsLayoutUtils::normalizedAngle( const double angle, const bool allowNegative ) +{ + double clippedAngle = angle; + if ( clippedAngle >= 360.0 || clippedAngle <= -360.0 ) + { + clippedAngle = fmod( clippedAngle, 360.0 ); + } + if ( !allowNegative && clippedAngle < 0.0 ) + { + clippedAngle += 360.0; + } + return clippedAngle; +} diff --git a/src/core/layout/qgslayoututils.h b/src/core/layout/qgslayoututils.h new file mode 100644 index 00000000000..4cd822c03a4 --- /dev/null +++ b/src/core/layout/qgslayoututils.h @@ -0,0 +1,40 @@ +/*************************************************************************** + qgslayoututils.h + ------------------- + begin : July 2017 + copyright : (C) 2017 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 QGSLAYOUTUTILS_H +#define QGSLAYOUTUTILS_H + +#include "qgis_core.h" + +/** + * \ingroup core + * Utilities for layouts. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutUtils +{ + public: + + /** + * Ensures that an \a angle (in degrees) is in the range 0 <= angle < 360. + * If \a allowNegative is true then angles between (-360, 360) are allowed. If false, + * angles are converted to positive angles in the range [0, 360). + */ + static double normalizedAngle( const double angle, const bool allowNegative = false ); + +}; + +#endif //QGSLAYOUTUTILS_H diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 32c62b090ff..50c9693bc8a 100755 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -131,6 +131,7 @@ SET(TESTS testqgslayoutitem.cpp testqgslayoutobject.cpp testqgslayoutunits.cpp + testqgslayoututils.cpp testqgslegendrenderer.cpp testqgscentroidfillsymbol.cpp testqgslinefillsymbol.cpp diff --git a/tests/src/core/testqgslayoututils.cpp b/tests/src/core/testqgslayoututils.cpp new file mode 100644 index 00000000000..b2d69e15269 --- /dev/null +++ b/tests/src/core/testqgslayoututils.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** + testqgslayoututils.cpp + --------------------- + begin : July 2017 + copyright : (C) 2017 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 "qgslayout.h" +#include "qgstest.h" +#include "qgslayoututils.h" + +class TestQgsLayoutUtils: public QObject +{ + Q_OBJECT + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init();// will be called before each testfunction is executed. + void cleanup();// will be called after every testfunction. + void normalizedAngle(); //test normalised angle function + + private: + QString mReport; + +}; + +void TestQgsLayoutUtils::initTestCase() +{ + mReport = "

Layout Utils Tests

\n"; +} + +void TestQgsLayoutUtils::cleanupTestCase() +{ + QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html"; + QFile myFile( myReportFile ); + if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) ) + { + QTextStream myQTextStream( &myFile ); + myQTextStream << mReport; + myFile.close(); + } +} + +void TestQgsLayoutUtils::init() +{ + +} + +void TestQgsLayoutUtils::cleanup() +{ + +} + +void TestQgsLayoutUtils::normalizedAngle() +{ + QList< QPair< double, double > > testVals; + testVals << qMakePair( 0.0, 0.0 ); + testVals << qMakePair( 90.0, 90.0 ); + testVals << qMakePair( 180.0, 180.0 ); + testVals << qMakePair( 270.0, 270.0 ); + testVals << qMakePair( 360.0, 0.0 ); + testVals << qMakePair( 390.0, 30.0 ); + testVals << qMakePair( 720.0, 0.0 ); + testVals << qMakePair( 730.0, 10.0 ); + testVals << qMakePair( -10.0, 350.0 ); + testVals << qMakePair( -360.0, 0.0 ); + testVals << qMakePair( -370.0, 350.0 ); + testVals << qMakePair( -760.0, 320.0 ); + + //test normalized angle helper function + QList< QPair< double, double > >::const_iterator it = testVals.constBegin(); + for ( ; it != testVals.constEnd(); ++it ) + + { + double result = QgsLayoutUtils::normalizedAngle( ( *it ).first ); + qDebug() << QString( "actual: %1 expected: %2" ).arg( result ).arg( ( *it ).second ); + QVERIFY( qgsDoubleNear( result, ( *it ).second ) ); + + } + + //test with allowing negative angles + QList< QPair< double, double > > negativeTestVals; + negativeTestVals << qMakePair( 0.0, 0.0 ); + negativeTestVals << qMakePair( 90.0, 90.0 ); + negativeTestVals << qMakePair( 360.0, 0.0 ); + negativeTestVals << qMakePair( -10.0, -10.0 ); + negativeTestVals << qMakePair( -359.0, -359.0 ); + negativeTestVals << qMakePair( -360.0, 0.0 ); + negativeTestVals << qMakePair( -361.0, -1.0 ); + negativeTestVals << qMakePair( -370.0, -10.0 ); + negativeTestVals << qMakePair( -760.0, -40.0 ); + it = negativeTestVals.constBegin(); + for ( ; it != negativeTestVals.constEnd(); ++it ) + + { + double result = QgsLayoutUtils::normalizedAngle( ( *it ).first, true ); + qDebug() << QString( "actual: %1 expected: %2" ).arg( result ).arg( ( *it ).second ); + QVERIFY( qgsDoubleNear( result, ( *it ).second ) ); + + } +} + + +QGSTEST_MAIN( TestQgsLayoutUtils ) +#include "testqgslayoututils.moc" From 00405fd6b51009c51bad2354d8a1e39fc4853a6e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Jul 2017 14:40:42 +1000 Subject: [PATCH 026/266] Start porting item rotation --- python/core/layout/qgslayoutitem.sip | 39 +- src/core/layout/qgslayoutitem.cpp | 114 +++++- src/core/layout/qgslayoutitem.h | 45 ++- tests/src/core/testqgslayoutitem.cpp | 377 +++++++++++++----- .../expected_layoutitem_rotation.png | Bin 0 -> 2280 bytes 5 files changed, 460 insertions(+), 115 deletions(-) create mode 100644 tests/testdata/control_images/layouts/expected_layoutitem_rotation/expected_layoutitem_rotation.png diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 979c900dbd0..0e8c763457d 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -125,6 +125,11 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem :rtype: QgsLayoutSize %End + double itemRotation() const; +%Docstring + :rtype: float +%End + public slots: virtual void refresh(); @@ -142,6 +147,20 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem refreshed. %End + virtual void setItemRotation( const double rotation ); +%Docstring + Sets the layout item's ``rotation``, in degrees clockwise. This rotation occurs around the center of the item. +.. seealso:: itemRotation() +.. seealso:: rotateItem() +%End + + virtual void rotateItem( const double angle, const QPointF &transformOrigin ); +%Docstring + Rotates the item by a specified ``angle`` in degrees clockwise around a specified reference point. +.. seealso:: setItemRotation() +.. seealso:: itemRotation() +%End + protected: virtual void drawDebugRect( QPainter *painter ); @@ -188,10 +207,24 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem .. seealso:: refreshItemSize() %End - QPointF adjustPointForReferencePosition( const QPointF &point, const QSizeF &size ) const; + void refreshItemRotation(); %Docstring - Adjusts the specified ``point`` at which the reference position of the item - sits and returns the top left corner of the item. + Refreshes an item's rotation by rechecking it against any possible overrides + such as data defined rotation. +.. seealso:: refreshItemSize() +.. seealso:: refreshItemPosition() +%End + + QPointF adjustPointForReferencePosition( const QPointF &point, const QSizeF &size, const ReferencePoint &reference ) const; +%Docstring + Adjusts the specified ``point`` at which a ``reference`` position of the item + sits and returns the top left corner of the item, if reference point where placed at the specified position. + :rtype: QPointF +%End + + QPointF positionAtReferencePoint( const ReferencePoint &reference ) const; +%Docstring + Returns the current position (in layout units) of a ``reference`` point for the item. :rtype: QPointF %End diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 31f644f4703..74112ead3f7 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -66,8 +66,7 @@ void QgsLayoutItem::setReferencePoint( const QgsLayoutItem::ReferencePoint &poin mReferencePoint = point; //also need to adjust stored position - QPointF positionReferencePointLayoutUnits = adjustPointForReferencePosition( pos(), QSizeF( -rect().width(), -rect().height() ) ); - mItemPosition = mLayout->convertFromLayoutUnits( positionReferencePointLayoutUnits, mItemPosition.units() ); + updateStoredItemPosition(); refreshItemPosition(); } @@ -108,8 +107,8 @@ void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) QgsLayoutPoint evaluatedPoint = applyDataDefinedPosition( point ); QPointF evaluatedPointLayoutUnits = mLayout->convertToLayoutUnits( evaluatedPoint ); - QPointF topLeftPointLayoutUnits = adjustPointForReferencePosition( evaluatedPointLayoutUnits, rect().size() ); - if ( topLeftPointLayoutUnits == pos() && point.units() == mItemPosition.units() ) + QPointF topLeftPointLayoutUnits = adjustPointForReferencePosition( evaluatedPointLayoutUnits, rect().size(), mReferencePoint ); + if ( topLeftPointLayoutUnits == scenePos() && point.units() == mItemPosition.units() ) { //TODO - add test for second condition return; @@ -118,7 +117,20 @@ void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) QgsLayoutPoint referencePointTargetUnits = mLayout->convertFromLayoutUnits( evaluatedPointLayoutUnits, point.units() ); mItemPosition = referencePointTargetUnits; - setPos( topLeftPointLayoutUnits ); + setScenePos( topLeftPointLayoutUnits ); +} + +void QgsLayoutItem::setScenePos( const QPointF &destinationPos ) +{ + //since setPos does not account for item rotation, use difference between + //current scenePos (which DOES account for rotation) and destination pos + //to calculate how much the item needs to move + setPos( pos() + ( destinationPos - scenePos() ) ); +} + +double QgsLayoutItem::itemRotation() const +{ + return rotation(); } QgsLayoutPoint QgsLayoutItem::applyDataDefinedPosition( const QgsLayoutPoint &position ) @@ -147,6 +159,18 @@ QgsLayoutSize QgsLayoutItem::applyDataDefinedSize( const QgsLayoutSize &size ) return QgsLayoutSize( evaluatedWidth, evaluatedHeight, size.units() ); } +double QgsLayoutItem::applyDataDefinedRotation( const double rotation ) +{ + if ( !mLayout ) + { + return rotation; + } + + QgsExpressionContext context = createExpressionContext(); + double evaluatedRotation = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemRotation, context, rotation ); + return evaluatedRotation; +} + void QgsLayoutItem::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property ) { //update data defined properties and update item to match @@ -162,6 +186,44 @@ void QgsLayoutItem::refreshDataDefinedProperty( const QgsLayoutObject::DataDefin { refreshItemPosition(); } + if ( property == QgsLayoutObject::ItemRotation || property == QgsLayoutObject::AllProperties ) + { + refreshItemRotation(); + } +} + +void QgsLayoutItem::setItemRotation( const double angle ) +{ + QPointF itemCenter = positionAtReferencePoint( QgsLayoutItem::Middle ); + double rotationRequired = angle - itemRotation(); + rotateItem( rotationRequired, itemCenter ); +} + +void QgsLayoutItem::updateStoredItemPosition() +{ + QPointF layoutPosReferencePoint = positionAtReferencePoint( mReferencePoint ); + mItemPosition = mLayout->convertFromLayoutUnits( layoutPosReferencePoint, mItemPosition.units() ); +} + +void QgsLayoutItem::rotateItem( const double angle, const QPointF &transformOrigin ) +{ + double evaluatedAngle = angle + rotation(); + evaluatedAngle = QgsLayoutUtils::normalizedAngle( evaluatedAngle, true ); + evaluatedAngle = applyDataDefinedRotation( evaluatedAngle ); + + QPointF itemTransformOrigin = mapFromScene( transformOrigin ); + setTransformOriginPoint( itemTransformOrigin ); + setRotation( evaluatedAngle ); + + //adjust stored position of item to match scene pos of reference point + updateStoredItemPosition(); + + //TODO + // emit itemRotationChanged( rotation ); + + //TODO + //update bounds of scene, since rotation may affect this + //mLayout->updateBounds(); } @@ -210,31 +272,44 @@ void QgsLayoutItem::refreshItemPosition() attemptMove( mItemPosition ); } -QPointF QgsLayoutItem::adjustPointForReferencePosition( const QPointF &position, const QSizeF &size ) const +QPointF QgsLayoutItem::itemPositionAtReferencePoint( const ReferencePoint reference, const QSizeF &size ) const { - switch ( mReferencePoint ) + switch ( reference ) { case UpperMiddle: - return QPointF( position.x() - size.width() / 2.0, position.y() ); + return QPointF( size.width() / 2.0, 0 ); case UpperRight: - return QPointF( position.x() - size.width(), position.y() ); + return QPointF( size.width(), 0 ); case MiddleLeft: - return QPointF( position.x(), position.y() - size.height() / 2.0 ); + return QPointF( 0, size.height() / 2.0 ); case Middle: - return QPointF( position.x() - size.width() / 2.0, position.y() - size.height() / 2.0 ); + return QPointF( size.width() / 2.0, size.height() / 2.0 ); case MiddleRight: - return QPointF( position.x() - size.width(), position.y() - size.height() / 2.0 ); + return QPointF( size.width(), size.height() / 2.0 ); case LowerLeft: - return QPointF( position.x(), position.y() - size.height() ); + return QPointF( 0, size.height() ); case LowerMiddle: - return QPointF( position.x() - size.width() / 2.0, position.y() - size.height() ); + return QPointF( size.width() / 2.0, size.height() ); case LowerRight: - return QPointF( position.x() - size.width(), position.y() - size.height() ); + return QPointF( size.width(), size.height() ); case UpperLeft: - return position; + return QPointF( 0, 0 ); } // no warnings - return position; + return QPointF( 0, 0 ); +} + +QPointF QgsLayoutItem::adjustPointForReferencePosition( const QPointF &position, const QSizeF &size, const ReferencePoint &reference ) const +{ + QPointF itemPosition = mapFromScene( position ); //need to map from scene to handle item rotation + QPointF adjustedPointInsideItem = itemPosition - itemPositionAtReferencePoint( reference, size ); + return mapToScene( adjustedPointInsideItem ); +} + +QPointF QgsLayoutItem::positionAtReferencePoint( const QgsLayoutItem::ReferencePoint &reference ) const +{ + QPointF pointWithinItem = itemPositionAtReferencePoint( reference, rect().size() ); + return mapToScene( pointWithinItem ); } void QgsLayoutItem::initConnectionsToLayout() @@ -287,3 +362,8 @@ QSizeF QgsLayoutItem::applyFixedSize( const QSizeF &targetSize ) QSizeF fixedSizeLayoutUnits = mLayout->convertToLayoutUnits( fixedSize() ); return targetSize.expandedTo( fixedSizeLayoutUnits ); } + +void QgsLayoutItem::refreshItemRotation() +{ + setItemRotation( itemRotation() ); +} diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index c7109dd25e3..3c7f0640cef 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -138,6 +138,13 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ QgsLayoutSize sizeWithUnits() const { return mItemSize; } + /** + * Returns the current rotation for the item, in degrees clockwise. + * \see setItemRotation() + */ + //TODO + double itemRotation() const; + public slots: /** @@ -154,6 +161,20 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ); + /** + * Sets the layout item's \a rotation, in degrees clockwise. This rotation occurs around the center of the item. + * \see itemRotation() + * \see rotateItem() + */ + virtual void setItemRotation( const double rotation ); + + /** + * Rotates the item by a specified \a angle in degrees clockwise around a specified reference point. + * \see setItemRotation() + * \see itemRotation() + */ + virtual void rotateItem( const double angle, const QPointF &transformOrigin ); + protected: /** Draws a debugging rectangle of the item's current bounds within the specified @@ -200,10 +221,23 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt void refreshItemPosition(); /** - * Adjusts the specified \a point at which the reference position of the item - * sits and returns the top left corner of the item. + * Refreshes an item's rotation by rechecking it against any possible overrides + * such as data defined rotation. + * \see refreshItemSize() + * \see refreshItemPosition() */ - QPointF adjustPointForReferencePosition( const QPointF &point, const QSizeF &size ) const; + void refreshItemRotation(); + + /** + * Adjusts the specified \a point at which a \a reference position of the item + * sits and returns the top left corner of the item, if reference point where placed at the specified position. + */ + QPointF adjustPointForReferencePosition( const QPointF &point, const QSizeF &size, const ReferencePoint &reference ) const; + + /** + * Returns the current position (in layout units) of a \a reference point for the item. + */ + QPointF positionAtReferencePoint( const ReferencePoint &reference ) const; private: @@ -213,6 +247,7 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt QgsLayoutSize mItemSize; QgsLayoutPoint mItemPosition; + double mItemRotation = 0.0; void initConnectionsToLayout(); @@ -225,6 +260,10 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt QSizeF applyFixedSize( const QSizeF &targetSize ); QgsLayoutPoint applyDataDefinedPosition( const QgsLayoutPoint &position ); QgsLayoutSize applyDataDefinedSize( const QgsLayoutSize &size ); + double applyDataDefinedRotation( const double rotation ); + void updateStoredItemPosition(); + QPointF itemPositionAtReferencePoint( const ReferencePoint reference, const QSizeF &size ) const; + void setScenePos( const QPointF &destinationPos ); friend class TestQgsLayoutItem; }; diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 83a0be1827f..11442a26102 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -44,7 +44,9 @@ class TestQgsLayoutItem: public QObject void draw(); void resize(); void referencePoint(); + void itemPositionReferencePoint(); void adjustPointForReference(); + void positionAtReferencePoint(); void fixedSize(); void minSize(); void move(); @@ -53,6 +55,7 @@ class TestQgsLayoutItem: public QObject void dataDefinedPosition(); void dataDefinedSize(); void combinedDataDefinedPositionAndSize(); + void rotation(); private: @@ -372,14 +375,14 @@ void TestQgsLayoutItem::dataDefinedPosition() item->refreshDataDefinedProperty( QgsLayoutObject::PositionX ); QCOMPARE( item->positionWithUnits().x(), 11.0 ); QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 110.0 ); //mm + QCOMPARE( item->scenePos().x(), 110.0 ); //mm //position y item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+3" ) ) ); item->refreshDataDefinedProperty( QgsLayoutObject::PositionY ); QCOMPARE( item->positionWithUnits().y(), 5.0 ); QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().y(), 50.0 ); //mm + QCOMPARE( item->scenePos().y(), 50.0 ); //mm //refreshPosition should also respect data defined positioning item->setPos( 0, 0 ); @@ -389,16 +392,16 @@ void TestQgsLayoutItem::dataDefinedPosition() QCOMPARE( item->positionWithUnits().x(), 12.0 ); QCOMPARE( item->positionWithUnits().y(), 6.0 ); QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 120.0 ); //mm - QCOMPARE( item->pos().y(), 60.0 ); //mm + QCOMPARE( item->scenePos().x(), 120.0 ); //mm + QCOMPARE( item->scenePos().y(), 60.0 ); //mm //also check that data defined position overrides when attempting to move item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) ); QCOMPARE( item->positionWithUnits().x(), 12.0 ); QCOMPARE( item->positionWithUnits().y(), 6.0 ); QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 120.0 ); //mm - QCOMPARE( item->pos().y(), 60.0 ); //mm + QCOMPARE( item->scenePos().x(), 120.0 ); //mm + QCOMPARE( item->scenePos().y(), 60.0 ); //mm //restriction only for x position item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty() ); @@ -406,8 +409,8 @@ void TestQgsLayoutItem::dataDefinedPosition() QCOMPARE( item->positionWithUnits().x(), 12.0 ); QCOMPARE( item->positionWithUnits().y(), 1.5 ); QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 120.0 ); //mm - QCOMPARE( item->pos().y(), 15.0 ); //mm + QCOMPARE( item->scenePos().x(), 120.0 ); //mm + QCOMPARE( item->scenePos().y(), 15.0 ); //mm //restriction only for y position item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty() ); item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) ); @@ -415,8 +418,8 @@ void TestQgsLayoutItem::dataDefinedPosition() QCOMPARE( item->positionWithUnits().x(), 7.0 ); QCOMPARE( item->positionWithUnits().y(), 6.0 ); QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 70.0 ); //mm - QCOMPARE( item->pos().y(), 60.0 ); //mm + QCOMPARE( item->scenePos().x(), 70.0 ); //mm + QCOMPARE( item->scenePos().y(), 60.0 ); //mm //check change of units should apply to data defined position item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) ); @@ -427,8 +430,8 @@ void TestQgsLayoutItem::dataDefinedPosition() QCOMPARE( item->positionWithUnits().x(), 12.0 ); //mm QCOMPARE( item->positionWithUnits().y(), 6.0 ); //mm QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutMillimeters ); - QCOMPARE( item->pos().x(), 12.0 ); //mm - QCOMPARE( item->pos().y(), 6.0 ); //mm + QCOMPARE( item->scenePos().x(), 12.0 ); //mm + QCOMPARE( item->scenePos().y(), 6.0 ); //mm //test that data defined position applies to item's reference point item->attemptMove( QgsLayoutPoint( 12.0, 6.0, QgsUnitTypes::LayoutCentimeters ) ); @@ -436,8 +439,8 @@ void TestQgsLayoutItem::dataDefinedPosition() QCOMPARE( item->positionWithUnits().x(), 12.0 ); //cm QCOMPARE( item->positionWithUnits().y(), 6.0 ); //cm QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 100.0 ); //mm - QCOMPARE( item->pos().y(), 20.0 ); //mm + QCOMPARE( item->scenePos().x(), 100.0 ); //mm + QCOMPARE( item->scenePos().y(), 20.0 ); //mm //also check setting data defined position AFTER setting reference point item->setPos( 0, 0 ); @@ -447,8 +450,8 @@ void TestQgsLayoutItem::dataDefinedPosition() QCOMPARE( item->positionWithUnits().x(), 16.0 ); //cm QCOMPARE( item->positionWithUnits().y(), 8.0 ); //cm QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 140.0 ); //mm - QCOMPARE( item->pos().y(), 40.0 ); //mm + QCOMPARE( item->scenePos().x(), 140.0 ); //mm + QCOMPARE( item->scenePos().y(), 40.0 ); //mm delete item; } @@ -536,8 +539,8 @@ void TestQgsLayoutItem::dataDefinedSize() item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "6" ) ) ); item->setReferencePoint( QgsLayoutItem::LowerRight ); item->refreshItemSize(); - QCOMPARE( item->pos().x(), 25.0 ); //mm - QCOMPARE( item->pos().y(), 9.0 ); //mm + QCOMPARE( item->scenePos().x(), 25.0 ); //mm + QCOMPARE( item->scenePos().y(), 9.0 ); //mm //test that data defined size applied after setting item's reference point respects reference item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() ); @@ -549,8 +552,8 @@ void TestQgsLayoutItem::dataDefinedSize() item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "7" ) ) ); item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "9" ) ) ); item->refreshItemSize(); - QCOMPARE( item->pos().x(), 23.0 ); //mm - QCOMPARE( item->pos().y(), 6.0 ); //mm + QCOMPARE( item->scenePos().x(), 23.0 ); //mm + QCOMPARE( item->scenePos().y(), 6.0 ); //mm delete item; } @@ -578,8 +581,8 @@ void TestQgsLayoutItem::combinedDataDefinedPositionAndSize() QCOMPARE( item->sizeWithUnits().width(), 13.0 ); QCOMPARE( item->sizeWithUnits().height(), 6.0 ); QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 110.0 ); //mm - QCOMPARE( item->pos().y(), 50.0 ); //mm + QCOMPARE( item->scenePos().x(), 110.0 ); //mm + QCOMPARE( item->scenePos().y(), 50.0 ); //mm QCOMPARE( item->rect().width(), 130.0 ); //mm QCOMPARE( item->rect().height(), 60.0 ); //mm @@ -596,8 +599,8 @@ void TestQgsLayoutItem::combinedDataDefinedPositionAndSize() QCOMPARE( item->sizeWithUnits().width(), 10.0 ); QCOMPARE( item->sizeWithUnits().height(), 4.0 ); QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters ); - QCOMPARE( item->pos().x(), 70.0 ); //mm - QCOMPARE( item->pos().y(), 40.0 ); //mm + QCOMPARE( item->scenePos().x(), 70.0 ); //mm + QCOMPARE( item->scenePos().y(), 40.0 ); //mm QCOMPARE( item->rect().width(), 100.0 ); //mm QCOMPARE( item->rect().height(), 40.0 ); //mm @@ -619,8 +622,8 @@ void TestQgsLayoutItem::resize() item->attemptResize( QgsLayoutSize( 100.0, 200.0, QgsUnitTypes::LayoutMillimeters ) ); QCOMPARE( item->rect().width(), 100.0 ); QCOMPARE( item->rect().height(), 200.0 ); - QCOMPARE( item->pos().x(), 27.0 ); //item should not move - QCOMPARE( item->pos().y(), 29.0 ); + QCOMPARE( item->scenePos().x(), 27.0 ); //item should not move + QCOMPARE( item->scenePos().y(), 29.0 ); //test conversion of units l.setUnits( QgsUnitTypes::LayoutCentimeters ); @@ -713,40 +716,40 @@ void TestQgsLayoutItem::referencePoint() item->attemptResize( QgsLayoutSize( 2, 4 ) ); item->setReferencePoint( QgsLayoutItem::UpperLeft ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), 1.0 ); - QCOMPARE( item->pos().y(), 2.0 ); + QCOMPARE( item->scenePos().x(), 1.0 ); + QCOMPARE( item->scenePos().y(), 2.0 ); item->setReferencePoint( QgsLayoutItem::UpperMiddle ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), 0.0 ); - QCOMPARE( item->pos().y(), 2.0 ); + QCOMPARE( item->scenePos().x(), 0.0 ); + QCOMPARE( item->scenePos().y(), 2.0 ); item->setReferencePoint( QgsLayoutItem::UpperRight ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), -1.0 ); - QCOMPARE( item->pos().y(), 2.0 ); + QCOMPARE( item->scenePos().x(), -1.0 ); + QCOMPARE( item->scenePos().y(), 2.0 ); item->setReferencePoint( QgsLayoutItem::MiddleLeft ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), 1.0 ); - QCOMPARE( item->pos().y(), 0.0 ); + QCOMPARE( item->scenePos().x(), 1.0 ); + QCOMPARE( item->scenePos().y(), 0.0 ); item->setReferencePoint( QgsLayoutItem::Middle ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), 0.0 ); - QCOMPARE( item->pos().y(), 0.0 ); + QCOMPARE( item->scenePos().x(), 0.0 ); + QCOMPARE( item->scenePos().y(), 0.0 ); item->setReferencePoint( QgsLayoutItem::MiddleRight ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), -1.0 ); - QCOMPARE( item->pos().y(), 0.0 ); + QCOMPARE( item->scenePos().x(), -1.0 ); + QCOMPARE( item->scenePos().y(), 0.0 ); item->setReferencePoint( QgsLayoutItem::LowerLeft ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), 1.0 ); - QCOMPARE( item->pos().y(), -2.0 ); + QCOMPARE( item->scenePos().x(), 1.0 ); + QCOMPARE( item->scenePos().y(), -2.0 ); item->setReferencePoint( QgsLayoutItem::LowerMiddle ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), 0.0 ); - QCOMPARE( item->pos().y(), -2.0 ); + QCOMPARE( item->scenePos().x(), 0.0 ); + QCOMPARE( item->scenePos().y(), -2.0 ); item->setReferencePoint( QgsLayoutItem::LowerRight ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); - QCOMPARE( item->pos().x(), -1.0 ); - QCOMPARE( item->pos().y(), -2.0 ); + QCOMPARE( item->scenePos().x(), -1.0 ); + QCOMPARE( item->scenePos().y(), -2.0 ); delete item; item = new TestItem( &l ); @@ -756,40 +759,77 @@ void TestQgsLayoutItem::referencePoint() item->setReferencePoint( QgsLayoutItem::UpperLeft ); item->attemptMove( QgsLayoutPoint( 1, 2 ) ); item->attemptResize( QgsLayoutSize( 4, 6 ) ); - QCOMPARE( item->pos().x(), 1.0 ); - QCOMPARE( item->pos().y(), 2.0 ); + QCOMPARE( item->scenePos().x(), 1.0 ); + QCOMPARE( item->scenePos().y(), 2.0 ); item->setReferencePoint( QgsLayoutItem::UpperMiddle ); item->attemptResize( QgsLayoutSize( 6, 4 ) ); - QCOMPARE( item->pos().x(), 0.0 ); - QCOMPARE( item->pos().y(), 2.0 ); + QCOMPARE( item->scenePos().x(), 0.0 ); + QCOMPARE( item->scenePos().y(), 2.0 ); item->setReferencePoint( QgsLayoutItem::UpperRight ); item->attemptResize( QgsLayoutSize( 4, 6 ) ); - QCOMPARE( item->pos().x(), 2.0 ); - QCOMPARE( item->pos().y(), 2.0 ); + QCOMPARE( item->scenePos().x(), 2.0 ); + QCOMPARE( item->scenePos().y(), 2.0 ); item->setReferencePoint( QgsLayoutItem::MiddleLeft ); item->attemptResize( QgsLayoutSize( 6, 4 ) ); - QCOMPARE( item->pos().x(), 2.0 ); - QCOMPARE( item->pos().y(), 3.0 ); + QCOMPARE( item->scenePos().x(), 2.0 ); + QCOMPARE( item->scenePos().y(), 3.0 ); item->setReferencePoint( QgsLayoutItem::Middle ); item->attemptResize( QgsLayoutSize( 4, 6 ) ); - QCOMPARE( item->pos().x(), 3.0 ); - QCOMPARE( item->pos().y(), 2.0 ); + QCOMPARE( item->scenePos().x(), 3.0 ); + QCOMPARE( item->scenePos().y(), 2.0 ); item->setReferencePoint( QgsLayoutItem::MiddleRight ); item->attemptResize( QgsLayoutSize( 6, 4 ) ); - QCOMPARE( item->pos().x(), 1.0 ); - QCOMPARE( item->pos().y(), 3.0 ); + QCOMPARE( item->scenePos().x(), 1.0 ); + QCOMPARE( item->scenePos().y(), 3.0 ); item->setReferencePoint( QgsLayoutItem::LowerLeft ); item->attemptResize( QgsLayoutSize( 4, 6 ) ); - QCOMPARE( item->pos().x(), 1.0 ); - QCOMPARE( item->pos().y(), 1.0 ); + QCOMPARE( item->scenePos().x(), 1.0 ); + QCOMPARE( item->scenePos().y(), 1.0 ); item->setReferencePoint( QgsLayoutItem::LowerMiddle ); item->attemptResize( QgsLayoutSize( 6, 4 ) ); - QCOMPARE( item->pos().x(), 0.0 ); - QCOMPARE( item->pos().y(), 3.0 ); + QCOMPARE( item->scenePos().x(), 0.0 ); + QCOMPARE( item->scenePos().y(), 3.0 ); item->setReferencePoint( QgsLayoutItem::LowerRight ); item->attemptResize( QgsLayoutSize( 4, 6 ) ); - QCOMPARE( item->pos().x(), 2.0 ); - QCOMPARE( item->pos().y(), 1.0 ); + QCOMPARE( item->scenePos().x(), 2.0 ); + QCOMPARE( item->scenePos().y(), 1.0 ); +} + +void TestQgsLayoutItem::itemPositionReferencePoint() +{ + QgsProject p; + QgsLayout l( &p ); + + TestItem *item = new TestItem( &l ); + QPointF result = item->itemPositionAtReferencePoint( QgsLayoutItem::UpperLeft, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 0.0 ); + QCOMPARE( result.y(), 0.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::UpperMiddle, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 1.0 ); + QCOMPARE( result.y(), 0.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::UpperRight, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 2.0 ); + QCOMPARE( result.y(), 0.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::MiddleLeft, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 0.0 ); + QCOMPARE( result.y(), 2.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::Middle, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 1.0 ); + QCOMPARE( result.y(), 2.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::MiddleRight, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 2.0 ); + QCOMPARE( result.y(), 2.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::LowerLeft, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 0.0 ); + QCOMPARE( result.y(), 4.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::LowerMiddle, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 1.0 ); + QCOMPARE( result.y(), 4.0 ); + result = item->itemPositionAtReferencePoint( QgsLayoutItem::LowerRight, QSizeF( 2, 4 ) ); + QCOMPARE( result.x(), 2.0 ); + QCOMPARE( result.y(), 4.0 ); + + delete item; } void TestQgsLayoutItem::adjustPointForReference() @@ -798,44 +838,104 @@ void TestQgsLayoutItem::adjustPointForReference() QgsLayout l( &p ); TestItem *item = new TestItem( &l ); - item->setReferencePoint( QgsLayoutItem::UpperLeft ); - QPointF result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + QPointF result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::UpperLeft ); QCOMPARE( result.x(), 5.0 ); QCOMPARE( result.y(), 7.0 ); - item->setReferencePoint( QgsLayoutItem::UpperMiddle ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::UpperMiddle ); QCOMPARE( result.x(), 4.0 ); QCOMPARE( result.y(), 7.0 ); - item->setReferencePoint( QgsLayoutItem::UpperRight ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::UpperRight ); QCOMPARE( result.x(), 3.0 ); QCOMPARE( result.y(), 7.0 ); - item->setReferencePoint( QgsLayoutItem::MiddleLeft ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::MiddleLeft ); QCOMPARE( result.x(), 5.0 ); QCOMPARE( result.y(), 5.0 ); - item->setReferencePoint( QgsLayoutItem::Middle ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::Middle ); QCOMPARE( result.x(), 4.0 ); QCOMPARE( result.y(), 5.0 ); - item->setReferencePoint( QgsLayoutItem::MiddleRight ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::MiddleRight ); QCOMPARE( result.x(), 3.0 ); QCOMPARE( result.y(), 5.0 ); - item->setReferencePoint( QgsLayoutItem::LowerLeft ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::LowerLeft ); QCOMPARE( result.x(), 5.0 ); QCOMPARE( result.y(), 3.0 ); - item->setReferencePoint( QgsLayoutItem::LowerMiddle ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::LowerMiddle ); QCOMPARE( result.x(), 4.0 ); QCOMPARE( result.y(), 3.0 ); - item->setReferencePoint( QgsLayoutItem::LowerRight ); - result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ) ); + result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::LowerRight ); QCOMPARE( result.x(), 3.0 ); QCOMPARE( result.y(), 3.0 ); } +void TestQgsLayoutItem::positionAtReferencePoint() +{ + QgsProject p; + QgsLayout l( &p ); + + TestItem *item = new TestItem( &l ); + item->setPos( 8.0, 6.0 ); + item->setRect( 0.0, 0.0, 4.0, 6.0 ); + QPointF result = item->positionAtReferencePoint( QgsLayoutItem::UpperLeft ); + QCOMPARE( result.x(), 8.0 ); + QCOMPARE( result.y(), 6.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::UpperMiddle ); + QCOMPARE( result.x(), 10.0 ); + QCOMPARE( result.y(), 6.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::UpperRight ); + QCOMPARE( result.x(), 12.0 ); + QCOMPARE( result.y(), 6.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::MiddleLeft ); + QCOMPARE( result.x(), 8.0 ); + QCOMPARE( result.y(), 9.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::Middle ); + QCOMPARE( result.x(), 10.0 ); + QCOMPARE( result.y(), 9.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::MiddleRight ); + QCOMPARE( result.x(), 12.0 ); + QCOMPARE( result.y(), 9.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::LowerLeft ); + QCOMPARE( result.x(), 8.0 ); + QCOMPARE( result.y(), 12.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::LowerMiddle ); + QCOMPARE( result.x(), 10.0 ); + QCOMPARE( result.y(), 12.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::LowerRight ); + QCOMPARE( result.x(), 12.0 ); + QCOMPARE( result.y(), 12.0 ); + + //test with a rotated item + item->setItemRotation( 90 ); + result = item->positionAtReferencePoint( QgsLayoutItem::UpperLeft ); + QCOMPARE( result.x(), 13.0 ); + QCOMPARE( result.y(), 7.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::UpperMiddle ); + QCOMPARE( result.x(), 13.0 ); + QCOMPARE( result.y(), 9.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::UpperRight ); + QCOMPARE( result.x(), 13.0 ); + QCOMPARE( result.y(), 11.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::MiddleLeft ); + QCOMPARE( result.x(), 10.0 ); + QCOMPARE( result.y(), 7.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::Middle ); + QCOMPARE( result.x(), 10.0 ); + QCOMPARE( result.y(), 9.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::MiddleRight ); + QCOMPARE( result.x(), 10.0 ); + QCOMPARE( result.y(), 11.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::LowerLeft ); + QCOMPARE( result.x(), 7.0 ); + QCOMPARE( result.y(), 7.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::LowerMiddle ); + QCOMPARE( result.x(), 7.0 ); + QCOMPARE( result.y(), 9.0 ); + result = item->positionAtReferencePoint( QgsLayoutItem::LowerRight ); + QCOMPARE( result.x(), 7.0 ); + QCOMPARE( result.y(), 11.0 ); + + delete item; +} + void TestQgsLayoutItem::fixedSize() { QgsProject p; @@ -915,15 +1015,15 @@ void TestQgsLayoutItem::move() item->attemptMove( QgsLayoutPoint( 60.0, 15.0, QgsUnitTypes::LayoutMillimeters ) ); QCOMPARE( item->rect().width(), 55.0 ); //size should not change QCOMPARE( item->rect().height(), 45.0 ); - QCOMPARE( item->pos().x(), 60.0 ); - QCOMPARE( item->pos().y(), 15.0 ); + QCOMPARE( item->scenePos().x(), 60.0 ); + QCOMPARE( item->scenePos().y(), 15.0 ); //test conversion of units l.setUnits( QgsUnitTypes::LayoutCentimeters ); item->setPos( 100, 200 ); item->attemptMove( QgsLayoutPoint( 0.30, 0.45, QgsUnitTypes::LayoutMeters ) ); - QCOMPARE( item->pos().x(), 30.0 ); - QCOMPARE( item->pos().y(), 45.0 ); + QCOMPARE( item->scenePos().x(), 30.0 ); + QCOMPARE( item->scenePos().y(), 45.0 ); //test pixel -> page conversion l.setUnits( QgsUnitTypes::LayoutInches ); @@ -931,13 +1031,13 @@ void TestQgsLayoutItem::move() item->refresh(); item->setPos( 1, 2 ); item->attemptMove( QgsLayoutPoint( 140, 280, QgsUnitTypes::LayoutPixels ) ); - QCOMPARE( item->pos().x(), 1.4 ); - QCOMPARE( item->pos().y(), 2.8 ); + QCOMPARE( item->scenePos().x(), 1.4 ); + QCOMPARE( item->scenePos().y(), 2.8 ); //changing the dpi should move the item l.context().setDpi( 200.0 ); item->refresh(); - QCOMPARE( item->pos().x(), 0.7 ); - QCOMPARE( item->pos().y(), 1.4 ); + QCOMPARE( item->scenePos().x(), 0.7 ); + QCOMPARE( item->scenePos().y(), 1.4 ); //test page -> pixel conversion l.setUnits( QgsUnitTypes::LayoutPixels ); @@ -945,18 +1045,111 @@ void TestQgsLayoutItem::move() item->refresh(); item->setPos( 2, 2 ); item->attemptMove( QgsLayoutPoint( 1, 3, QgsUnitTypes::LayoutInches ) ); - QCOMPARE( item->pos().x(), 100.0 ); - QCOMPARE( item->pos().y(), 300.0 ); + QCOMPARE( item->scenePos().x(), 100.0 ); + QCOMPARE( item->scenePos().y(), 300.0 ); //changing dpi results in item move l.context().setDpi( 200.0 ); item->refresh(); - QCOMPARE( item->pos().x(), 200.0 ); - QCOMPARE( item->pos().y(), 600.0 ); + QCOMPARE( item->scenePos().x(), 200.0 ); + QCOMPARE( item->scenePos().y(), 600.0 ); l.setUnits( QgsUnitTypes::LayoutMillimeters ); //TODO - reference points } +void TestQgsLayoutItem::rotation() +{ + QgsProject proj; + QgsLayout l( &proj ); + + TestItem *item = new TestItem( &l ); + l.setUnits( QgsUnitTypes::LayoutMillimeters ); + item->setPos( 6.0, 10.0 ); + item->setRect( 0.0, 0.0, 10.0, 8.0 ); + item->setPen( Qt::NoPen ); + QRectF bounds = item->sceneBoundingRect(); + QCOMPARE( bounds.left(), 6.0 ); + QCOMPARE( bounds.right(), 16.0 ); + QCOMPARE( bounds.top(), 10.0 ); + QCOMPARE( bounds.bottom(), 18.0 ); + + item->setItemRotation( 90.0 ); + QCOMPARE( item->itemRotation(), 90.0 ); + QCOMPARE( item->rotation(), 90.0 ); + bounds = item->sceneBoundingRect(); + QCOMPARE( bounds.left(), 7.0 ); + QCOMPARE( bounds.right(), 15.0 ); + QCOMPARE( bounds.top(), 9.0 ); + QCOMPARE( bounds.bottom(), 19.0 ); + + //check that negative angles are preserved as negative + item->setItemRotation( -90.0 ); + QCOMPARE( item->itemRotation(), -90.0 ); + QCOMPARE( item->rotation(), -90.0 ); + bounds = item->sceneBoundingRect(); + QCOMPARE( bounds.width(), 8.0 ); + QCOMPARE( bounds.height(), 10.0 ); + + //check that rotating changes stored item position for reference point + item->setItemRotation( 0.0 ); + item->attemptMove( QgsLayoutPoint( 5.0, 8.0 ) ); + item->attemptResize( QgsLayoutSize( 10.0, 6.0 ) ); + item->setItemRotation( 90.0 ); + QCOMPARE( item->positionWithUnits().x(), 13.0 ); + QCOMPARE( item->positionWithUnits().y(), 6.0 ); + + //setting item position (for reference point) respects rotation + item->attemptMove( QgsLayoutPoint( 10.0, 8.0 ) ); + QCOMPARE( item->scenePos().x(), 10.0 ); + QCOMPARE( item->scenePos().y(), 8.0 ); + QRectF p = item->sceneBoundingRect(); + qDebug() << p.left(); + QCOMPARE( item->sceneBoundingRect().left(), 4.0 ); + QCOMPARE( item->sceneBoundingRect().right(), 10.0 ); + QCOMPARE( item->sceneBoundingRect().top(), 8.0 ); + QCOMPARE( item->sceneBoundingRect().bottom(), 18.0 ); + + //TODO also changing size? + + + //data defined rotation + item->setItemRotation( 0.0 ); + item->attemptMove( QgsLayoutPoint( 5.0, 8.0 ) ); + item->attemptResize( QgsLayoutSize( 10.0, 6.0 ) ); + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemRotation, QgsProperty::fromExpression( QStringLiteral( "90" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::ItemRotation ); + QCOMPARE( item->itemRotation(), 90.0 ); + //also check when refreshing all properties + item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemRotation, QgsProperty::fromExpression( QStringLiteral( "45" ) ) ); + item->refreshDataDefinedProperty( QgsLayoutObject::AllProperties ); + QCOMPARE( item->itemRotation(), 45.0 ); + + delete item; + + //render check + item = new TestItem( &l ); + item->setItemRotation( 0.0 ); + item->setPos( 100, 150 ); + item->setRect( 0, 0, 200, 100 ); + l.addItem( item ); + item->setItemRotation( 45 ); + l.setSceneRect( 0, 0, 400, 400 ); + l.context().setFlag( QgsLayoutContext::FlagDebug, true ); + QImage image( l.sceneRect().size().toSize(), QImage::Format_ARGB32 ); + image.fill( 0 ); + QPainter painter( &image ); + l.render( &painter ); + painter.end(); + + bool result = renderCheck( "layoutitem_rotation", image, 0 ); + delete item; + QVERIFY( result ); +} + +//TODO rotation tests: +//restoring item from xml respects rotation/position +//rotate item around layout point + QGSTEST_MAIN( TestQgsLayoutItem ) #include "testqgslayoutitem.moc" diff --git a/tests/testdata/control_images/layouts/expected_layoutitem_rotation/expected_layoutitem_rotation.png b/tests/testdata/control_images/layouts/expected_layoutitem_rotation/expected_layoutitem_rotation.png new file mode 100644 index 0000000000000000000000000000000000000000..312f285283f6172e06c9015e867fd29e17805010 GIT binary patch literal 2280 zcmd6ok5dy@0>`u4ZGFjEcrO7;{jqLdAQ4nbwP*1zl{Gvd;YX0_SeQYClM{QrA3^Py zj^53ptKFcO01ARus-~?sYOCisMWxEcSg10TbFgyP*3r~MdhHcs(Q-G}Y2VxXCz_d^ zy!pKE`+mRs-fj|D?k#-MG{a;t7@qXF$%_U97h)H9f)eG@;z9al^tmfH8Vm_hc5(aW zCCoG!K$nLsTj@Xe@6SIy``O~@^4}U>`m+DkbKRTvZH+cftQEHvByqk8)70j6V($H% zn*78*>%YF2oBP9|51y#$o^$3Y!+~IY>fskR*DSip_u8efZ{k`Wk)G#j&(~i%NrVSx z`1U`}+_U0Po045xce#p4bGX_j+dFh_1Aw74u)^#N}l2K2);yd4I`+L6d zu1z|ad3lS7`I``W+pOYD)EF1k6?IMh1*B#^cjtFce{mY2)I!-`Jm&u`5k7=4;W!L7 zYkmI_)V$0qYi-z<#aU1M%DEWx_Yibt_A`{g)xMt)AJsr_nLPT|3N|@`VD-OJ*~$h! zvf>S9n^PeB*Y9AqF+shsie*X#^3=L33_Y^IrIkxqp+5@h4}tzUn19a*-v6}J`3*vT zuMC3@B8ZmIl|cV1nE%c=5Q(nMMU4|eU3m;S@cm9@zT3ISf~NB16q`6f2SH^%aSqwA zBwd5SZv)`CCG@z~pMxV;h47MiY8k26r#xpDZz8lRO)3raKP2i6;LeLZ8`Msjxt9x^ zV-~c^Dm80`B954ZurWSRNXEVL3>fSs>W~o5i4T;K@gmt|3I2kpdxY@Z_%W&|kWJ>` zd7^rSaBBP*Rk&mm3N9sTf)EDr@j`OUE1O{OB2ljjX-necW#m|qY_3AiUKGLRPS2EwK`A;E~2}5bo?e7uwYaxw`-$ux~i0dqI`t!*^k!&|7 zJ~E?)=8z}4eJ--j6+ zw{q$LlYU?XR;HLtmsDncoqFg6zX#xT9qdx>r?9yHLU_wgr{*MN8uEpXVA^le>xW8k=;gP1B}z8-Z|Q7K-1V$2Gp0Y{TMbHp$mlgixZ0V9xp- zRPr$Zy@xlF==PTc;nv8rIQCbO=N;ooC|>gn*Vwj6)BMmX`Hozri;f6_cNLQ$o_}m* zljisBRr;i4j79ol)x0@KCAZS0?1~SmZ#{%P^4n~HV2^tz8<=6w@E{vlV9)4oI*;3v_=|O#`M2f@vxh#SK#0 Syv;X`vvQt%FL~Ehv+YN1BfXCR literal 0 HcmV?d00001 From 7a0851c2ce6340b203c13b906fdaa91a8dc764a6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 14 Jul 2017 13:14:02 +1000 Subject: [PATCH 027/266] Shell class for QgsLayoutItemMap --- python/core/core_auto.sip | 1 + python/core/layout/qgslayoutitemmap.sip | 40 +++++++++++++++++++++ src/core/CMakeLists.txt | 2 ++ src/core/layout/qgslayoutitemmap.cpp | 30 ++++++++++++++++ src/core/layout/qgslayoutitemmap.h | 46 +++++++++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 python/core/layout/qgslayoutitemmap.sip create mode 100644 src/core/layout/qgslayoutitemmap.cpp create mode 100644 src/core/layout/qgslayoutitemmap.h diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index e958891c4a9..f966a473a70 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -381,6 +381,7 @@ %Include gps/qgsgpsdconnection.sip %Include layout/qgslayout.sip %Include layout/qgslayoutitem.sip +%Include layout/qgslayoutitemmap.sip %Include layout/qgslayoutitemregistry.sip %Include layout/qgslayoutobject.sip %Include symbology-ng/qgscptcityarchive.sip diff --git a/python/core/layout/qgslayoutitemmap.sip b/python/core/layout/qgslayoutitemmap.sip new file mode 100644 index 00000000000..29a837c3577 --- /dev/null +++ b/python/core/layout/qgslayoutitemmap.sip @@ -0,0 +1,40 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitemmap.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsLayoutItemMap : QgsLayoutItem +{ +%Docstring + Layout graphical items for displaying a map. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemmap.h" +%End + public: + + explicit QgsLayoutItemMap( QgsLayout *layout ); +%Docstring + Constructor for QgsLayoutItemMap, with the specified parent ``layout``. +%End + + protected: + + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitemmap.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9c1ea975222..f3e31a5b1a8 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -352,6 +352,7 @@ SET(QGIS_CORE_SRCS layout/qgslayout.cpp layout/qgslayoutcontext.cpp layout/qgslayoutitem.cpp + layout/qgslayoutitemmap.cpp layout/qgslayoutitemregistry.cpp layout/qgslayoutmeasurement.cpp layout/qgslayoutmeasurementconverter.cpp @@ -674,6 +675,7 @@ SET(QGIS_CORE_MOC_HDRS layout/qgslayout.h layout/qgslayoutitem.h + layout/qgslayoutitemmap.h layout/qgslayoutitemregistry.h layout/qgslayoutobject.h diff --git a/src/core/layout/qgslayoutitemmap.cpp b/src/core/layout/qgslayoutitemmap.cpp new file mode 100644 index 00000000000..263b01c48ef --- /dev/null +++ b/src/core/layout/qgslayoutitemmap.cpp @@ -0,0 +1,30 @@ +/*************************************************************************** + qgslayoutitemmap.cpp + --------------------- + begin : July 2017 + copyright : (C) 2017 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 "qgslayoutitemmap.h" +#include "qgslayout.h" +#include "qgslayoututils.h" +#include + +QgsLayoutItemMap::QgsLayoutItemMap( QgsLayout *layout ) + : QgsLayoutItem( layout ) +{ +} + +void QgsLayoutItemMap::draw( QgsRenderContext &, const QStyleOptionGraphicsItem * ) +{ + +} diff --git a/src/core/layout/qgslayoutitemmap.h b/src/core/layout/qgslayoutitemmap.h new file mode 100644 index 00000000000..05af3636942 --- /dev/null +++ b/src/core/layout/qgslayoutitemmap.h @@ -0,0 +1,46 @@ +/*************************************************************************** + qgslayoutitemmap.h + ------------------- + begin : July 2017 + copyright : (C) 2017 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 QGSLAYOUTITEMMAP_H +#define QGSLAYOUTITEMMAP_H + +#include "qgis_core.h" +#include "qgslayoutitem.h" + +/** + * \ingroup core + * \class QgsLayoutItemMap + * \brief Layout graphical items for displaying a map. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutItemMap, with the specified parent \a layout. + */ + explicit QgsLayoutItemMap( QgsLayout *layout ); + + protected: + + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; +}; + +#endif //QGSLAYOUTITEMMAP_H From c28202474812375fcea5e1695a7b186e8f992c87 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 14 Jul 2017 13:15:52 +1000 Subject: [PATCH 028/266] Partially port some layout utils for render context creation --- python/core/layout/qgslayout.sip | 8 ++ python/core/layout/qgslayoututils.sip | 20 +++++ src/core/layout/qgslayout.cpp | 10 +++ src/core/layout/qgslayout.h | 21 +++++ src/core/layout/qgslayoututils.cpp | 52 ++++++++++++ src/core/layout/qgslayoututils.h | 22 +++++ tests/src/core/testqgslayout.cpp | 33 ++++++++ tests/src/core/testqgslayoututils.cpp | 116 ++++++++++++++++++++++++++ 8 files changed, 282 insertions(+) diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index 1661e0c22fa..ed584efa664 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -7,6 +7,7 @@ ************************************************************************/ + class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator { %Docstring @@ -175,6 +176,13 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator :rtype: list of str %End + QgsLayoutItemMap *referenceMap() const; +%Docstring + :rtype: QgsLayoutItemMap +%End + + void setReferenceMap( QgsLayoutItemMap *map ); + signals: void variablesChanged(); diff --git a/python/core/layout/qgslayoututils.sip b/python/core/layout/qgslayoututils.sip index a8a0346f88b..edb168fe482 100644 --- a/python/core/layout/qgslayoututils.sip +++ b/python/core/layout/qgslayoututils.sip @@ -8,6 +8,7 @@ + class QgsLayoutUtils { %Docstring @@ -28,6 +29,25 @@ class QgsLayoutUtils :rtype: float %End + static QgsRenderContext createRenderContextForMap( QgsLayoutItemMap *map, QPainter *painter, double dpi = -1 ); +%Docstring + Creates a render context suitable for the specified layout ``map`` and ``painter`` destination. + This method returns a new QgsRenderContext which matches the scale and settings of the + target map. If the ``dpi`` argument is not specified then the dpi will be taken from the destinatation + painter device. +.. seealso:: createRenderContextForLayout() + :rtype: QgsRenderContext +%End + + static QgsRenderContext createRenderContextForLayout( QgsLayout *layout, QPainter *painter ); +%Docstring + Creates a render context suitable for the specified ``layout`` and ``painter`` destination. + This method returns a new QgsRenderContext which matches the scale and settings from the layout's + QgsLayout.referenceMap(). +.. seealso:: createRenderContextForMap() + :rtype: QgsRenderContext +%End + }; /************************************************************************ diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index 4fdd13544c8..d569ee5cdba 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -93,3 +93,13 @@ QStringList QgsLayout::customProperties() const { return mCustomProperties.keys(); } + +QgsLayoutItemMap *QgsLayout::referenceMap() const +{ + return nullptr; +} + +void QgsLayout::setReferenceMap( QgsLayoutItemMap *map ) +{ + Q_UNUSED( map ); +} diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index 481209e3f8d..adfce6f0f9e 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -21,6 +21,8 @@ #include "qgslayoutcontext.h" #include "qgsexpressioncontextgenerator.h" +class QgsLayoutItemMap; + /** * \ingroup core * \class QgsLayout @@ -182,6 +184,25 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext */ QStringList customProperties() const; + /** + * Returns the map item which will be used to generate corresponding world files when the + * layout is exported. If no map was explicitly set via setReferenceMap(), the largest + * map in the layout will be returned (or nullptr if there are no maps in the layout). + * \see setReferenceMap() + * \see generateWorldFile() + */ + //TODO + QgsLayoutItemMap *referenceMap() const; + + /** + * Sets the \a map item which will be used to generate corresponding world files when the + * layout is exported. + * \see referenceMap() + * \see setGenerateWorldFile() + */ + //TODO + void setReferenceMap( QgsLayoutItemMap *map ); + signals: /** diff --git a/src/core/layout/qgslayoututils.cpp b/src/core/layout/qgslayoututils.cpp index 18ecc20ecd0..702fafefd67 100644 --- a/src/core/layout/qgslayoututils.cpp +++ b/src/core/layout/qgslayoututils.cpp @@ -16,6 +16,10 @@ ***************************************************************************/ #include "qgslayoututils.h" +#include "qgslayout.h" +#include "qgsrendercontext.h" +#include "qgslayoutitemmap.h" +#include #include double QgsLayoutUtils::normalizedAngle( const double angle, const bool allowNegative ) @@ -31,3 +35,51 @@ double QgsLayoutUtils::normalizedAngle( const double angle, const bool allowNega } return clippedAngle; } + +QgsRenderContext QgsLayoutUtils::createRenderContextForMap( QgsLayoutItemMap *map, QPainter *painter, double dpi ) +{ + if ( !map ) + { + QgsRenderContext context; + context.setPainter( painter ); + if ( dpi < 0 && painter && painter->device() ) + { + context.setScaleFactor( painter->device()->logicalDpiX() / 25.4 ); + } + else if ( dpi > 0 ) + { + context.setScaleFactor( dpi / 25.4 ); + } + else + { + context.setScaleFactor( 3.465 ); //assume 88 dpi as standard value + } + return context; + } + else + { + // default to 88 dpi if no painter specified + if ( dpi < 0 ) + { + dpi = ( painter && painter->device() ) ? painter->device()->logicalDpiX() : 88; + } +#if 0 + double dotsPerMM = dpi / 25.4; +// TODO + // get map settings from reference map + QgsRectangle extent = *( map->currentMapExtent() ); + QSizeF mapSizeMM = map->rect().size(); + QgsMapSettings ms = map->mapSettings( extent, mapSizeMM * dotsPerMM, dpi ); +#endif + QgsRenderContext context; // = QgsRenderContext::fromMapSettings( ms ); + if ( painter ) + context.setPainter( painter ); + return context; + } +} + +QgsRenderContext QgsLayoutUtils::createRenderContextForLayout( QgsLayout *layout, QPainter *painter ) +{ + QgsLayoutItemMap *referenceMap = layout ? layout->referenceMap() : nullptr; + return createRenderContextForMap( referenceMap, painter ); +} diff --git a/src/core/layout/qgslayoututils.h b/src/core/layout/qgslayoututils.h index 4cd822c03a4..4141d5e4b9a 100644 --- a/src/core/layout/qgslayoututils.h +++ b/src/core/layout/qgslayoututils.h @@ -19,6 +19,11 @@ #include "qgis_core.h" +class QgsRenderContext; +class QgsLayout; +class QgsLayoutItemMap; +class QPainter; + /** * \ingroup core * Utilities for layouts. @@ -35,6 +40,23 @@ class CORE_EXPORT QgsLayoutUtils */ static double normalizedAngle( const double angle, const bool allowNegative = false ); + /** + * Creates a render context suitable for the specified layout \a map and \a painter destination. + * This method returns a new QgsRenderContext which matches the scale and settings of the + * target map. If the \a dpi argument is not specified then the dpi will be taken from the destinatation + * painter device. + * \see createRenderContextForLayout() + */ + static QgsRenderContext createRenderContextForMap( QgsLayoutItemMap *map, QPainter *painter, double dpi = -1 ); + + /** + * Creates a render context suitable for the specified \a layout and \a painter destination. + * This method returns a new QgsRenderContext which matches the scale and settings from the layout's + * QgsLayout::referenceMap(). + * \see createRenderContextForMap() + */ + static QgsRenderContext createRenderContextForLayout( QgsLayout *layout, QPainter *painter ); + }; #endif //QGSLAYOUTUTILS_H diff --git a/tests/src/core/testqgslayout.cpp b/tests/src/core/testqgslayout.cpp index a8ddc12e433..61421717145 100644 --- a/tests/src/core/testqgslayout.cpp +++ b/tests/src/core/testqgslayout.cpp @@ -18,6 +18,7 @@ #include "qgslayout.h" #include "qgstest.h" #include "qgsproject.h" +#include "qgslayoutitemmap.h" class TestQgsLayout: public QObject { @@ -34,6 +35,7 @@ class TestQgsLayout: public QObject void customProperties(); void variablesEdited(); void scope(); + void referenceMap(); private: QString mReport; @@ -213,6 +215,37 @@ void TestQgsLayout::scope() } +void TestQgsLayout::referenceMap() +{ + QgsRectangle extent( 2000, 2800, 2500, 2900 ); + QgsProject p; + QgsLayout l( &p ); + + // no maps + QVERIFY( !l.referenceMap() ); +#if 0 + + QgsLayoutItemMap *map = new QgsLayoutItemMap( &l ); + map->setNewExtent( extent ); + map->setSceneRect( QRectF( 30, 60, 200, 100 ) ); + l.addComposerMap( map ); + QCOMPARE( l.referenceMap(), map ); +#endif +#if 0 // TODO + + // add a larger map + QgsLayoutItemMap *map2 = new QgsLayoutItemMap( &l ); + map2->setNewExtent( extent ); + map2->setSceneRect( QRectF( 30, 60, 250, 150 ) ); + l.addComposerMap( map2 ); + QCOMPARE( l.referenceMap(), map2 ); + // explicitly set reference map + l.setReferenceMap( map ); + QCOMPARE( l.referenceMap(), map ); +#endif + +} + QGSTEST_MAIN( TestQgsLayout ) #include "testqgslayout.moc" diff --git a/tests/src/core/testqgslayoututils.cpp b/tests/src/core/testqgslayoututils.cpp index b2d69e15269..e509c5d201e 100644 --- a/tests/src/core/testqgslayoututils.cpp +++ b/tests/src/core/testqgslayoututils.cpp @@ -18,6 +18,9 @@ #include "qgslayout.h" #include "qgstest.h" #include "qgslayoututils.h" +#include "qgstestutils.h" +#include "qgsproject.h" +#include "qgslayoutitemmap.h" class TestQgsLayoutUtils: public QObject { @@ -29,6 +32,8 @@ class TestQgsLayoutUtils: public QObject void init();// will be called before each testfunction is executed. void cleanup();// will be called after every testfunction. void normalizedAngle(); //test normalised angle function + void createRenderContextFromLayout(); + void createRenderContextFromMap(); private: QString mReport; @@ -112,5 +117,116 @@ void TestQgsLayoutUtils::normalizedAngle() } +void TestQgsLayoutUtils::createRenderContextFromLayout() +{ + QImage testImage = QImage( 250, 250, QImage::Format_RGB32 ); + testImage.setDotsPerMeterX( 150 / 25.4 * 1000 ); + testImage.setDotsPerMeterY( 150 / 25.4 * 1000 ); + QPainter p( &testImage ); + + // no composition + QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( nullptr, &p ); + QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); + QCOMPARE( rc.painter(), &p ); + + // no composition, no painter + rc = QgsLayoutUtils::createRenderContextForLayout( nullptr, nullptr ); + QGSCOMPARENEAR( rc.scaleFactor(), 88 / 25.4, 0.001 ); + QVERIFY( !rc.painter() ); + + //create composition with no reference map + QgsRectangle extent( 2000, 2800, 2500, 2900 ); + QgsProject project; + QgsLayout l( &project ); + rc = QgsLayoutUtils::createRenderContextForLayout( &l, &p ); + QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); + QCOMPARE( rc.painter(), &p ); + + // layout, no map, no painter + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QGSCOMPARENEAR( rc.scaleFactor(), 88 / 25.4, 0.001 ); + QVERIFY( !rc.painter() ); + + // add a reference map + QgsLayoutItemMap *map = new QgsLayoutItemMap( &l ); +#if 0 // TODO + map->setNewExtent( extent ); + map->setSceneRect( QRectF( 30, 60, 200, 100 ) ); + composition->addComposerMap( map ); +#endif + l.setReferenceMap( map ); + + rc = QgsLayoutUtils::createRenderContextForLayout( &l, &p ); + QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); + QGSCOMPARENEAR( rc.rendererScale(), map->scale(), 1000000 ); + QCOMPARE( rc.painter(), &p ); + + // layout, reference map, no painter + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QGSCOMPARENEAR( rc.scaleFactor(), 88 / 25.4, 0.001 ); + QGSCOMPARENEAR( rc.rendererScale(), map->scale(), 1000000 ); + QVERIFY( !rc.painter() ); + + p.end(); +} + +void TestQgsLayoutUtils::createRenderContextFromMap() +{ + QImage testImage = QImage( 250, 250, QImage::Format_RGB32 ); + testImage.setDotsPerMeterX( 150 / 25.4 * 1000 ); + testImage.setDotsPerMeterY( 150 / 25.4 * 1000 ); + QPainter p( &testImage ); + + // no map + QgsRenderContext rc = QgsLayoutUtils::createRenderContextForMap( nullptr, &p ); + QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); + QCOMPARE( rc.painter(), &p ); + + // no map, no painter + rc = QgsLayoutUtils::createRenderContextForMap( nullptr, nullptr ); + QGSCOMPARENEAR( rc.scaleFactor(), 88 / 25.4, 0.001 ); + QVERIFY( !rc.painter() ); + + //create composition with no reference map + QgsRectangle extent( 2000, 2800, 2500, 2900 ); + QgsProject project; + QgsLayout l( &project ); + +#if 0 // TODO + // add a map + QgsLayoutItemMap *map = new QgsLayoutItemMap( &l ); + + map->setNewExtent( extent ); + map->setSceneRect( QRectF( 30, 60, 200, 100 ) ); + l.addComposerMap( map ); +#endif + +#if 0 //TODO + rc = QgsLayoutUtils::createRenderContextForMap( map, &p ); + QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); + QGSCOMPARENEAR( rc.rendererScale(), map->scale(), 1000000 ); + QCOMPARE( rc.painter(), &p ); + + // map, no painter + rc = QgsLayoutUtils::createRenderContextForMap( map, nullptr ); + QGSCOMPARENEAR( rc.scaleFactor(), 88 / 25.4, 0.001 ); + QGSCOMPARENEAR( rc.rendererScale(), map->scale(), 1000000 ); + QVERIFY( !rc.painter() ); + + // secondary map + QgsLayoutItemMap *map2 = new QgsLayoutItemMap( &l ); + + map2->setNewExtent( extent ); + map2->setSceneRect( QRectF( 30, 60, 100, 50 ) ); + composition->addComposerMap( map2 ); + + rc = QgsLayoutUtils::createRenderContextForMap( map2, &p ); + QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); + QGSCOMPARENEAR( rc.rendererScale(), map2->scale(), 1000000 ); + QVERIFY( rc.painter() ); +#endif + p.end(); +} + QGSTEST_MAIN( TestQgsLayoutUtils ) #include "testqgslayoututils.moc" From 56bb65709d22ec01157cd1eb893ffb7cad89bd8f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 14 Jul 2017 13:24:02 +1000 Subject: [PATCH 029/266] Change QgsLayoutItem::draw to use a renderContext instead of direct QPainter argument This will make use of other rendering code within layout items much easier - since symbology/text renderer/diagrams/etc all require QgsRenderContexts for use, it makes sense for layout item rendering to also use this approach. This also avoids lots of duplicate code which was scattered throughout different composer item types to manually handle creation of QgsRenderContexts when required. --- python/core/layout/qgslayoutitem.sip | 4 ++-- src/core/layout/qgslayoutitem.cpp | 5 +++-- src/core/layout/qgslayoutitem.h | 5 +++-- src/core/layout/qgslayoutitemregistry.cpp | 5 +++-- src/core/layout/qgslayoutitemregistry.h | 2 +- tests/src/core/testqgslayoutitem.cpp | 7 ++++--- tests/src/gui/testqgslayoutview.cpp | 2 +- 7 files changed, 17 insertions(+), 13 deletions(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 0e8c763457d..75df2681147 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -170,9 +170,9 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem @param painter destination QPainter %End - virtual void draw( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) = 0; + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ) = 0; %Docstring - Draws the item's contents on a specified ``painter``. + Draws the item's contents using the specified render ``context``. %End virtual void setFixedSize( const QgsLayoutSize &size ); diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 74112ead3f7..f245f4373ff 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -33,7 +33,7 @@ QgsLayoutItem::QgsLayoutItem( QgsLayout *layout ) initConnectionsToLayout(); } -void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) +void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * ) { if ( !painter || !painter->device() ) { @@ -50,7 +50,8 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it } else { - draw( painter, itemStyle, pWidget ); + QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, painter ); + draw( context, itemStyle ); } painter->restore(); diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 3c7f0640cef..9b07fea64cb 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -21,6 +21,7 @@ #include "qgslayoutobject.h" #include "qgslayoutsize.h" #include "qgslayoutpoint.h" +#include "qgsrendercontext.h" #include class QgsLayout; @@ -184,9 +185,9 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt virtual void drawDebugRect( QPainter *painter ); /** - * Draws the item's contents on a specified \a painter. + * Draws the item's contents using the specified render \a context. */ - virtual void draw( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) = 0; + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) = 0; /** * Sets a fixed \a size for the layout item, which prevents it from being freely diff --git a/src/core/layout/qgslayoutitemregistry.cpp b/src/core/layout/qgslayoutitemregistry.cpp index 6bed127a26c..8f7eed167e7 100644 --- a/src/core/layout/qgslayoutitemregistry.cpp +++ b/src/core/layout/qgslayoutitemregistry.cpp @@ -95,10 +95,11 @@ TestLayoutItem::TestLayoutItem( QgsLayout *layout ) mColor = QColor::fromHsv( h, s, v ); } -void TestLayoutItem::draw( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) +void TestLayoutItem::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle ) { Q_UNUSED( itemStyle ); - Q_UNUSED( pWidget ); + QPainter *painter = context.painter(); + painter->save(); painter->setRenderHint( QPainter::Antialiasing, false ); painter->setPen( Qt::NoPen ); diff --git a/src/core/layout/qgslayoutitemregistry.h b/src/core/layout/qgslayoutitemregistry.h index 0d2eed98b31..a2cda2eb7e7 100644 --- a/src/core/layout/qgslayoutitemregistry.h +++ b/src/core/layout/qgslayoutitemregistry.h @@ -267,7 +267,7 @@ class TestLayoutItem : public QgsLayoutItem //implement pure virtual methods int type() const { return QgsLayoutItemRegistry::LayoutItem + 102; } - void draw( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ); + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ); private: QColor mColor; diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 11442a26102..11aaf40d0d0 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -69,10 +69,11 @@ class TestQgsLayoutItem: public QObject //implement pure virtual methods int type() const { return QgsLayoutItemRegistry::LayoutItem + 101; } - void draw( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) + + protected: + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * = nullptr ) override { - Q_UNUSED( itemStyle ); - Q_UNUSED( pWidget ); + QPainter *painter = context.painter(); painter->save(); painter->setRenderHint( QPainter::Antialiasing, false ); painter->setPen( Qt::NoPen ); diff --git a/tests/src/gui/testqgslayoutview.cpp b/tests/src/gui/testqgslayoutview.cpp index abc0d5bfa3c..096dca3877b 100644 --- a/tests/src/gui/testqgslayoutview.cpp +++ b/tests/src/gui/testqgslayoutview.cpp @@ -242,7 +242,7 @@ class TestItem : public QgsLayoutItem //implement pure virtual methods int type() const override { return QgsLayoutItemRegistry::LayoutItem + 101; } - void draw( QPainter *, const QStyleOptionGraphicsItem *, QWidget * ) override + void draw( QgsRenderContext &, const QStyleOptionGraphicsItem * = nullptr ) override { } }; From 436710a177dcac783a7d6bf904cb73fe232299e3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 14 Jul 2017 16:10:20 +1000 Subject: [PATCH 030/266] Ensure that item painter is correctly scaled so that painter units are pixels --- python/core/layout/qgslayoutitem.sip | 2 ++ src/core/layout/qgslayoutitem.cpp | 8 +++++++- src/core/layout/qgslayoutitem.h | 2 ++ src/core/layout/qgslayoutitemregistry.cpp | 15 ++++++++++++++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 75df2681147..b717e36065f 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -173,6 +173,8 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ) = 0; %Docstring Draws the item's contents using the specified render ``context``. + Note that the context's painter has been scaled so that painter units are pixels. + Use the QgsRenderContext methods to convert from millimeters or other units to the painter's units. %End virtual void setFixedSize( const QgsLayoutSize &size ); diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index f245f4373ff..d449749fa89 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -18,11 +18,14 @@ #include "qgslayout.h" #include "qgslayoututils.h" #include +#include QgsLayoutItem::QgsLayoutItem( QgsLayout *layout ) : QgsLayoutObject( layout ) , QGraphicsRectItem( 0 ) { + // needed to access current view transform during paint operations + setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption ); setCacheMode( QGraphicsItem::DeviceCoordinateCache ); //record initial position @@ -50,7 +53,10 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it } else { - QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, painter ); + double destinationDpi = itemStyle->matrix.m11() * 25.4; + QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, painter, destinationDpi ); + // scale painter from mm to dots + painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() ); draw( context, itemStyle ); } diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 9b07fea64cb..3e70bfabbd4 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -186,6 +186,8 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt /** * Draws the item's contents using the specified render \a context. + * Note that the context's painter has been scaled so that painter units are pixels. + * Use the QgsRenderContext methods to convert from millimeters or other units to the painter's units. */ virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) = 0; diff --git a/src/core/layout/qgslayoutitemregistry.cpp b/src/core/layout/qgslayoutitemregistry.cpp index 8f7eed167e7..5d5e8f81086 100644 --- a/src/core/layout/qgslayoutitemregistry.cpp +++ b/src/core/layout/qgslayoutitemregistry.cpp @@ -15,6 +15,8 @@ ***************************************************************************/ #include "qgslayoutitemregistry.h" +#include "qgsgloweffect.h" +#include "qgseffectstack.h" #include QgsLayoutItemRegistry::QgsLayoutItemRegistry( QObject *parent ) @@ -98,13 +100,24 @@ TestLayoutItem::TestLayoutItem( QgsLayout *layout ) void TestLayoutItem::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle ) { Q_UNUSED( itemStyle ); + + QgsEffectStack stack; + stack.appendEffect( new QgsDrawSourceEffect() ); + stack.appendEffect( new QgsInnerGlowEffect() ); + stack.begin( context ); + QPainter *painter = context.painter(); painter->save(); painter->setRenderHint( QPainter::Antialiasing, false ); painter->setPen( Qt::NoPen ); painter->setBrush( mColor ); - painter->drawRect( rect() ); + + double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ); + QRectF r = QRectF( rect().left() * scale, rect().top() * scale, + rect().width() * scale, rect().height() * scale ); + painter->drawRect( r ); painter->restore(); + stack.end( context ); } ///@endcond From 38cbbe23aa475f1171007a2b7c99ff539c3fece6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 14 Jul 2017 16:44:04 +1000 Subject: [PATCH 031/266] Implement a cache for item content renders Speeds up redraw of items, making use of layout designer much faster with slow to redraw items. This will also make it possible to use live effects on layout items without killing performance of the designer. --- src/core/layout/qgslayoutitem.cpp | 78 +++++++++++++++++++-- src/core/layout/qgslayoutitem.h | 3 + src/core/layout/qgslayoutitemregistry.cpp | 22 +++++- src/core/layout/qgslayoutitemregistry.h | 3 + src/gui/layout/qgslayoutviewtooladditem.cpp | 3 +- 5 files changed, 100 insertions(+), 9 deletions(-) diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index d449749fa89..da1e78fc25d 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -20,6 +20,8 @@ #include #include +#define CACHE_SIZE_LIMIT 5000 + QgsLayoutItem::QgsLayoutItem( QgsLayout *layout ) : QgsLayoutObject( layout ) , QGraphicsRectItem( 0 ) @@ -44,23 +46,89 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it } //TODO - remember to disable saving/restoring on graphics view!! - painter->save(); - preparePainter( painter ); if ( shouldDrawDebugRect() ) { drawDebugRect( painter ); + return; + } + + double destinationDpi = itemStyle->matrix.m11() * 25.4; + bool useImageCache = true; + + if ( useImageCache ) + { + double widthInPixels = boundingRect().width() * itemStyle->matrix.m11(); + double heightInPixels = boundingRect().height() * itemStyle->matrix.m11(); + + // limit size of image for better performance + double scale = 1.0; + if ( widthInPixels > CACHE_SIZE_LIMIT || heightInPixels > CACHE_SIZE_LIMIT ) + { + if ( widthInPixels > heightInPixels ) + { + scale = widthInPixels / CACHE_SIZE_LIMIT; + widthInPixels = CACHE_SIZE_LIMIT; + heightInPixels /= scale; + } + else + { + scale = heightInPixels / CACHE_SIZE_LIMIT; + heightInPixels = CACHE_SIZE_LIMIT; + widthInPixels /= scale; + } + destinationDpi = destinationDpi / scale; + } + + if ( !mItemCachedImage.isNull() && qgsDoubleNear( mItemCacheDpi, destinationDpi ) ) + { + // can reuse last cached image + QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, painter, destinationDpi ); + painter->save(); + preparePainter( painter ); + double cacheScale = destinationDpi / mItemCacheDpi; + painter->scale( cacheScale / context.scaleFactor(), cacheScale / context.scaleFactor() ); + painter->drawImage( boundingRect().x() * context.scaleFactor() / cacheScale, + boundingRect().y() * context.scaleFactor() / cacheScale, mItemCachedImage ); + painter->restore(); + return; + } + else + { + mItemCacheDpi = destinationDpi; + + mItemCachedImage = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 ); + mItemCachedImage.fill( Qt::transparent ); + mItemCachedImage.setDotsPerMeterX( 1000 * destinationDpi * 25.4 ); + mItemCachedImage.setDotsPerMeterY( 1000 * destinationDpi * 25.4 ); + QPainter p( &mItemCachedImage ); + + preparePainter( &p ); + QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, &p, destinationDpi ); + // painter is already scaled to dots + // need to translate so that item origin is at 0,0 in painter coordinates (not bounding rect origin) + p.translate( -boundingRect().x() * context.scaleFactor(), -boundingRect().y() * context.scaleFactor() ); + draw( context, itemStyle ); + p.end(); + + painter->save(); + // scale painter from mm to dots + painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() ); + painter->drawImage( boundingRect().x() * context.scaleFactor(), + boundingRect().y() * context.scaleFactor(), mItemCachedImage ); + painter->restore(); + } } else { - double destinationDpi = itemStyle->matrix.m11() * 25.4; + // no caching or flattening + painter->save(); QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, painter, destinationDpi ); // scale painter from mm to dots painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() ); draw( context, itemStyle ); + painter->restore(); } - - painter->restore(); } void QgsLayoutItem::setReferencePoint( const QgsLayoutItem::ReferencePoint &point ) diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 3e70bfabbd4..537b89529f6 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -252,6 +252,9 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt QgsLayoutPoint mItemPosition; double mItemRotation = 0.0; + QImage mItemCachedImage; + double mItemCacheDpi = -1; + void initConnectionsToLayout(); //! Prepares a painter by setting rendering flags diff --git a/src/core/layout/qgslayoutitemregistry.cpp b/src/core/layout/qgslayoutitemregistry.cpp index 5d5e8f81086..08683ef7e38 100644 --- a/src/core/layout/qgslayoutitemregistry.cpp +++ b/src/core/layout/qgslayoutitemregistry.cpp @@ -95,6 +95,16 @@ TestLayoutItem::TestLayoutItem( QgsLayout *layout ) int s = ( qrand() % ( 200 - 100 + 1 ) ) + 100; int v = ( qrand() % ( 130 - 255 + 1 ) ) + 130; mColor = QColor::fromHsv( h, s, v ); + + QgsStringMap properties; + properties.insert( QStringLiteral( "color" ), mColor.name() ); + properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) ); + properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) ); + properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) ); + properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) ); + properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) ); + mShapeStyleSymbol = QgsFillSymbol::createSimple( properties ); + } void TestLayoutItem::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle ) @@ -114,9 +124,15 @@ void TestLayoutItem::draw( QgsRenderContext &context, const QStyleOptionGraphics painter->setBrush( mColor ); double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ); - QRectF r = QRectF( rect().left() * scale, rect().top() * scale, - rect().width() * scale, rect().height() * scale ); - painter->drawRect( r ); + + QPolygonF shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) ); + QList rings; //empty list + + mShapeStyleSymbol->startRender( context ); + mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, nullptr, context ); + mShapeStyleSymbol->stopRender( context ); + +// painter->drawRect( r ); painter->restore(); stack.end( context ); } diff --git a/src/core/layout/qgslayoutitemregistry.h b/src/core/layout/qgslayoutitemregistry.h index a2cda2eb7e7..c4abaae1952 100644 --- a/src/core/layout/qgslayoutitemregistry.h +++ b/src/core/layout/qgslayoutitemregistry.h @@ -29,6 +29,7 @@ class QgsLayout; class QgsLayoutView; class QgsLayoutItem; +class QgsFillSymbol; /** * \ingroup core @@ -271,8 +272,10 @@ class TestLayoutItem : public QgsLayoutItem private: QColor mColor; + QgsFillSymbol *mShapeStyleSymbol = nullptr; }; + ///@endcond #endif diff --git a/src/gui/layout/qgslayoutviewtooladditem.cpp b/src/gui/layout/qgslayoutviewtooladditem.cpp index 497924aa1a1..b4634fbb530 100644 --- a/src/gui/layout/qgslayoutviewtooladditem.cpp +++ b/src/gui/layout/qgslayoutviewtooladditem.cpp @@ -86,7 +86,8 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even Q_UNUSED( clickOnly ); QgsLayoutItem *item = QgsApplication::layoutItemRegistry()->createItem( mItemType, layout() ); - item->setRect( rect ); + item->attemptResize( QgsLayoutSize( rect.width(), rect.height(), QgsUnitTypes::LayoutMillimeters ) ); + item->attemptMove( QgsLayoutPoint( rect.left(), rect.top(), QgsUnitTypes::LayoutMillimeters ) ); layout()->addItem( item ); } From b4f5025d4fb8f8b1d32ba577bed2d1596dc290f8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 17:11:44 +1000 Subject: [PATCH 032/266] Port shape items to layouts --- python/core/core_auto.sip | 1 + python/core/layout/qgslayoutitemregistry.sip | 3 + python/core/layout/qgslayoutitemshape.sip | 156 ++++++++++++++++ python/gui/layout/qgslayoutviewrubberband.sip | 30 +++ src/core/CMakeLists.txt | 2 + src/core/layout/qgslayoutitemregistry.cpp | 5 + src/core/layout/qgslayoutitemregistry.h | 3 + src/core/layout/qgslayoutitemshape.cpp | 173 ++++++++++++++++++ src/core/layout/qgslayoutitemshape.h | 158 ++++++++++++++++ src/gui/layout/qgslayoutitemguiregistry.cpp | 13 +- src/gui/layout/qgslayoutviewrubberband.cpp | 76 ++++++++ src/gui/layout/qgslayoutviewrubberband.h | 32 ++++ 12 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 python/core/layout/qgslayoutitemshape.sip create mode 100644 src/core/layout/qgslayoutitemshape.cpp create mode 100644 src/core/layout/qgslayoutitemshape.h diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index f966a473a70..73ff541cbcd 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -383,6 +383,7 @@ %Include layout/qgslayoutitem.sip %Include layout/qgslayoutitemmap.sip %Include layout/qgslayoutitemregistry.sip +%Include layout/qgslayoutitemshape.sip %Include layout/qgslayoutobject.sip %Include symbology-ng/qgscptcityarchive.sip %Include symbology-ng/qgssvgcache.sip diff --git a/python/core/layout/qgslayoutitemregistry.sip b/python/core/layout/qgslayoutitemregistry.sip index 854d914758e..afad7f19e66 100644 --- a/python/core/layout/qgslayoutitemregistry.sip +++ b/python/core/layout/qgslayoutitemregistry.sip @@ -101,6 +101,9 @@ class QgsLayoutItemRegistry : QObject // known LayoutPage, + LayoutRectangle, + LayoutEllipse, + LayoutTriangle, // item PluginItem, diff --git a/python/core/layout/qgslayoutitemshape.sip b/python/core/layout/qgslayoutitemshape.sip new file mode 100644 index 00000000000..47191ecc90d --- /dev/null +++ b/python/core/layout/qgslayoutitemshape.sip @@ -0,0 +1,156 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitemshape.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsLayoutItemShape : QgsLayoutItem +{ +%Docstring + Base class for layout items which are basic shapes (e.g. rectangles, ellipses). +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemshape.h" +%End + public: + + void setSymbol( QgsFillSymbol *symbol ); +%Docstring + Sets the fill ``symbol`` used to draw the shape. Ownership is not transferred + and a clone of the symbol is made. +.. seealso:: symbol() +%End + + QgsFillSymbol *symbol(); +%Docstring + Returns the fill symbol used to draw the shape. +.. seealso:: setSymbol() + :rtype: QgsFillSymbol +%End + + protected: + + explicit QgsLayoutItemShape( QgsLayout *layout ); +%Docstring + Constructor for QgsLayoutItemShape, with the specified parent ``layout``. +%End + +}; + + +class QgsLayoutItemRectangularShape : QgsLayoutItemShape +{ +%Docstring + A rectangular shape item for layouts. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemshape.h" +%End + public: + + explicit QgsLayoutItemRectangularShape( QgsLayout *layout ); +%Docstring + Constructor for QgsLayoutItemRectangularShape, with the specified parent ``layout``. +%End + virtual int type() const; + + static QgsLayoutItemRectangularShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; +%Docstring + :rtype: QgsLayoutItemRectangularShape +%End + + void setCornerRadius( QgsLayoutMeasurement radius ); +%Docstring + Sets the corner ``radius`` for rounded rectangle corners. +.. seealso:: cornerRadius() +%End + + QgsLayoutMeasurement cornerRadius() const; +%Docstring + Returns the corner radius for rounded rectangle corners. +.. seealso:: setCornerRadius() + :rtype: QgsLayoutMeasurement +%End + + protected: + + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); + + +}; + +class QgsLayoutItemEllipseShape : QgsLayoutItemShape +{ +%Docstring + A ellipse shape item for layouts. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemshape.h" +%End + public: + + explicit QgsLayoutItemEllipseShape( QgsLayout *layout ); +%Docstring + Constructor for QgsLayoutItemEllipseShape, with the specified parent ``layout``. +%End + virtual int type() const; + + static QgsLayoutItemEllipseShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; +%Docstring + :rtype: QgsLayoutItemEllipseShape +%End + + protected: + + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); + + +}; + +class QgsLayoutItemTriangleShape : QgsLayoutItemShape +{ +%Docstring + A triangle shape item for layouts. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemshape.h" +%End + public: + + explicit QgsLayoutItemTriangleShape( QgsLayout *layout ); +%Docstring + Constructor for QgsLayoutItemTriangleShape, with the specified parent ``layout``. +%End + virtual int type() const; + + static QgsLayoutItemTriangleShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; +%Docstring + :rtype: QgsLayoutItemTriangleShape +%End + + protected: + + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitemshape.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/layout/qgslayoutviewrubberband.sip b/python/gui/layout/qgslayoutviewrubberband.sip index fae3771f666..12871d999d8 100644 --- a/python/gui/layout/qgslayoutviewrubberband.sip +++ b/python/gui/layout/qgslayoutviewrubberband.sip @@ -179,6 +179,36 @@ class QgsLayoutViewEllipticalRubberBand : QgsLayoutViewRubberBand virtual QRectF finish( QPointF position = QPointF(), Qt::KeyboardModifiers modifiers = 0 ); +}; + +class QgsLayoutViewTriangleRubberBand : QgsLayoutViewRubberBand +{ +%Docstring + QgsLayoutViewTriangleRubberBand is triangular rubber band for use within QgsLayoutView widgets. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutviewrubberband.h" +%End + public: + + QgsLayoutViewTriangleRubberBand( QgsLayoutView *view = 0 ); +%Docstring + Constructor for QgsLayoutViewTriangleRubberBand. +%End + virtual QgsLayoutViewTriangleRubberBand *create( QgsLayoutView *view ) const /Factory/; + + + ~QgsLayoutViewTriangleRubberBand(); + + virtual void start( QPointF position, Qt::KeyboardModifiers modifiers ); + + virtual void update( QPointF position, Qt::KeyboardModifiers modifiers ); + + virtual QRectF finish( QPointF position = QPointF(), Qt::KeyboardModifiers modifiers = 0 ); + + }; /************************************************************************ * This file has been generated automatically from * diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f3e31a5b1a8..f5d58c8803d 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -354,6 +354,7 @@ SET(QGIS_CORE_SRCS layout/qgslayoutitem.cpp layout/qgslayoutitemmap.cpp layout/qgslayoutitemregistry.cpp + layout/qgslayoutitemshape.cpp layout/qgslayoutmeasurement.cpp layout/qgslayoutmeasurementconverter.cpp layout/qgslayoutobject.cpp @@ -677,6 +678,7 @@ SET(QGIS_CORE_MOC_HDRS layout/qgslayoutitem.h layout/qgslayoutitemmap.h layout/qgslayoutitemregistry.h + layout/qgslayoutitemshape.h layout/qgslayoutobject.h symbology-ng/qgscptcityarchive.h diff --git a/src/core/layout/qgslayoutitemregistry.cpp b/src/core/layout/qgslayoutitemregistry.cpp index 08683ef7e38..0b9b8dbffea 100644 --- a/src/core/layout/qgslayoutitemregistry.cpp +++ b/src/core/layout/qgslayoutitemregistry.cpp @@ -15,6 +15,7 @@ ***************************************************************************/ #include "qgslayoutitemregistry.h" +#include "qgslayoutitemshape.h" #include "qgsgloweffect.h" #include "qgseffectstack.h" #include @@ -41,6 +42,10 @@ bool QgsLayoutItemRegistry::populate() }; addLayoutItemType( new QgsLayoutItemMetadata( 101, QStringLiteral( "temp type" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), createTemporaryItem ) ); + addLayoutItemType( new QgsLayoutItemMetadata( LayoutRectangle, QStringLiteral( "Rectangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), QgsLayoutItemRectangularShape::create ) ); + addLayoutItemType( new QgsLayoutItemMetadata( LayoutEllipse, QStringLiteral( "Ellipse" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), QgsLayoutItemEllipseShape::create ) ); + addLayoutItemType( new QgsLayoutItemMetadata( LayoutTriangle, QStringLiteral( "Triangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), QgsLayoutItemTriangleShape::create ) ); + return true; } diff --git a/src/core/layout/qgslayoutitemregistry.h b/src/core/layout/qgslayoutitemregistry.h index c4abaae1952..58c0d04448d 100644 --- a/src/core/layout/qgslayoutitemregistry.h +++ b/src/core/layout/qgslayoutitemregistry.h @@ -183,6 +183,9 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject // known item types LayoutPage, //!< Page items + LayoutRectangle, //!< Rectangular shape item + LayoutEllipse, //!< Ellipse shape item + LayoutTriangle, //!< Triangle shape item // item types provided by plugins PluginItem, //!< Starting point for plugin item types diff --git a/src/core/layout/qgslayoutitemshape.cpp b/src/core/layout/qgslayoutitemshape.cpp new file mode 100644 index 00000000000..5b11fb8a109 --- /dev/null +++ b/src/core/layout/qgslayoutitemshape.cpp @@ -0,0 +1,173 @@ +/*************************************************************************** + qgslayoutitemshape.cpp + ----------------------- + begin : July 2017 + copyright : (C) 2017 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 "qgslayoutitemshape.h" +#include "qgslayout.h" +#include "qgslayoututils.h" + +#include + +QgsLayoutItemShape::QgsLayoutItemShape( QgsLayout *layout ) + : QgsLayoutItem( layout ) +{ + QgsStringMap properties; + properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) ); + properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) ); + properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) ); + properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) ); + properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) ); + properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) ); + mShapeStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) ); +} + +void QgsLayoutItemShape::setSymbol( QgsFillSymbol *symbol ) +{ + if ( !symbol ) + return; + + mShapeStyleSymbol.reset( symbol->clone() ); +} + +// +// QgsLayoutItemRectangularShape +// + +QgsLayoutItemRectangularShape::QgsLayoutItemRectangularShape( QgsLayout *layout ) + : QgsLayoutItemShape( layout ) + , mCornerRadius( 0.0 ) +{ + +} + +QgsLayoutItemRectangularShape *QgsLayoutItemRectangularShape::create( QgsLayout *layout, const QVariantMap &settings ) +{ + Q_UNUSED( settings ); + return new QgsLayoutItemRectangularShape( layout ); +} + +void QgsLayoutItemRectangularShape::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) +{ + QPainter *painter = context.painter(); + painter->setPen( Qt::NoPen ); + painter->setBrush( Qt::NoBrush ); + + double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ); + + QPolygonF shapePolygon; + if ( mCornerRadius.length() > 0 ) + { + //shapes with curves must be enlarged before conversion to QPolygonF, or + //the curves are approximated too much and appear jaggy + QTransform t = QTransform::fromScale( 100, 100 ); + //inverse transform used to scale created polygons back to expected size + QTransform ti = t.inverted(); + + QPainterPath roundedRectPath; + double radius = mLayout->convertToLayoutUnits( mCornerRadius ) * scale; + roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ), radius, radius ); + QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t ); + shapePolygon = ti.map( roundedPoly ); + } + else + { + shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) ); + } + + QList rings; //empty list + + symbol()->startRender( context ); + symbol()->renderPolygon( shapePolygon, &rings, nullptr, context ); + symbol()->stopRender( context ); +} + + +// +// QgsLayoutItemEllipseShape +// + +QgsLayoutItemEllipseShape::QgsLayoutItemEllipseShape( QgsLayout *layout ) + : QgsLayoutItemShape( layout ) +{ + +} + +QgsLayoutItemEllipseShape *QgsLayoutItemEllipseShape::create( QgsLayout *layout, const QVariantMap &settings ) +{ + Q_UNUSED( settings ); + return new QgsLayoutItemEllipseShape( layout ); +} + +void QgsLayoutItemEllipseShape::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) +{ + QPainter *painter = context.painter(); + painter->setPen( Qt::NoPen ); + painter->setBrush( Qt::NoBrush ); + + double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ); + + //shapes with curves must be enlarged before conversion to QPolygonF, or + //the curves are approximated too much and appear jaggy + QTransform t = QTransform::fromScale( 100, 100 ); + //inverse transform used to scale created polygons back to expected size + QTransform ti = t.inverted(); + + //create an ellipse + QPainterPath ellipsePath; + ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) ); + QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t ); + QPolygonF shapePolygon = ti.map( ellipsePoly ); + + QList rings; //empty list + + symbol()->startRender( context ); + symbol()->renderPolygon( shapePolygon, &rings, nullptr, context ); + symbol()->stopRender( context ); +} + +// +// QgsLayoutItemTriangleShape +// + +QgsLayoutItemTriangleShape::QgsLayoutItemTriangleShape( QgsLayout *layout ) + : QgsLayoutItemShape( layout ) +{ + +} + +QgsLayoutItemTriangleShape *QgsLayoutItemTriangleShape::create( QgsLayout *layout, const QVariantMap &settings ) +{ + Q_UNUSED( settings ); + return new QgsLayoutItemTriangleShape( layout ); +} + +void QgsLayoutItemTriangleShape::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) +{ + QPainter *painter = context.painter(); + painter->setPen( Qt::NoPen ); + painter->setBrush( Qt::NoBrush ); + + double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ); + QPolygonF shapePolygon = QPolygonF() << QPointF( 0, rect().height() * scale ) + << QPointF( rect().width() * scale, rect().height() * scale ) + << QPointF( rect().width() / 2.0 * scale, 0 ) + << QPointF( 0, rect().height() * scale ); + + QList rings; //empty list + + symbol()->startRender( context ); + symbol()->renderPolygon( shapePolygon, &rings, nullptr, context ); + symbol()->stopRender( context ); +} diff --git a/src/core/layout/qgslayoutitemshape.h b/src/core/layout/qgslayoutitemshape.h new file mode 100644 index 00000000000..d7c79809162 --- /dev/null +++ b/src/core/layout/qgslayoutitemshape.h @@ -0,0 +1,158 @@ +/*************************************************************************** + qgslayoutitemshape.h + --------------------- + begin : July 2017 + copyright : (C) 2017 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 QGSLAYOUTITEMSHAPE_H +#define QGSLAYOUTITEMSHAPE_H + +#include "qgis_core.h" +#include "qgslayoutitem.h" +#include "qgslayoutitemregistry.h" +#include "qgssymbol.h" +#include "qgslayoutmeasurement.h" + +/** + * \ingroup core + * \class QgsLayoutItemShape + * \brief Base class for layout items which are basic shapes (e.g. rectangles, ellipses). + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutItemShape : public QgsLayoutItem +{ + Q_OBJECT + + public: + + /** + * Sets the fill \a symbol used to draw the shape. Ownership is not transferred + * and a clone of the symbol is made. + * \see symbol() + */ + void setSymbol( QgsFillSymbol *symbol ); + + /** + * Returns the fill symbol used to draw the shape. + * \see setSymbol() + */ + QgsFillSymbol *symbol() { return mShapeStyleSymbol.get(); } + + protected: + + /** + * Constructor for QgsLayoutItemShape, with the specified parent \a layout. + */ + explicit QgsLayoutItemShape( QgsLayout *layout ); + + private: + std::unique_ptr< QgsFillSymbol > mShapeStyleSymbol; +}; + + +/** + * \ingroup core + * \class QgsLayoutItemRectangularShape + * \brief A rectangular shape item for layouts. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutItemRectangularShape : public QgsLayoutItemShape +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutItemRectangularShape, with the specified parent \a layout. + */ + explicit QgsLayoutItemRectangularShape( QgsLayout *layout ); + virtual int type() const override { return QgsLayoutItemRegistry::LayoutRectangle; } + + static QgsLayoutItemRectangularShape *create( QgsLayout *layout, const QVariantMap &settings ) SIP_FACTORY; + + /** + * Sets the corner \a radius for rounded rectangle corners. + * \see cornerRadius() + */ + void setCornerRadius( QgsLayoutMeasurement radius ) { mCornerRadius = radius; } + + /** + * Returns the corner radius for rounded rectangle corners. + * \see setCornerRadius() + */ + QgsLayoutMeasurement cornerRadius() const { return mCornerRadius; } + + protected: + + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; + + private: + QgsLayoutMeasurement mCornerRadius; +}; + +/** + * \ingroup core + * \class QgsLayoutItemEllipseShape + * \brief A ellipse shape item for layouts. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutItemEllipseShape : public QgsLayoutItemShape +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutItemEllipseShape, with the specified parent \a layout. + */ + explicit QgsLayoutItemEllipseShape( QgsLayout *layout ); + virtual int type() const override { return QgsLayoutItemRegistry::LayoutEllipse; } + + static QgsLayoutItemEllipseShape *create( QgsLayout *layout, const QVariantMap &settings ) SIP_FACTORY; + + protected: + + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; + +}; + +/** + * \ingroup core + * \class QgsLayoutItemTriangleShape + * \brief A triangle shape item for layouts. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutItemTriangleShape : public QgsLayoutItemShape +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutItemTriangleShape, with the specified parent \a layout. + */ + explicit QgsLayoutItemTriangleShape( QgsLayout *layout ); + virtual int type() const override { return QgsLayoutItemRegistry::LayoutTriangle; } + + static QgsLayoutItemTriangleShape *create( QgsLayout *layout, const QVariantMap &settings ) SIP_FACTORY; + + protected: + + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; + +}; + +#endif //QGSLAYOUTITEMSHAPE_H diff --git a/src/gui/layout/qgslayoutitemguiregistry.cpp b/src/gui/layout/qgslayoutitemguiregistry.cpp index f31b6ba2500..09e58c892f4 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.cpp +++ b/src/gui/layout/qgslayoutitemguiregistry.cpp @@ -16,6 +16,7 @@ #include "qgslayoutitemguiregistry.h" #include "qgslayoutviewrubberband.h" +#include "qgslayoutitemregistry.h" #include @@ -40,13 +41,23 @@ bool QgsLayoutItemGuiRegistry::populate() if ( !mMetadata.isEmpty() ) return false; - // add temporary item to register auto createRubberBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand * { return new QgsLayoutViewRectangularRubberBand( view ); } ); + auto createEllipseBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand * + { + return new QgsLayoutViewEllipticalRubberBand( view ); + } ); + auto createTriangleBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand * + { + return new QgsLayoutViewTriangleRubberBand( view ); + } ); addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( 101, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), nullptr, createRubberBand ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutRectangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), nullptr, createRubberBand ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutEllipse, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), nullptr, createEllipseBand ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutTriangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), nullptr, createTriangleBand ) ); return true; } diff --git a/src/gui/layout/qgslayoutviewrubberband.cpp b/src/gui/layout/qgslayoutviewrubberband.cpp index 73500903416..7dc65655160 100644 --- a/src/gui/layout/qgslayoutviewrubberband.cpp +++ b/src/gui/layout/qgslayoutviewrubberband.cpp @@ -19,6 +19,7 @@ #include "qgslayoutview.h" #include #include +#include QgsLayoutViewRubberBand::QgsLayoutViewRubberBand( QgsLayoutView *view ) : mView( view ) @@ -248,3 +249,78 @@ QRectF QgsLayoutViewEllipticalRubberBand::finish( QPointF position, Qt::Keyboard } return updateRect( mRubberBandStartPos, position, constrainSquare, fromCenter ); } + +// +// QgsLayoutViewTriangleRubberBand +// + +QgsLayoutViewTriangleRubberBand::QgsLayoutViewTriangleRubberBand( QgsLayoutView *view ) + : QgsLayoutViewRubberBand( view ) +{ + +} + +QgsLayoutViewTriangleRubberBand *QgsLayoutViewTriangleRubberBand::create( QgsLayoutView *view ) const +{ + return new QgsLayoutViewTriangleRubberBand( view ); +} + +QgsLayoutViewTriangleRubberBand::~QgsLayoutViewTriangleRubberBand() +{ + if ( mRubberBandItem ) + { + layout()->removeItem( mRubberBandItem ); + delete mRubberBandItem; + } +} + +void QgsLayoutViewTriangleRubberBand::start( QPointF position, Qt::KeyboardModifiers ) +{ + QTransform t; + mRubberBandItem = new QGraphicsPolygonItem(); + mRubberBandItem->setBrush( brush() ); + mRubberBandItem->setPen( pen() ); + mRubberBandStartPos = position; + t.translate( position.x(), position.y() ); + mRubberBandItem->setTransform( t ); + mRubberBandItem->setZValue( QgsLayout::ZMapTool ); + layout()->addItem( mRubberBandItem ); + layout()->update(); +} + +void QgsLayoutViewTriangleRubberBand::update( QPointF position, Qt::KeyboardModifiers modifiers ) +{ + if ( !mRubberBandItem ) + { + return; + } + + bool constrainSquare = modifiers & Qt::ShiftModifier; + bool fromCenter = modifiers & Qt::AltModifier; + + QRectF newRect = updateRect( mRubberBandStartPos, position, constrainSquare, fromCenter ); + + QPolygonF shapePolygon = QPolygonF() << QPointF( 0, newRect.height() ) + << QPointF( newRect.width(), newRect.height() ) + << QPointF( newRect.width() / 2.0, 0 ) + << QPointF( 0, newRect.height() ); + + mRubberBandItem->setPolygon( shapePolygon ); + QTransform t; + t.translate( newRect.x(), newRect.y() ); + mRubberBandItem->setTransform( t ); +} + +QRectF QgsLayoutViewTriangleRubberBand::finish( QPointF position, Qt::KeyboardModifiers modifiers ) +{ + bool constrainSquare = modifiers & Qt::ShiftModifier; + bool fromCenter = modifiers & Qt::AltModifier; + + if ( mRubberBandItem ) + { + layout()->removeItem( mRubberBandItem ); + delete mRubberBandItem; + mRubberBandItem = nullptr; + } + return updateRect( mRubberBandStartPos, position, constrainSquare, fromCenter ); +} diff --git a/src/gui/layout/qgslayoutviewrubberband.h b/src/gui/layout/qgslayoutviewrubberband.h index 703c3ab6bd5..38d3479786f 100644 --- a/src/gui/layout/qgslayoutviewrubberband.h +++ b/src/gui/layout/qgslayoutviewrubberband.h @@ -26,6 +26,7 @@ class QgsLayoutView; class QGraphicsRectItem; class QGraphicsEllipseItem; +class QGraphicsPolygonItem; class QgsLayout; /** @@ -199,5 +200,36 @@ class GUI_EXPORT QgsLayoutViewEllipticalRubberBand : public QgsLayoutViewRubberB //! Start of rubber band creation QPointF mRubberBandStartPos; +}; + +/** + * \ingroup gui + * QgsLayoutViewTriangleRubberBand is triangular rubber band for use within QgsLayoutView widgets. + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsLayoutViewTriangleRubberBand : public QgsLayoutViewRubberBand +{ + public: + + /** + * Constructor for QgsLayoutViewTriangleRubberBand. + */ + QgsLayoutViewTriangleRubberBand( QgsLayoutView *view = nullptr ); + QgsLayoutViewTriangleRubberBand *create( QgsLayoutView *view ) const override SIP_FACTORY; + + ~QgsLayoutViewTriangleRubberBand(); + + void start( QPointF position, Qt::KeyboardModifiers modifiers ) override; + void update( QPointF position, Qt::KeyboardModifiers modifiers ) override; + QRectF finish( QPointF position = QPointF(), Qt::KeyboardModifiers modifiers = 0 ) override; + + private: + + //! Rubber band item + QGraphicsPolygonItem *mRubberBandItem = nullptr; + + //! Start of rubber band creation + QPointF mRubberBandStartPos; + }; #endif // QGSLAYOUTVIEWRUBBERBAND_H From cdec70babe3f924f8f408b267823fdb1595fc9ee Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 17:59:14 +1000 Subject: [PATCH 033/266] [needs-docs] Add a new item properties dialog When adding a new item to a layout, if the user just does a single click of the mouse (vs a click and drag to set the new item position/size), then a dialog is shown asking to user for the new item size/position/reference point. This allows easy creation of items with an exact position/size and is common behavior in most proper DTP/illustration apps. --- python/gui/gui_auto.sip | 1 + .../qgslayoutnewitempropertiesdialog.sip | 46 ++ src/gui/CMakeLists.txt | 2 + .../qgslayoutnewitempropertiesdialog.cpp | 44 ++ .../layout/qgslayoutnewitempropertiesdialog.h | 47 ++ src/gui/layout/qgslayoutviewtooladditem.cpp | 34 +- src/ui/layout/qgslayoutnewitemproperties.ui | 401 ++++++++++++++++++ 7 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 python/gui/layout/qgslayoutnewitempropertiesdialog.sip create mode 100644 src/gui/layout/qgslayoutnewitempropertiesdialog.cpp create mode 100644 src/gui/layout/qgslayoutnewitempropertiesdialog.h create mode 100644 src/ui/layout/qgslayoutnewitemproperties.ui diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 3f1b9e7ead5..d39ac42153f 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -278,6 +278,7 @@ %Include layertree/qgslayertreeviewdefaultactions.sip %Include layout/qgslayoutdesignerinterface.sip %Include layout/qgslayoutitemguiregistry.sip +%Include layout/qgslayoutnewitempropertiesdialog.sip %Include layout/qgslayoutruler.sip %Include layout/qgslayoutview.sip %Include layout/qgslayoutviewtool.sip diff --git a/python/gui/layout/qgslayoutnewitempropertiesdialog.sip b/python/gui/layout/qgslayoutnewitempropertiesdialog.sip new file mode 100644 index 00000000000..6861bac51ad --- /dev/null +++ b/python/gui/layout/qgslayoutnewitempropertiesdialog.sip @@ -0,0 +1,46 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/layout/qgslayoutnewitempropertiesdialog.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsLayoutNewItemPropertiesDialog : QDialog +{ +%Docstring + A dialog for configuring properties like the size and position of new layout items. +%End + +%TypeHeaderCode +#include "qgslayoutnewitempropertiesdialog.h" +%End + public: + + QgsLayoutNewItemPropertiesDialog( QWidget *parent = 0, Qt::WindowFlags flags = 0 ); + + + void setInitialItemPosition( QPointF position ); + + QgsLayoutPoint itemPosition() const; +%Docstring + :rtype: QgsLayoutPoint +%End + + QgsLayoutSize itemSize() const; +%Docstring + :rtype: QgsLayoutSize +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/layout/qgslayoutnewitempropertiesdialog.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a9716063428..b6b40afeba3 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -159,6 +159,7 @@ SET(QGIS_GUI_SRCS layertree/qgslayertreeviewdefaultactions.cpp layout/qgslayoutitemguiregistry.cpp + layout/qgslayoutnewitempropertiesdialog.cpp layout/qgslayoutruler.cpp layout/qgslayoutview.cpp layout/qgslayoutviewmouseevent.cpp @@ -647,6 +648,7 @@ SET(QGIS_GUI_MOC_HDRS layout/qgslayoutdesignerinterface.h layout/qgslayoutitemguiregistry.h + layout/qgslayoutnewitempropertiesdialog.h layout/qgslayoutruler.h layout/qgslayoutview.h layout/qgslayoutviewtool.h diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp new file mode 100644 index 00000000000..dabfeca87f2 --- /dev/null +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + qgslayoutnewitempropertiesdialog.cpp + ------------------------------------ + Date : July 2017 + Copyright : (C) 2017 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 "qgslayoutnewitempropertiesdialog.h" +#include "qgssettings.h" + +QgsLayoutNewItemPropertiesDialog::QgsLayoutNewItemPropertiesDialog( QWidget *parent, Qt::WindowFlags flags ) + : QDialog( parent, flags ) +{ + setupUi( this ); + QgsSettings settings; + double lastWidth = settings.value( QStringLiteral( "LayoutDesigner/lastItemWidth" ), QStringLiteral( "50" ) ).toDouble(); + double lastHeight = settings.value( QStringLiteral( "LayoutDesigner/lastItemHeight" ), QStringLiteral( "50" ) ).toDouble(); + mWidthSpin->setValue( lastWidth ); + mHeightSpin->setValue( lastHeight ); +} + +void QgsLayoutNewItemPropertiesDialog::setInitialItemPosition( QPointF position ) +{ + mXPosSpin->setValue( position.x() ); + mYPosSpin->setValue( position.y() ); +} + +QgsLayoutPoint QgsLayoutNewItemPropertiesDialog::itemPosition() const +{ + return QgsLayoutPoint( mXPosSpin->value(), mYPosSpin->value() ); +} + +QgsLayoutSize QgsLayoutNewItemPropertiesDialog::itemSize() const +{ + return QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value() ); +} diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.h b/src/gui/layout/qgslayoutnewitempropertiesdialog.h new file mode 100644 index 00000000000..b6db4b3a2a4 --- /dev/null +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.h @@ -0,0 +1,47 @@ +/*************************************************************************** + qgslayoutnewitempropertiesdialog.h + ---------------------------------- + Date : July 2017 + Copyright : (C) 2017 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 QGSLAYOUTNEWITEMPROPERTIESDIALOG_H +#define QGSLAYOUTNEWITEMPROPERTIESDIALOG_H + +#include "qgis.h" +#include "qgis_gui.h" +#include "ui_qgslayoutnewitemproperties.h" + +#include "qgslayoutsize.h" +#include "qgslayoutpoint.h" + +/** + * \ingroup gui + * \brief A dialog for configuring properties like the size and position of new layout items. + */ +class GUI_EXPORT QgsLayoutNewItemPropertiesDialog : public QDialog, private Ui::QgsLayoutNewItemPropertiesDialog +{ + Q_OBJECT + + public: + + QgsLayoutNewItemPropertiesDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = 0 ); + + + void setInitialItemPosition( QPointF position ); + + QgsLayoutPoint itemPosition() const; + + QgsLayoutSize itemSize() const; + +}; + +#endif // QGSLAYOUTNEWITEMPROPERTIESDIALOG_H diff --git a/src/gui/layout/qgslayoutviewtooladditem.cpp b/src/gui/layout/qgslayoutviewtooladditem.cpp index b4634fbb530..2c5e94d305e 100644 --- a/src/gui/layout/qgslayoutviewtooladditem.cpp +++ b/src/gui/layout/qgslayoutviewtooladditem.cpp @@ -24,6 +24,8 @@ #include "qgslayoutviewrubberband.h" #include "qgsgui.h" #include "qgslayoutitemguiregistry.h" +#include "qgslayoutnewitempropertiesdialog.h" +#include "qgssettings.h" #include #include #include @@ -81,13 +83,37 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even QRectF rect = mRubberBand->finish( event->layoutPoint(), event->modifiers() ); + QgsLayoutItem *item = QgsApplication::layoutItemRegistry()->createItem( mItemType, layout() ); + // click? or click-and-drag? bool clickOnly = !isClickAndDrag( mMousePressStartPos, event->pos() ); - Q_UNUSED( clickOnly ); + if ( clickOnly ) + { + QgsLayoutNewItemPropertiesDialog dlg( view() ); + dlg.setInitialItemPosition( event->layoutPoint() ); + if ( dlg.exec() ) + { + item->attemptResize( dlg.itemSize() ); + item->attemptMove( dlg.itemPosition() ); + } + else + { + delete item; + return; + } + } + else + { + item->attemptResize( QgsLayoutSize( rect.width(), rect.height(), QgsUnitTypes::LayoutMillimeters ) ); + item->attemptMove( QgsLayoutPoint( rect.left(), rect.top(), QgsUnitTypes::LayoutMillimeters ) ); + } + + // record last created item size + QgsSettings settings; + settings.setValue( QStringLiteral( "LayoutDesigner/lastItemWidth" ), item->sizeWithUnits().width() ); + settings.setValue( QStringLiteral( "LayoutDesigner/lastItemHeight" ), item->sizeWithUnits().height() ); + - QgsLayoutItem *item = QgsApplication::layoutItemRegistry()->createItem( mItemType, layout() ); - item->attemptResize( QgsLayoutSize( rect.width(), rect.height(), QgsUnitTypes::LayoutMillimeters ) ); - item->attemptMove( QgsLayoutPoint( rect.left(), rect.top(), QgsUnitTypes::LayoutMillimeters ) ); layout()->addItem( item ); } diff --git a/src/ui/layout/qgslayoutnewitemproperties.ui b/src/ui/layout/qgslayoutnewitemproperties.ui new file mode 100644 index 00000000000..b947dc5bb7a --- /dev/null +++ b/src/ui/layout/qgslayoutnewitemproperties.ui @@ -0,0 +1,401 @@ + + + QgsLayoutNewItemPropertiesDialog + + + + 0 + 0 + 391 + 263 + + + + New Item Properties + + + + + + Position and size + + + + + + + + mm + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + mm + + + 3 + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + false + + + + + + + Height + + + + + + + mm + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + mm + + + 3 + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + false + + + + + + + Width + + + + + + + Y + + + + + + + X + + + + + + + + + + + + Reference point + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+
+ + mXPosSpin + mYPosSpin + mWidthSpin + mHeightSpin + mUpperLeftCheckBox + mUpperMiddleCheckBox + mUpperRightCheckBox + mMiddleLeftCheckBox + mMiddleCheckBox + mMiddleRightCheckBox + mLowerLeftCheckBox + mLowerMiddleCheckBox + mLowerRightCheckBox + + + + + buttonBox + accepted() + QgsLayoutNewItemPropertiesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QgsLayoutNewItemPropertiesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
From d14f3b9c137ab3e760b4c005adb7f4c4074249a8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 11:38:21 +1000 Subject: [PATCH 034/266] Add QComboBox subclass widget for selecting layout units --- python/gui/gui_auto.sip | 1 + python/gui/layout/qgslayoutunitscombobox.sip | 56 +++++++++++++++++ src/core/qgsapplication.cpp | 2 + src/gui/CMakeLists.txt | 2 + src/gui/layout/qgslayoutunitscombobox.cpp | 53 ++++++++++++++++ src/gui/layout/qgslayoutunitscombobox.h | 62 +++++++++++++++++++ tests/src/python/CMakeLists.txt | 3 +- .../src/python/test_qgslayoutunitscombobox.py | 47 ++++++++++++++ 8 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 python/gui/layout/qgslayoutunitscombobox.sip create mode 100644 src/gui/layout/qgslayoutunitscombobox.cpp create mode 100644 src/gui/layout/qgslayoutunitscombobox.h create mode 100644 tests/src/python/test_qgslayoutunitscombobox.py diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index d39ac42153f..81e0ad5f34b 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -280,6 +280,7 @@ %Include layout/qgslayoutitemguiregistry.sip %Include layout/qgslayoutnewitempropertiesdialog.sip %Include layout/qgslayoutruler.sip +%Include layout/qgslayoutunitscombobox.sip %Include layout/qgslayoutview.sip %Include layout/qgslayoutviewtool.sip %Include layout/qgslayoutviewtooladditem.sip diff --git a/python/gui/layout/qgslayoutunitscombobox.sip b/python/gui/layout/qgslayoutunitscombobox.sip new file mode 100644 index 00000000000..5b7027d8b95 --- /dev/null +++ b/python/gui/layout/qgslayoutunitscombobox.sip @@ -0,0 +1,56 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/layout/qgslayoutunitscombobox.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + +class QgsLayoutUnitsComboBox : QComboBox +{ +%Docstring + A custom combo box for selecting units for layout settings. + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutunitscombobox.h" +%End + public: + + QgsLayoutUnitsComboBox( QWidget *parent /TransferThis/ = 0 ); +%Docstring + Constructor for QgsLayoutUnitsComboBox. +%End + + QgsUnitTypes::LayoutUnit unit() const; +%Docstring + Returns the unit currently selected in the combo box. +.. seealso:: setUnit() + :rtype: QgsUnitTypes.LayoutUnit +%End + + void setUnit( QgsUnitTypes::LayoutUnit unit ); +%Docstring + Sets the ``unit`` currently selected in the combo box. +.. seealso:: unit() +%End + + signals: + + void changed( QgsUnitTypes::LayoutUnit unit ); +%Docstring + Emitted when the ``unit`` is changed. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/layout/qgslayoutunitscombobox.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index fef4b85cc20..17f05552d15 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -38,6 +38,7 @@ #include "qgsmessagelog.h" #include "qgsannotationregistry.h" #include "qgssettings.h" +#include "qgsunittypes.h" #include "gps/qgsgpsconnectionregistry.h" #include "processing/qgsprocessingregistry.h" @@ -135,6 +136,7 @@ void QgsApplication::init( QString customConfigPath ) qRegisterMetaType( "QgsGeometry::Error" ); qRegisterMetaType( "QgsProcessingFeatureSourceDefinition" ); qRegisterMetaType( "QgsProcessingOutputLayerDefinition" ); + qRegisterMetaType( "QgsUnitTypes::LayoutUnit" ); QString prefixPath( getenv( "QGIS_PREFIX_PATH" ) ? getenv( "QGIS_PREFIX_PATH" ) : applicationDirPath() ); // QgsDebugMsg( QString( "prefixPath(): %1" ).arg( prefixPath ) ); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b6b40afeba3..a7b51b49c8e 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -161,6 +161,7 @@ SET(QGIS_GUI_SRCS layout/qgslayoutitemguiregistry.cpp layout/qgslayoutnewitempropertiesdialog.cpp layout/qgslayoutruler.cpp + layout/qgslayoutunitscombobox.cpp layout/qgslayoutview.cpp layout/qgslayoutviewmouseevent.cpp layout/qgslayoutviewrubberband.cpp @@ -650,6 +651,7 @@ SET(QGIS_GUI_MOC_HDRS layout/qgslayoutitemguiregistry.h layout/qgslayoutnewitempropertiesdialog.h layout/qgslayoutruler.h + layout/qgslayoutunitscombobox.h layout/qgslayoutview.h layout/qgslayoutviewtool.h layout/qgslayoutviewtooladditem.h diff --git a/src/gui/layout/qgslayoutunitscombobox.cpp b/src/gui/layout/qgslayoutunitscombobox.cpp new file mode 100644 index 00000000000..9517c256c64 --- /dev/null +++ b/src/gui/layout/qgslayoutunitscombobox.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + qgslayoutunitscombobox.cpp + -------------------------- + Date : July 2017 + Copyright : (C) 2017 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 "qgslayoutunitscombobox.h" + +QgsLayoutUnitsComboBox::QgsLayoutUnitsComboBox( QWidget *parent ) + : QComboBox( parent ) +{ + addItem( tr( "mm" ), QgsUnitTypes::LayoutMillimeters ); + setItemData( 0, tr( "Millimeters" ), Qt::ToolTipRole ); + addItem( tr( "cm" ), QgsUnitTypes::LayoutCentimeters ); + setItemData( 1, tr( "Centimeters" ), Qt::ToolTipRole ); + addItem( tr( "m" ), QgsUnitTypes::LayoutMeters ); + setItemData( 2, tr( "Meters" ), Qt::ToolTipRole ); + addItem( tr( "in" ), QgsUnitTypes::LayoutInches ); + setItemData( 3, tr( "Inches" ), Qt::ToolTipRole ); + addItem( tr( "ft" ), QgsUnitTypes::LayoutFeet ); + setItemData( 4, tr( "Feet" ), Qt::ToolTipRole ); + addItem( tr( "pt" ), QgsUnitTypes::LayoutPoints ); + setItemData( 5, tr( "Points" ), Qt::ToolTipRole ); + addItem( tr( "pica" ), QgsUnitTypes::LayoutPicas ); + setItemData( 6, tr( "Picas" ), Qt::ToolTipRole ); + addItem( tr( "px" ), QgsUnitTypes::LayoutPixels ); + setItemData( 7, tr( "Pixels" ), Qt::ToolTipRole ); + connect( this, static_cast( &QgsLayoutUnitsComboBox::currentIndexChanged ), this, [ = ]( int ) + { + emit changed( unit() ); + } ); +} + +QgsUnitTypes::LayoutUnit QgsLayoutUnitsComboBox::unit() const +{ + return static_cast< QgsUnitTypes::LayoutUnit >( currentData().toInt() ); +} + +void QgsLayoutUnitsComboBox::setUnit( QgsUnitTypes::LayoutUnit unit ) +{ + setCurrentIndex( findData( unit ) ); +} + +#include "qgslayoutunitscombobox.h" diff --git a/src/gui/layout/qgslayoutunitscombobox.h b/src/gui/layout/qgslayoutunitscombobox.h new file mode 100644 index 00000000000..7fbfff11de9 --- /dev/null +++ b/src/gui/layout/qgslayoutunitscombobox.h @@ -0,0 +1,62 @@ +/*************************************************************************** + qgslayoutunitscombobox.h + ------------------------ + Date : July 2017 + Copyright : (C) 2017 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 QGSLAYOUTUNITSCOMBOBOX_H +#define QGSLAYOUTUNITSCOMBOBOX_H + +#include +#include "qgis_gui.h" +#include "qgis_sip.h" +#include "qgsunittypes.h" + +/** + * \ingroup gui + * A custom combo box for selecting units for layout settings. + * + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsLayoutUnitsComboBox : public QComboBox +{ + Q_OBJECT + Q_PROPERTY( QgsUnitTypes::LayoutUnit unit READ unit WRITE setUnit NOTIFY changed ) + + public: + + /** + * Constructor for QgsLayoutUnitsComboBox. + */ + QgsLayoutUnitsComboBox( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Returns the unit currently selected in the combo box. + * \see setUnit() + */ + QgsUnitTypes::LayoutUnit unit() const; + + /** + * Sets the \a unit currently selected in the combo box. + * \see unit() + */ + void setUnit( QgsUnitTypes::LayoutUnit unit ); + + signals: + + /** + * Emitted when the \a unit is changed. + */ + void changed( QgsUnitTypes::LayoutUnit unit ); + +}; + +#endif // QGSLAYOUTUNITSCOMBOBOX_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index df4ab669b36..feb45b43ac4 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -70,12 +70,13 @@ ADD_PYTHON_TEST(PyQgsGeometryValidator test_qgsgeometryvalidator.py) ADD_PYTHON_TEST(PyQgsGraduatedSymbolRenderer test_qgsgraduatedsymbolrenderer.py) ADD_PYTHON_TEST(PyQgsInterval test_qgsinterval.py) ADD_PYTHON_TEST(PyQgsJsonUtils test_qgsjsonutils.py) +ADD_PYTHON_TEST(PyQgsLayerMetadata test_qgslayermetadata.py) ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.py) ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py) ADD_PYTHON_TEST(PyQgsLayoutManager test_qgslayoutmanager.py) ADD_PYTHON_TEST(PyQgsLayoutView test_qgslayoutview.py) +ADD_PYTHON_TEST(PyQgsLayoutUnitsComboBox test_qgslayoutunitscombobox.py) ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py) -ADD_PYTHON_TEST(PyQgsLayerMetadata test_qgslayermetadata.py) ADD_PYTHON_TEST(PyQgsLocator test_qgslocator.py) ADD_PYTHON_TEST(PyQgsMapCanvas test_qgsmapcanvas.py) ADD_PYTHON_TEST(PyQgsMapCanvasAnnotationItem test_qgsmapcanvasannotationitem.py) diff --git a/tests/src/python/test_qgslayoutunitscombobox.py b/tests/src/python/test_qgslayoutunitscombobox.py new file mode 100644 index 00000000000..f81c42f390e --- /dev/null +++ b/tests/src/python/test_qgslayoutunitscombobox.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsLayoutUnitsComboBox + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '18/07/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.core import QgsUnitTypes +from qgis.gui import QgsLayoutUnitsComboBox + +from qgis.PyQt.QtTest import QSignalSpy +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsLayoutUnitsComboBox(unittest.TestCase): + + def testGettersSetters(self): + """ test widget getters/setters """ + w = qgis.gui.QgsLayoutUnitsComboBox() + + w.setUnit(QgsUnitTypes.LayoutPixels) + self.assertEqual(w.unit(), QgsUnitTypes.LayoutPixels) + + def test_ChangedSignals(self): + """ test that signals are correctly emitted when setting unit""" + w = qgis.gui.QgsLayoutUnitsComboBox() + + spy = QSignalSpy(w.changed) + w.setUnit(QgsUnitTypes.LayoutPixels) + + self.assertEqual(len(spy), 1) + self.assertEqual(spy[0][0], QgsUnitTypes.LayoutPixels) + + +if __name__ == '__main__': + unittest.main() From 19a7863ea49c8076d2df94d88f138898e7ad25a2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 11:43:41 +1000 Subject: [PATCH 035/266] Add methods to construct layout size/point from QSizeF/QPointF --- python/core/layout/qgslayoutpoint.sip | 5 +++++ python/core/layout/qgslayoutsize.sip | 5 +++++ src/core/layout/qgslayoutpoint.cpp | 8 ++++++++ src/core/layout/qgslayoutpoint.h | 5 +++++ src/core/layout/qgslayoutsize.cpp | 7 +++++++ src/core/layout/qgslayoutsize.h | 5 +++++ tests/src/core/testqgslayoutunits.cpp | 12 ++++++++++++ 7 files changed, 47 insertions(+) diff --git a/python/core/layout/qgslayoutpoint.sip b/python/core/layout/qgslayoutpoint.sip index baa3ed9eae5..c3273335ee3 100644 --- a/python/core/layout/qgslayoutpoint.sip +++ b/python/core/layout/qgslayoutpoint.sip @@ -36,6 +36,11 @@ class QgsLayoutPoint Constructor for QgsLayoutPoint. %End + explicit QgsLayoutPoint( const QPointF point, const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); +%Docstring + Constructor for QgsLayoutPoint. +%End + explicit QgsLayoutPoint( const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); %Docstring Constructor for an empty point, where both x and y are set to 0. diff --git a/python/core/layout/qgslayoutsize.sip b/python/core/layout/qgslayoutsize.sip index 9a0d8917879..abd558220cc 100644 --- a/python/core/layout/qgslayoutsize.sip +++ b/python/core/layout/qgslayoutsize.sip @@ -40,6 +40,11 @@ class QgsLayoutSize \param units units for width and height %End + explicit QgsLayoutSize( const QSizeF size, const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); +%Docstring + Constructor for QgsLayoutSize. +%End + explicit QgsLayoutSize( const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); %Docstring Constructor for an empty layout size diff --git a/src/core/layout/qgslayoutpoint.cpp b/src/core/layout/qgslayoutpoint.cpp index c1703d86608..7480f5837a7 100644 --- a/src/core/layout/qgslayoutpoint.cpp +++ b/src/core/layout/qgslayoutpoint.cpp @@ -28,6 +28,14 @@ QgsLayoutPoint::QgsLayoutPoint( const double x, const double y, const QgsUnitTyp } +QgsLayoutPoint::QgsLayoutPoint( const QPointF point, const QgsUnitTypes::LayoutUnit units ) + : mX( point.x() ) + , mY( point.y() ) + , mUnits( units ) +{ + +} + QgsLayoutPoint::QgsLayoutPoint( const QgsUnitTypes::LayoutUnit units ) : mUnits( units ) { diff --git a/src/core/layout/qgslayoutpoint.h b/src/core/layout/qgslayoutpoint.h index cd92b67db7b..8ce91be7ee2 100644 --- a/src/core/layout/qgslayoutpoint.h +++ b/src/core/layout/qgslayoutpoint.h @@ -45,6 +45,11 @@ class CORE_EXPORT QgsLayoutPoint */ QgsLayoutPoint( const double x, const double y, const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); + /** + * Constructor for QgsLayoutPoint. + */ + explicit QgsLayoutPoint( const QPointF point, const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); + /** * Constructor for an empty point, where both x and y are set to 0. * \param units units for measurement diff --git a/src/core/layout/qgslayoutsize.cpp b/src/core/layout/qgslayoutsize.cpp index 3d2d459cc30..7b279f015f8 100644 --- a/src/core/layout/qgslayoutsize.cpp +++ b/src/core/layout/qgslayoutsize.cpp @@ -26,6 +26,13 @@ QgsLayoutSize::QgsLayoutSize( const double width, const double height, const Qgs { } +QgsLayoutSize::QgsLayoutSize( const QSizeF size, const QgsUnitTypes::LayoutUnit units ) + : mWidth( size.width() ) + , mHeight( size.height() ) + , mUnits( units ) +{ +} + QgsLayoutSize::QgsLayoutSize( const QgsUnitTypes::LayoutUnit units ) : mUnits( units ) { diff --git a/src/core/layout/qgslayoutsize.h b/src/core/layout/qgslayoutsize.h index 32c8b3a9763..1ea8e0c273c 100644 --- a/src/core/layout/qgslayoutsize.h +++ b/src/core/layout/qgslayoutsize.h @@ -49,6 +49,11 @@ class CORE_EXPORT QgsLayoutSize */ QgsLayoutSize( const double width, const double height, const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); + /** + * Constructor for QgsLayoutSize. + */ + explicit QgsLayoutSize( const QSizeF size, const QgsUnitTypes::LayoutUnit units = QgsUnitTypes::LayoutMillimeters ); + /** * Constructor for an empty layout size * \param units units for measurement diff --git a/tests/src/core/testqgslayoutunits.cpp b/tests/src/core/testqgslayoutunits.cpp index f86e7879017..43e38822d98 100644 --- a/tests/src/core/testqgslayoutunits.cpp +++ b/tests/src/core/testqgslayoutunits.cpp @@ -259,6 +259,12 @@ void TestQgsLayoutUnits::createSize() QCOMPARE( empty.units(), QgsUnitTypes::LayoutPixels ); QCOMPARE( empty.width(), 0.0 ); QCOMPARE( empty.height(), 0.0 ); + + //test constructing from QSizeF + QgsLayoutSize fromQSizeF( QSizeF( 17.0, 18.0 ), QgsUnitTypes::LayoutInches ); + QCOMPARE( fromQSizeF.units(), QgsUnitTypes::LayoutInches ); + QCOMPARE( fromQSizeF.width(), 17.0 ); + QCOMPARE( fromQSizeF.height(), 18.0 ); } void TestQgsLayoutUnits::sizeGettersSetters() @@ -393,6 +399,12 @@ void TestQgsLayoutUnits::createPoint() QCOMPARE( empty.units(), QgsUnitTypes::LayoutPixels ); QCOMPARE( empty.x(), 0.0 ); QCOMPARE( empty.y(), 0.0 ); + + //test constructing from QPointF + QgsLayoutPoint fromQPointF( QPointF( 17.0, 18.0 ), QgsUnitTypes::LayoutInches ); + QCOMPARE( fromQPointF.units(), QgsUnitTypes::LayoutInches ); + QCOMPARE( fromQPointF.x(), 17.0 ); + QCOMPARE( fromQPointF.y(), 18.0 ); } void TestQgsLayoutUnits::pointGettersSetters() From de2626d65c59db9831f18d9532621d69d1544d8e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 12:02:55 +1000 Subject: [PATCH 036/266] Add unit types to new item properties dialog, handle different reference point settings --- .../qgslayoutnewitempropertiesdialog.sip | 44 ++++++- .../qgslayoutnewitempropertiesdialog.cpp | 121 ++++++++++++++++-- .../layout/qgslayoutnewitempropertiesdialog.h | 48 ++++++- src/gui/layout/qgslayoutviewtooladditem.cpp | 7 +- src/ui/layout/qgslayoutnewitemproperties.ui | 91 +++++++------ tests/src/python/CMakeLists.txt | 1 + .../test_qgslayoutitempropertiesdialog.py | 49 +++++++ 7 files changed, 301 insertions(+), 60 deletions(-) create mode 100644 tests/src/python/test_qgslayoutitempropertiesdialog.py diff --git a/python/gui/layout/qgslayoutnewitempropertiesdialog.sip b/python/gui/layout/qgslayoutnewitempropertiesdialog.sip index 6861bac51ad..a4c5110af04 100644 --- a/python/gui/layout/qgslayoutnewitempropertiesdialog.sip +++ b/python/gui/layout/qgslayoutnewitempropertiesdialog.sip @@ -9,10 +9,15 @@ -class QgsLayoutNewItemPropertiesDialog : QDialog +class QgsLayoutItemPropertiesDialog : QDialog { %Docstring - A dialog for configuring properties like the size and position of new layout items. + A dialog for configuring properties like the size and position of layout items. + + This is usually used only when constructing new layout items, allowing users to precisely + enter their sizes and positions. + +.. versionadded:: 3.0 %End %TypeHeaderCode @@ -20,21 +25,50 @@ class QgsLayoutNewItemPropertiesDialog : QDialog %End public: - QgsLayoutNewItemPropertiesDialog( QWidget *parent = 0, Qt::WindowFlags flags = 0 ); + QgsLayoutItemPropertiesDialog( QWidget *parent = 0, Qt::WindowFlags flags = 0 ); +%Docstring + Constructor for QgsLayoutNewItemPropertiesDialog. +%End - - void setInitialItemPosition( QPointF position ); + void setItemPosition( QgsLayoutPoint position ); +%Docstring + Sets the item ``position`` to show in the dialog. +.. seealso:: itemPosition() +%End QgsLayoutPoint itemPosition() const; %Docstring + Returns the current item position defined by the dialog. +.. seealso:: setItemPosition() :rtype: QgsLayoutPoint %End + void setItemSize( QgsLayoutSize size ); +%Docstring + Sets the item ``size`` to show in the dialog. +.. seealso:: itemSize() +%End + QgsLayoutSize itemSize() const; %Docstring + Returns the item size defined by the dialog. +.. seealso:: setItemSize() :rtype: QgsLayoutSize %End + QgsLayoutItem::ReferencePoint referencePoint() const; +%Docstring + Returns the item reference point defined by the dialog. +.. seealso:: setReferencePoint() + :rtype: QgsLayoutItem.ReferencePoint +%End + + void setReferencePoint( QgsLayoutItem::ReferencePoint point ); +%Docstring + Sets the item reference ``point`` defined to show in the dialog. +.. seealso:: referencePoint() +%End + }; /************************************************************************ diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp index dabfeca87f2..0aa0ba4f935 100644 --- a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp @@ -16,29 +16,134 @@ #include "qgslayoutnewitempropertiesdialog.h" #include "qgssettings.h" -QgsLayoutNewItemPropertiesDialog::QgsLayoutNewItemPropertiesDialog( QWidget *parent, Qt::WindowFlags flags ) +QgsLayoutItemPropertiesDialog::QgsLayoutItemPropertiesDialog( QWidget *parent, Qt::WindowFlags flags ) : QDialog( parent, flags ) { setupUi( this ); + + //make button exclusive + QButtonGroup *buttonGroup = new QButtonGroup( this ); + buttonGroup->addButton( mUpperLeftCheckBox ); + buttonGroup->addButton( mUpperMiddleCheckBox ); + buttonGroup->addButton( mUpperRightCheckBox ); + buttonGroup->addButton( mMiddleLeftCheckBox ); + buttonGroup->addButton( mMiddleCheckBox ); + buttonGroup->addButton( mMiddleRightCheckBox ); + buttonGroup->addButton( mLowerLeftCheckBox ); + buttonGroup->addButton( mLowerMiddleCheckBox ); + buttonGroup->addButton( mLowerRightCheckBox ); + buttonGroup->setExclusive( true ); + QgsSettings settings; double lastWidth = settings.value( QStringLiteral( "LayoutDesigner/lastItemWidth" ), QStringLiteral( "50" ) ).toDouble(); double lastHeight = settings.value( QStringLiteral( "LayoutDesigner/lastItemHeight" ), QStringLiteral( "50" ) ).toDouble(); - mWidthSpin->setValue( lastWidth ); - mHeightSpin->setValue( lastHeight ); + QgsUnitTypes::LayoutUnit lastSizeUnit = static_cast< QgsUnitTypes::LayoutUnit >( settings.value( QStringLiteral( "LayoutDesigner/lastSizeUnit" ) ).toInt() ); + setItemSize( QgsLayoutSize( lastWidth, lastHeight, lastSizeUnit ) ); } -void QgsLayoutNewItemPropertiesDialog::setInitialItemPosition( QPointF position ) +void QgsLayoutItemPropertiesDialog::setItemPosition( QgsLayoutPoint position ) { mXPosSpin->setValue( position.x() ); mYPosSpin->setValue( position.y() ); + mPosUnitsComboBox->setUnit( position.units() ); } -QgsLayoutPoint QgsLayoutNewItemPropertiesDialog::itemPosition() const +QgsLayoutPoint QgsLayoutItemPropertiesDialog::itemPosition() const { - return QgsLayoutPoint( mXPosSpin->value(), mYPosSpin->value() ); + return QgsLayoutPoint( mXPosSpin->value(), mYPosSpin->value(), mPosUnitsComboBox->unit() ); } -QgsLayoutSize QgsLayoutNewItemPropertiesDialog::itemSize() const +void QgsLayoutItemPropertiesDialog::setItemSize( QgsLayoutSize size ) { - return QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value() ); + mWidthSpin->setValue( size.width() ); + mHeightSpin->setValue( size.height() ); + mSizeUnitsComboBox->setUnit( size.units() ); +} + +QgsLayoutSize QgsLayoutItemPropertiesDialog::itemSize() const +{ + return QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value(), mSizeUnitsComboBox->unit() ); +} + +QgsLayoutItem::ReferencePoint QgsLayoutItemPropertiesDialog::referencePoint() const +{ + if ( mUpperLeftCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::UpperLeft; + } + else if ( mUpperMiddleCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::UpperMiddle; + } + else if ( mUpperRightCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::UpperRight; + } + else if ( mMiddleLeftCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::MiddleLeft; + } + else if ( mMiddleCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::Middle; + } + else if ( mMiddleRightCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::MiddleRight; + } + else if ( mLowerLeftCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::LowerLeft; + } + else if ( mLowerMiddleCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::LowerMiddle; + } + else if ( mLowerRightCheckBox->checkState() == Qt::Checked ) + { + return QgsLayoutItem::LowerRight; + } + return QgsLayoutItem::UpperLeft; +} + +void QgsLayoutItemPropertiesDialog::setReferencePoint( QgsLayoutItem::ReferencePoint point ) +{ + switch ( point ) + { + case QgsLayoutItem::UpperLeft: + mUpperLeftCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::UpperMiddle: + mUpperMiddleCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::UpperRight: + mUpperRightCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::MiddleLeft: + mMiddleLeftCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::Middle: + mMiddleCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::MiddleRight: + mMiddleRightCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::LowerLeft: + mLowerLeftCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::LowerMiddle: + mLowerMiddleCheckBox->setChecked( true ); + break; + + case QgsLayoutItem::LowerRight: + mLowerRightCheckBox->setChecked( true ); + break; + } } diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.h b/src/gui/layout/qgslayoutnewitempropertiesdialog.h index b6db4b3a2a4..736125bfd21 100644 --- a/src/gui/layout/qgslayoutnewitempropertiesdialog.h +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.h @@ -22,26 +22,64 @@ #include "qgslayoutsize.h" #include "qgslayoutpoint.h" +#include "qgslayoutitem.h" /** * \ingroup gui - * \brief A dialog for configuring properties like the size and position of new layout items. + * \brief A dialog for configuring properties like the size and position of layout items. + * + * This is usually used only when constructing new layout items, allowing users to precisely + * enter their sizes and positions. + * + * \since QGIS 3.0 */ -class GUI_EXPORT QgsLayoutNewItemPropertiesDialog : public QDialog, private Ui::QgsLayoutNewItemPropertiesDialog +class GUI_EXPORT QgsLayoutItemPropertiesDialog : public QDialog, private Ui::QgsLayoutNewItemPropertiesDialog { Q_OBJECT public: - QgsLayoutNewItemPropertiesDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = 0 ); + /** + * Constructor for QgsLayoutNewItemPropertiesDialog. + */ + QgsLayoutItemPropertiesDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = 0 ); + /** + * Sets the item \a position to show in the dialog. + * \see itemPosition() + */ + void setItemPosition( QgsLayoutPoint position ); - void setInitialItemPosition( QPointF position ); - + /** + * Returns the current item position defined by the dialog. + * \see setItemPosition() + */ QgsLayoutPoint itemPosition() const; + /** + * Sets the item \a size to show in the dialog. + * \see itemSize() + */ + void setItemSize( QgsLayoutSize size ); + + /** + * Returns the item size defined by the dialog. + * \see setItemSize() + */ QgsLayoutSize itemSize() const; + /** + * Returns the item reference point defined by the dialog. + * \see setReferencePoint() + */ + QgsLayoutItem::ReferencePoint referencePoint() const; + + /** + * Sets the item reference \a point defined to show in the dialog. + * \see referencePoint() + */ + void setReferencePoint( QgsLayoutItem::ReferencePoint point ); + }; #endif // QGSLAYOUTNEWITEMPROPERTIESDIALOG_H diff --git a/src/gui/layout/qgslayoutviewtooladditem.cpp b/src/gui/layout/qgslayoutviewtooladditem.cpp index 2c5e94d305e..5e008f509dc 100644 --- a/src/gui/layout/qgslayoutviewtooladditem.cpp +++ b/src/gui/layout/qgslayoutviewtooladditem.cpp @@ -89,10 +89,11 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even bool clickOnly = !isClickAndDrag( mMousePressStartPos, event->pos() ); if ( clickOnly ) { - QgsLayoutNewItemPropertiesDialog dlg( view() ); - dlg.setInitialItemPosition( event->layoutPoint() ); + QgsLayoutItemPropertiesDialog dlg( view() ); + dlg.setItemPosition( QgsLayoutPoint( event->layoutPoint(), layout()->units() ) ); if ( dlg.exec() ) { + item->setReferencePoint( dlg.referencePoint() ); item->attemptResize( dlg.itemSize() ); item->attemptMove( dlg.itemPosition() ); } @@ -112,7 +113,7 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even QgsSettings settings; settings.setValue( QStringLiteral( "LayoutDesigner/lastItemWidth" ), item->sizeWithUnits().width() ); settings.setValue( QStringLiteral( "LayoutDesigner/lastItemHeight" ), item->sizeWithUnits().height() ); - + settings.setValue( QStringLiteral( "LayoutDesigner/lastSizeUnit" ), static_cast< int >( item->sizeWithUnits().units() ) ); layout()->addItem( item ); } diff --git a/src/ui/layout/qgslayoutnewitemproperties.ui b/src/ui/layout/qgslayoutnewitemproperties.ui index b947dc5bb7a..3e5a67fdaf6 100644 --- a/src/ui/layout/qgslayoutnewitemproperties.ui +++ b/src/ui/layout/qgslayoutnewitemproperties.ui @@ -6,7 +6,7 @@ 0 0 - 391 + 468 263 @@ -21,11 +21,18 @@ - + + + + + Height + + + - mm + 3 @@ -44,7 +51,7 @@ - mm + 3 @@ -60,17 +67,43 @@ - - + + - Height + X + + + + + + + + + + 3 + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + false + + + + + + + Y - mm + 3 @@ -86,25 +119,6 @@ - - - - mm - - - 3 - - - -9999999.000000000000000 - - - 9999999.000000000000000 - - - false - - - @@ -112,19 +126,11 @@ - - - - Y - - + + - - - - X - - + + @@ -347,12 +353,19 @@ QDoubleSpinBox
qgsdoublespinbox.h
+ + QgsLayoutUnitsComboBox + QComboBox +
qgslayoutunitscombobox.h
+
mXPosSpin mYPosSpin + mPosUnitsComboBox mWidthSpin mHeightSpin + mSizeUnitsComboBox mUpperLeftCheckBox mUpperMiddleCheckBox mUpperRightCheckBox diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index feb45b43ac4..6282008c9ef 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -75,6 +75,7 @@ ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.p ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py) ADD_PYTHON_TEST(PyQgsLayoutManager test_qgslayoutmanager.py) ADD_PYTHON_TEST(PyQgsLayoutView test_qgslayoutview.py) +ADD_PYTHON_TEST(PyQgsLayoutItemPropertiesDialog test_qgslayoutitempropertiesdialog.py) ADD_PYTHON_TEST(PyQgsLayoutUnitsComboBox test_qgslayoutunitscombobox.py) ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py) ADD_PYTHON_TEST(PyQgsLocator test_qgslocator.py) diff --git a/tests/src/python/test_qgslayoutitempropertiesdialog.py b/tests/src/python/test_qgslayoutitempropertiesdialog.py new file mode 100644 index 00000000000..a6a1202eb2a --- /dev/null +++ b/tests/src/python/test_qgslayoutitempropertiesdialog.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsLayoutItemPropertiesDialog + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '18/07/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.core import QgsUnitTypes, QgsLayoutSize, QgsLayoutPoint, QgsLayoutItem +from qgis.gui import QgsLayoutItemPropertiesDialog + +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsLayoutItemPropertiesDialog(unittest.TestCase): + + def testGettersSetters(self): + """ test dialog getters/setters """ + dlg = qgis.gui.QgsLayoutItemPropertiesDialog() + + dlg.setItemPosition(QgsLayoutPoint(5, 6, QgsUnitTypes.LayoutPixels)) + self.assertEqual(dlg.itemPosition().x(), 5.0) + self.assertEqual(dlg.itemPosition().y(), 6.0) + self.assertEqual(dlg.itemPosition().units(), QgsUnitTypes.LayoutPixels) + + dlg.setItemSize(QgsLayoutSize(15, 16, QgsUnitTypes.LayoutInches)) + self.assertEqual(dlg.itemSize().width(), 15.0) + self.assertEqual(dlg.itemSize().height(), 16.0) + self.assertEqual(dlg.itemSize().units(), QgsUnitTypes.LayoutInches) + + for p in [QgsLayoutItem.UpperLeft, QgsLayoutItem.UpperMiddle, QgsLayoutItem.UpperRight, + QgsLayoutItem.MiddleLeft, QgsLayoutItem.Middle, QgsLayoutItem.MiddleRight, + QgsLayoutItem.LowerLeft, QgsLayoutItem.LowerMiddle, QgsLayoutItem.LowerRight]: + dlg.setReferencePoint(p) + self.assertEqual(dlg.referencePoint(), p) + + +if __name__ == '__main__': + unittest.main() From 7f066672b3ea9982fc57e9f58d90f5231b75d383 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 12:36:16 +1000 Subject: [PATCH 037/266] Add method to specify item groups for item classes in QgsLayoutItemGuiRegistry This allows the designer dialog to group the corresponding item actions together (i.e. grouping all basic shape creation actions together), but without any hardcoded special handling so that plugin based items can also be grouped. --- .../gui/layout/qgslayoutitemguiregistry.sip | 71 +++++++++++++++- src/app/layout/qgslayoutdesignerdialog.cpp | 54 ++++++++++++- src/app/layout/qgslayoutdesignerdialog.h | 4 + src/gui/layout/qgslayoutitemguiregistry.cpp | 22 ++++- src/gui/layout/qgslayoutitemguiregistry.h | 80 ++++++++++++++++++- tests/src/gui/testqgslayoutview.cpp | 6 ++ 6 files changed, 228 insertions(+), 9 deletions(-) diff --git a/python/gui/layout/qgslayoutitemguiregistry.sip b/python/gui/layout/qgslayoutitemguiregistry.sip index 059bcd09860..67de0cd3cb3 100644 --- a/python/gui/layout/qgslayoutitemguiregistry.sip +++ b/python/gui/layout/qgslayoutitemguiregistry.sip @@ -28,9 +28,11 @@ class QgsLayoutItemAbstractGuiMetadata %End public: - QgsLayoutItemAbstractGuiMetadata( int type ); + QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString() ); %Docstring Constructor for QgsLayoutItemAbstractGuiMetadata with the specified class ``type``. + + An optional ``groupId`` can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details. %End virtual ~QgsLayoutItemAbstractGuiMetadata(); @@ -41,6 +43,12 @@ class QgsLayoutItemAbstractGuiMetadata :rtype: int %End + QString groupId() const; +%Docstring + Returns the item group ID, if set. + :rtype: str +%End + virtual QIcon creationIcon() const; %Docstring Returns an icon representing creation of the layout item type. @@ -65,6 +73,48 @@ class QgsLayoutItemAbstractGuiMetadata +class QgsLayoutItemGuiGroup +{ +%Docstring + Stores GUI metadata about a group of layout item classes. + + QgsLayoutItemGuiGroup stores settings about groups of related layout item classes + which should be presented to users grouped together. + + For instance, the various basic shape creation tools would use QgsLayoutItemGuiGroup + to display grouped within designer dialogs. + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemguiregistry.h" +%End + public: + + QgsLayoutItemGuiGroup( const QString &id = QString(), const QString &name = QString(), const QIcon &icon = QIcon() ); +%Docstring + Constructor for QgsLayoutItemGuiGroup. +%End + + QString id; +%Docstring + Unique (untranslated) group ID string. +%End + + QString name; +%Docstring + Translated group name. +%End + + QIcon icon; +%Docstring + Icon for group. +%End + +}; + + class QgsLayoutItemGuiRegistry : QObject { %Docstring @@ -117,6 +167,25 @@ class QgsLayoutItemGuiRegistry : QObject :rtype: bool %End + bool addItemGroup( const QgsLayoutItemGuiGroup &group ); +%Docstring + Registers a new item group with the registry. This must be done before calling + addLayoutItemGuiMetadata() for any item types associated with the group. + + Returns true if group was added, or false if group could not be added (e.g. due to + duplicate id value). + +.. seealso:: itemGroup() + :rtype: bool +%End + + const QgsLayoutItemGuiGroup &itemGroup( const QString &id ); +%Docstring + Returns a reference to the item group with matching ``id``. +.. seealso:: addItemGroup() + :rtype: QgsLayoutItemGuiGroup +%End + QWidget *createItemWidget( int type ) const /Factory/; %Docstring Creates a new instance of a layout item configuration widget for the specified item ``type``. diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index f987818cf79..9bb9eb58fb5 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -301,15 +301,65 @@ void QgsLayoutDesignerDialog::closeEvent( QCloseEvent * ) void QgsLayoutDesignerDialog::itemTypeAdded( int type ) { QString name = QgsApplication::layoutItemRegistry()->itemMetadata( type )->visibleName(); + QString groupId = QgsGui::layoutItemGuiRegistry()->itemMetadata( type )->groupId(); + QToolButton *groupButton = nullptr; + QMenu *itemSubmenu = nullptr; + if ( !groupId.isEmpty() ) + { + // find existing group toolbutton and submenu, or create new ones if this is the first time the group has been encountered + const QgsLayoutItemGuiGroup &group = QgsGui::layoutItemGuiRegistry()->itemGroup( groupId ); + QIcon groupIcon = group.icon.isNull() ? QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicShape.svg" ) ) : group.icon; + QString groupText = tr( "Add %1" ).arg( group.name ); + if ( mItemGroupToolButtons.contains( groupId ) ) + { + groupButton = mItemGroupToolButtons.value( groupId ); + } + else + { + QToolButton *groupToolButton = new QToolButton( mToolsToolbar ); + groupToolButton->setIcon( groupIcon ); + groupToolButton->setCheckable( true ); + groupToolButton->setPopupMode( QToolButton::InstantPopup ); + groupToolButton->setAutoRaise( true ); + groupToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly ); + groupToolButton->setToolTip( groupText ); + mToolsToolbar->addWidget( groupToolButton ); + mItemGroupToolButtons.insert( groupId, groupToolButton ); + groupButton = groupToolButton; + } + + if ( mItemGroupSubmenus.contains( groupId ) ) + { + itemSubmenu = mItemGroupSubmenus.value( groupId ); + } + else + { + QMenu *groupSubmenu = mItemMenu->addMenu( groupText ); + groupSubmenu->setIcon( groupIcon ); + mItemMenu->addMenu( groupSubmenu ); + mItemGroupSubmenus.insert( groupId, groupSubmenu ); + itemSubmenu = groupSubmenu; + } + } + // update UI for new item type QAction *action = new QAction( tr( "Add %1" ).arg( name ), this ); action->setToolTip( tr( "Adds a new %1 to the layout" ).arg( name ) ); action->setCheckable( true ); action->setData( type ); action->setIcon( QgsGui::layoutItemGuiRegistry()->itemMetadata( type )->creationIcon() ); + mToolsActionGroup->addAction( action ); - mItemMenu->addAction( action ); - mToolsToolbar->addAction( action ); + if ( itemSubmenu ) + itemSubmenu->addAction( action ); + else + mItemMenu->addAction( action ); + + if ( groupButton ) + groupButton->addAction( action ); + else + mToolsToolbar->addAction( action ); + connect( action, &QAction::triggered, this, [this, type]() { activateNewItemCreationTool( type ); diff --git a/src/app/layout/qgslayoutdesignerdialog.h b/src/app/layout/qgslayoutdesignerdialog.h index a97ef467281..6f919269708 100644 --- a/src/app/layout/qgslayoutdesignerdialog.h +++ b/src/app/layout/qgslayoutdesignerdialog.h @@ -19,6 +19,7 @@ #include "ui_qgslayoutdesignerbase.h" #include "qgslayoutdesignerinterface.h" +#include class QgsLayoutDesignerDialog; class QgsLayoutView; @@ -161,6 +162,9 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner QgsLayoutViewToolZoom *mZoomTool = nullptr; QgsLayoutViewToolSelect *mSelectTool = nullptr; + QMap< QString, QToolButton * > mItemGroupToolButtons; + QMap< QString, QMenu * > mItemGroupSubmenus; + //! Save window state void saveWindowState(); diff --git a/src/gui/layout/qgslayoutitemguiregistry.cpp b/src/gui/layout/qgslayoutitemguiregistry.cpp index 09e58c892f4..7d2022c8b82 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.cpp +++ b/src/gui/layout/qgslayoutitemguiregistry.cpp @@ -41,6 +41,8 @@ bool QgsLayoutItemGuiRegistry::populate() if ( !mMetadata.isEmpty() ) return false; + addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "shapes" ), tr( "Shape" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicShape.svg" ) ) ) ); + auto createRubberBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand * { return new QgsLayoutViewRectangularRubberBand( view ); @@ -55,9 +57,9 @@ bool QgsLayoutItemGuiRegistry::populate() } ); addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( 101, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), nullptr, createRubberBand ) ); - addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutRectangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), nullptr, createRubberBand ) ); - addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutEllipse, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), nullptr, createEllipseBand ) ); - addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutTriangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), nullptr, createTriangleBand ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutRectangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), nullptr, createRubberBand, QStringLiteral( "shapes" ) ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutEllipse, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), nullptr, createEllipseBand, QStringLiteral( "shapes" ) ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutTriangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), nullptr, createTriangleBand, QStringLiteral( "shapes" ) ) ); return true; } @@ -76,6 +78,20 @@ bool QgsLayoutItemGuiRegistry::addLayoutItemGuiMetadata( QgsLayoutItemAbstractGu return true; } +bool QgsLayoutItemGuiRegistry::addItemGroup( const QgsLayoutItemGuiGroup &group ) +{ + if ( mItemGroups.contains( group.id ) ) + return false; + + mItemGroups.insert( group.id, group ); + return true; +} + +const QgsLayoutItemGuiGroup &QgsLayoutItemGuiRegistry::itemGroup( const QString &id ) +{ + return mItemGroups[ id ]; +} + QWidget *QgsLayoutItemGuiRegistry::createItemWidget( int type ) const { if ( !mMetadata.contains( type ) ) diff --git a/src/gui/layout/qgslayoutitemguiregistry.h b/src/gui/layout/qgslayoutitemguiregistry.h index 8c0c1147551..546f8d04a0e 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.h +++ b/src/gui/layout/qgslayoutitemguiregistry.h @@ -47,9 +47,12 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata /** * Constructor for QgsLayoutItemAbstractGuiMetadata with the specified class \a type. + * + * An optional \a groupId can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details. */ - QgsLayoutItemAbstractGuiMetadata( int type ) + QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString() ) : mType( type ) + , mGroupId( groupId ) {} virtual ~QgsLayoutItemAbstractGuiMetadata() = default; @@ -59,6 +62,11 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata */ int type() const { return mType; } + /** + * Returns the item group ID, if set. + */ + QString groupId() const { return mGroupId; } + /** * Returns an icon representing creation of the layout item type. */ @@ -78,6 +86,8 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata private: int mType = -1; + QString mGroupId; + }; //! Layout item configuration widget creation function @@ -102,11 +112,13 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad * Constructor for QgsLayoutItemGuiMetadata with the specified class \a type * and \a creationIcon, and function pointers for the various * configuration widget creation functions. + * + * An optional \a groupId can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details. */ QgsLayoutItemGuiMetadata( int type, const QIcon &creationIcon, QgsLayoutItemWidgetFunc pfWidget = nullptr, - QgsLayoutItemRubberBandFunc pfRubberBand = nullptr ) - : QgsLayoutItemAbstractGuiMetadata( type ) + QgsLayoutItemRubberBandFunc pfRubberBand = nullptr, const QString &groupId = QString() ) + : QgsLayoutItemAbstractGuiMetadata( type, groupId ) , mIcon( creationIcon ) , mWidgetFunc( pfWidget ) , mRubberBandFunc( pfRubberBand ) @@ -149,6 +161,49 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad #endif +/** + * \ingroup gui + * \brief Stores GUI metadata about a group of layout item classes. + * + * QgsLayoutItemGuiGroup stores settings about groups of related layout item classes + * which should be presented to users grouped together. + * + * For instance, the various basic shape creation tools would use QgsLayoutItemGuiGroup + * to display grouped within designer dialogs. + * + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsLayoutItemGuiGroup +{ + public: + + /** + * Constructor for QgsLayoutItemGuiGroup. + */ + QgsLayoutItemGuiGroup( const QString &id = QString(), const QString &name = QString(), const QIcon &icon = QIcon() ) + : id( id ) + , name( name ) + , icon( icon ) + {} + + /** + * Unique (untranslated) group ID string. + */ + QString id; + + /** + * Translated group name. + */ + QString name; + + /** + * Icon for group. + */ + QIcon icon; + +}; + + /** * \ingroup core * \class QgsLayoutItemGuiRegistry @@ -202,6 +257,23 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject */ bool addLayoutItemGuiMetadata( QgsLayoutItemAbstractGuiMetadata *metadata SIP_TRANSFER ); + /** + * Registers a new item group with the registry. This must be done before calling + * addLayoutItemGuiMetadata() for any item types associated with the group. + * + * Returns true if group was added, or false if group could not be added (e.g. due to + * duplicate id value). + * + * \see itemGroup() + */ + bool addItemGroup( const QgsLayoutItemGuiGroup &group ); + + /** + * Returns a reference to the item group with matching \a id. + * \see addItemGroup() + */ + const QgsLayoutItemGuiGroup &itemGroup( const QString &id ); + /** * Creates a new instance of a layout item configuration widget for the specified item \a type. */ @@ -233,6 +305,8 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject QMap mMetadata; + QMap< QString, QgsLayoutItemGuiGroup > mItemGroups; + }; #endif //QGSLAYOUTITEMGUIREGISTRY_H diff --git a/tests/src/gui/testqgslayoutview.cpp b/tests/src/gui/testqgslayoutview.cpp index 096dca3877b..608a3a81085 100644 --- a/tests/src/gui/testqgslayoutview.cpp +++ b/tests/src/gui/testqgslayoutview.cpp @@ -300,6 +300,12 @@ void TestQgsLayoutView::guiRegistry() QCOMPARE( band->view(), view ); delete band; + // groups + QVERIFY( registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) ); + QCOMPARE( registry.itemGroup( QStringLiteral( "g1" ) ).id, QStringLiteral( "g1" ) ); + // can't add duplicate group + QVERIFY( !registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) ); + //test populate QgsLayoutItemGuiRegistry reg2; QVERIFY( reg2.itemTypes().isEmpty() ); From 4e0e038aabbef9ad91227556741598e922fb75aa Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 13:04:03 +1000 Subject: [PATCH 038/266] Dox updates --- python/core/layout/qgslayoutitemshape.sip | 9 +++++++++ src/core/layout/qgslayoutcontext.h | 2 +- src/core/layout/qgslayoutitemshape.h | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/python/core/layout/qgslayoutitemshape.sip b/python/core/layout/qgslayoutitemshape.sip index 47191ecc90d..8eb1008a893 100644 --- a/python/core/layout/qgslayoutitemshape.sip +++ b/python/core/layout/qgslayoutitemshape.sip @@ -64,6 +64,9 @@ class QgsLayoutItemRectangularShape : QgsLayoutItemShape static QgsLayoutItemRectangularShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; %Docstring + Returns a new rectangular item for the specified ``layout``. + + The caller takes responsibility for deleting the returned object. :rtype: QgsLayoutItemRectangularShape %End @@ -107,6 +110,9 @@ class QgsLayoutItemEllipseShape : QgsLayoutItemShape static QgsLayoutItemEllipseShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; %Docstring + Returns a new ellipse item for the specified ``layout``. + + The caller takes responsibility for deleting the returned object. :rtype: QgsLayoutItemEllipseShape %End @@ -137,6 +143,9 @@ class QgsLayoutItemTriangleShape : QgsLayoutItemShape static QgsLayoutItemTriangleShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; %Docstring + Returns a new triangle item for the specified ``layout``. + + The caller takes responsibility for deleting the returned object. :rtype: QgsLayoutItemTriangleShape %End diff --git a/src/core/layout/qgslayoutcontext.h b/src/core/layout/qgslayoutcontext.h index 13055a46e8b..5e9253a8182 100644 --- a/src/core/layout/qgslayoutcontext.h +++ b/src/core/layout/qgslayoutcontext.h @@ -26,7 +26,7 @@ class QgsFeature; class QgsVectorLayer; /** - * \ingroup Layout + * \ingroup core * \class QgsLayoutContext * \brief Stores information relating to the current context and rendering settings for a layout. * \since QGIS 3.0 diff --git a/src/core/layout/qgslayoutitemshape.h b/src/core/layout/qgslayoutitemshape.h index d7c79809162..66476eb44e7 100644 --- a/src/core/layout/qgslayoutitemshape.h +++ b/src/core/layout/qgslayoutitemshape.h @@ -79,6 +79,11 @@ class CORE_EXPORT QgsLayoutItemRectangularShape : public QgsLayoutItemShape explicit QgsLayoutItemRectangularShape( QgsLayout *layout ); virtual int type() const override { return QgsLayoutItemRegistry::LayoutRectangle; } + /** + * Returns a new rectangular item for the specified \a layout. + * + * The caller takes responsibility for deleting the returned object. + */ static QgsLayoutItemRectangularShape *create( QgsLayout *layout, const QVariantMap &settings ) SIP_FACTORY; /** @@ -120,6 +125,11 @@ class CORE_EXPORT QgsLayoutItemEllipseShape : public QgsLayoutItemShape explicit QgsLayoutItemEllipseShape( QgsLayout *layout ); virtual int type() const override { return QgsLayoutItemRegistry::LayoutEllipse; } + /** + * Returns a new ellipse item for the specified \a layout. + * + * The caller takes responsibility for deleting the returned object. + */ static QgsLayoutItemEllipseShape *create( QgsLayout *layout, const QVariantMap &settings ) SIP_FACTORY; protected: @@ -147,6 +157,11 @@ class CORE_EXPORT QgsLayoutItemTriangleShape : public QgsLayoutItemShape explicit QgsLayoutItemTriangleShape( QgsLayout *layout ); virtual int type() const override { return QgsLayoutItemRegistry::LayoutTriangle; } + /** + * Returns a new triangle item for the specified \a layout. + * + * The caller takes responsibility for deleting the returned object. + */ static QgsLayoutItemTriangleShape *create( QgsLayout *layout, const QVariantMap &settings ) SIP_FACTORY; protected: From 54bf013004f73667f3fb13bc5bbb879c67426465 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 13:42:04 +1000 Subject: [PATCH 039/266] Update tests --- tests/src/core/testqgslayoutitem.cpp | 2 +- .../expected_layoutitem_draw_mask.png | Bin 0 -> 1551 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/testdata/control_images/layouts/expected_layoutitem_draw/expected_layoutitem_draw_mask.png diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 11aaf40d0d0..4e46aec3244 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -68,7 +68,7 @@ class TestQgsLayoutItem: public QObject ~TestItem() {} //implement pure virtual methods - int type() const { return QgsLayoutItemRegistry::LayoutItem + 101; } + int type() const override { return QgsLayoutItemRegistry::LayoutItem + 101; } protected: void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * = nullptr ) override diff --git a/tests/testdata/control_images/layouts/expected_layoutitem_draw/expected_layoutitem_draw_mask.png b/tests/testdata/control_images/layouts/expected_layoutitem_draw/expected_layoutitem_draw_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..0d8137f57190c2f1c05ea05c7ad32ad9620d5a15 GIT binary patch literal 1551 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~teM`SSr1Aih2Gp?{-p2@(# zdeGCwF{EP7+Z&EtOojrk2VM63pL%;ObJGtq$%U_2>MvJL*n4djD}#O2965#q^+Ey+ zLX3i4F`%hMTZwqB2l(`Kl9#?3vNifNFM<2c*-)Wlv{uK Date: Tue, 18 Jul 2017 10:47:06 +0700 Subject: [PATCH 040/266] Non-blocking save as image/PDF dialogs (#4874) --- python/core/qgsmaprenderertask.sip | 3 +- src/app/qgisapp.cpp | 155 ++--------------------------- src/app/qgsmapsavedialog.cpp | 108 +++++++++++++++++++- src/app/qgsmapsavedialog.h | 13 ++- src/core/qgsmaprenderertask.cpp | 73 ++++++++++---- src/core/qgsmaprenderertask.h | 4 +- 6 files changed, 183 insertions(+), 173 deletions(-) diff --git a/python/core/qgsmaprenderertask.sip b/python/core/qgsmaprenderertask.sip index b4f34f922c9..26429a2b048 100644 --- a/python/core/qgsmaprenderertask.sip +++ b/python/core/qgsmaprenderertask.sip @@ -31,7 +31,8 @@ class QgsMapRendererTask : QgsTask QgsMapRendererTask( const QgsMapSettings &ms, const QString &fileName, - const QString &fileFormat = QString( "PNG" ) ); + const QString &fileFormat = QString( "PNG" ), + const bool forceRaster = false ); %Docstring Constructor for QgsMapRendererTask to render a map to an image file. %End diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index d43ddb726de..b05e7124355 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -5740,173 +5740,34 @@ void QgisApp::updateFilterLegend() void QgisApp::saveMapAsImage() { - QList< QgsMapDecoration * > decorations; - QString activeDecorations; + QList< QgsDecorationItem * > decorations; Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems ) { if ( decoration->enabled() ) { decorations << decoration; - if ( activeDecorations.isEmpty() ) - activeDecorations = decoration->name().toLower(); - else - activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() ); } } - QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations ); - if ( !dlg.exec() ) - return; - - QPair< QString, QString> fileNameAndFilter = QgsGuiUtils::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) ); - if ( fileNameAndFilter.first != QLatin1String( "" ) ) - { - QgsMapSettings ms = QgsMapSettings(); - dlg.applyMapSettings( ms ); - - QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileNameAndFilter.first, fileNameAndFilter.second ); - - if ( dlg.drawAnnotations() ) - { - mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() ); - } - - if ( dlg.drawDecorations() ) - { - mapRendererTask->addDecorations( decorations ); - } - - mapRendererTask->setSaveWorldFile( dlg.saveWorldFile() ); - - connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ = ] - { - messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully saved map to image" ) ); - } ); - connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ = ]( int error ) - { - switch ( error ) - { - case QgsMapRendererTask::ImageAllocationFail: - { - messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) ); - break; - } - case QgsMapRendererTask::ImageSaveFail: - { - messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not save the image to file" ) ); - break; - } - } - } ); - - QgsApplication::taskManager()->addTask( mapRendererTask ); - } - + QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, decorations, QgsProject::instance()->annotationManager()->annotations() ); + dlg->setAttribute( Qt::WA_DeleteOnClose ); + dlg->show(); } // saveMapAsImage void QgisApp::saveMapAsPdf() { - QList< QgsMapDecoration * > decorations; - QString activeDecorations; + QList< QgsDecorationItem * > decorations; Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems ) { if ( decoration->enabled() ) { decorations << decoration; - if ( activeDecorations.isEmpty() ) - activeDecorations = decoration->name().toLower(); - else - activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() ); } } - QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations, QgsMapSaveDialog::Pdf ); - if ( !dlg.exec() ) - return; - - QgsSettings settings; - QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString(); - QString fileName = QFileDialog::getSaveFileName( this, tr( "Save map as" ), lastUsedDir, tr( "PDF Format" ) + " (*.pdf *.PDF)" ); - if ( !fileName.isEmpty() ) - { - QgsMapSettings ms = QgsMapSettings(); - dlg.applyMapSettings( ms ); - - QPrinter *printer = new QPrinter(); - printer->setOutputFileName( fileName ); - printer->setOutputFormat( QPrinter::PdfFormat ); - printer->setOrientation( QPrinter::Portrait ); - // paper size needs to be given in millimeters in order to be able to set a resolution to pass onto the map renderer - printer->setPaperSize( dlg.size() * 25.4 / dlg.dpi(), QPrinter::Millimeter ); - printer->setPageMargins( 0, 0, 0, 0, QPrinter::Millimeter ); - printer->setResolution( dlg.dpi() ); - - QPainter *p = new QPainter(); - QImage *image = nullptr; - if ( dlg.saveAsRaster() ) - { - image = new QImage( dlg.size(), QImage::Format_ARGB32 ); - if ( image->isNull() ) - { - messageBar()->pushWarning( tr( "Save as PDF" ), tr( "Could not allocate required memory for image" ) ); - delete p; - delete image; - delete printer; - - return; - } - - image->setDotsPerMeterX( 1000 * dlg.dpi() / 25.4 ); - image->setDotsPerMeterY( 1000 * dlg.dpi() / 25.4 ); - p->begin( image ); - } - else - { - p->begin( printer ); - } - - QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, p ); - - if ( dlg.drawAnnotations() ) - { - mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() ); - } - - if ( dlg.drawDecorations() ) - { - mapRendererTask->addDecorations( decorations ); - } - - mapRendererTask->setSaveWorldFile( dlg.saveWorldFile() ); - - connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ this, p, image, printer ] - { - p->end(); - - if ( image ) - { - QPainter pp; - pp.begin( printer ); - QRectF rect( 0, 0, image->width(), image->height() ); - pp.drawImage( rect, *image, rect ); - pp.end(); - } - - messageBar()->pushSuccess( tr( "Save as PDF" ), tr( "Successfully saved map to PDF" ) ); - delete p; - delete image; - delete printer; - } ); - connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ this, p, image, printer ]( int ) - { - delete p; - delete image; - delete printer; - } ); - - QgsApplication::taskManager()->addTask( mapRendererTask ); - } - + QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, decorations, QgsProject::instance()->annotationManager()->annotations(), QgsMapSaveDialog::Pdf ); + dlg->setAttribute( Qt::WA_DeleteOnClose ); + dlg->show(); } // saveMapAsPdf //overloaded version of the above function diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index ab0fb5f9231..be15d956623 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -18,25 +18,32 @@ #include "qgsmapsavedialog.h" #include "qgis.h" +#include "qgisapp.h" #include "qgsscalecalculator.h" #include "qgsdecorationitem.h" #include "qgsexpressioncontext.h" #include "qgsextentgroupbox.h" #include "qgsmapsettings.h" #include "qgsmapsettingsutils.h" +#include "qgsmaprenderertask.h" #include "qgsproject.h" #include "qgssettings.h" #include -#include +#include +#include #include +#include +#include +#include Q_GUI_EXPORT extern int qt_defaultDpiX(); -QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations, DialogType type ) +QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QList< QgsDecorationItem * > decorations, QList< QgsAnnotation *> annotations, DialogType type ) : QDialog( parent ) , mDialogType( type ) , mMapCanvas( mapCanvas ) + , mAnnotations( annotations ) { setupUi( this ); @@ -57,6 +64,15 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, co mScaleWidget->setMapCanvas( mMapCanvas ); mScaleWidget->setShowCurrentScaleButton( true ); + QString activeDecorations; + Q_FOREACH ( QgsDecorationItem *decoration, decorations ) + { + mDecorations << decoration; + if ( activeDecorations.isEmpty() ) + activeDecorations = decoration->name().toLower(); + else + activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() ); + } mDrawDecorations->setText( tr( "Draw active decorations: %1" ).arg( !activeDecorations.isEmpty() ? activeDecorations : tr( "none" ) ) ); connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateDpi ); @@ -92,6 +108,8 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, co this->setWindowTitle( tr( "Save map as PDF" ) ); } + + connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsMapSaveDialog::accepted ); } void QgsMapSaveDialog::updateDpi( int dpi ) @@ -222,3 +240,89 @@ void QgsMapSaveDialog::applyMapSettings( QgsMapSettings &mapSettings ) mapSettings.setExpressionContext( expressionContext ); } + +void QgsMapSaveDialog::accepted() +{ + if ( mDialogType == Image ) + { + QPair< QString, QString> fileNameAndFilter = QgsGuiUtils::getSaveAsImageName( QgisApp::instance(), tr( "Choose a file name to save the map image as" ) ); + if ( fileNameAndFilter.first != QLatin1String( "" ) ) + { + QgsMapSettings ms = QgsMapSettings(); + applyMapSettings( ms ); + + QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileNameAndFilter.first, fileNameAndFilter.second ); + + if ( drawAnnotations() ) + { + mapRendererTask->addAnnotations( mAnnotations ); + } + + if ( drawDecorations() ) + { + mapRendererTask->addDecorations( mDecorations ); + } + + mapRendererTask->setSaveWorldFile( saveWorldFile() ); + + connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, [ = ] + { + QgisApp::instance()->messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully saved map to image" ) ); + } ); + connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, [ = ]( int error ) + { + switch ( error ) + { + case QgsMapRendererTask::ImageAllocationFail: + { + QgisApp::instance()->messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) ); + break; + } + case QgsMapRendererTask::ImageSaveFail: + { + QgisApp::instance()->messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not save the map to file" ) ); + break; + } + } + } ); + + QgsApplication::taskManager()->addTask( mapRendererTask ); + } + } + else + { + QgsSettings settings; + QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString(); + QString fileName = QFileDialog::getSaveFileName( QgisApp::instance(), tr( "Save map as" ), lastUsedDir, tr( "PDF Format" ) + " (*.pdf *.PDF)" ); + if ( !fileName.isEmpty() ) + { + QgsMapSettings ms = QgsMapSettings(); + applyMapSettings( ms ); + + QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileName, QStringLiteral( "PDF" ), saveAsRaster() ); + + if ( drawAnnotations() ) + { + mapRendererTask->addAnnotations( mAnnotations ); + } + + if ( drawDecorations() ) + { + mapRendererTask->addDecorations( mDecorations ); + } + + mapRendererTask->setSaveWorldFile( saveWorldFile() ); + + connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, [ = ] + { + QgisApp::instance()->messageBar()->pushSuccess( tr( "Save as PDF" ), tr( "Successfully saved map to PDF" ) ); + } ); + connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, [ = ]( int ) + { + QgisApp::instance()->messageBar()->pushWarning( tr( "Save as PDF" ), tr( "Could not save the map to PDF..." ) ); + } ); + + QgsApplication::taskManager()->addTask( mapRendererTask ); + } + } +} diff --git a/src/app/qgsmapsavedialog.h b/src/app/qgsmapsavedialog.h index b6194f804fc..b1291b3c2f1 100644 --- a/src/app/qgsmapsavedialog.h +++ b/src/app/qgsmapsavedialog.h @@ -21,8 +21,9 @@ #include "ui_qgsmapsavedialog.h" #include "qgisapp.h" -#include "qgsrectangle.h" #include "qgsmapcanvas.h" +#include "qgsmapdecoration.h" +#include "qgsrectangle.h" #include #include @@ -45,7 +46,10 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog /** Constructor for QgsMapSaveDialog */ - QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString(), DialogType type = Image ); + QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, + QList< QgsDecorationItem * > decorations = QList< QgsDecorationItem * >(), + QList< QgsAnnotation *> annotations = QList< QgsAnnotation * >(), + DialogType type = Image ); //! returns extent rectangle QgsRectangle extent() const; @@ -73,6 +77,8 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog private: + void accepted(); + void updateDpi( int dpi ); void updateOutputWidth( int width ); void updateOutputHeight( int height ); @@ -82,6 +88,9 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog DialogType mDialogType; QgsMapCanvas *mMapCanvas; + QList< QgsMapDecoration * > mDecorations; + QList< QgsAnnotation *> mAnnotations; + QgsRectangle mExtent; int mDpi; QSize mSize; diff --git a/src/core/qgsmaprenderertask.cpp b/src/core/qgsmaprenderertask.cpp index c80b337c2a0..bbf218ea626 100644 --- a/src/core/qgsmaprenderertask.cpp +++ b/src/core/qgsmaprenderertask.cpp @@ -22,17 +22,19 @@ #include #include +#include -QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, const QString &fileName, const QString &fileFormat ) +QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, const QString &fileName, const QString &fileFormat, const bool forceRaster ) : QgsTask( tr( "Saving as image" ) ) , mMapSettings( ms ) , mFileName( fileName ) , mFileFormat( fileFormat ) + , mForceRaster( forceRaster ) { } QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, QPainter *p ) - : QgsTask( tr( "Saving as image" ) ) + : QgsTask( tr( "Rendering to painter" ) ) , mMapSettings( ms ) , mPainter( p ) { @@ -70,8 +72,27 @@ bool QgsMapRendererTask::run() QImage img; std::unique_ptr< QPainter > tempPainter; QPainter *destPainter = mPainter; + std::unique_ptr< QPrinter > printer; - if ( !mPainter ) + if ( mFileFormat == QStringLiteral( "PDF" ) ) + { + printer.reset( new QPrinter() ); + printer->setOutputFileName( mFileName ); + printer->setOutputFormat( QPrinter::PdfFormat ); + printer->setOrientation( QPrinter::Portrait ); + // paper size needs to be given in millimeters in order to be able to set a resolution to pass onto the map renderer + printer->setPaperSize( mMapSettings.outputSize() * 25.4 / mMapSettings.outputDpi(), QPrinter::Millimeter ); + printer->setPageMargins( 0, 0, 0, 0, QPrinter::Millimeter ); + printer->setResolution( mMapSettings.outputDpi() ); + + if ( !mForceRaster ) + { + tempPainter.reset( new QPainter( printer.get() ) ); + destPainter = tempPainter.get(); + } + } + + if ( !destPainter ) { // save rendered map to an image file img = QImage( mMapSettings.outputSize(), QImage::Format_ARGB32 ); @@ -149,27 +170,39 @@ bool QgsMapRendererTask::run() if ( !mFileName.isEmpty() ) { destPainter->end(); - bool success = img.save( mFileName, mFileFormat.toLocal8Bit().data() ); - if ( !success ) + + if ( mForceRaster && mFileFormat == QStringLiteral( "PDF" ) ) { - mError = ImageSaveFail; - return false; + QPainter pp; + pp.begin( printer.get() ); + QRectF rect( 0, 0, img.width(), img.height() ); + pp.drawImage( rect, img, rect ); + pp.end(); } - - if ( mSaveWorldFile ) + else if ( mFileFormat != QStringLiteral( "PDF" ) ) { - QFileInfo info = QFileInfo( mFileName ); - - // build the world file name - QString outputSuffix = info.suffix(); - QString worldFileName = info.absolutePath() + '/' + info.baseName() + '.' - + outputSuffix.at( 0 ) + outputSuffix.at( info.suffix().size() - 1 ) + 'w'; - QFile worldFile( worldFileName ); - - if ( worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text + bool success = img.save( mFileName, mFileFormat.toLocal8Bit().data() ); + if ( !success ) { - QTextStream stream( &worldFile ); - stream << QgsMapSettingsUtils::worldFileContent( mMapSettings ); + mError = ImageSaveFail; + return false; + } + + if ( mSaveWorldFile ) + { + QFileInfo info = QFileInfo( mFileName ); + + // build the world file name + QString outputSuffix = info.suffix(); + QString worldFileName = info.absolutePath() + '/' + info.baseName() + '.' + + outputSuffix.at( 0 ) + outputSuffix.at( info.suffix().size() - 1 ) + 'w'; + QFile worldFile( worldFileName ); + + if ( worldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text + { + QTextStream stream( &worldFile ); + stream << QgsMapSettingsUtils::worldFileContent( mMapSettings ); + } } } } diff --git a/src/core/qgsmaprenderertask.h b/src/core/qgsmaprenderertask.h index 521c84d197b..4484a84a3ce 100644 --- a/src/core/qgsmaprenderertask.h +++ b/src/core/qgsmaprenderertask.h @@ -55,7 +55,8 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask */ QgsMapRendererTask( const QgsMapSettings &ms, const QString &fileName, - const QString &fileFormat = QString( "PNG" ) ); + const QString &fileFormat = QString( "PNG" ), + const bool forceRaster = false ); /** * Constructor for QgsMapRendererTask to render a map to a painter object. @@ -108,6 +109,7 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask QString mFileName; QString mFileFormat; + bool mForceRaster = false; bool mSaveWorldFile = false; QList< QgsAnnotation * > mAnnotations; From b83415688a51ead27aa1bb24e38077d1e66c5c06 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 10:53:57 +1000 Subject: [PATCH 041/266] Save/restore window geometry for plugin installer repo fetching dialog --- .../qgsplugininstallerfetchingdialog.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/python/pyplugin_installer/qgsplugininstallerfetchingdialog.py b/python/pyplugin_installer/qgsplugininstallerfetchingdialog.py index a7ff7d358a9..414e78a2acb 100644 --- a/python/pyplugin_installer/qgsplugininstallerfetchingdialog.py +++ b/python/pyplugin_installer/qgsplugininstallerfetchingdialog.py @@ -24,7 +24,9 @@ ***************************************************************************/ """ +from qgis.PyQt.QtCore import QByteArray from qgis.PyQt.QtWidgets import QDialog, QTreeWidgetItem +from qgis.core import QgsSettings from .ui_qgsplugininstallerfetchingbase import Ui_QgsPluginInstallerFetchingDialogBase from .installer_data import repositories @@ -52,6 +54,14 @@ class QgsPluginInstallerFetchingDialog(QDialog, Ui_QgsPluginInstallerFetchingDia repositories.repositoryFetched.connect(self.repositoryFetched) repositories.anythingChanged.connect(self.displayState) + settings = QgsSettings() + self.restoreGeometry(settings.value("/Qgis/plugin-installer/fetching_geometry", QByteArray())) + + def closeEvent(self, event): + settings = QgsSettings() + settings.setValue("/Qgis/plugin-installer/fetching_geometry", self.saveGeometry()) + super(QgsPluginInstallerFetchingDialog, self).closeEvent(event) + # ----------------------------------------- # def displayState(self, key, state, state2=None): messages = [self.tr("Success"), self.tr("Resolving host name..."), self.tr("Connecting..."), self.tr("Host connected. Sending request..."), self.tr("Downloading data..."), self.tr("Idle"), self.tr("Closing connection..."), self.tr("Error")] From 1f9512e6dbf034080b291abe40957c3f4c0daa1d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 13:47:04 +1000 Subject: [PATCH 042/266] Make locator bar a bit wider --- src/gui/locator/qgslocatorwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/locator/qgslocatorwidget.cpp b/src/gui/locator/qgslocatorwidget.cpp index 123e40ad0ee..29f117ca6bc 100644 --- a/src/gui/locator/qgslocatorwidget.cpp +++ b/src/gui/locator/qgslocatorwidget.cpp @@ -41,7 +41,7 @@ QgsLocatorWidget::QgsLocatorWidget( QWidget *parent ) #endif int placeholderMinWidth = mLineEdit->fontMetrics().width( mLineEdit->placeholderText() ); - int minWidth = qMax( 200, ( int )( placeholderMinWidth * 1.3 ) ); + int minWidth = qMax( 200, ( int )( placeholderMinWidth * 1.6 ) ); resize( minWidth, 30 ); QSizePolicy sizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); sizePolicy.setHorizontalStretch( 0 ); From fd9213ce72235bb5b361646bc747e9e19b2cde16 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 17 Jul 2017 17:57:49 +0200 Subject: [PATCH 043/266] Fix crash in attributetable when removing more than one column References #16746 --- src/gui/attributetable/qgsattributetablefiltermodel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/attributetable/qgsattributetablefiltermodel.cpp b/src/gui/attributetable/qgsattributetablefiltermodel.cpp index a44f49780f7..aa1f77d8367 100644 --- a/src/gui/attributetable/qgsattributetablefiltermodel.cpp +++ b/src/gui/attributetable/qgsattributetablefiltermodel.cpp @@ -197,7 +197,8 @@ void QgsAttributeTableFilterModel::setAttributeTableConfig( const QgsAttributeTa { if ( newColumnMapping.size() == mColumnMapping.size() - removedColumnCount ) { - beginRemoveColumns( QModelIndex(), firstRemovedColumn, firstRemovedColumn ); + //the amount of removed column in the model need to be equal removedColumnCount + beginRemoveColumns( QModelIndex(), firstRemovedColumn, firstRemovedColumn+removedColumnCount-1 ); mColumnMapping = newColumnMapping; endRemoveColumns(); } From 11bb2488ead9ab9ccf98e3090cd4493f3ee9a070 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 17 Jul 2017 18:35:57 +0200 Subject: [PATCH 044/266] Fix in attributetable - unable to update column Fix #16746 --- src/gui/attributetable/qgsdualview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/attributetable/qgsdualview.cpp b/src/gui/attributetable/qgsdualview.cpp index 00eed144901..63e2d2c6e10 100644 --- a/src/gui/attributetable/qgsdualview.cpp +++ b/src/gui/attributetable/qgsdualview.cpp @@ -772,10 +772,10 @@ void QgsDualView::setFeatureSelectionManager( QgsIFeatureSelectionManager *featu void QgsDualView::setAttributeTableConfig( const QgsAttributeTableConfig &config ) { + mConfig = config; mLayer->setAttributeTableConfig( config ); mFilterModel->setAttributeTableConfig( config ); mTableView->setAttributeTableConfig( config ); - mConfig = config; } void QgsDualView::setSortExpression( const QString &sortExpression, Qt::SortOrder sortOrder ) From 7ec8c4ae3be51fff318c9ea0c87218c9d8f72ee2 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 17 Jul 2017 18:39:02 +0200 Subject: [PATCH 045/266] Codestyle --- src/gui/attributetable/qgsattributetablefiltermodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/attributetable/qgsattributetablefiltermodel.cpp b/src/gui/attributetable/qgsattributetablefiltermodel.cpp index aa1f77d8367..41a4f9cc5b2 100644 --- a/src/gui/attributetable/qgsattributetablefiltermodel.cpp +++ b/src/gui/attributetable/qgsattributetablefiltermodel.cpp @@ -198,7 +198,7 @@ void QgsAttributeTableFilterModel::setAttributeTableConfig( const QgsAttributeTa if ( newColumnMapping.size() == mColumnMapping.size() - removedColumnCount ) { //the amount of removed column in the model need to be equal removedColumnCount - beginRemoveColumns( QModelIndex(), firstRemovedColumn, firstRemovedColumn+removedColumnCount-1 ); + beginRemoveColumns( QModelIndex(), firstRemovedColumn, firstRemovedColumn + removedColumnCount - 1 ); mColumnMapping = newColumnMapping; endRemoveColumns(); } From 9ff73c0b77773ba725fa3d79759f4f52d8822526 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 15 Jun 2017 12:52:50 +0200 Subject: [PATCH 046/266] [bugfix] Sync the bowser connections when changed from the select dialogs For now, the sync works only in one direction: from the dialogs to the browser instances --- python/gui/qgsbrowserdockwidget.sip | 4 ++++ src/app/qgisapp.cpp | 6 ++++++ src/app/qgisapp.h | 6 ++++++ src/gui/qgsbrowserdockwidget.cpp | 5 +++-- src/gui/qgsbrowserdockwidget.h | 2 ++ src/gui/qgsdatasourcemanagerdialog.cpp | 12 ++++++++++++ src/gui/qgsdatasourcemanagerdialog.h | 7 +++++++ 7 files changed, 40 insertions(+), 2 deletions(-) diff --git a/python/gui/qgsbrowserdockwidget.sip b/python/gui/qgsbrowserdockwidget.sip index df6403d7ac6..913e2cc88a5 100644 --- a/python/gui/qgsbrowserdockwidget.sip +++ b/python/gui/qgsbrowserdockwidget.sip @@ -120,6 +120,10 @@ Emitted when a file needs to be opened void handleDropUriList( const QgsMimeDataUtils::UriList & ); %Docstring Emitted when drop uri list needs to be handled +%End + void connectionsChanged( ); +%Docstring +Connections changed in the browser %End protected: diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index b05e7124355..bcf77d409bf 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -888,6 +888,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh addDockWidget( Qt::LeftDockWidgetArea, mBrowserWidget ); mBrowserWidget->hide(); connect( this, &QgisApp::newProject, mBrowserWidget, &QgsBrowserDockWidget::updateProjectHome ); + connect( this, &QgisApp::connectionsChanged, mBrowserWidget, &QgsBrowserDockWidget::refresh ); + connect( mBrowserWidget, &QgsBrowserDockWidget::connectionsChanged, this, &QgisApp::connectionsChanged ); connect( mBrowserWidget, &QgsBrowserDockWidget::openFile, this, &QgisApp::openFile ); connect( mBrowserWidget, &QgsBrowserDockWidget::handleDropUriList, this, &QgisApp::handleDropUriList ); @@ -896,6 +898,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh addDockWidget( Qt::LeftDockWidgetArea, mBrowserWidget2 ); mBrowserWidget2->hide(); connect( this, &QgisApp::newProject, mBrowserWidget2, &QgsBrowserDockWidget::updateProjectHome ); + connect( mBrowserWidget2, &QgsBrowserDockWidget::connectionsChanged, this, &QgisApp::connectionsChanged ); + connect( this, &QgisApp::connectionsChanged, mBrowserWidget2, &QgsBrowserDockWidget::refresh ); connect( mBrowserWidget2, &QgsBrowserDockWidget::openFile, this, &QgisApp::openFile ); connect( mBrowserWidget2, &QgsBrowserDockWidget::handleDropUriList, this, &QgisApp::handleDropUriList ); @@ -1594,6 +1598,8 @@ void QgisApp::dataSourceManager( QString pageName ) { mDataSourceManagerDialog = new QgsDataSourceManagerDialog( mapCanvas( ), this ); // Forward signals to this + connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh ); + connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged ); connect( mDataSourceManagerDialog, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ), this, SLOT( addRasterLayer( QString const &, QString const &, QString const & ) ) ); connect( mDataSourceManagerDialog, SIGNAL( addVectorLayer( QString const &, QString const &, QString const & ) ), diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 08e316be521..e29e3dc2629 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -1494,6 +1494,12 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow signals: + /** + * Emitted when a connection has been added/removed or changed by the provider + * selection dialogs + */ + void connectionsChanged( ); + /** Emitted when a key is pressed and we want non widget sublasses to be able to pick up on this (e.g. maplayer) */ void keyPressed( QKeyEvent *e ); diff --git a/src/gui/qgsbrowserdockwidget.cpp b/src/gui/qgsbrowserdockwidget.cpp index 6f372bfd750..c728b2fc776 100644 --- a/src/gui/qgsbrowserdockwidget.cpp +++ b/src/gui/qgsbrowserdockwidget.cpp @@ -117,7 +117,7 @@ void QgsBrowserDockWidget::showEvent( QShowEvent *e ) mModel = new QgsBrowserModel( mBrowserView ); mProxyModel = new QgsBrowserTreeFilterProxyModel( this ); mProxyModel->setBrowserModel( mModel ); - mBrowserView->setSettingsSection( objectName().toLower() ); // to distinguish 2 instances ow browser + mBrowserView->setSettingsSection( objectName().toLower() ); // to distinguish 2 or more instances of the browser mBrowserView->setBrowserModel( mModel ); mBrowserView->setModel( mProxyModel ); // provide a horizontal scroll bar instead of using ellipse (...) for longer items @@ -242,7 +242,8 @@ void QgsBrowserDockWidget::removeFavorite() void QgsBrowserDockWidget::refresh() { - refreshModel( QModelIndex() ); + if ( mModel ) + refreshModel( QModelIndex() ); } void QgsBrowserDockWidget::refreshModel( const QModelIndex &index ) diff --git a/src/gui/qgsbrowserdockwidget.h b/src/gui/qgsbrowserdockwidget.h index bbacee93913..a9a6d8d9a07 100644 --- a/src/gui/qgsbrowserdockwidget.h +++ b/src/gui/qgsbrowserdockwidget.h @@ -102,6 +102,8 @@ class GUI_EXPORT QgsBrowserDockWidget : public QgsDockWidget, private Ui::QgsBro void openFile( const QString & ); //! Emitted when drop uri list needs to be handled void handleDropUriList( const QgsMimeDataUtils::UriList & ); + //! Connections changed in the browser + void connectionsChanged( ); protected: //! Show event override diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 8bd37891f3c..0bd2f6c6f22 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -51,6 +51,7 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, // Forward all browser signals connect( mBrowserWidget, &QgsBrowserDockWidget::handleDropUriList, this, &QgsDataSourceManagerDialog::handleDropUriList ); connect( mBrowserWidget, &QgsBrowserDockWidget::openFile, this, &QgsDataSourceManagerDialog::openFile ); + connect( mBrowserWidget, &QgsBrowserDockWidget::connectionsChanged, this, &QgsDataSourceManagerDialog::connectionsChanged ); connect( this, &QgsDataSourceManagerDialog::updateProjectHome, mBrowserWidget, &QgsBrowserDockWidget::updateProjectHome ); // VECTOR Layers (completely different interface: it's not a provider) @@ -115,6 +116,7 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, { this->vectorLayerAdded( vectorLayerPath, baseName, QStringLiteral( "WFS" ) ); } ); + connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); } addRasterProviderDialog( QStringLiteral( "arcgismapserver" ), tr( "ArcGIS Map Server" ), QStringLiteral( "/mActionAddAmsLayer.svg" ) ); @@ -166,6 +168,12 @@ void QgsDataSourceManagerDialog::setPreviousPage() setCurrentPage( prevPage ); } +void QgsDataSourceManagerDialog::refresh() +{ + mBrowserWidget->refresh( ); + emit dlg_refresh(); +} + void QgsDataSourceManagerDialog::rasterLayerAdded( const QString &uri, const QString &baseName, const QString &providerKey ) { emit addRasterLayer( uri, baseName, providerKey ); @@ -212,6 +220,8 @@ void QgsDataSourceManagerDialog::addDbProviderDialog( const QString providerKey, this, SIGNAL( showProgress( int, int ) ) ); connect( dlg, SIGNAL( progressMessage( QString ) ), this, SIGNAL( showStatusMessage( QString ) ) ); + connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); + connect( this, SIGNAL( dlg_refresh( ) ), dlg, SLOT( refresh( ) ) ); } } @@ -223,5 +233,7 @@ void QgsDataSourceManagerDialog::addRasterProviderDialog( const QString provider // Forward connect( dlg, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ), this, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ) ); + connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); + connect( this, SIGNAL( dlg_refresh( ) ), dlg, SLOT( refresh( ) ) ); } } diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index 5fe10138e0e..db5d85dd465 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -74,6 +74,8 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva void vectorLayersAdded( const QStringList &layerQStringList, const QString &enc, const QString &dataSourceType ); //! Reset current page to previously selected page void setPreviousPage(); + //! Refresh the browser view + void refresh( ); signals: //! Emitted when a raster layer was selected for addition: for signal forwarding to QgisApp @@ -102,6 +104,10 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva void handleDropUriList( const QgsMimeDataUtils::UriList & ); //! Update project home directory void updateProjectHome(); + //! Connections changed + void connectionsChanged( ); + // internal signal + void dlg_refresh( ); private: //! Return the dialog from the provider @@ -114,6 +120,7 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva QgsMapCanvas *mMapCanvas = nullptr; int mPreviousRow; QStringList mPageNames; + }; #endif // QGSDATASOURCEMANAGERDIALOG_H From e57399bae044c4100d9f75e4cb0517ca5b63a6b1 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 11 Jul 2017 13:52:45 +0200 Subject: [PATCH 047/266] [bugfix] Sync the dialogs connections when changed from the browser For now it's only for WMS but you get the idea. There is a new abstract base class for the source select dialogs, that will grow with common behavior for all the select dialogs. Signals are forwarded from the (root) data items to the app and then delivered to the various browser instances and to the unified layer dialog. A change in one of the browser items should trigger a refresh in all the other browsers and dialogs. --- python/core/qgsbrowsermodel.sip | 4 ++ python/core/qgsdataitem.sip | 9 ++++ python/gui/qgsbrowserdockwidget.sip | 2 +- src/core/qgsbrowsermodel.cpp | 8 +++ src/core/qgsbrowsermodel.h | 3 ++ src/core/qgsdataitem.cpp | 6 +++ src/core/qgsdataitem.h | 5 ++ src/gui/CMakeLists.txt | 3 ++ src/gui/qgsbrowserdockwidget.cpp | 5 ++ src/gui/qgsbrowserdockwidget.h | 2 +- src/gui/qgsdatasourcemanagerdialog.cpp | 6 +-- src/gui/qgsdatasourcemanagerdialog.h | 5 +- src/gui/qgssourceselect.cpp | 30 +++++++++++ src/gui/qgssourceselect.h | 65 ++++++++++++++++++++++++ src/providers/wms/qgswmsdataitems.cpp | 12 ++--- src/providers/wms/qgswmsdataitems.h | 2 +- src/providers/wms/qgswmssourceselect.cpp | 15 ++++-- src/providers/wms/qgswmssourceselect.h | 9 ++-- 18 files changed, 166 insertions(+), 25 deletions(-) create mode 100644 src/gui/qgssourceselect.cpp create mode 100644 src/gui/qgssourceselect.h diff --git a/python/core/qgsbrowsermodel.sip b/python/core/qgsbrowsermodel.sip index 64250bd6218..1a976477891 100644 --- a/python/core/qgsbrowsermodel.sip +++ b/python/core/qgsbrowsermodel.sip @@ -138,6 +138,10 @@ Refresh item children void stateChanged( const QModelIndex &index, QgsDataItem::State oldState ); %Docstring Emitted when item children fetch was finished +%End + void connectionsChanged( ); +%Docstring +notify the provider dialogs of a changed connection %End public slots: diff --git a/python/core/qgsdataitem.sip b/python/core/qgsdataitem.sip index 45e9aa2f8f8..8aefa8bb408 100644 --- a/python/core/qgsdataitem.sip +++ b/python/core/qgsdataitem.sip @@ -304,6 +304,11 @@ Remove children recursively and set as not populated. This is used when refreshi virtual void refresh(); + virtual void refreshConnections(); +%Docstring +Refresh connections: update GUI and emit signal +%End + virtual void childrenCreated(); signals: @@ -313,6 +318,10 @@ Remove children recursively and set as not populated. This is used when refreshi void endRemoveItems(); void dataChanged( QgsDataItem *item ); void stateChanged( QgsDataItem *item, QgsDataItem::State oldState ); + void connectionsChanged( ); +%Docstring +Emitted when the provider's connections of the child items have changed +%End protected slots: diff --git a/python/gui/qgsbrowserdockwidget.sip b/python/gui/qgsbrowserdockwidget.sip index 913e2cc88a5..89c466fd976 100644 --- a/python/gui/qgsbrowserdockwidget.sip +++ b/python/gui/qgsbrowserdockwidget.sip @@ -105,7 +105,7 @@ Toggle fast scan void selectionChanged( const QItemSelection &selected, const QItemSelection &deselected ); %Docstring -Selection hass changed +Selection has changed %End void splitterMoved(); %Docstring diff --git a/src/core/qgsbrowsermodel.cpp b/src/core/qgsbrowsermodel.cpp index f2a9881aa88..401e98ffa6e 100644 --- a/src/core/qgsbrowsermodel.cpp +++ b/src/core/qgsbrowsermodel.cpp @@ -139,6 +139,8 @@ void QgsBrowserModel::addRootItems() QgsDataItem *item = pr->createDataItem( QLatin1String( "" ), nullptr ); // empty path -> top level if ( item ) { + // Forward the signal from the root items to the model (and then to the app) + connect( item, &QgsDataItem::connectionsChanged, this, &QgsBrowserModel::connectionsChanged ); QgsDebugMsgLevel( "Add new top level item : " + item->name(), 4 ); connectItem( item ); providerMap.insertMulti( capabilities, item ); @@ -411,6 +413,7 @@ void QgsBrowserModel::itemStateChanged( QgsDataItem *item, QgsDataItem::State ol QgsDebugMsgLevel( QString( "item %1 state changed %2 -> %3" ).arg( item->path() ).arg( oldState ).arg( item->state() ), 4 ); emit stateChanged( idx, oldState ); } + void QgsBrowserModel::connectItem( QgsDataItem *item ) { connect( item, &QgsDataItem::beginInsertItems, @@ -425,6 +428,11 @@ void QgsBrowserModel::connectItem( QgsDataItem *item ) this, &QgsBrowserModel::itemDataChanged ); connect( item, &QgsDataItem::stateChanged, this, &QgsBrowserModel::itemStateChanged ); + + // if it's a collection item, also forwards connectionsChanged + QgsDataCollectionItem *collectionItem = dynamic_cast( item ); + if ( collectionItem ) + connect( collectionItem, &QgsDataCollectionItem::connectionsChanged, this, &QgsBrowserModel::connectionsChanged ); } QStringList QgsBrowserModel::mimeTypes() const diff --git a/src/core/qgsbrowsermodel.h b/src/core/qgsbrowsermodel.h index d9b21170235..4a9ff01018f 100644 --- a/src/core/qgsbrowsermodel.h +++ b/src/core/qgsbrowsermodel.h @@ -134,6 +134,9 @@ class CORE_EXPORT QgsBrowserModel : public QAbstractItemModel signals: //! Emitted when item children fetch was finished void stateChanged( const QModelIndex &index, QgsDataItem::State oldState ); + //! Connections changed in the browser, forwarded to the widget and used to + //! notify the provider dialogs of a changed connection + void connectionsChanged( ); public slots: //! Reload the whole model diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp index 30f9a773b01..e39aa322361 100644 --- a/src/core/qgsdataitem.cpp +++ b/src/core/qgsdataitem.cpp @@ -342,6 +342,12 @@ void QgsDataItem::refresh() } } +void QgsDataItem::refreshConnections() +{ + refresh( ); + emit connectionsChanged( ); +} + void QgsDataItem::refresh( const QVector &children ) { QgsDebugMsgLevel( "mPath = " + mPath, 2 ); diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 2dc8123a94a..083daaa8c36 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -283,6 +283,9 @@ class CORE_EXPORT QgsDataItem : public QObject virtual void refresh(); + //! Refresh connections: update GUI and emit signal + virtual void refreshConnections(); + virtual void childrenCreated(); signals: @@ -292,6 +295,8 @@ class CORE_EXPORT QgsDataItem : public QObject void endRemoveItems(); void dataChanged( QgsDataItem *item ); void stateChanged( QgsDataItem *item, QgsDataItem::State oldState ); + //! Emitted when the provider's connections of the child items have changed + void connectionsChanged( ); protected slots: diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a9716063428..79f09c5af28 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -346,6 +346,7 @@ SET(QGIS_GUI_SRCS qgsvertexmarker.cpp qgsfiledownloader.cpp qgsdatasourcemanagerdialog.cpp + qgssourceselect.cpp ) SET(QGIS_GUI_MOC_HDRS @@ -502,6 +503,7 @@ SET(QGIS_GUI_MOC_HDRS ogr/qgsopenvectorlayerdialog.h ogr/qgsnewogrconnection.h ogr/qgsvectorlayersaveasdialog.h + qgssourceselect.h raster/qgsmultibandcolorrendererwidget.h raster/qgspalettedrendererwidget.h @@ -710,6 +712,7 @@ SET(QGIS_GUI_HDRS ogr/qgsnewogrconnection.h ogr/qgsvectorlayersaveasdialog.h + qgssourceselect.h attributetable/qgsfeaturemodel.h diff --git a/src/gui/qgsbrowserdockwidget.cpp b/src/gui/qgsbrowserdockwidget.cpp index c728b2fc776..6dca2149f14 100644 --- a/src/gui/qgsbrowserdockwidget.cpp +++ b/src/gui/qgsbrowserdockwidget.cpp @@ -129,6 +129,11 @@ void QgsBrowserDockWidget::showEvent( QShowEvent *e ) connect( mBrowserView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsBrowserDockWidget::selectionChanged ); + // Forward the model changed signals to the widget + connect( mModel, &QgsBrowserModel::connectionsChanged, + this, &QgsBrowserDockWidget::connectionsChanged ); + + // objectName used by settingsSection() is not yet set in constructor QgsSettings settings; mPropertiesWidgetEnabled = settings.value( settingsSection() + "/propertiesWidgetEnabled", false ).toBool(); diff --git a/src/gui/qgsbrowserdockwidget.h b/src/gui/qgsbrowserdockwidget.h index a9a6d8d9a07..789d02d5760 100644 --- a/src/gui/qgsbrowserdockwidget.h +++ b/src/gui/qgsbrowserdockwidget.h @@ -92,7 +92,7 @@ class GUI_EXPORT QgsBrowserDockWidget : public QgsDockWidget, private Ui::QgsBro //! Toggle fast scan void toggleFastScan(); - //! Selection hass changed + //! Selection has changed void selectionChanged( const QItemSelection &selected, const QItemSelection &deselected ); //! Splitter has been moved void splitterMoved(); diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 0bd2f6c6f22..836174ba0f8 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -171,7 +171,7 @@ void QgsDataSourceManagerDialog::setPreviousPage() void QgsDataSourceManagerDialog::refresh() { mBrowserWidget->refresh( ); - emit dlg_refresh(); + emit providerDialogsRefreshRequested(); } void QgsDataSourceManagerDialog::rasterLayerAdded( const QString &uri, const QString &baseName, const QString &providerKey ) @@ -221,7 +221,7 @@ void QgsDataSourceManagerDialog::addDbProviderDialog( const QString providerKey, connect( dlg, SIGNAL( progressMessage( QString ) ), this, SIGNAL( showStatusMessage( QString ) ) ); connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); - connect( this, SIGNAL( dlg_refresh( ) ), dlg, SLOT( refresh( ) ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); } } @@ -234,6 +234,6 @@ void QgsDataSourceManagerDialog::addRasterProviderDialog( const QString provider connect( dlg, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ), this, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ) ); connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); - connect( this, SIGNAL( dlg_refresh( ) ), dlg, SLOT( refresh( ) ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); } } diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index db5d85dd465..ff41601f862 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -106,8 +106,9 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva void updateProjectHome(); //! Connections changed void connectionsChanged( ); - // internal signal - void dlg_refresh( ); + //! One or more provider connections have changed and the + //! dialogs should be refreshed + void providerDialogsRefreshRequested( ); private: //! Return the dialog from the provider diff --git a/src/gui/qgssourceselect.cpp b/src/gui/qgssourceselect.cpp new file mode 100644 index 00000000000..9308098a391 --- /dev/null +++ b/src/gui/qgssourceselect.cpp @@ -0,0 +1,30 @@ +/*************************************************************************** + qgssourceselect.cpp - base class for source selector widgets + ------------------- + begin : 10 July 2017 + original : (C) 2017 by Alessandro Pasotti email : apasotti at boundlessgeo dot com + + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgssourceselect.h" + +QgsSourceSelect::QgsSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): + QDialog( parent, fl ), + mWidgetMode( widgetMode ) +{ + +} + +QgsSourceSelect::~QgsSourceSelect() +{ + +} diff --git a/src/gui/qgssourceselect.h b/src/gui/qgssourceselect.h new file mode 100644 index 00000000000..87529c1c82a --- /dev/null +++ b/src/gui/qgssourceselect.h @@ -0,0 +1,65 @@ +/*************************************************************************** + qgssourceselect.h - base class for source selector widgets + ------------------- + begin : 10 July 2017 + original : (C) 2017 by Alessandro Pasotti email : apasotti at boundlessgeo dot com + + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSSOURCESELECT_H +#define QGSSOURCESELECT_H +#include "qgis_sip.h" +#include "qgis.h" +#include "qgis_gui.h" + +#include "qgsproviderregistry.h" +#include "qgsguiutils.h" + +#include + +/** \ingroup gui + * \brief Abstract base Dialog to create connections and add layers + * This class must provide common functionality and the interface for all + * source select dialogs used by data providers to configure data sources + * and add layers. + */ +class GUI_EXPORT QgsSourceSelect : public QDialog +{ + Q_OBJECT + + public: + + //! Constructor + QgsSourceSelect( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + + //! Destructor + ~QgsSourceSelect( ); + + //! Return the widget mode + QgsProviderRegistry::WidgetMode widgetMode( ) { return mWidgetMode; } + + public slots: + + //! Triggered when the provider's connections need to be refreshed + virtual void refresh( ) = 0; + + signals: + + //! Emitted when the provider's connections have changed + void connectionsChanged(); + + private: + + QgsProviderRegistry::WidgetMode mWidgetMode; +}; + +#endif // QGSSOURCESELECT_H diff --git a/src/providers/wms/qgswmsdataitems.cpp b/src/providers/wms/qgswmsdataitems.cpp index 9b3065323de..9a07aecff0b 100644 --- a/src/providers/wms/qgswmsdataitems.cpp +++ b/src/providers/wms/qgswmsdataitems.cpp @@ -241,7 +241,7 @@ void QgsWMSConnectionItem::editConnection() if ( nc.exec() ) { // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } } @@ -249,7 +249,7 @@ void QgsWMSConnectionItem::deleteConnection() { QgsWMSConnection::deleteConnection( mName ); // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } #endif @@ -418,22 +418,16 @@ QList QgsWMSRootItem::actions() QWidget *QgsWMSRootItem::paramWidget() { QgsWMSSourceSelect *select = new QgsWMSSourceSelect( nullptr, 0, QgsProviderRegistry::WidgetMode::Manager ); - connect( select, &QgsWMSSourceSelect::connectionsChanged, this, &QgsWMSRootItem::connectionsChanged ); return select; } -void QgsWMSRootItem::connectionsChanged() -{ - refresh(); -} - void QgsWMSRootItem::newConnection() { QgsNewHttpConnection nc( nullptr ); if ( nc.exec() ) { - refresh(); + refreshConnections( ); } } #endif diff --git a/src/providers/wms/qgswmsdataitems.h b/src/providers/wms/qgswmsdataitems.h index dd853650eb6..facd17c9fdb 100644 --- a/src/providers/wms/qgswmsdataitems.h +++ b/src/providers/wms/qgswmsdataitems.h @@ -107,9 +107,9 @@ class QgsWMSRootItem : public QgsDataCollectionItem public slots: #ifdef HAVE_GUI - void connectionsChanged(); void newConnection(); #endif + }; diff --git a/src/providers/wms/qgswmssourceselect.cpp b/src/providers/wms/qgswmssourceselect.cpp index 2512b6b59bd..20db899c3f9 100644 --- a/src/providers/wms/qgswmssourceselect.cpp +++ b/src/providers/wms/qgswmssourceselect.cpp @@ -55,14 +55,13 @@ #include QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) - , mWidgetMode( widgetMode ) + : QgsSourceSelect( parent, fl, widgetMode ) , mDefaultCRS( GEO_EPSG_CRS_AUTHID ) , mCurrentTileset( nullptr ) { setupUi( this ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { // For some obscure reason hiding does not work! // buttonBox->button( QDialogButtonBox::Close )->hide(); @@ -81,7 +80,7 @@ QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs mImageFormatGroup = new QButtonGroup; - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsWMSSourceSelect::addClicked ); @@ -156,6 +155,13 @@ QgsWMSSourceSelect::~QgsWMSSourceSelect() settings.setValue( QStringLiteral( "Windows/WMSSourceSelect/geometry" ), saveGeometry() ); } +void QgsWMSSourceSelect::refresh( ) +{ + // Reload WMS connections and update the GUI + QgsDebugMsg( "Refreshing WMS connections ..." ); + populateConnectionList( ); +} + void QgsWMSSourceSelect::populateConnectionList() { @@ -164,6 +170,7 @@ void QgsWMSSourceSelect::populateConnectionList() setConnectionListPosition(); } + void QgsWMSSourceSelect::on_btnNew_clicked() { QgsNewHttpConnection *nc = new QgsNewHttpConnection( this ); diff --git a/src/providers/wms/qgswmssourceselect.h b/src/providers/wms/qgswmssourceselect.h index 54ae9d05efc..5fc06677bef 100644 --- a/src/providers/wms/qgswmssourceselect.h +++ b/src/providers/wms/qgswmssourceselect.h @@ -23,6 +23,7 @@ #include "qgshelp.h" #include "qgsproviderregistry.h" #include "qgswmsprovider.h" +#include "qgssourceselect.h" #include #include @@ -42,7 +43,7 @@ class QgsWmsCapabilities; * The user can then connect and add * layers from the WMS server to the map canvas. */ -class QgsWMSSourceSelect : public QDialog, private Ui::QgsWMSSourceSelectBase +class QgsWMSSourceSelect : public QgsSourceSelect, private Ui::QgsWMSSourceSelectBase { Q_OBJECT @@ -54,6 +55,9 @@ class QgsWMSSourceSelect : public QDialog, private Ui::QgsWMSSourceSelectBase public slots: + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; + //! Opens the create connection dialog to build a new connection void on_btnNew_clicked(); //! Opens a dialog to edit an existing connection @@ -108,9 +112,6 @@ class QgsWMSSourceSelect : public QDialog, private Ui::QgsWMSSourceSelectBase //! Add a few example servers to the list. void addDefaultServers(); - //! Embedded mode, without 'Close' - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; - //! Selected CRS QString mCRS; From 593770a5799884bfdbe34c85b603c223285482e7 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 11 Jul 2017 17:21:34 +0200 Subject: [PATCH 048/266] Added python bindings for the base source select class --- python/gui/qgssourceselect.sip | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 python/gui/qgssourceselect.sip diff --git a/python/gui/qgssourceselect.sip b/python/gui/qgssourceselect.sip new file mode 100644 index 00000000000..6d60ae05c1f --- /dev/null +++ b/python/gui/qgssourceselect.sip @@ -0,0 +1,65 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgssourceselect.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsSourceSelect : QDialog +{ +%Docstring + Abstract base Dialog to create connections and add layers + This class must provide common functionality and the interface for all + source select dialogs used by data providers to configure data sources + and add layers. +%End + +%TypeHeaderCode +#include "qgssourceselect.h" +%End + public: + + QgsSourceSelect( QWidget *parent /TransferThis/ = 0, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); +%Docstring +Constructor +%End + + ~QgsSourceSelect( ); +%Docstring +Destructor +%End + + QgsProviderRegistry::WidgetMode widgetMode( ); +%Docstring +Return the widget mode + :rtype: QgsProviderRegistry.WidgetMode +%End + + public slots: + + virtual void refresh( ) = 0; +%Docstring +Triggered when the provider's connections need to be refreshed +%End + + signals: + + void connectionsChanged(); +%Docstring +Emitted when the provider's connections have changed +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgssourceselect.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ From 385bb86854fa5c096acd476eff4614fd934edd6a Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 11 Jul 2017 17:22:42 +0200 Subject: [PATCH 049/266] SIP include --- python/gui/gui_auto.sip | 1 + 1 file changed, 1 insertion(+) diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 3f1b9e7ead5..5ae4531955e 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -21,6 +21,7 @@ %Include qgsbrowserdockwidget.sip %Include qgsvertexmarker.sip %Include qgsfiledownloader.sip +%Include qgssourceselect.sip %Include attributetable/qgsfeaturemodel.sip %Include auth/qgsauthauthoritieseditor.sip %Include auth/qgsauthcertificateinfo.sip From bc516cf82a478747a84369996b2e769ffa1914b4 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 11 Jul 2017 17:44:44 +0200 Subject: [PATCH 050/266] Added since 3.0 --- python/gui/qgssourceselect.sip | 1 + src/gui/qgssourceselect.h | 1 + 2 files changed, 2 insertions(+) diff --git a/python/gui/qgssourceselect.sip b/python/gui/qgssourceselect.sip index 6d60ae05c1f..ea08591a1c3 100644 --- a/python/gui/qgssourceselect.sip +++ b/python/gui/qgssourceselect.sip @@ -17,6 +17,7 @@ class QgsSourceSelect : QDialog This class must provide common functionality and the interface for all source select dialogs used by data providers to configure data sources and add layers. +.. versionadded:: 3.0 %End %TypeHeaderCode diff --git a/src/gui/qgssourceselect.h b/src/gui/qgssourceselect.h index 87529c1c82a..86bc591269b 100644 --- a/src/gui/qgssourceselect.h +++ b/src/gui/qgssourceselect.h @@ -31,6 +31,7 @@ * This class must provide common functionality and the interface for all * source select dialogs used by data providers to configure data sources * and add layers. + * \since QGIS 3.0 */ class GUI_EXPORT QgsSourceSelect : public QDialog { From 7bb797f8a99e00cd25c49d9329440cda127d1845 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Wed, 12 Jul 2017 16:11:27 +0200 Subject: [PATCH 051/266] Renamed QgsSourceSelectDialog to QgsArcGisServiceSourceSelect --- python/gui/gui_auto.sip | 2 +- python/gui/qgsarcgisservicesourceselect.sip | 86 +++++++++++++++++++ src/app/qgisapp.cpp | 2 +- src/gui/CMakeLists.txt | 10 +-- ...g.cpp => qgsarcgisservicesourceselect.cpp} | 64 +++++++------- ...ialog.h => qgsarcgisservicesourceselect.h} | 16 ++-- src/gui/qgsdatasourcemanagerdialog.cpp | 8 +- src/providers/arcgisrest/qgsafsdataitems.cpp | 2 +- .../arcgisrest/qgsafssourceselect.cpp | 2 +- src/providers/arcgisrest/qgsafssourceselect.h | 4 +- src/providers/arcgisrest/qgsamsdataitems.cpp | 2 +- .../arcgisrest/qgsamssourceselect.cpp | 2 +- src/providers/arcgisrest/qgsamssourceselect.h | 4 +- ...ui => qgsarcgisservicesourceselectbase.ui} | 4 +- 14 files changed, 147 insertions(+), 61 deletions(-) create mode 100644 python/gui/qgsarcgisservicesourceselect.sip rename src/gui/{qgssourceselectdialog.cpp => qgsarcgisservicesourceselect.cpp} (84%) rename src/gui/{qgssourceselectdialog.h => qgsarcgisservicesourceselect.h} (89%) rename src/ui/{qgssourceselectdialogbase.ui => qgsarcgisservicesourceselectbase.ui} (98%) diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 5ae4531955e..10083e32bc7 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -158,7 +158,7 @@ %Include qgsoptionswidgetfactory.sip %Include qgsorderbydialog.sip %Include qgsowssourceselect.sip -%Include qgssourceselectdialog.sip +%Include qgsarcgisservicesourceselect.sip %Include qgspanelwidget.sip %Include qgspanelwidgetstack.sip %Include qgspasswordlineedit.sip diff --git a/python/gui/qgsarcgisservicesourceselect.sip b/python/gui/qgsarcgisservicesourceselect.sip new file mode 100644 index 00000000000..3c7283ef79e --- /dev/null +++ b/python/gui/qgsarcgisservicesourceselect.sip @@ -0,0 +1,86 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsarcgisservicesourceselect.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsArcGisServiceSourceSelect : QDialog, protected Ui::QgsArcGisServiceSourceSelectBase +{ +%Docstring + Generic class listing layers available from a remote service. +%End + +%TypeHeaderCode +#include "qgsarcgisservicesourceselect.h" +%End + public: + enum ServiceType { MapService, FeatureService }; + + QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ); +%Docstring +Constructor +%End + + ~QgsArcGisServiceSourceSelect(); + void setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ); +%Docstring +Sets the current extent and CRS. Used to select an appropriate CRS and possibly to retrieve data only in the current extent +%End + + signals: + void addLayer( QString uri, QString typeName ); +%Docstring +Emitted when a layer is added from the dialog +%End + void connectionsChanged(); +%Docstring +Emitted when the connections for the service were changed +%End + + protected: + + virtual bool connectToService( const QgsOwsConnection &connection ) = 0; +%Docstring +To be implemented in the child class. Called when a new connection is initiated. + :rtype: bool +%End + virtual void buildQuery( const QgsOwsConnection &, const QModelIndex & ); +%Docstring +May be implemented in child classes for services which support customized queries. +%End + virtual QString getLayerURI( const QgsOwsConnection &connection, + const QString &layerTitle, + const QString &layerName, + const QString &crs = QString(), + const QString &filter = QString(), + const QgsRectangle &bBox = QgsRectangle() ) const = 0; +%Docstring +To be implemented in the child class. Constructs an URI for the specified service layer. + :rtype: str +%End + void populateImageEncodings( const QStringList &availableEncodings ); +%Docstring +Updates the UI for the list of available image encodings from the specified list. +%End + QString getSelectedImageEncoding() const; +%Docstring +Returns the selected image encoding. + :rtype: str +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsarcgisservicesourceselect.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index bcf77d409bf..6c57310cc0b 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -247,7 +247,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsshortcutsmanager.h" #include "qgssinglebandgrayrenderer.h" #include "qgssnappingwidget.h" -#include "qgssourceselectdialog.h" +#include "qgsarcgisservicesourceselect.h" #include "qgsstatisticalsummarydockwidget.h" #include "qgsstatusbar.h" #include "qgsstatusbarcoordinateswidget.h" diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 79f09c5af28..e96328d056f 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -302,7 +302,7 @@ SET(QGIS_GUI_SRCS qgsoptionsdialogbase.cpp qgsorderbydialog.cpp qgsowssourceselect.cpp - qgssourceselectdialog.cpp + qgsarcgisservicesourceselect.cpp qgspanelwidget.cpp qgspanelwidgetstack.cpp qgspasswordlineedit.cpp @@ -458,7 +458,7 @@ SET(QGIS_GUI_MOC_HDRS qgsoptionswidgetfactory.h qgsorderbydialog.h qgsowssourceselect.h - qgssourceselectdialog.h + qgsarcgisservicesourceselect.h qgspanelwidget.h qgspanelwidgetstack.h qgspasswordlineedit.h @@ -499,11 +499,11 @@ SET(QGIS_GUI_MOC_HDRS qgsvariableeditorwidget.h qgsfiledownloader.h qgsdatasourcemanagerdialog.h + qgssourceselect.h ogr/qgsopenvectorlayerdialog.h ogr/qgsnewogrconnection.h ogr/qgsvectorlayersaveasdialog.h - qgssourceselect.h raster/qgsmultibandcolorrendererwidget.h raster/qgspalettedrendererwidget.h @@ -706,14 +706,13 @@ SET(QGIS_GUI_HDRS qgsvertexmarker.h qgsfiledownloader.h qgsdatasourcemanagerdialog.h + qgssourceselect.h ogr/qgsopenvectorlayerdialog.h ogr/qgsogrhelperfunctions.h ogr/qgsnewogrconnection.h ogr/qgsvectorlayersaveasdialog.h - qgssourceselect.h - attributetable/qgsfeaturemodel.h auth/qgsauthauthoritieseditor.h @@ -801,6 +800,7 @@ SET(QGIS_GUI_UI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsmessagelogviewer.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsmessageviewer.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsowssourceselectbase.h + ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsarcgisservicesourceselectbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsprojectionselectorbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsquerybuilderbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssqlcomposerdialogbase.h diff --git a/src/gui/qgssourceselectdialog.cpp b/src/gui/qgsarcgisservicesourceselect.cpp similarity index 84% rename from src/gui/qgssourceselectdialog.cpp rename to src/gui/qgsarcgisservicesourceselect.cpp index 9042de77833..c31fc1cba9a 100644 --- a/src/gui/qgssourceselectdialog.cpp +++ b/src/gui/qgsarcgisservicesourceselect.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - qgssourceselectdialog.cpp + qgsarcgisservicesourceselect.cpp ------------------------- begin : Nov 26, 2015 copyright : (C) 2015 by Sandro Mani @@ -15,7 +15,7 @@ * * ***************************************************************************/ -#include "qgssourceselectdialog.h" +#include "qgsarcgisservicesourceselect.h" #include "qgsowsconnection.h" #include "qgsnewhttpconnection.h" #include "qgsprojectionselectiondialog.h" @@ -49,7 +49,7 @@ class QgsSourceSelectItemDelegate : public QItemDelegate }; -QgsSourceSelectDialog::QgsSourceSelectDialog( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ) +QgsArcGisServiceSourceSelect::QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ) : QDialog( parent, fl ), mServiceName( serviceName ), mServiceType( serviceType ), @@ -61,22 +61,22 @@ QgsSourceSelectDialog::QgsSourceSelectDialog( const QString &serviceName, Servic mAddButton = buttonBox->addButton( tr( "&Add" ), QDialogButtonBox::ActionRole ); mAddButton->setEnabled( false ); - connect( mAddButton, &QAbstractButton::clicked, this, &QgsSourceSelectDialog::addButtonClicked ); + connect( mAddButton, &QAbstractButton::clicked, this, &QgsArcGisServiceSourceSelect::addButtonClicked ); if ( mServiceType == FeatureService ) { mBuildQueryButton = buttonBox->addButton( tr( "&Build query" ), QDialogButtonBox::ActionRole ); mBuildQueryButton->setDisabled( true ); - connect( mBuildQueryButton, &QAbstractButton::clicked, this, &QgsSourceSelectDialog::buildQueryButtonClicked ); + connect( mBuildQueryButton, &QAbstractButton::clicked, this, &QgsArcGisServiceSourceSelect::buildQueryButtonClicked ); } connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject ); - connect( btnNew, &QAbstractButton::clicked, this, &QgsSourceSelectDialog::addEntryToServerList ); - connect( btnEdit, &QAbstractButton::clicked, this, &QgsSourceSelectDialog::modifyEntryOfServerList ); - connect( btnDelete, &QAbstractButton::clicked, this, &QgsSourceSelectDialog::deleteEntryOfServerList ); - connect( btnConnect, &QAbstractButton::clicked, this, &QgsSourceSelectDialog::connectToServer ); - connect( btnChangeSpatialRefSys, &QAbstractButton::clicked, this, &QgsSourceSelectDialog::changeCrs ); - connect( lineFilter, &QLineEdit::textChanged, this, &QgsSourceSelectDialog::filterChanged ); + connect( btnNew, &QAbstractButton::clicked, this, &QgsArcGisServiceSourceSelect::addEntryToServerList ); + connect( btnEdit, &QAbstractButton::clicked, this, &QgsArcGisServiceSourceSelect::modifyEntryOfServerList ); + connect( btnDelete, &QAbstractButton::clicked, this, &QgsArcGisServiceSourceSelect::deleteEntryOfServerList ); + connect( btnConnect, &QAbstractButton::clicked, this, &QgsArcGisServiceSourceSelect::connectToServer ); + connect( btnChangeSpatialRefSys, &QAbstractButton::clicked, this, &QgsArcGisServiceSourceSelect::changeCrs ); + connect( lineFilter, &QLineEdit::textChanged, this, &QgsArcGisServiceSourceSelect::filterChanged ); populateConnectionList(); mProjectionSelector = new QgsProjectionSelectionDialog( this ); mProjectionSelector->setMessage( QString() ); @@ -108,11 +108,11 @@ QgsSourceSelectDialog::QgsSourceSelectDialog( const QString &serviceName, Servic mModelProxy->setSortCaseSensitivity( Qt::CaseInsensitive ); treeView->setModel( mModelProxy ); - connect( treeView, &QAbstractItemView::doubleClicked, this, &QgsSourceSelectDialog::treeWidgetItemDoubleClicked ); - connect( treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QgsSourceSelectDialog::treeWidgetCurrentRowChanged ); + connect( treeView, &QAbstractItemView::doubleClicked, this, &QgsArcGisServiceSourceSelect::treeWidgetItemDoubleClicked ); + connect( treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QgsArcGisServiceSourceSelect::treeWidgetCurrentRowChanged ); } -QgsSourceSelectDialog::~QgsSourceSelectDialog() +QgsArcGisServiceSourceSelect::~QgsArcGisServiceSourceSelect() { QgsSettings settings; settings.setValue( QStringLiteral( "Windows/SourceSelectDialog/geometry" ), saveGeometry() ); @@ -123,13 +123,13 @@ QgsSourceSelectDialog::~QgsSourceSelectDialog() delete mModelProxy; } -void QgsSourceSelectDialog::setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ) +void QgsArcGisServiceSourceSelect::setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ) { mCanvasExtent = canvasExtent; mCanvasCrs = canvasCrs; } -void QgsSourceSelectDialog::populateImageEncodings( const QStringList &availableEncodings ) +void QgsArcGisServiceSourceSelect::populateImageEncodings( const QStringList &availableEncodings ) { QLayoutItem *item = nullptr; while ( ( item = gbImageEncoding->layout()->takeAt( 0 ) ) != nullptr ) @@ -162,12 +162,12 @@ void QgsSourceSelectDialog::populateImageEncodings( const QStringList &available } } -QString QgsSourceSelectDialog::getSelectedImageEncoding() const +QString QgsArcGisServiceSourceSelect::getSelectedImageEncoding() const { return mImageEncodingGroup ? mImageEncodingGroup->checkedButton()->text() : QString(); } -void QgsSourceSelectDialog::populateConnectionList() +void QgsArcGisServiceSourceSelect::populateConnectionList() { QStringList conns = QgsOwsConnection::connectionList( mServiceName ); cmbConnections->clear(); @@ -190,7 +190,7 @@ void QgsSourceSelectDialog::populateConnectionList() } } -QString QgsSourceSelectDialog::getPreferredCrs( const QSet &crsSet ) const +QString QgsArcGisServiceSourceSelect::getPreferredCrs( const QSet &crsSet ) const { if ( crsSet.size() < 1 ) { @@ -221,7 +221,7 @@ QString QgsSourceSelectDialog::getPreferredCrs( const QSet &crsSet ) co return *( crsSet.constBegin() ); } -void QgsSourceSelectDialog::addEntryToServerList() +void QgsArcGisServiceSourceSelect::addEntryToServerList() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-%1/" ).arg( mServiceName.toLower() ) ); @@ -234,7 +234,7 @@ void QgsSourceSelectDialog::addEntryToServerList() } } -void QgsSourceSelectDialog::modifyEntryOfServerList() +void QgsArcGisServiceSourceSelect::modifyEntryOfServerList() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-%1/" ).arg( mServiceName.toLower() ), cmbConnections->currentText() ); nc.setWindowTitle( tr( "Modify %1 connection" ).arg( mServiceName ) ); @@ -246,7 +246,7 @@ void QgsSourceSelectDialog::modifyEntryOfServerList() } } -void QgsSourceSelectDialog::deleteEntryOfServerList() +void QgsArcGisServiceSourceSelect::deleteEntryOfServerList() { QString msg = tr( "Are you sure you want to remove the %1 connection and all associated settings?" ) .arg( cmbConnections->currentText() ); @@ -264,7 +264,7 @@ void QgsSourceSelectDialog::deleteEntryOfServerList() } } -void QgsSourceSelectDialog::connectToServer() +void QgsArcGisServiceSourceSelect::connectToServer() { bool haveLayers = false; btnConnect->setEnabled( false ); @@ -308,7 +308,7 @@ void QgsSourceSelectDialog::connectToServer() btnChangeSpatialRefSys->setEnabled( haveLayers ); } -void QgsSourceSelectDialog::addButtonClicked() +void QgsArcGisServiceSourceSelect::addButtonClicked() { if ( treeView->selectionModel()->selectedRows().isEmpty() ) { @@ -368,7 +368,7 @@ void QgsSourceSelectDialog::addButtonClicked() accept(); } -void QgsSourceSelectDialog::changeCrs() +void QgsArcGisServiceSourceSelect::changeCrs() { if ( mProjectionSelector->exec() ) { @@ -377,7 +377,7 @@ void QgsSourceSelectDialog::changeCrs() } } -void QgsSourceSelectDialog::changeCrsFilter() +void QgsArcGisServiceSourceSelect::changeCrsFilter() { QgsDebugMsg( "changeCRSFilter called" ); //evaluate currently selected typename and set the CRS filter in mProjectionSelector @@ -411,20 +411,20 @@ void QgsSourceSelectDialog::changeCrsFilter() } } -void QgsSourceSelectDialog::on_cmbConnections_activated( int index ) +void QgsArcGisServiceSourceSelect::on_cmbConnections_activated( int index ) { Q_UNUSED( index ); QgsOwsConnection::setSelectedConnection( mServiceName, cmbConnections->currentText() ); } -void QgsSourceSelectDialog::treeWidgetItemDoubleClicked( const QModelIndex &index ) +void QgsArcGisServiceSourceSelect::treeWidgetItemDoubleClicked( const QModelIndex &index ) { QgsDebugMsg( "double-click called" ); QgsOwsConnection connection( mServiceName, cmbConnections->currentText() ); buildQuery( connection, index ); } -void QgsSourceSelectDialog::treeWidgetCurrentRowChanged( const QModelIndex ¤t, const QModelIndex &previous ) +void QgsArcGisServiceSourceSelect::treeWidgetCurrentRowChanged( const QModelIndex ¤t, const QModelIndex &previous ) { Q_UNUSED( previous ) QgsDebugMsg( "treeWidget_currentRowChanged called" ); @@ -436,14 +436,14 @@ void QgsSourceSelectDialog::treeWidgetCurrentRowChanged( const QModelIndex &curr mAddButton->setEnabled( current.isValid() ); } -void QgsSourceSelectDialog::buildQueryButtonClicked() +void QgsArcGisServiceSourceSelect::buildQueryButtonClicked() { QgsDebugMsg( "mBuildQueryButton click called" ); QgsOwsConnection connection( mServiceName, cmbConnections->currentText() ); buildQuery( connection, treeView->selectionModel()->currentIndex() ); } -void QgsSourceSelectDialog::filterChanged( const QString &text ) +void QgsArcGisServiceSourceSelect::filterChanged( const QString &text ) { QgsDebugMsg( "FeatureType filter changed to :" + text ); QRegExp::PatternSyntax mySyntax = QRegExp::PatternSyntax( QRegExp::RegExp ); @@ -465,7 +465,7 @@ QSize QgsSourceSelectItemDelegate::sizeHint( const QStyleOptionViewItem &option, return size; } -void QgsSourceSelectDialog::on_buttonBox_helpRequested() const +void QgsArcGisServiceSourceSelect::on_buttonBox_helpRequested() const { QgsContextHelp::run( metaObject()->className() ); } diff --git a/src/gui/qgssourceselectdialog.h b/src/gui/qgsarcgisservicesourceselect.h similarity index 89% rename from src/gui/qgssourceselectdialog.h rename to src/gui/qgsarcgisservicesourceselect.h index 39114720ac1..438838813f5 100644 --- a/src/gui/qgssourceselectdialog.h +++ b/src/gui/qgsarcgisservicesourceselect.h @@ -1,5 +1,5 @@ /*************************************************************************** - qgssourceselectdialog.h + qgsarcgisservicesourceselect.h --------------------- begin : Nov 26, 2015 copyright : (C) 2015 by Sandro Mani @@ -13,10 +13,10 @@ * * ***************************************************************************/ -#ifndef QGSSOURCESELECTDIALOG_H -#define QGSSOURCESELECTDIALOG_H +#ifndef QGSARCGISSERVICESOURCESELECTDIALOG_H +#define QGSARCGISSERVICESOURCESELECTDIALOG_H -#include "ui_qgssourceselectdialogbase.h" +#include "ui_qgsarcgisservicesourceselectbase.h" #include "qgsrectangle.h" #include "qgscoordinatereferencesystem.h" @@ -30,7 +30,7 @@ class QgsOwsConnection; /** \ingroup gui * Generic class listing layers available from a remote service. */ -class GUI_EXPORT QgsSourceSelectDialog : public QDialog, protected Ui::QgsSourceSelectBase +class GUI_EXPORT QgsArcGisServiceSourceSelect : public QDialog, protected Ui::QgsArcGisServiceSourceSelectBase { Q_OBJECT @@ -39,9 +39,9 @@ class GUI_EXPORT QgsSourceSelectDialog : public QDialog, protected Ui::QgsSource enum ServiceType { MapService, FeatureService }; //! Constructor - QgsSourceSelectDialog( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ); + QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ); - ~QgsSourceSelectDialog(); + ~QgsArcGisServiceSourceSelect(); //! Sets the current extent and CRS. Used to select an appropriate CRS and possibly to retrieve data only in the current extent void setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ); @@ -108,4 +108,4 @@ class GUI_EXPORT QgsSourceSelectDialog : public QDialog, protected Ui::QgsSource }; -#endif // QGSSOURCESELECTDIALOG_H +#endif // QGSARCGISSERVICESOURCESELECTDIALOG_H diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 836174ba0f8..a6264557c68 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -23,7 +23,7 @@ #include "qgssettings.h" #include "qgsproviderregistry.h" #include "qgsopenvectorlayerdialog.h" -#include "qgssourceselectdialog.h" +#include "qgsarcgisservicesourceselect.h" #include "qgsmapcanvas.h" @@ -121,9 +121,9 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, addRasterProviderDialog( QStringLiteral( "arcgismapserver" ), tr( "ArcGIS Map Server" ), QStringLiteral( "/mActionAddAmsLayer.svg" ) ); - QgsSourceSelectDialog *afss = dynamic_cast( providerDialog( QStringLiteral( "arcgisfeatureserver" ), - tr( "ArcGIS Feature Server" ), - QStringLiteral( "/mActionAddAfsLayer.svg" ) ) ); + QgsArcGisServiceSourceSelect *afss = dynamic_cast( providerDialog( QStringLiteral( "arcgisfeatureserver" ), + tr( "ArcGIS Feature Server" ), + QStringLiteral( "/mActionAddAfsLayer.svg" ) ) ); if ( afss && mMapCanvas ) { afss->setCurrentExtentAndCrs( mMapCanvas->extent(), mMapCanvas->mapSettings().destinationCrs() ); diff --git a/src/providers/arcgisrest/qgsafsdataitems.cpp b/src/providers/arcgisrest/qgsafsdataitems.cpp index 61bf848f28e..5b44432bfb2 100644 --- a/src/providers/arcgisrest/qgsafsdataitems.cpp +++ b/src/providers/arcgisrest/qgsafsdataitems.cpp @@ -60,7 +60,7 @@ QList QgsAfsRootItem::actions() QWidget *QgsAfsRootItem::paramWidget() { QgsAfsSourceSelect *select = new QgsAfsSourceSelect( 0, 0, QgsProviderRegistry::WidgetMode::Manager ); - connect( select, &QgsSourceSelectDialog::connectionsChanged, this, &QgsAfsRootItem::connectionsChanged ); + connect( select, &QgsArcGisServiceSourceSelect::connectionsChanged, this, &QgsAfsRootItem::connectionsChanged ); return select; } diff --git a/src/providers/arcgisrest/qgsafssourceselect.cpp b/src/providers/arcgisrest/qgsafssourceselect.cpp index 48d607f908c..0acdbb2bd8f 100644 --- a/src/providers/arcgisrest/qgsafssourceselect.cpp +++ b/src/providers/arcgisrest/qgsafssourceselect.cpp @@ -27,7 +27,7 @@ QgsAfsSourceSelect::QgsAfsSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelectDialog( QStringLiteral( "ArcGisFeatureServer" ), QgsSourceSelectDialog::FeatureService, parent, fl ) + : QgsArcGisServiceSourceSelect( QStringLiteral( "ArcGisFeatureServer" ), QgsArcGisServiceSourceSelect::FeatureService, parent, fl ) { if ( widgetMode == QgsProviderRegistry::WidgetMode::Embedded || widgetMode == QgsProviderRegistry::WidgetMode::Manager ) { diff --git a/src/providers/arcgisrest/qgsafssourceselect.h b/src/providers/arcgisrest/qgsafssourceselect.h index 9aba6f53699..db68c6ce30d 100644 --- a/src/providers/arcgisrest/qgsafssourceselect.h +++ b/src/providers/arcgisrest/qgsafssourceselect.h @@ -20,11 +20,11 @@ #include "qgsguiutils.h" #include "qgsproviderregistry.h" -#include "qgssourceselectdialog.h" +#include "qgsarcgisservicesourceselect.h" class QCheckBox; -class QgsAfsSourceSelect: public QgsSourceSelectDialog +class QgsAfsSourceSelect: public QgsArcGisServiceSourceSelect { Q_OBJECT diff --git a/src/providers/arcgisrest/qgsamsdataitems.cpp b/src/providers/arcgisrest/qgsamsdataitems.cpp index a078198373b..d465873362a 100644 --- a/src/providers/arcgisrest/qgsamsdataitems.cpp +++ b/src/providers/arcgisrest/qgsamsdataitems.cpp @@ -59,7 +59,7 @@ QList QgsAmsRootItem::actions() QWidget *QgsAmsRootItem::paramWidget() { QgsAmsSourceSelect *select = new QgsAmsSourceSelect( 0, 0, QgsProviderRegistry::WidgetMode::Manager ); - connect( select, &QgsSourceSelectDialog::connectionsChanged, this, &QgsAmsRootItem::connectionsChanged ); + connect( select, &QgsArcGisServiceSourceSelect::connectionsChanged, this, &QgsAmsRootItem::connectionsChanged ); return select; } diff --git a/src/providers/arcgisrest/qgsamssourceselect.cpp b/src/providers/arcgisrest/qgsamssourceselect.cpp index 948eecf1b8b..11972be05e6 100644 --- a/src/providers/arcgisrest/qgsamssourceselect.cpp +++ b/src/providers/arcgisrest/qgsamssourceselect.cpp @@ -26,7 +26,7 @@ QgsAmsSourceSelect::QgsAmsSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelectDialog( QStringLiteral( "ArcGisMapServer" ), QgsSourceSelectDialog::MapService, parent, fl ) + : QgsArcGisServiceSourceSelect( QStringLiteral( "ArcGisMapServer" ), QgsArcGisServiceSourceSelect::MapService, parent, fl ) { if ( widgetMode == QgsProviderRegistry::WidgetMode::Embedded || widgetMode == QgsProviderRegistry::WidgetMode::Manager ) { diff --git a/src/providers/arcgisrest/qgsamssourceselect.h b/src/providers/arcgisrest/qgsamssourceselect.h index 42bdcd9d556..39a329a197d 100644 --- a/src/providers/arcgisrest/qgsamssourceselect.h +++ b/src/providers/arcgisrest/qgsamssourceselect.h @@ -18,12 +18,12 @@ #ifndef QGSAMSSOURCESELECT_H #define QGSAMSSOURCESELECT_H -#include "qgssourceselectdialog.h" +#include "qgsarcgisservicesourceselect.h" #include "qgsproviderregistry.h" class QCheckBox; -class QgsAmsSourceSelect: public QgsSourceSelectDialog +class QgsAmsSourceSelect: public QgsArcGisServiceSourceSelect { Q_OBJECT diff --git a/src/ui/qgssourceselectdialogbase.ui b/src/ui/qgsarcgisservicesourceselectbase.ui similarity index 98% rename from src/ui/qgssourceselectdialogbase.ui rename to src/ui/qgsarcgisservicesourceselectbase.ui index 459179a5d9c..2d5526fd94e 100644 --- a/src/ui/qgssourceselectdialogbase.ui +++ b/src/ui/qgsarcgisservicesourceselectbase.ui @@ -1,7 +1,7 @@ - QgsSourceSelectBase - + QgsArcGisServiceSourceSelectBase + 0 From 1e6a4ab301e05a7bc39cf3d018e659aa4ed5172c Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Wed, 12 Jul 2017 17:46:13 +0200 Subject: [PATCH 052/266] Added since 3.0 to the renamed class --- python/gui/qgsarcgisservicesourceselect.sip | 1 + src/gui/qgsarcgisservicesourceselect.h | 1 + 2 files changed, 2 insertions(+) diff --git a/python/gui/qgsarcgisservicesourceselect.sip b/python/gui/qgsarcgisservicesourceselect.sip index 3c7283ef79e..cee9e616251 100644 --- a/python/gui/qgsarcgisservicesourceselect.sip +++ b/python/gui/qgsarcgisservicesourceselect.sip @@ -14,6 +14,7 @@ class QgsArcGisServiceSourceSelect : QDialog, protected Ui::QgsArcGisServiceSour { %Docstring Generic class listing layers available from a remote service. +.. versionadded:: 3.0 %End %TypeHeaderCode diff --git a/src/gui/qgsarcgisservicesourceselect.h b/src/gui/qgsarcgisservicesourceselect.h index 438838813f5..ad892fb82dd 100644 --- a/src/gui/qgsarcgisservicesourceselect.h +++ b/src/gui/qgsarcgisservicesourceselect.h @@ -29,6 +29,7 @@ class QgsOwsConnection; /** \ingroup gui * Generic class listing layers available from a remote service. + * \since QGIS 3.0 */ class GUI_EXPORT QgsArcGisServiceSourceSelect : public QDialog, protected Ui::QgsArcGisServiceSourceSelectBase { From dbd50b4bdd6fa13af1754de26f494d0bf8bdcab6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 19:17:16 +1000 Subject: [PATCH 053/266] On first load, default to 80% of current screen size for main window Using a fixed default window geometry wasn't working well anymore - it made the default window size tiny on hidpi screens. --- src/app/qgisapp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index b05e7124355..2a5b615d38f 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -3787,9 +3787,13 @@ void QgisApp::restoreWindowState() } // restore window geometry - if ( !restoreGeometry( settings.value( QStringLiteral( "UI/geometry" ), QByteArray::fromRawData( reinterpret_cast< const char * >( defaultUIgeometry ), sizeof defaultUIgeometry ) ).toByteArray() ) ) + if ( !restoreGeometry( settings.value( QStringLiteral( "UI/geometry" ) ).toByteArray() ) ) { QgsDebugMsg( "restore of UI geometry failed" ); + // default to 80% of screen size, at 10% from top left corner + resize( QDesktopWidget().availableGeometry( this ).size() * 0.8 ); + QSize pos = QDesktopWidget().availableGeometry( this ).size() * 0.1; + move( pos.width(), pos.height() ); } } From 71b9ce25c630a30d3b20b4d2099db1e64573b647 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 19:40:03 +1000 Subject: [PATCH 054/266] On first run, try to guess a good default icon size based on screen DPI Otherwise icons are miniscule when loading QGIS on hidpi screens, and users must know that they need to manually change the icon size to make it usable... --- src/app/qgisapp.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- src/app/qgisapp.h | 3 +++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 2a5b615d38f..e7504658f68 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -1033,8 +1034,20 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh } // Set icon size of toolbars - int size = settings.value( QStringLiteral( "IconSize" ), QGIS_ICON_SIZE ).toInt(); - setIconSizes( size ); + if ( settings.contains( QStringLiteral( "IconSize" ) ) ) + { + int size = settings.value( QStringLiteral( "IconSize" ) ).toInt(); + if ( size < 16 ) + size = QGIS_ICON_SIZE; + setIconSizes( size ); + } + else + { + // first run, guess a good icon size + int size = chooseReasonableDefaultIconSize(); + settings.setValue( QStringLiteral( "IconSize" ), size ); + setIconSizes( size ); + } mSplash->showMessage( tr( "Initializing file filters" ), Qt::AlignHCenter | Qt::AlignBottom ); qApp->processEvents(); @@ -1729,6 +1742,31 @@ QgsCoordinateReferenceSystem QgisApp::defaultCrsForNewLayers() const return defaultCrs; } +int QgisApp::chooseReasonableDefaultIconSize() const +{ + QScreen *screen = QApplication::screens().at( 0 ); + if ( screen->physicalDotsPerInch() < 115 ) + { + // no hidpi screen, use default size + return QGIS_ICON_SIZE; + } + else + { + double size = fontMetrics().width( QStringLiteral( "XXX" ) ); + if ( size < 24 ) + return 16; + else if ( size < 32 ) + return 24; + else if ( size < 48 ) + return 32; + else if ( size < 64 ) + return 48; + else + return 64; + } + +} + void QgisApp::readSettings() { QgsSettings settings; diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 08e316be521..c7642030477 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -1688,6 +1688,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsCoordinateReferenceSystem defaultCrsForNewLayers() const; + //! Attempts to choose a reasonable default icon size based on the window's screen DPI + int chooseReasonableDefaultIconSize() const; + QgisAppStyleSheet *mStyleSheetBuilder = nullptr; // actions for menus and toolbars ----------------- From 1a41624370fd0aff510f123c98c90448831c1c33 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Jul 2017 19:28:21 +1000 Subject: [PATCH 055/266] Add QgsProcessingFeatureBasedAlgorithm subclass An abstract QgsProcessingAlgorithm base class for processing algorithms which operate "feature-by-feature". Feature based algorithms are algorithms which operate on individual features in isolation. These are algorithms where one feature is output for each input feature, and the output feature result for each input feature is not dependent on any other features present in the source. For instance, algorithms like "centroids" and "buffers" are feature based algorithms since the centroid or buffer of a feature is calculated for each feature in isolation. An algorithm like "dissolve" is NOT suitable for a feature based algorithm as the dissolved output depends on multiple input features and these features cannot be processed in isolation. Using QgsProcessingFeatureBasedAlgorithm as the base class for feature based algorithms allows shortcutting much of the common algorithm code for handling iterating over sources and pushing features to output sinks. It also allows the algorithm execution to be optimised in future (for instance allowing automatic multi-thread processing of the algorithm, or use of the algorithm in "chains", avoiding the need for temporary outputs in multi-step models). --- .../processing/qgsprocessingalgorithm.sip | 103 ++++++++++++++++++ src/core/processing/qgsnativealgorithms.cpp | 47 +------- src/core/processing/qgsnativealgorithms.h | 10 +- .../processing/qgsprocessingalgorithm.cpp | 54 +++++++++ src/core/processing/qgsprocessingalgorithm.h | 94 ++++++++++++++++ 5 files changed, 263 insertions(+), 45 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index 55685f05abe..bb5d03d5d6d 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -30,6 +30,8 @@ class QgsProcessingAlgorithm %ConvertToSubClassCode if ( dynamic_cast< QgsProcessingModelAlgorithm * >( sipCpp ) != NULL ) sipType = sipType_QgsProcessingModelAlgorithm; + else if ( dynamic_cast< QgsProcessingFeatureBasedAlgorithm * >( sipCpp ) != NULL ) + sipType = sipType_QgsProcessingFeatureBasedAlgorithm; else sipType = sipType_QgsProcessingAlgorithm; %End @@ -717,6 +719,107 @@ QFlags operator|(QgsProcessingAlgorithm::Flag f1, +class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm +{ +%Docstring + An abstract QgsProcessingAlgorithm base class for processing algorithms which operate "feature-by-feature". + + Feature based algorithms are algorithms which operate on individual features in isolation. These + are algorithms where one feature is output for each input feature, and the output feature result + for each input feature is not dependent on any other features present in the source. + + For instance, algorithms like "centroids" and "buffers" are feature based algorithms since the centroid + or buffer of a feature is calculated for each feature in isolation. An algorithm like "dissolve" + is NOT suitable for a feature based algorithm as the dissolved output depends on multiple input features + and these features cannot be processed in isolation. + + Using QgsProcessingFeatureBasedAlgorithm as the base class for feature based algorithms allows + shortcutting much of the common algorithm code for handling iterating over sources and pushing + features to output sinks. It also allows the algorithm execution to be optimised in future + (for instance allowing automatic multi-thread processing of the algorithm, or use of the + algorithm in "chains", avoiding the need for temporary outputs in multi-step models). + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsprocessingalgorithm.h" +%End + public: + + QgsProcessingFeatureBasedAlgorithm(); +%Docstring + Constructor for QgsProcessingFeatureBasedAlgorithm. +%End + + protected: + + virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() ); + + + virtual QString outputName() const = 0; +%Docstring + Returns the translated, user visible name for any layers created by this algorithm. + This name will be used as the default name when loading the resultant layer into a + QGIS project. + :rtype: str +%End + + virtual QgsProcessing::LayerType outputLayerType() const; +%Docstring + Returns the layer type for layers generated by this algorithm, if + this is possible to determine in advance. + :rtype: QgsProcessing.LayerType +%End + + virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const; +%Docstring + Maps the input WKB geometry type (``inputWkbType``) to the corresponding + output WKB type generated by the algorithm. The default behavior is that the algorithm maintains + the same WKB type. + :rtype: QgsWkbTypes.Type +%End + + virtual QgsFields outputFields( const QgsFields &inputFields ) const; +%Docstring + Maps the input source fields (``inputFields``) to corresponding + output fields generated by the algorithm. The default behavior is that the algorithm maintains + the same fields as are input. + Algorithms which add, remove or modify existing fields should override this method and + implement logic here to indicate which fields are output by the algorithm. + :rtype: QgsFields +%End + + virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const; +%Docstring + Maps the input source coordinate reference system (``inputCrs``) to a corresponding + output CRS generated by the algorithm. The default behavior is that the algorithm maintains + the same CRS as the input source. + :rtype: QgsCoordinateReferenceSystem +%End + + virtual bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; +%Docstring + Processes an individual input ``feature`` from the source. Algorithms should implement their + logic in this method for performing the algorithm's operation (e.g. replacing the feature's + geometry with the centroid of the original feature geometry for a 'centroid' type + algorithm). + + Implementations should return true if the feature should be kept and added to the algorithm's + output sink, or false if the feature should be skipped and omitted from the output. + + The provided ``feedback`` object can be used to push messages to the log and for giving feedback + to users. Note that handling of progress reports and algorithm cancelation is handled by + the base class and subclasses do not need to reimplement this logic. + :rtype: bool +%End + + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + +}; + + /************************************************************************ * This file has been generated automatically from * diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 138073d95f3..f4a5f84a4fe 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -87,53 +87,18 @@ QgsCentroidAlgorithm *QgsCentroidAlgorithm::createInstance() const return new QgsCentroidAlgorithm(); } -QVariantMap QgsCentroidAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +bool QgsCentroidAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); - - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::Point, source->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); - - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); - - QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); - - double step = 100.0 / count; - int current = 0; - while ( it.nextFeature( f ) ) + if ( feature.hasGeometry() ) { - if ( feedback->isCanceled() ) + feature.setGeometry( feature.geometry().centroid() ); + if ( !feature.geometry() ) { - break; + feedback->pushInfo( QObject::tr( "Error calculating centroid for feature %1" ).arg( feature.id() ) ); } - - QgsFeature out = f; - if ( out.hasGeometry() ) - { - out.setGeometry( f.geometry().centroid() ); - if ( !out.geometry() ) - { - QgsMessageLog::logMessage( QObject::tr( "Error calculating centroid for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); - } - } - sink->addFeature( out, QgsFeatureSink::FastInsert ); - - feedback->setProgress( current * step ); - current++; } - - QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); - return outputs; + return true; } - // // QgsBufferAlgorithm // diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index 3be3e5933d1..bc68995356c 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -48,7 +48,7 @@ class QgsNativeAlgorithms: public QgsProcessingProvider /** * Native centroid algorithm. */ -class QgsCentroidAlgorithm : public QgsProcessingAlgorithm +class QgsCentroidAlgorithm : public QgsProcessingFeatureBasedAlgorithm { public: @@ -57,16 +57,18 @@ class QgsCentroidAlgorithm : public QgsProcessingAlgorithm void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; QString name() const override { return QStringLiteral( "centroids" ); } QString displayName() const override { return QObject::tr( "Centroids" ); } - virtual QStringList tags() const override { return QObject::tr( "centroid,center,average,point,middle" ).split( ',' ); } + QStringList tags() const override { return QObject::tr( "centroid,center,average,point,middle" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; QgsCentroidAlgorithm *createInstance() const override SIP_FACTORY; protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QString outputName() const override { return QObject::tr( "Centroids" ); } + QgsProcessing::LayerType outputLayerType() const override { return QgsProcessing::TypeVectorPoint; } + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override { Q_UNUSED( inputWkbType ); return QgsWkbTypes::Point; } + bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) override; }; /** diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index a9e87907589..936b344e5ce 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -608,3 +608,57 @@ bool QgsProcessingAlgorithm::createAutoOutputForParameter( QgsProcessingParamete } +// +// QgsProcessingFeatureBasedAlgorithm +// + +void QgsProcessingFeatureBasedAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), outputName(), outputLayerType() ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), outputName(), outputLayerType() ) ); +} + +QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); + + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, + outputFields( source->fields() ), + outputWkbType( source->wkbType() ), + outputCrs( source->sourceCrs() ) ) ); + if ( !sink ) + return QVariantMap(); + + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); + + QgsFeature f; + QgsFeatureIterator it = source->getFeatures(); + + double step = 100.0 / count; + int current = 0; + while ( it.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + { + break; + } + + if ( processFeature( f, feedback ) ) + { + sink->addFeature( f, QgsFeatureSink::FastInsert ); + } + + feedback->setProgress( current * step ); + current++; + } + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + return outputs; +} diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 54b865b6699..ce3d3696f92 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -53,6 +53,8 @@ class CORE_EXPORT QgsProcessingAlgorithm SIP_CONVERT_TO_SUBCLASS_CODE if ( dynamic_cast< QgsProcessingModelAlgorithm * >( sipCpp ) != NULL ) sipType = sipType_QgsProcessingModelAlgorithm; + else if ( dynamic_cast< QgsProcessingFeatureBasedAlgorithm * >( sipCpp ) != NULL ) + sipType = sipType_QgsProcessingFeatureBasedAlgorithm; else sipType = sipType_QgsProcessingAlgorithm; SIP_END @@ -697,6 +699,98 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( QgsProcessingAlgorithm::Flags ) +/** + * \class QgsProcessingFeatureBasedAlgorithm + * \ingroup core + * An abstract QgsProcessingAlgorithm base class for processing algorithms which operate "feature-by-feature". + * + * Feature based algorithms are algorithms which operate on individual features in isolation. These + * are algorithms where one feature is output for each input feature, and the output feature result + * for each input feature is not dependent on any other features present in the source. + * + * For instance, algorithms like "centroids" and "buffers" are feature based algorithms since the centroid + * or buffer of a feature is calculated for each feature in isolation. An algorithm like "dissolve" + * is NOT suitable for a feature based algorithm as the dissolved output depends on multiple input features + * and these features cannot be processed in isolation. + * + * Using QgsProcessingFeatureBasedAlgorithm as the base class for feature based algorithms allows + * shortcutting much of the common algorithm code for handling iterating over sources and pushing + * features to output sinks. It also allows the algorithm execution to be optimised in future + * (for instance allowing automatic multi-thread processing of the algorithm, or use of the + * algorithm in "chains", avoiding the need for temporary outputs in multi-step models). + * + * \since QGIS 3.0 + */ + +class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgorithm +{ + public: + + /** + * Constructor for QgsProcessingFeatureBasedAlgorithm. + */ + QgsProcessingFeatureBasedAlgorithm() = default; + + protected: + + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + + /** + * Returns the translated, user visible name for any layers created by this algorithm. + * This name will be used as the default name when loading the resultant layer into a + * QGIS project. + */ + virtual QString outputName() const = 0; + + /** + * Returns the layer type for layers generated by this algorithm, if + * this is possible to determine in advance. + */ + virtual QgsProcessing::LayerType outputLayerType() const { return QgsProcessing::TypeVectorAny; } + + /** + * Maps the input WKB geometry type (\a inputWkbType) to the corresponding + * output WKB type generated by the algorithm. The default behavior is that the algorithm maintains + * the same WKB type. + */ + virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const { return inputWkbType; } + + /** + * Maps the input source fields (\a inputFields) to corresponding + * output fields generated by the algorithm. The default behavior is that the algorithm maintains + * the same fields as are input. + * Algorithms which add, remove or modify existing fields should override this method and + * implement logic here to indicate which fields are output by the algorithm. + */ + virtual QgsFields outputFields( const QgsFields &inputFields ) const { return inputFields; } + + /** + * Maps the input source coordinate reference system (\a inputCrs) to a corresponding + * output CRS generated by the algorithm. The default behavior is that the algorithm maintains + * the same CRS as the input source. + */ + virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const { return inputCrs; } + + /** + * Processes an individual input \a feature from the source. Algorithms should implement their + * logic in this method for performing the algorithm's operation (e.g. replacing the feature's + * geometry with the centroid of the original feature geometry for a 'centroid' type + * algorithm). + * + * Implementations should return true if the feature should be kept and added to the algorithm's + * output sink, or false if the feature should be skipped and omitted from the output. + * + * The provided \a feedback object can be used to push messages to the log and for giving feedback + * to users. Note that handling of progress reports and algorithm cancelation is handled by + * the base class and subclasses do not need to reimplement this logic. + */ + virtual bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; + + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + +}; + #endif // QGSPROCESSINGALGORITHM_H From 7e3c435dd6e3c8e83a957550493d2ed3c632c6d4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 10:43:38 +1000 Subject: [PATCH 056/266] Port some existing algorithms to QgsProcessingFeatureBasedAlgorithm --- .../processing/qgsprocessingalgorithm.sip | 14 ++ .../processing/algs/qgis/AddTableField.py | 61 ++++----- .../algs/qgis/AutoincrementalField.py | 51 +++----- .../processing/algs/qgis/QgisAlgorithm.py | 24 +++- .../tests/testdata/qgis_algorithm_tests.yaml | 4 +- src/core/processing/qgsnativealgorithms.cpp | 122 ++++++------------ src/core/processing/qgsnativealgorithms.h | 32 +++-- .../processing/qgsprocessingalgorithm.cpp | 28 ++-- src/core/processing/qgsprocessingalgorithm.h | 17 +++ 9 files changed, 176 insertions(+), 177 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index bb5d03d5d6d..f1a4fe17175 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -798,6 +798,20 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm :rtype: QgsCoordinateReferenceSystem %End + virtual void initParameters( const QVariantMap &configuration = QVariantMap() ); +%Docstring + Initializes any extra parameters added by the algorithm subclass. There is no need + to declare the input source or output sink, as these are automatically created by + QgsProcessingFeatureBasedAlgorithm. +%End + + QgsCoordinateReferenceSystem sourceCrs() const; +%Docstring + Returns the source's coordinate reference system. This will only return a valid CRS when + called from a subclasses' processFeature() implementation. + :rtype: QgsCoordinateReferenceSystem +%End + virtual bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; %Docstring Processes an individual input ``feature`` from the source. Algorithms should implement their diff --git a/python/plugins/processing/algs/qgis/AddTableField.py b/python/plugins/processing/algs/qgis/AddTableField.py index 0454bc1d626..c93f4630bf9 100644 --- a/python/plugins/processing/algs/qgis/AddTableField.py +++ b/python/plugins/processing/algs/qgis/AddTableField.py @@ -27,19 +27,14 @@ __revision__ = '$Format:%H$' from qgis.PyQt.QtCore import QVariant from qgis.core import (QgsField, - QgsFeatureSink, - QgsProcessingParameterFeatureSource, QgsProcessingParameterString, QgsProcessingParameterNumber, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm + QgsProcessingParameterEnum) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class AddTableField(QgisAlgorithm): +class AddTableField(QgisFeatureBasedAlgorithm): - OUTPUT_LAYER = 'OUTPUT_LAYER' - INPUT_LAYER = 'INPUT_LAYER' FIELD_NAME = 'FIELD_NAME' FIELD_TYPE = 'FIELD_TYPE' FIELD_LENGTH = 'FIELD_LENGTH' @@ -55,10 +50,9 @@ class AddTableField(QgisAlgorithm): self.type_names = [self.tr('Integer'), self.tr('Float'), self.tr('String')] + self.field = None - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, - self.tr('Input layer'))) + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterString(self.FIELD_NAME, self.tr('Field name'))) self.addParameter(QgsProcessingParameterEnum(self.FIELD_TYPE, @@ -68,7 +62,6 @@ class AddTableField(QgisAlgorithm): 10, False, 1, 255)) self.addParameter(QgsProcessingParameterNumber(self.FIELD_PRECISION, self.tr('Field precision'), QgsProcessingParameterNumber.Integer, 0, False, 0, 10)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Added'))) def name(self): return 'addfieldtoattributestable' @@ -76,33 +69,25 @@ class AddTableField(QgisAlgorithm): def displayName(self): return self.tr('Add field to attributes table') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def outputName(self): + return self.tr('Added') - fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context) - fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context) - fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) - fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) + def prepareAlgorithm(self, parameters, context, feedback): + field_type = self.parameterAsEnum(parameters, self.FIELD_TYPE, context) + field_name = self.parameterAsString(parameters, self.FIELD_NAME, context) + field_length = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) + field_precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) - fields = source.fields() - fields.append(QgsField(fieldName, self.TYPES[fieldType], '', - fieldLength, fieldPrecision)) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - fields, source.wkbType(), source.sourceCrs()) + self.field = QgsField(field_name, self.TYPES[field_type], '', + field_length, field_precision) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def outputFields(self, inputFields): + inputFields.append(self.field) + return inputFields - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - - output_feature = input_feature - attributes = input_feature.attributes() - attributes.append(None) - output_feature.setAttributes(attributes) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT_LAYER: dest_id} + def processFeature(self, feature, feedback): + attributes = feature.attributes() + attributes.append(None) + feature.setAttributes(attributes) + return True diff --git a/python/plugins/processing/algs/qgis/AutoincrementalField.py b/python/plugins/processing/algs/qgis/AutoincrementalField.py index 11da5bce680..f9187f395ae 100644 --- a/python/plugins/processing/algs/qgis/AutoincrementalField.py +++ b/python/plugins/processing/algs/qgis/AutoincrementalField.py @@ -26,26 +26,15 @@ __copyright__ = '(C) 2012, Victor Olaya' __revision__ = '$Format:%H$' from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsField, - QgsFeatureSink, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from qgis.core import (QgsField) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class AutoincrementalField(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' +class AutoincrementalField(QgisFeatureBasedAlgorithm): def __init__(self): super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'))) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Incremented'))) + self.current = 0 def group(self): return self.tr('Vector table tools') @@ -56,26 +45,16 @@ class AutoincrementalField(QgisAlgorithm): def displayName(self): return self.tr('Add autoincremental field') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - fields = source.fields() - fields.append(QgsField('AUTO', QVariant.Int)) + def outputName(self): + return self.tr('Incremented') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, source.wkbType(), source.sourceCrs()) + def outputFields(self, inputFields): + inputFields.append(QgsField('AUTO', QVariant.Int)) + return inputFields - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - - output_feature = input_feature - attributes = input_feature.attributes() - attributes.append(current) - output_feature.setAttributes(attributes) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + attributes = feature.attributes() + attributes.append(self.current) + self.current += 1 + feature.setAttributes(attributes) + return True diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithm.py b/python/plugins/processing/algs/qgis/QgisAlgorithm.py index 1321e6b7d70..a04d6ec9db2 100644 --- a/python/plugins/processing/algs/qgis/QgisAlgorithm.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithm.py @@ -25,7 +25,7 @@ __copyright__ = '(C) 2017, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import QgsProcessingAlgorithm +from qgis.core import QgsProcessingAlgorithm, QgsProcessingFeatureBasedAlgorithm from qgis.PyQt.QtCore import QCoreApplication from processing.algs.help import shortHelp @@ -50,3 +50,25 @@ class QgisAlgorithm(QgsProcessingAlgorithm): def createInstance(self): return type(self)() + + +class QgisFeatureBasedAlgorithm(QgsProcessingFeatureBasedAlgorithm): + + def __init__(self): + super().__init__() + + def shortHelpString(self): + return shortHelp.get(self.id(), None) + + def tr(self, string, context=''): + if context == '': + context = self.__class__.__name__ + return QCoreApplication.translate(context, string) + + def trAlgorithm(self, string, context=''): + if context == '': + context = self.__class__.__name__ + return string, QCoreApplication.translate(context, string) + + def createInstance(self): + return type(self)() diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 9e2f973f460..33dafa17b23 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2556,11 +2556,11 @@ tests: FIELD_NAME: field FIELD_PRECISION: 2 FIELD_TYPE: '1' - INPUT_LAYER: + INPUT: name: custom/points.shp type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/add_field.gml type: vector diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index f4a5f84a4fe..9dd1a11ae0d 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -528,11 +528,9 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, Q } -void QgsTransformAlgorithm::initAlgorithm( const QVariantMap & ) +void QgsTransformAlgorithm::initParameters( const QVariantMap & ) { - addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); addParameter( new QgsProcessingParameterCrs( QStringLiteral( "TARGET_CRS" ), QObject::tr( "Target CRS" ), QStringLiteral( "EPSG:4326" ) ) ); - addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Reprojected" ) ) ); } QString QgsTransformAlgorithm::shortHelpString() const @@ -547,57 +545,40 @@ QgsTransformAlgorithm *QgsTransformAlgorithm::createInstance() const return new QgsTransformAlgorithm(); } -QVariantMap QgsTransformAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +bool QgsTransformAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mDestCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context ); + return true; +} - QgsCoordinateReferenceSystem targetCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context ); - - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), source->wkbType(), targetCrs ) ); - if ( !sink ) - return QVariantMap(); - - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); - - QgsFeature f; - QgsFeatureRequest req; - // perform reprojection in the iterators... - req.setDestinationCrs( targetCrs ); - - QgsFeatureIterator it = source->getFeatures( req ); - - double step = 100.0 / count; - int current = 0; - while ( it.nextFeature( f ) ) +bool QgsTransformAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFeedback * ) +{ + if ( !mCreatedTransform ) { - if ( feedback->isCanceled() ) - { - break; - } - - sink->addFeature( f, QgsFeatureSink::FastInsert ); - feedback->setProgress( current * step ); - current++; + mCreatedTransform = true; + mTransform = QgsCoordinateTransform( sourceCrs(), mDestCrs ); } - QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); - return outputs; + if ( feature.hasGeometry() ) + { + QgsGeometry g = feature.geometry(); + if ( g.transform( mTransform ) == 0 ) + { + feature.setGeometry( g ); + } + else + { + feature.clearGeometry(); + } + } + return true; } -void QgsSubdivideAlgorithm::initAlgorithm( const QVariantMap & ) +void QgsSubdivideAlgorithm::initParameters( const QVariantMap & ) { - addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MAX_NODES" ), QObject::tr( "Maximum nodes in parts" ), QgsProcessingParameterNumber::Integer, 256, false, 8, 100000 ) ); - - addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Subdivided" ) ) ); } QString QgsSubdivideAlgorithm::shortHelpString() const @@ -615,53 +596,28 @@ QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::createInstance() const return new QgsSubdivideAlgorithm(); } -QVariantMap QgsSubdivideAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +QgsWkbTypes::Type QgsSubdivideAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + return QgsWkbTypes::multiType( inputWkbType ); +} - int maxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context ); - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), - QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); - - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); - - QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); - - double step = 100.0 / count; - int current = 0; - while ( it.nextFeature( f ) ) +bool QgsSubdivideAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) +{ + if ( feature.hasGeometry() ) { - if ( feedback->isCanceled() ) + feature.setGeometry( feature.geometry().subdivide( mMaxNodes ) ); + if ( !feature.geometry() ) { - break; + feedback->reportError( QObject::tr( "Error calculating subdivision for feature %1" ).arg( feature.id() ) ); } - - QgsFeature out = f; - if ( out.hasGeometry() ) - { - out.setGeometry( f.geometry().subdivide( maxNodes ) ); - if ( !out.geometry() ) - { - QgsMessageLog::logMessage( QObject::tr( "Error calculating subdivision for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); - } - } - sink->addFeature( out, QgsFeatureSink::FastInsert ); - - feedback->setProgress( current * step ); - current++; } + return true; +} - QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); - return outputs; +bool QgsSubdivideAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +{ + mMaxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context ); + return true; } diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index bc68995356c..c2909b9fe08 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -74,13 +74,12 @@ class QgsCentroidAlgorithm : public QgsProcessingFeatureBasedAlgorithm /** * Native transform algorithm. */ -class QgsTransformAlgorithm : public QgsProcessingAlgorithm +class QgsTransformAlgorithm : public QgsProcessingFeatureBasedAlgorithm { public: QgsTransformAlgorithm() = default; - void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; QString name() const override { return QStringLiteral( "reprojectlayer" ); } QString displayName() const override { return QObject::tr( "Reproject layer" ); } virtual QStringList tags() const override { return QObject::tr( "transform,reproject,crs,srs,warp" ).split( ',' ); } @@ -90,8 +89,18 @@ class QgsTransformAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + void initParameters( const QVariantMap &configuration = QVariantMap() ) override; + QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem & ) const override { return mDestCrs; } + QString outputName() const override { return QObject::tr( "Reprojected" ); } + + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + + private: + + bool mCreatedTransform = false; + QgsCoordinateReferenceSystem mDestCrs; + QgsCoordinateTransform mTransform; }; @@ -235,13 +244,13 @@ class QgsClipAlgorithm : public QgsProcessingAlgorithm /** * Native subdivide algorithm. */ -class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm +class QgsSubdivideAlgorithm : public QgsProcessingFeatureBasedAlgorithm { public: QgsSubdivideAlgorithm() = default; - void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + void initParameters( const QVariantMap &configuration = QVariantMap() ) override; QString name() const override { return QStringLiteral( "subdivide" ); } QString displayName() const override { return QObject::tr( "Subdivide" ); } virtual QStringList tags() const override { return QObject::tr( "subdivide,segmentize,split,tesselate" ).split( ',' ); } @@ -250,9 +259,16 @@ class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm QgsSubdivideAlgorithm *createInstance() const override SIP_FACTORY; protected: + QString outputName() const override { return QObject::tr( "Subdivided" ); } - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override; + bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + int mMaxNodes = -1; }; diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index 936b344e5ce..b6195c55056 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -612,33 +612,41 @@ bool QgsProcessingAlgorithm::createAutoOutputForParameter( QgsProcessingParamete // QgsProcessingFeatureBasedAlgorithm // -void QgsProcessingFeatureBasedAlgorithm::initAlgorithm( const QVariantMap & ) +void QgsProcessingFeatureBasedAlgorithm::initAlgorithm( const QVariantMap &config ) { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + initParameters( config ); addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), outputName(), outputLayerType() ) ); - addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), outputName(), outputLayerType() ) ); +} + +QgsCoordinateReferenceSystem QgsProcessingFeatureBasedAlgorithm::sourceCrs() const +{ + if ( mSource ) + return mSource->sourceCrs(); + else + return QgsCoordinateReferenceSystem(); } QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) return QVariantMap(); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, - outputFields( source->fields() ), - outputWkbType( source->wkbType() ), - outputCrs( source->sourceCrs() ) ) ); + outputFields( mSource->fields() ), + outputWkbType( mSource->wkbType() ), + outputCrs( mSource->sourceCrs() ) ) ); if ( !sink ) return QVariantMap(); - long count = source->featureCount(); + long count = mSource->featureCount(); if ( count <= 0 ) return QVariantMap(); QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); double step = 100.0 / count; int current = 0; @@ -658,6 +666,8 @@ QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariant current++; } + mSource.reset(); + QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index ce3d3696f92..5eb858437cd 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -771,6 +771,19 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor */ virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const { return inputCrs; } + /** + * Initializes any extra parameters added by the algorithm subclass. There is no need + * to declare the input source or output sink, as these are automatically created by + * QgsProcessingFeatureBasedAlgorithm. + */ + virtual void initParameters( const QVariantMap &configuration = QVariantMap() ) { Q_UNUSED( configuration ); } + + /** + * Returns the source's coordinate reference system. This will only return a valid CRS when + * called from a subclasses' processFeature() implementation. + */ + QgsCoordinateReferenceSystem sourceCrs() const; + /** * Processes an individual input \a feature from the source. Algorithms should implement their * logic in this method for performing the algorithm's operation (e.g. replacing the feature's @@ -789,6 +802,10 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + private: + + std::unique_ptr< QgsFeatureSource > mSource; + }; #endif // QGSPROCESSINGALGORITHM_H From b9f225905a8330064abc4d4bf29e99d5e5b2b890 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 10:49:57 +1000 Subject: [PATCH 057/266] Port a single python algorithm to QgsProcessingFeatureBasedAlgorithm --- .../processing/algs/qgis/BoundingBox.py | 52 ++++++------------- .../tests/testdata/qgis_algorithm_tests.yaml | 24 ++++----- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/python/plugins/processing/algs/qgis/BoundingBox.py b/python/plugins/processing/algs/qgis/BoundingBox.py index db848d9125e..c5b08f597fa 100644 --- a/python/plugins/processing/algs/qgis/BoundingBox.py +++ b/python/plugins/processing/algs/qgis/BoundingBox.py @@ -29,24 +29,17 @@ import os from qgis.core import (QgsGeometry, QgsWkbTypes, - QgsFeatureSink, - QgsProcessing, - QgsProcessingException, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink) + QgsProcessingException) from qgis.PyQt.QtGui import QIcon -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class BoundingBox(QgisAlgorithm): - - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' +class BoundingBox(QgisFeatureBasedAlgorithm): def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'matrix.png')) @@ -57,39 +50,26 @@ class BoundingBox(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Bounds'), QgsProcessing.TypeVectorPolygon)) - def name(self): return 'boundingboxes' def displayName(self): return self.tr('Bounding boxes') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def outputName(self): + return self.tr('Bounds') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - source.fields(), QgsWkbTypes.Polygon, source.sourceCrs()) + def outputWkbType(self, inputWkb): + return QgsWkbTypes.Polygon - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = QgsGeometry.fromRect(input_geometry.boundingBox()) + if not output_geometry: + raise QgsProcessingException( + self.tr('Error calculating bounding box')) - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = QgsGeometry.fromRect(input_geometry.boundingBox()) - if not output_geometry: - raise QgsProcessingException( - self.tr('Error calculating bounding box')) + feature.setGeometry(output_geometry) - output_feature.setGeometry(output_geometry) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT_LAYER: dest_id} + return True diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 33dafa17b23..be5be9d10b1 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -415,66 +415,66 @@ tests: - algorithm: qgis:boundingboxes name: Bounding boxes for lines params: - INPUT_LAYER: + INPUT: name: lines.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/lines_bounds.gml type: vector - algorithm: qgis:boundingboxes name: Bounding boxes for multilines params: - INPUT_LAYER: + INPUT: name: multilines.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/multiline_bounds.gml type: vector - algorithm: qgis:boundingboxes name: Bounding boxes for multipolygons params: - INPUT_LAYER: + INPUT: name: multipolys.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/multipoly_bounds.gml type: vector - algorithm: qgis:boundingboxes name: Bounding boxes for points params: - INPUT_LAYER: + INPUT: name: points.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/point_bounds.gml type: vector - algorithm: qgis:boundingboxes name: Bounding boxes for polygons params: - INPUT_LAYER: + INPUT: name: polys.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/poly_bounds.gml type: vector - algorithm: qgis:boundingboxes name: Bounding boxes for multipoints params: - INPUT_LAYER: + INPUT: name: multipoints.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/multipoint_bounds.gml type: vector From 340cf93f93fb28e1aa9e23b2d80a7c38e6a89d6c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 11:31:34 +1000 Subject: [PATCH 058/266] [FEATURE] New algorithms to add Z/M values to existing geometries Allows upgrading geometries to include these dimensions, or overwriting any existing Z/M values with a new value. Intended mostly as a test run for QgsProcessingFeatureBasedAlgorithm --- python/plugins/processing/algs/help/qgis.yaml | 9 ++ .../algs/qgis/QGISAlgorithmProvider.py | 4 + .../plugins/processing/algs/qgis/SetMValue.py | 86 ++++++++++++++++++ .../plugins/processing/algs/qgis/SetZValue.py | 86 ++++++++++++++++++ .../tests/testdata/expected/set_m_value.dbf | Bin 0 -> 2604 bytes .../tests/testdata/expected/set_m_value.prj | 1 + .../tests/testdata/expected/set_m_value.qpj | 1 + .../tests/testdata/expected/set_m_value.shp | Bin 0 -> 424 bytes .../tests/testdata/expected/set_m_value.shx | Bin 0 -> 172 bytes .../tests/testdata/expected/set_z_value.dbf | Bin 0 -> 2604 bytes .../tests/testdata/expected/set_z_value.prj | 1 + .../tests/testdata/expected/set_z_value.qpj | 1 + .../tests/testdata/expected/set_z_value.shp | Bin 0 -> 424 bytes .../tests/testdata/expected/set_z_value.shx | Bin 0 -> 172 bytes .../tests/testdata/qgis_algorithm_tests.yaml | 24 +++++ 15 files changed, 213 insertions(+) create mode 100644 python/plugins/processing/algs/qgis/SetMValue.py create mode 100644 python/plugins/processing/algs/qgis/SetZValue.py create mode 100644 python/plugins/processing/tests/testdata/expected/set_m_value.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/set_m_value.prj create mode 100644 python/plugins/processing/tests/testdata/expected/set_m_value.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/set_m_value.shp create mode 100644 python/plugins/processing/tests/testdata/expected/set_m_value.shx create mode 100644 python/plugins/processing/tests/testdata/expected/set_z_value.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/set_z_value.prj create mode 100644 python/plugins/processing/tests/testdata/expected/set_z_value.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/set_z_value.shp create mode 100644 python/plugins/processing/tests/testdata/expected/set_z_value.shx diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index 6bf5b08eab1..e04c83843e5 100755 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -503,6 +503,10 @@ qgis:selectbyexpression: > qgis:selectbylocation: > This algorithm creates a selection in a vector layer. The criteria for selecting features is based on the spatial relationship between each feature and the features in an additional layer. +qgis:setmvalue: > + This algorithm sets the M value for geometries in a layer. + + If M values already exist in the layer, they will be overwritten with the new value. If no M values exist, the geometry will be upgraded to include M values and the specified value used as the initial M value for all geometries. qgis:setstyleforrasterlayer: > This algorithm sets the style of a raster layer. The style must be defined in a QML file. @@ -510,6 +514,11 @@ qgis:setstyleforrasterlayer: > qgis:setstyleforvectorlayer: > This algorithm sets the style of a vector layer. The style must be defined in a QML file. +qgis:setzvalue: > + This algorithm sets the Z value for geometries in a layer. + + If Z values already exist in the layer, they will be overwritten with the new value. If no Z values exist, the geometry will be upgraded to include Z values and the specified value used as the initial Z value for all geometries. + qgis:simplifygeometries: > This algorithm simplifies the geometries in a line or polygon layer. It creates a new layer with the same features as the ones in the input layer, but with geometries containing a lower number of vertices. diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index fde3c1757c7..200c322e046 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -89,6 +89,8 @@ from .SelectByAttribute import SelectByAttribute from .SelectByExpression import SelectByExpression from .ServiceAreaFromLayer import ServiceAreaFromLayer from .ServiceAreaFromPoint import ServiceAreaFromPoint +from .SetMValue import SetMValue +from .SetZValue import SetZValue from .ShortestPathLayerToPoint import ShortestPathLayerToPoint from .ShortestPathPointToLayer import ShortestPathPointToLayer from .ShortestPathPointToPoint import ShortestPathPointToPoint @@ -276,6 +278,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider): SelectByExpression(), ServiceAreaFromLayer(), ServiceAreaFromPoint(), + SetMValue(), + SetZValue(), ShortestPathLayerToPoint(), ShortestPathPointToLayer(), ShortestPathPointToPoint(), diff --git a/python/plugins/processing/algs/qgis/SetMValue.py b/python/plugins/processing/algs/qgis/SetMValue.py new file mode 100644 index 00000000000..f22c02f14b8 --- /dev/null +++ b/python/plugins/processing/algs/qgis/SetMValue.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + SetMValue.py + -------------- + Date : July 2017 + Copyright : (C) 2017 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. * +* * +*************************************************************************** +""" + +__author__ = 'Nyall Dawson' +__date__ = 'July 2017' +__copyright__ = '(C) 2017, Nyall Dawson' + +# This will get replaced with a git SHA1 when you do a git archive323 + +__revision__ = '$Format:%H$' + +import os + +from qgis.core import (QgsGeometry, + QgsWkbTypes, + QgsProcessingParameterNumber) + + +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm + +pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] + + +class SetMValue(QgisFeatureBasedAlgorithm): + + M_VALUE = 'M_VALUE' + + def group(self): + return self.tr('Vector geometry tools') + + def __init__(self): + super().__init__() + self.m_value = 0 + + def name(self): + return 'setmvalue' + + def displayName(self): + return self.tr('Set M Value') + + def outputName(self): + return self.tr('M Added') + + def tags(self): + return self.tr('set,add,m,measure,values').split(',') + + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterNumber(self.M_VALUE, + self.tr('M Value'), QgsProcessingParameterNumber.Double, defaultValue=0.0)) + + def outputWkbType(self, inputWkb): + return QgsWkbTypes.addM(inputWkb) + + def prepareAlgorithm(self, parameters, context, feedback): + self.m_value = self.parameterAsDouble(parameters, self.M_VALUE, context) + return True + + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + new_geom = input_geometry.geometry().clone() + if QgsWkbTypes.hasM(new_geom.wkbType()): + # addMValue won't alter existing M values, so drop them first + new_geom.dropMValue() + + new_geom.addMValue(self.m_value) + + feature.setGeometry(QgsGeometry(new_geom)) + + return True diff --git a/python/plugins/processing/algs/qgis/SetZValue.py b/python/plugins/processing/algs/qgis/SetZValue.py new file mode 100644 index 00000000000..1611be177c3 --- /dev/null +++ b/python/plugins/processing/algs/qgis/SetZValue.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + SetZValue.py + -------------- + Date : July 2017 + Copyright : (C) 2017 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. * +* * +*************************************************************************** +""" + +__author__ = 'Nyall Dawson' +__date__ = 'July 2017' +__copyright__ = '(C) 2017, Nyall Dawson' + +# This will get replaced with a git SHA1 when you do a git archive323 + +__revision__ = '$Format:%H$' + +import os + +from qgis.core import (QgsGeometry, + QgsWkbTypes, + QgsProcessingParameterNumber) + + +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm + +pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] + + +class SetZValue(QgisFeatureBasedAlgorithm): + + Z_VALUE = 'Z_VALUE' + + def group(self): + return self.tr('Vector geometry tools') + + def __init__(self): + super().__init__() + self.z_value = 0 + + def name(self): + return 'setzvalue' + + def displayName(self): + return self.tr('Set Z Value') + + def outputName(self): + return self.tr('Z Added') + + def tags(self): + return self.tr('set,add,z,25d,3d,values').split(',') + + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterNumber(self.Z_VALUE, + self.tr('Z Value'), QgsProcessingParameterNumber.Double, defaultValue=0.0)) + + def outputWkbType(self, inputWkb): + return QgsWkbTypes.addZ(inputWkb) + + def prepareAlgorithm(self, parameters, context, feedback): + self.z_value = self.parameterAsDouble(parameters, self.Z_VALUE, context) + return True + + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + new_geom = input_geometry.geometry().clone() + if QgsWkbTypes.hasZ(new_geom.wkbType()): + # addZValue won't alter existing Z values, so drop them first + new_geom.dropZValue() + + new_geom.addZValue(self.z_value) + + feature.setGeometry(QgsGeometry(new_geom)) + + return True diff --git a/python/plugins/processing/tests/testdata/expected/set_m_value.dbf b/python/plugins/processing/tests/testdata/expected/set_m_value.dbf new file mode 100644 index 0000000000000000000000000000000000000000..cd3f0bb7691cb9cb8a8daaa04d276ca495dce4c6 GIT binary patch literal 2604 zcmd^;F$%&!5Jg8Uf`UcJ1#$w}teR+T?>p~8-~9F7n!RA_0-;RxYEg^sqYL}ktNgm9t4fwt)b Dg&0od literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/set_m_value.prj b/python/plugins/processing/tests/testdata/expected/set_m_value.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/set_m_value.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/set_m_value.qpj b/python/plugins/processing/tests/testdata/expected/set_m_value.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/set_m_value.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/set_m_value.shp b/python/plugins/processing/tests/testdata/expected/set_m_value.shp new file mode 100644 index 0000000000000000000000000000000000000000..b3540dfa002aa6fb2438c6d59a5d5ab8147dbf31 GIT binary patch literal 424 zcmZvYI}(5(3`FDqQ#cmbS=f0Dy}2I3Ls)VIGaE9H5ejy)JeCBaeWd()-im1Xow-}A z?G){UN)`7t_|Ss=DQ5XYI8`vEWi(e640n{~&?z~pU~0>_9Mf244vguiwG3BSIzQg( Y*h=QoKk=8aV0z2IW6LmuW#o3se4Vcp`~Uy| literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/set_m_value.shx b/python/plugins/processing/tests/testdata/expected/set_m_value.shx new file mode 100644 index 0000000000000000000000000000000000000000..6a28b1d3ddd447b2e8137eccad9e42bb2482ca40 GIT binary patch literal 172 zcmZQzQ0HR64#HkAGcbr^$cY?)2q`!~Xbvb1;UP#Fs5qE50*dniu?v(AgVGsLx(Z77 OK>p~8-~9F7n!RA_0-;RxYEg^sqYL}ktNgm9t4fwt)b Dg&0od literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/set_z_value.prj b/python/plugins/processing/tests/testdata/expected/set_z_value.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/set_z_value.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/set_z_value.qpj b/python/plugins/processing/tests/testdata/expected/set_z_value.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/set_z_value.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/set_z_value.shp b/python/plugins/processing/tests/testdata/expected/set_z_value.shp new file mode 100644 index 0000000000000000000000000000000000000000..e0f2a15aa22dbd03a9fbfa53986432019712d995 GIT binary patch literal 424 zcmZQzQ0HR64z9dlW?T=&0*!{qLP;i+GoW-8l Date: Mon, 17 Jul 2017 13:16:03 +1000 Subject: [PATCH 059/266] [FEATURE] Followup addition of set Z/M values algs with Drop Z/M Values algorithm Allows easy access to drop any z or m values present in a layer (e.g. if required for compatibility with a database destination, etc) --- python/plugins/processing/algs/help/qgis.yaml | 3 + .../processing/algs/qgis/DropMZValues.py | 94 ++++++++++++++++++ .../algs/qgis/QGISAlgorithmProvider.py | 2 + .../tests/testdata/custom/pointszm.dbf | Bin 0 -> 2604 bytes .../tests/testdata/custom/pointszm.prj | 1 + .../tests/testdata/custom/pointszm.qpj | 1 + .../tests/testdata/custom/pointszm.shp | Bin 0 -> 496 bytes .../tests/testdata/custom/pointszm.shx | Bin 0 -> 172 bytes .../tests/testdata/expected/m_dropped.dbf | Bin 0 -> 2604 bytes .../tests/testdata/expected/m_dropped.prj | 1 + .../tests/testdata/expected/m_dropped.qpj | 1 + .../tests/testdata/expected/m_dropped.shp | Bin 0 -> 424 bytes .../tests/testdata/expected/m_dropped.shx | Bin 0 -> 172 bytes .../tests/testdata/expected/set_m_value.gml | 77 ++++++++++++++ .../tests/testdata/expected/z_dropped.dbf | Bin 0 -> 2604 bytes .../tests/testdata/expected/z_dropped.prj | 1 + .../tests/testdata/expected/z_dropped.qpj | 1 + .../tests/testdata/expected/z_dropped.shp | Bin 0 -> 424 bytes .../tests/testdata/expected/z_dropped.shx | Bin 0 -> 172 bytes .../tests/testdata/expected/zm_dropped.dbf | Bin 0 -> 2604 bytes .../tests/testdata/expected/zm_dropped.prj | 1 + .../tests/testdata/expected/zm_dropped.qpj | 1 + .../tests/testdata/expected/zm_dropped.shp | Bin 0 -> 352 bytes .../tests/testdata/expected/zm_dropped.shx | Bin 0 -> 172 bytes .../tests/testdata/qgis_algorithm_tests.yaml | 39 ++++++++ 25 files changed, 223 insertions(+) create mode 100644 python/plugins/processing/algs/qgis/DropMZValues.py create mode 100644 python/plugins/processing/tests/testdata/custom/pointszm.dbf create mode 100644 python/plugins/processing/tests/testdata/custom/pointszm.prj create mode 100644 python/plugins/processing/tests/testdata/custom/pointszm.qpj create mode 100644 python/plugins/processing/tests/testdata/custom/pointszm.shp create mode 100644 python/plugins/processing/tests/testdata/custom/pointszm.shx create mode 100644 python/plugins/processing/tests/testdata/expected/m_dropped.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/m_dropped.prj create mode 100644 python/plugins/processing/tests/testdata/expected/m_dropped.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/m_dropped.shp create mode 100644 python/plugins/processing/tests/testdata/expected/m_dropped.shx create mode 100644 python/plugins/processing/tests/testdata/expected/set_m_value.gml create mode 100644 python/plugins/processing/tests/testdata/expected/z_dropped.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/z_dropped.prj create mode 100644 python/plugins/processing/tests/testdata/expected/z_dropped.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/z_dropped.shp create mode 100644 python/plugins/processing/tests/testdata/expected/z_dropped.shx create mode 100644 python/plugins/processing/tests/testdata/expected/zm_dropped.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/zm_dropped.prj create mode 100644 python/plugins/processing/tests/testdata/expected/zm_dropped.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/zm_dropped.shp create mode 100644 python/plugins/processing/tests/testdata/expected/zm_dropped.shx diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index e04c83843e5..ccb7cec6a98 100755 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -160,6 +160,9 @@ qgis:distancetonearesthub: > qgis:dropgeometries: > This algorithm removes any geometries from an input layer and returns a layer containing only the feature attributes. +qgis:dropmzvalues: > + This algorithm can remove any measure (M) or Z values from input geometries. + qgis:eliminateselectedpolygons: > This algorithm combines selected polygons of the input layer with certain adjacent polygons by erasing their common boundary. The adjacent polygon can be either the one with the largest or smallest area or the one sharing the largest common boundary with the polygon to be eliminated. The selected features will always be eliminated whether the option "Use only selected features" is set or not. Eliminate is normally used to get rid of sliver polygons, i.e. tiny polygons that are a result of polygon intersection processes where boundaries of the inputs are similar but not identical. diff --git a/python/plugins/processing/algs/qgis/DropMZValues.py b/python/plugins/processing/algs/qgis/DropMZValues.py new file mode 100644 index 00000000000..521d695014f --- /dev/null +++ b/python/plugins/processing/algs/qgis/DropMZValues.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + DropMZValues.py + -------------- + Date : July 2017 + Copyright : (C) 2017 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. * +* * +*************************************************************************** +""" + +__author__ = 'Nyall Dawson' +__date__ = 'July 2017' +__copyright__ = '(C) 2017, Nyall Dawson' + +# This will get replaced with a git SHA1 when you do a git archive323 + +__revision__ = '$Format:%H$' + +import os + +from qgis.core import (QgsGeometry, + QgsWkbTypes, + QgsProcessingParameterBoolean) + + +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm + +pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] + + +class DropMZValues(QgisFeatureBasedAlgorithm): + + DROP_M_VALUES = 'DROP_M_VALUES' + DROP_Z_VALUES = 'DROP_Z_VALUES' + + def group(self): + return self.tr('Vector geometry tools') + + def __init__(self): + super().__init__() + self.drop_m = False + self.drop_z = False + + def name(self): + return 'dropmzvalues' + + def displayName(self): + return self.tr('Drop M/Z Values') + + def outputName(self): + return self.tr('Z/M Dropped') + + def tags(self): + return self.tr('drop,set,convert,m,measure,z,25d,3d,values').split(',') + + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterBoolean(self.DROP_M_VALUES, + self.tr('Drop M Values'), defaultValue=False)) + self.addParameter(QgsProcessingParameterBoolean(self.DROP_Z_VALUES, + self.tr('Drop Z Values'), defaultValue=False)) + + def outputWkbType(self, inputWkb): + wkb = inputWkb + if self.drop_m: + wkb = QgsWkbTypes.dropM(wkb) + if self.drop_z: + wkb = QgsWkbTypes.dropZ(wkb) + return wkb + + def prepareAlgorithm(self, parameters, context, feedback): + self.drop_m = self.parameterAsBool(parameters, self.DROP_M_VALUES, context) + self.drop_z = self.parameterAsBool(parameters, self.DROP_Z_VALUES, context) + return True + + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + new_geom = input_geometry.geometry().clone() + if self.drop_m: + new_geom.dropMValue() + if self.drop_z: + new_geom.dropZValue() + feature.setGeometry(QgsGeometry(new_geom)) + + return True diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 200c322e046..06b84cdb565 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -56,6 +56,7 @@ from .DensifyGeometries import DensifyGeometries from .DensifyGeometriesInterval import DensifyGeometriesInterval from .Difference import Difference from .DropGeometry import DropGeometry +from .DropMZValues import DropMZValues from .ExtentFromLayer import ExtentFromLayer from .ExtractNodes import ExtractNodes from .FixGeometry import FixGeometry @@ -245,6 +246,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): DensifyGeometriesInterval(), Difference(), DropGeometry(), + DropMZValues(), ExtentFromLayer(), ExtractNodes(), FixGeometry(), diff --git a/python/plugins/processing/tests/testdata/custom/pointszm.dbf b/python/plugins/processing/tests/testdata/custom/pointszm.dbf new file mode 100644 index 0000000000000000000000000000000000000000..cd3f0bb7691cb9cb8a8daaa04d276ca495dce4c6 GIT binary patch literal 2604 zcmd^;F$%&!5Jg8Uf`UcJ1#$w}teR+T?>p~8-~9F7n!RA_0-;RxYEg^sqYL}ktNgm9t4fwt)b Dg&0od literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/custom/pointszm.prj b/python/plugins/processing/tests/testdata/custom/pointszm.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/custom/pointszm.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/custom/pointszm.qpj b/python/plugins/processing/tests/testdata/custom/pointszm.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/custom/pointszm.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/custom/pointszm.shp b/python/plugins/processing/tests/testdata/custom/pointszm.shp new file mode 100644 index 0000000000000000000000000000000000000000..0231b3a5a81b8c684b4b94bec8e2f7ae7dd0164e GIT binary patch literal 496 zcmaKoI}(5(3`E!euW=ko3p$S>H`n7>T3Q;38HqBcUIQF9>rV&p_!%AQ}Lb9SObw literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/m_dropped.dbf b/python/plugins/processing/tests/testdata/expected/m_dropped.dbf new file mode 100644 index 0000000000000000000000000000000000000000..cd3f0bb7691cb9cb8a8daaa04d276ca495dce4c6 GIT binary patch literal 2604 zcmd^;F$%&!5Jg8Uf`UcJ1#$w}teR+T?>p~8-~9F7n!RA_0-;RxYEg^sqYL}ktNgm9t4fwt)b Dg&0od literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/m_dropped.prj b/python/plugins/processing/tests/testdata/expected/m_dropped.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/m_dropped.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/m_dropped.qpj b/python/plugins/processing/tests/testdata/expected/m_dropped.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/m_dropped.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/m_dropped.shp b/python/plugins/processing/tests/testdata/expected/m_dropped.shp new file mode 100644 index 0000000000000000000000000000000000000000..82f7499cc3626ec305c43afd91ebd87bbbb146cf GIT binary patch literal 424 zcmZvXJq~~%3`XleYGNE19oRgE+*}XgAsjh^O-ln+7=546_SG!;i+GoW-8l + + + + 0-5 + 83 + + + + + + 1,1 + 1 + 2 + + + + + 3,3 + 2 + 1 + + + + + 2,2 + 3 + 0 + + + + + 5,2 + 4 + 2 + + + + + 4,1 + 5 + 1 + + + + + 0,-5 + 6 + 0 + + + + + 8,-1 + 7 + 0 + + + + + 7,-1 + 8 + 0 + + + + + 0,-1 + 9 + 0 + + + diff --git a/python/plugins/processing/tests/testdata/expected/z_dropped.dbf b/python/plugins/processing/tests/testdata/expected/z_dropped.dbf new file mode 100644 index 0000000000000000000000000000000000000000..cd3f0bb7691cb9cb8a8daaa04d276ca495dce4c6 GIT binary patch literal 2604 zcmd^;F$%&!5Jg8Uf`UcJ1#$w}teR+T?>p~8-~9F7n!RA_0-;RxYEg^sqYL}ktNgm9t4fwt)b Dg&0od literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/z_dropped.prj b/python/plugins/processing/tests/testdata/expected/z_dropped.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/z_dropped.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/z_dropped.qpj b/python/plugins/processing/tests/testdata/expected/z_dropped.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/z_dropped.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/z_dropped.shp b/python/plugins/processing/tests/testdata/expected/z_dropped.shp new file mode 100644 index 0000000000000000000000000000000000000000..bb569a09d480742a070423ee80d8020f19faaebe GIT binary patch literal 424 zcmZvYy%B&Q429!AT6h-NS=c#-Y%W7Mge4=GONfw@6g-lAc^3dUCMd`KGyv a2E1qQl0OMo#u&|Pj(dt3HNyn%^ZC3%Clq)9 literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/z_dropped.shx b/python/plugins/processing/tests/testdata/expected/z_dropped.shx new file mode 100644 index 0000000000000000000000000000000000000000..315dc3eb858c55c6e952a5df5dc8a7b735a9078b GIT binary patch literal 172 zcmZQzQ0HR64#HkAGcbr^$cY?)2q`!~Xbvb1;UP!?s5qE50*dniu?v(AgVGsLx(Z77 OK>p~8-~9F7n!RA_0-;RxYEg^sqYL}ktNgm9t4fwt)b Dg&0od literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/zm_dropped.prj b/python/plugins/processing/tests/testdata/expected/zm_dropped.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/zm_dropped.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/zm_dropped.qpj b/python/plugins/processing/tests/testdata/expected/zm_dropped.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/zm_dropped.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/zm_dropped.shp b/python/plugins/processing/tests/testdata/expected/zm_dropped.shp new file mode 100644 index 0000000000000000000000000000000000000000..a0a4405be68d7fe9671c75de17323b842ea19994 GIT binary patch literal 352 zcmZQzQ0HR64mP}CW?*2%kP|ro5mIn~&>T=2!oxv=jNw9<^}!yZ4oowl%E8P7(?Ay> z>tk?$=mXO%sB$96a;&Iw0#G@yeQc<5Kpq%?{lyMsBDoXh#}E5~;vA@QG7dmK5P;=4 Mfea*lU$cY?)2q`!~Xbvb1;o%^SfZ`x~9H6uxl#YSYSx~wHO1DAj KDNuS5hz0=uJ_iH< literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 5cc0f3bac8d..09e562f193c 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -546,6 +546,45 @@ tests: name: expected/set_z_value.shp type: vector + - algorithm: qgis:dropmzvalues + name: Drop M Value + params: + INPUT: + name: custom/pointszm.shp + type: vector + DROP_Z_VALUES: False + DROP_M_VALUES: True + results: + OUTPUT: + name: expected/m_dropped.shp + type: vector + + - algorithm: qgis:dropmzvalues + name: Drop Z Value + params: + INPUT: + name: custom/pointszm.shp + type: vector + DROP_Z_VALUES: True + DROP_M_VALUES: False + results: + OUTPUT: + name: expected/z_dropped.shp + type: vector + + - algorithm: qgis:dropmzvalues + name: Drop ZM Value + params: + INPUT: + name: custom/pointszm.shp + type: vector + DROP_Z_VALUES: True + DROP_M_VALUES: True + results: + OUTPUT: + name: expected/zm_dropped.shp + type: vector + - algorithm: qgis:pointonsurface name: Point on polygon surface params: From 792e6bd563b73db9857a9028b510996d3100e1aa Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 19:21:00 +1000 Subject: [PATCH 060/266] Always output algorithm results, even when input source has no features Allows more versatile models which can handle empty layers --- src/core/processing/qgsnativealgorithms.cpp | 31 ++++++------------- .../processing/qgsprocessingalgorithm.cpp | 4 +-- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 9dd1a11ae0d..4f455f97db6 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -153,13 +153,11 @@ QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, const QgsProcessingParameterDefinition *distanceParamDef = parameterDefinition( QStringLiteral( "DISTANCE" ) ); long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); QgsFeature f; QgsFeatureIterator it = source->getFeatures(); - double step = 100.0 / count; + double step = count > 0 ? 100.0 / count : 1; int current = 0; QList< QgsGeometry > bufferedGeometriesForDissolve; @@ -254,13 +252,11 @@ QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meter QStringList fields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context ); long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); QgsFeature f; QgsFeatureIterator it = source->getFeatures(); - double step = 100.0 / count; + double step = count > 0 ? 100.0 / count : 1; int current = 0; if ( fields.isEmpty() ) @@ -421,8 +417,11 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, Q clipGeoms << f.geometry(); } + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + if ( clipGeoms.isEmpty() ) - return QVariantMap(); + return outputs; // are we clipping against a single feature? if so, we can show finer progress reports bool singleClipFeature = false; @@ -522,8 +521,6 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, Q } } - QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; } @@ -655,13 +652,11 @@ QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantM return QVariantMap(); long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); QgsFeature f; QgsFeatureIterator it = source->getFeatures(); - double step = 100.0 / count; + double step = count > 0 ? 100.0 / count : 1; int current = 0; while ( it.nextFeature( f ) ) { @@ -754,10 +749,8 @@ QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); - double step = 100.0 / count; + double step = count > 0 ? 100.0 / count : 1; int current = 0; if ( !nonMatchingSink ) @@ -953,10 +946,8 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); - double step = 100.0 / count; + double step = count > 0 ? 100.0 / count : 1; int current = 0; if ( !nonMatchingSink ) @@ -1058,10 +1049,8 @@ QVariantMap QgsRemoveNullGeometryAlgorithm::processAlgorithm( const QVariantMap std::unique_ptr< QgsFeatureSink > nullSink( parameterAsSink( parameters, QStringLiteral( "NULL_OUTPUT" ), context, nullSinkId, source->fields() ) ); long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); - double step = 100.0 / count; + double step = count > 0 ? 100.0 / count : 1; int current = 0; QgsFeature f; diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index b6195c55056..6ae487db255 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -642,13 +642,11 @@ QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariant return QVariantMap(); long count = mSource->featureCount(); - if ( count <= 0 ) - return QVariantMap(); QgsFeature f; QgsFeatureIterator it = mSource->getFeatures(); - double step = 100.0 / count; + double step = count > 0 ? 100.0 / count : 1; int current = 0; while ( it.nextFeature( f ) ) { From d926789d3b81def6172f41dd4b6d4fbe9f7d854c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 17 Jul 2017 19:26:56 +1000 Subject: [PATCH 061/266] Improve dox --- python/core/processing/qgsprocessingalgorithm.sip | 8 ++++++++ src/core/processing/qgsprocessingalgorithm.h | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index f1a4fe17175..eb023405b4d 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -777,6 +777,8 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm Maps the input WKB geometry type (``inputWkbType``) to the corresponding output WKB type generated by the algorithm. The default behavior is that the algorithm maintains the same WKB type. + This is called once by the base class when creating the output sink for the algorithm (i.e. it is + not called once per feature processed). :rtype: QgsWkbTypes.Type %End @@ -787,6 +789,9 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm the same fields as are input. Algorithms which add, remove or modify existing fields should override this method and implement logic here to indicate which fields are output by the algorithm. + + This is called once by the base class when creating the output sink for the algorithm (i.e. it is + not called once per feature processed). :rtype: QgsFields %End @@ -795,6 +800,9 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm Maps the input source coordinate reference system (``inputCrs``) to a corresponding output CRS generated by the algorithm. The default behavior is that the algorithm maintains the same CRS as the input source. + + This is called once by the base class when creating the output sink for the algorithm (i.e. it is + not called once per feature processed). :rtype: QgsCoordinateReferenceSystem %End diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 5eb858437cd..00e8c46c826 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -752,6 +752,8 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor * Maps the input WKB geometry type (\a inputWkbType) to the corresponding * output WKB type generated by the algorithm. The default behavior is that the algorithm maintains * the same WKB type. + * This is called once by the base class when creating the output sink for the algorithm (i.e. it is + * not called once per feature processed). */ virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const { return inputWkbType; } @@ -761,6 +763,9 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor * the same fields as are input. * Algorithms which add, remove or modify existing fields should override this method and * implement logic here to indicate which fields are output by the algorithm. + * + * This is called once by the base class when creating the output sink for the algorithm (i.e. it is + * not called once per feature processed). */ virtual QgsFields outputFields( const QgsFields &inputFields ) const { return inputFields; } @@ -768,6 +773,9 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor * Maps the input source coordinate reference system (\a inputCrs) to a corresponding * output CRS generated by the algorithm. The default behavior is that the algorithm maintains * the same CRS as the input source. + * + * This is called once by the base class when creating the output sink for the algorithm (i.e. it is + * not called once per feature processed). */ virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const { return inputCrs; } From aba9da5bc4b8d975389d6c8e8daeb8133565a63d Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 14 Jul 2017 18:55:34 +0200 Subject: [PATCH 062/266] Refactored all providers to use the new base class Also use refreshConnections from the data items when a refresh is required, this function also emits the signal to update the other GUI elements. --- python/gui/qgsarcgisservicesourceselect.sip | 20 ++++++++----- python/gui/qgsowssourceselect.sip | 15 ++++++---- python/gui/qgssourceselect.sip | 10 +++---- src/gui/qgsarcgisservicesourceselect.cpp | 21 +++++++++----- src/gui/qgsarcgisservicesourceselect.h | 29 ++++++++++++++----- src/gui/qgsbrowserdockwidget_p.h | 2 +- src/gui/qgsdatasourcemanagerdialog.cpp | 3 +- src/gui/qgsowssourceselect.cpp | 12 +++++--- src/gui/qgsowssourceselect.h | 15 +++++----- src/gui/qgssourceselect.h | 9 +++--- src/providers/arcgisrest/qgsamsdataitems.cpp | 6 ++-- src/providers/db2/qgsdb2dataitems.cpp | 6 ++-- src/providers/db2/qgsdb2sourceselect.cpp | 12 +++++--- src/providers/db2/qgsdb2sourceselect.h | 11 ++++--- .../qgsdelimitedtextsourceselect.cpp | 8 ++--- .../qgsdelimitedtextsourceselect.h | 6 ++-- src/providers/mssql/qgsmssqldataitems.cpp | 6 ++-- src/providers/mssql/qgsmssqlsourceselect.cpp | 12 +++++--- src/providers/mssql/qgsmssqlsourceselect.h | 12 ++++---- src/providers/oracle/qgsoracledataitems.cpp | 6 ++-- .../oracle/qgsoraclesourceselect.cpp | 5 ++-- src/providers/oracle/qgsoraclesourceselect.h | 9 ++---- src/providers/ows/qgsowsdataitems.cpp | 6 ++-- src/providers/postgres/qgspgsourceselect.cpp | 14 +++++---- src/providers/postgres/qgspgsourceselect.h | 9 +++--- .../postgres/qgspostgresdataitems.cpp | 11 +++++-- .../spatialite/qgsspatialitedataitems.cpp | 4 +-- .../spatialite/qgsspatialitesourceselect.cpp | 12 +++++--- .../spatialite/qgsspatialitesourceselect.h | 10 ++++--- .../virtual/qgsvirtuallayersourceselect.cpp | 13 ++++++--- .../virtual/qgsvirtuallayersourceselect.h | 10 +++++-- src/providers/wcs/qgswcsdataitems.cpp | 6 ++-- src/providers/wfs/qgswfsdataitems.cpp | 6 ++-- src/providers/wfs/qgswfssourceselect.cpp | 12 +++++--- src/providers/wfs/qgswfssourceselect.h | 13 +++++---- 35 files changed, 213 insertions(+), 148 deletions(-) diff --git a/python/gui/qgsarcgisservicesourceselect.sip b/python/gui/qgsarcgisservicesourceselect.sip index cee9e616251..c0e74f96f0e 100644 --- a/python/gui/qgsarcgisservicesourceselect.sip +++ b/python/gui/qgsarcgisservicesourceselect.sip @@ -10,11 +10,11 @@ -class QgsArcGisServiceSourceSelect : QDialog, protected Ui::QgsArcGisServiceSourceSelectBase + +class QgsArcGisServiceSourceSelect : QgsSourceSelect, protected Ui::QgsArcGisServiceSourceSelectBase { %Docstring - Generic class listing layers available from a remote service. -.. versionadded:: 3.0 + Base class for listing ArcGis layers available from a remote service. %End %TypeHeaderCode @@ -23,7 +23,7 @@ class QgsArcGisServiceSourceSelect : QDialog, protected Ui::QgsArcGisServiceSour public: enum ServiceType { MapService, FeatureService }; - QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ); + QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); %Docstring Constructor %End @@ -38,10 +38,6 @@ Sets the current extent and CRS. Used to select an appropriate CRS and possibly void addLayer( QString uri, QString typeName ); %Docstring Emitted when a layer is added from the dialog -%End - void connectionsChanged(); -%Docstring -Emitted when the connections for the service were changed %End protected: @@ -75,6 +71,14 @@ Returns the selected image encoding. :rtype: str %End + public slots: + + virtual void refresh( ); + +%Docstring +Triggered when the provider's connections need to be refreshed +%End + }; diff --git a/python/gui/qgsowssourceselect.sip b/python/gui/qgsowssourceselect.sip index a05129b202c..f30cb0633f8 100644 --- a/python/gui/qgsowssourceselect.sip +++ b/python/gui/qgsowssourceselect.sip @@ -11,16 +11,16 @@ -class QgsOWSSourceSelect : QDialog, protected Ui::QgsOWSSourceSelectBase +class QgsOWSSourceSelect : QgsSourceSelect, protected Ui::QgsOWSSourceSelectBase { %Docstring - Dialog to create connections and add layers from WMS, WFS, WCS etc. + Dialog to create connections and add layers WCS etc. This dialog allows the user to define and save connection information for WMS servers, etc. The user can then connect and add - layers from the WMS server to the map canvas. + layers from the WCS server to the map canvas. %End %TypeHeaderCode @@ -42,6 +42,12 @@ Constructor public slots: + virtual void refresh( ); + +%Docstring +Triggered when the provider's connections need to be refreshed +%End + void on_mNewButton_clicked(); %Docstring Opens the create connection dialog to build a new connection @@ -110,7 +116,6 @@ Add some default wms servers to the list void addRasterLayer( const QString &rasterLayerPath, const QString &baseName, const QString &providerKey ); - void connectionsChanged(); protected: @@ -198,8 +203,6 @@ Add a few example servers to the list. %End - - virtual void populateLayerList(); %Docstring Populate the layer list. diff --git a/python/gui/qgssourceselect.sip b/python/gui/qgssourceselect.sip index ea08591a1c3..43af0d7bc92 100644 --- a/python/gui/qgssourceselect.sip +++ b/python/gui/qgssourceselect.sip @@ -13,7 +13,7 @@ class QgsSourceSelect : QDialog { %Docstring - Abstract base Dialog to create connections and add layers + Abstract base Data Source Widget to create connections and add layers This class must provide common functionality and the interface for all source select dialogs used by data providers to configure data sources and add layers. @@ -30,9 +30,9 @@ class QgsSourceSelect : QDialog Constructor %End - ~QgsSourceSelect( ); + virtual ~QgsSourceSelect( ) = 0; %Docstring -Destructor +Pure Virtual Destructor %End QgsProviderRegistry::WidgetMode widgetMode( ); @@ -43,9 +43,9 @@ Return the widget mode public slots: - virtual void refresh( ) = 0; + virtual void refresh( ); %Docstring -Triggered when the provider's connections need to be refreshed +The default implementation does nothing %End signals: diff --git a/src/gui/qgsarcgisservicesourceselect.cpp b/src/gui/qgsarcgisservicesourceselect.cpp index c31fc1cba9a..59cba337066 100644 --- a/src/gui/qgsarcgisservicesourceselect.cpp +++ b/src/gui/qgsarcgisservicesourceselect.cpp @@ -37,9 +37,9 @@ #include #include -/** \ingroup gui +/** * Item delegate with tweaked sizeHint. - * @note not available in Python bindings */ + */ class QgsSourceSelectItemDelegate : public QItemDelegate { public: @@ -49,12 +49,12 @@ class QgsSourceSelectItemDelegate : public QItemDelegate }; -QgsArcGisServiceSourceSelect::QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ) - : QDialog( parent, fl ), - mServiceName( serviceName ), - mServiceType( serviceType ), - mBuildQueryButton( 0 ), - mImageEncodingGroup( 0 ) +QgsArcGisServiceSourceSelect::QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): + QgsSourceSelect( parent, fl, widgetMode ), + mServiceName( serviceName ), + mServiceType( serviceType ), + mBuildQueryButton( 0 ), + mImageEncodingGroup( 0 ) { setupUi( this ); setWindowTitle( QStringLiteral( "Add %1 Layer from a Server" ).arg( mServiceName ) ); @@ -221,6 +221,11 @@ QString QgsArcGisServiceSourceSelect::getPreferredCrs( const QSet &crsS return *( crsSet.constBegin() ); } +void QgsArcGisServiceSourceSelect::refresh() +{ + populateConnectionList(); +} + void QgsArcGisServiceSourceSelect::addEntryToServerList() { diff --git a/src/gui/qgsarcgisservicesourceselect.h b/src/gui/qgsarcgisservicesourceselect.h index ad892fb82dd..b9bec3c4fcb 100644 --- a/src/gui/qgsarcgisservicesourceselect.h +++ b/src/gui/qgsarcgisservicesourceselect.h @@ -16,10 +16,21 @@ #ifndef QGSARCGISSERVICESOURCESELECTDIALOG_H #define QGSARCGISSERVICESOURCESELECTDIALOG_H +/// @cond PRIVATE + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QGIS API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// + #include "ui_qgsarcgisservicesourceselectbase.h" #include "qgsrectangle.h" #include "qgscoordinatereferencesystem.h" - +#include "qgssourceselect.h" #include "qgis_gui.h" class QStandardItemModel; @@ -27,11 +38,10 @@ class QSortFilterProxyModel; class QgsProjectionSelectionDialog; class QgsOwsConnection; -/** \ingroup gui - * Generic class listing layers available from a remote service. - * \since QGIS 3.0 +/** + * Base class for listing ArcGis layers available from a remote service. */ -class GUI_EXPORT QgsArcGisServiceSourceSelect : public QDialog, protected Ui::QgsArcGisServiceSourceSelectBase +class GUI_EXPORT QgsArcGisServiceSourceSelect : public QgsSourceSelect, protected Ui::QgsArcGisServiceSourceSelectBase { Q_OBJECT @@ -40,7 +50,7 @@ class GUI_EXPORT QgsArcGisServiceSourceSelect : public QDialog, protected Ui::Qg enum ServiceType { MapService, FeatureService }; //! Constructor - QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl ); + QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); ~QgsArcGisServiceSourceSelect(); //! Sets the current extent and CRS. Used to select an appropriate CRS and possibly to retrieve data only in the current extent @@ -49,8 +59,6 @@ class GUI_EXPORT QgsArcGisServiceSourceSelect : public QDialog, protected Ui::Qg signals: //! Emitted when a layer is added from the dialog void addLayer( QString uri, QString typeName ); - //! Emitted when the connections for the service were changed - void connectionsChanged(); protected: QString mServiceName; @@ -92,6 +100,11 @@ class GUI_EXPORT QgsArcGisServiceSourceSelect : public QDialog, protected Ui::Qg \returns the authority id of the crs or an empty string in case of error*/ QString getPreferredCrs( const QSet &crsSet ) const; + public slots: + + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; + private slots: void addEntryToServerList(); void deleteEntryOfServerList(); diff --git a/src/gui/qgsbrowserdockwidget_p.h b/src/gui/qgsbrowserdockwidget_p.h index 1de539831db..c7e06b1e892 100644 --- a/src/gui/qgsbrowserdockwidget_p.h +++ b/src/gui/qgsbrowserdockwidget_p.h @@ -50,7 +50,7 @@ class QgsLayerItem; class QgsDataItem; class QgsBrowserTreeFilterProxyModel; -SIP_NO_FILE +#define SIP_NO_FILE /** * Hack to show wrapped text without spaces diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index a6264557c68..6b06c846c08 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -117,6 +117,7 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, this->vectorLayerAdded( vectorLayerPath, baseName, QStringLiteral( "WFS" ) ); } ); connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); } addRasterProviderDialog( QStringLiteral( "arcgismapserver" ), tr( "ArcGIS Map Server" ), QStringLiteral( "/mActionAddAmsLayer.svg" ) ); @@ -129,6 +130,7 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, afss->setCurrentExtentAndCrs( mMapCanvas->extent(), mMapCanvas->mapSettings().destinationCrs() ); // Forward (if only a common interface for the signals had been used in the providers ...) connect( afss, SIGNAL( addLayer( QString, QString ) ), this, SIGNAL( addAfsLayer( QString, QString ) ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), afss, SLOT( refresh( ) ) ); connect( this, &QgsDataSourceManagerDialog::addAfsLayer, this, [ = ]( const QString & vectorLayerPath, const QString & baseName ) { this->vectorLayerAdded( vectorLayerPath, baseName, QStringLiteral( "arcgisfeatureserver" ) ); } ); @@ -234,6 +236,5 @@ void QgsDataSourceManagerDialog::addRasterProviderDialog( const QString provider connect( dlg, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ), this, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ) ); connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); - connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); } } diff --git a/src/gui/qgsowssourceselect.cpp b/src/gui/qgsowssourceselect.cpp index 55cc130f2e6..3e2ed8dcb71 100644 --- a/src/gui/qgsowssourceselect.cpp +++ b/src/gui/qgsowssourceselect.cpp @@ -55,14 +55,13 @@ #include QgsOWSSourceSelect::QgsOWSSourceSelect( const QString &service, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) + : QgsSourceSelect( parent, fl, widgetMode ) , mService( service ) - , mWidgetMode( widgetMode ) , mCurrentTileset( nullptr ) { setupUi( this ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); } @@ -89,7 +88,7 @@ QgsOWSSourceSelect::QgsOWSSourceSelect( const QString &service, QWidget *parent, // 'Prefer network' is the default noted in the combobox's tool tip mCacheComboBox->setCurrentIndex( mCacheComboBox->findData( QNetworkRequest::PreferNetwork ) ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { connect( mAddButton, &QAbstractButton::clicked, this, &QgsOWSSourceSelect::addClicked ); //set the current project CRS if available @@ -126,6 +125,11 @@ QgsOWSSourceSelect::~QgsOWSSourceSelect() settings.setValue( QStringLiteral( "Windows/WMSSourceSelect/geometry" ), saveGeometry() ); } +void QgsOWSSourceSelect::refresh() +{ + populateConnectionList(); +} + void QgsOWSSourceSelect::clearFormats() { mFormatComboBox->clear(); diff --git a/src/gui/qgsowssourceselect.h b/src/gui/qgsowssourceselect.h index 69e84cc2dbd..0d3732b57d3 100644 --- a/src/gui/qgsowssourceselect.h +++ b/src/gui/qgsowssourceselect.h @@ -26,6 +26,7 @@ #include "qgsguiutils.h" #include "qgscontexthelp.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" #include #include @@ -40,15 +41,15 @@ class QDomElement; /** \ingroup gui - * \brief Dialog to create connections and add layers from WMS, WFS, WCS etc. + * \brief Dialog to create connections and add layers WCS etc. * * This dialog allows the user to define and save connection information * for WMS servers, etc. * * The user can then connect and add - * layers from the WMS server to the map canvas. + * layers from the WCS server to the map canvas. */ -class GUI_EXPORT QgsOWSSourceSelect : public QDialog, protected Ui::QgsOWSSourceSelectBase +class GUI_EXPORT QgsOWSSourceSelect : public QgsSourceSelect, protected Ui::QgsOWSSourceSelectBase { Q_OBJECT @@ -67,6 +68,9 @@ class GUI_EXPORT QgsOWSSourceSelect : public QDialog, protected Ui::QgsOWSSource public slots: + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; + //! Opens the create connection dialog to build a new connection void on_mNewButton_clicked(); //! Opens a dialog to edit an existing connection @@ -110,7 +114,6 @@ class GUI_EXPORT QgsOWSSourceSelect : public QDialog, protected Ui::QgsOWSSource void addRasterLayer( const QString &rasterLayerPath, const QString &baseName, const QString &providerKey ); - void connectionsChanged(); protected: @@ -167,10 +170,6 @@ class GUI_EXPORT QgsOWSSourceSelect : public QDialog, protected Ui::QgsOWSSource //! Service name QString mService; - //! Embedded mode, without 'Close' - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; - - /** * \brief Populate the layer list. * diff --git a/src/gui/qgssourceselect.h b/src/gui/qgssourceselect.h index 86bc591269b..030bbc5b8d1 100644 --- a/src/gui/qgssourceselect.h +++ b/src/gui/qgssourceselect.h @@ -27,7 +27,7 @@ #include /** \ingroup gui - * \brief Abstract base Dialog to create connections and add layers + * \brief Abstract base Data Source Widget to create connections and add layers * This class must provide common functionality and the interface for all * source select dialogs used by data providers to configure data sources * and add layers. @@ -42,8 +42,8 @@ class GUI_EXPORT QgsSourceSelect : public QDialog //! Constructor QgsSourceSelect( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); - //! Destructor - ~QgsSourceSelect( ); + //! Pure Virtual Destructor + virtual ~QgsSourceSelect( ) = 0; //! Return the widget mode QgsProviderRegistry::WidgetMode widgetMode( ) { return mWidgetMode; } @@ -51,7 +51,8 @@ class GUI_EXPORT QgsSourceSelect : public QDialog public slots: //! Triggered when the provider's connections need to be refreshed - virtual void refresh( ) = 0; + //! The default implementation does nothing + virtual void refresh( ) {} signals: diff --git a/src/providers/arcgisrest/qgsamsdataitems.cpp b/src/providers/arcgisrest/qgsamsdataitems.cpp index d465873362a..9f2a7ce80ba 100644 --- a/src/providers/arcgisrest/qgsamsdataitems.cpp +++ b/src/providers/arcgisrest/qgsamsdataitems.cpp @@ -75,7 +75,7 @@ void QgsAmsRootItem::newConnection() if ( nc.exec() ) { - refresh(); + refreshConnections(); } } #endif @@ -158,14 +158,14 @@ void QgsAmsConnectionItem::editConnection() if ( nc.exec() ) { - mParent->refresh(); + mParent->refreshConnections(); } } void QgsAmsConnectionItem::deleteConnection() { QgsOwsConnection::deleteConnection( QStringLiteral( "arcgismapserver" ), mName ); - mParent->refresh(); + mParent->refreshConnections(); } #endif diff --git a/src/providers/db2/qgsdb2dataitems.cpp b/src/providers/db2/qgsdb2dataitems.cpp index 6f84e65179d..12c4e263272 100644 --- a/src/providers/db2/qgsdb2dataitems.cpp +++ b/src/providers/db2/qgsdb2dataitems.cpp @@ -275,7 +275,7 @@ void QgsDb2ConnectionItem::editConnection() if ( nc.exec() ) { // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } } @@ -292,7 +292,7 @@ void QgsDb2ConnectionItem::deleteConnection() settings.remove( key + "/password" ); settings.remove( key + "/environment" ); settings.remove( key ); - mParent->refresh(); + mParent->refreshConnections(); } void QgsDb2ConnectionItem::refreshConnection() @@ -453,7 +453,7 @@ void QgsDb2RootItem::newConnection() QgsDb2NewConnection newConnection( NULL, mName ); if ( newConnection.exec() ) { - refresh(); + refreshConnections(); } } diff --git a/src/providers/db2/qgsdb2sourceselect.cpp b/src/providers/db2/qgsdb2sourceselect.cpp index 159dfdef166..11b697b6f1b 100644 --- a/src/providers/db2/qgsdb2sourceselect.cpp +++ b/src/providers/db2/qgsdb2sourceselect.cpp @@ -117,8 +117,7 @@ void QgsDb2SourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMod } QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) - , mWidgetMode( widgetMode ) + : QgsSourceSelect( parent, fl, widgetMode ) , mColumnTypeThread( NULL ) , mUseEstimatedMetadata( false ) { @@ -126,7 +125,7 @@ QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs setWindowTitle( tr( "Add Db2 Table(s)" ) ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -139,7 +138,7 @@ QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsDb2SourceSelect::addTables ); @@ -305,6 +304,11 @@ void QgsDb2SourceSelect::buildQuery() setSql( mTablesTreeView->currentIndex() ); } +void QgsDb2SourceSelect::refresh() +{ + populateConnectionList(); +} + void QgsDb2SourceSelect::on_mTablesTreeView_clicked( const QModelIndex &index ) { mBuildQueryButton->setEnabled( index.parent().isValid() ); diff --git a/src/providers/db2/qgsdb2sourceselect.h b/src/providers/db2/qgsdb2sourceselect.h index 762dbf0724c..2f7dba09d7f 100644 --- a/src/providers/db2/qgsdb2sourceselect.h +++ b/src/providers/db2/qgsdb2sourceselect.h @@ -25,6 +25,7 @@ #include "qgsdb2tablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" #include #include @@ -87,7 +88,7 @@ class QgsDb2GeomColumnTypeThread : public QThread * for Db2 databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsDb2SourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase +class QgsDb2SourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase { Q_OBJECT @@ -97,7 +98,7 @@ class QgsDb2SourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase static void deleteConnection( const QString &key ); //! Constructor - QgsDb2SourceSelect( QWidget *parent = 0, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + QgsDb2SourceSelect( QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); ~QgsDb2SourceSelect(); //! Populate the connection list combo box @@ -109,7 +110,6 @@ class QgsDb2SourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase signals: void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); - void connectionsChanged(); void addGeometryColumn( QgsDb2LayerProperty ); void progress( int, int ); void progressMessage( QString ); @@ -118,6 +118,8 @@ class QgsDb2SourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase //! Determines the tables the user selected and closes the dialog void addTables(); void buildQuery(); + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; /** Connects to the database using the stored connection parameters. * Once connected, available layers are displayed. @@ -156,9 +158,6 @@ class QgsDb2SourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase typedef QPair geomPair; typedef QList geomCol; - //! Embedded mode, without 'Close' - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; - // queue another query for the thread void addSearchGeometryColumn( const QString &connectionName, const QgsDb2LayerProperty &layerProperty, bool estimateMetadata ); diff --git a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp index 92b2bee474f..95cafa509c2 100644 --- a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp +++ b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp @@ -35,13 +35,12 @@ const int MAX_SAMPLE_LENGTH = 200; QgsDelimitedTextSourceSelect::QgsDelimitedTextSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) + : QgsSourceSelect( parent, fl, widgetMode ) , mFile( new QgsDelimitedTextFile() ) , mExampleRowCount( 20 ) , mBadRowCount( 0 ) , mPluginKey( QStringLiteral( "/Plugin-DelimitedText" ) ) , mLastFileType( QLatin1String( "" ) ) - , mWidgetMode( widgetMode ) { setupUi( this ); @@ -49,7 +48,7 @@ QgsDelimitedTextSourceSelect::QgsDelimitedTextSourceSelect( QWidget *parent, Qt: QgsSettings settings; restoreGeometry( settings.value( mPluginKey + "/geometry" ).toByteArray() ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Cancel ) ); buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add" ) ); @@ -200,7 +199,7 @@ void QgsDelimitedTextSourceSelect::on_buttonBox_accepted() // add the layer to the map emit addVectorLayer( QString::fromAscii( url.toEncoded() ), txtLayerName->text(), QStringLiteral( "delimitedtext" ) ); - if ( mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } @@ -743,6 +742,7 @@ bool QgsDelimitedTextSourceSelect::validate() return enabled; } + void QgsDelimitedTextSourceSelect::enableAccept() { bool enabled = validate(); diff --git a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h index 9b6b80be719..78f5399d904 100644 --- a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h +++ b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h @@ -19,6 +19,7 @@ #include "qgshelp.h" #include "qgsguiutils.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" class QButtonGroup; class QgisInterface; @@ -27,12 +28,12 @@ class QgsDelimitedTextFile; /** * \class QgsDelimitedTextSourceSelect */ -class QgsDelimitedTextSourceSelect : public QDialog, private Ui::QgsDelimitedTextSourceSelectBase +class QgsDelimitedTextSourceSelect : public QgsSourceSelect, private Ui::QgsDelimitedTextSourceSelectBase { Q_OBJECT public: - QgsDelimitedTextSourceSelect( QWidget *parent, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + QgsDelimitedTextSourceSelect( QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); ~QgsDelimitedTextSourceSelect(); QStringList splitLine( QString line ); @@ -57,7 +58,6 @@ class QgsDelimitedTextSourceSelect : public QDialog, private Ui::QgsDelimitedTex QString mLastFileType; QButtonGroup *bgFileFormat = nullptr; QButtonGroup *bgGeomType = nullptr; - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; private slots: void on_buttonBox_accepted(); diff --git a/src/providers/mssql/qgsmssqldataitems.cpp b/src/providers/mssql/qgsmssqldataitems.cpp index e8620542383..73205030838 100644 --- a/src/providers/mssql/qgsmssqldataitems.cpp +++ b/src/providers/mssql/qgsmssqldataitems.cpp @@ -366,7 +366,7 @@ void QgsMssqlConnectionItem::editConnection() if ( nc.exec() ) { // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } } @@ -374,7 +374,7 @@ void QgsMssqlConnectionItem::deleteConnection() { QgsMssqlSourceSelect::deleteConnection( mName ); // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } #endif @@ -633,7 +633,7 @@ void QgsMssqlRootItem::newConnection() QgsMssqlNewConnection nc( nullptr ); if ( nc.exec() ) { - refresh(); + refreshConnections(); } } #endif diff --git a/src/providers/mssql/qgsmssqlsourceselect.cpp b/src/providers/mssql/qgsmssqlsourceselect.cpp index 068913582d8..f89d105ad12 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.cpp +++ b/src/providers/mssql/qgsmssqlsourceselect.cpp @@ -119,8 +119,7 @@ void QgsMssqlSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemM } QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) - , mWidgetMode( widgetMode ) + : QgsSourceSelect( parent, fl, widgetMode ) , mColumnTypeThread( nullptr ) , mUseEstimatedMetadata( false ) { @@ -128,7 +127,7 @@ QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, setWindowTitle( tr( "Add MSSQL Table(s)" ) ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -141,7 +140,7 @@ QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsMssqlSourceSelect::addTables ); @@ -657,6 +656,11 @@ QString QgsMssqlSourceSelect::connectionInfo() return mConnInfo; } +void QgsMssqlSourceSelect::refresh() +{ + populateConnectionList(); +} + void QgsMssqlSourceSelect::setSql( const QModelIndex &index ) { if ( !index.parent().isValid() ) diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h index 680ddb3aa03..1142dfe8b07 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.h +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -23,6 +23,7 @@ #include "qgsmssqltablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" #include #include @@ -57,7 +58,7 @@ class QgsMssqlSourceSelectDelegate : public QItemDelegate * for MSSQL databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsMssqlSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase +class QgsMssqlSourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase { Q_OBJECT @@ -79,12 +80,15 @@ class QgsMssqlSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase signals: void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); - void connectionsChanged(); void addGeometryColumn( const QgsMssqlLayerProperty & ); void progress( int, int ); void progressMessage( QString ); public slots: + + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; + //! Determines the tables the user selected and closes the dialog void addTables(); void buildQuery(); @@ -122,13 +126,11 @@ class QgsMssqlSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase void columnThreadFinished(); + private: typedef QPair geomPair; typedef QList geomCol; - //! Embedded mode, without 'Close' - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; - // queue another query for the thread void addSearchGeometryColumn( const QString &connectionName, const QgsMssqlLayerProperty &layerProperty, bool estimateMetadata ); diff --git a/src/providers/oracle/qgsoracledataitems.cpp b/src/providers/oracle/qgsoracledataitems.cpp index 72cc8fe3eb3..573c2b8a38e 100644 --- a/src/providers/oracle/qgsoracledataitems.cpp +++ b/src/providers/oracle/qgsoracledataitems.cpp @@ -199,7 +199,7 @@ void QgsOracleConnectionItem::editConnection() if ( nc.exec() ) { // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } } @@ -214,7 +214,7 @@ void QgsOracleConnectionItem::deleteConnection() // the parent should be updated if ( mParent ) - mParent->refresh(); + mParent->refreshConnections(); } void QgsOracleConnectionItem::refreshConnection() @@ -487,7 +487,7 @@ void QgsOracleRootItem::newConnection() QgsOracleNewConnection nc( NULL ); if ( nc.exec() ) { - refresh(); + refreshConnections(); } } diff --git a/src/providers/oracle/qgsoraclesourceselect.cpp b/src/providers/oracle/qgsoraclesourceselect.cpp index 545775a9c08..9918491e553 100644 --- a/src/providers/oracle/qgsoraclesourceselect.cpp +++ b/src/providers/oracle/qgsoraclesourceselect.cpp @@ -167,14 +167,13 @@ void QgsOracleSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItem } QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) - , mWidgetMode( widgetMode ) + : QgsSourceSelect( parent, fl, widgetMode ) , mColumnTypeThread( 0 ) , mIsConnected( false ) { setupUi( this ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::Embedded ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); diff --git a/src/providers/oracle/qgsoraclesourceselect.h b/src/providers/oracle/qgsoraclesourceselect.h index a873a22b505..ec5083039c9 100644 --- a/src/providers/oracle/qgsoraclesourceselect.h +++ b/src/providers/oracle/qgsoraclesourceselect.h @@ -24,6 +24,7 @@ #include "qgshelp.h" #include "qgsoracleconnpool.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" #include #include @@ -81,13 +82,13 @@ class QgsOracleSourceSelectDelegate : public QItemDelegate * for Oracle databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsOracleSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase +class QgsOracleSourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase { Q_OBJECT public: //! Constructor - QgsOracleSourceSelect( QWidget *parent = 0, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + QgsOracleSourceSelect( QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); //! Destructor ~QgsOracleSourceSelect(); //! Populate the connection list combo box @@ -97,7 +98,6 @@ class QgsOracleSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase signals: void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); - void connectionsChanged(); void progress( int, int ); void progressMessage( QString ); @@ -143,9 +143,6 @@ class QgsOracleSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase typedef QPair geomPair; typedef QList geomCol; - //! Embedded mode, without 'Close' - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; - //! try to load list of tables from local cache void loadTableFromCache(); diff --git a/src/providers/ows/qgsowsdataitems.cpp b/src/providers/ows/qgsowsdataitems.cpp index f6a13e8e239..c58302c79c4 100644 --- a/src/providers/ows/qgsowsdataitems.cpp +++ b/src/providers/ows/qgsowsdataitems.cpp @@ -157,7 +157,7 @@ void QgsOWSConnectionItem::editConnection() if ( nc.exec() ) { // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } #endif } @@ -167,7 +167,7 @@ void QgsOWSConnectionItem::deleteConnection() #if 0 QgsOWSConnection::deleteConnection( "OWS", mName ); // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); #endif } #endif @@ -247,7 +247,7 @@ void QgsOWSRootItem::newConnection() if ( nc.exec() ) { - refresh(); + refreshConnections(); } #endif } diff --git a/src/providers/postgres/qgspgsourceselect.cpp b/src/providers/postgres/qgspgsourceselect.cpp index fc9b5d55c11..e4bcb065462 100644 --- a/src/providers/postgres/qgspgsourceselect.cpp +++ b/src/providers/postgres/qgspgsourceselect.cpp @@ -194,14 +194,13 @@ void QgsPgSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMode } QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) - , mWidgetMode( widgetMode ) + : QgsSourceSelect( parent, fl, widgetMode ) , mColumnTypeThread( nullptr ) , mUseEstimatedMetadata( false ) { setupUi( this ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -218,7 +217,7 @@ QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsPr mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsPgSourceSelect::addTables ); @@ -509,7 +508,7 @@ void QgsPgSourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "postgres" ) ); - if ( ! mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( ! mHoldDialogOpen->isChecked() && QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) { accept(); } @@ -591,6 +590,11 @@ QgsDataSourceUri QgsPgSourceSelect::dataSourceUri() return mDataSrcUri; } +void QgsPgSourceSelect::refresh() +{ + populateConnectionList(); +} + void QgsPgSourceSelect::setSql( const QModelIndex &index ) { if ( !index.parent().isValid() ) diff --git a/src/providers/postgres/qgspgsourceselect.h b/src/providers/postgres/qgspgsourceselect.h index 2d18688210a..5658cfa6a34 100644 --- a/src/providers/postgres/qgspgsourceselect.h +++ b/src/providers/postgres/qgspgsourceselect.h @@ -24,6 +24,7 @@ #include "qgspgtablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" #include #include @@ -58,7 +59,7 @@ class QgsPgSourceSelectDelegate : public QItemDelegate * for PostGIS enabled PostgreSQL databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsPgSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase +class QgsPgSourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase { Q_OBJECT @@ -78,12 +79,13 @@ class QgsPgSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase signals: void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); - void connectionsChanged(); void addGeometryColumn( const QgsPostgresLayerProperty & ); void progress( int, int ); void progressMessage( const QString & ); public slots: + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; //! Determines the tables the user selected and closes the dialog void addTables(); void buildQuery(); @@ -125,9 +127,6 @@ class QgsPgSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase typedef QPair geomPair; typedef QList geomCol; - //! Embedded mode, without 'Close' - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; - // queue another query for the thread void addSearchGeometryColumn( const QgsPostgresLayerProperty &layerProperty ); diff --git a/src/providers/postgres/qgspostgresdataitems.cpp b/src/providers/postgres/qgspostgresdataitems.cpp index 52aaaedafb1..fb8deb10935 100644 --- a/src/providers/postgres/qgspostgresdataitems.cpp +++ b/src/providers/postgres/qgspostgresdataitems.cpp @@ -134,7 +134,7 @@ void QgsPGConnectionItem::editConnection() { // the parent should be updated if ( mParent ) - mParent->refresh(); + mParent->refreshConnections(); } } @@ -148,13 +148,15 @@ void QgsPGConnectionItem::deleteConnection() QgsPostgresConn::deleteConnection( mName ); // the parent should be updated if ( mParent ) - mParent->refresh(); + mParent->refreshConnections(); } void QgsPGConnectionItem::refreshConnection() { - // the parent should be updated refresh(); + // the parent should be updated + if ( mParent ) + mParent->refreshConnections(); } void QgsPGConnectionItem::createSchema() @@ -185,6 +187,9 @@ void QgsPGConnectionItem::createSchema() conn->unref(); refresh(); + // the parent should be updated + if ( mParent ) + mParent->refreshConnections(); } #endif diff --git a/src/providers/spatialite/qgsspatialitedataitems.cpp b/src/providers/spatialite/qgsspatialitedataitems.cpp index a331c1c3573..932e1116bf4 100644 --- a/src/providers/spatialite/qgsspatialitedataitems.cpp +++ b/src/providers/spatialite/qgsspatialitedataitems.cpp @@ -193,7 +193,7 @@ void QgsSLConnectionItem::deleteConnection() QgsSpatiaLiteConnection::deleteConnection( mName ); // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } #endif @@ -326,7 +326,7 @@ void QgsSLRootItem::newConnection() { if ( QgsSpatiaLiteSourceSelect::newConnection( nullptr ) ) { - refresh(); + refreshConnections(); } } #endif diff --git a/src/providers/spatialite/qgsspatialitesourceselect.cpp b/src/providers/spatialite/qgsspatialitesourceselect.cpp index 62b9214de6d..2de7fc938eb 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.cpp +++ b/src/providers/spatialite/qgsspatialitesourceselect.cpp @@ -41,8 +41,7 @@ email : a.furieri@lqt.it #endif QgsSpatiaLiteSourceSelect::QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): - QDialog( parent, fl ), - mWidgetMode( widgetMode ) + QgsSourceSelect( parent, fl, widgetMode ) { setupUi( this ); @@ -67,7 +66,7 @@ QgsSpatiaLiteSourceSelect::QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::Windo connect( mBuildQueryButton, &QAbstractButton::clicked, this, &QgsSpatiaLiteSourceSelect::buildQuery ); mBuildQueryButton->setEnabled( false ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -418,7 +417,7 @@ void QgsSpatiaLiteSourceSelect::addTables() else { emit addDatabaseLayers( m_selectedTables, QStringLiteral( "spatialite" ) ); - if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None && ! mHoldDialogOpen->isChecked() ) { accept(); } @@ -555,6 +554,11 @@ void QgsSpatiaLiteSourceSelect::dbChanged() settings.setValue( QStringLiteral( "SpatiaLite/connections/selected" ), cmbConnections->currentText() ); } +void QgsSpatiaLiteSourceSelect::refresh() +{ + populateConnectionList(); +} + void QgsSpatiaLiteSourceSelect::setConnectionListPosition() { QgsSettings settings; diff --git a/src/providers/spatialite/qgsspatialitesourceselect.h b/src/providers/spatialite/qgsspatialitesourceselect.h index 17fbc745601..607fd1af2f3 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.h +++ b/src/providers/spatialite/qgsspatialitesourceselect.h @@ -22,6 +22,7 @@ #include "qgsspatialitetablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" #include #include @@ -41,7 +42,7 @@ class QPushButton; * for SpatiaLite/SQLite databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsSpatiaLiteSourceSelect: public QDialog, private Ui::QgsDbSourceSelectBase +class QgsSpatiaLiteSourceSelect: public QgsSourceSelect, private Ui::QgsDbSourceSelectBase { Q_OBJECT @@ -51,7 +52,7 @@ class QgsSpatiaLiteSourceSelect: public QDialog, private Ui::QgsDbSourceSelectBa static bool newConnection( QWidget *parent ); //! Constructor - QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + QgsSpatiaLiteSourceSelect( QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); ~QgsSpatiaLiteSourceSelect(); //! Populate the connection list combo box @@ -67,6 +68,9 @@ class QgsSpatiaLiteSourceSelect: public QDialog, private Ui::QgsDbSourceSelectBa public slots: + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; + /** Connects to the database using the stored connection parameters. * Once connected, available layers are displayed. */ @@ -95,7 +99,6 @@ class QgsSpatiaLiteSourceSelect: public QDialog, private Ui::QgsDbSourceSelectBa void on_buttonBox_helpRequested() { QgsHelp::openHelp( QStringLiteral( "working_with_vector/supported_data.html#spatialite-layers" ) ); } signals: - void connectionsChanged(); void addDatabaseLayers( QStringList const &paths, QString const &providerKey ); void progress( int, int ); void progressMessage( QString ); @@ -132,7 +135,6 @@ class QgsSpatiaLiteSourceSelect: public QDialog, private Ui::QgsDbSourceSelectBa QPushButton *mBuildQueryButton = nullptr; QPushButton *mAddButton = nullptr; QPushButton *mStatsButton = nullptr; - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; }; #endif // QGSSPATIALITESOURCESELECT_H diff --git a/src/providers/virtual/qgsvirtuallayersourceselect.cpp b/src/providers/virtual/qgsvirtuallayersourceselect.cpp index f13c7f1ad1c..2e20769e5ce 100644 --- a/src/providers/virtual/qgsvirtuallayersourceselect.cpp +++ b/src/providers/virtual/qgsvirtuallayersourceselect.cpp @@ -38,14 +38,13 @@ email : hugo dot mercier at oslandia dot com #include QgsVirtualLayerSourceSelect::QgsVirtualLayerSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) + : QgsSourceSelect( parent, fl, widgetMode ) , mSrid( 0 ) - , mWidgetMode( widgetMode ) , mTreeView( nullptr ) { setupUi( this ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Cancel ) ); buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add" ) ); @@ -95,6 +94,12 @@ QgsVirtualLayerSourceSelect::QgsVirtualLayerSourceSelect( QWidget *parent, Qt::W } } +void QgsVirtualLayerSourceSelect::refresh() +{ + // TODO: check that this really works + updateLayersList(); +} + void QgsVirtualLayerSourceSelect::onLayerComboChanged( int idx ) { if ( idx == -1 ) @@ -375,7 +380,7 @@ void QgsVirtualLayerSourceSelect::on_buttonBox_accepted() { emit addVectorLayer( def.toString(), layerName, QStringLiteral( "virtual" ) ); } - if ( mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) { accept( ); } diff --git a/src/providers/virtual/qgsvirtuallayersourceselect.h b/src/providers/virtual/qgsvirtuallayersourceselect.h index c90f915f119..4d5d391f67c 100644 --- a/src/providers/virtual/qgsvirtuallayersourceselect.h +++ b/src/providers/virtual/qgsvirtuallayersourceselect.h @@ -24,18 +24,23 @@ email : hugo dot mercier at oslandia dot com #include "qgsguiutils.h" #include #include "qgsproviderregistry.h" +#include "qgssourceselect.h" class QgsVectorLayer; class QMainWindow; class QgsEmbeddedLayerSelectDialog; class QgsLayerTreeView; -class QgsVirtualLayerSourceSelect : public QDialog, private Ui::QgsVirtualLayerSourceSelectBase +class QgsVirtualLayerSourceSelect : public QgsSourceSelect, private Ui::QgsVirtualLayerSourceSelectBase { Q_OBJECT public: - QgsVirtualLayerSourceSelect( QWidget *parent, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + QgsVirtualLayerSourceSelect( QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + + public slots: + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; private slots: void on_buttonBox_accepted(); @@ -59,7 +64,6 @@ class QgsVirtualLayerSourceSelect : public QDialog, private Ui::QgsVirtualLayerS long mSrid; QStringList mProviderList; QgsEmbeddedLayerSelectDialog *mEmbeddedSelectionDialog = nullptr; - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; void addEmbeddedLayer( const QString &name, const QString &provider, const QString &encoding, const QString &source ); QgsLayerTreeView *mTreeView = nullptr; }; diff --git a/src/providers/wcs/qgswcsdataitems.cpp b/src/providers/wcs/qgswcsdataitems.cpp index cd207110f50..a2944715b38 100644 --- a/src/providers/wcs/qgswcsdataitems.cpp +++ b/src/providers/wcs/qgswcsdataitems.cpp @@ -108,7 +108,7 @@ void QgsWCSConnectionItem::editConnection() if ( nc.exec() ) { // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } } @@ -116,7 +116,7 @@ void QgsWCSConnectionItem::deleteConnection() { QgsOwsConnection::deleteConnection( QStringLiteral( "WCS" ), mName ); // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } #endif @@ -276,7 +276,7 @@ void QgsWCSRootItem::newConnection() if ( nc.exec() ) { - refresh(); + refreshConnections(); } } #endif diff --git a/src/providers/wfs/qgswfsdataitems.cpp b/src/providers/wfs/qgswfsdataitems.cpp index edb06a54972..7a8cb900bd6 100644 --- a/src/providers/wfs/qgswfsdataitems.cpp +++ b/src/providers/wfs/qgswfsdataitems.cpp @@ -114,7 +114,7 @@ void QgsWfsConnectionItem::editConnection() if ( nc.exec() ) { // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } } @@ -122,7 +122,7 @@ void QgsWfsConnectionItem::deleteConnection() { QgsWfsConnection::deleteConnection( mName ); // the parent should be updated - mParent->refresh(); + mParent->refreshConnections(); } #endif @@ -187,7 +187,7 @@ void QgsWfsRootItem::newConnection() if ( nc.exec() ) { - refresh(); + refreshConnections(); } } #endif diff --git a/src/providers/wfs/qgswfssourceselect.cpp b/src/providers/wfs/qgswfssourceselect.cpp index f60aa507d38..01c1b04cd3c 100644 --- a/src/providers/wfs/qgswfssourceselect.cpp +++ b/src/providers/wfs/qgswfssourceselect.cpp @@ -49,14 +49,13 @@ enum }; QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QDialog( parent, fl ) + : QgsSourceSelect( parent, fl, widgetMode ) , mCapabilities( nullptr ) , mSQLComposerDialog( nullptr ) - , mWidgetMode( widgetMode ) { setupUi( this ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { // For some obscure reason hiding does not work! // buttonBox->button( QDialogButtonBox::Close )->hide(); @@ -206,6 +205,11 @@ QString QgsWFSSourceSelect::getPreferredCrs( const QSet &crsSet ) const return *( crsSet.constBegin() ); } +void QgsWFSSourceSelect::refresh() +{ + populateConnectionList(); +} + void QgsWFSSourceSelect::capabilitiesReplyFinished() { btnConnect->setEnabled( true ); @@ -404,7 +408,7 @@ void QgsWFSSourceSelect::addLayer() emit addWfsLayer( mUri, layerName ); } - if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( ! mHoldDialogOpen->isChecked() && QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/wfs/qgswfssourceselect.h b/src/providers/wfs/qgswfssourceselect.h index 39800720fa8..84bd81ac1c5 100644 --- a/src/providers/wfs/qgswfssourceselect.h +++ b/src/providers/wfs/qgswfssourceselect.h @@ -22,6 +22,7 @@ #include "qgscontexthelp.h" #include "qgswfscapabilities.h" #include "qgsproviderregistry.h" +#include "qgssourceselect.h" #include #include @@ -42,18 +43,17 @@ class QgsWFSItemDelegate : public QItemDelegate }; -class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase +class QgsWFSSourceSelect: public QgsSourceSelect, private Ui::QgsWFSSourceSelectBase { Q_OBJECT public: - QgsWFSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ); + QgsWFSSourceSelect( QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); ~QgsWFSSourceSelect(); signals: void addWfsLayer( const QString &uri, const QString &layerName ); - void connectionsChanged(); private: QgsWFSSourceSelect(); //default constructor is forbidden @@ -73,8 +73,6 @@ class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase QgsWfsCapabilities::Capabilities mCaps; QModelIndex mSQLIndex; QgsSQLComposerDialog *mSQLComposerDialog = nullptr; - //! Embedded mode, without 'Close' - QgsProviderRegistry::WidgetMode mWidgetMode = QgsProviderRegistry::WidgetMode::None; /** Returns the best suited CRS from a set of authority ids 1. project CRS if contained in the set @@ -83,6 +81,11 @@ class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase \returns the authority id of the crs or an empty string in case of error*/ QString getPreferredCrs( const QSet &crsSet ) const; + public slots: + + //! Triggered when the provider's connections need to be refreshed + void refresh( ) override; + private slots: void addEntryToServerList(); void modifyEntryOfServerList(); From c4e26d72c0c0f60ad2278492d499b4da5835f75a Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 17 Jul 2017 10:16:12 +0200 Subject: [PATCH 063/266] Renamed base class to QgsAbstractDataSourceWidget --- python/gui/gui_auto.sip | 2 +- ...eselect.sip => qgsabstractdatasourcewidget.sip} | 12 ++++++------ python/gui/qgsarcgisservicesourceselect.sip | 2 +- python/gui/qgsowssourceselect.sip | 2 +- src/gui/CMakeLists.txt | 6 +++--- ...eselect.cpp => qgsabstractdatasourcewidget.cpp} | 8 ++++---- ...ourceselect.h => qgsabstractdatasourcewidget.h} | 14 +++++++------- src/gui/qgsarcgisservicesourceselect.cpp | 10 +++++----- src/gui/qgsarcgisservicesourceselect.h | 4 ++-- src/gui/qgsowssourceselect.cpp | 6 +++--- src/gui/qgsowssourceselect.h | 4 ++-- src/providers/db2/qgsdb2sourceselect.cpp | 6 +++--- src/providers/db2/qgsdb2sourceselect.h | 4 ++-- .../delimitedtext/qgsdelimitedtextsourceselect.cpp | 6 +++--- .../delimitedtext/qgsdelimitedtextsourceselect.h | 4 ++-- src/providers/mssql/qgsmssqlsourceselect.cpp | 6 +++--- src/providers/mssql/qgsmssqlsourceselect.h | 4 ++-- src/providers/oracle/qgsoraclesourceselect.cpp | 4 ++-- src/providers/oracle/qgsoraclesourceselect.h | 4 ++-- src/providers/postgres/qgspgsourceselect.cpp | 8 ++++---- src/providers/postgres/qgspgsourceselect.h | 4 ++-- .../spatialite/qgsspatialitesourceselect.cpp | 6 +++--- .../spatialite/qgsspatialitesourceselect.h | 4 ++-- .../virtual/qgsvirtuallayersourceselect.cpp | 6 +++--- .../virtual/qgsvirtuallayersourceselect.h | 4 ++-- src/providers/wfs/qgswfssourceselect.cpp | 6 +++--- src/providers/wfs/qgswfssourceselect.h | 4 ++-- src/providers/wms/qgswmssourceselect.cpp | 6 +++--- src/providers/wms/qgswmssourceselect.h | 4 ++-- 29 files changed, 80 insertions(+), 80 deletions(-) rename python/gui/{qgssourceselect.sip => qgsabstractdatasourcewidget.sip} (77%) rename src/gui/{qgssourceselect.cpp => qgsabstractdatasourcewidget.cpp} (76%) rename src/gui/{qgssourceselect.h => qgsabstractdatasourcewidget.h} (78%) diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 10083e32bc7..ffa91b9c1aa 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -21,7 +21,7 @@ %Include qgsbrowserdockwidget.sip %Include qgsvertexmarker.sip %Include qgsfiledownloader.sip -%Include qgssourceselect.sip +%Include qgsabstractdatasourcewidget.sip %Include attributetable/qgsfeaturemodel.sip %Include auth/qgsauthauthoritieseditor.sip %Include auth/qgsauthcertificateinfo.sip diff --git a/python/gui/qgssourceselect.sip b/python/gui/qgsabstractdatasourcewidget.sip similarity index 77% rename from python/gui/qgssourceselect.sip rename to python/gui/qgsabstractdatasourcewidget.sip index 43af0d7bc92..549fbec2392 100644 --- a/python/gui/qgssourceselect.sip +++ b/python/gui/qgsabstractdatasourcewidget.sip @@ -1,7 +1,7 @@ /************************************************************************ * This file has been generated automatically from * * * - * src/gui/qgssourceselect.h * + * src/gui/qgsabstractdatasourcewidget.h * * * * Do not edit manually ! Edit header and run scripts/sipify.pl again * ************************************************************************/ @@ -10,7 +10,7 @@ -class QgsSourceSelect : QDialog +class QgsAbstractDataSourceWidget : QDialog { %Docstring Abstract base Data Source Widget to create connections and add layers @@ -21,16 +21,16 @@ class QgsSourceSelect : QDialog %End %TypeHeaderCode -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" %End public: - QgsSourceSelect( QWidget *parent /TransferThis/ = 0, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + QgsAbstractDataSourceWidget( QWidget *parent /TransferThis/ = 0, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); %Docstring Constructor %End - virtual ~QgsSourceSelect( ) = 0; + virtual ~QgsAbstractDataSourceWidget( ) = 0; %Docstring Pure Virtual Destructor %End @@ -60,7 +60,7 @@ Emitted when the provider's connections have changed /************************************************************************ * This file has been generated automatically from * * * - * src/gui/qgssourceselect.h * + * src/gui/qgsabstractdatasourcewidget.h * * * * Do not edit manually ! Edit header and run scripts/sipify.pl again * ************************************************************************/ diff --git a/python/gui/qgsarcgisservicesourceselect.sip b/python/gui/qgsarcgisservicesourceselect.sip index c0e74f96f0e..f49a3f9e2d1 100644 --- a/python/gui/qgsarcgisservicesourceselect.sip +++ b/python/gui/qgsarcgisservicesourceselect.sip @@ -11,7 +11,7 @@ -class QgsArcGisServiceSourceSelect : QgsSourceSelect, protected Ui::QgsArcGisServiceSourceSelectBase +class QgsArcGisServiceSourceSelect : QgsAbstractDataSourceWidget, protected Ui::QgsArcGisServiceSourceSelectBase { %Docstring Base class for listing ArcGis layers available from a remote service. diff --git a/python/gui/qgsowssourceselect.sip b/python/gui/qgsowssourceselect.sip index f30cb0633f8..45b1009822f 100644 --- a/python/gui/qgsowssourceselect.sip +++ b/python/gui/qgsowssourceselect.sip @@ -11,7 +11,7 @@ -class QgsOWSSourceSelect : QgsSourceSelect, protected Ui::QgsOWSSourceSelectBase +class QgsOWSSourceSelect : QgsAbstractDataSourceWidget, protected Ui::QgsOWSSourceSelectBase { %Docstring Dialog to create connections and add layers WCS etc. diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e96328d056f..2739032c7ed 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -346,7 +346,7 @@ SET(QGIS_GUI_SRCS qgsvertexmarker.cpp qgsfiledownloader.cpp qgsdatasourcemanagerdialog.cpp - qgssourceselect.cpp + qgsabstractdatasourcewidget.cpp ) SET(QGIS_GUI_MOC_HDRS @@ -499,7 +499,7 @@ SET(QGIS_GUI_MOC_HDRS qgsvariableeditorwidget.h qgsfiledownloader.h qgsdatasourcemanagerdialog.h - qgssourceselect.h + qgsabstractdatasourcewidget.h ogr/qgsopenvectorlayerdialog.h ogr/qgsnewogrconnection.h @@ -706,7 +706,7 @@ SET(QGIS_GUI_HDRS qgsvertexmarker.h qgsfiledownloader.h qgsdatasourcemanagerdialog.h - qgssourceselect.h + qgsabstractdatasourcewidget.h ogr/qgsopenvectorlayerdialog.h ogr/qgsogrhelperfunctions.h diff --git a/src/gui/qgssourceselect.cpp b/src/gui/qgsabstractdatasourcewidget.cpp similarity index 76% rename from src/gui/qgssourceselect.cpp rename to src/gui/qgsabstractdatasourcewidget.cpp index 9308098a391..1e714a9ab30 100644 --- a/src/gui/qgssourceselect.cpp +++ b/src/gui/qgsabstractdatasourcewidget.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - qgssourceselect.cpp - base class for source selector widgets + qgsabstractdatasourcewidget.cpp - base class for source selector widgets ------------------- begin : 10 July 2017 original : (C) 2017 by Alessandro Pasotti email : apasotti at boundlessgeo dot com @@ -15,16 +15,16 @@ * * ***************************************************************************/ -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" -QgsSourceSelect::QgsSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): +QgsAbstractDataSourceWidget::QgsAbstractDataSourceWidget( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): QDialog( parent, fl ), mWidgetMode( widgetMode ) { } -QgsSourceSelect::~QgsSourceSelect() +QgsAbstractDataSourceWidget::~QgsAbstractDataSourceWidget() { } diff --git a/src/gui/qgssourceselect.h b/src/gui/qgsabstractdatasourcewidget.h similarity index 78% rename from src/gui/qgssourceselect.h rename to src/gui/qgsabstractdatasourcewidget.h index 030bbc5b8d1..339ff9c07f6 100644 --- a/src/gui/qgssourceselect.h +++ b/src/gui/qgsabstractdatasourcewidget.h @@ -1,5 +1,5 @@ /*************************************************************************** - qgssourceselect.h - base class for source selector widgets + qgsabstractdatasourcewidget.h - base class for source selector widgets ------------------- begin : 10 July 2017 original : (C) 2017 by Alessandro Pasotti email : apasotti at boundlessgeo dot com @@ -15,8 +15,8 @@ * * ***************************************************************************/ -#ifndef QGSSOURCESELECT_H -#define QGSSOURCESELECT_H +#ifndef QGSABSTRACTDATASOURCEWIDGET_H +#define QGSABSTRACTDATASOURCEWIDGET_H #include "qgis_sip.h" #include "qgis.h" #include "qgis_gui.h" @@ -33,17 +33,17 @@ * and add layers. * \since QGIS 3.0 */ -class GUI_EXPORT QgsSourceSelect : public QDialog +class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog { Q_OBJECT public: //! Constructor - QgsSourceSelect( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + QgsAbstractDataSourceWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); //! Pure Virtual Destructor - virtual ~QgsSourceSelect( ) = 0; + virtual ~QgsAbstractDataSourceWidget( ) = 0; //! Return the widget mode QgsProviderRegistry::WidgetMode widgetMode( ) { return mWidgetMode; } @@ -64,4 +64,4 @@ class GUI_EXPORT QgsSourceSelect : public QDialog QgsProviderRegistry::WidgetMode mWidgetMode; }; -#endif // QGSSOURCESELECT_H +#endif // QGSABSTRACTDATASOURCEWIDGET_H diff --git a/src/gui/qgsarcgisservicesourceselect.cpp b/src/gui/qgsarcgisservicesourceselect.cpp index 59cba337066..650d6141e50 100644 --- a/src/gui/qgsarcgisservicesourceselect.cpp +++ b/src/gui/qgsarcgisservicesourceselect.cpp @@ -40,17 +40,17 @@ /** * Item delegate with tweaked sizeHint. */ -class QgsSourceSelectItemDelegate : public QItemDelegate +class QgsAbstractDataSourceWidgetItemDelegate : public QItemDelegate { public: //! Constructor - QgsSourceSelectItemDelegate( QObject *parent = 0 ) : QItemDelegate( parent ) { } + QgsAbstractDataSourceWidgetItemDelegate( QObject *parent = 0 ) : QItemDelegate( parent ) { } QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const override; }; QgsArcGisServiceSourceSelect::QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): - QgsSourceSelect( parent, fl, widgetMode ), + QgsAbstractDataSourceWidget( parent, fl, widgetMode ), mServiceName( serviceName ), mServiceType( serviceType ), mBuildQueryButton( 0 ), @@ -81,7 +81,7 @@ QgsArcGisServiceSourceSelect::QgsArcGisServiceSourceSelect( const QString &servi mProjectionSelector = new QgsProjectionSelectionDialog( this ); mProjectionSelector->setMessage( QString() ); - treeView->setItemDelegate( new QgsSourceSelectItemDelegate( treeView ) ); + treeView->setItemDelegate( new QgsAbstractDataSourceWidgetItemDelegate( treeView ) ); QgsSettings settings; restoreGeometry( settings.value( QStringLiteral( "Windows/SourceSelectDialog/geometry" ) ).toByteArray() ); @@ -458,7 +458,7 @@ void QgsArcGisServiceSourceSelect::filterChanged( const QString &text ) mModelProxy->sort( mModelProxy->sortColumn(), mModelProxy->sortOrder() ); } -QSize QgsSourceSelectItemDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const +QSize QgsAbstractDataSourceWidgetItemDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const { QVariant indexData = index.data( Qt::DisplayRole ); if ( indexData.isNull() ) diff --git a/src/gui/qgsarcgisservicesourceselect.h b/src/gui/qgsarcgisservicesourceselect.h index b9bec3c4fcb..dfa19e79e91 100644 --- a/src/gui/qgsarcgisservicesourceselect.h +++ b/src/gui/qgsarcgisservicesourceselect.h @@ -30,7 +30,7 @@ #include "ui_qgsarcgisservicesourceselectbase.h" #include "qgsrectangle.h" #include "qgscoordinatereferencesystem.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include "qgis_gui.h" class QStandardItemModel; @@ -41,7 +41,7 @@ class QgsOwsConnection; /** * Base class for listing ArcGis layers available from a remote service. */ -class GUI_EXPORT QgsArcGisServiceSourceSelect : public QgsSourceSelect, protected Ui::QgsArcGisServiceSourceSelectBase +class GUI_EXPORT QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidget, protected Ui::QgsArcGisServiceSourceSelectBase { Q_OBJECT diff --git a/src/gui/qgsowssourceselect.cpp b/src/gui/qgsowssourceselect.cpp index 3e2ed8dcb71..d2a93e30715 100644 --- a/src/gui/qgsowssourceselect.cpp +++ b/src/gui/qgsowssourceselect.cpp @@ -55,13 +55,13 @@ #include QgsOWSSourceSelect::QgsOWSSourceSelect( const QString &service, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mService( service ) , mCurrentTileset( nullptr ) { setupUi( this ); - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); } @@ -88,7 +88,7 @@ QgsOWSSourceSelect::QgsOWSSourceSelect( const QString &service, QWidget *parent, // 'Prefer network' is the default noted in the combobox's tool tip mCacheComboBox->setCurrentIndex( mCacheComboBox->findData( QNetworkRequest::PreferNetwork ) ); - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { connect( mAddButton, &QAbstractButton::clicked, this, &QgsOWSSourceSelect::addClicked ); //set the current project CRS if available diff --git a/src/gui/qgsowssourceselect.h b/src/gui/qgsowssourceselect.h index 0d3732b57d3..50358e2dd48 100644 --- a/src/gui/qgsowssourceselect.h +++ b/src/gui/qgsowssourceselect.h @@ -26,7 +26,7 @@ #include "qgsguiutils.h" #include "qgscontexthelp.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -49,7 +49,7 @@ class QDomElement; * The user can then connect and add * layers from the WCS server to the map canvas. */ -class GUI_EXPORT QgsOWSSourceSelect : public QgsSourceSelect, protected Ui::QgsOWSSourceSelectBase +class GUI_EXPORT QgsOWSSourceSelect : public QgsAbstractDataSourceWidget, protected Ui::QgsOWSSourceSelectBase { Q_OBJECT diff --git a/src/providers/db2/qgsdb2sourceselect.cpp b/src/providers/db2/qgsdb2sourceselect.cpp index 11b697b6f1b..bf8329df9fb 100644 --- a/src/providers/db2/qgsdb2sourceselect.cpp +++ b/src/providers/db2/qgsdb2sourceselect.cpp @@ -117,7 +117,7 @@ void QgsDb2SourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMod } QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mColumnTypeThread( NULL ) , mUseEstimatedMetadata( false ) { @@ -125,7 +125,7 @@ QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs setWindowTitle( tr( "Add Db2 Table(s)" ) ); - if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -138,7 +138,7 @@ QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsDb2SourceSelect::addTables ); diff --git a/src/providers/db2/qgsdb2sourceselect.h b/src/providers/db2/qgsdb2sourceselect.h index 2f7dba09d7f..81a9db2c8a3 100644 --- a/src/providers/db2/qgsdb2sourceselect.h +++ b/src/providers/db2/qgsdb2sourceselect.h @@ -25,7 +25,7 @@ #include "qgsdb2tablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -88,7 +88,7 @@ class QgsDb2GeomColumnTypeThread : public QThread * for Db2 databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsDb2SourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase +class QgsDb2SourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDbSourceSelectBase { Q_OBJECT diff --git a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp index 95cafa509c2..71dfb30199e 100644 --- a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp +++ b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp @@ -35,7 +35,7 @@ const int MAX_SAMPLE_LENGTH = 200; QgsDelimitedTextSourceSelect::QgsDelimitedTextSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mFile( new QgsDelimitedTextFile() ) , mExampleRowCount( 20 ) , mBadRowCount( 0 ) @@ -48,7 +48,7 @@ QgsDelimitedTextSourceSelect::QgsDelimitedTextSourceSelect( QWidget *parent, Qt: QgsSettings settings; restoreGeometry( settings.value( mPluginKey + "/geometry" ).toByteArray() ); - if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Cancel ) ); buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add" ) ); @@ -199,7 +199,7 @@ void QgsDelimitedTextSourceSelect::on_buttonBox_accepted() // add the layer to the map emit addVectorLayer( QString::fromAscii( url.toEncoded() ), txtLayerName->text(), QStringLiteral( "delimitedtext" ) ); - if ( QgsSourceSelect::widgetMode() == QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h index 78f5399d904..c7441d7096b 100644 --- a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h +++ b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.h @@ -19,7 +19,7 @@ #include "qgshelp.h" #include "qgsguiutils.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" class QButtonGroup; class QgisInterface; @@ -28,7 +28,7 @@ class QgsDelimitedTextFile; /** * \class QgsDelimitedTextSourceSelect */ -class QgsDelimitedTextSourceSelect : public QgsSourceSelect, private Ui::QgsDelimitedTextSourceSelectBase +class QgsDelimitedTextSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDelimitedTextSourceSelectBase { Q_OBJECT diff --git a/src/providers/mssql/qgsmssqlsourceselect.cpp b/src/providers/mssql/qgsmssqlsourceselect.cpp index f89d105ad12..3756f7c6c1b 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.cpp +++ b/src/providers/mssql/qgsmssqlsourceselect.cpp @@ -119,7 +119,7 @@ void QgsMssqlSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemM } QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mColumnTypeThread( nullptr ) , mUseEstimatedMetadata( false ) { @@ -127,7 +127,7 @@ QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, setWindowTitle( tr( "Add MSSQL Table(s)" ) ); - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -140,7 +140,7 @@ QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsMssqlSourceSelect::addTables ); diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h index 1142dfe8b07..c4580481ebf 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.h +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -23,7 +23,7 @@ #include "qgsmssqltablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -58,7 +58,7 @@ class QgsMssqlSourceSelectDelegate : public QItemDelegate * for MSSQL databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsMssqlSourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase +class QgsMssqlSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDbSourceSelectBase { Q_OBJECT diff --git a/src/providers/oracle/qgsoraclesourceselect.cpp b/src/providers/oracle/qgsoraclesourceselect.cpp index 9918491e553..52cc9db9446 100644 --- a/src/providers/oracle/qgsoraclesourceselect.cpp +++ b/src/providers/oracle/qgsoraclesourceselect.cpp @@ -167,13 +167,13 @@ void QgsOracleSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItem } QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mColumnTypeThread( 0 ) , mIsConnected( false ) { setupUi( this ); - if ( QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::Embedded ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::Embedded ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); diff --git a/src/providers/oracle/qgsoraclesourceselect.h b/src/providers/oracle/qgsoraclesourceselect.h index ec5083039c9..cf987a070f6 100644 --- a/src/providers/oracle/qgsoraclesourceselect.h +++ b/src/providers/oracle/qgsoraclesourceselect.h @@ -24,7 +24,7 @@ #include "qgshelp.h" #include "qgsoracleconnpool.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -82,7 +82,7 @@ class QgsOracleSourceSelectDelegate : public QItemDelegate * for Oracle databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsOracleSourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase +class QgsOracleSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDbSourceSelectBase { Q_OBJECT diff --git a/src/providers/postgres/qgspgsourceselect.cpp b/src/providers/postgres/qgspgsourceselect.cpp index e4bcb065462..5b64631ba7b 100644 --- a/src/providers/postgres/qgspgsourceselect.cpp +++ b/src/providers/postgres/qgspgsourceselect.cpp @@ -194,13 +194,13 @@ void QgsPgSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMode } QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mColumnTypeThread( nullptr ) , mUseEstimatedMetadata( false ) { setupUi( this ); - if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -217,7 +217,7 @@ QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsPr mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsPgSourceSelect::addTables ); @@ -508,7 +508,7 @@ void QgsPgSourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "postgres" ) ); - if ( ! mHoldDialogOpen->isChecked() && QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) + if ( ! mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/postgres/qgspgsourceselect.h b/src/providers/postgres/qgspgsourceselect.h index 5658cfa6a34..5ad750f5e6c 100644 --- a/src/providers/postgres/qgspgsourceselect.h +++ b/src/providers/postgres/qgspgsourceselect.h @@ -24,7 +24,7 @@ #include "qgspgtablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -59,7 +59,7 @@ class QgsPgSourceSelectDelegate : public QItemDelegate * for PostGIS enabled PostgreSQL databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsPgSourceSelect : public QgsSourceSelect, private Ui::QgsDbSourceSelectBase +class QgsPgSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDbSourceSelectBase { Q_OBJECT diff --git a/src/providers/spatialite/qgsspatialitesourceselect.cpp b/src/providers/spatialite/qgsspatialitesourceselect.cpp index 2de7fc938eb..4c7447cc128 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.cpp +++ b/src/providers/spatialite/qgsspatialitesourceselect.cpp @@ -41,7 +41,7 @@ email : a.furieri@lqt.it #endif QgsSpatiaLiteSourceSelect::QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): - QgsSourceSelect( parent, fl, widgetMode ) + QgsAbstractDataSourceWidget( parent, fl, widgetMode ) { setupUi( this ); @@ -66,7 +66,7 @@ QgsSpatiaLiteSourceSelect::QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::Windo connect( mBuildQueryButton, &QAbstractButton::clicked, this, &QgsSpatiaLiteSourceSelect::buildQuery ); mBuildQueryButton->setEnabled( false ); - if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -417,7 +417,7 @@ void QgsSpatiaLiteSourceSelect::addTables() else { emit addDatabaseLayers( m_selectedTables, QStringLiteral( "spatialite" ) ); - if ( QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None && ! mHoldDialogOpen->isChecked() ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None && ! mHoldDialogOpen->isChecked() ) { accept(); } diff --git a/src/providers/spatialite/qgsspatialitesourceselect.h b/src/providers/spatialite/qgsspatialitesourceselect.h index 607fd1af2f3..9be162a7962 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.h +++ b/src/providers/spatialite/qgsspatialitesourceselect.h @@ -22,7 +22,7 @@ #include "qgsspatialitetablemodel.h" #include "qgshelp.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -42,7 +42,7 @@ class QPushButton; * for SpatiaLite/SQLite databases. The user can then connect and add * tables from the database to the map canvas. */ -class QgsSpatiaLiteSourceSelect: public QgsSourceSelect, private Ui::QgsDbSourceSelectBase +class QgsSpatiaLiteSourceSelect: public QgsAbstractDataSourceWidget, private Ui::QgsDbSourceSelectBase { Q_OBJECT diff --git a/src/providers/virtual/qgsvirtuallayersourceselect.cpp b/src/providers/virtual/qgsvirtuallayersourceselect.cpp index 2e20769e5ce..e828f69f2f1 100644 --- a/src/providers/virtual/qgsvirtuallayersourceselect.cpp +++ b/src/providers/virtual/qgsvirtuallayersourceselect.cpp @@ -38,13 +38,13 @@ email : hugo dot mercier at oslandia dot com #include QgsVirtualLayerSourceSelect::QgsVirtualLayerSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mSrid( 0 ) , mTreeView( nullptr ) { setupUi( this ); - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Cancel ) ); buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add" ) ); @@ -380,7 +380,7 @@ void QgsVirtualLayerSourceSelect::on_buttonBox_accepted() { emit addVectorLayer( def.toString(), layerName, QStringLiteral( "virtual" ) ); } - if ( QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) { accept( ); } diff --git a/src/providers/virtual/qgsvirtuallayersourceselect.h b/src/providers/virtual/qgsvirtuallayersourceselect.h index 4d5d391f67c..cf45156887c 100644 --- a/src/providers/virtual/qgsvirtuallayersourceselect.h +++ b/src/providers/virtual/qgsvirtuallayersourceselect.h @@ -24,14 +24,14 @@ email : hugo dot mercier at oslandia dot com #include "qgsguiutils.h" #include #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" class QgsVectorLayer; class QMainWindow; class QgsEmbeddedLayerSelectDialog; class QgsLayerTreeView; -class QgsVirtualLayerSourceSelect : public QgsSourceSelect, private Ui::QgsVirtualLayerSourceSelectBase +class QgsVirtualLayerSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsVirtualLayerSourceSelectBase { Q_OBJECT diff --git a/src/providers/wfs/qgswfssourceselect.cpp b/src/providers/wfs/qgswfssourceselect.cpp index 01c1b04cd3c..dfb9d1f52e9 100644 --- a/src/providers/wfs/qgswfssourceselect.cpp +++ b/src/providers/wfs/qgswfssourceselect.cpp @@ -49,13 +49,13 @@ enum }; QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mCapabilities( nullptr ) , mSQLComposerDialog( nullptr ) { setupUi( this ); - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) { // For some obscure reason hiding does not work! // buttonBox->button( QDialogButtonBox::Close )->hide(); @@ -408,7 +408,7 @@ void QgsWFSSourceSelect::addLayer() emit addWfsLayer( mUri, layerName ); } - if ( ! mHoldDialogOpen->isChecked() && QgsSourceSelect::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) + if ( ! mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/wfs/qgswfssourceselect.h b/src/providers/wfs/qgswfssourceselect.h index 84bd81ac1c5..c775711c00b 100644 --- a/src/providers/wfs/qgswfssourceselect.h +++ b/src/providers/wfs/qgswfssourceselect.h @@ -22,7 +22,7 @@ #include "qgscontexthelp.h" #include "qgswfscapabilities.h" #include "qgsproviderregistry.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -43,7 +43,7 @@ class QgsWFSItemDelegate : public QItemDelegate }; -class QgsWFSSourceSelect: public QgsSourceSelect, private Ui::QgsWFSSourceSelectBase +class QgsWFSSourceSelect: public QgsAbstractDataSourceWidget, private Ui::QgsWFSSourceSelectBase { Q_OBJECT diff --git a/src/providers/wms/qgswmssourceselect.cpp b/src/providers/wms/qgswmssourceselect.cpp index 20db899c3f9..b9ac53954e3 100644 --- a/src/providers/wms/qgswmssourceselect.cpp +++ b/src/providers/wms/qgswmssourceselect.cpp @@ -55,13 +55,13 @@ #include QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsSourceSelect( parent, fl, widgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) , mDefaultCRS( GEO_EPSG_CRS_AUTHID ) , mCurrentTileset( nullptr ) { setupUi( this ); - if ( QgsSourceSelect::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) { // For some obscure reason hiding does not work! // buttonBox->button( QDialogButtonBox::Close )->hide(); @@ -80,7 +80,7 @@ QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs mImageFormatGroup = new QButtonGroup; - if ( QgsSourceSelect::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsWMSSourceSelect::addClicked ); diff --git a/src/providers/wms/qgswmssourceselect.h b/src/providers/wms/qgswmssourceselect.h index 5fc06677bef..ed1fd698b2e 100644 --- a/src/providers/wms/qgswmssourceselect.h +++ b/src/providers/wms/qgswmssourceselect.h @@ -23,7 +23,7 @@ #include "qgshelp.h" #include "qgsproviderregistry.h" #include "qgswmsprovider.h" -#include "qgssourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include #include @@ -43,7 +43,7 @@ class QgsWmsCapabilities; * The user can then connect and add * layers from the WMS server to the map canvas. */ -class QgsWMSSourceSelect : public QgsSourceSelect, private Ui::QgsWMSSourceSelectBase +class QgsWMSSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsWMSSourceSelectBase { Q_OBJECT From f6c8ef3ad1168b78fb1f6df3733ec2b020da566c Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 17 Jul 2017 10:23:33 +0200 Subject: [PATCH 064/266] Complete documentation for connectionsChanged signals --- python/core/qgsdataitem.sip | 2 +- python/gui/qgsabstractdatasourcewidget.sip | 2 +- src/core/qgsdataitem.h | 3 +++ src/gui/qgsabstractdatasourcewidget.h | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/python/core/qgsdataitem.sip b/python/core/qgsdataitem.sip index 8aefa8bb408..4bdb80f2432 100644 --- a/python/core/qgsdataitem.sip +++ b/python/core/qgsdataitem.sip @@ -320,7 +320,7 @@ Refresh connections: update GUI and emit signal void stateChanged( QgsDataItem *item, QgsDataItem::State oldState ); void connectionsChanged( ); %Docstring -Emitted when the provider's connections of the child items have changed +open browsers %End protected slots: diff --git a/python/gui/qgsabstractdatasourcewidget.sip b/python/gui/qgsabstractdatasourcewidget.sip index 549fbec2392..301ddef3cbe 100644 --- a/python/gui/qgsabstractdatasourcewidget.sip +++ b/python/gui/qgsabstractdatasourcewidget.sip @@ -52,7 +52,7 @@ The default implementation does nothing void connectionsChanged(); %Docstring -Emitted when the provider's connections have changed +This signal is normally forwarded the app and used to refresh browser items %End }; diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 083daaa8c36..2ada4a11b25 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -296,6 +296,9 @@ class CORE_EXPORT QgsDataItem : public QObject void dataChanged( QgsDataItem *item ); void stateChanged( QgsDataItem *item, QgsDataItem::State oldState ); //! Emitted when the provider's connections of the child items have changed + //! This signal is normally forwarded to the app in order to refresh the connection + //! item in the provider dialogs and to refresh the connection items in the other + //! open browsers void connectionsChanged( ); protected slots: diff --git a/src/gui/qgsabstractdatasourcewidget.h b/src/gui/qgsabstractdatasourcewidget.h index 339ff9c07f6..349ca26580e 100644 --- a/src/gui/qgsabstractdatasourcewidget.h +++ b/src/gui/qgsabstractdatasourcewidget.h @@ -57,6 +57,7 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog signals: //! Emitted when the provider's connections have changed + //! This signal is normally forwarded the app and used to refresh browser items void connectionsChanged(); private: From c2cad99d6b18f22a0dd3981a372b9dbeb07b97af Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 19:44:33 +1000 Subject: [PATCH 065/266] Add note about throwing QgsProcessingException to cancel algorithm execution --- python/core/processing/qgsprocessingalgorithm.sip | 5 +++++ src/core/processing/qgsprocessingalgorithm.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index eb023405b4d..9f340eaedbf 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -833,6 +833,11 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm The provided ``feedback`` object can be used to push messages to the log and for giving feedback to users. Note that handling of progress reports and algorithm cancelation is handled by the base class and subclasses do not need to reimplement this logic. + + Algorithms can throw a QgsProcessingException if a fatal error occurred which should + prevent the algorithm execution from continuing. This can be annoying for users though as it + can break valid model execution - so use with extreme caution, and consider using + ``feedback`` to instead report non-fatal processing failures for features instead. :rtype: bool %End diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 00e8c46c826..f947d07ebe8 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -804,6 +804,11 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor * The provided \a feedback object can be used to push messages to the log and for giving feedback * to users. Note that handling of progress reports and algorithm cancelation is handled by * the base class and subclasses do not need to reimplement this logic. + * + * Algorithms can throw a QgsProcessingException if a fatal error occurred which should + * prevent the algorithm execution from continuing. This can be annoying for users though as it + * can break valid model execution - so use with extreme caution, and consider using + * \a feedback to instead report non-fatal processing failures for features instead. */ virtual bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; From 73b66fa1f5003aae7e9bca96c703eadf88f838d0 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 17 Jul 2017 16:59:33 +0200 Subject: [PATCH 066/266] Homogenize signals from different source select dialogs, move to base class --- python/gui/gui_auto.sip | 1 - python/gui/qgsabstractdatasourcewidget.sip | 67 +++++++++++--- python/gui/qgsarcgisservicesourceselect.sip | 91 ------------------- python/gui/qgsowssourceselect.sip | 6 +- src/app/qgisapp.cpp | 2 +- src/gui/qgsabstractdatasourcewidget.cpp | 27 +++++- src/gui/qgsabstractdatasourcewidget.h | 63 ++++++++++--- src/gui/qgsarcgisservicesourceselect.cpp | 2 +- src/gui/qgsarcgisservicesourceselect.h | 16 ++-- src/gui/qgsdatasourcemanagerdialog.cpp | 66 ++++++-------- src/gui/qgsdatasourcemanagerdialog.h | 27 +++--- src/gui/qgsowssourceselect.h | 6 +- src/providers/db2/qgsdb2sourceselect.h | 1 - src/providers/mssql/qgsmssqlsourceselect.h | 1 - src/providers/postgres/qgspgsourceselect.h | 1 - .../spatialite/qgsspatialitesourceselect.h | 5 - src/providers/wfs/qgswfssourceselect.cpp | 2 +- src/providers/wfs/qgswfssourceselect.h | 3 - src/providers/wms/qgswmssourceselect.h | 5 - 19 files changed, 190 insertions(+), 202 deletions(-) delete mode 100644 python/gui/qgsarcgisservicesourceselect.sip diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index ffa91b9c1aa..852a5807728 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -158,7 +158,6 @@ %Include qgsoptionswidgetfactory.sip %Include qgsorderbydialog.sip %Include qgsowssourceselect.sip -%Include qgsarcgisservicesourceselect.sip %Include qgspanelwidget.sip %Include qgspanelwidgetstack.sip %Include qgspasswordlineedit.sip diff --git a/python/gui/qgsabstractdatasourcewidget.sip b/python/gui/qgsabstractdatasourcewidget.sip index 301ddef3cbe..e50d3fbf469 100644 --- a/python/gui/qgsabstractdatasourcewidget.sip +++ b/python/gui/qgsabstractdatasourcewidget.sip @@ -14,7 +14,7 @@ class QgsAbstractDataSourceWidget : QDialog { %Docstring Abstract base Data Source Widget to create connections and add layers - This class must provide common functionality and the interface for all + This class provides common functionality and the interface for all source select dialogs used by data providers to configure data sources and add layers. .. versionadded:: 3.0 @@ -25,34 +25,79 @@ class QgsAbstractDataSourceWidget : QDialog %End public: - QgsAbstractDataSourceWidget( QWidget *parent /TransferThis/ = 0, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + ~QgsAbstractDataSourceWidget( ); %Docstring -Constructor +Destructor %End - virtual ~QgsAbstractDataSourceWidget( ) = 0; -%Docstring -Pure Virtual Destructor -%End - - QgsProviderRegistry::WidgetMode widgetMode( ); + QgsProviderRegistry::WidgetMode widgetMode( ) const; %Docstring Return the widget mode :rtype: QgsProviderRegistry.WidgetMode %End + void setCurrentCrs( const QgsCoordinateReferenceSystem &crs ); +%Docstring + Set the current CRS + The CRS is normally the CRS of the map canvas, and it can be used + by the provider dialog to transform the extent and constraint the service +%End + + void setCurrentExtent( const QgsRectangle &extent ); +%Docstring + Set the current extent + The extent is normally the extent of the map canvas, and it can be used + by the provider dialog to constraint the service +%End + + QgsRectangle currentExtent( ) const; +%Docstring +Return the current extent + :rtype: QgsRectangle +%End + + QgsCoordinateReferenceSystem currentCrs( ) const; +%Docstring +Return the current CRS + :rtype: QgsCoordinateReferenceSystem +%End + public slots: virtual void refresh( ); %Docstring -The default implementation does nothing + Triggered when the provider's connections need to be refreshed + The default implementation does nothing %End signals: void connectionsChanged(); %Docstring -This signal is normally forwarded the app and used to refresh browser items + Emitted when the provider's connections have changed + This signal is normally forwarded the app and used to refresh browser items +%End + + void addDatabaseLayers( const QStringList &paths, const QString &providerKey ); +%Docstring +Emitted when a DB layer has been selected for addition +%End + + void addRasterLayer( const QString &rasterLayerPath, const QString &baseName, const QString &providerKey ); +%Docstring +Emitted when a raster layer has been selected for addition +%End + + void addVectorLayer( const QString &uri, const QString &layerName ); +%Docstring +Emitted when a vector layer has been selected for addition +%End + + protected: + + QgsAbstractDataSourceWidget( QWidget *parent /TransferThis/ = 0, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); +%Docstring +Constructor %End }; diff --git a/python/gui/qgsarcgisservicesourceselect.sip b/python/gui/qgsarcgisservicesourceselect.sip deleted file mode 100644 index f49a3f9e2d1..00000000000 --- a/python/gui/qgsarcgisservicesourceselect.sip +++ /dev/null @@ -1,91 +0,0 @@ -/************************************************************************ - * This file has been generated automatically from * - * * - * src/gui/qgsarcgisservicesourceselect.h * - * * - * Do not edit manually ! Edit header and run scripts/sipify.pl again * - ************************************************************************/ - - - - - - -class QgsArcGisServiceSourceSelect : QgsAbstractDataSourceWidget, protected Ui::QgsArcGisServiceSourceSelectBase -{ -%Docstring - Base class for listing ArcGis layers available from a remote service. -%End - -%TypeHeaderCode -#include "qgsarcgisservicesourceselect.h" -%End - public: - enum ServiceType { MapService, FeatureService }; - - QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); -%Docstring -Constructor -%End - - ~QgsArcGisServiceSourceSelect(); - void setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ); -%Docstring -Sets the current extent and CRS. Used to select an appropriate CRS and possibly to retrieve data only in the current extent -%End - - signals: - void addLayer( QString uri, QString typeName ); -%Docstring -Emitted when a layer is added from the dialog -%End - - protected: - - virtual bool connectToService( const QgsOwsConnection &connection ) = 0; -%Docstring -To be implemented in the child class. Called when a new connection is initiated. - :rtype: bool -%End - virtual void buildQuery( const QgsOwsConnection &, const QModelIndex & ); -%Docstring -May be implemented in child classes for services which support customized queries. -%End - virtual QString getLayerURI( const QgsOwsConnection &connection, - const QString &layerTitle, - const QString &layerName, - const QString &crs = QString(), - const QString &filter = QString(), - const QgsRectangle &bBox = QgsRectangle() ) const = 0; -%Docstring -To be implemented in the child class. Constructs an URI for the specified service layer. - :rtype: str -%End - void populateImageEncodings( const QStringList &availableEncodings ); -%Docstring -Updates the UI for the list of available image encodings from the specified list. -%End - QString getSelectedImageEncoding() const; -%Docstring -Returns the selected image encoding. - :rtype: str -%End - - public slots: - - virtual void refresh( ); - -%Docstring -Triggered when the provider's connections need to be refreshed -%End - -}; - - -/************************************************************************ - * This file has been generated automatically from * - * * - * src/gui/qgsarcgisservicesourceselect.h * - * * - * Do not edit manually ! Edit header and run scripts/sipify.pl again * - ************************************************************************/ diff --git a/python/gui/qgsowssourceselect.sip b/python/gui/qgsowssourceselect.sip index 45b1009822f..02b394ef496 100644 --- a/python/gui/qgsowssourceselect.sip +++ b/python/gui/qgsowssourceselect.sip @@ -112,10 +112,7 @@ Stores the selected datasource whenerver it is changed Add some default wms servers to the list %End - signals: - void addRasterLayer( const QString &rasterLayerPath, - const QString &baseName, - const QString &providerKey ); + void on_mDialogButtonBox_helpRequested(); protected: @@ -256,6 +253,7 @@ Returns currently selected cache load control + }; /************************************************************************ diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 6c57310cc0b..3a4e8466664 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1596,7 +1596,7 @@ void QgisApp::dataSourceManager( QString pageName ) { if ( ! mDataSourceManagerDialog ) { - mDataSourceManagerDialog = new QgsDataSourceManagerDialog( mapCanvas( ), this ); + mDataSourceManagerDialog = new QgsDataSourceManagerDialog( this, mapCanvas( ) ); // Forward signals to this connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh ); connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged ); diff --git a/src/gui/qgsabstractdatasourcewidget.cpp b/src/gui/qgsabstractdatasourcewidget.cpp index 1e714a9ab30..b6bab91dca0 100644 --- a/src/gui/qgsabstractdatasourcewidget.cpp +++ b/src/gui/qgsabstractdatasourcewidget.cpp @@ -2,7 +2,8 @@ qgsabstractdatasourcewidget.cpp - base class for source selector widgets ------------------- begin : 10 July 2017 - original : (C) 2017 by Alessandro Pasotti email : apasotti at boundlessgeo dot com + original : (C) 2017 by Alessandro Pasotti + email : apasotti at boundlessgeo dot com ***************************************************************************/ @@ -24,7 +25,27 @@ QgsAbstractDataSourceWidget::QgsAbstractDataSourceWidget( QWidget *parent, Qt::W } -QgsAbstractDataSourceWidget::~QgsAbstractDataSourceWidget() +QgsProviderRegistry::WidgetMode QgsAbstractDataSourceWidget::widgetMode() const { - + return mWidgetMode; +} + +void QgsAbstractDataSourceWidget::setCurrentCrs( const QgsCoordinateReferenceSystem &crs ) +{ + mCurrentCrs = crs; +} + +void QgsAbstractDataSourceWidget::setCurrentExtent( const QgsRectangle &extent ) +{ + mCurrentExtent = extent; +} + +QgsRectangle QgsAbstractDataSourceWidget::currentExtent() const +{ + return mCurrentExtent; +} + +QgsCoordinateReferenceSystem QgsAbstractDataSourceWidget::currentCrs() const +{ + return mCurrentCrs; } diff --git a/src/gui/qgsabstractdatasourcewidget.h b/src/gui/qgsabstractdatasourcewidget.h index 349ca26580e..7b57132f514 100644 --- a/src/gui/qgsabstractdatasourcewidget.h +++ b/src/gui/qgsabstractdatasourcewidget.h @@ -2,7 +2,8 @@ qgsabstractdatasourcewidget.h - base class for source selector widgets ------------------- begin : 10 July 2017 - original : (C) 2017 by Alessandro Pasotti email : apasotti at boundlessgeo dot com + original : (C) 2017 by Alessandro Pasotti + email : apasotti at boundlessgeo dot com ***************************************************************************/ @@ -17,18 +18,20 @@ #ifndef QGSABSTRACTDATASOURCEWIDGET_H #define QGSABSTRACTDATASOURCEWIDGET_H + #include "qgis_sip.h" #include "qgis.h" #include "qgis_gui.h" #include "qgsproviderregistry.h" #include "qgsguiutils.h" - +#include "qgsrectangle.h" +#include "qgscoordinatereferencesystem.h" #include /** \ingroup gui * \brief Abstract base Data Source Widget to create connections and add layers - * This class must provide common functionality and the interface for all + * This class provides common functionality and the interface for all * source select dialogs used by data providers to configure data sources * and add layers. * \since QGIS 3.0 @@ -39,30 +42,64 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog public: - //! Constructor - QgsAbstractDataSourceWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); - - //! Pure Virtual Destructor - virtual ~QgsAbstractDataSourceWidget( ) = 0; + //! Destructor + ~QgsAbstractDataSourceWidget( ) = default; //! Return the widget mode - QgsProviderRegistry::WidgetMode widgetMode( ) { return mWidgetMode; } + QgsProviderRegistry::WidgetMode widgetMode( ) const; + + /** Set the current CRS + * The CRS is normally the CRS of the map canvas, and it can be used + * by the provider dialog to transform the extent and constraint the service + */ + void setCurrentCrs( const QgsCoordinateReferenceSystem &crs ); + + /** Set the current extent + * The extent is normally the extent of the map canvas, and it can be used + * by the provider dialog to constraint the service + */ + void setCurrentExtent( const QgsRectangle &extent ); + + //! Return the current extent + QgsRectangle currentExtent( ) const; + + //! Return the current CRS + QgsCoordinateReferenceSystem currentCrs( ) const; public slots: - //! Triggered when the provider's connections need to be refreshed - //! The default implementation does nothing + /** Triggered when the provider's connections need to be refreshed + * The default implementation does nothing + */ virtual void refresh( ) {} signals: - //! Emitted when the provider's connections have changed - //! This signal is normally forwarded the app and used to refresh browser items + /** Emitted when the provider's connections have changed + * This signal is normally forwarded the app and used to refresh browser items + */ void connectionsChanged(); + //! Emitted when a DB layer has been selected for addition + void addDatabaseLayers( const QStringList &paths, const QString &providerKey ); + + //! Emitted when a raster layer has been selected for addition + void addRasterLayer( const QString &rasterLayerPath, const QString &baseName, const QString &providerKey ); + + //! Emitted when a vector layer has been selected for addition + void addVectorLayer( const QString &uri, const QString &layerName ); + + protected: + + //! Constructor + QgsAbstractDataSourceWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + private: QgsProviderRegistry::WidgetMode mWidgetMode; + QgsCoordinateReferenceSystem mCurrentCrs; + QgsRectangle mCurrentExtent; + }; #endif // QGSABSTRACTDATASOURCEWIDGET_H diff --git a/src/gui/qgsarcgisservicesourceselect.cpp b/src/gui/qgsarcgisservicesourceselect.cpp index 650d6141e50..1bae28f0a2c 100644 --- a/src/gui/qgsarcgisservicesourceselect.cpp +++ b/src/gui/qgsarcgisservicesourceselect.cpp @@ -25,10 +25,10 @@ #include "qgscoordinatereferencesystem.h" #include "qgscoordinatetransform.h" #include "qgslogger.h" -#include "qgsmapcanvas.h" #include "qgsmanageconnectionsdialog.h" #include "qgsexception.h" #include "qgssettings.h" +#include "qgsmapcanvas.h" #include #include diff --git a/src/gui/qgsarcgisservicesourceselect.h b/src/gui/qgsarcgisservicesourceselect.h index dfa19e79e91..6560aa98491 100644 --- a/src/gui/qgsarcgisservicesourceselect.h +++ b/src/gui/qgsarcgisservicesourceselect.h @@ -13,8 +13,8 @@ * * ***************************************************************************/ -#ifndef QGSARCGISSERVICESOURCESELECTDIALOG_H -#define QGSARCGISSERVICESOURCESELECTDIALOG_H +#ifndef QGSARCGISSERVICESOURCESELECT_H +#define QGSARCGISSERVICESOURCESELECT_H /// @cond PRIVATE @@ -27,6 +27,9 @@ // version without notice, or even be removed. // + +#define SIP_NO_FILE + #include "ui_qgsarcgisservicesourceselectbase.h" #include "qgsrectangle.h" #include "qgscoordinatereferencesystem.h" @@ -52,9 +55,8 @@ class GUI_EXPORT QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidg //! Constructor QgsArcGisServiceSourceSelect( const QString &serviceName, ServiceType serviceType, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); - ~QgsArcGisServiceSourceSelect(); - //! Sets the current extent and CRS. Used to select an appropriate CRS and possibly to retrieve data only in the current extent - void setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ); + //! Destructor + ~QgsArcGisServiceSourceSelect() override; signals: //! Emitted when a layer is added from the dialog @@ -89,6 +91,8 @@ class GUI_EXPORT QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidg void populateImageEncodings( const QStringList &availableEncodings ); //! Returns the selected image encoding. QString getSelectedImageEncoding() const; + //! Sets the current extent and CRS. Used to select an appropriate CRS and possibly to retrieve data only in the current extent + void setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ); private: void populateConnectionList(); @@ -122,4 +126,4 @@ class GUI_EXPORT QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidg }; -#endif // QGSARCGISSERVICESOURCESELECTDIALOG_H +#endif // QGSARCGISSERVICESOURCESELECT_H diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 6b06c846c08..4b6c43f2cf2 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -23,15 +23,14 @@ #include "qgssettings.h" #include "qgsproviderregistry.h" #include "qgsopenvectorlayerdialog.h" -#include "qgsarcgisservicesourceselect.h" +#include "qgsabstractdatasourcewidget.h" #include "qgsmapcanvas.h" - -QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, QWidget *parent, Qt::WindowFlags fl ) : +QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QWidget *parent, QgsMapCanvas *canvas, Qt::WindowFlags fl ) : QgsOptionsDialogBase( QStringLiteral( "Data Source Manager" ), parent, fl ), ui( new Ui::QgsDataSourceManagerDialog ), - mMapCanvas( mapCanvas ), - mPreviousRow( -1 ) + mPreviousRow( -1 ), + mMapCanvas( canvas ) { ui->setupUi( this ); @@ -71,7 +70,7 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, mPageNames.append( QStringLiteral( "raster" ) ); // Add data provider dialogs - QDialog *dlg = nullptr; + QWidget *dlg = nullptr; dlg = providerDialog( QStringLiteral( "delimitedtext" ), tr( "Delimited Text" ), QStringLiteral( "/mActionAddDelimitedTextLayer.svg" ) ); @@ -106,35 +105,11 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, addRasterProviderDialog( QStringLiteral( "wcs" ), tr( "WCS" ), QStringLiteral( "/mActionAddWcsLayer.svg" ) ); - dlg = providerDialog( QStringLiteral( "WFS" ), tr( "WFS" ), QStringLiteral( "/mActionAddWfsLayer.svg" ) ); - - if ( dlg ) - { - // Forward (if only a common interface for the signals had been used in the providers ...) - connect( dlg, SIGNAL( addWfsLayer( QString, QString ) ), this, SIGNAL( addWfsLayer( QString, QString ) ) ); - connect( this, &QgsDataSourceManagerDialog::addWfsLayer, this, [ = ]( const QString & vectorLayerPath, const QString & baseName ) - { - this->vectorLayerAdded( vectorLayerPath, baseName, QStringLiteral( "WFS" ) ); - } ); - connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); - connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); - } + addVectorProviderDialog( QStringLiteral( "WFS" ), tr( "WFS" ), QStringLiteral( "/mActionAddWfsLayer.svg" ) ); addRasterProviderDialog( QStringLiteral( "arcgismapserver" ), tr( "ArcGIS Map Server" ), QStringLiteral( "/mActionAddAmsLayer.svg" ) ); - QgsArcGisServiceSourceSelect *afss = dynamic_cast( providerDialog( QStringLiteral( "arcgisfeatureserver" ), - tr( "ArcGIS Feature Server" ), - QStringLiteral( "/mActionAddAfsLayer.svg" ) ) ); - if ( afss && mMapCanvas ) - { - afss->setCurrentExtentAndCrs( mMapCanvas->extent(), mMapCanvas->mapSettings().destinationCrs() ); - // Forward (if only a common interface for the signals had been used in the providers ...) - connect( afss, SIGNAL( addLayer( QString, QString ) ), this, SIGNAL( addAfsLayer( QString, QString ) ) ); - connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), afss, SLOT( refresh( ) ) ); - connect( this, &QgsDataSourceManagerDialog::addAfsLayer, - this, [ = ]( const QString & vectorLayerPath, const QString & baseName ) - { this->vectorLayerAdded( vectorLayerPath, baseName, QStringLiteral( "arcgisfeatureserver" ) ); } ); - } + addVectorProviderDialog( QStringLiteral( "arcgisfeatureserver" ), tr( "ArcGIS Feature Server" ), QStringLiteral( "/mActionAddAfsLayer.svg" ) ); } @@ -192,9 +167,9 @@ void QgsDataSourceManagerDialog::vectorLayersAdded( const QStringList &layerQStr } -QDialog *QgsDataSourceManagerDialog::providerDialog( const QString providerKey, const QString providerName, const QString icon, QString title ) +QgsAbstractDataSourceWidget *QgsDataSourceManagerDialog::providerDialog( const QString providerKey, const QString providerName, const QString icon, QString title ) { - QDialog *dlg = dynamic_cast( QgsProviderRegistry::instance()->createSelectionWidget( providerKey, this, Qt::Widget, QgsProviderRegistry::WidgetMode::Embedded ) ); + QgsAbstractDataSourceWidget *dlg = dynamic_cast( QgsProviderRegistry::instance()->createSelectionWidget( providerKey, this, Qt::Widget, QgsProviderRegistry::WidgetMode::Embedded ) ); if ( !dlg ) { QMessageBox::warning( this, providerName, tr( "Cannot get %1 select dialog from provider %2." ).arg( providerName, providerKey ) ); @@ -207,13 +182,19 @@ QDialog *QgsDataSourceManagerDialog::providerDialog( const QString providerKey, QListWidgetItem *layerItem = new QListWidgetItem( providerName, ui->mOptionsListWidget ); layerItem->setToolTip( title.isEmpty() ? tr( "Add %1 layer" ).arg( providerName ) : title ); layerItem->setIcon( QgsApplication::getThemeIcon( icon ) ); + // Set crs and extent from canvas + if ( mMapCanvas ) + { + dlg->setCurrentExtent( mMapCanvas->extent() ); + dlg->setCurrentCrs( mMapCanvas->mapSettings().destinationCrs( ) ); + } return dlg; } } void QgsDataSourceManagerDialog::addDbProviderDialog( const QString providerKey, const QString providerName, const QString icon, QString title ) { - QDialog *dlg = providerDialog( providerKey, providerName, icon, title ); + QgsAbstractDataSourceWidget *dlg = providerDialog( providerKey, providerName, icon, title ); if ( dlg ) { connect( dlg, SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ), @@ -229,12 +210,23 @@ void QgsDataSourceManagerDialog::addDbProviderDialog( const QString providerKey, void QgsDataSourceManagerDialog::addRasterProviderDialog( const QString providerKey, const QString providerName, const QString icon, QString title ) { - QDialog *dlg = providerDialog( providerKey, providerName, icon, title ); + QgsAbstractDataSourceWidget *dlg = providerDialog( providerKey, providerName, icon, title ); if ( dlg ) { - // Forward connect( dlg, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ), this, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ) ); connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); + } +} + +void QgsDataSourceManagerDialog::addVectorProviderDialog( const QString providerKey, const QString providerName, const QString icon, QString title ) +{ + QgsAbstractDataSourceWidget *dlg = providerDialog( providerKey, providerName, icon, title ); + if ( dlg ) + { + connect( dlg, &QgsAbstractDataSourceWidget::addVectorLayer, this, [ = ]( const QString & vectorLayerPath, const QString & baseName ) + { this->vectorLayerAdded( vectorLayerPath, baseName, providerKey ); } ); + connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); } } diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index ff41601f862..e47eb3f7875 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -31,6 +31,7 @@ class QgsBrowserDockWidget; class QgsRasterLayer; class QgsMapCanvas; +class QgsAbstractDataSourceWidget; /** \ingroup gui * The QgsDataSourceManagerDialog class embeds the browser panel and all @@ -47,11 +48,10 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva public: /** QgsDataSourceManagerDialog constructor - * @param mapCanvas the map canvas * @param parent the object * @param fl window flags */ - explicit QgsDataSourceManagerDialog( QgsMapCanvas *mapCanvas, QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags ); + explicit QgsDataSourceManagerDialog( QWidget *parent = nullptr, QgsMapCanvas *canvas = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags ); ~QgsDataSourceManagerDialog(); /** @@ -67,6 +67,7 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva void setCurrentPage( int index ); //! A raster layer was added: for signal forwarding to QgisApp + //! TODO: use this with an internal source select dialog instead of forwarding the whole raster selection to app void rasterLayerAdded( QString const &uri, QString const &baseName, QString const &providerKey ); //! A vector layer was added: for signal forwarding to QgisApp void vectorLayerAdded( const QString &vectorLayerPath, const QString &baseName, const QString &providerKey ); @@ -79,17 +80,13 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva signals: //! Emitted when a raster layer was selected for addition: for signal forwarding to QgisApp - void addRasterLayer( QString const &uri, QString const &baseName, QString const &providerKey ); + void addRasterLayer( const QString &uri, const QString &baseName, const QString &providerKey ); //! Emitted when the user wants to select a raster layer: for signal forwarding to QgisApp void addRasterLayer(); //! Emitted when a vector layer was selected for addition: for signal forwarding to QgisApp void addVectorLayer( const QString &vectorLayerPath, const QString &baseName, const QString &providerKey ); //! Replace the selected layer by a vector layer defined by uri, layer name, data source uri void replaceSelectedVectorLayer( const QString &oldId, const QString &uri, const QString &layerName, const QString &provider ); - //! Emitted when a WFS layer was selected for addition: for signal forwarding to QgisApp - void addWfsLayer( const QString &uri, const QString &typeName ); - //! Emitted when a AFS layer was selected for addition: for signal forwarding to QgisApp - void addAfsLayer( const QString &uri, const QString &typeName ); //! Emitted when a one or more layer were selected for addition: for signal forwarding to QgisApp void addVectorLayers( const QStringList &layerQStringList, const QString &enc, const QString &dataSourceType ); //! Emitted when the dialog is busy: for signal forwarding to QgisApp @@ -97,30 +94,34 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva //! Emitted when a status message needs to be shown: for signal forwarding to QgisApp void showStatusMessage( const QString &message ); //! Emitted when a DB layer was selected for addition: for signal forwarding to QgisApp - void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); + void addDatabaseLayers( const QStringList &layerPathList, const QString &providerKey ); //! Emitted when a file needs to be opened void openFile( const QString & ); //! Emitted when drop uri list needs to be handled from the browser void handleDropUriList( const QgsMimeDataUtils::UriList & ); //! Update project home directory void updateProjectHome(); - //! Connections changed + //! Emitted when a connection has changed inside the provider dialogs + //! This signal is normally forwarded to the application to notify other + //! browsers that they need to refresh their connections list void connectionsChanged( ); //! One or more provider connections have changed and the //! dialogs should be refreshed void providerDialogsRefreshRequested( ); private: - //! Return the dialog from the provider - QDialog *providerDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString( ) ); + // Return the dialog from the provider + QgsAbstractDataSourceWidget *providerDialog( const QString providerKey, const QString providerName, const QString icon, QString title = QString( ) ); void addDbProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString( ) ); void addRasterProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString( ) ); + void addVectorProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString( ) ); Ui::QgsDataSourceManagerDialog *ui; QgsBrowserDockWidget *mBrowserWidget = nullptr; - //! Map canvas - QgsMapCanvas *mMapCanvas = nullptr; int mPreviousRow; QStringList mPageNames; + // Map canvas + QgsMapCanvas *mMapCanvas = nullptr; + }; diff --git a/src/gui/qgsowssourceselect.h b/src/gui/qgsowssourceselect.h index 50358e2dd48..0ef2047a3f8 100644 --- a/src/gui/qgsowssourceselect.h +++ b/src/gui/qgsowssourceselect.h @@ -110,10 +110,7 @@ class GUI_EXPORT QgsOWSSourceSelect : public QgsAbstractDataSourceWidget, protec //! Add some default wms servers to the list void on_mAddDefaultButton_clicked(); - signals: - void addRasterLayer( const QString &rasterLayerPath, - const QString &baseName, - const QString &providerKey ); + void on_mDialogButtonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); } protected: @@ -225,6 +222,7 @@ class GUI_EXPORT QgsOWSSourceSelect : public QgsAbstractDataSourceWidget, protec //! URI for selected connection QgsDataSourceUri mUri; + private: //! Selected CRS QString mSelectedCRS; diff --git a/src/providers/db2/qgsdb2sourceselect.h b/src/providers/db2/qgsdb2sourceselect.h index 81a9db2c8a3..f3155923efa 100644 --- a/src/providers/db2/qgsdb2sourceselect.h +++ b/src/providers/db2/qgsdb2sourceselect.h @@ -109,7 +109,6 @@ class QgsDb2SourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDb QString connectionInfo(); signals: - void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); void addGeometryColumn( QgsDb2LayerProperty ); void progress( int, int ); void progressMessage( QString ); diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h index c4580481ebf..b0e4b81998f 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.h +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -79,7 +79,6 @@ class QgsMssqlSourceSelect : public QgsAbstractDataSourceWidget, private Ui::Qgs QString connectionInfo(); signals: - void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); void addGeometryColumn( const QgsMssqlLayerProperty & ); void progress( int, int ); void progressMessage( QString ); diff --git a/src/providers/postgres/qgspgsourceselect.h b/src/providers/postgres/qgspgsourceselect.h index 5ad750f5e6c..1fa5ce3fff1 100644 --- a/src/providers/postgres/qgspgsourceselect.h +++ b/src/providers/postgres/qgspgsourceselect.h @@ -78,7 +78,6 @@ class QgsPgSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDbS QgsDataSourceUri dataSourceUri(); signals: - void addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey ); void addGeometryColumn( const QgsPostgresLayerProperty & ); void progress( int, int ); void progressMessage( const QString & ); diff --git a/src/providers/spatialite/qgsspatialitesourceselect.h b/src/providers/spatialite/qgsspatialitesourceselect.h index 9be162a7962..281d6d2ffd4 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.h +++ b/src/providers/spatialite/qgsspatialitesourceselect.h @@ -98,11 +98,6 @@ class QgsSpatiaLiteSourceSelect: public QgsAbstractDataSourceWidget, private Ui: void on_buttonBox_helpRequested() { QgsHelp::openHelp( QStringLiteral( "working_with_vector/supported_data.html#spatialite-layers" ) ); } - signals: - void addDatabaseLayers( QStringList const &paths, QString const &providerKey ); - void progress( int, int ); - void progressMessage( QString ); - private: enum Columns { diff --git a/src/providers/wfs/qgswfssourceselect.cpp b/src/providers/wfs/qgswfssourceselect.cpp index dfb9d1f52e9..760b84451dd 100644 --- a/src/providers/wfs/qgswfssourceselect.cpp +++ b/src/providers/wfs/qgswfssourceselect.cpp @@ -405,7 +405,7 @@ void QgsWFSSourceSelect::addLayer() mUri = QgsWFSDataSourceURI::build( connection.uri().uri(), typeName, pCrsString, sql, cbxFeatureCurrentViewExtent->isChecked() ); - emit addWfsLayer( mUri, layerName ); + emit addVectorLayer( mUri, layerName ); } if ( ! mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) diff --git a/src/providers/wfs/qgswfssourceselect.h b/src/providers/wfs/qgswfssourceselect.h index c775711c00b..959b8de9b98 100644 --- a/src/providers/wfs/qgswfssourceselect.h +++ b/src/providers/wfs/qgswfssourceselect.h @@ -52,9 +52,6 @@ class QgsWFSSourceSelect: public QgsAbstractDataSourceWidget, private Ui::QgsWFS QgsWFSSourceSelect( QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); ~QgsWFSSourceSelect(); - signals: - void addWfsLayer( const QString &uri, const QString &layerName ); - private: QgsWFSSourceSelect(); //default constructor is forbidden QgsProjectionSelectionDialog *mProjectionSelector = nullptr; diff --git a/src/providers/wms/qgswmssourceselect.h b/src/providers/wms/qgswmssourceselect.h index ed1fd698b2e..e80a5be2f0c 100644 --- a/src/providers/wms/qgswmssourceselect.h +++ b/src/providers/wms/qgswmssourceselect.h @@ -188,11 +188,6 @@ class QgsWMSSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsWM QList mTileLayers; - signals: - void addRasterLayer( QString const &rasterLayerPath, - QString const &baseName, - QString const &providerKey ); - void connectionsChanged(); private slots: void on_btnSearch_clicked(); void on_btnAddWMS_clicked(); From f459790fc6bab63edbb6a02535e31c04b722df04 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 17 Jul 2017 17:37:33 +0200 Subject: [PATCH 067/266] Moved arcgis dialogs to the provider directory --- src/app/qgisapp.cpp | 1 - src/gui/CMakeLists.txt | 2 -- src/providers/arcgisrest/CMakeLists.txt | 4 ++++ .../arcgisrest}/qgsarcgisservicesourceselect.cpp | 0 .../arcgisrest}/qgsarcgisservicesourceselect.h | 15 +-------------- 5 files changed, 5 insertions(+), 17 deletions(-) rename src/{gui => providers/arcgisrest}/qgsarcgisservicesourceselect.cpp (100%) rename src/{gui => providers/arcgisrest}/qgsarcgisservicesourceselect.h (92%) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 3a4e8466664..04fd13bc7ce 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -247,7 +247,6 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsshortcutsmanager.h" #include "qgssinglebandgrayrenderer.h" #include "qgssnappingwidget.h" -#include "qgsarcgisservicesourceselect.h" #include "qgsstatisticalsummarydockwidget.h" #include "qgsstatusbar.h" #include "qgsstatusbarcoordinateswidget.h" diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 2739032c7ed..c3e4f26e87f 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -302,7 +302,6 @@ SET(QGIS_GUI_SRCS qgsoptionsdialogbase.cpp qgsorderbydialog.cpp qgsowssourceselect.cpp - qgsarcgisservicesourceselect.cpp qgspanelwidget.cpp qgspanelwidgetstack.cpp qgspasswordlineedit.cpp @@ -458,7 +457,6 @@ SET(QGIS_GUI_MOC_HDRS qgsoptionswidgetfactory.h qgsorderbydialog.h qgsowssourceselect.h - qgsarcgisservicesourceselect.h qgspanelwidget.h qgspanelwidgetstack.h qgspasswordlineedit.h diff --git a/src/providers/arcgisrest/CMakeLists.txt b/src/providers/arcgisrest/CMakeLists.txt index e702696623d..a4dfa5a3904 100644 --- a/src/providers/arcgisrest/CMakeLists.txt +++ b/src/providers/arcgisrest/CMakeLists.txt @@ -42,9 +42,11 @@ SET (AFS_MOC_HDRS IF (WITH_GUI) SET(AFS_SRCS ${AFS_SRCS} qgsafssourceselect.cpp + qgsarcgisservicesourceselect.cpp ) SET(AFS_MOC_HDRS ${AFS_MOC_HDRS} qgsafssourceselect.h + qgsarcgisservicesourceselect.h ) ENDIF () @@ -83,9 +85,11 @@ SET (AMS_MOC_HDRS IF (WITH_GUI) SET(AMS_SRCS ${AMS_SRCS} qgsamssourceselect.cpp + qgsarcgisservicesourceselect.cpp ) SET(AMS_MOC_HDRS ${AMS_MOC_HDRS} qgsamssourceselect.h + qgsarcgisservicesourceselect.h ) ENDIF () diff --git a/src/gui/qgsarcgisservicesourceselect.cpp b/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp similarity index 100% rename from src/gui/qgsarcgisservicesourceselect.cpp rename to src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp diff --git a/src/gui/qgsarcgisservicesourceselect.h b/src/providers/arcgisrest/qgsarcgisservicesourceselect.h similarity index 92% rename from src/gui/qgsarcgisservicesourceselect.h rename to src/providers/arcgisrest/qgsarcgisservicesourceselect.h index 6560aa98491..9653ed51929 100644 --- a/src/gui/qgsarcgisservicesourceselect.h +++ b/src/providers/arcgisrest/qgsarcgisservicesourceselect.h @@ -16,25 +16,12 @@ #ifndef QGSARCGISSERVICESOURCESELECT_H #define QGSARCGISSERVICESOURCESELECT_H -/// @cond PRIVATE - -// -// W A R N I N G -// ------------- -// -// This file is not part of the QGIS API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// - - #define SIP_NO_FILE #include "ui_qgsarcgisservicesourceselectbase.h" #include "qgsrectangle.h" #include "qgscoordinatereferencesystem.h" #include "qgsabstractdatasourcewidget.h" -#include "qgis_gui.h" class QStandardItemModel; class QSortFilterProxyModel; @@ -44,7 +31,7 @@ class QgsOwsConnection; /** * Base class for listing ArcGis layers available from a remote service. */ -class GUI_EXPORT QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidget, protected Ui::QgsArcGisServiceSourceSelectBase +class QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidget, protected Ui::QgsArcGisServiceSourceSelectBase { Q_OBJECT From e83ef2e8b59ee9aa4bd39994c5bfdf1c854957e5 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 18 Jul 2017 09:12:44 +0200 Subject: [PATCH 068/266] Store canvas into the base class and set extent/crs from the arcgis classes This modification was necessary because the current implementation of the source select dialogs within the unified add layer dialog create the provider dialogs the first time and do not destroy them, this means that the canvas extent and CRS can change from a dialog invocation to the next and the extent and CRS need to be updated at layer creation time. --- python/gui/qgsabstractdatasourcewidget.sip | 44 +++++++------------ src/gui/qgsabstractdatasourcewidget.cpp | 17 ++----- src/gui/qgsabstractdatasourcewidget.h | 31 ++++++------- src/gui/qgsdatasourcemanagerdialog.cpp | 3 +- .../qgsarcgisservicesourceselect.cpp | 19 ++++---- .../arcgisrest/qgsarcgisservicesourceselect.h | 9 +++- 6 files changed, 51 insertions(+), 72 deletions(-) diff --git a/python/gui/qgsabstractdatasourcewidget.sip b/python/gui/qgsabstractdatasourcewidget.sip index e50d3fbf469..ecc3145af2b 100644 --- a/python/gui/qgsabstractdatasourcewidget.sip +++ b/python/gui/qgsabstractdatasourcewidget.sip @@ -10,6 +10,7 @@ + class QgsAbstractDataSourceWidget : QDialog { %Docstring @@ -30,37 +31,12 @@ class QgsAbstractDataSourceWidget : QDialog Destructor %End - QgsProviderRegistry::WidgetMode widgetMode( ) const; + void setMapCanvas( const QgsMapCanvas *mapCanvas ); %Docstring -Return the widget mode - :rtype: QgsProviderRegistry.WidgetMode + Store a pointer to the map canvas to retrieve extent and CRS + Used to select an appropriate CRS and possibly to retrieve data only in the current extent %End - void setCurrentCrs( const QgsCoordinateReferenceSystem &crs ); -%Docstring - Set the current CRS - The CRS is normally the CRS of the map canvas, and it can be used - by the provider dialog to transform the extent and constraint the service -%End - - void setCurrentExtent( const QgsRectangle &extent ); -%Docstring - Set the current extent - The extent is normally the extent of the map canvas, and it can be used - by the provider dialog to constraint the service -%End - - QgsRectangle currentExtent( ) const; -%Docstring -Return the current extent - :rtype: QgsRectangle -%End - - QgsCoordinateReferenceSystem currentCrs( ) const; -%Docstring -Return the current CRS - :rtype: QgsCoordinateReferenceSystem -%End public slots: @@ -100,6 +76,18 @@ Emitted when a vector layer has been selected for addition Constructor %End + QgsProviderRegistry::WidgetMode widgetMode( ) const; +%Docstring +Return the widget mode + :rtype: QgsProviderRegistry.WidgetMode +%End + + const QgsMapCanvas *mapCanvas( ) const; +%Docstring + Return the map canvas (can be null) + :rtype: QgsMapCanvas +%End + }; /************************************************************************ diff --git a/src/gui/qgsabstractdatasourcewidget.cpp b/src/gui/qgsabstractdatasourcewidget.cpp index b6bab91dca0..8fddb860c64 100644 --- a/src/gui/qgsabstractdatasourcewidget.cpp +++ b/src/gui/qgsabstractdatasourcewidget.cpp @@ -30,22 +30,13 @@ QgsProviderRegistry::WidgetMode QgsAbstractDataSourceWidget::widgetMode() const return mWidgetMode; } -void QgsAbstractDataSourceWidget::setCurrentCrs( const QgsCoordinateReferenceSystem &crs ) +const QgsMapCanvas *QgsAbstractDataSourceWidget::mapCanvas() const { - mCurrentCrs = crs; + return mMapCanvas; } -void QgsAbstractDataSourceWidget::setCurrentExtent( const QgsRectangle &extent ) -{ - mCurrentExtent = extent; -} -QgsRectangle QgsAbstractDataSourceWidget::currentExtent() const +void QgsAbstractDataSourceWidget::setMapCanvas( const QgsMapCanvas *mapCanvas ) { - return mCurrentExtent; -} - -QgsCoordinateReferenceSystem QgsAbstractDataSourceWidget::currentCrs() const -{ - return mCurrentCrs; + mMapCanvas = mapCanvas; } diff --git a/src/gui/qgsabstractdatasourcewidget.h b/src/gui/qgsabstractdatasourcewidget.h index 7b57132f514..f8672b4d583 100644 --- a/src/gui/qgsabstractdatasourcewidget.h +++ b/src/gui/qgsabstractdatasourcewidget.h @@ -29,6 +29,8 @@ #include "qgscoordinatereferencesystem.h" #include +class QgsMapCanvas; + /** \ingroup gui * \brief Abstract base Data Source Widget to create connections and add layers * This class provides common functionality and the interface for all @@ -45,26 +47,11 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog //! Destructor ~QgsAbstractDataSourceWidget( ) = default; - //! Return the widget mode - QgsProviderRegistry::WidgetMode widgetMode( ) const; - - /** Set the current CRS - * The CRS is normally the CRS of the map canvas, and it can be used - * by the provider dialog to transform the extent and constraint the service + /** Store a pointer to the map canvas to retrieve extent and CRS + * Used to select an appropriate CRS and possibly to retrieve data only in the current extent */ - void setCurrentCrs( const QgsCoordinateReferenceSystem &crs ); + void setMapCanvas( const QgsMapCanvas *mapCanvas ); - /** Set the current extent - * The extent is normally the extent of the map canvas, and it can be used - * by the provider dialog to constraint the service - */ - void setCurrentExtent( const QgsRectangle &extent ); - - //! Return the current extent - QgsRectangle currentExtent( ) const; - - //! Return the current CRS - QgsCoordinateReferenceSystem currentCrs( ) const; public slots: @@ -94,11 +81,19 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog //! Constructor QgsAbstractDataSourceWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); + //! Return the widget mode + QgsProviderRegistry::WidgetMode widgetMode( ) const; + + /** Return the map canvas (can be null) + */ + const QgsMapCanvas *mapCanvas( ) const; + private: QgsProviderRegistry::WidgetMode mWidgetMode; QgsCoordinateReferenceSystem mCurrentCrs; QgsRectangle mCurrentExtent; + QgsMapCanvas const *mMapCanvas = nullptr; }; diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 4b6c43f2cf2..2288b7d2c5e 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -185,8 +185,7 @@ QgsAbstractDataSourceWidget *QgsDataSourceManagerDialog::providerDialog( const Q // Set crs and extent from canvas if ( mMapCanvas ) { - dlg->setCurrentExtent( mMapCanvas->extent() ); - dlg->setCurrentCrs( mMapCanvas->mapSettings().destinationCrs( ) ); + dlg->setMapCanvas( mMapCanvas ); } return dlg; } diff --git a/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp b/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp index 1bae28f0a2c..2462062c2f5 100644 --- a/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp +++ b/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp @@ -123,11 +123,6 @@ QgsArcGisServiceSourceSelect::~QgsArcGisServiceSourceSelect() delete mModelProxy; } -void QgsArcGisServiceSourceSelect::setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ) -{ - mCanvasExtent = canvasExtent; - mCanvasCrs = canvasCrs; -} void QgsArcGisServiceSourceSelect::populateImageEncodings( const QStringList &availableEncodings ) { @@ -325,15 +320,21 @@ void QgsArcGisServiceSourceSelect::addButtonClicked() QString pCrsString( labelCoordRefSys->text() ); QgsCoordinateReferenceSystem pCrs( pCrsString ); //prepare canvas extent info for layers with "cache features" option not set - QgsRectangle extent = mCanvasExtent; + QgsRectangle extent; + QgsCoordinateReferenceSystem canvasCrs; + if ( mapCanvas() ) + { + extent = mapCanvas()->extent(); + canvasCrs = mapCanvas()->mapSettings().destinationCrs(); + } //does canvas have "on the fly" reprojection set? - if ( pCrs.isValid() && mCanvasCrs.isValid() ) + if ( pCrs.isValid() && canvasCrs.isValid() ) { try { - extent = QgsCoordinateTransform( mCanvasCrs, pCrs ).transform( extent ); + extent = QgsCoordinateTransform( canvasCrs, pCrs ).transform( extent ); QgsDebugMsg( QString( "canvas transform: Canvas CRS=%1, Provider CRS=%2, BBOX=%3" ) - .arg( mCanvasCrs.authid(), pCrs.authid(), extent.asWktCoordinates() ) ); + .arg( canvasCrs.authid(), pCrs.authid(), extent.asWktCoordinates() ) ); } catch ( const QgsCsException & ) { diff --git a/src/providers/arcgisrest/qgsarcgisservicesourceselect.h b/src/providers/arcgisrest/qgsarcgisservicesourceselect.h index 9653ed51929..7f8d94a8272 100644 --- a/src/providers/arcgisrest/qgsarcgisservicesourceselect.h +++ b/src/providers/arcgisrest/qgsarcgisservicesourceselect.h @@ -27,6 +27,7 @@ class QStandardItemModel; class QSortFilterProxyModel; class QgsProjectionSelectionDialog; class QgsOwsConnection; +class QgsMapCanvas; /** * Base class for listing ArcGis layers available from a remote service. @@ -78,8 +79,6 @@ class QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidget, protect void populateImageEncodings( const QStringList &availableEncodings ); //! Returns the selected image encoding. QString getSelectedImageEncoding() const; - //! Sets the current extent and CRS. Used to select an appropriate CRS and possibly to retrieve data only in the current extent - void setCurrentExtentAndCrs( const QgsRectangle &canvasExtent, const QgsCoordinateReferenceSystem &canvasCrs ); private: void populateConnectionList(); @@ -91,6 +90,12 @@ class QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidget, protect \returns the authority id of the crs or an empty string in case of error*/ QString getPreferredCrs( const QSet &crsSet ) const; + /** Store a pointer to map canvas to retrieve extent and CRS + * Used to select an appropriate CRS and possibly to retrieve data only in the current extent + */ + QgsMapCanvas *mMapCanvas = nullptr; + + public slots: //! Triggered when the provider's connections need to be refreshed From 29855b39423087dd847839cb31577953c2896489 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 19:55:48 +1000 Subject: [PATCH 069/266] Change signature of processFeature so that features are no longer modified in place --- python/core/processing/qgsprocessingalgorithm.sip | 7 +++---- .../plugins/processing/algs/qgis/AddTableField.py | 2 +- .../processing/algs/qgis/AutoincrementalField.py | 2 +- .../plugins/processing/algs/qgis/BoundingBox.py | 2 +- .../plugins/processing/algs/qgis/DropMZValues.py | 2 +- python/plugins/processing/algs/qgis/SetMValue.py | 2 +- python/plugins/processing/algs/qgis/SetZValue.py | 2 +- src/core/processing/qgsnativealgorithms.cpp | 15 +++++++++------ src/core/processing/qgsnativealgorithms.h | 6 +++--- src/core/processing/qgsprocessingalgorithm.cpp | 6 ++---- src/core/processing/qgsprocessingalgorithm.h | 5 ++--- 11 files changed, 25 insertions(+), 26 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index 9f340eaedbf..c5d12ad85d3 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -820,15 +820,14 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm :rtype: QgsCoordinateReferenceSystem %End - virtual bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; + virtual QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; %Docstring Processes an individual input ``feature`` from the source. Algorithms should implement their logic in this method for performing the algorithm's operation (e.g. replacing the feature's geometry with the centroid of the original feature geometry for a 'centroid' type algorithm). - Implementations should return true if the feature should be kept and added to the algorithm's - output sink, or false if the feature should be skipped and omitted from the output. + Implementations should return the modified feature. The provided ``feedback`` object can be used to push messages to the log and for giving feedback to users. Note that handling of progress reports and algorithm cancelation is handled by @@ -838,7 +837,7 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm prevent the algorithm execution from continuing. This can be annoying for users though as it can break valid model execution - so use with extreme caution, and consider using ``feedback`` to instead report non-fatal processing failures for features instead. - :rtype: bool + :rtype: QgsFeature %End virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, diff --git a/python/plugins/processing/algs/qgis/AddTableField.py b/python/plugins/processing/algs/qgis/AddTableField.py index c93f4630bf9..321c6eb43c2 100644 --- a/python/plugins/processing/algs/qgis/AddTableField.py +++ b/python/plugins/processing/algs/qgis/AddTableField.py @@ -90,4 +90,4 @@ class AddTableField(QgisFeatureBasedAlgorithm): attributes = feature.attributes() attributes.append(None) feature.setAttributes(attributes) - return True + return feature diff --git a/python/plugins/processing/algs/qgis/AutoincrementalField.py b/python/plugins/processing/algs/qgis/AutoincrementalField.py index f9187f395ae..9f93b688eb5 100644 --- a/python/plugins/processing/algs/qgis/AutoincrementalField.py +++ b/python/plugins/processing/algs/qgis/AutoincrementalField.py @@ -57,4 +57,4 @@ class AutoincrementalField(QgisFeatureBasedAlgorithm): attributes.append(self.current) self.current += 1 feature.setAttributes(attributes) - return True + return feature diff --git a/python/plugins/processing/algs/qgis/BoundingBox.py b/python/plugins/processing/algs/qgis/BoundingBox.py index c5b08f597fa..796ec52de16 100644 --- a/python/plugins/processing/algs/qgis/BoundingBox.py +++ b/python/plugins/processing/algs/qgis/BoundingBox.py @@ -72,4 +72,4 @@ class BoundingBox(QgisFeatureBasedAlgorithm): feature.setGeometry(output_geometry) - return True + return feature diff --git a/python/plugins/processing/algs/qgis/DropMZValues.py b/python/plugins/processing/algs/qgis/DropMZValues.py index 521d695014f..71eeb443e5a 100644 --- a/python/plugins/processing/algs/qgis/DropMZValues.py +++ b/python/plugins/processing/algs/qgis/DropMZValues.py @@ -91,4 +91,4 @@ class DropMZValues(QgisFeatureBasedAlgorithm): new_geom.dropZValue() feature.setGeometry(QgsGeometry(new_geom)) - return True + return feature diff --git a/python/plugins/processing/algs/qgis/SetMValue.py b/python/plugins/processing/algs/qgis/SetMValue.py index f22c02f14b8..54558059af1 100644 --- a/python/plugins/processing/algs/qgis/SetMValue.py +++ b/python/plugins/processing/algs/qgis/SetMValue.py @@ -83,4 +83,4 @@ class SetMValue(QgisFeatureBasedAlgorithm): feature.setGeometry(QgsGeometry(new_geom)) - return True + return feature diff --git a/python/plugins/processing/algs/qgis/SetZValue.py b/python/plugins/processing/algs/qgis/SetZValue.py index 1611be177c3..b93a08a1b9d 100644 --- a/python/plugins/processing/algs/qgis/SetZValue.py +++ b/python/plugins/processing/algs/qgis/SetZValue.py @@ -83,4 +83,4 @@ class SetZValue(QgisFeatureBasedAlgorithm): feature.setGeometry(QgsGeometry(new_geom)) - return True + return feature diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 4f455f97db6..e2ea81fab03 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -87,8 +87,9 @@ QgsCentroidAlgorithm *QgsCentroidAlgorithm::createInstance() const return new QgsCentroidAlgorithm(); } -bool QgsCentroidAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) +QgsFeature QgsCentroidAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback *feedback ) { + QgsFeature feature = f; if ( feature.hasGeometry() ) { feature.setGeometry( feature.geometry().centroid() ); @@ -97,7 +98,7 @@ bool QgsCentroidAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFee feedback->pushInfo( QObject::tr( "Error calculating centroid for feature %1" ).arg( feature.id() ) ); } } - return true; + return feature; } // // QgsBufferAlgorithm @@ -548,8 +549,9 @@ bool QgsTransformAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, Qgs return true; } -bool QgsTransformAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFeedback * ) +QgsFeature QgsTransformAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback * ) { + QgsFeature feature = f; if ( !mCreatedTransform ) { mCreatedTransform = true; @@ -568,7 +570,7 @@ bool QgsTransformAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFe feature.clearGeometry(); } } - return true; + return feature; } @@ -598,8 +600,9 @@ QgsWkbTypes::Type QgsSubdivideAlgorithm::outputWkbType( QgsWkbTypes::Type inputW return QgsWkbTypes::multiType( inputWkbType ); } -bool QgsSubdivideAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) +QgsFeature QgsSubdivideAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback *feedback ) { + QgsFeature feature = f; if ( feature.hasGeometry() ) { feature.setGeometry( feature.geometry().subdivide( mMaxNodes ) ); @@ -608,7 +611,7 @@ bool QgsSubdivideAlgorithm::processFeature( QgsFeature &feature, QgsProcessingFe feedback->reportError( QObject::tr( "Error calculating subdivision for feature %1" ).arg( feature.id() ) ); } } - return true; + return feature; } bool QgsSubdivideAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index c2909b9fe08..f0485ac8131 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -68,7 +68,7 @@ class QgsCentroidAlgorithm : public QgsProcessingFeatureBasedAlgorithm QgsProcessing::LayerType outputLayerType() const override { return QgsProcessing::TypeVectorPoint; } QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override { Q_UNUSED( inputWkbType ); return QgsWkbTypes::Point; } - bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; }; /** @@ -94,7 +94,7 @@ class QgsTransformAlgorithm : public QgsProcessingFeatureBasedAlgorithm QString outputName() const override { return QObject::tr( "Reprojected" ); } bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; private: @@ -262,7 +262,7 @@ class QgsSubdivideAlgorithm : public QgsProcessingFeatureBasedAlgorithm QString outputName() const override { return QObject::tr( "Subdivided" ); } QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override; - bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index 6ae487db255..ce6b99cb098 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -655,10 +655,8 @@ QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariant break; } - if ( processFeature( f, feedback ) ) - { - sink->addFeature( f, QgsFeatureSink::FastInsert ); - } + QgsFeature transformed = processFeature( f, feedback ); + sink->addFeature( transformed, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index f947d07ebe8..bdb4da6f5f2 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -798,8 +798,7 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor * geometry with the centroid of the original feature geometry for a 'centroid' type * algorithm). * - * Implementations should return true if the feature should be kept and added to the algorithm's - * output sink, or false if the feature should be skipped and omitted from the output. + * Implementations should return the modified feature. * * The provided \a feedback object can be used to push messages to the log and for giving feedback * to users. Note that handling of progress reports and algorithm cancelation is handled by @@ -810,7 +809,7 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor * can break valid model execution - so use with extreme caution, and consider using * \a feedback to instead report non-fatal processing failures for features instead. */ - virtual bool processFeature( QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; + virtual QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0; virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; From bd925cd64831cd9d657386f9717d6d0e4b7433e2 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 18 Jul 2017 12:15:57 +0200 Subject: [PATCH 070/266] Rebase and partially revert 9bae83275368 All signals are now in the base class, even if only a subset of available providers actually emits them. This way we can handle all source select dialogs the same way, regardless if they are vector, DB or raster (or others). --- python/gui/qgsabstractdatasourcewidget.sip | 10 ++++++++++ python/gui/qgsowssourceselect.sip | 2 -- src/gui/qgsabstractdatasourcewidget.h | 6 ++++++ src/gui/qgsdatasourcemanagerdialog.h | 4 ++-- src/gui/qgsowssourceselect.h | 2 -- src/providers/arcgisrest/qgsamssourceselect.h | 5 ----- src/providers/db2/qgsdb2sourceselect.cpp | 2 +- src/providers/db2/qgsdb2sourceselect.h | 2 -- src/providers/mssql/qgsmssqlsourceselect.cpp | 2 +- src/providers/mssql/qgsmssqlsourceselect.h | 2 -- 10 files changed, 20 insertions(+), 17 deletions(-) diff --git a/python/gui/qgsabstractdatasourcewidget.sip b/python/gui/qgsabstractdatasourcewidget.sip index ecc3145af2b..8ac0f38b226 100644 --- a/python/gui/qgsabstractdatasourcewidget.sip +++ b/python/gui/qgsabstractdatasourcewidget.sip @@ -67,6 +67,16 @@ Emitted when a raster layer has been selected for addition void addVectorLayer( const QString &uri, const QString &layerName ); %Docstring Emitted when a vector layer has been selected for addition +%End + + void progress( int, int ); +%Docstring +Emitted when a progress dialog is shown by the provider dialog +%End + + void progressMessage( QString message ); +%Docstring +Emitted when a progress dialog is shown by the provider dialog %End protected: diff --git a/python/gui/qgsowssourceselect.sip b/python/gui/qgsowssourceselect.sip index 02b394ef496..c237d09bc22 100644 --- a/python/gui/qgsowssourceselect.sip +++ b/python/gui/qgsowssourceselect.sip @@ -112,8 +112,6 @@ Stores the selected datasource whenerver it is changed Add some default wms servers to the list %End - void on_mDialogButtonBox_helpRequested(); - protected: virtual QList providerFormats(); diff --git a/src/gui/qgsabstractdatasourcewidget.h b/src/gui/qgsabstractdatasourcewidget.h index f8672b4d583..10f8f484b1c 100644 --- a/src/gui/qgsabstractdatasourcewidget.h +++ b/src/gui/qgsabstractdatasourcewidget.h @@ -76,6 +76,12 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog //! Emitted when a vector layer has been selected for addition void addVectorLayer( const QString &uri, const QString &layerName ); + //! Emitted when a progress dialog is shown by the provider dialog + void progress( int, int ); + + //! Emitted when a progress dialog is shown by the provider dialog + void progressMessage( QString message ); + protected: //! Constructor diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index e47eb3f7875..6ea71588866 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -104,10 +104,10 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva //! Emitted when a connection has changed inside the provider dialogs //! This signal is normally forwarded to the application to notify other //! browsers that they need to refresh their connections list - void connectionsChanged( ); + void connectionsChanged(); //! One or more provider connections have changed and the //! dialogs should be refreshed - void providerDialogsRefreshRequested( ); + void providerDialogsRefreshRequested(); private: // Return the dialog from the provider diff --git a/src/gui/qgsowssourceselect.h b/src/gui/qgsowssourceselect.h index 0ef2047a3f8..942504d7e1c 100644 --- a/src/gui/qgsowssourceselect.h +++ b/src/gui/qgsowssourceselect.h @@ -110,8 +110,6 @@ class GUI_EXPORT QgsOWSSourceSelect : public QgsAbstractDataSourceWidget, protec //! Add some default wms servers to the list void on_mAddDefaultButton_clicked(); - void on_mDialogButtonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); } - protected: /** diff --git a/src/providers/arcgisrest/qgsamssourceselect.h b/src/providers/arcgisrest/qgsamssourceselect.h index 39a329a197d..c97d134a22a 100644 --- a/src/providers/arcgisrest/qgsamssourceselect.h +++ b/src/providers/arcgisrest/qgsamssourceselect.h @@ -30,11 +30,6 @@ class QgsAmsSourceSelect: public QgsArcGisServiceSourceSelect public: QgsAmsSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); - signals: - void addRasterLayer( QString const &rasterLayerPath, - QString const &baseName, - QString const &providerKey ); - protected: bool connectToService( const QgsOwsConnection &connection ) override; QString getLayerURI( const QgsOwsConnection &connection, diff --git a/src/providers/db2/qgsdb2sourceselect.cpp b/src/providers/db2/qgsdb2sourceselect.cpp index bf8329df9fb..e7e55c7e1e0 100644 --- a/src/providers/db2/qgsdb2sourceselect.cpp +++ b/src/providers/db2/qgsdb2sourceselect.cpp @@ -458,7 +458,7 @@ void QgsDb2SourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "DB2" ) ); - if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( !mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/db2/qgsdb2sourceselect.h b/src/providers/db2/qgsdb2sourceselect.h index f3155923efa..b8803def21f 100644 --- a/src/providers/db2/qgsdb2sourceselect.h +++ b/src/providers/db2/qgsdb2sourceselect.h @@ -110,8 +110,6 @@ class QgsDb2SourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDb signals: void addGeometryColumn( QgsDb2LayerProperty ); - void progress( int, int ); - void progressMessage( QString ); public slots: //! Determines the tables the user selected and closes the dialog diff --git a/src/providers/mssql/qgsmssqlsourceselect.cpp b/src/providers/mssql/qgsmssqlsourceselect.cpp index 3756f7c6c1b..fd14526cc6e 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.cpp +++ b/src/providers/mssql/qgsmssqlsourceselect.cpp @@ -453,7 +453,7 @@ void QgsMssqlSourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "mssql" ) ); - if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + if ( !mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h index b0e4b81998f..d9310cfb61d 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.h +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -80,8 +80,6 @@ class QgsMssqlSourceSelect : public QgsAbstractDataSourceWidget, private Ui::Qgs signals: void addGeometryColumn( const QgsMssqlLayerProperty & ); - void progress( int, int ); - void progressMessage( QString ); public slots: From 854430991eb6a6760cd28d0871a450ff649ad56a Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 18 Jul 2017 12:31:38 +0200 Subject: [PATCH 071/266] Remove unused includes and variables --- src/gui/qgsabstractdatasourcewidget.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/gui/qgsabstractdatasourcewidget.h b/src/gui/qgsabstractdatasourcewidget.h index 10f8f484b1c..1cd66ee069a 100644 --- a/src/gui/qgsabstractdatasourcewidget.h +++ b/src/gui/qgsabstractdatasourcewidget.h @@ -25,8 +25,6 @@ #include "qgsproviderregistry.h" #include "qgsguiutils.h" -#include "qgsrectangle.h" -#include "qgscoordinatereferencesystem.h" #include class QgsMapCanvas; @@ -97,8 +95,6 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog private: QgsProviderRegistry::WidgetMode mWidgetMode; - QgsCoordinateReferenceSystem mCurrentCrs; - QgsRectangle mCurrentExtent; QgsMapCanvas const *mMapCanvas = nullptr; }; From 6acd326a8f02e7a15ed87fc81971683e68052155 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Tue, 18 Jul 2017 18:13:52 +0700 Subject: [PATCH 072/266] [FEATURE] Draw extent onto canvas in save as image/PDF dialog (#4878) --- python/gui/gui_auto.sip | 1 + python/gui/qgsextentgroupbox.sip | 16 ++++ python/gui/qgsmaptoolextent.sip | 75 ++++++++++++++++++ src/app/qgsmapsavedialog.cpp | 1 + src/gui/CMakeLists.txt | 2 + src/gui/qgsextentgroupbox.cpp | 69 ++++++++++++++++- src/gui/qgsextentgroupbox.h | 25 ++++++ src/gui/qgsmaptoolextent.cpp | 125 ++++++++++++++++++++++++++++++ src/gui/qgsmaptoolextent.h | 86 ++++++++++++++++++++ src/ui/qgsextentgroupboxwidget.ui | 13 ++++ 10 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 python/gui/qgsmaptoolextent.sip create mode 100644 src/gui/qgsmaptoolextent.cpp create mode 100644 src/gui/qgsmaptoolextent.h diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 3f1b9e7ead5..d6947f798c6 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -137,6 +137,7 @@ %Include qgsmaptoolcapture.sip %Include qgsmaptooledit.sip %Include qgsmaptoolemitpoint.sip +%Include qgsmaptoolextent.sip %Include qgsmaptoolidentify.sip %Include qgsmaptoolidentifyfeature.sip %Include qgsmaptoolpan.sip diff --git a/python/gui/qgsextentgroupbox.sip b/python/gui/qgsextentgroupbox.sip index f2b73086b24..7f423674835 100644 --- a/python/gui/qgsextentgroupbox.sip +++ b/python/gui/qgsextentgroupbox.sip @@ -10,6 +10,8 @@ + + class QgsExtentGroupBox : QgsCollapsibleGroupBox { %Docstring @@ -34,6 +36,7 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox CurrentExtent, UserExtent, ProjectLayerExtent, + DrawOnCanvas, }; explicit QgsExtentGroupBox( QWidget *parent /TransferThis/ = 0 ); @@ -124,6 +127,13 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox :rtype: str %End + void setMapCanvas( QgsMapCanvas *canvas ); +%Docstring + Sets the map canvas to enable dragging of extent on a canvas. + \param canvas the map canvas +.. versionadded:: 3.0 +%End + public slots: void setOutputExtentFromOriginal(); @@ -145,6 +155,12 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox %Docstring Sets the output extent to match a ``layer``'s extent (may be transformed to output CRS). .. versionadded:: 3.0 +%End + + void setOutputExtentFromDrawOnCanvas(); +%Docstring + Sets the output extent by dragging on the canvas. +.. versionadded:: 3.0 %End signals: diff --git a/python/gui/qgsmaptoolextent.sip b/python/gui/qgsmaptoolextent.sip new file mode 100644 index 00000000000..4c489accc0a --- /dev/null +++ b/python/gui/qgsmaptoolextent.sip @@ -0,0 +1,75 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsmaptoolextent.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsMapToolExtent : QgsMapTool +{ +%Docstring + A map tool that emits an extent from a rectangle drawn onto the map canvas. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsmaptoolextent.h" +%End + public: + + QgsMapToolExtent( QgsMapCanvas *canvas ); +%Docstring +constructor +%End + + virtual Flags flags() const; + virtual void canvasMoveEvent( QgsMapMouseEvent *e ); + virtual void canvasPressEvent( QgsMapMouseEvent *e ); + virtual void canvasReleaseEvent( QgsMapMouseEvent *e ); + virtual void activate(); + virtual void deactivate(); + + void setRatio( QSize ratio ); +%Docstring + Sets a fixed aspect ratio to be used when dragging extent onto the canvas. + To unset a fixed aspect ratio, set the width and height to zero. + \param ratio aspect ratio's width and height + * +%End + + QSize ratio() const; +%Docstring + Returns the current fixed aspect ratio to be used when dragging extent onto the canvas. + If the aspect ratio isn't fixed, the width and height will be set to zero. + * + :rtype: QSize +%End + + QgsRectangle extent() const; +%Docstring + Returns the current extent drawn onto the canvas. + :rtype: QgsRectangle +%End + + signals: + + void extentChanged( const QgsRectangle &extent ); +%Docstring +signal emitted on extent change +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsmaptoolextent.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index be15d956623..c37359e9f1b 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -59,6 +59,7 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QL mExtentGroupBox->setOutputCrs( ms.destinationCrs() ); mExtentGroupBox->setCurrentExtent( mExtent, ms.destinationCrs() ); mExtentGroupBox->setOutputExtentFromCurrent(); + mExtentGroupBox->setMapCanvas( mapCanvas ); mScaleWidget->setScale( ms.scale() ); mScaleWidget->setMapCanvas( mMapCanvas ); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a9716063428..2953a331ae0 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -283,6 +283,7 @@ SET(QGIS_GUI_SRCS qgsmaptoolcapture.cpp qgsmaptooledit.cpp qgsmaptoolemitpoint.cpp + qgsmaptoolextent.cpp qgsmaptoolidentify.cpp qgsmaptoolidentifyfeature.cpp qgsmaptoolpan.cpp @@ -437,6 +438,7 @@ SET(QGIS_GUI_MOC_HDRS qgsmaptoolcapture.h qgsmaptooledit.h qgsmaptoolemitpoint.h + qgsmaptoolextent.h qgsmaptoolidentify.h qgsmaptoolidentifyfeature.h qgsmaptoolpan.h diff --git a/src/gui/qgsextentgroupbox.cpp b/src/gui/qgsextentgroupbox.cpp index 28bcb520ee7..55aff8f8f2b 100644 --- a/src/gui/qgsextentgroupbox.cpp +++ b/src/gui/qgsextentgroupbox.cpp @@ -12,13 +12,17 @@ * (at your option) any later version. * * * ***************************************************************************/ + #include "qgsextentgroupbox.h" +#include "qgslogger.h" #include "qgscoordinatetransform.h" #include "qgsrasterblock.h" +#include "qgsmapcanvas.h" #include "qgsmaplayermodel.h" #include "qgsexception.h" #include "qgsproject.h" + #include #include @@ -40,13 +44,15 @@ QgsExtentGroupBox::QgsExtentGroupBox( QWidget *parent ) mYMaxLineEdit->setValidator( new QDoubleValidator( this ) ); mOriginalExtentButton->setVisible( false ); + mButtonDrawOnCanvas->setVisible( false ); connect( mCurrentExtentButton, &QAbstractButton::clicked, this, &QgsExtentGroupBox::setOutputExtentFromCurrent ); connect( mOriginalExtentButton, &QAbstractButton::clicked, this, &QgsExtentGroupBox::setOutputExtentFromOriginal ); + connect( mButtonDrawOnCanvas, &QAbstractButton::clicked, this, &QgsExtentGroupBox::setOutputExtentFromDrawOnCanvas ); + connect( this, &QGroupBox::clicked, this, &QgsExtentGroupBox::groupBoxClicked ); } - void QgsExtentGroupBox::setOriginalExtent( const QgsRectangle &originalExtent, const QgsCoordinateReferenceSystem &originalCrs ) { mOriginalExtent = originalExtent; @@ -83,6 +89,11 @@ void QgsExtentGroupBox::setOutputCrs( const QgsCoordinateReferenceSystem &output setOutputExtentFromLayer( mExtentLayer.data() ); break; + case DrawOnCanvas: + mOutputCrs = outputCrs; + extentDrawn( outputExtent() ); + break; + case UserExtent: try { @@ -167,6 +178,9 @@ void QgsExtentGroupBox::updateTitle() case ProjectLayerExtent: msg = mExtentLayerName; break; + case DrawOnCanvas: + msg = tr( "drawn on canvas" ); + break; } if ( isCheckable() && !isChecked() ) msg = tr( "none" ); @@ -213,7 +227,17 @@ void QgsExtentGroupBox::setExtentToLayerExtent( const QString &layerId ) void QgsExtentGroupBox::setOutputExtentFromCurrent() { - setOutputExtent( mCurrentExtent, mCurrentCrs, CurrentExtent ); + if ( mCanvas ) + { + // Use unrotated visible extent to insure output size and scale matches canvas + QgsMapSettings ms = mCanvas->mapSettings(); + ms.setRotation( 0 ); + setOutputExtent( ms.visibleExtent(), ms.destinationCrs(), CurrentExtent ); + } + else + { + setOutputExtent( mCurrentExtent, mCurrentCrs, CurrentExtent ); + } } @@ -238,6 +262,34 @@ void QgsExtentGroupBox::setOutputExtentFromLayer( const QgsMapLayer *layer ) setOutputExtent( layer->extent(), layer->crs(), ProjectLayerExtent ); } +void QgsExtentGroupBox::setOutputExtentFromDrawOnCanvas() +{ + if ( mCanvas ) + { + mMapToolPrevious = mCanvas->mapTool(); + if ( !mMapToolExtent ) + { + mMapToolExtent.reset( new QgsMapToolExtent( mCanvas ) ); + connect( mMapToolExtent.get(), &QgsMapToolExtent::extentChanged, this, &QgsExtentGroupBox::extentDrawn ); + connect( mMapToolExtent.get(), &QgsMapTool::deactivated, this, [ = ] + { + window()->setVisible( true ); + mMapToolPrevious = nullptr; + } ); + } + mCanvas->setMapTool( mMapToolExtent.get() ); + window()->setVisible( false ); + } +} + +void QgsExtentGroupBox::extentDrawn( const QgsRectangle &extent ) +{ + setOutputExtent( extent, mCanvas->mapSettings().destinationCrs(), DrawOnCanvas ); + mCanvas->setMapTool( mMapToolPrevious ); + window()->setVisible( true ); + mMapToolPrevious = nullptr; +} + void QgsExtentGroupBox::groupBoxClicked() { if ( !isCheckable() ) @@ -269,3 +321,16 @@ QString QgsExtentGroupBox::titleBase() const { return mTitleBase; } + +void QgsExtentGroupBox::setMapCanvas( QgsMapCanvas *canvas ) +{ + if ( canvas ) + { + mCanvas = canvas; + mButtonDrawOnCanvas->setVisible( true ); + } + else + { + mButtonDrawOnCanvas->setVisible( false ); + } +} diff --git a/src/gui/qgsextentgroupbox.h b/src/gui/qgsextentgroupbox.h index 67053bc91b2..e6d7d9fe0db 100644 --- a/src/gui/qgsextentgroupbox.h +++ b/src/gui/qgsextentgroupbox.h @@ -12,10 +12,13 @@ * (at your option) any later version. * * * ***************************************************************************/ + #ifndef QGSEXTENTGROUPBOX_H #define QGSEXTENTGROUPBOX_H #include "qgscollapsiblegroupbox.h" +#include "qgsmaptool.h" +#include "qgsmaptoolextent.h" #include "qgis.h" #include "ui_qgsextentgroupboxwidget.h" @@ -24,6 +27,8 @@ #include "qgsrectangle.h" #include "qgis_gui.h" +#include + class QgsCoordinateReferenceSystem; class QgsMapLayerModel; class QgsMapLayer; @@ -52,6 +57,7 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui:: CurrentExtent, //!< Map canvas extent UserExtent, //!< Extent manually entered/modified by the user ProjectLayerExtent, //!< Extent taken from a layer within the project + DrawOnCanvas, //!< Extent taken from a rectangled drawn onto the map canvas }; /** @@ -135,6 +141,13 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui:: */ QString titleBase() const; + /** + * Sets the map canvas to enable dragging of extent on a canvas. + * \param canvas the map canvas + * \since QGIS 3.0 + */ + void setMapCanvas( QgsMapCanvas *canvas ); + public slots: /** @@ -158,6 +171,12 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui:: */ void setOutputExtentFromLayer( const QgsMapLayer *layer ); + /** + * Sets the output extent by dragging on the canvas. + * \since QGIS 3.0 + */ + void setOutputExtentFromDrawOnCanvas(); + signals: /** @@ -175,6 +194,8 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui:: void groupBoxClicked(); void layerMenuAboutToShow(); + void extentDrawn( const QgsRectangle &extent ); + private: void setOutputExtent( const QgsRectangle &r, const QgsCoordinateReferenceSystem &srcCrs, QgsExtentGroupBox::ExtentState state ); void setOutputExtentFromLineEdit(); @@ -199,6 +220,10 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui:: QPointer< const QgsMapLayer > mExtentLayer; QString mExtentLayerName; + std::unique_ptr< QgsMapToolExtent > mMapToolExtent; + QPointer< QgsMapTool > mMapToolPrevious = nullptr; + QgsMapCanvas *mCanvas = nullptr; + void setExtentToLayerExtent( const QString &layerId ); }; diff --git a/src/gui/qgsmaptoolextent.cpp b/src/gui/qgsmaptoolextent.cpp new file mode 100644 index 00000000000..118a3e010af --- /dev/null +++ b/src/gui/qgsmaptoolextent.cpp @@ -0,0 +1,125 @@ +/*************************************************************************** + qgsmaptoolextent.h - map tool that emits an extent + --------------------- + begin : July 2017 + copyright : (C) 2017 by Mathieu Pellerin + email : nirvn dot asia 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 "qgsmaptoolextent.h" +#include "qgsmapcanvas.h" +#include "qgswkbtypes.h" + +#include + + +QgsMapToolExtent::QgsMapToolExtent( QgsMapCanvas *canvas ) + : QgsMapTool( canvas ) +{ + mRubberBand.reset( new QgsRubberBand( canvas, QgsWkbTypes::PolygonGeometry ) ); +} + +void QgsMapToolExtent::activate() +{ + QgsMapTool::activate(); +} + +void QgsMapToolExtent::deactivate() +{ + mRubberBand->reset( QgsWkbTypes::PolygonGeometry ); + + QgsMapTool::deactivate(); +} + +void QgsMapToolExtent::canvasMoveEvent( QgsMapMouseEvent *e ) +{ + if ( !mDraw ) + return; + + + QgsPointXY p = toMapCoordinates( e->pos() ); + if ( mRatio.width() > 0 && mRatio.height() > 0 ) + { + double width = qAbs( p.x() - mStartPoint.x() ); + double height = width * ( mRatio.width() / mRatio.height() ); + if ( p.y() - mStartPoint.y() < 0 ) + height *= -1; + p.setY( mStartPoint.y() + height ); + } + + mEndPoint = toMapCoordinates( e->pos() ); + calculateEndPoint( mEndPoint ); + drawExtent(); +} + +void QgsMapToolExtent::canvasPressEvent( QgsMapMouseEvent *e ) +{ + mStartPoint = toMapCoordinates( e->pos() ); + mEndPoint = mStartPoint; + drawExtent(); + + mDraw = true; +} + +void QgsMapToolExtent::canvasReleaseEvent( QgsMapMouseEvent *e ) +{ + if ( !mDraw ) + return; + + mEndPoint = toMapCoordinates( e->pos() ); + calculateEndPoint( mEndPoint ); + drawExtent(); + + emit extentChanged( extent() ); + + mDraw = false; +} + +QgsRectangle QgsMapToolExtent::extent() const +{ + if ( mStartPoint.x() != mEndPoint.x() && mStartPoint.y() != mEndPoint.y() ) + { + return QgsRectangle( mStartPoint, mEndPoint ); + } + else + { + return QgsRectangle(); + } +} + +void QgsMapToolExtent::calculateEndPoint( QgsPointXY &point ) +{ + if ( mRatio.width() > 0 && mRatio.height() > 0 ) + { + double width = qAbs( point.x() - mStartPoint.x() ); + double height = width * mRatio.height() / mRatio.width(); + if ( point.y() - mStartPoint.y() < 0 ) + height *= -1; + point.setY( mStartPoint.y() + height ); + } +} + +void QgsMapToolExtent::drawExtent() +{ + if ( qgsDoubleNear( mStartPoint.x(), mEndPoint.x() ) && qgsDoubleNear( mStartPoint.y(), mEndPoint.y() ) ) + return; + + mRubberBand->reset( QgsWkbTypes::PolygonGeometry ); + + QgsRectangle rect( mStartPoint, mEndPoint ); + + mRubberBand->reset( QgsWkbTypes::PolygonGeometry ); + mRubberBand->addPoint( QgsPointXY( rect.xMinimum(), rect.yMinimum() ), false ); + mRubberBand->addPoint( QgsPointXY( rect.xMaximum(), rect.yMinimum() ), false ); + mRubberBand->addPoint( QgsPointXY( rect.xMaximum(), rect.yMaximum() ), false ); + mRubberBand->addPoint( QgsPointXY( rect.xMinimum(), rect.yMaximum() ), true ); + + mRubberBand->show(); +} diff --git a/src/gui/qgsmaptoolextent.h b/src/gui/qgsmaptoolextent.h new file mode 100644 index 00000000000..71a4330ba3a --- /dev/null +++ b/src/gui/qgsmaptoolextent.h @@ -0,0 +1,86 @@ +/*************************************************************************** + qgsmaptoolextent.h - map tool that emits an extent + --------------------- + begin : July 2017 + copyright : (C) 2017 by Mathieu Pellerin + email : nirvn dot asia 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 QGSMAPTOOLEXTENT_H +#define QGSMAPTOOLEXTENT_H + +#include "qgsmaptool.h" +#include "qgspointxy.h" +#include "qgsrubberband.h" +#include "qgis_gui.h" + +#include + +class QgsMapCanvas; + + +/** \ingroup gui + * A map tool that emits an extent from a rectangle drawn onto the map canvas. + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsMapToolExtent : public QgsMapTool +{ + Q_OBJECT + + public: + + //! constructor + QgsMapToolExtent( QgsMapCanvas *canvas ); + + virtual Flags flags() const override { return QgsMapTool::AllowZoomRect; } + virtual void canvasMoveEvent( QgsMapMouseEvent *e ) override; + virtual void canvasPressEvent( QgsMapMouseEvent *e ) override; + virtual void canvasReleaseEvent( QgsMapMouseEvent *e ) override; + virtual void activate() override; + virtual void deactivate() override; + + /** Sets a fixed aspect ratio to be used when dragging extent onto the canvas. + * To unset a fixed aspect ratio, set the width and height to zero. + * \param ratio aspect ratio's width and height + * */ + void setRatio( QSize ratio ) { mRatio = ratio; } + + /** Returns the current fixed aspect ratio to be used when dragging extent onto the canvas. + * If the aspect ratio isn't fixed, the width and height will be set to zero. + * */ + QSize ratio() const { return mRatio; } + + /** Returns the current extent drawn onto the canvas. + */ + QgsRectangle extent() const; + + signals: + + //! signal emitted on extent change + void extentChanged( const QgsRectangle &extent ); + + private: + + void calculateEndPoint( QgsPointXY &point ); + + void drawExtent(); + + std::unique_ptr< QgsRubberBand > mRubberBand; + + QgsPointXY mStartPoint; + QgsPointXY mEndPoint; + + bool mDraw = false; + + QSize mRatio; + +}; + +#endif diff --git a/src/ui/qgsextentgroupboxwidget.ui b/src/ui/qgsextentgroupboxwidget.ui index 7b1d0d9e812..b86ee1983e6 100755 --- a/src/ui/qgsextentgroupboxwidget.ui +++ b/src/ui/qgsextentgroupboxwidget.ui @@ -155,6 +155,19 @@ + + + + + 0 + 0 + + + + Draw on canvas + + +
From 11cfc78a246e19de67de60000cfbc99f6a2288be Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 22:13:50 +1000 Subject: [PATCH 073/266] Skip invalid returned features --- python/core/processing/qgsprocessingalgorithm.sip | 5 ++++- src/core/processing/qgsprocessingalgorithm.cpp | 3 ++- src/core/processing/qgsprocessingalgorithm.h | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index c5d12ad85d3..a0a72d869fa 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -827,7 +827,10 @@ class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm geometry with the centroid of the original feature geometry for a 'centroid' type algorithm). - Implementations should return the modified feature. + Implementations should return the modified feature. Returning an invalid feature (e.g. + a default constructed QgsFeature) will indicate that this feature should be 'skipped', + and will not be added to the algorithm's output. Subclasses can use this approach to + filter the incoming features as desired. The provided ``feedback`` object can be used to push messages to the log and for giving feedback to users. Note that handling of progress reports and algorithm cancelation is handled by diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index ce6b99cb098..ad49eaf37f3 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -656,7 +656,8 @@ QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariant } QgsFeature transformed = processFeature( f, feedback ); - sink->addFeature( transformed, QgsFeatureSink::FastInsert ); + if ( transformed.isValid() ) + sink->addFeature( transformed, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index bdb4da6f5f2..7d061dced2e 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -798,7 +798,10 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor * geometry with the centroid of the original feature geometry for a 'centroid' type * algorithm). * - * Implementations should return the modified feature. + * Implementations should return the modified feature. Returning an invalid feature (e.g. + * a default constructed QgsFeature) will indicate that this feature should be 'skipped', + * and will not be added to the algorithm's output. Subclasses can use this approach to + * filter the incoming features as desired. * * The provided \a feedback object can be used to push messages to the log and for giving feedback * to users. Note that handling of progress reports and algorithm cancelation is handled by From e423eea2cf9c18855aed2a212f460c747ae84304 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 18 Jul 2017 16:13:41 +0200 Subject: [PATCH 074/266] [feature][needs-docs] XYZ authentication and referer added to GUI Fixes #16883 --- src/providers/wms/qgsxyzconnection.cpp | 16 ++ src/providers/wms/qgsxyzconnection.h | 8 + src/providers/wms/qgsxyzconnectiondialog.cpp | 21 +- src/providers/wms/qgsxyzconnectiondialog.h | 8 + src/ui/qgsxyzconnectiondialog.ui | 262 +++++++++++++------ 5 files changed, 241 insertions(+), 74 deletions(-) diff --git a/src/providers/wms/qgsxyzconnection.cpp b/src/providers/wms/qgsxyzconnection.cpp index 471e5552010..2d623b1e939 100644 --- a/src/providers/wms/qgsxyzconnection.cpp +++ b/src/providers/wms/qgsxyzconnection.cpp @@ -27,6 +27,14 @@ QString QgsXyzConnection::encodedUri() const uri.setParam( QStringLiteral( "zmin" ), QString::number( zMin ) ); if ( zMax != -1 ) uri.setParam( QStringLiteral( "zmax" ), QString::number( zMax ) ); + if ( ! authCfg.isEmpty() ) + uri.setParam( QStringLiteral( "authcfg" ), authCfg ); + if ( ! username.isEmpty() ) + uri.setParam( QStringLiteral( "username" ), username ); + if ( ! password.isEmpty() ) + uri.setParam( QStringLiteral( "password" ), password ); + if ( ! referer.isEmpty() ) + uri.setParam( QStringLiteral( "referer" ), referer ); return uri.encodedUri(); } @@ -47,6 +55,10 @@ QgsXyzConnection QgsXyzConnectionUtils::connection( const QString &name ) conn.url = settings.value( QStringLiteral( "url" ) ).toString(); conn.zMin = settings.value( QStringLiteral( "zmin" ), -1 ).toInt(); conn.zMax = settings.value( QStringLiteral( "zmax" ), -1 ).toInt(); + conn.authCfg = settings.value( QStringLiteral( "authcfg" ) ).toString(); + conn.username = settings.value( QStringLiteral( "username" ) ).toString(); + conn.password = settings.value( QStringLiteral( "password" ) ).toString(); + conn.referer = settings.value( QStringLiteral( "referer" ) ).toString(); return conn; } @@ -63,4 +75,8 @@ void QgsXyzConnectionUtils::addConnection( const QgsXyzConnection &conn ) settings.setValue( QStringLiteral( "url" ), conn.url ); settings.setValue( QStringLiteral( "zmin" ), conn.zMin ); settings.setValue( QStringLiteral( "zmax" ), conn.zMax ); + settings.setValue( QStringLiteral( "authcfg" ), conn.authCfg ); + settings.setValue( QStringLiteral( "username" ), conn.username ); + settings.setValue( QStringLiteral( "password" ), conn.password ); + settings.setValue( QStringLiteral( "referer" ), conn.referer ); } diff --git a/src/providers/wms/qgsxyzconnection.h b/src/providers/wms/qgsxyzconnection.h index 02b5ebc0ae9..d41548f74b3 100644 --- a/src/providers/wms/qgsxyzconnection.h +++ b/src/providers/wms/qgsxyzconnection.h @@ -24,6 +24,14 @@ struct QgsXyzConnection QString url; int zMin = -1; int zMax = -1; + // Authentication configuration id + QString authCfg; + // HTTP Basic username + QString username; + // HTTP Basic password + QString password; + // Referer + QString referer; QString encodedUri() const; }; diff --git a/src/providers/wms/qgsxyzconnectiondialog.cpp b/src/providers/wms/qgsxyzconnectiondialog.cpp index fce172a1539..a9666e2de26 100644 --- a/src/providers/wms/qgsxyzconnectiondialog.cpp +++ b/src/providers/wms/qgsxyzconnectiondialog.cpp @@ -13,15 +13,18 @@ * * ***************************************************************************/ +#include "qgsauthconfigselect.h" #include "qgsxyzconnectiondialog.h" - #include "qgsxyzconnection.h" QgsXyzConnectionDialog::QgsXyzConnectionDialog( QWidget *parent ) : QDialog( parent ) + , mAuthConfigSelect( nullptr ) { setupUi( this ); + mAuthConfigSelect = new QgsAuthConfigSelect( this ); + mTabAuth->insertTab( 1, mAuthConfigSelect, tr( "Configurations" ) ); // Behavior for min and max zoom checkbox connect( mCheckBoxZMin, &QCheckBox::toggled, mSpinZMin, &QSpinBox::setEnabled ); connect( mCheckBoxZMax, &QCheckBox::toggled, mSpinZMax, &QSpinBox::setEnabled ); @@ -35,6 +38,18 @@ void QgsXyzConnectionDialog::setConnection( const QgsXyzConnection &conn ) mSpinZMin->setValue( conn.zMin != -1 ? conn.zMin : 0 ); mCheckBoxZMax->setChecked( conn.zMax != -1 ); mSpinZMax->setValue( conn.zMax != -1 ? conn.zMax : 18 ); + mEditUsername->setText( conn.username ); + mEditPassword->setText( conn.password ); + mEditReferer->setText( conn.referer ); + mAuthConfigSelect->setConfigId( conn.authCfg ); + if ( ! conn.authCfg.isEmpty( ) ) + { + mTabAuth->setCurrentIndex( mTabAuth->indexOf( mAuthConfigSelect ) ); + } + else + { + mTabAuth->setCurrentIndex( 0 ); + } } QgsXyzConnection QgsXyzConnectionDialog::connection() const @@ -46,5 +61,9 @@ QgsXyzConnection QgsXyzConnectionDialog::connection() const conn.zMin = mSpinZMin->value(); if ( mCheckBoxZMax->isChecked() ) conn.zMax = mSpinZMax->value(); + conn.username = mEditUsername->text(); + conn.password = mEditPassword->text(); + conn.referer = mEditReferer->text(); + conn.authCfg = mAuthConfigSelect->configId( ); return conn; } diff --git a/src/providers/wms/qgsxyzconnectiondialog.h b/src/providers/wms/qgsxyzconnectiondialog.h index e768d67a687..123084a112f 100644 --- a/src/providers/wms/qgsxyzconnectiondialog.h +++ b/src/providers/wms/qgsxyzconnectiondialog.h @@ -20,6 +20,8 @@ #include "ui_qgsxyzconnectiondialog.h" +class QgsAuthConfigSelect; + struct QgsXyzConnection; @@ -33,6 +35,12 @@ class QgsXyzConnectionDialog : public QDialog, public Ui::QgsXyzConnectionDialog QgsXyzConnection connection() const; + private: + + QString mBaseKey; + QString mCredentialsBaseKey; + QgsAuthConfigSelect *mAuthConfigSelect = nullptr; + }; #endif // QGSXYZCONNECTIONDIALOG_H diff --git a/src/ui/qgsxyzconnectiondialog.ui b/src/ui/qgsxyzconnectiondialog.ui index 50617c3d300..3a64174854a 100644 --- a/src/ui/qgsxyzconnectiondialog.ui +++ b/src/ui/qgsxyzconnectiondialog.ui @@ -6,8 +6,8 @@ 0 0 - 746 - 426 + 869 + 787 @@ -15,75 +15,186 @@ - - - - - http://example.com/{z}/{x}/{y}.png - - - - - - - URL - - - - - - - Min. Zoom Level - - - true - - - - - - - Name - - - - - - - - 0 - 0 - - - - - - - - Max. Zoom Level - - - true - - - - - - - - 0 - 0 - - - - 18 - - - - - - - + + + Connection details + + + + + + Min. Zoom Level + + + true + + + + + + + + 0 + 0 + + + + 0 + + + + Authentication + + + + + + &User name + + + mEditUsername + + + + + + + + + + + 0 + 0 + + + + If the service requires basic authentication, enter a user name and optional password + + + Qt::PlainText + + + true + + + + + + + Password + + + mEditPassword + + + + + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + Name of the new connection + + + + + + + + 0 + 0 + + + + 18 + + + + + + + + 0 + 0 + + + + + + + + URL + + + + + + + Name + + + + + + + URL of the connection, {z}, {y}, and {z} will be replaced with actual values. Use {-y} for inverted y axis. + + + http://example.com/{z}/{x}/{y}.png + + + + + + + Max. Zoom Level + + + true + + + + + + + Referer + + + mEditReferer + + + + + + + Optional custom referer + + + + + @@ -110,13 +221,18 @@ + + + QgsPasswordLineEdit + QLineEdit +
qgspasswordlineedit.h
+
+
- mEditName mEditUrl mCheckBoxZMin mSpinZMin mCheckBoxZMax - mSpinZMax buttonBox From efff4f03766dfacf6d03fa573e0f77a361e0dc3b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 19 Jul 2017 09:09:51 +1000 Subject: [PATCH 075/266] Fix windows build --- src/core/processing/qgsprocessingalgorithm.h | 1 + src/core/processing/qgsprocessingparameters.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 7d061dced2e..490b520a3c7 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -23,6 +23,7 @@ #include "qgsprocessingparameters.h" #include "qgsprocessingoutputs.h" #include "qgsprocessingcontext.h" +#include "qgsfeaturesource.h" #include #include #include diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index f109621ebd7..8ba27a0764f 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -23,6 +23,7 @@ #include "qgsprocessing.h" #include "qgsproperty.h" #include "qgscoordinatereferencesystem.h" +#include "qgsfeaturesource.h" #include #include @@ -30,7 +31,6 @@ class QgsProcessingContext; class QgsRasterLayer; class QgsVectorLayer; class QgsFeatureSink; -class QgsFeatureSource; class QgsProcessingFeatureSource; class QgsProcessingOutputDefinition; class QgsProcessingFeedback; From e7700db46ad6bc717a4826157074fb8a5aee5edb Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Tue, 18 Jul 2017 00:22:20 +0200 Subject: [PATCH 076/266] oracle provider: skip updating of key attributes of added features for versioned tables --- src/providers/oracle/qgsoracleprovider.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp index d8bbd8e1b2e..207635ddb81 100644 --- a/src/providers/oracle/qgsoracleprovider.cpp +++ b/src/providers/oracle/qgsoracleprovider.cpp @@ -1186,6 +1186,16 @@ bool QgsOracleProvider::addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flag bool returnvalue = true; + if ( !( flags & QgsFeatureSink::FastInsert ) && !getWorkspace().isEmpty() && getWorkspace().toUpper() != "LIVE" ) + { + static bool warn = true; + if ( warn ) + { + QgsMessageLog::logMessage( tr( "Retrieval of updated primary keys from versioned tables not supported" ), tr( "Oracle" ) ); + warn = false; + } + flags |= QgsFeatureSink::FastInsert; + } QSqlDatabase db( *mConnection ); From f814ba04c345e402c6b61bf9f2a9aefaaf8deefb Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Wed, 19 Jul 2017 09:18:34 +0200 Subject: [PATCH 077/266] fix oracle provider build (followup c4e26d72) --- src/gui/qgsowssourceselect.cpp | 8 +++--- src/providers/db2/qgsdb2sourceselect.cpp | 10 ++++---- .../qgsdelimitedtextsourceselect.cpp | 8 +++--- src/providers/mssql/qgsmssqlsourceselect.cpp | 16 ++++++------ .../oracle/qgsoraclesourceselect.cpp | 25 ++++++++----------- src/providers/postgres/qgspgsourceselect.cpp | 15 ++++------- .../spatialite/qgsspatialitesourceselect.cpp | 8 +++--- .../virtual/qgsvirtuallayersourceselect.cpp | 10 ++++---- src/providers/wfs/qgswfssourceselect.cpp | 8 +++--- src/providers/wms/qgswmssourceselect.cpp | 12 ++++----- 10 files changed, 56 insertions(+), 64 deletions(-) diff --git a/src/gui/qgsowssourceselect.cpp b/src/gui/qgsowssourceselect.cpp index d2a93e30715..2d906ca7308 100644 --- a/src/gui/qgsowssourceselect.cpp +++ b/src/gui/qgsowssourceselect.cpp @@ -54,14 +54,14 @@ #include #include -QgsOWSSourceSelect::QgsOWSSourceSelect( const QString &service, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsOWSSourceSelect::QgsOWSSourceSelect( const QString &service, QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mService( service ) , mCurrentTileset( nullptr ) { setupUi( this ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); } @@ -88,7 +88,7 @@ QgsOWSSourceSelect::QgsOWSSourceSelect( const QString &service, QWidget *parent, // 'Prefer network' is the default noted in the combobox's tool tip mCacheComboBox->setCurrentIndex( mCacheComboBox->findData( QNetworkRequest::PreferNetwork ) ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { connect( mAddButton, &QAbstractButton::clicked, this, &QgsOWSSourceSelect::addClicked ); //set the current project CRS if available diff --git a/src/providers/db2/qgsdb2sourceselect.cpp b/src/providers/db2/qgsdb2sourceselect.cpp index e7e55c7e1e0..32dd867c6bf 100644 --- a/src/providers/db2/qgsdb2sourceselect.cpp +++ b/src/providers/db2/qgsdb2sourceselect.cpp @@ -116,8 +116,8 @@ void QgsDb2SourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMod model->setData( index, le->text() ); } -QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mColumnTypeThread( NULL ) , mUseEstimatedMetadata( false ) { @@ -125,7 +125,7 @@ QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs setWindowTitle( tr( "Add Db2 Table(s)" ) ); - if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -138,7 +138,7 @@ QgsDb2SourceSelect::QgsDb2SourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsDb2SourceSelect::addTables ); @@ -458,7 +458,7 @@ void QgsDb2SourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "DB2" ) ); - if ( !mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode() == QgsProviderRegistry::WidgetMode::None ) + if ( !mHoldDialogOpen->isChecked() && widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp index 71dfb30199e..4bcc33659a6 100644 --- a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp +++ b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp @@ -34,8 +34,8 @@ const int MAX_SAMPLE_LENGTH = 200; -QgsDelimitedTextSourceSelect::QgsDelimitedTextSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsDelimitedTextSourceSelect::QgsDelimitedTextSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mFile( new QgsDelimitedTextFile() ) , mExampleRowCount( 20 ) , mBadRowCount( 0 ) @@ -48,7 +48,7 @@ QgsDelimitedTextSourceSelect::QgsDelimitedTextSourceSelect( QWidget *parent, Qt: QgsSettings settings; restoreGeometry( settings.value( mPluginKey + "/geometry" ).toByteArray() ); - if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Cancel ) ); buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add" ) ); @@ -199,7 +199,7 @@ void QgsDelimitedTextSourceSelect::on_buttonBox_accepted() // add the layer to the map emit addVectorLayer( QString::fromAscii( url.toEncoded() ), txtLayerName->text(), QStringLiteral( "delimitedtext" ) ); - if ( QgsAbstractDataSourceWidget::widgetMode() == QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/mssql/qgsmssqlsourceselect.cpp b/src/providers/mssql/qgsmssqlsourceselect.cpp index fd14526cc6e..0c698383ab8 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.cpp +++ b/src/providers/mssql/qgsmssqlsourceselect.cpp @@ -118,20 +118,22 @@ void QgsMssqlSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemM model->setData( index, le->text() ); } -QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mColumnTypeThread( nullptr ) , mUseEstimatedMetadata( false ) { setupUi( this ); - setWindowTitle( tr( "Add MSSQL Table(s)" ) ); - - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); } + else + { + setWindowTitle( tr( "Add MSSQL Table(s)" ) ); + } mAddButton = new QPushButton( tr( "&Add" ) ); mAddButton->setEnabled( false ); @@ -140,7 +142,7 @@ QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WindowFlags fl, mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsMssqlSourceSelect::addTables ); @@ -453,7 +455,7 @@ void QgsMssqlSourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "mssql" ) ); - if ( !mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode() == QgsProviderRegistry::WidgetMode::None ) + if ( !mHoldDialogOpen->isChecked() && widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/oracle/qgsoraclesourceselect.cpp b/src/providers/oracle/qgsoraclesourceselect.cpp index 52cc9db9446..c8be1b1a8f7 100644 --- a/src/providers/oracle/qgsoraclesourceselect.cpp +++ b/src/providers/oracle/qgsoraclesourceselect.cpp @@ -166,14 +166,14 @@ void QgsOracleSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItem } } -QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mColumnTypeThread( 0 ) , mIsConnected( false ) { setupUi( this ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::Embedded ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -190,7 +190,7 @@ QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WindowFlags f mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( mWidgetMode != QgsProviderRegistry::WidgetMode::Manager ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsOracleSourceSelect::addTables ); @@ -357,7 +357,7 @@ void QgsOracleSourceSelect::on_mTablesTreeView_clicked( const QModelIndex &index void QgsOracleSourceSelect::on_mTablesTreeView_doubleClicked( const QModelIndex &index ) { QgsSettings settings; - if ( settings.value( "qgis/addOracleDC", false ).toBool() ) + if ( settings.value( QStringLiteral( "qgis/addOracleDC" ), false ).toBool() ) { addTables(); } @@ -443,12 +443,12 @@ QgsOracleSourceSelect::~QgsOracleSourceSelect() } QgsSettings settings; - settings.setValue( "/Windows/OracleSourceSelect/geometry", saveGeometry() ); - settings.setValue( "/Windows/OracleSourceSelect/HoldDialogOpen", mHoldDialogOpen->isChecked() ); + settings.setValue( QStringLiteral( "/Windows/OracleSourceSelect/geometry" ), saveGeometry() ); + settings.setValue( QStringLiteral( "/Windows/OracleSourceSelect/HoldDialogOpen" ), mHoldDialogOpen->isChecked() ); for ( int i = 0; i < mTableModel.columnCount(); i++ ) { - settings.setValue( QString( "/Windows/OracleSourceSelect/columnWidths/%1" ).arg( i ), mTablesTreeView->columnWidth( i ) ); + settings.setValue( QStringLiteral( "Windows/OracleSourceSelect/columnWidths/%1" ).arg( i ), mTablesTreeView->columnWidth( i ) ); } } @@ -492,8 +492,8 @@ void QgsOracleSourceSelect::addTables() } else { - emit addDatabaseLayers( mSelectedTables, "oracle" ); - if ( !mHoldDialogOpen->isChecked() && mWidgetMode == QgsProviderRegistry::WidgetMode::None ) + emit addDatabaseLayers( mSelectedTables, QStringLiteral( "oracle" ) ); + if ( !mHoldDialogOpen->isChecked() && widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } @@ -541,11 +541,6 @@ void QgsOracleSourceSelect::finishList() { QApplication::restoreOverrideCursor(); -#if 0 - for ( int i = 0; i < QgsOracleTableModel::DbtmColumns; i++ ) - mTablesTreeView->resizeColumnToContents( i ); -#endif - mTablesTreeView->sortByColumn( QgsOracleTableModel::DbtmTable, Qt::AscendingOrder ); mTablesTreeView->sortByColumn( QgsOracleTableModel::DbtmOwner, Qt::AscendingOrder ); } diff --git a/src/providers/postgres/qgspgsourceselect.cpp b/src/providers/postgres/qgspgsourceselect.cpp index 5b64631ba7b..099940f26f4 100644 --- a/src/providers/postgres/qgspgsourceselect.cpp +++ b/src/providers/postgres/qgspgsourceselect.cpp @@ -193,14 +193,14 @@ void QgsPgSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemMode } } -QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mColumnTypeThread( nullptr ) , mUseEstimatedMetadata( false ) { setupUi( this ); - if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -217,7 +217,7 @@ QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsPr mBuildQueryButton->setToolTip( tr( "Set Filter" ) ); mBuildQueryButton->setDisabled( true ); - if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsPgSourceSelect::addTables ); @@ -508,7 +508,7 @@ void QgsPgSourceSelect::addTables() else { emit addDatabaseLayers( mSelectedTables, QStringLiteral( "postgres" ) ); - if ( ! mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) + if ( !mHoldDialogOpen->isChecked() && widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } @@ -557,11 +557,6 @@ void QgsPgSourceSelect::finishList() { QApplication::restoreOverrideCursor(); -#if 0 - for ( int i = 0; i < QgsPgTableModel::DbtmColumns; i++ ) - mTablesTreeView->resizeColumnToContents( i ); -#endif - mTablesTreeView->sortByColumn( QgsPgTableModel::DbtmTable, Qt::AscendingOrder ); mTablesTreeView->sortByColumn( QgsPgTableModel::DbtmSchema, Qt::AscendingOrder ); } diff --git a/src/providers/spatialite/qgsspatialitesourceselect.cpp b/src/providers/spatialite/qgsspatialitesourceselect.cpp index 4c7447cc128..22151354090 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.cpp +++ b/src/providers/spatialite/qgsspatialitesourceselect.cpp @@ -40,8 +40,8 @@ email : a.furieri@lqt.it #define strcasecmp(a,b) stricmp(a,b) #endif -QgsSpatiaLiteSourceSelect::QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ): - QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsSpatiaLiteSourceSelect::QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ): + QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) { setupUi( this ); @@ -66,7 +66,7 @@ QgsSpatiaLiteSourceSelect::QgsSpatiaLiteSourceSelect( QWidget *parent, Qt::Windo connect( mBuildQueryButton, &QAbstractButton::clicked, this, &QgsSpatiaLiteSourceSelect::buildQuery ); mBuildQueryButton->setEnabled( false ); - if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Close ) ); mHoldDialogOpen->hide(); @@ -417,7 +417,7 @@ void QgsSpatiaLiteSourceSelect::addTables() else { emit addDatabaseLayers( m_selectedTables, QStringLiteral( "spatialite" ) ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None && ! mHoldDialogOpen->isChecked() ) + if ( widgetMode() == QgsProviderRegistry::WidgetMode::None && ! mHoldDialogOpen->isChecked() ) { accept(); } diff --git a/src/providers/virtual/qgsvirtuallayersourceselect.cpp b/src/providers/virtual/qgsvirtuallayersourceselect.cpp index e828f69f2f1..8d8ccf00ecf 100644 --- a/src/providers/virtual/qgsvirtuallayersourceselect.cpp +++ b/src/providers/virtual/qgsvirtuallayersourceselect.cpp @@ -44,7 +44,7 @@ QgsVirtualLayerSourceSelect::QgsVirtualLayerSourceSelect( QWidget *parent, Qt::W { setupUi( this ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Cancel ) ); buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add" ) ); @@ -70,7 +70,7 @@ QgsVirtualLayerSourceSelect::QgsVirtualLayerSourceSelect( QWidget *parent, Qt::W } // It needs to find the layertree view without relying on the parent // being the main window - for ( const QWidget *widget : qApp->allWidgets( ) ) + for ( const QWidget *widget : qApp->allWidgets() ) { if ( ! mTreeView ) { @@ -137,7 +137,7 @@ void QgsVirtualLayerSourceSelect::onLayerComboChanged( int idx ) } // Clear embedded layers table - mLayersTable->model()->removeRows( 0, mLayersTable->model()->rowCount( ) ); + mLayersTable->model()->removeRows( 0, mLayersTable->model()->rowCount() ); // Add embedded layers Q_FOREACH ( const QgsVirtualLayerDefinition::SourceLayer &l, def.sourceLayers() ) { @@ -380,9 +380,9 @@ void QgsVirtualLayerSourceSelect::on_buttonBox_accepted() { emit addVectorLayer( def.toString(), layerName, QStringLiteral( "virtual" ) ); } - if ( QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() == QgsProviderRegistry::WidgetMode::None ) { - accept( ); + accept(); } } diff --git a/src/providers/wfs/qgswfssourceselect.cpp b/src/providers/wfs/qgswfssourceselect.cpp index 760b84451dd..cad280e2736 100644 --- a/src/providers/wfs/qgswfssourceselect.cpp +++ b/src/providers/wfs/qgswfssourceselect.cpp @@ -48,14 +48,14 @@ enum MODEL_IDX_SQL }; -QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mCapabilities( nullptr ) , mSQLComposerDialog( nullptr ) { setupUi( this ); - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { // For some obscure reason hiding does not work! // buttonBox->button( QDialogButtonBox::Close )->hide(); @@ -408,7 +408,7 @@ void QgsWFSSourceSelect::addLayer() emit addVectorLayer( mUri, layerName ); } - if ( ! mHoldDialogOpen->isChecked() && QgsAbstractDataSourceWidget::widgetMode( ) == QgsProviderRegistry::WidgetMode::None ) + if ( ! mHoldDialogOpen->isChecked() && widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/wms/qgswmssourceselect.cpp b/src/providers/wms/qgswmssourceselect.cpp index b9ac53954e3..b707764c142 100644 --- a/src/providers/wms/qgswmssourceselect.cpp +++ b/src/providers/wms/qgswmssourceselect.cpp @@ -54,14 +54,14 @@ #include #include -QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode ) - : QgsAbstractDataSourceWidget( parent, fl, widgetMode ) +QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode theWidgetMode ) + : QgsAbstractDataSourceWidget( parent, fl, theWidgetMode ) , mDefaultCRS( GEO_EPSG_CRS_AUTHID ) , mCurrentTileset( nullptr ) { setupUi( this ); - if ( QgsAbstractDataSourceWidget::widgetMode() != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::None ) { // For some obscure reason hiding does not work! // buttonBox->button( QDialogButtonBox::Close )->hide(); @@ -80,7 +80,7 @@ QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs mImageFormatGroup = new QButtonGroup; - if ( QgsAbstractDataSourceWidget::widgetMode( ) != QgsProviderRegistry::WidgetMode::Manager ) + if ( widgetMode() != QgsProviderRegistry::WidgetMode::Manager ) { buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); connect( mAddButton, &QAbstractButton::clicked, this, &QgsWMSSourceSelect::addClicked ); @@ -155,11 +155,11 @@ QgsWMSSourceSelect::~QgsWMSSourceSelect() settings.setValue( QStringLiteral( "Windows/WMSSourceSelect/geometry" ), saveGeometry() ); } -void QgsWMSSourceSelect::refresh( ) +void QgsWMSSourceSelect::refresh() { // Reload WMS connections and update the GUI QgsDebugMsg( "Refreshing WMS connections ..." ); - populateConnectionList( ); + populateConnectionList(); } From 4f9a9e0360a3ce6b4289e948bb2549a0496f48f2 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Wed, 19 Jul 2017 09:19:37 +0200 Subject: [PATCH 078/266] s/( )/()/; s/== /== /; s/!= /!= /; --- python/core/geometry/qgsregularpolygon.sip | 24 +++++----- python/core/geometry/qgstriangle.sip | 18 ++++---- python/core/qgsbrowsermodel.sip | 2 +- python/core/qgsdataitem.sip | 2 +- python/gui/qgsabstractdatasourcewidget.sip | 8 ++-- python/gui/qgsbrowserdockwidget.sip | 2 +- python/gui/qgsbrowsertreeview.sip | 2 +- python/gui/qgsowssourceselect.sip | 2 +- python/server/qgsbufferserverrequest.sip | 4 +- python/server/qgsbufferserverresponse.sip | 2 +- python/server/qgsrequesthandler.sip | 2 +- python/server/qgsserver.sip | 2 +- python/server/qgsserverrequest.sip | 6 +-- python/server/qgsserverresponse.sip | 4 +- scripts/spell_check/check_spelling.sh | 2 +- src/app/main.cpp | 6 +-- src/app/nodetool/qgsnodeeditor.cpp | 4 +- src/app/nodetool/qgsnodeeditor.h | 2 +- src/app/qgisapp.cpp | 10 ++-- src/app/qgisapp.h | 4 +- src/app/qgsidentifyresultsdialog.cpp | 12 ++--- src/app/qgsmaptoolannotation.cpp | 4 +- src/app/qgssnappingwidget.h | 2 +- src/core/auth/qgsauthmanager.cpp | 24 +++++----- src/core/auth/qgsauthmanager.h | 2 +- src/core/composer/qgspaperitem.cpp | 4 +- src/core/expression/qgsexpressionfunction.cpp | 2 +- src/core/geometry/qgsregularpolygon.cpp | 10 ++-- src/core/geometry/qgsregularpolygon.h | 24 +++++----- src/core/geometry/qgstriangle.h | 18 ++++---- src/core/qgsbrowsermodel.h | 2 +- src/core/qgsdataitem.cpp | 4 +- src/core/qgsdataitem.h | 2 +- src/core/qgsogcutils.cpp | 2 +- src/core/qgsprojectfiletransform.cpp | 2 +- src/core/qgssettings.cpp | 14 +++--- src/core/raster/qgsrasteriterator.cpp | 2 +- .../symbology-ng/qgsmarkersymbollayer.cpp | 46 +++++++++---------- src/core/symbology-ng/qgssymbol.cpp | 34 +++++++------- src/core/symbology-ng/qgssymbollayerutils.cpp | 2 +- .../qgsattributetabledelegate.cpp | 4 +- .../qgsattributetablefiltermodel.cpp | 2 +- src/gui/auth/qgsautheditorwidgets.cpp | 4 +- src/gui/auth/qgsautheditorwidgets.h | 8 ++-- src/gui/auth/qgsauthguiutils.cpp | 2 +- src/gui/layertree/qgslayertreeview.cpp | 2 +- src/gui/ogr/qgsopenvectorlayerdialog.cpp | 4 +- src/gui/qgsabstractdatasourcewidget.h | 8 ++-- src/gui/qgsadvanceddigitizingcanvasitem.cpp | 2 +- src/gui/qgsattributeformeditorwidget.cpp | 2 +- src/gui/qgsbrowserdockwidget.h | 2 +- src/gui/qgsbrowserdockwidget_p.h | 2 +- src/gui/qgsbrowsertreeview.h | 2 +- src/gui/qgscredentialdialog.cpp | 6 +-- src/gui/qgsdatasourcemanagerdialog.cpp | 16 +++---- src/gui/qgsdatasourcemanagerdialog.h | 10 ++-- src/gui/qgsfiledownloader.cpp | 2 +- src/gui/qgsguiutils.cpp | 2 +- src/gui/qgsmessagebar.cpp | 2 +- src/gui/qgsowssourceselect.h | 2 +- src/gui/qgstaskmanagerwidget.cpp | 2 +- src/plugins/grass/qtermwidget/History.cpp | 2 +- .../grass/qtermwidget/Vt102Emulation.cpp | 6 +-- .../arcgisrest/qgsarcgisservicesourceselect.h | 2 +- src/providers/db2/qgsdb2geometrycolumns.cpp | 2 +- src/providers/db2/qgsdb2provider.cpp | 2 +- src/providers/db2/qgsdb2sourceselect.h | 2 +- .../qgsdelimitedtextsourceselect.cpp | 2 +- src/providers/grass/qgsgrassimport.cpp | 6 +-- src/providers/mssql/qgsmssqlprovider.cpp | 6 +-- src/providers/mssql/qgsmssqlsourceselect.h | 2 +- src/providers/postgres/qgspgsourceselect.h | 2 +- .../spatialite/qgsspatialitesourceselect.h | 2 +- .../virtual/qgsembeddedlayerselectdialog.cpp | 6 +-- .../virtual/qgsembeddedlayerselectdialog.h | 2 +- .../virtual/qgsvirtuallayersourceselect.cpp | 2 +- .../virtual/qgsvirtuallayersourceselect.h | 2 +- src/providers/wfs/qgswfscapabilities.cpp | 4 +- src/providers/wfs/qgswfsprovider.cpp | 2 +- src/providers/wfs/qgswfsshareddata.cpp | 2 +- src/providers/wfs/qgswfssourceselect.h | 2 +- src/providers/wms/qgswmsdataitems.cpp | 2 +- src/providers/wms/qgswmssourceselect.h | 2 +- src/server/qgsbufferserverrequest.h | 4 +- src/server/qgsbufferserverresponse.h | 2 +- src/server/qgsfcgiserverresponse.h | 2 +- src/server/qgsfilterresponsedecorator.h | 4 +- src/server/qgsrequesthandler.cpp | 8 ++-- src/server/qgsrequesthandler.h | 2 +- src/server/qgsserver.cpp | 4 +- src/server/qgsserver.h | 2 +- src/server/qgsserverrequest.cpp | 2 +- src/server/qgsserverrequest.h | 6 +-- src/server/qgsserverresponse.h | 4 +- tests/code_layout/sipifyheader.expected.sip | 4 +- tests/src/app/testqgsattributetable.cpp | 18 ++++---- tests/src/core/testqgs25drenderer.cpp | 2 +- tests/src/core/testqgsauthmanager.cpp | 18 ++++---- .../core/testqgscoordinatereferencesystem.cpp | 2 +- tests/src/core/testqgsgeometry.cpp | 12 ++--- tests/src/core/testqgsprocessing.cpp | 2 +- tests/src/core/testqgsproperty.cpp | 4 +- tests/src/gui/testqgsfiledownloader.cpp | 2 +- .../providers/grass/testqgsgrassprovider.cpp | 4 +- 104 files changed, 288 insertions(+), 288 deletions(-) diff --git a/python/core/geometry/qgsregularpolygon.sip b/python/core/geometry/qgsregularpolygon.sip index e5a9cac14b3..4294f02faad 100644 --- a/python/core/geometry/qgsregularpolygon.sip +++ b/python/core/geometry/qgsregularpolygon.sip @@ -137,45 +137,45 @@ A regular polygon is empty if radius equal to 0 or number of sides < 3 .. seealso:: numberSides() %End - QgsPointSequence points( ) const; + QgsPointSequence points() const; %Docstring Returns a list including the vertices of the regular polygon. :rtype: QgsPointSequence %End - QgsPolygonV2 *toPolygon( ) const /Factory/; + QgsPolygonV2 *toPolygon() const /Factory/; %Docstring Returns as a polygon. :rtype: QgsPolygonV2 %End - QgsLineString *toLineString( ) const /Factory/; + QgsLineString *toLineString() const /Factory/; %Docstring Returns as a linestring. :rtype: QgsLineString %End - QgsTriangle toTriangle( ) const; + QgsTriangle toTriangle() const; %Docstring Returns as a triangle. An empty triangle is returned if the regular polygon is empty or if the number of sides is different from 3. :rtype: QgsTriangle %End - QList triangulate( ) const; + QList triangulate() const; %Docstring Returns a triangulation (vertices from sides to the center) of the regular polygon. An empty list is returned if the regular polygon is empty. :rtype: list of QgsTriangle %End - QgsCircle inscribedCircle( ) const; + QgsCircle inscribedCircle() const; %Docstring Returns the inscribed circle :rtype: QgsCircle %End - QgsCircle circumscribedCircle( ) const; + QgsCircle circumscribedCircle() const; %Docstring Returns the circumscribed circle :rtype: QgsCircle @@ -188,33 +188,33 @@ A regular polygon is empty if radius equal to 0 or number of sides < 3 :rtype: str %End - double interiorAngle( ) const; + double interiorAngle() const; %Docstring Returns the measure of the interior angles in degrees. :rtype: float %End - double centralAngle( ) const; + double centralAngle() const; %Docstring Returns the measure of the central angle (the angle subtended at the center of the polygon by one of its sides) in degrees. :rtype: float %End - double area( ) const; + double area() const; %Docstring Returns the area. Returns 0 if the regular polygon is empty. :rtype: float %End - double perimeter( ) const; + double perimeter() const; %Docstring Returns the perimeter. Returns 0 if the regular polygon is empty. :rtype: float %End - double length( ) const; + double length() const; %Docstring Returns the length of a side. Returns 0 if the regular polygon is empty. diff --git a/python/core/geometry/qgstriangle.sip b/python/core/geometry/qgstriangle.sip index e3b72bd08a6..cc5482ab5d9 100644 --- a/python/core/geometry/qgstriangle.sip +++ b/python/core/geometry/qgstriangle.sip @@ -201,7 +201,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: bool %End - QVector altitudes( ) const; + QVector altitudes() const; %Docstring An altitude is a segment (defined by a QgsLineString) from a vertex to the opposite side (or, if necessary, to the extension of the opposite side). :return: Three altitudes from this triangle @@ -214,7 +214,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: list of QgsLineString %End - QVector medians( ) const; + QVector medians() const; %Docstring A median is a segment (defined by a QgsLineString) from a vertex to the midpoint of the opposite side. :return: Three medians from this triangle @@ -241,7 +241,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: list of QgsLineString %End - QgsTriangle medial( ) const; + QgsTriangle medial() const; %Docstring Medial (or midpoint) triangle of a triangle ABC is the triangle with vertices at the midpoints of the triangle's sides. :return: The medial from this triangle @@ -268,7 +268,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: QgsPoint %End - QgsPoint circumscribedCenter( ) const; + QgsPoint circumscribedCenter() const; %Docstring Center of the circumscribed circle of the triangle. :return: The center of the circumscribed circle of the triangle @@ -281,7 +281,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: QgsPoint %End - double circumscribedRadius( ) const; + double circumscribedRadius() const; %Docstring Radius of the circumscribed circle of the triangle. :return: The radius of the circumscribed circle of the triangle @@ -294,7 +294,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: float %End - QgsCircle circumscribedCircle( ) const; + QgsCircle circumscribedCircle() const; %Docstring Circumscribed circle of the triangle. @return The circumbscribed of the triangle with a QgsCircle. @@ -307,7 +307,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: QgsCircle %End - QgsPoint inscribedCenter( ) const; + QgsPoint inscribedCenter() const; %Docstring Center of the inscribed circle of the triangle. :return: The center of the inscribed circle of the triangle @@ -320,7 +320,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: QgsPoint %End - double inscribedRadius( ) const; + double inscribedRadius() const; %Docstring Radius of the inscribed circle of the triangle. :return: The radius of the inscribed circle of the triangle @@ -333,7 +333,7 @@ Inherited method not used. You cannot delete or insert a vertex directly. Return :rtype: float %End - QgsCircle inscribedCircle( ) const; + QgsCircle inscribedCircle() const; %Docstring Inscribed circle of the triangle. @return The inscribed of the triangle with a QgsCircle. diff --git a/python/core/qgsbrowsermodel.sip b/python/core/qgsbrowsermodel.sip index 1a976477891..4d14394bb56 100644 --- a/python/core/qgsbrowsermodel.sip +++ b/python/core/qgsbrowsermodel.sip @@ -139,7 +139,7 @@ Refresh item children %Docstring Emitted when item children fetch was finished %End - void connectionsChanged( ); + void connectionsChanged(); %Docstring notify the provider dialogs of a changed connection %End diff --git a/python/core/qgsdataitem.sip b/python/core/qgsdataitem.sip index 4bdb80f2432..fe47058b798 100644 --- a/python/core/qgsdataitem.sip +++ b/python/core/qgsdataitem.sip @@ -318,7 +318,7 @@ Refresh connections: update GUI and emit signal void endRemoveItems(); void dataChanged( QgsDataItem *item ); void stateChanged( QgsDataItem *item, QgsDataItem::State oldState ); - void connectionsChanged( ); + void connectionsChanged(); %Docstring open browsers %End diff --git a/python/gui/qgsabstractdatasourcewidget.sip b/python/gui/qgsabstractdatasourcewidget.sip index 8ac0f38b226..ce78e620694 100644 --- a/python/gui/qgsabstractdatasourcewidget.sip +++ b/python/gui/qgsabstractdatasourcewidget.sip @@ -26,7 +26,7 @@ class QgsAbstractDataSourceWidget : QDialog %End public: - ~QgsAbstractDataSourceWidget( ); + ~QgsAbstractDataSourceWidget(); %Docstring Destructor %End @@ -40,7 +40,7 @@ Destructor public slots: - virtual void refresh( ); + virtual void refresh(); %Docstring Triggered when the provider's connections need to be refreshed The default implementation does nothing @@ -86,13 +86,13 @@ Emitted when a progress dialog is shown by the provider dialog Constructor %End - QgsProviderRegistry::WidgetMode widgetMode( ) const; + QgsProviderRegistry::WidgetMode widgetMode() const; %Docstring Return the widget mode :rtype: QgsProviderRegistry.WidgetMode %End - const QgsMapCanvas *mapCanvas( ) const; + const QgsMapCanvas *mapCanvas() const; %Docstring Return the map canvas (can be null) :rtype: QgsMapCanvas diff --git a/python/gui/qgsbrowserdockwidget.sip b/python/gui/qgsbrowserdockwidget.sip index 89c466fd976..eab451a3be7 100644 --- a/python/gui/qgsbrowserdockwidget.sip +++ b/python/gui/qgsbrowserdockwidget.sip @@ -121,7 +121,7 @@ Emitted when a file needs to be opened %Docstring Emitted when drop uri list needs to be handled %End - void connectionsChanged( ); + void connectionsChanged(); %Docstring Connections changed in the browser %End diff --git a/python/gui/qgsbrowsertreeview.sip b/python/gui/qgsbrowsertreeview.sip index e39b6696330..9a80bf0b32f 100644 --- a/python/gui/qgsbrowsertreeview.sip +++ b/python/gui/qgsbrowsertreeview.sip @@ -29,7 +29,7 @@ class QgsBrowserTreeView : QTreeView %Docstring Set the browser model %End - QgsBrowserModel *browserModel( ); + QgsBrowserModel *browserModel(); %Docstring Return the browser model :rtype: QgsBrowserModel diff --git a/python/gui/qgsowssourceselect.sip b/python/gui/qgsowssourceselect.sip index c237d09bc22..9277935db1a 100644 --- a/python/gui/qgsowssourceselect.sip +++ b/python/gui/qgsowssourceselect.sip @@ -42,7 +42,7 @@ Constructor public slots: - virtual void refresh( ); + virtual void refresh(); %Docstring Triggered when the provider's connections need to be refreshed diff --git a/python/server/qgsbufferserverrequest.sip b/python/server/qgsbufferserverrequest.sip index 7fc2acbc23c..156fb1c37b3 100644 --- a/python/server/qgsbufferserverrequest.sip +++ b/python/server/qgsbufferserverrequest.sip @@ -21,7 +21,7 @@ class QgsBufferServerRequest : QgsServerRequest %End public: - QgsBufferServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ), QByteArray *data = 0 ); + QgsBufferServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers(), QByteArray *data = 0 ); %Docstring Constructor @@ -29,7 +29,7 @@ class QgsBufferServerRequest : QgsServerRequest \param method the request method %End - QgsBufferServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ), QByteArray *data = 0 ); + QgsBufferServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers(), QByteArray *data = 0 ); %Docstring Constructor diff --git a/python/server/qgsbufferserverresponse.sip b/python/server/qgsbufferserverresponse.sip index cbdcb056605..50891e8ac88 100644 --- a/python/server/qgsbufferserverresponse.sip +++ b/python/server/qgsbufferserverresponse.sip @@ -66,7 +66,7 @@ class QgsBufferServerResponse: QgsServerResponse \param code HTTP status code value %End - virtual int statusCode( ) const; + virtual int statusCode() const; %Docstring Return the http status code :rtype: int diff --git a/python/server/qgsrequesthandler.sip b/python/server/qgsrequesthandler.sip index 844bfdd8d5e..c718df01f03 100644 --- a/python/server/qgsrequesthandler.sip +++ b/python/server/qgsrequesthandler.sip @@ -135,7 +135,7 @@ Return request url Set response http status code %End - int statusCode( ) const; + int statusCode() const; %Docstring Return response http status code :rtype: int diff --git a/python/server/qgsserver.sip b/python/server/qgsserver.sip index 1246e38d28d..6791d9a330d 100644 --- a/python/server/qgsserver.sip +++ b/python/server/qgsserver.sip @@ -53,7 +53,7 @@ Returns a pointer to the server interface :rtype: QgsServerInterfaceImpl %End - void initPython( ); + void initPython(); %Docstring Note: not in Python bindings %End diff --git a/python/server/qgsserverrequest.sip b/python/server/qgsserverrequest.sip index 55b0da0500b..9f1a7f59df0 100644 --- a/python/server/qgsserverrequest.sip +++ b/python/server/qgsserverrequest.sip @@ -36,7 +36,7 @@ class QgsServerRequest Constructor %End - QgsServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ) ); + QgsServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers() ); %Docstring Constructor @@ -45,7 +45,7 @@ class QgsServerRequest \param headers %End - QgsServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ) ); + QgsServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers() ); %Docstring Constructor @@ -109,7 +109,7 @@ destructor @param value %End - QMap headers( ) const; + QMap headers() const; %Docstring Return the header map @return the headers map diff --git a/python/server/qgsserverresponse.sip b/python/server/qgsserverresponse.sip index b79136084e9..e225eaf1cc1 100644 --- a/python/server/qgsserverresponse.sip +++ b/python/server/qgsserverresponse.sip @@ -49,7 +49,7 @@ destructor :rtype: str %End - virtual QMap headers( ) const = 0; + virtual QMap headers() const = 0; %Docstring Return the header value :rtype: QMap @@ -68,7 +68,7 @@ destructor \param code HTTP status code value %End - virtual int statusCode( ) const = 0; + virtual int statusCode() const = 0; %Docstring Return the http status code :rtype: int diff --git a/scripts/spell_check/check_spelling.sh b/scripts/spell_check/check_spelling.sh index 5e8af163e0c..ff728fc3557 100755 --- a/scripts/spell_check/check_spelling.sh +++ b/scripts/spell_check/check_spelling.sh @@ -155,7 +155,7 @@ for I in $(seq -f '%02g' 0 $(($SPLIT-1)) ) ; do echo "*** error: could not find error in $LINE" >&2 else # if the error is not in IGNORECASE_INWORD, then it matched previous and next character (needs to remove them) - # also make error small case and escape special chars: ( ) | + # also make error small case and escape special chars: () | ERRORSMALLCASE=$(echo ${ERROR,,} |${GP}sed -r 's/\(/\\(/g' |${GP}sed -r 's/\)/\\)/g' |${GP}sed -r 's/\|/\\|/g') if [[ ! "${ERRORSMALLCASE}" =~ $IGNORECASE_INWORD ]]; then if [[ -n $(ag --nonumbers --case-sensitive "^${ERRORSMALLCASE:1:-1}${ERRORSMALLCASE: -1}?:" scripts/spell_check/spelling.dat) ]]; then diff --git a/src/app/main.cpp b/src/app/main.cpp index 959bbbcbb27..a092b701117 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -812,13 +812,13 @@ int main( int argc, char *argv[] ) // - use the path specified with --globalsettings path, // - use the environment if not found // - use a default location as a fallback - if ( globalsettingsfile.isEmpty( ) ) + if ( globalsettingsfile.isEmpty() ) { globalsettingsfile = getenv( "QGIS_GLOBAL_SETTINGS_FILE" ); } - if ( globalsettingsfile.isEmpty( ) ) + if ( globalsettingsfile.isEmpty() ) { - QString default_globalsettingsfile = QgsApplication::pkgDataPath( ) + "/qgis_global_settings.ini"; + QString default_globalsettingsfile = QgsApplication::pkgDataPath() + "/qgis_global_settings.ini"; if ( QFile::exists( default_globalsettingsfile ) ) { globalsettingsfile = default_globalsettingsfile; diff --git a/src/app/nodetool/qgsnodeeditor.cpp b/src/app/nodetool/qgsnodeeditor.cpp index 7242c072e2a..b9b8b654e73 100644 --- a/src/app/nodetool/qgsnodeeditor.cpp +++ b/src/app/nodetool/qgsnodeeditor.cpp @@ -140,7 +140,7 @@ QVariant QgsNodeEditorModel::data( const QModelIndex &index, int role ) const return vertex->point().y(); else if ( index.column() == mZCol ) return vertex->point().z(); - else if ( index.column() == mMCol ) + else if ( index.column() == mMCol ) return vertex->point().m(); else if ( index.column() == mRCol ) { @@ -175,7 +175,7 @@ QVariant QgsNodeEditorModel::headerData( int section, Qt::Orientation orientatio return QVariant( tr( "y" ) ); else if ( section == mZCol ) return QVariant( tr( "z" ) ); - else if ( section == mMCol ) + else if ( section == mMCol ) return QVariant( tr( "m" ) ); else if ( section == mRCol ) return QVariant( tr( "r" ) ); diff --git a/src/app/nodetool/qgsnodeeditor.h b/src/app/nodetool/qgsnodeeditor.h index 862001f62d0..a12bce1dcb4 100644 --- a/src/app/nodetool/qgsnodeeditor.h +++ b/src/app/nodetool/qgsnodeeditor.h @@ -85,7 +85,7 @@ class QgsNodeEditor : public QgsDockWidget QgsNodeEditorModel *mNodeModel = nullptr; signals: - void deleteSelectedRequested( ); + void deleteSelectedRequested(); protected: void keyPressEvent( QKeyEvent *event ); diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index d674841ef00..b0d60b26daa 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1608,7 +1608,7 @@ void QgisApp::dataSourceManager( QString pageName ) { if ( ! mDataSourceManagerDialog ) { - mDataSourceManagerDialog = new QgsDataSourceManagerDialog( this, mapCanvas( ) ); + mDataSourceManagerDialog = new QgsDataSourceManagerDialog( this, mapCanvas() ); // Forward signals to this connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh ); connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged ); @@ -1629,11 +1629,11 @@ void QgisApp::dataSourceManager( QString pageName ) } // Try to open the dialog on a particular page - if ( ! pageName.isEmpty( ) ) + if ( ! pageName.isEmpty() ) { mDataSourceManagerDialog->openPage( pageName ); } - if ( QgsSettings().value( "/qgis/dataSourceManagerNonModal", true ).toBool( ) ) + if ( QgsSettings().value( "/qgis/dataSourceManagerNonModal", true ).toBool() ) { mDataSourceManagerDialog->show(); } @@ -1892,7 +1892,7 @@ void QgisApp::createActions() // Layer Menu Items - connect( mActionDataSourceManager, &QAction::triggered, this, [ = ]( ) { dataSourceManager( ); } ); + connect( mActionDataSourceManager, &QAction::triggered, this, [ = ]() { dataSourceManager(); } ); connect( mActionNewVectorLayer, &QAction::triggered, this, &QgisApp::newVectorLayer ); connect( mActionNewSpatiaLiteLayer, &QAction::triggered, this, &QgisApp::newSpatialiteLayer ); connect( mActionNewGeoPackageLayer, &QAction::triggered, this, &QgisApp::newGeoPackageLayer ); @@ -11391,7 +11391,7 @@ void QgisApp::renameView() // this is a slot for action from GUI to open and add raster layers -void QgisApp::addRasterLayer( ) +void QgisApp::addRasterLayer() { QStringList selectedFiles; QString e;//only for parameter correctness diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index cf71f4d7f5a..4dfb97c1740 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -777,7 +777,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow * \param pageName the page name, usually the provider name or "browser" (for the browser panel) * or "ogr" (vector layers) or "raster" (raster layers) */ - void dataSourceManager( QString pageName = QString( ) ); + void dataSourceManager( QString pageName = QString() ); /** Add a raster layer directly without prompting user for location The caller must provide information compatible with the provider plugin @@ -1498,7 +1498,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow * Emitted when a connection has been added/removed or changed by the provider * selection dialogs */ - void connectionsChanged( ); + void connectionsChanged(); /** Emitted when a key is pressed and we want non widget sublasses to be able to pick up on this (e.g. maplayer) */ diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp index cb09540e626..868c17d4d10 100644 --- a/src/app/qgsidentifyresultsdialog.cpp +++ b/src/app/qgsidentifyresultsdialog.cpp @@ -109,28 +109,28 @@ void QgsIdentifyResultsWebView::handleDownload( QUrl url ) { if ( ! url.isValid() ) { - QMessageBox::warning( this, tr( "Invalid URL" ), tr( "The download URL is not valid: %1" ).arg( url.toString( ) ) ); + QMessageBox::warning( this, tr( "Invalid URL" ), tr( "The download URL is not valid: %1" ).arg( url.toString() ) ); } else { const QString DOWNLOADER_LAST_DIR_KEY( "Qgis/fileDownloaderLastDir" ); QgsSettings settings; // Try to get some information from the URL - QFileInfo info( url.toString( ) ); - QString savePath = settings.value( DOWNLOADER_LAST_DIR_KEY ).toString( ); + QFileInfo info( url.toString() ); + QString savePath = settings.value( DOWNLOADER_LAST_DIR_KEY ).toString(); QString fileName = info.fileName().replace( QRegExp( "[^A-z0-9\\-_\\.]" ), "_" ); - if ( ! savePath.isEmpty() && ! fileName.isEmpty( ) ) + if ( ! savePath.isEmpty() && ! fileName.isEmpty() ) { savePath = QDir::cleanPath( savePath + QDir::separator() + fileName ); } QString targetFile = QFileDialog::getSaveFileName( this, tr( "Save as" ), savePath, - info.suffix( ).isEmpty() ? QString( ) : "*." + info.suffix( ) + info.suffix().isEmpty() ? QString() : "*." + info.suffix() ); if ( ! targetFile.isEmpty() ) { - settings.setValue( DOWNLOADER_LAST_DIR_KEY, QFileInfo( targetFile ).dir().absolutePath( ) ); + settings.setValue( DOWNLOADER_LAST_DIR_KEY, QFileInfo( targetFile ).dir().absolutePath() ); // Start the download new QgsFileDownloader( url, targetFile ); } diff --git a/src/app/qgsmaptoolannotation.cpp b/src/app/qgsmaptoolannotation.cpp index 138d877902c..098f967b2ec 100644 --- a/src/app/qgsmaptoolannotation.cpp +++ b/src/app/qgsmaptoolannotation.cpp @@ -230,14 +230,14 @@ void QgsMapToolAnnotation::canvasMoveEvent( QgsMapMouseEvent *e ) mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeftUp ) { xmin += e->posF().x() - mLastMousePosition.x(); - relPosX = ( relPosX * mCanvas->width() + e->posF().x() - mLastMousePosition.x( ) ) / ( double )mCanvas->width(); + relPosX = ( relPosX * mCanvas->width() + e->posF().x() - mLastMousePosition.x() ) / ( double )mCanvas->width(); } if ( mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameUp || mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeftUp || mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameRightUp ) { ymin += e->posF().y() - mLastMousePosition.y(); - relPosY = ( relPosY * mCanvas->height() + e->posF().y() - mLastMousePosition.y( ) ) / ( double )mCanvas->height(); + relPosY = ( relPosY * mCanvas->height() + e->posF().y() - mLastMousePosition.y() ) / ( double )mCanvas->height(); } if ( mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameDown || mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeftDown || diff --git a/src/app/qgssnappingwidget.h b/src/app/qgssnappingwidget.h index f156e6a3687..e9f235bf25b 100644 --- a/src/app/qgssnappingwidget.h +++ b/src/app/qgssnappingwidget.h @@ -75,7 +75,7 @@ class APP_EXPORT QgsSnappingWidget : public QWidget QAction *enableTracingAction() { return mEnableTracingAction; } signals: - void snappingConfigChanged( ); + void snappingConfigChanged(); private slots: void projectSnapSettingsChanged(); diff --git a/src/core/auth/qgsauthmanager.cpp b/src/core/auth/qgsauthmanager.cpp index b213ef71747..15abd87ebf2 100644 --- a/src/core/auth/qgsauthmanager.cpp +++ b/src/core/auth/qgsauthmanager.cpp @@ -2745,7 +2745,7 @@ const QByteArray QgsAuthManager::getTrustedCaCertsPemText() bool QgsAuthManager::passwordHelperSync() { - if ( masterPasswordIsSet( ) ) + if ( masterPasswordIsSet() ) { return passwordHelperWrite( mMasterPass ); } @@ -2895,7 +2895,7 @@ QString QgsAuthManager::passwordHelperName() const void QgsAuthManager::passwordHelperLog( const QString &msg ) const { - if ( passwordHelperLoggingEnabled( ) ) + if ( passwordHelperLoggingEnabled() ) { QgsMessageLog::logMessage( msg, passwordHelperName() ); } @@ -2907,7 +2907,7 @@ bool QgsAuthManager::passwordHelperDelete() bool result; QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME ); QgsSettings settings; - job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool( ) ); + job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() ); job.setAutoDelete( false ); job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); QEventLoop loop; @@ -2939,7 +2939,7 @@ QString QgsAuthManager::passwordHelperRead() passwordHelperLog( tr( "Opening %1 for READ ..." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) ); QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME ); QgsSettings settings; - job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool( ) ); + job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() ); job.setAutoDelete( false ); job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); QEventLoop loop; @@ -2981,7 +2981,7 @@ bool QgsAuthManager::passwordHelperWrite( const QString &password ) passwordHelperLog( tr( "Opening %1 for WRITE ..." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) ); QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME ); QgsSettings settings; - job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool( ) ); + job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() ); job.setAutoDelete( false ); job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); job.setTextData( password ); @@ -3004,7 +3004,7 @@ bool QgsAuthManager::passwordHelperWrite( const QString &password ) emit passwordHelperSuccess(); result = true; } - passwordHelperProcessError( ); + passwordHelperProcessError(); return result; } @@ -3012,7 +3012,7 @@ bool QgsAuthManager::passwordHelperEnabled() const { // Does the user want to store the password in the wallet? QgsSettings settings; - return settings.value( QStringLiteral( "use_password_helper" ), true, QgsSettings::Section::Auth ).toBool( ); + return settings.value( QStringLiteral( "use_password_helper" ), true, QgsSettings::Section::Auth ).toBool(); } void QgsAuthManager::setPasswordHelperEnabled( const bool enabled ) @@ -3029,7 +3029,7 @@ bool QgsAuthManager::passwordHelperLoggingEnabled() const { // Does the user want to store the password in the wallet? QgsSettings settings; - return settings.value( QStringLiteral( "password_helper_logging" ), false, QgsSettings::Section::Auth ).toBool( ); + return settings.value( QStringLiteral( "password_helper_logging" ), false, QgsSettings::Section::Auth ).toBool(); } void QgsAuthManager::setPasswordHelperLoggingEnabled( const bool enabled ) @@ -3081,10 +3081,10 @@ bool QgsAuthManager::masterPasswordInput() bool ok = false; // Read the password from the wallet - if ( passwordHelperEnabled( ) ) + if ( passwordHelperEnabled() ) { - pass = passwordHelperRead( ); - if ( ! pass.isEmpty( ) && ( mPasswordHelperErrorCode == QKeychain::NoError ) ) + pass = passwordHelperRead(); + if ( ! pass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) ) { // Let's check the password! if ( verifyMasterPassword( pass ) ) @@ -3112,7 +3112,7 @@ bool QgsAuthManager::masterPasswordInput() if ( ok && !pass.isEmpty() && mMasterPass != pass ) { mMasterPass = pass; - if ( passwordHelperEnabled( ) && ! storedPasswordIsValid ) + if ( passwordHelperEnabled() && ! storedPasswordIsValid ) { if ( passwordHelperWrite( pass ) ) { diff --git a/src/core/auth/qgsauthmanager.h b/src/core/auth/qgsauthmanager.h index 2214670a625..d5c513cb313 100644 --- a/src/core/auth/qgsauthmanager.h +++ b/src/core/auth/qgsauthmanager.h @@ -528,7 +528,7 @@ class CORE_EXPORT QgsAuthManager : public QObject //! Store the password manager into the wallet //! @note not available in Python bindings - bool passwordHelperSync( ) SIP_SKIP; + bool passwordHelperSync() SIP_SKIP; //! The display name of the password helper (platform dependent) static const QString AUTH_PASSWORD_HELPER_DISPLAY_NAME; diff --git a/src/core/composer/qgspaperitem.cpp b/src/core/composer/qgspaperitem.cpp index 5f26909fa92..82d1202005c 100644 --- a/src/core/composer/qgspaperitem.cpp +++ b/src/core/composer/qgspaperitem.cpp @@ -43,7 +43,7 @@ void QgsPaperGrid::paint( QPainter *painter, const QStyleOptionGraphicsItem *ite //draw grid if ( mComposition ) { - if ( mComposition->gridVisible() && mComposition->plotStyle() == QgsComposition::Preview + if ( mComposition->gridVisible() && mComposition->plotStyle() == QgsComposition::Preview && mComposition->snapGridResolution() > 0 ) { int gridMultiplyX = static_cast< int >( mComposition->snapGridOffsetX() / mComposition->snapGridResolution() ); @@ -164,7 +164,7 @@ void QgsPaperItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *ite painter->save(); - if ( mComposition->plotStyle() == QgsComposition::Preview ) + if ( mComposition->plotStyle() == QgsComposition::Preview ) { //if in preview mode, draw page border and shadow so that it's //still possible to tell where pages with a transparent style begin and end diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index d9f152142da..d687de8bdba 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -2080,7 +2080,7 @@ static QVariant fcnMakeRegularPolygon( const QVariantList &values, const QgsExpr QgsRegularPolygon rp = QgsRegularPolygon( *center, *corner, nbEdges, option ); - return QVariant::fromValue( QgsGeometry( rp.toPolygon( ) ) ); + return QVariant::fromValue( QgsGeometry( rp.toPolygon() ) ); } diff --git a/src/core/geometry/qgsregularpolygon.cpp b/src/core/geometry/qgsregularpolygon.cpp index a0b8394c6bb..cc984b899c5 100644 --- a/src/core/geometry/qgsregularpolygon.cpp +++ b/src/core/geometry/qgsregularpolygon.cpp @@ -174,7 +174,7 @@ void QgsRegularPolygon::setNumberSides( const int numSides ) } } -QgsPointSequence QgsRegularPolygon::points( ) const +QgsPointSequence QgsRegularPolygon::points() const { QgsPointSequence pts; if ( isEmpty() ) @@ -210,7 +210,7 @@ QgsPolygonV2 *QgsRegularPolygon::toPolygon() const return polygon.release(); } - polygon->setExteriorRing( toLineString( ) ); + polygon->setExteriorRing( toLineString() ); return polygon.release(); } @@ -224,7 +224,7 @@ QgsLineString *QgsRegularPolygon::toLineString() const } QgsPointSequence pts; - pts = points( ); + pts = points(); ext->setPoints( pts ); @@ -239,7 +239,7 @@ QgsTriangle QgsRegularPolygon::toTriangle() const } QgsPointSequence pts; - pts = points( ); + pts = points(); return QgsTriangle( pts.at( 0 ), pts.at( 1 ), pts.at( 2 ) ); } @@ -253,7 +253,7 @@ QList QgsRegularPolygon::triangulate() const } QgsPointSequence pts; - pts = points( ); + pts = points(); unsigned int n = 0; while ( n < mNumberSides - 1 ) diff --git a/src/core/geometry/qgsregularpolygon.h b/src/core/geometry/qgsregularpolygon.h index f65b3bf36d9..4b42c04ba49 100644 --- a/src/core/geometry/qgsregularpolygon.h +++ b/src/core/geometry/qgsregularpolygon.h @@ -137,33 +137,33 @@ class CORE_EXPORT QgsRegularPolygon /** Returns a list including the vertices of the regular polygon. */ - QgsPointSequence points( ) const; + QgsPointSequence points() const; /** Returns as a polygon. */ - QgsPolygonV2 *toPolygon( ) const SIP_FACTORY; + QgsPolygonV2 *toPolygon() const SIP_FACTORY; /** Returns as a linestring. */ - QgsLineString *toLineString( ) const SIP_FACTORY; + QgsLineString *toLineString() const SIP_FACTORY; /** Returns as a triangle. * An empty triangle is returned if the regular polygon is empty or if the number of sides is different from 3. */ - QgsTriangle toTriangle( ) const; + QgsTriangle toTriangle() const; /** Returns a triangulation (vertices from sides to the center) of the regular polygon. * An empty list is returned if the regular polygon is empty. */ - QList triangulate( ) const; + QList triangulate() const; /** Returns the inscribed circle */ - QgsCircle inscribedCircle( ) const; + QgsCircle inscribedCircle() const; /** Returns the circumscribed circle */ - QgsCircle circumscribedCircle( ) const; + QgsCircle circumscribedCircle() const; /** * Returns a string representation of the regular polygon. @@ -173,26 +173,26 @@ class CORE_EXPORT QgsRegularPolygon /** Returns the measure of the interior angles in degrees. */ - double interiorAngle( ) const; + double interiorAngle() const; /** Returns the measure of the central angle (the angle subtended at the center of the polygon by one of its sides) in degrees. */ - double centralAngle( ) const; + double centralAngle() const; /** Returns the area. * Returns 0 if the regular polygon is empty. */ - double area( ) const; + double area() const; /** Returns the perimeter. * Returns 0 if the regular polygon is empty. */ - double perimeter( ) const; + double perimeter() const; /** Returns the length of a side. * Returns 0 if the regular polygon is empty. */ - double length( ) const; + double length() const; private: QgsPoint mCenter; diff --git a/src/core/geometry/qgstriangle.h b/src/core/geometry/qgstriangle.h index c401926b265..f7d2fc89b81 100644 --- a/src/core/geometry/qgstriangle.h +++ b/src/core/geometry/qgstriangle.h @@ -203,7 +203,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # ['LineString (0 0, 0 5)', 'LineString (0 5, 2.5 2.5)', 'LineString (5 5, 0 5)'] * \endcode */ - QVector altitudes( ) const; + QVector altitudes() const; /** * A median is a segment (defined by a QgsLineString) from a vertex to the midpoint of the opposite side. @@ -215,7 +215,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # ['LineString (0 0, 2.5 5)', 'LineString (0 5, 2.5 2.5)', 'LineString (5 5, 0 2.5)'] * \endcode */ - QVector medians( ) const; + QVector medians() const; /** * The segment (defined by a QgsLineString) returned bisect the angle of a vertex to the opposite side. @@ -240,7 +240,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # 'Triangle ((0 2.5, 2.5 5, 2.5 2.5, 0 2.5))' * \endcode */ - QgsTriangle medial( ) const; + QgsTriangle medial() const; /** * An orthocenter is the point of intersection of the altitudes of a triangle. @@ -265,7 +265,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # 'Point (2.5 2.5)' * \endcode */ - QgsPoint circumscribedCenter( ) const; + QgsPoint circumscribedCenter() const; /** * Radius of the circumscribed circle of the triangle. @@ -277,7 +277,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # 3.5355339059327378 * \endcode */ - double circumscribedRadius( ) const; + double circumscribedRadius() const; /** * Circumscribed circle of the triangle. @@ -289,7 +289,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # QgsCircle(Point (2.5 2.5), 3.5355339059327378, 0) * \endcode */ - QgsCircle circumscribedCircle( ) const; + QgsCircle circumscribedCircle() const; /** * Center of the inscribed circle of the triangle. @@ -301,7 +301,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # 'Point (1.46446609406726225 3.53553390593273775)' * \endcode */ - QgsPoint inscribedCenter( ) const; + QgsPoint inscribedCenter() const; /** * Radius of the inscribed circle of the triangle. @@ -313,7 +313,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # 1.4644660940672622 * \endcode */ - double inscribedRadius( ) const; + double inscribedRadius() const; /** * Inscribed circle of the triangle. @@ -325,7 +325,7 @@ class CORE_EXPORT QgsTriangle : public QgsPolygonV2 * # QgsCircle(Point (1.46446609406726225 3.53553390593273775), 1.4644660940672622, 0) * \endcode */ - QgsCircle inscribedCircle( ) const; + QgsCircle inscribedCircle() const; private: diff --git a/src/core/qgsbrowsermodel.h b/src/core/qgsbrowsermodel.h index 4a9ff01018f..c813ac0220b 100644 --- a/src/core/qgsbrowsermodel.h +++ b/src/core/qgsbrowsermodel.h @@ -136,7 +136,7 @@ class CORE_EXPORT QgsBrowserModel : public QAbstractItemModel void stateChanged( const QModelIndex &index, QgsDataItem::State oldState ); //! Connections changed in the browser, forwarded to the widget and used to //! notify the provider dialogs of a changed connection - void connectionsChanged( ); + void connectionsChanged(); public slots: //! Reload the whole model diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp index e39aa322361..4c34c833ac8 100644 --- a/src/core/qgsdataitem.cpp +++ b/src/core/qgsdataitem.cpp @@ -344,8 +344,8 @@ void QgsDataItem::refresh() void QgsDataItem::refreshConnections() { - refresh( ); - emit connectionsChanged( ); + refresh(); + emit connectionsChanged(); } void QgsDataItem::refresh( const QVector &children ) diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 2ada4a11b25..aa398aa3224 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -299,7 +299,7 @@ class CORE_EXPORT QgsDataItem : public QObject //! This signal is normally forwarded to the app in order to refresh the connection //! item in the provider dialogs and to refresh the connection items in the other //! open browsers - void connectionsChanged( ); + void connectionsChanged(); protected slots: diff --git a/src/core/qgsogcutils.cpp b/src/core/qgsogcutils.cpp index 6918c768637..ec7a041a500 100644 --- a/src/core/qgsogcutils.cpp +++ b/src/core/qgsogcutils.cpp @@ -2053,7 +2053,7 @@ QgsExpressionNode *QgsOgcUtils::nodeIsBetweenFromOgcFilter( QDomElement &element QDomElement lowerBoundElem = operandElem.firstChildElement(); lowerBound = nodeFromOgcFilter( lowerBoundElem, errorMessage ); } - else if ( operandElem.tagName() == QLatin1String( "UpperBoundary" ) ) + else if ( operandElem.tagName() == QLatin1String( "UpperBoundary" ) ) { QDomElement upperBoundElem = operandElem.firstChildElement(); upperBound = nodeFromOgcFilter( upperBoundElem, errorMessage ); diff --git a/src/core/qgsprojectfiletransform.cpp b/src/core/qgsprojectfiletransform.cpp index 4d30e8d42ab..1a311345b15 100644 --- a/src/core/qgsprojectfiletransform.cpp +++ b/src/core/qgsprojectfiletransform.cpp @@ -1142,7 +1142,7 @@ void QgsProjectFileTransform::transformContrastEnhancement( QDomDocument &doc, c { enhancementNameList << QStringLiteral( "contrastEnhancement" ); } - if ( minMaxEntryList.size() == 3 ) + if ( minMaxEntryList.size() == 3 ) { enhancementNameList << QStringLiteral( "redContrastEnhancement" ) << QStringLiteral( "greenContrastEnhancement" ) << QStringLiteral( "blueContrastEnhancement" ); } diff --git a/src/core/qgssettings.cpp b/src/core/qgssettings.cpp index 6670cab8b6a..0b3604444d7 100644 --- a/src/core/qgssettings.cpp +++ b/src/core/qgssettings.cpp @@ -35,7 +35,7 @@ bool QgsSettings::setGlobalSettingsPath( QString path ) void QgsSettings::init() { - if ( ! sGlobalSettingsPath.isEmpty( ) ) + if ( ! sGlobalSettingsPath.isEmpty() ) { mGlobalSettings = new QSettings( sGlobalSettingsPath, QSettings::IniFormat ); mGlobalSettings->setIniCodec( "UTF-8" ); @@ -93,17 +93,17 @@ void QgsSettings::beginGroup( const QString &prefix ) void QgsSettings::endGroup() { - mUserSettings->endGroup( ); + mUserSettings->endGroup(); if ( mGlobalSettings ) { - mGlobalSettings->endGroup( ); + mGlobalSettings->endGroup(); } } QStringList QgsSettings::allKeys() const { - QStringList keys = mUserSettings->allKeys( ); + QStringList keys = mUserSettings->allKeys(); if ( mGlobalSettings ) { for ( auto &s : mGlobalSettings->allKeys() ) @@ -120,7 +120,7 @@ QStringList QgsSettings::allKeys() const QStringList QgsSettings::childKeys() const { - QStringList keys = mUserSettings->childKeys( ); + QStringList keys = mUserSettings->childKeys(); if ( mGlobalSettings ) { for ( auto &s : mGlobalSettings->childKeys() ) @@ -136,7 +136,7 @@ QStringList QgsSettings::childKeys() const QStringList QgsSettings::childGroups() const { - QStringList keys = mUserSettings->childGroups( ); + QStringList keys = mUserSettings->childGroups(); if ( mGlobalSettings ) { for ( auto &s : mGlobalSettings->childGroups() ) @@ -173,7 +173,7 @@ bool QgsSettings::contains( const QString &key, const QgsSettings::Section secti QString QgsSettings::fileName() const { - return mUserSettings->fileName( ); + return mUserSettings->fileName(); } void QgsSettings::sync() diff --git a/src/core/raster/qgsrasteriterator.cpp b/src/core/raster/qgsrasteriterator.cpp index 6c3b7712a22..137f6f16be3 100644 --- a/src/core/raster/qgsrasteriterator.cpp +++ b/src/core/raster/qgsrasteriterator.cpp @@ -76,7 +76,7 @@ bool QgsRasterIterator::readNextRasterPart( int bandNumber, RasterPartInfo &pInfo = partIt.value(); // If we started with zero cols or zero rows, just return (avoids divide by zero below) - if ( 0 == pInfo.nCols || 0 == pInfo.nRows ) + if ( 0 == pInfo.nCols || 0 == pInfo.nRows ) { return false; } diff --git a/src/core/symbology-ng/qgsmarkersymbollayer.cpp b/src/core/symbology-ng/qgsmarkersymbollayer.cpp index 6548586d1da..cbfdf8a2b96 100644 --- a/src/core/symbology-ng/qgsmarkersymbollayer.cpp +++ b/src/core/symbology-ng/qgsmarkersymbollayer.cpp @@ -279,51 +279,51 @@ QgsSimpleMarkerSymbolLayerBase::Shape QgsSimpleMarkerSymbolLayerBase::decodeShap *ok = true; QString cleaned = name.toLower().trimmed(); - if ( cleaned == QLatin1String( "square" ) || cleaned == QLatin1String( "rectangle" ) ) + if ( cleaned == QLatin1String( "square" ) || cleaned == QLatin1String( "rectangle" ) ) return Square; - else if ( cleaned == QLatin1String( "diamond" ) ) + else if ( cleaned == QLatin1String( "diamond" ) ) return Diamond; - else if ( cleaned == QLatin1String( "pentagon" ) ) + else if ( cleaned == QLatin1String( "pentagon" ) ) return Pentagon; - else if ( cleaned == QLatin1String( "hexagon" ) ) + else if ( cleaned == QLatin1String( "hexagon" ) ) return Hexagon; - else if ( cleaned == QLatin1String( "triangle" ) ) + else if ( cleaned == QLatin1String( "triangle" ) ) return Triangle; - else if ( cleaned == QLatin1String( "equilateral_triangle" ) ) + else if ( cleaned == QLatin1String( "equilateral_triangle" ) ) return EquilateralTriangle; - else if ( cleaned == QLatin1String( "star" ) || cleaned == QLatin1String( "regular_star" ) ) + else if ( cleaned == QLatin1String( "star" ) || cleaned == QLatin1String( "regular_star" ) ) return Star; - else if ( cleaned == QLatin1String( "arrow" ) ) + else if ( cleaned == QLatin1String( "arrow" ) ) return Arrow; - else if ( cleaned == QLatin1String( "circle" ) ) + else if ( cleaned == QLatin1String( "circle" ) ) return Circle; - else if ( cleaned == QLatin1String( "cross" ) ) + else if ( cleaned == QLatin1String( "cross" ) ) return Cross; - else if ( cleaned == QLatin1String( "cross_fill" ) ) + else if ( cleaned == QLatin1String( "cross_fill" ) ) return CrossFill; - else if ( cleaned == QLatin1String( "cross2" ) || cleaned == QLatin1String( "x" ) ) + else if ( cleaned == QLatin1String( "cross2" ) || cleaned == QLatin1String( "x" ) ) return Cross2; - else if ( cleaned == QLatin1String( "line" ) ) + else if ( cleaned == QLatin1String( "line" ) ) return Line; - else if ( cleaned == QLatin1String( "arrowhead" ) ) + else if ( cleaned == QLatin1String( "arrowhead" ) ) return ArrowHead; - else if ( cleaned == QLatin1String( "filled_arrowhead" ) ) + else if ( cleaned == QLatin1String( "filled_arrowhead" ) ) return ArrowHeadFilled; - else if ( cleaned == QLatin1String( "semi_circle" ) ) + else if ( cleaned == QLatin1String( "semi_circle" ) ) return SemiCircle; - else if ( cleaned == QLatin1String( "third_circle" ) ) + else if ( cleaned == QLatin1String( "third_circle" ) ) return ThirdCircle; - else if ( cleaned == QLatin1String( "quarter_circle" ) ) + else if ( cleaned == QLatin1String( "quarter_circle" ) ) return QuarterCircle; - else if ( cleaned == QLatin1String( "quarter_square" ) ) + else if ( cleaned == QLatin1String( "quarter_square" ) ) return QuarterSquare; - else if ( cleaned == QLatin1String( "half_square" ) ) + else if ( cleaned == QLatin1String( "half_square" ) ) return HalfSquare; - else if ( cleaned == QLatin1String( "diagonal_half_square" ) ) + else if ( cleaned == QLatin1String( "diagonal_half_square" ) ) return DiagonalHalfSquare; - else if ( cleaned == QLatin1String( "right_half_triangle" ) ) + else if ( cleaned == QLatin1String( "right_half_triangle" ) ) return RightHalfTriangle; - else if ( cleaned == QLatin1String( "left_half_triangle" ) ) + else if ( cleaned == QLatin1String( "left_half_triangle" ) ) return LeftHalfTriangle; if ( ok ) diff --git a/src/core/symbology-ng/qgssymbol.cpp b/src/core/symbology-ng/qgssymbol.cpp index 7f631d6c32b..4952e355edc 100644 --- a/src/core/symbology-ng/qgssymbol.cpp +++ b/src/core/symbology-ng/qgssymbol.cpp @@ -1114,7 +1114,7 @@ double QgsMarkerSymbol::angle() const { Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); return markerLayer->angle(); @@ -1126,7 +1126,7 @@ void QgsMarkerSymbol::setLineAngle( double lineAng ) { Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); markerLayer->setLineAngle( lineAng ); @@ -1139,7 +1139,7 @@ void QgsMarkerSymbol::setDataDefinedAngle( const QgsProperty &property ) Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); if ( !property ) @@ -1169,7 +1169,7 @@ QgsProperty QgsMarkerSymbol::dataDefinedAngle() const // find the base of the "en masse" pattern Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); if ( qgsDoubleNear( markerLayer->angle(), symbolRotation ) && markerLayer->dataDefinedProperties().isActive( QgsSymbolLayer::PropertyAngle ) ) @@ -1185,7 +1185,7 @@ QgsProperty QgsMarkerSymbol::dataDefinedAngle() const // check that all layer's angle expressions match the "en masse" pattern Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); @@ -1213,7 +1213,7 @@ void QgsMarkerSymbol::setSize( double s ) Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); if ( qgsDoubleNear( markerLayer->size(), origSize ) ) @@ -1236,7 +1236,7 @@ double QgsMarkerSymbol::size() const double maxSize = 0; Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); double lsize = markerLayer->size(); @@ -1250,7 +1250,7 @@ void QgsMarkerSymbol::setSizeUnit( QgsUnitTypes::RenderUnit unit ) { Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); @@ -1265,7 +1265,7 @@ QgsUnitTypes::RenderUnit QgsMarkerSymbol::sizeUnit() const Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); @@ -1286,7 +1286,7 @@ void QgsMarkerSymbol::setSizeMapUnitScale( const QgsMapUnitScale &scale ) { Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); @@ -1298,7 +1298,7 @@ QgsMapUnitScale QgsMarkerSymbol::sizeMapUnitScale() const { Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); @@ -1313,7 +1313,7 @@ void QgsMarkerSymbol::setDataDefinedSize( const QgsProperty &property ) Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); @@ -1352,7 +1352,7 @@ QgsProperty QgsMarkerSymbol::dataDefinedSize() const // find the base of the "en masse" pattern Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); if ( qgsDoubleNear( markerLayer->size(), symbolSize ) && markerLayer->dataDefinedProperties().isActive( QgsSymbolLayer::PropertySize ) ) @@ -1368,7 +1368,7 @@ QgsProperty QgsMarkerSymbol::dataDefinedSize() const // check that all layers size expressions match the "en masse" pattern Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); @@ -1402,7 +1402,7 @@ void QgsMarkerSymbol::setScaleMethod( QgsSymbol::ScaleMethod scaleMethod ) { Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); markerLayer->setScaleMethod( scaleMethod ); @@ -1413,7 +1413,7 @@ QgsSymbol::ScaleMethod QgsMarkerSymbol::scaleMethod() { Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Marker ) + if ( layer->type() != QgsSymbol::Marker ) continue; const QgsMarkerSymbolLayer *markerLayer = static_cast( layer ); // return scale method of the first symbol layer @@ -1623,7 +1623,7 @@ QgsProperty QgsLineSymbol::dataDefinedWidth() const // check that all layers width expressions match the "en masse" pattern Q_FOREACH ( QgsSymbolLayer *layer, mLayers ) { - if ( layer->type() != QgsSymbol::Line ) + if ( layer->type() != QgsSymbol::Line ) continue; const QgsLineSymbolLayer *lineLayer = static_cast( layer ); diff --git a/src/core/symbology-ng/qgssymbollayerutils.cpp b/src/core/symbology-ng/qgssymbollayerutils.cpp index 211ec00ebf0..a9c20e8f601 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.cpp +++ b/src/core/symbology-ng/qgssymbollayerutils.cpp @@ -3903,7 +3903,7 @@ QList QgsSymbolLayerUtils::prettyBreaks( double minimum, double maximum, QgsDebugMsg( QString( "pretty classes: %1" ).arg( end ) ); // If we don't have quite enough labels, extend the range out - // to make more (these labels are beyond the data :( ) + // to make more (these labels are beyond the data :() int k = floor( 0.5 + end - start ); if ( k < minimumCount ) { diff --git a/src/gui/attributetable/qgsattributetabledelegate.cpp b/src/gui/attributetable/qgsattributetabledelegate.cpp index 890b2f03f18..78a46231a0d 100644 --- a/src/gui/attributetable/qgsattributetabledelegate.cpp +++ b/src/gui/attributetable/qgsattributetabledelegate.cpp @@ -100,10 +100,10 @@ void QgsAttributeTableDelegate::setModelData( QWidget *editor, QAbstractItemMode // This fixes https://issues.qgis.org/issues/16492 QgsFeatureRequest request( fid ); request.setFlags( QgsFeatureRequest::NoGeometry ); - request.setSubsetOfAttributes( QgsAttributeList( ) ); + request.setSubsetOfAttributes( QgsAttributeList() ); QgsFeature feature; vl->getFeatures( request ).nextFeature( feature ); - if ( feature.isValid( ) ) + if ( feature.isValid() ) { vl->beginEditCommand( tr( "Attribute changed" ) ); vl->changeAttributeValue( fid, fieldIdx, newValue, oldValue ); diff --git a/src/gui/attributetable/qgsattributetablefiltermodel.cpp b/src/gui/attributetable/qgsattributetablefiltermodel.cpp index 41a4f9cc5b2..5fa8affa12f 100644 --- a/src/gui/attributetable/qgsattributetablefiltermodel.cpp +++ b/src/gui/attributetable/qgsattributetablefiltermodel.cpp @@ -99,7 +99,7 @@ QVariant QgsAttributeTableFilterModel::data( const QModelIndex &index, int role QVariant QgsAttributeTableFilterModel::headerData( int section, Qt::Orientation orientation, int role ) const { - if ( orientation == Qt::Horizontal ) + if ( orientation == Qt::Horizontal ) { if ( mColumnMapping.at( section ) == -1 && role == Qt::DisplayRole ) return tr( "Actions" ); diff --git a/src/gui/auth/qgsautheditorwidgets.cpp b/src/gui/auth/qgsautheditorwidgets.cpp index f91155d118a..b809c2a21a7 100644 --- a/src/gui/auth/qgsautheditorwidgets.cpp +++ b/src/gui/auth/qgsautheditorwidgets.cpp @@ -161,10 +161,10 @@ void QgsAuthEditorWidgets::setupUtilitiesMenu() mActionPasswordHelperLoggingEnable = new QAction( tr( "Enable password helper debug log" ), this ); mActionPasswordHelperEnable->setCheckable( true ); - mActionPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled( ) ); + mActionPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled() ); mActionPasswordHelperLoggingEnable->setCheckable( true ); - mActionPasswordHelperLoggingEnable->setChecked( QgsAuthManager::instance()->passwordHelperLoggingEnabled( ) ); + mActionPasswordHelperLoggingEnable->setChecked( QgsAuthManager::instance()->passwordHelperLoggingEnabled() ); connect( mActionSetMasterPassword, &QAction::triggered, this, &QgsAuthEditorWidgets::setMasterPassword ); connect( mActionClearCachedMasterPassword, &QAction::triggered, this, &QgsAuthEditorWidgets::clearCachedMasterPassword ); diff --git a/src/gui/auth/qgsautheditorwidgets.h b/src/gui/auth/qgsautheditorwidgets.h index 91cb97bbdb6..e979481f52b 100644 --- a/src/gui/auth/qgsautheditorwidgets.h +++ b/src/gui/auth/qgsautheditorwidgets.h @@ -90,16 +90,16 @@ class GUI_EXPORT QgsAuthEditorWidgets : public QWidget, private Ui::QgsAuthEdito void authMessageOut( const QString &message, const QString &authtag, QgsAuthManager::MessageLevel level ); //! Remove master password from wallet - void passwordHelperDelete( ); + void passwordHelperDelete(); //! Store master password into the wallet - void passwordHelperSync( ); + void passwordHelperSync(); //! Toggle password helper (enable/disable) - void passwordHelperEnableTriggered( ); + void passwordHelperEnableTriggered(); //! Toggle password helper logging (enable/disable) - void passwordHelperLoggingEnableTriggered( ); + void passwordHelperLoggingEnableTriggered(); private: void setupUtilitiesMenu(); diff --git a/src/gui/auth/qgsauthguiutils.cpp b/src/gui/auth/qgsauthguiutils.cpp index 5ababdac6e6..9953cdd317b 100644 --- a/src/gui/auth/qgsauthguiutils.cpp +++ b/src/gui/auth/qgsauthguiutils.cpp @@ -293,7 +293,7 @@ void QgsAuthGuiUtils::passwordHelperSync( QgsMessageBar *msgbar, int timeout ) { QString msg; QgsMessageBar::MessageLevel level; - if ( ! QgsAuthManager::instance()->masterPasswordIsSet( ) ) + if ( ! QgsAuthManager::instance()->masterPasswordIsSet() ) { msg = QObject::tr( "Master password is not set and cannot be stored in your %1" ) .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ); diff --git a/src/gui/layertree/qgslayertreeview.cpp b/src/gui/layertree/qgslayertreeview.cpp index bf91e2df552..1a77eada58b 100644 --- a/src/gui/layertree/qgslayertreeview.cpp +++ b/src/gui/layertree/qgslayertreeview.cpp @@ -255,7 +255,7 @@ void QgsLayerTreeView::updateExpandedStateFromNode( QgsLayerTreeNode *node ) QgsMapLayer *QgsLayerTreeView::layerForIndex( const QModelIndex &index ) const { // Check if model has been set and index is valid - if ( layerTreeModel() && index.isValid( ) ) + if ( layerTreeModel() && index.isValid() ) { QgsLayerTreeNode *node = layerTreeModel()->index2node( index ); if ( node ) diff --git a/src/gui/ogr/qgsopenvectorlayerdialog.cpp b/src/gui/ogr/qgsopenvectorlayerdialog.cpp index ad8f426c762..f35420560d3 100644 --- a/src/gui/ogr/qgsopenvectorlayerdialog.cpp +++ b/src/gui/ogr/qgsopenvectorlayerdialog.cpp @@ -399,9 +399,9 @@ void QgsOpenVectorLayerDialog::accept() { QDialog::accept(); } - else if ( ! mDataSources.isEmpty( ) ) + else if ( ! mDataSources.isEmpty() ) { - emit addVectorLayers( mDataSources, encoding(), dataSourceType( ) ); + emit addVectorLayers( mDataSources, encoding(), dataSourceType() ); } } diff --git a/src/gui/qgsabstractdatasourcewidget.h b/src/gui/qgsabstractdatasourcewidget.h index 1cd66ee069a..976e94823be 100644 --- a/src/gui/qgsabstractdatasourcewidget.h +++ b/src/gui/qgsabstractdatasourcewidget.h @@ -43,7 +43,7 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog public: //! Destructor - ~QgsAbstractDataSourceWidget( ) = default; + ~QgsAbstractDataSourceWidget() = default; /** Store a pointer to the map canvas to retrieve extent and CRS * Used to select an appropriate CRS and possibly to retrieve data only in the current extent @@ -56,7 +56,7 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog /** Triggered when the provider's connections need to be refreshed * The default implementation does nothing */ - virtual void refresh( ) {} + virtual void refresh() {} signals: @@ -86,11 +86,11 @@ class GUI_EXPORT QgsAbstractDataSourceWidget : public QDialog QgsAbstractDataSourceWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None ); //! Return the widget mode - QgsProviderRegistry::WidgetMode widgetMode( ) const; + QgsProviderRegistry::WidgetMode widgetMode() const; /** Return the map canvas (can be null) */ - const QgsMapCanvas *mapCanvas( ) const; + const QgsMapCanvas *mapCanvas() const; private: diff --git a/src/gui/qgsadvanceddigitizingcanvasitem.cpp b/src/gui/qgsadvanceddigitizingcanvasitem.cpp index 060e3e6d398..ea5f877edbd 100644 --- a/src/gui/qgsadvanceddigitizingcanvasitem.cpp +++ b/src/gui/qgsadvanceddigitizingcanvasitem.cpp @@ -107,7 +107,7 @@ void QgsAdvancedDigitizingCanvasItem::paint( QPainter *painter ) } // Draw segment par/per input - if ( mAdvancedDigitizingDockWidget->additionalConstraint() != QgsAdvancedDigitizingDockWidget::NoConstraint && hasSnappedSegment ) + if ( mAdvancedDigitizingDockWidget->additionalConstraint() != QgsAdvancedDigitizingDockWidget::NoConstraint && hasSnappedSegment ) { painter->setPen( mConstruction2Pen ); painter->drawLine( snapSegmentPix1.x(), diff --git a/src/gui/qgsattributeformeditorwidget.cpp b/src/gui/qgsattributeformeditorwidget.cpp index ae6a064c79c..0480c298c92 100644 --- a/src/gui/qgsattributeformeditorwidget.cpp +++ b/src/gui/qgsattributeformeditorwidget.cpp @@ -143,7 +143,7 @@ void QgsAttributeFormEditorWidget::setMode( QgsAttributeFormEditorWidget::Mode m void QgsAttributeFormEditorWidget::setIsMixed( bool mixed ) { if ( mWidget && mixed ) - mWidget->showIndeterminateState( ); + mWidget->showIndeterminateState(); mMultiEditButton->setIsMixed( mixed ); mIsMixed = mixed; } diff --git a/src/gui/qgsbrowserdockwidget.h b/src/gui/qgsbrowserdockwidget.h index 789d02d5760..418cef1e635 100644 --- a/src/gui/qgsbrowserdockwidget.h +++ b/src/gui/qgsbrowserdockwidget.h @@ -103,7 +103,7 @@ class GUI_EXPORT QgsBrowserDockWidget : public QgsDockWidget, private Ui::QgsBro //! Emitted when drop uri list needs to be handled void handleDropUriList( const QgsMimeDataUtils::UriList & ); //! Connections changed in the browser - void connectionsChanged( ); + void connectionsChanged(); protected: //! Show event override diff --git a/src/gui/qgsbrowserdockwidget_p.h b/src/gui/qgsbrowserdockwidget_p.h index c7e06b1e892..780a2febe9b 100644 --- a/src/gui/qgsbrowserdockwidget_p.h +++ b/src/gui/qgsbrowserdockwidget_p.h @@ -218,7 +218,7 @@ class QgsBrowserTreeFilterProxyModel : public QSortFilterProxyModel //! Set the browser model void setBrowserModel( QgsBrowserModel *model ); //! Get the browser model - QgsBrowserModel *browserModel( ) { return mModel; } + QgsBrowserModel *browserModel() { return mModel; } //! Set the filter syntax void setFilterSyntax( const QString &syntax ); //! Set the filter diff --git a/src/gui/qgsbrowsertreeview.h b/src/gui/qgsbrowsertreeview.h index 5dedf219649..6610db572fa 100644 --- a/src/gui/qgsbrowsertreeview.h +++ b/src/gui/qgsbrowsertreeview.h @@ -38,7 +38,7 @@ class GUI_EXPORT QgsBrowserTreeView : public QTreeView //! Set the browser model void setBrowserModel( QgsBrowserModel *model ); //! Return the browser model - QgsBrowserModel *browserModel( ) { return mBrowserModel; } + QgsBrowserModel *browserModel() { return mBrowserModel; } virtual void showEvent( QShowEvent *e ) override; virtual void hideEvent( QHideEvent *e ) override; diff --git a/src/gui/qgscredentialdialog.cpp b/src/gui/qgscredentialdialog.cpp index 38c380312c0..755e65841e3 100644 --- a/src/gui/qgscredentialdialog.cpp +++ b/src/gui/qgscredentialdialog.cpp @@ -70,7 +70,7 @@ void QgsCredentialDialog::requestCredentials( const QString &realm, QString *use QgsDebugMsg( "Entering." ); stackedWidget->setCurrentIndex( 0 ); - chkbxPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled( ) ); + chkbxPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled() ); labelRealm->setText( realm ); leUsername->setText( *username ); lePassword->setText( *password ); @@ -124,7 +124,7 @@ void QgsCredentialDialog::requestCredentialsMasterPassword( QString *password, b QString titletxt( stored ? tr( "Enter CURRENT master authentication password" ) : tr( "Set NEW master authentication password" ) ); lblPasswordTitle->setText( titletxt ); - chkbxPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled( ) ); + chkbxPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled() ); leMasterPassVerify->setVisible( !stored ); lblDontForget->setVisible( !stored ); @@ -176,7 +176,7 @@ void QgsCredentialDialog::requestCredentialsMasterPassword( QString *password, b { *password = leMasterPass->text(); // Let's store user's preferences to use the password helper - if ( chkbxPasswordHelperEnable->isChecked() != QgsAuthManager::instance()->passwordHelperEnabled( ) ) + if ( chkbxPasswordHelperEnable->isChecked() != QgsAuthManager::instance()->passwordHelperEnabled() ) { QgsAuthManager::instance()->setPasswordHelperEnabled( chkbxPasswordHelperEnable->isChecked() ); } diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 2288b7d2c5e..db96e186bb6 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -129,9 +129,9 @@ void QgsDataSourceManagerDialog::openPage( QString pageName ) void QgsDataSourceManagerDialog::setCurrentPage( int index ) { - mPreviousRow = ui->mOptionsStackedWidget->currentIndex( ); + mPreviousRow = ui->mOptionsStackedWidget->currentIndex(); ui->mOptionsStackedWidget->setCurrentIndex( index ); - setWindowTitle( tr( "Data Source Manager | %1" ).arg( ui->mOptionsListWidget->currentItem()->text( ) ) ); + setWindowTitle( tr( "Data Source Manager | %1" ).arg( ui->mOptionsListWidget->currentItem()->text() ) ); if ( 0 <= index && index < mPageNames.size() && mPageNames.at( index ) == QStringLiteral( "raster" ) ) { emit addRasterLayer(); @@ -147,7 +147,7 @@ void QgsDataSourceManagerDialog::setPreviousPage() void QgsDataSourceManagerDialog::refresh() { - mBrowserWidget->refresh( ); + mBrowserWidget->refresh(); emit providerDialogsRefreshRequested(); } @@ -202,8 +202,8 @@ void QgsDataSourceManagerDialog::addDbProviderDialog( const QString providerKey, this, SIGNAL( showProgress( int, int ) ) ); connect( dlg, SIGNAL( progressMessage( QString ) ), this, SIGNAL( showStatusMessage( QString ) ) ); - connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); - connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); + connect( dlg, SIGNAL( connectionsChanged() ), this, SIGNAL( connectionsChanged() ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested() ), dlg, SLOT( refresh() ) ); } } @@ -214,8 +214,8 @@ void QgsDataSourceManagerDialog::addRasterProviderDialog( const QString provider { connect( dlg, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ), this, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ) ); - connect( dlg, SIGNAL( connectionsChanged( ) ), this, SIGNAL( connectionsChanged( ) ) ); - connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); + connect( dlg, SIGNAL( connectionsChanged() ), this, SIGNAL( connectionsChanged() ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested() ), dlg, SLOT( refresh() ) ); } } @@ -226,6 +226,6 @@ void QgsDataSourceManagerDialog::addVectorProviderDialog( const QString provider { connect( dlg, &QgsAbstractDataSourceWidget::addVectorLayer, this, [ = ]( const QString & vectorLayerPath, const QString & baseName ) { this->vectorLayerAdded( vectorLayerPath, baseName, providerKey ); } ); - connect( this, SIGNAL( providerDialogsRefreshRequested( ) ), dlg, SLOT( refresh( ) ) ); + connect( this, SIGNAL( providerDialogsRefreshRequested() ), dlg, SLOT( refresh() ) ); } } diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index 6ea71588866..b81835648f7 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -76,7 +76,7 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva //! Reset current page to previously selected page void setPreviousPage(); //! Refresh the browser view - void refresh( ); + void refresh(); signals: //! Emitted when a raster layer was selected for addition: for signal forwarding to QgisApp @@ -111,10 +111,10 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva private: // Return the dialog from the provider - QgsAbstractDataSourceWidget *providerDialog( const QString providerKey, const QString providerName, const QString icon, QString title = QString( ) ); - void addDbProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString( ) ); - void addRasterProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString( ) ); - void addVectorProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString( ) ); + QgsAbstractDataSourceWidget *providerDialog( const QString providerKey, const QString providerName, const QString icon, QString title = QString() ); + void addDbProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString() ); + void addRasterProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString() ); + void addVectorProviderDialog( QString const providerKey, QString const providerName, QString const icon, QString title = QString() ); Ui::QgsDataSourceManagerDialog *ui; QgsBrowserDockWidget *mBrowserWidget = nullptr; int mPreviousRow; diff --git a/src/gui/qgsfiledownloader.cpp b/src/gui/qgsfiledownloader.cpp index 2c23b78d348..f019651a327 100644 --- a/src/gui/qgsfiledownloader.cpp +++ b/src/gui/qgsfiledownloader.cpp @@ -33,7 +33,7 @@ QgsFileDownloader::QgsFileDownloader( const QUrl &url, const QString &outputFile , mDownloadCanceled( false ) , mErrors() , mGuiNotificationsEnabled( enableGuiNotifications ) - , mAuthCfg( ) + , mAuthCfg() { mFile.setFileName( outputFileName ); mAuthCfg = authcfg; diff --git a/src/gui/qgsguiutils.cpp b/src/gui/qgsguiutils.cpp index 4befb632971..f9e10f64077 100644 --- a/src/gui/qgsguiutils.cpp +++ b/src/gui/qgsguiutils.cpp @@ -91,7 +91,7 @@ namespace QgsGuiUtils Q_FOREACH ( const QByteArray &format, QImageWriter::supportedImageFormats() ) { //svg doesn't work so skip it - if ( format == "svg" ) + if ( format == "svg" ) continue; filterMap.insert( createFileFilter_( format ), format ); diff --git a/src/gui/qgsmessagebar.cpp b/src/gui/qgsmessagebar.cpp index 28da7f5b8f6..fb6a62e3560 100644 --- a/src/gui/qgsmessagebar.cpp +++ b/src/gui/qgsmessagebar.cpp @@ -85,7 +85,7 @@ QgsMessageBar::QgsMessageBar( QWidget *parent ) mCloseBtn->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ); mCloseBtn->setMenu( mCloseMenu ); mCloseBtn->setPopupMode( QToolButton::MenuButtonPopup ); - connect( mCloseBtn, &QAbstractButton::clicked, this, static_cast < bool ( QgsMessageBar::* )( ) > ( &QgsMessageBar::popWidget ) ); + connect( mCloseBtn, &QAbstractButton::clicked, this, static_cast < bool ( QgsMessageBar::* )() > ( &QgsMessageBar::popWidget ) ); mLayout->addWidget( mCloseBtn, 0, 3, 1, 1 ); mCountdownTimer = new QTimer( this ); diff --git a/src/gui/qgsowssourceselect.h b/src/gui/qgsowssourceselect.h index 942504d7e1c..62b524f0f4b 100644 --- a/src/gui/qgsowssourceselect.h +++ b/src/gui/qgsowssourceselect.h @@ -69,7 +69,7 @@ class GUI_EXPORT QgsOWSSourceSelect : public QgsAbstractDataSourceWidget, protec public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; //! Opens the create connection dialog to build a new connection void on_mNewButton_clicked(); diff --git a/src/gui/qgstaskmanagerwidget.cpp b/src/gui/qgstaskmanagerwidget.cpp index 9f6536cf609..173054a08a7 100644 --- a/src/gui/qgstaskmanagerwidget.cpp +++ b/src/gui/qgstaskmanagerwidget.cpp @@ -529,7 +529,7 @@ void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress ) mProgressBar->setValue( progress ); if ( qgsDoubleNear( progress, 0.0 ) ) mProgressBar->setMaximum( 0 ); - else if ( mProgressBar->maximum( ) == 0 ) + else if ( mProgressBar->maximum() == 0 ) mProgressBar->setMaximum( 100 ); setToolTip( mManager->activeTasks().at( 0 )->description() ); } diff --git a/src/plugins/grass/qtermwidget/History.cpp b/src/plugins/grass/qtermwidget/History.cpp index 943a4c76eec..909ebd10b64 100644 --- a/src/plugins/grass/qtermwidget/History.cpp +++ b/src/plugins/grass/qtermwidget/History.cpp @@ -547,7 +547,7 @@ void* CompactHistoryBlock::allocate ( size_t length ) return block; } -void CompactHistoryBlock::deallocate ( ) +void CompactHistoryBlock::deallocate () { allocCount--; Q_ASSERT ( allocCount >= 0 ); diff --git a/src/plugins/grass/qtermwidget/Vt102Emulation.cpp b/src/plugins/grass/qtermwidget/Vt102Emulation.cpp index bf34d357301..822a16a6f19 100644 --- a/src/plugins/grass/qtermwidget/Vt102Emulation.cpp +++ b/src/plugins/grass/qtermwidget/Vt102Emulation.cpp @@ -263,9 +263,9 @@ void Vt102Emulation::initTokenizer() #define eec(C) (p >= 3 && cc == (C)) #define ees(C) (p >= 3 && cc < 256 && (charClass[cc] & (C)) == (C)) #define eps(C) (p >= 3 && s[2] != '?' && s[2] != '!' && s[2] != '>' && cc < 256 && (charClass[cc] & (C)) == (C)) -#define epp( ) (p >= 3 && s[2] == '?') -#define epe( ) (p >= 3 && s[2] == '!') -#define egt( ) (p >= 3 && s[2] == '>') +#define epp() (p >= 3 && s[2] == '?') +#define epe() (p >= 3 && s[2] == '!') +#define egt() (p >= 3 && s[2] == '>') #define Xpe (tokenBufferPos >= 2 && tokenBuffer[1] == ']') #define Xte (Xpe && cc == 7 ) #define ces(C) (cc < 256 && (charClass[cc] & (C)) == (C) && !Xte) diff --git a/src/providers/arcgisrest/qgsarcgisservicesourceselect.h b/src/providers/arcgisrest/qgsarcgisservicesourceselect.h index 7f8d94a8272..4f8187e5523 100644 --- a/src/providers/arcgisrest/qgsarcgisservicesourceselect.h +++ b/src/providers/arcgisrest/qgsarcgisservicesourceselect.h @@ -99,7 +99,7 @@ class QgsArcGisServiceSourceSelect : public QgsAbstractDataSourceWidget, protect public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; private slots: void addEntryToServerList(); diff --git a/src/providers/db2/qgsdb2geometrycolumns.cpp b/src/providers/db2/qgsdb2geometrycolumns.cpp index e03a9f5ed49..35b579dcf77 100644 --- a/src/providers/db2/qgsdb2geometrycolumns.cpp +++ b/src/providers/db2/qgsdb2geometrycolumns.cpp @@ -27,7 +27,7 @@ QgsDb2GeometryColumns::QgsDb2GeometryColumns( const QSqlDatabase &db ) QgsDebugMsg( "constructing" ); } -QgsDb2GeometryColumns::~QgsDb2GeometryColumns( ) +QgsDb2GeometryColumns::~QgsDb2GeometryColumns() { mQuery.clear(); } diff --git a/src/providers/db2/qgsdb2provider.cpp b/src/providers/db2/qgsdb2provider.cpp index 091ce93c13e..294f2bbda1d 100644 --- a/src/providers/db2/qgsdb2provider.cpp +++ b/src/providers/db2/qgsdb2provider.cpp @@ -952,7 +952,7 @@ bool QgsDb2Provider::addFeatures( QgsFeatureList &flist, Flags flags ) { copyOperation = true; // FID is first field but no attribute in attrs } - else if ( mAttributeFields.count() != attrs.count() ) + else if ( mAttributeFields.count() != attrs.count() ) { QgsDebugMsg( "Count mismatch - failing" ); return false; diff --git a/src/providers/db2/qgsdb2sourceselect.h b/src/providers/db2/qgsdb2sourceselect.h index b8803def21f..e58a327b850 100644 --- a/src/providers/db2/qgsdb2sourceselect.h +++ b/src/providers/db2/qgsdb2sourceselect.h @@ -116,7 +116,7 @@ class QgsDb2SourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDb void addTables(); void buildQuery(); //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; /** Connects to the database using the stored connection parameters. * Once connected, available layers are displayed. diff --git a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp index 4bcc33659a6..62174644b4b 100644 --- a/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp +++ b/src/providers/delimitedtext/qgsdelimitedtextsourceselect.cpp @@ -199,7 +199,7 @@ void QgsDelimitedTextSourceSelect::on_buttonBox_accepted() // add the layer to the map emit addVectorLayer( QString::fromAscii( url.toEncoded() ), txtLayerName->text(), QStringLiteral( "delimitedtext" ) ); - if ( widgetMode() == QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode() == QgsProviderRegistry::WidgetMode::None ) { accept(); } diff --git a/src/providers/grass/qgsgrassimport.cpp b/src/providers/grass/qgsgrassimport.cpp index 4c3abc31e97..4fd679cd8d5 100644 --- a/src/providers/grass/qgsgrassimport.cpp +++ b/src/providers/grass/qgsgrassimport.cpp @@ -250,15 +250,15 @@ bool QgsGrassRasterImport::import() { QgsDebugMsg( QString( "band = %1" ).arg( band ) ); int colorInterpretation = provider->colorInterpretation( band ); - if ( colorInterpretation == QgsRaster::RedBand ) + if ( colorInterpretation == QgsRaster::RedBand ) { redBand = band; } - else if ( colorInterpretation == QgsRaster::GreenBand ) + else if ( colorInterpretation == QgsRaster::GreenBand ) { greenBand = band; } - else if ( colorInterpretation == QgsRaster::BlueBand ) + else if ( colorInterpretation == QgsRaster::BlueBand ) { blueBand = band; } diff --git a/src/providers/mssql/qgsmssqlprovider.cpp b/src/providers/mssql/qgsmssqlprovider.cpp index 47a098c5aaf..31cd69190e8 100644 --- a/src/providers/mssql/qgsmssqlprovider.cpp +++ b/src/providers/mssql/qgsmssqlprovider.cpp @@ -379,7 +379,7 @@ void QgsMssqlProvider::loadFields() // Get computed columns which need to be ignored on insert or update. if ( !query.exec( QStringLiteral( "SELECT name FROM sys.columns WHERE is_computed = 1 AND object_id = OBJECT_ID('[%1].[%2]')" ).arg( mSchemaName, mTableName ) ) ) { - pushError( query.lastError().text( ) ); + pushError( query.lastError().text() ); return; } @@ -393,7 +393,7 @@ void QgsMssqlProvider::loadFields() if ( !query.exec( QStringLiteral( "exec sp_columns @table_name = N'%1', @table_owner = '%2'" ).arg( mTableName, mSchemaName ) ) ) { - pushError( query.lastError().text( ) ); + pushError( query.lastError().text() ); return; } if ( query.isActive() ) @@ -2302,7 +2302,7 @@ QGISEXTERN QString getStyleById( const QString &uri, QString styleId, QString &e QString msg = query.lastError().text(); QgsDebugMsg( msg ); errCause = query.lastError().text(); - return QString( ); + return QString(); } while ( query.next() ) { diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h index d9310cfb61d..f80a27a0b25 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.h +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -84,7 +84,7 @@ class QgsMssqlSourceSelect : public QgsAbstractDataSourceWidget, private Ui::Qgs public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; //! Determines the tables the user selected and closes the dialog void addTables(); diff --git a/src/providers/postgres/qgspgsourceselect.h b/src/providers/postgres/qgspgsourceselect.h index 1fa5ce3fff1..f81a799da81 100644 --- a/src/providers/postgres/qgspgsourceselect.h +++ b/src/providers/postgres/qgspgsourceselect.h @@ -84,7 +84,7 @@ class QgsPgSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsDbS public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; //! Determines the tables the user selected and closes the dialog void addTables(); void buildQuery(); diff --git a/src/providers/spatialite/qgsspatialitesourceselect.h b/src/providers/spatialite/qgsspatialitesourceselect.h index 281d6d2ffd4..659759587be 100644 --- a/src/providers/spatialite/qgsspatialitesourceselect.h +++ b/src/providers/spatialite/qgsspatialitesourceselect.h @@ -69,7 +69,7 @@ class QgsSpatiaLiteSourceSelect: public QgsAbstractDataSourceWidget, private Ui: public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; /** Connects to the database using the stored connection parameters. * Once connected, available layers are displayed. diff --git a/src/providers/virtual/qgsembeddedlayerselectdialog.cpp b/src/providers/virtual/qgsembeddedlayerselectdialog.cpp index aab805cd433..27c0a6735ba 100644 --- a/src/providers/virtual/qgsembeddedlayerselectdialog.cpp +++ b/src/providers/virtual/qgsembeddedlayerselectdialog.cpp @@ -34,7 +34,7 @@ QgsEmbeddedLayerSelectDialog::QgsEmbeddedLayerSelectDialog( QWidget *parent, Qgs mTreeView( tv ) { setupUi( this ); - updateLayersList( ); + updateLayersList(); } QStringList QgsEmbeddedLayerSelectDialog::layers() const @@ -49,10 +49,10 @@ QStringList QgsEmbeddedLayerSelectDialog::layers() const return ids; } -void QgsEmbeddedLayerSelectDialog::updateLayersList( ) +void QgsEmbeddedLayerSelectDialog::updateLayersList() { // populate list - mLayers->clear( ); + mLayers->clear(); QList layers = mTreeView->layerTreeModel()->rootGroup()->findLayers(); Q_FOREACH ( const QgsLayerTreeLayer *l, layers ) { diff --git a/src/providers/virtual/qgsembeddedlayerselectdialog.h b/src/providers/virtual/qgsembeddedlayerselectdialog.h index 2bca74ce3ca..bce7da6f74f 100644 --- a/src/providers/virtual/qgsembeddedlayerselectdialog.h +++ b/src/providers/virtual/qgsembeddedlayerselectdialog.h @@ -37,7 +37,7 @@ class QgsEmbeddedLayerSelectDialog : public QDialog, private Ui::QgsEmbeddedLaye QStringList layers() const; public slots: - void updateLayersList( ); + void updateLayersList(); private: QgsLayerTreeView *mTreeView = nullptr; diff --git a/src/providers/virtual/qgsvirtuallayersourceselect.cpp b/src/providers/virtual/qgsvirtuallayersourceselect.cpp index 8d8ccf00ecf..d87887126fc 100644 --- a/src/providers/virtual/qgsvirtuallayersourceselect.cpp +++ b/src/providers/virtual/qgsvirtuallayersourceselect.cpp @@ -44,7 +44,7 @@ QgsVirtualLayerSourceSelect::QgsVirtualLayerSourceSelect( QWidget *parent, Qt::W { setupUi( this ); - if ( widgetMode != QgsProviderRegistry::WidgetMode::None ) + if ( widgetMode != QgsProviderRegistry::WidgetMode::None ) { buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Cancel ) ); buttonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add" ) ); diff --git a/src/providers/virtual/qgsvirtuallayersourceselect.h b/src/providers/virtual/qgsvirtuallayersourceselect.h index cf45156887c..08f6f97f03a 100644 --- a/src/providers/virtual/qgsvirtuallayersourceselect.h +++ b/src/providers/virtual/qgsvirtuallayersourceselect.h @@ -40,7 +40,7 @@ class QgsVirtualLayerSourceSelect : public QgsAbstractDataSourceWidget, private public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; private slots: void on_buttonBox_accepted(); diff --git a/src/providers/wfs/qgswfscapabilities.cpp b/src/providers/wfs/qgswfscapabilities.cpp index a2261fd2519..fc20e4911a0 100644 --- a/src/providers/wfs/qgswfscapabilities.cpp +++ b/src/providers/wfs/qgswfscapabilities.cpp @@ -297,8 +297,8 @@ void QgsWfsCapabilities::capabilitiesReplyFinished() QDomNodeList operationNodes = doc.elementsByTagName( "Operation" ); for ( int i = 0; i < operationNodes.count(); i++ ) { - QDomElement operationElement = operationNodes.at( i ).toElement( ); - if ( operationElement.isElement( ) && "Transaction" == operationElement.attribute( "name" ) ) + QDomElement operationElement = operationNodes.at( i ).toElement(); + if ( operationElement.isElement() && "Transaction" == operationElement.attribute( "name" ) ) { insertCap = true; updateCap = true; diff --git a/src/providers/wfs/qgswfsprovider.cpp b/src/providers/wfs/qgswfsprovider.cpp index 39369d4ab17..bb589f42abd 100644 --- a/src/providers/wfs/qgswfsprovider.cpp +++ b/src/providers/wfs/qgswfsprovider.cpp @@ -825,7 +825,7 @@ bool QgsWFSProvider::addFeatures( QgsFeatureList &flist, Flags flags ) QDomElement geomElem = transactionDoc.createElementNS( mApplicationNamespace, mShared->mGeometryAttribute ); QgsGeometry the_geom( geometry ); // convert to multi if the layer geom type is multi and the geom is not - if ( QgsWkbTypes::isMultiType( this->wkbType( ) ) && ! the_geom.isMultipart( ) ) + if ( QgsWkbTypes::isMultiType( this->wkbType() ) && ! the_geom.isMultipart() ) { the_geom.convertToMultiType(); } diff --git a/src/providers/wfs/qgswfsshareddata.cpp b/src/providers/wfs/qgswfsshareddata.cpp index ddf2dce4a30..1e5d508d475 100644 --- a/src/providers/wfs/qgswfsshareddata.cpp +++ b/src/providers/wfs/qgswfsshareddata.cpp @@ -884,7 +884,7 @@ void QgsWFSSharedData::serializeFeatures( QVector &featu const QVariant &v = gmlFeature.attributes().value( i ); if ( v.type() == QVariant::DateTime && !v.isNull() ) cachedFeature.setAttribute( idx, QVariant( v.toDateTime().toMSecsSinceEpoch() ) ); - else if ( v.type() != dataProviderFields.at( idx ).type() ) + else if ( v.type() != dataProviderFields.at( idx ).type() ) cachedFeature.setAttribute( idx, QgsVectorDataProvider::convertValue( dataProviderFields.at( idx ).type(), v.toString() ) ); else cachedFeature.setAttribute( idx, v ); diff --git a/src/providers/wfs/qgswfssourceselect.h b/src/providers/wfs/qgswfssourceselect.h index 959b8de9b98..42d7bc15e1e 100644 --- a/src/providers/wfs/qgswfssourceselect.h +++ b/src/providers/wfs/qgswfssourceselect.h @@ -81,7 +81,7 @@ class QgsWFSSourceSelect: public QgsAbstractDataSourceWidget, private Ui::QgsWFS public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; private slots: void addEntryToServerList(); diff --git a/src/providers/wms/qgswmsdataitems.cpp b/src/providers/wms/qgswmsdataitems.cpp index 9a07aecff0b..d44c11150e8 100644 --- a/src/providers/wms/qgswmsdataitems.cpp +++ b/src/providers/wms/qgswmsdataitems.cpp @@ -427,7 +427,7 @@ void QgsWMSRootItem::newConnection() if ( nc.exec() ) { - refreshConnections( ); + refreshConnections(); } } #endif diff --git a/src/providers/wms/qgswmssourceselect.h b/src/providers/wms/qgswmssourceselect.h index e80a5be2f0c..ba1f062a8c9 100644 --- a/src/providers/wms/qgswmssourceselect.h +++ b/src/providers/wms/qgswmssourceselect.h @@ -56,7 +56,7 @@ class QgsWMSSourceSelect : public QgsAbstractDataSourceWidget, private Ui::QgsWM public slots: //! Triggered when the provider's connections need to be refreshed - void refresh( ) override; + void refresh() override; //! Opens the create connection dialog to build a new connection void on_btnNew_clicked(); diff --git a/src/server/qgsbufferserverrequest.h b/src/server/qgsbufferserverrequest.h index f1ba269c15d..67cd12854f8 100644 --- a/src/server/qgsbufferserverrequest.h +++ b/src/server/qgsbufferserverrequest.h @@ -41,7 +41,7 @@ class SERVER_EXPORT QgsBufferServerRequest : public QgsServerRequest * \param url the url string * \param method the request method */ - QgsBufferServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ), QByteArray *data = nullptr ); + QgsBufferServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers(), QByteArray *data = nullptr ); /** * Constructor @@ -49,7 +49,7 @@ class SERVER_EXPORT QgsBufferServerRequest : public QgsServerRequest * \param url QUrl * \param method the request method */ - QgsBufferServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ), QByteArray *data = nullptr ); + QgsBufferServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers(), QByteArray *data = nullptr ); ~QgsBufferServerRequest(); diff --git a/src/server/qgsbufferserverresponse.h b/src/server/qgsbufferserverresponse.h index 197def6b1ed..446b5e1c3ee 100644 --- a/src/server/qgsbufferserverresponse.h +++ b/src/server/qgsbufferserverresponse.h @@ -75,7 +75,7 @@ class SERVER_EXPORT QgsBufferServerResponse: public QgsServerResponse /** Return the http status code */ - int statusCode( ) const override { return mStatusCode; } + int statusCode() const override { return mStatusCode; } /** * Send error diff --git a/src/server/qgsfcgiserverresponse.h b/src/server/qgsfcgiserverresponse.h index dedc46465b4..3601eb8d69d 100644 --- a/src/server/qgsfcgiserverresponse.h +++ b/src/server/qgsfcgiserverresponse.h @@ -51,7 +51,7 @@ class SERVER_EXPORT QgsFcgiServerResponse: public QgsServerResponse void setStatusCode( int code ) override; - int statusCode( ) const override { return mStatusCode; } + int statusCode() const override { return mStatusCode; } void sendError( int code, const QString &message ) override; diff --git a/src/server/qgsfilterresponsedecorator.h b/src/server/qgsfilterresponsedecorator.h index 14727eb7495..af9fa4d486b 100644 --- a/src/server/qgsfilterresponsedecorator.h +++ b/src/server/qgsfilterresponsedecorator.h @@ -50,13 +50,13 @@ class QgsFilterResponseDecorator: public QgsServerResponse QString header( const QString &key ) const override { return mResponse.header( key ); } - QMap headers() const override { return mResponse.headers( ); } + QMap headers() const override { return mResponse.headers(); } bool headersSent() const override { return mResponse.headersSent(); } void setStatusCode( int code ) override { mResponse.setStatusCode( code ); } - int statusCode( ) const override { return mResponse.statusCode( ); } + int statusCode() const override { return mResponse.statusCode(); } void sendError( int code, const QString &message ) override { mResponse.sendError( code, message ); } diff --git a/src/server/qgsrequesthandler.cpp b/src/server/qgsrequesthandler.cpp index 8a57732b67c..0254eb0ed83 100644 --- a/src/server/qgsrequesthandler.cpp +++ b/src/server/qgsrequesthandler.cpp @@ -76,7 +76,7 @@ QString QgsRequestHandler::responseHeader( const QString &name ) const QMap QgsRequestHandler::responseHeaders() const { - return mResponse.headers( ); + return mResponse.headers(); } void QgsRequestHandler::setRequestHeader( const QString &name, const QString &value ) @@ -97,7 +97,7 @@ QString QgsRequestHandler::requestHeader( const QString &name ) const QMap QgsRequestHandler::requestHeaders() const { - return mRequest.headers( ); + return mRequest.headers(); } @@ -123,12 +123,12 @@ QByteArray QgsRequestHandler::body() const QByteArray QgsRequestHandler::data() const { - return mRequest.data( ); + return mRequest.data(); } QString QgsRequestHandler::url() const { - return mRequest.url( ).toString( ); + return mRequest.url().toString(); } void QgsRequestHandler::setStatusCode( int code ) diff --git a/src/server/qgsrequesthandler.h b/src/server/qgsrequesthandler.h index 7ab50f30975..f4ff2544c2c 100644 --- a/src/server/qgsrequesthandler.h +++ b/src/server/qgsrequesthandler.h @@ -114,7 +114,7 @@ class SERVER_EXPORT QgsRequestHandler void setStatusCode( int code ); //! Return response http status code - int statusCode( ) const; + int statusCode() const; /** Return the parsed parameters as a key-value pair, to modify * a parameter setParameter( const QString &key, const QString &value) diff --git a/src/server/qgsserver.cpp b/src/server/qgsserver.cpp index 59514a7ccdc..33e8ebc19b7 100644 --- a/src/server/qgsserver.cpp +++ b/src/server/qgsserver.cpp @@ -65,7 +65,7 @@ QgsServerSettings QgsServer::sSettings; QgsServiceRegistry QgsServer::sServiceRegistry; -QgsServer::QgsServer( ) +QgsServer::QgsServer() { // QgsApplication must exist if ( qobject_cast( qApp ) == nullptr ) @@ -183,7 +183,7 @@ QString QgsServer::configPath( const QString &defaultConfigPath, const QMap QgsServerRequest::headers( ) const +QMap QgsServerRequest::headers() const { return mHeaders; } diff --git a/src/server/qgsserverrequest.h b/src/server/qgsserverrequest.h index 26c7e80cf84..b3ae6f12a55 100644 --- a/src/server/qgsserverrequest.h +++ b/src/server/qgsserverrequest.h @@ -66,7 +66,7 @@ class SERVER_EXPORT QgsServerRequest * \param method the request method * \param headers */ - QgsServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ) ); + QgsServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers() ); /** * Constructor @@ -75,7 +75,7 @@ class SERVER_EXPORT QgsServerRequest * \param method the request method * \param headers */ - QgsServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers( ) ); + QgsServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers() ); //! destructor virtual ~QgsServerRequest() = default; @@ -129,7 +129,7 @@ class SERVER_EXPORT QgsServerRequest * Return the header map * @return the headers map */ - QMap headers( ) const; + QMap headers() const; /** * Remove an header diff --git a/src/server/qgsserverresponse.h b/src/server/qgsserverresponse.h index d5c8026ec31..022f837ad03 100644 --- a/src/server/qgsserverresponse.h +++ b/src/server/qgsserverresponse.h @@ -70,7 +70,7 @@ class SERVER_EXPORT QgsServerResponse /** * Return the header value */ - virtual QMap headers( ) const = 0; + virtual QMap headers() const = 0; /** * Return true if the headers have alredy been sent @@ -85,7 +85,7 @@ class SERVER_EXPORT QgsServerResponse /** Return the http status code */ - virtual int statusCode( ) const = 0; + virtual int statusCode() const = 0; /** * Send error diff --git a/tests/code_layout/sipifyheader.expected.sip b/tests/code_layout/sipifyheader.expected.sip index 542ef882676..07bdc5f3c38 100644 --- a/tests/code_layout/sipifyheader.expected.sip +++ b/tests/code_layout/sipifyheader.expected.sip @@ -308,8 +308,8 @@ Mulitline body %Docstring remove argument %End - void method( ); - void test( ); + void method(); + void test(); void avoidIntersections( const QList &avoidIntersectionsLayers ); void position( ); diff --git a/tests/src/app/testqgsattributetable.cpp b/tests/src/app/testqgsattributetable.cpp index cc2a0bc1050..0fea0b0152f 100644 --- a/tests/src/app/testqgsattributetable.cpp +++ b/tests/src/app/testqgsattributetable.cpp @@ -255,31 +255,31 @@ void TestQgsAttributeTable::testRegression15974() QString path = QDir::tempPath() + "/testshp15974.shp"; std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "polygon?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); QVERIFY( tempLayer->isValid() ); - QgsVectorFileWriter::writeAsVectorFormat( tempLayer.get( ), path, "system", QgsCoordinateReferenceSystem( 4326 ), "ESRI Shapefile" ); + QgsVectorFileWriter::writeAsVectorFormat( tempLayer.get(), path, "system", QgsCoordinateReferenceSystem( 4326 ), "ESRI Shapefile" ); std::unique_ptr< QgsVectorLayer> shpLayer( new QgsVectorLayer( path, QStringLiteral( "test" ), QStringLiteral( "ogr" ) ) ); QgsFeature f1( shpLayer->dataProvider()->fields(), 1 ); QgsGeometry geom; geom = QgsGeometry().fromWkt( QStringLiteral( "polygon((0 0, 0 1, 1 1, 1 0, 0 0))" ) ); - Q_ASSERT( geom.isGeosValid( ) ); + Q_ASSERT( geom.isGeosValid() ); f1.setGeometry( geom ); QgsFeature f2( shpLayer->dataProvider()->fields(), 2 ); f2.setGeometry( geom ); QgsFeature f3( shpLayer->dataProvider()->fields(), 3 ); f3.setGeometry( geom ); - QVERIFY( shpLayer->startEditing( ) ); + QVERIFY( shpLayer->startEditing() ); QVERIFY( shpLayer->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) ); std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( shpLayer.get() ) ); - QCOMPARE( shpLayer->featureCount( ), 3L ); - mQgisApp->saveEdits( shpLayer.get( ) ); - QCOMPARE( shpLayer->featureCount( ), 3L ); + QCOMPARE( shpLayer->featureCount(), 3L ); + mQgisApp->saveEdits( shpLayer.get() ); + QCOMPARE( shpLayer->featureCount(), 3L ); QCOMPARE( dlg->mMainView->masterModel()->rowCount(), 3 ); - QCOMPARE( dlg->mMainView->mLayerCache->cachedFeatureIds( ).count(), 3 ); - QCOMPARE( dlg->mMainView->featureCount( ), 3 ); + QCOMPARE( dlg->mMainView->mLayerCache->cachedFeatureIds().count(), 3 ); + QCOMPARE( dlg->mMainView->featureCount(), 3 ); // All the following instructions made the test pass, before the connections to invalidate() // were introduced in QgsDualView::initModels // dlg->mMainView->mFilterModel->setSourceModel( dlg->mMainView->masterModel() ); // dlg->mMainView->mFilterModel->invalidate(); - QCOMPARE( dlg->mMainView->filteredFeatureCount( ), 3 ); + QCOMPARE( dlg->mMainView->filteredFeatureCount(), 3 ); } diff --git a/tests/src/core/testqgs25drenderer.cpp b/tests/src/core/testqgs25drenderer.cpp index 04d61766805..0621345f037 100644 --- a/tests/src/core/testqgs25drenderer.cpp +++ b/tests/src/core/testqgs25drenderer.cpp @@ -115,7 +115,7 @@ void TestQgs25DRenderer::render() mReport += QLatin1String( "

Render

\n" ); //setup 25d renderer - Qgs25DRenderer *renderer = new Qgs25DRenderer( ); + Qgs25DRenderer *renderer = new Qgs25DRenderer(); renderer->setShadowEnabled( false ); renderer->setWallShadingEnabled( false ); renderer->setRoofColor( QColor( "#fdbf6f" ) ); diff --git a/tests/src/core/testqgsauthmanager.cpp b/tests/src/core/testqgsauthmanager.cpp index 7d254f0af5f..e43e5b36282 100644 --- a/tests/src/core/testqgsauthmanager.cpp +++ b/tests/src/core/testqgsauthmanager.cpp @@ -419,7 +419,7 @@ QList TestQgsAuthManager::registerAuthConfigs() void TestQgsAuthManager::doSync() { QgsAuthManager *authm = QgsAuthManager::instance(); - QVERIFY( authm->passwordHelperSync( ) ); + QVERIFY( authm->passwordHelperSync() ); } void TestQgsAuthManager::testPasswordHelper() @@ -441,7 +441,7 @@ void TestQgsAuthManager::testPasswordHelper() // Sync with wallet QVERIFY( authm->setMasterPassword( mPass, true ) ); - QVERIFY( authm->masterPasswordIsSet( ) ); + QVERIFY( authm->masterPasswordIsSet() ); QObject::connect( authm, &QgsAuthManager::passwordHelperSuccess, QApplication::instance(), &QCoreApplication::quit ); QObject::connect( authm, &QgsAuthManager::passwordHelperFailure, @@ -449,22 +449,22 @@ void TestQgsAuthManager::testPasswordHelper() QMetaObject::invokeMethod( this, "doSync", Qt::QueuedConnection ); qApp->exec(); authm->clearMasterPassword(); - QVERIFY( authm->setMasterPassword( ) ); - QVERIFY( authm->masterPasswordIsSet( ) ); + QVERIFY( authm->setMasterPassword() ); + QVERIFY( authm->masterPasswordIsSet() ); // Delete from wallet authm->clearMasterPassword(); - QVERIFY( authm->passwordHelperDelete( ) ); - QVERIFY( ! authm->setMasterPassword( ) ); - QVERIFY( ! authm->masterPasswordIsSet( ) ); + QVERIFY( authm->passwordHelperDelete() ); + QVERIFY( ! authm->setMasterPassword() ); + QVERIFY( ! authm->masterPasswordIsSet() ); // Re-sync QVERIFY( authm->setMasterPassword( mPass, true ) ); QMetaObject::invokeMethod( this, "doSync", Qt::QueuedConnection ); qApp->exec(); authm->clearMasterPassword(); - QVERIFY( authm->setMasterPassword( ) ); - QVERIFY( authm->masterPasswordIsSet( ) ); + QVERIFY( authm->setMasterPassword() ); + QVERIFY( authm->masterPasswordIsSet() ); } diff --git a/tests/src/core/testqgscoordinatereferencesystem.cpp b/tests/src/core/testqgscoordinatereferencesystem.cpp index f9811c09f22..ebf0d292da3 100644 --- a/tests/src/core/testqgscoordinatereferencesystem.cpp +++ b/tests/src/core/testqgscoordinatereferencesystem.cpp @@ -328,7 +328,7 @@ QString TestQgsCoordinateReferenceSystem::testESRIWkt( int i, QgsCoordinateRefer if ( myCrs.toProj4().indexOf( myTOWGS84Strings[i] ) == -1 ) return QStringLiteral( "test %1 [%2] not found, PROJ.4 = [%3] expecting [%4]" ).arg( i ).arg( myTOWGS84Strings[i], myCrs.toProj4(), myProj4Strings[i] ); - if ( myCrs.authid() != myAuthIdStrings[i] ) + if ( myCrs.authid() != myAuthIdStrings[i] ) return QStringLiteral( "test %1 AUTHID = [%2] expecting [%3]" ).arg( i ).arg( myCrs.authid(), myAuthIdStrings[i] ); diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index 485093e642d..458e67aa0f6 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -3570,12 +3570,12 @@ void TestQgsGeometry::triangle() QVERIFY( !t3.interiorRing( 0 ) ); // equality - QVERIFY( QgsTriangle() == QgsTriangle( ) ); // empty + QVERIFY( QgsTriangle() == QgsTriangle() ); // empty QVERIFY( QgsTriangle() == QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 10 ) ) ); // empty - QVERIFY( QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 10 ) ) == QgsTriangle() ); // empty + QVERIFY( QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 10 ) ) == QgsTriangle() ); // empty QVERIFY( QgsTriangle() != QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ), QgsPoint( 0, 10 ) ) ); - QVERIFY( QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ), QgsPoint( 0, 10 ) ) != QgsTriangle() ); - QVERIFY( QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ), QgsPoint( 0, 10 ) ) != QgsTriangle( QgsPoint( 0, 10 ), QgsPoint( 5, 5 ), QgsPoint( 0, 0 ) ) ); + QVERIFY( QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ), QgsPoint( 0, 10 ) ) != QgsTriangle() ); + QVERIFY( QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ), QgsPoint( 0, 10 ) ) != QgsTriangle( QgsPoint( 0, 10 ), QgsPoint( 5, 5 ), QgsPoint( 0, 0 ) ) ); // clone QgsTriangle *t4 = t3.clone(); @@ -4426,7 +4426,7 @@ void TestQgsGeometry::regularPolygon() // polygon QgsPointSequence ptsPol; std::unique_ptr< QgsPolygonV2 > pol( new QgsPolygonV2() ); - pol.reset( rp10.toPolygon( ) ); + pol.reset( rp10.toPolygon() ); QCOMPARE( pol->numInteriorRings(), 0 ); QCOMPARE( pol->exteriorRing()->numPoints(), 5 ); @@ -4440,7 +4440,7 @@ void TestQgsGeometry::regularPolygon() ptsPol.pop_back(); std::unique_ptr< QgsLineString > l( new QgsLineString() ); - l.reset( rp10.toLineString( ) ); + l.reset( rp10.toLineString() ); QCOMPARE( l->numPoints(), 4 ); QgsPointSequence pts_l; l->points( pts_l ); diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 99fc0c2dedd..9bbae9118be 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -2088,7 +2088,7 @@ void TestQgsProcessing::parameterMatrix() QgsProcessingContext context; // not optional! - std::unique_ptr< QgsProcessingParameterMatrix > def( new QgsProcessingParameterMatrix( "non_optional", QString(), 3, false, QStringList(), QString( ), false ) ); + std::unique_ptr< QgsProcessingParameterMatrix > def( new QgsProcessingParameterMatrix( "non_optional", QString(), 3, false, QStringList(), QString(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); diff --git a/tests/src/core/testqgsproperty.cpp b/tests/src/core/testqgsproperty.cpp index 6518d0fd326..905bfa36aed 100644 --- a/tests/src/core/testqgsproperty.cpp +++ b/tests/src/core/testqgsproperty.cpp @@ -144,8 +144,8 @@ void TestQgsProperty::conversions() collection.property( 0 ).setStaticValue( QColor( 255, 200, 100, 50 ) ); //color in qvariant QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) ); QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) ); - c1.setStaticValue( QColor( ) ); //invalid color in qvariant, should return default color - collection.property( 0 ).setStaticValue( QColor( ) ); //invalid color in qvariant, should return default color + c1.setStaticValue( QColor() ); //invalid color in qvariant, should return default color + collection.property( 0 ).setStaticValue( QColor() ); //invalid color in qvariant, should return default color QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) ); QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) ); c1.setStaticValue( QgsSymbolLayerUtils::encodeColor( QColor( 255, 200, 100, 50 ) ) ); //encoded color diff --git a/tests/src/gui/testqgsfiledownloader.cpp b/tests/src/gui/testqgsfiledownloader.cpp index 96685fc1b8b..14af041da5f 100644 --- a/tests/src/gui/testqgsfiledownloader.cpp +++ b/tests/src/gui/testqgsfiledownloader.cpp @@ -139,7 +139,7 @@ void TestQgsFileDownloader::init() mError = false; mCompleted = false; mExited = false; - mTempFile = new QTemporaryFile( ); + mTempFile = new QTemporaryFile(); Q_ASSERT( mTempFile->open() ); mTempFile->close(); } diff --git a/tests/src/providers/grass/testqgsgrassprovider.cpp b/tests/src/providers/grass/testqgsgrassprovider.cpp index 320c45dc242..acf5faad0e1 100644 --- a/tests/src/providers/grass/testqgsgrassprovider.cpp +++ b/tests/src/providers/grass/testqgsgrassprovider.cpp @@ -1385,7 +1385,7 @@ void TestQgsGrassProvider::edit() } else if ( command.command == TestQgsGrassCommand::UndoAll ) { - if ( grassLayer->undoStack()->count() != editCommands.size() || + if ( grassLayer->undoStack()->count() != editCommands.size() || grassLayer->undoStack()->count() != expectedLayer->undoStack()->count() ) { reportRow( QStringLiteral( "Different undo stack size: %1, expected: %2, editCommands: %3" ) @@ -1414,7 +1414,7 @@ void TestQgsGrassProvider::edit() } else if ( command.command == TestQgsGrassCommand::RedoAll ) { - if ( grassLayer->undoStack()->count() != editCommands.size() || + if ( grassLayer->undoStack()->count() != editCommands.size() || grassLayer->undoStack()->count() != expectedLayer->undoStack()->count() ) { reportRow( QStringLiteral( "Different undo stack size: %1, expected: %2, editCommands: %3" ) From 744fbc534633d0e53bfaf134176e1db566bc1153 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Wed, 19 Jul 2017 09:48:42 +0200 Subject: [PATCH 079/266] fix sipify tests followup 4f9a9e0360a3ce6b4289e948bb2549a0496f48f2 --- tests/code_layout/sipifyheader.expected.sip | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/code_layout/sipifyheader.expected.sip b/tests/code_layout/sipifyheader.expected.sip index 07bdc5f3c38..542ef882676 100644 --- a/tests/code_layout/sipifyheader.expected.sip +++ b/tests/code_layout/sipifyheader.expected.sip @@ -308,8 +308,8 @@ Mulitline body %Docstring remove argument %End - void method(); - void test(); + void method( ); + void test( ); void avoidIntersections( const QList &avoidIntersectionsLayers ); void position( ); From 0665072d940056f03e78ecbab6755657210b4e08 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Wed, 19 Jul 2017 15:04:52 +0700 Subject: [PATCH 080/266] [FEATURE] Locked aspect ratio state for Save as image/PDF" (#4880) Sponsored by Andreas Neumann. --- images/images.qrc | 1 + images/themes/default/unlockedGray.svg | 1 + python/gui/gui_auto.sip | 1 + python/gui/qgsextentgroupbox.sip | 18 ++++ python/gui/qgsratiolockbutton.sip | 69 +++++++++++++++ src/app/qgsmapsavedialog.cpp | 52 ++++++++++- src/app/qgsmapsavedialog.h | 1 + src/gui/CMakeLists.txt | 2 + src/gui/qgsextentgroupbox.cpp | 1 + src/gui/qgsextentgroupbox.h | 14 +++ src/gui/qgsratiolockbutton.cpp | 110 +++++++++++++++++++++++ src/gui/qgsratiolockbutton.h | 78 ++++++++++++++++ src/ui/qgsmapsavedialog.ui | 118 +++++++++++++++++-------- 13 files changed, 427 insertions(+), 39 deletions(-) create mode 100644 images/themes/default/unlockedGray.svg create mode 100644 python/gui/qgsratiolockbutton.sip create mode 100644 src/gui/qgsratiolockbutton.cpp create mode 100644 src/gui/qgsratiolockbutton.h diff --git a/images/images.qrc b/images/images.qrc index 658450c734c..f8baa88fd4a 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -467,6 +467,7 @@ themes/default/transformed.svg themes/default/transp-background_8x8.png themes/default/unlocked.svg + themes/default/unlockedGray.svg themes/default/user.svg flags/eu.png flags/bn.png diff --git a/images/themes/default/unlockedGray.svg b/images/themes/default/unlockedGray.svg new file mode 100644 index 00000000000..1cdb0a295e9 --- /dev/null +++ b/images/themes/default/unlockedGray.svg @@ -0,0 +1 @@ + diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index fb5c3b92946..00f2a328af7 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -124,6 +124,7 @@ %Include qgslistwidget.sip %Include qgslegendfilterbutton.sip %Include qgslimitedrandomcolorrampdialog.sip +%Include qgsratiolockbutton.sip %Include qgslonglongvalidator.sip %Include qgsludialog.sip %Include qgsmanageconnectionsdialog.sip diff --git a/python/gui/qgsextentgroupbox.sip b/python/gui/qgsextentgroupbox.sip index 7f423674835..033aa793b0e 100644 --- a/python/gui/qgsextentgroupbox.sip +++ b/python/gui/qgsextentgroupbox.sip @@ -163,6 +163,24 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox .. versionadded:: 3.0 %End + void setRatio( QSize ratio ); +%Docstring + Sets a fixed aspect ratio to be used when dragging extent onto the canvas. + To unset a fixed aspect ratio, set the width and height to zero. + \param ratio aspect ratio's width and height +.. versionadded:: 3.0 + * +%End + + QSize ratio() const; +%Docstring + Returns the current fixed aspect ratio to be used when dragging extent onto the canvas. + If the aspect ratio isn't fixed, the width and height will be set to zero. +.. versionadded:: 3.0 + * + :rtype: QSize +%End + signals: void extentChanged( const QgsRectangle &r ); diff --git a/python/gui/qgsratiolockbutton.sip b/python/gui/qgsratiolockbutton.sip new file mode 100644 index 00000000000..22c1daa9054 --- /dev/null +++ b/python/gui/qgsratiolockbutton.sip @@ -0,0 +1,69 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsratiolockbutton.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsRatioLockButton : QToolButton +{ +%Docstring + A cross platform button subclass used to represent a locked / unlocked ratio state. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsratiolockbutton.h" +%End + public: + + QgsRatioLockButton( QWidget *parent /TransferThis/ = 0 ); +%Docstring + Construct a new ratio lock button. + Use ``parent`` to attach a parent QWidget to the button. +%End + + void setLocked( const bool locked ); +%Docstring + Sets whether the button state is locked. + \param locked locked state +.. seealso:: locked +%End + + bool locked() const; +%Docstring + Returns whether the button state is locked. + :return: true if the button state is locked. +.. seealso:: setLocked + :rtype: bool +%End + + signals: + + void lockChanged( const bool locked ); +%Docstring + Emitted whenever the lock state changes. +%End + + protected: + + virtual void changeEvent( QEvent *e ); + + virtual void showEvent( QShowEvent *e ); + + virtual void resizeEvent( QResizeEvent *event ); + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsratiolockbutton.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index c37359e9f1b..a0b5f390b22 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -81,6 +81,7 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QL connect( mOutputHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateOutputHeight ); connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsMapSaveDialog::updateExtent ); connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale ); + connect( mLockAspectRatio, &QgsRatioLockButton::lockChanged, this, &QgsMapSaveDialog::lockChanged ); updateOutputSize(); @@ -126,12 +127,25 @@ void QgsMapSaveDialog::updateOutputWidth( int width ) double scale = ( double )width / mSize.width(); double adjustment = ( ( mExtent.width() * scale ) - mExtent.width() ) / 2; + mSize.setWidth( width ); + mExtent.setXMinimum( mExtent.xMinimum() - adjustment ); mExtent.setXMaximum( mExtent.xMaximum() + adjustment ); - whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() ); + if ( mLockAspectRatio->locked() ) + { + int height = width * mExtentGroupBox->ratio().height() / mExtentGroupBox->ratio().width(); + double scale = ( double )height / mSize.height(); + double adjustment = ( ( mExtent.height() * scale ) - mExtent.height() ) / 2; - mSize.setWidth( width ); + whileBlocking( mOutputHeightSpinBox )->setValue( height ); + mSize.setHeight( height ); + + mExtent.setYMinimum( mExtent.yMinimum() - adjustment ); + mExtent.setYMaximum( mExtent.yMaximum() + adjustment ); + } + + whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() ); } void QgsMapSaveDialog::updateOutputHeight( int height ) @@ -139,12 +153,25 @@ void QgsMapSaveDialog::updateOutputHeight( int height ) double scale = ( double )height / mSize.height(); double adjustment = ( ( mExtent.height() * scale ) - mExtent.height() ) / 2; + mSize.setHeight( height ); + mExtent.setYMinimum( mExtent.yMinimum() - adjustment ); mExtent.setYMaximum( mExtent.yMaximum() + adjustment ); - whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() ); + if ( mLockAspectRatio->locked() ) + { + int width = height * mExtentGroupBox->ratio().width() / mExtentGroupBox->ratio().height(); + double scale = ( double )width / mSize.width(); + double adjustment = ( ( mExtent.width() * scale ) - mExtent.width() ) / 2; - mSize.setHeight( height ); + whileBlocking( mOutputWidthSpinBox )->setValue( height ); + mSize.setWidth( width ); + + mExtent.setXMinimum( mExtent.xMinimum() - adjustment ); + mExtent.setXMaximum( mExtent.xMaximum() + adjustment ); + } + + whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() ); } void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent ) @@ -153,6 +180,11 @@ void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent ) mSize.setHeight( mSize.height() * extent.height() / mExtent.height() ); mExtent = extent; + if ( mLockAspectRatio->locked() ) + { + mExtentGroupBox->setRatio( QSize( mSize.width(), mSize.height() ) ); + } + updateOutputSize(); } @@ -242,6 +274,18 @@ void QgsMapSaveDialog::applyMapSettings( QgsMapSettings &mapSettings ) mapSettings.setExpressionContext( expressionContext ); } +void QgsMapSaveDialog::lockChanged( const bool locked ) +{ + if ( locked ) + { + mExtentGroupBox->setRatio( QSize( mOutputWidthSpinBox->value(), mOutputHeightSpinBox->value() ) ); + } + else + { + mExtentGroupBox->setRatio( QSize( 0, 0 ) ); + } +} + void QgsMapSaveDialog::accepted() { if ( mDialogType == Image ) diff --git a/src/app/qgsmapsavedialog.h b/src/app/qgsmapsavedialog.h index b1291b3c2f1..e2518315b78 100644 --- a/src/app/qgsmapsavedialog.h +++ b/src/app/qgsmapsavedialog.h @@ -77,6 +77,7 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog private: + void lockChanged( const bool locked ); void accepted(); void updateDpi( int dpi ); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a12bc4cf889..3d4d4e4fd20 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -263,6 +263,7 @@ SET(QGIS_GUI_SRCS qgslistwidget.cpp qgslegendfilterbutton.cpp qgslimitedrandomcolorrampdialog.cpp + qgsratiolockbutton.cpp qgsludialog.cpp qgsmanageconnectionsdialog.cpp qgsmapcanvas.cpp @@ -421,6 +422,7 @@ SET(QGIS_GUI_MOC_HDRS qgslistwidget.h qgslegendfilterbutton.h qgslimitedrandomcolorrampdialog.h + qgsratiolockbutton.h qgslonglongvalidator.h qgsludialog.h qgsmanageconnectionsdialog.h diff --git a/src/gui/qgsextentgroupbox.cpp b/src/gui/qgsextentgroupbox.cpp index 55aff8f8f2b..8e3b1f80e48 100644 --- a/src/gui/qgsextentgroupbox.cpp +++ b/src/gui/qgsextentgroupbox.cpp @@ -277,6 +277,7 @@ void QgsExtentGroupBox::setOutputExtentFromDrawOnCanvas() mMapToolPrevious = nullptr; } ); } + mMapToolExtent->setRatio( mRatio ); mCanvas->setMapTool( mMapToolExtent.get() ); window()->setVisible( false ); } diff --git a/src/gui/qgsextentgroupbox.h b/src/gui/qgsextentgroupbox.h index e6d7d9fe0db..487b1a1f8bd 100644 --- a/src/gui/qgsextentgroupbox.h +++ b/src/gui/qgsextentgroupbox.h @@ -177,6 +177,19 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui:: */ void setOutputExtentFromDrawOnCanvas(); + /** Sets a fixed aspect ratio to be used when dragging extent onto the canvas. + * To unset a fixed aspect ratio, set the width and height to zero. + * \param ratio aspect ratio's width and height + * \since QGIS 3.0 + * */ + void setRatio( QSize ratio ) { mRatio = ratio; } + + /** Returns the current fixed aspect ratio to be used when dragging extent onto the canvas. + * If the aspect ratio isn't fixed, the width and height will be set to zero. + * \since QGIS 3.0 + * */ + QSize ratio() const { return mRatio; } + signals: /** @@ -223,6 +236,7 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui:: std::unique_ptr< QgsMapToolExtent > mMapToolExtent; QPointer< QgsMapTool > mMapToolPrevious = nullptr; QgsMapCanvas *mCanvas = nullptr; + QSize mRatio; void setExtentToLayerExtent( const QString &layerId ); diff --git a/src/gui/qgsratiolockbutton.cpp b/src/gui/qgsratiolockbutton.cpp new file mode 100644 index 00000000000..98f9b1a1431 --- /dev/null +++ b/src/gui/qgsratiolockbutton.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + qgsratiolockbutton.cpp - Lock button + -------------------------------------- + Date : July, 2017 + Copyright : (C) 2017 by Mathieu Pellerin + Email : nirvn dot asia 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 "qgsratiolockbutton.h" + +#include +#include +#include +#include +#include + +QgsRatioLockButton::QgsRatioLockButton( QWidget *parent ) + : QToolButton( parent ) + , mLocked( false ) + +{ + setMinimumSize( QSize( 24, 24 ) ); + setCheckable( true ); + setAutoRaise( true ); + connect( this, &QPushButton::clicked, this, &QgsRatioLockButton::buttonClicked ); +} + +void QgsRatioLockButton::setLocked( const bool locked ) +{ + if ( mLocked != locked ) + buttonClicked(); +} + +void QgsRatioLockButton::buttonClicked() +{ + mLocked = !mLocked; + setDown( mLocked ); + + emit lockChanged( mLocked ); + + drawButton(); +} + +void QgsRatioLockButton::changeEvent( QEvent *e ) +{ + if ( e->type() == QEvent::EnabledChange ) + { + drawButton(); + } + QToolButton::changeEvent( e ); +} + +void QgsRatioLockButton::showEvent( QShowEvent *e ) +{ + drawButton(); + QToolButton::showEvent( e ); +} + +void QgsRatioLockButton::resizeEvent( QResizeEvent *event ) +{ + QToolButton::resizeEvent( event ); + drawButton(); +} + +void QgsRatioLockButton::drawButton() +{ + QSize currentIconSize; + +#ifdef Q_OS_WIN + currentIconSize = QSize( width() - 10, height() - 6 ); +#else + currentIconSize = QSize( width() - 10, height() - 12 ); +#endif + + if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 ) + { + return; + } + + QPixmap pm; + pm = QPixmap( currentIconSize ); + pm.fill( Qt::transparent ); + + QPainter painter; + QPen pen = ( QColor( 136, 136, 136 ) ); + pen.setWidth( 2 ); + + painter.begin( &pm ); + painter.setPen( pen ); + + painter.drawLine( 1, 1, currentIconSize.width() / 2, 1 ); + painter.drawLine( currentIconSize.width() / 2, 1, currentIconSize.width() / 2, currentIconSize.height() / 2 - 13 ); + painter.drawLine( currentIconSize.width() / 2, currentIconSize.height() / 2 + 13, currentIconSize.width() / 2, currentIconSize.height() - 2 ); + painter.drawLine( currentIconSize.width() / 2, currentIconSize.height() - 2, 1, currentIconSize.height() - 2 ); + + QImage image( mLocked ? QStringLiteral( ":/images/themes/default/lockedGray.svg" ) : QStringLiteral( ":/images/themes/default/unlockedGray.svg" ) ); + painter.drawImage( QRectF( currentIconSize.width() / 2 - 8, currentIconSize.height() / 2 - 8, 16, 16 ), image, QRectF( 0, 0, 16, 16 ) ); + + painter.end(); + + setIconSize( currentIconSize ); + setIcon( pm ); +} diff --git a/src/gui/qgsratiolockbutton.h b/src/gui/qgsratiolockbutton.h new file mode 100644 index 00000000000..a49ca88a92d --- /dev/null +++ b/src/gui/qgsratiolockbutton.h @@ -0,0 +1,78 @@ +/*************************************************************************** + qgsratiolockbutton.cpp - Lock button + -------------------------------------- + Date : July, 2017 + Copyright : (C) 2017 by Mathieu Pellerin + Email : nirvn dot asia 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 QGSLOCKBUTTON_H +#define QGSLOCKBUTTON_H + +#include "qgsratiolockbutton.h" + +#include +#include "qgis_gui.h" +#include "qgis.h" + +/** \ingroup gui + * \class QgsRatioLockButton + * A cross platform button subclass used to represent a locked / unlocked ratio state. + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsRatioLockButton : public QToolButton +{ + Q_OBJECT + Q_PROPERTY( bool locked READ locked WRITE setLocked ) + + public: + + /** Construct a new ratio lock button. + * Use \a parent to attach a parent QWidget to the button. + */ + QgsRatioLockButton( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** Sets whether the button state is locked. + * \param locked locked state + * \see locked + */ + void setLocked( const bool locked ); + + /** Returns whether the button state is locked. + * \returns true if the button state is locked. + * \see setLocked + */ + bool locked() const { return mLocked; } + + signals: + + /** Emitted whenever the lock state changes. + */ + void lockChanged( const bool locked ); + + protected: + + void changeEvent( QEvent *e ) override; + void showEvent( QShowEvent *e ) override; + void resizeEvent( QResizeEvent *event ) override; + + private: + + void drawButton(); + + bool mLocked = false; + + private slots: + + void buttonClicked(); + +}; + +#endif diff --git a/src/ui/qgsmapsavedialog.ui b/src/ui/qgsmapsavedialog.ui index e8d09f99b22..bd0bc3adcc3 100644 --- a/src/ui/qgsmapsavedialog.ui +++ b/src/ui/qgsmapsavedialog.ui @@ -77,25 +77,6 @@ Rasterizing the map is recommended when such effects are used. - - - - px - - - - - - 1 - - - 99999 - - - false - - - @@ -103,24 +84,85 @@ Rasterizing the map is recommended when such effects are used. - - - - px + + + + 0 - - + + 6 - - 1 - - - 99999 - - - false - - + + + + px + + + + + + 1 + + + 99999 + + + false + + + + + + + 2 + + + 2 + + + 0 + + + 2 + + + + + 13 + + + + 0 + 0 + + + + Lock aspect ratio (including while drawing extent onto canvas) + + + + + + + + + px + + + + + + 1 + + + 99999 + + + false + + + + @@ -206,6 +248,12 @@ Rasterizing the map is recommended when such effects are used.
qgsextentgroupbox.h
1 + + QgsRatioLockButton + QToolButton +
qgsratiolockbutton.h
+ 1 +
QgsSpinBox QSpinBox From c29fc35155b35edbe4d432b8b83d41232e5c5e17 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Wed, 19 Jul 2017 10:34:46 +0200 Subject: [PATCH 081/266] also "s/( )/()/;" in sipify.pl (followup 4f9a9e036) --- scripts/sipify.pl | 1 + tests/code_layout/sipifyheader.expected.sip | 4 ++-- tests/code_layout/sipifyheader.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/sipify.pl b/scripts/sipify.pl index 19722709182..3bdea4ee9ed 100755 --- a/scripts/sipify.pl +++ b/scripts/sipify.pl @@ -253,6 +253,7 @@ sub fix_annotations { } # see https://regex101.com/r/5iNptO/4 $line =~ s/(?, +)?(const )?(\w+)(\<(?>[^<>]|(?4))*\>)?\s+[\w&*]+\s+SIP_PYARGREMOVE( = [^()]*(\(\s*(?:[^()]++|(?6))*\s*\))?)?(?()|,?)//g; + $line =~ s/\(\s+\)/()/; } $line =~ s/SIP_FORCE//; return $line; diff --git a/tests/code_layout/sipifyheader.expected.sip b/tests/code_layout/sipifyheader.expected.sip index 542ef882676..d94cf621727 100644 --- a/tests/code_layout/sipifyheader.expected.sip +++ b/tests/code_layout/sipifyheader.expected.sip @@ -304,7 +304,7 @@ Mulitline body void combinedAnnotations() /Factory,PyName=otherName/; void multiAnnotationArg( SomeClass **object /Out,TransferBack/, int &another /Out/ ); - void simple( ); + void simple(); %Docstring remove argument %End @@ -312,7 +312,7 @@ remove argument void test( ); void avoidIntersections( const QList &avoidIntersectionsLayers ); - void position( ); + void position(); void position( bool keep ); void position( bool keep, bool keep ); void position( bool keep ); diff --git a/tests/code_layout/sipifyheader.h b/tests/code_layout/sipifyheader.h index 278ecfb5f0e..3a7f19f715a 100644 --- a/tests/code_layout/sipifyheader.h +++ b/tests/code_layout/sipifyheader.h @@ -333,7 +333,7 @@ class CORE_EXPORT QgsSipifyHeader : public QtClass, private Ui::QgsBas //! remove argument void simple( bool test SIP_PYARGREMOVE ); - void method( bool myArg SIP_PYARGREMOVE = test ); + void method( bool myArg SIP_PYARGREMOVE = test ); void test( QgsMapLayer *vl SIP_PYARGREMOVE = nullptr ); void avoidIntersections( const QList &avoidIntersectionsLayers, const QHash > &ignoreFeatures SIP_PYARGREMOVE = ( QHash >() ) ); From ff0df6d9572b48a3b96d2fb1f50b18b7c0a74c84 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Wed, 19 Jul 2017 10:37:28 +0200 Subject: [PATCH 082/266] fix c29fc35155 --- tests/code_layout/sipifyheader.expected.sip | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/code_layout/sipifyheader.expected.sip b/tests/code_layout/sipifyheader.expected.sip index d94cf621727..fb3574720b7 100644 --- a/tests/code_layout/sipifyheader.expected.sip +++ b/tests/code_layout/sipifyheader.expected.sip @@ -308,8 +308,8 @@ Mulitline body %Docstring remove argument %End - void method( ); - void test( ); + void method(); + void test(); void avoidIntersections( const QList &avoidIntersectionsLayers ); void position(); From 2a572be73efc27697ea65a44f1bf4a39ce9df7f2 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Wed, 19 Jul 2017 10:07:13 +0200 Subject: [PATCH 083/266] Add test for #16833 Tested to pass with Python 2.7.13 --- .../db_manager/db_plugins/postgis/plugin_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/python/plugins/db_manager/db_plugins/postgis/plugin_test.py b/python/plugins/db_manager/db_plugins/postgis/plugin_test.py index 1d869eb7e84..f5d8380b2bf 100644 --- a/python/plugins/db_manager/db_plugins/postgis/plugin_test.py +++ b/python/plugins/db_manager/db_plugins/postgis/plugin_test.py @@ -35,6 +35,7 @@ start_app() from db_manager.db_plugins.postgis.plugin import PostGisDBPlugin, PGRasterTable from db_manager.db_plugins.postgis.plugin import PGDatabase +from db_manager.db_plugins.postgis.data_model import PGSqlResultModel from db_manager.db_plugins.plugin import Table from db_manager.db_plugins.postgis.connector import PostGisDBConnector @@ -125,6 +126,18 @@ class TestDBManagerPostgisPlugin(unittest.TestCase): check_rasterTableGdalURI(expected_dbname) + # See http://issues.qgis.org/issues/16833 + def test_unicodeInQuery(self): + os.environ['PGDATABASE'] = self.testdb + obj = QObject() # needs to be kept alive + database = PGDatabase(obj, QgsDataSourceUri()) + self.assertIsInstance(database, PGDatabase) + sql = "SELECT 'é'::text" + res = database.sqlResultModel(sql, obj) + self.assertIsInstance(res, PGSqlResultModel) + dat = res.getData(0, 0) + self.assertEqual(dat, "é") + if __name__ == '__main__': unittest.main() From 5b186aea7d585678e0b61ff96f3f2d55a1342096 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Wed, 19 Jul 2017 17:12:06 +0200 Subject: [PATCH 084/266] [travis] Force precise --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a103a8e5768..3ae53a7b701 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ matrix: env: - LLVM_VERSION=3.8 - TRAVIS_CONFIG=linux + dist: precise sudo: false cache: apt: true From 90d4768a06ded5477b50dabc71438e9452f15a32 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Wed, 19 Jul 2017 18:50:29 +0200 Subject: [PATCH 085/266] Fix addWfsLayer in the WFS GUI test (blacklisted on Travis) This test segfaults locally. --- tests/src/python/test_provider_wfs_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/python/test_provider_wfs_gui.py b/tests/src/python/test_provider_wfs_gui.py index 226c4e5e6df..a59a07ba1a5 100644 --- a/tests/src/python/test_provider_wfs_gui.py +++ b/tests/src/python/test_provider_wfs_gui.py @@ -217,7 +217,7 @@ class TestPyQgsWFSProviderGUI(unittest.TestCase): # Add layer self.addWfsLayer_uri = None self.addWfsLayer_layer_name = None - main_dialog.addWfsLayer.connect(self.slotAddWfsLayer) + main_dialog.addVectorLayer.connect(self.slotAddWfsLayer) QTest.mouseClick(buttonAdd, Qt.LeftButton) self.assertEqual(self.addWfsLayer_uri, ' restrictToRequestBBOX=\'1\' srsname=\'EPSG:4326\' typename=\'my:typename\' url=\'' + "http://" + expected_endpoint + '\' version=\'auto\' table="" sql=') self.assertEqual(self.addWfsLayer_layer_name, 'my:typename') @@ -290,7 +290,7 @@ class TestPyQgsWFSProviderGUI(unittest.TestCase): self.addWfsLayer_uri = None self.addWfsLayer_layer_name = None - main_dialog.addWfsLayer.connect(self.slotAddWfsLayer) + main_dialog.addVectorLayer.connect(self.slotAddWfsLayer) QTest.mouseClick(buttonAdd, Qt.LeftButton) self.assertEqual(self.addWfsLayer_uri, ' restrictToRequestBBOX=\'1\' srsname=\'EPSG:4326\' typename=\'my:typename\' url=\'' + "http://" + expected_endpoint + '\' version=\'auto\' table="" sql=SELECT * FROM typename WHERE 1 = 1') self.assertEqual(self.addWfsLayer_layer_name, 'my:typename') From 9efd666e482c47f8139789fbc702f4a5e91ff178 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Wed, 19 Jul 2017 18:12:22 +0200 Subject: [PATCH 086/266] Test that PostGIS query can be passed as both unicode and string literal See https://issues.qgis.org/issues/16833 --- .../db_manager/db_plugins/postgis/plugin_test.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/plugins/db_manager/db_plugins/postgis/plugin_test.py b/python/plugins/db_manager/db_plugins/postgis/plugin_test.py index f5d8380b2bf..c8ac8dd1885 100644 --- a/python/plugins/db_manager/db_plugins/postgis/plugin_test.py +++ b/python/plugins/db_manager/db_plugins/postgis/plugin_test.py @@ -132,11 +132,16 @@ class TestDBManagerPostgisPlugin(unittest.TestCase): obj = QObject() # needs to be kept alive database = PGDatabase(obj, QgsDataSourceUri()) self.assertIsInstance(database, PGDatabase) - sql = "SELECT 'é'::text" - res = database.sqlResultModel(sql, obj) + # SQL as string literal + res = database.sqlResultModel("SELECT 'é'::text", obj) self.assertIsInstance(res, PGSqlResultModel) dat = res.getData(0, 0) - self.assertEqual(dat, "é") + self.assertEqual(dat, u"é") + # SQL as unicode literal + res = database.sqlResultModel(u"SELECT 'é'::text", obj) + self.assertIsInstance(res, PGSqlResultModel) + dat = res.getData(0, 0) + self.assertEqual(dat, u"é") if __name__ == '__main__': From 6e49403163cb30517f97af55115003e693dd6462 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 10:13:54 +1000 Subject: [PATCH 087/266] Fix QgsFieldComboBox::setField sets incorrect field when filter is present --- src/gui/qgsfieldcombobox.cpp | 2 +- tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_qgsfieldcombobox.py | 65 +++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/src/python/test_qgsfieldcombobox.py diff --git a/src/gui/qgsfieldcombobox.cpp b/src/gui/qgsfieldcombobox.cpp index f2d4b679d70..0663eb9d587 100644 --- a/src/gui/qgsfieldcombobox.cpp +++ b/src/gui/qgsfieldcombobox.cpp @@ -62,7 +62,7 @@ void QgsFieldComboBox::setField( const QString &fieldName ) QModelIndex proxyIdx = mFieldProxyModel->mapFromSource( idx ); if ( proxyIdx.isValid() ) { - setCurrentIndex( idx.row() ); + setCurrentIndex( proxyIdx.row() ); emit fieldChanged( currentField() ); return; } diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index df4ab669b36..52602906698 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -52,6 +52,7 @@ ADD_PYTHON_TEST(PyQgsExtentGroupBox test_qgsextentgroupbox.py) ADD_PYTHON_TEST(PyQgsFeature test_qgsfeature.py) ADD_PYTHON_TEST(PyQgsFeatureSink test_qgsfeaturesink.py) ADD_PYTHON_TEST(PyQgsFeatureSource test_qgsfeaturesource.py) +ADD_PYTHON_TEST(PyQgsFieldComboBoxTest test_qgsfieldcombobox.py) ADD_PYTHON_TEST(PyQgsFieldFormattersTest test_qgsfieldformatters.py) ADD_PYTHON_TEST(PyQgsFillSymbolLayers test_qgsfillsymbollayers.py) ADD_PYTHON_TEST(PyQgsProject test_qgsproject.py) diff --git a/tests/src/python/test_qgsfieldcombobox.py b/tests/src/python/test_qgsfieldcombobox.py new file mode 100644 index 00000000000..1e506115d8d --- /dev/null +++ b/tests/src/python/test_qgsfieldcombobox.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsFieldComboBox + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '20/07/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.core import QgsFields, QgsVectorLayer, QgsFieldProxyModel +from qgis.gui import QgsFieldComboBox +from qgis.PyQt.QtCore import QVariant, Qt + +from qgis.testing import start_app, unittest + +start_app() + + +def create_layer(): + layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=fldint2:integer", + "addfeat", "memory") + assert layer.isValid() + return layer + + +def create_model(): + l = create_layer() + m = QgsFieldModel() + m.setLayer(l) + return l, m + + +class TestQgsFieldComboBox(unittest.TestCase): + + def testGettersSetters(self): + """ test combobox getters/setters """ + l = create_layer() + w = QgsFieldComboBox() + w.setLayer(l) + self.assertEqual(w.layer(), l) + + w.setField('fldint') + self.assertEqual(w.currentField(), 'fldint') + + def testFilter(self): + """ test setting field with filter """ + l = create_layer() + w = QgsFieldComboBox() + w.setLayer(l) + w.setFilters(QgsFieldProxyModel.Int) + self.assertEqual(w.layer(), l) + + w.setField('fldint') + self.assertEqual(w.currentField(), 'fldint') + + +if __name__ == '__main__': + unittest.main() From a231f1625a6cd36b60e7590e2465500e84887f0e Mon Sep 17 00:00:00 2001 From: gacarrillor Date: Wed, 19 Jul 2017 23:07:00 -0500 Subject: [PATCH 088/266] Close Data Source Manager if child widget is closed --- src/gui/qgsdatasourcemanagerdialog.cpp | 8 ++++++++ src/gui/qgsdatasourcemanagerdialog.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index db96e186bb6..c596b257329 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -60,6 +60,7 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QWidget *parent, QgsMapC ogrItem->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddOgrLayer.svg" ) ) ); ogrItem->setToolTip( tr( "Add Vector layer" ) ); connect( ovl, &QgsOpenVectorLayerDialog::addVectorLayers, this, &QgsDataSourceManagerDialog::vectorLayersAdded ); + connect( ovl, &QgsOpenVectorLayerDialog::rejected, this, &QgsDataSourceManagerDialog::reject ); mPageNames.append( QStringLiteral( "ogr" ) ); // RASTER (forward to app) @@ -187,6 +188,7 @@ QgsAbstractDataSourceWidget *QgsDataSourceManagerDialog::providerDialog( const Q { dlg->setMapCanvas( mMapCanvas ); } + connect( dlg, &QgsAbstractDataSourceWidget::rejected, this, &QgsDataSourceManagerDialog::reject ); return dlg; } } @@ -229,3 +231,9 @@ void QgsDataSourceManagerDialog::addVectorProviderDialog( const QString provider connect( this, SIGNAL( providerDialogsRefreshRequested() ), dlg, SLOT( refresh() ) ); } } + +void QgsDataSourceManagerDialog::showEvent( QShowEvent *e ) +{ + ui->mOptionsStackedWidget->currentWidget()->show(); + QDialog::showEvent( e ); +} diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index b81835648f7..5543b0fd87a 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -78,6 +78,9 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva //! Refresh the browser view void refresh(); + protected: + virtual void showEvent( QShowEvent *event ) override; + signals: //! Emitted when a raster layer was selected for addition: for signal forwarding to QgisApp void addRasterLayer( const QString &uri, const QString &baseName, const QString &providerKey ); From 63a2b741f28a7dd0a2ebb14d815402513ed5f2f4 Mon Sep 17 00:00:00 2001 From: gacarrillor Date: Wed, 19 Jul 2017 23:54:37 -0500 Subject: [PATCH 089/266] Fix wrong indentation detected by Travis --- src/gui/qgsdatasourcemanagerdialog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index 5543b0fd87a..9e997048e63 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -79,7 +79,7 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva void refresh(); protected: - virtual void showEvent( QShowEvent *event ) override; + virtual void showEvent( QShowEvent *event ) override; signals: //! Emitted when a raster layer was selected for addition: for signal forwarding to QgisApp From d4af76150f9db96bf75d13ee10fadbe4076da230 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 13:06:18 +1000 Subject: [PATCH 090/266] Flip some more algorithms to feature based algorithms --- .../plugins/processing/algs/qgis/Boundary.py | 56 +++++--------- .../processing/algs/qgis/DeleteColumn.py | 70 +++++++----------- .../processing/algs/qgis/DeleteHoles.py | 50 ++++--------- .../processing/algs/qgis/DensifyGeometries.py | 50 ++++--------- .../algs/qgis/DensifyGeometriesInterval.py | 50 ++++--------- .../processing/algs/qgis/DropGeometry.py | 45 ++++-------- .../processing/algs/qgis/LinesToPolygons.py | 56 ++++---------- .../processing/algs/qgis/OffsetLine.py | 64 +++++++--------- .../processing/algs/qgis/Orthogonalize.py | 67 ++++++----------- .../processing/algs/qgis/PointOnSurface.py | 56 +++++--------- .../processing/algs/qgis/PolygonsToLines.py | 62 ++++------------ .../algs/qgis/ReverseLineDirection.py | 56 +++++--------- .../algs/qgis/SimplifyGeometries.py | 73 ++++++------------- python/plugins/processing/algs/qgis/Smooth.py | 58 +++++---------- .../tests/testdata/qgis_algorithm_tests.yaml | 28 +++---- 15 files changed, 277 insertions(+), 564 deletions(-) diff --git a/python/plugins/processing/algs/qgis/Boundary.py b/python/plugins/processing/algs/qgis/Boundary.py index b3b0c15ba4d..558d71276e8 100644 --- a/python/plugins/processing/algs/qgis/Boundary.py +++ b/python/plugins/processing/algs/qgis/Boundary.py @@ -28,31 +28,20 @@ __revision__ = '$Format:%H$' import os from qgis.core import (QgsGeometry, - QgsWkbTypes, - QgsFeatureSink, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink) + QgsWkbTypes) from qgis.PyQt.QtGui import QIcon -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class Boundary(QgisAlgorithm): - - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' +class Boundary(QgisFeatureBasedAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.tr('Input layer'), [QgsProcessing.TypeVectorLine, QgsProcessing.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Boundary'))) - def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'convex_hull.png')) @@ -65,10 +54,10 @@ class Boundary(QgisAlgorithm): def displayName(self): return self.tr('Boundary') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def outputName(self): + return self.tr('Boundary') - input_wkb = source.wkbType() + def outputWkbType(self, input_wkb): if QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.LineGeometry: output_wkb = QgsWkbTypes.MultiPoint elif QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.PolygonGeometry: @@ -78,26 +67,15 @@ class Boundary(QgisAlgorithm): if QgsWkbTypes.hasM(input_wkb): output_wkb = QgsWkbTypes.addM(output_wkb) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - source.fields(), output_wkb, source.sourceCrs()) + return output_wkb - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = QgsGeometry(input_geometry.geometry().boundary()) - if not output_geometry: - feedback.reportError(self.tr('No boundary for feature {} (possibly a closed linestring?)').format(input_feature.id())) - output_feature.clearGeometry() - else: - output_feature.setGeometry(output_geometry) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT_LAYER: dest_id} + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = QgsGeometry(input_geometry.geometry().boundary()) + if not output_geometry: + feedback.reportError(self.tr('No boundary for feature {} (possibly a closed linestring?)').format(feature.id())) + feature.clearGeometry() + else: + feature.setGeometry(output_geometry) + return feature diff --git a/python/plugins/processing/algs/qgis/DeleteColumn.py b/python/plugins/processing/algs/qgis/DeleteColumn.py index 52dbd90eba8..2a2c8c34247 100644 --- a/python/plugins/processing/algs/qgis/DeleteColumn.py +++ b/python/plugins/processing/algs/qgis/DeleteColumn.py @@ -25,18 +25,13 @@ __copyright__ = '(C) 2010, Michael Minn' __revision__ = '$Format:%H$' -from qgis.core import (QgsFeatureSink, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink, - QgsProcessingParameterField) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from qgis.core import (QgsProcessingParameterField) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class DeleteColumn(QgisAlgorithm): +class DeleteColumn(QgisFeatureBasedAlgorithm): - INPUT = 'INPUT' COLUMNS = 'COLUMN' - OUTPUT = 'OUTPUT' def tags(self): return self.tr('drop,delete,remove,fields,columns,attributes').split(',') @@ -46,14 +41,13 @@ class DeleteColumn(QgisAlgorithm): def __init__(self): super().__init__() + self.fields_to_delete = [] + self.field_indices = [] - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterField(self.COLUMNS, self.tr('Fields to drop'), - None, self.INPUT, QgsProcessingParameterField.Any, True)) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Output layer'))) + None, 'INPUT', QgsProcessingParameterField.Any, True)) def name(self): return 'deletecolumn' @@ -61,40 +55,30 @@ class DeleteColumn(QgisAlgorithm): def displayName(self): return self.tr('Drop field(s)') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - fields_to_delete = self.parameterAsFields(parameters, self.COLUMNS, context) + def outputName(self): + return self.tr('Fields dropped') - fields = source.fields() - field_indices = [] + def prepareAlgorithm(self, parameters, context, feedback): + self.fields_to_delete = self.parameterAsFields(parameters, self.COLUMNS, context) + return True + + def outputFields(self, input_fields): # loop through twice - first we need to build up a list of original attribute indices - for f in fields_to_delete: - index = fields.lookupField(f) - field_indices.append(index) + for f in self.fields_to_delete: + index = input_fields.lookupField(f) + self.field_indices.append(index) # important - make sure we remove from the end so we aren't changing used indices as we go - field_indices.sort(reverse=True) + self.field_indices.sort(reverse=True) # this second time we make a cleaned version of the fields - for index in field_indices: - fields.remove(index) + for index in self.field_indices: + input_fields.remove(index) + return input_fields - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, source.wkbType(), source.sourceCrs()) - - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - - for current, f in enumerate(features): - if feedback.isCanceled(): - break - - attributes = f.attributes() - for index in field_indices: - del attributes[index] - f.setAttributes(attributes) - sink.addFeature(f, QgsFeatureSink.FastInsert) - - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + attributes = feature.attributes() + for index in self.field_indices: + del attributes[index] + feature.setAttributes(attributes) + return feature diff --git a/python/plugins/processing/algs/qgis/DeleteHoles.py b/python/plugins/processing/algs/qgis/DeleteHoles.py index 735f79b0db4..2e52a69d793 100644 --- a/python/plugins/processing/algs/qgis/DeleteHoles.py +++ b/python/plugins/processing/algs/qgis/DeleteHoles.py @@ -24,32 +24,23 @@ __copyright__ = '(C) 2015, Etienne Trimaille' __revision__ = '$Format:%H$' -from qgis.core import (QgsFeatureSink, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterNumber, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from qgis.core import (QgsProcessingParameterNumber) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class DeleteHoles(QgisAlgorithm): +class DeleteHoles(QgisFeatureBasedAlgorithm): - INPUT = 'INPUT' MIN_AREA = 'MIN_AREA' - OUTPUT = 'OUTPUT' def __init__(self): super().__init__() + self.min_area = None - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), [QgsProcessing.TypeVectorPolygon])) + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterNumber(self.MIN_AREA, self.tr('Remove holes with area less than'), QgsProcessingParameterNumber.Double, 0, True, 0.0, 10000000.0)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Cleaned'), QgsProcessing.TypeVectorPolygon)) - def tags(self): return self.tr('remove,delete,drop,holes,rings,fill').split(',') @@ -62,25 +53,16 @@ class DeleteHoles(QgisAlgorithm): def displayName(self): return self.tr('Delete holes') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - min_area = self.parameterAsDouble(parameters, self.MIN_AREA, context) - if min_area == 0.0: - min_area = -1.0 + def outputName(self): + return self.tr('Cleaned') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + def prepareAlgorithm(self, parameters, context, feedback): + self.min_area = self.parameterAsDouble(parameters, self.MIN_AREA, context) + if self.min_area == 0.0: + self.min_area = -1.0 + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - - for current, f in enumerate(features): - if feedback.isCanceled(): - break - - if f.hasGeometry(): - f.setGeometry(f.geometry().removeInteriorRings(min_area)) - sink.addFeature(f, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + if feature.hasGeometry(): + feature.setGeometry(feature.geometry().removeInteriorRings(self.min_area)) + return feature diff --git a/python/plugins/processing/algs/qgis/DensifyGeometries.py b/python/plugins/processing/algs/qgis/DensifyGeometries.py index f5a0a039094..87885473c59 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometries.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometries.py @@ -28,19 +28,14 @@ __revision__ = '$Format:%H$' import os -from qgis.core import (QgsFeatureSink, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterNumber, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from qgis.core import (QgsProcessingParameterNumber) + +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class DensifyGeometries(QgisAlgorithm): +class DensifyGeometries(QgisFeatureBasedAlgorithm): - INPUT = 'INPUT' VERTICES = 'VERTICES' - OUTPUT = 'OUTPUT' def tags(self): return self.tr('add,vertices,points').split(',') @@ -50,41 +45,28 @@ class DensifyGeometries(QgisAlgorithm): def __init__(self): super().__init__() + self.vertices = None - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), [QgsProcessing.TypeVectorPolygon, QgsProcessing.TypeVectorLine])) + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterNumber(self.VERTICES, self.tr('Vertices to add'), QgsProcessingParameterNumber.Integer, 1, False, 1, 10000000)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) - def name(self): return 'densifygeometries' def displayName(self): return self.tr('Densify geometries') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - vertices = self.parameterAsInt(parameters, self.VERTICES, context) + def outputName(self): + return self.tr('Densified') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + def prepareAlgorithm(self, parameters, context, feedback): + self.vertices = self.parameterAsInt(parameters, self.VERTICES, context) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - - for current, f in enumerate(features): - if feedback.isCanceled(): - break - - feature = f - if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByCount(vertices) - feature.setGeometry(new_geometry) - sink.addFeature(feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + if feature.hasGeometry(): + new_geometry = feature.geometry().densifyByCount(self.vertices) + feature.setGeometry(new_geometry) + return feature diff --git a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py index 9ac668e4035..20b62df4ecb 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py @@ -27,60 +27,42 @@ __copyright__ = '(C) 2012, Anita Graser' __revision__ = '$Format:%H$' -from qgis.core import (QgsFeatureSink, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterNumber, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from qgis.core import (QgsProcessingParameterNumber) + +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class DensifyGeometriesInterval(QgisAlgorithm): +class DensifyGeometriesInterval(QgisFeatureBasedAlgorithm): - INPUT = 'INPUT' INTERVAL = 'INTERVAL' - OUTPUT = 'OUTPUT' def group(self): return self.tr('Vector geometry tools') def __init__(self): super().__init__() + self.interval = None - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), [QgsProcessing.TypeVectorPolygon, QgsProcessing.TypeVectorLine])) + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterNumber(self.INTERVAL, self.tr('Interval between vertices to add'), QgsProcessingParameterNumber.Double, 1, False, 0, 10000000)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) - def name(self): return 'densifygeometriesgivenaninterval' def displayName(self): return self.tr('Densify geometries given an interval') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) + def outputName(self): + return self.tr('Densified') + + def prepareAlgorithm(self, parameters, context, feedback): interval = self.parameterAsDouble(parameters, self.INTERVAL, context) + return True - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) - - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - for current, f in enumerate(features): - if feedback.isCanceled(): - break - - feature = f - if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByDistance(float(interval)) - feature.setGeometry(new_geometry) - sink.addFeature(feature, QgsFeatureSink.FastInsert) - - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + if feature.hasGeometry(): + new_geometry = feature.geometry().densifyByDistance(float(interval)) + feature.setGeometry(new_geometry) + return feature diff --git a/python/plugins/processing/algs/qgis/DropGeometry.py b/python/plugins/processing/algs/qgis/DropGeometry.py index 11597e3a38d..57f2e9048be 100644 --- a/python/plugins/processing/algs/qgis/DropGeometry.py +++ b/python/plugins/processing/algs/qgis/DropGeometry.py @@ -25,20 +25,13 @@ __copyright__ = '(C) 2016, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import (QgsFeatureRequest, - QgsWkbTypes, - QgsFeatureSink, - QgsCoordinateReferenceSystem, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from qgis.core import (QgsWkbTypes, + QgsCoordinateReferenceSystem) + +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class DropGeometry(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' +class DropGeometry(QgisFeatureBasedAlgorithm): def tags(self): return self.tr('remove,drop,delete,geometry,objects').split(',') @@ -49,31 +42,21 @@ class DropGeometry(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), [QgsProcessing.TypeVectorPoint, QgsProcessing.TypeVectorLine, QgsProcessing.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Dropped geometry'))) - def name(self): return 'dropgeometries' def displayName(self): return self.tr('Drop geometries') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) + def outputName(self): + return self.tr('Dropped geometries') - request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) - features = source.getFeatures(request) - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def outputCrs(self, input_crs): + return QgsCoordinateReferenceSystem() - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break + def outputWkbType(self, input_wkb_type): + return QgsWkbTypes.NoGeometry - input_feature.clearGeometry() - sink.addFeature(input_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + feature.clearGeometry() + return feature diff --git a/python/plugins/processing/algs/qgis/LinesToPolygons.py b/python/plugins/processing/algs/qgis/LinesToPolygons.py index e6bc8b781a7..8ac1abf9c39 100644 --- a/python/plugins/processing/algs/qgis/LinesToPolygons.py +++ b/python/plugins/processing/algs/qgis/LinesToPolygons.py @@ -42,16 +42,13 @@ from qgis.core import (QgsFeature, QgsProcessingParameterFeatureSink, QgsProcessingUtils) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm from processing.tools import dataobjects, vector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class LinesToPolygons(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' +class LinesToPolygons(QgisFeatureBasedAlgorithm): def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'to_lines.png')) @@ -65,52 +62,27 @@ class LinesToPolygons(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), - [QgsProcessing.TypeVectorLine])) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, - self.tr('Lines to polygons'), - QgsProcessing.TypeVectorPolygon)) - def name(self): return 'linestopolygons' def displayName(self): return self.tr('Lines to polygons') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) + def outputName(self): + return self.tr('Polygons') - geomType = self.convertWkbToPolygons(source.wkbType()) + def outputType(self): + return QgsProcessing.TypeVectorPolygon - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), geomType, source.sourceCrs()) + def outputWkbType(self, input_wkb_type): + return self.convertWkbToPolygons(input_wkb_type) - outFeat = QgsFeature() - - total = 100.0 / source.featureCount() if source.featureCount() else 0 - count = 0 - - for feat in source.getFeatures(): - if feedback.isCanceled(): - break - - if feat.hasGeometry(): - outFeat.setGeometry(QgsGeometry(self.convertToPolygons(feat.geometry()))) - attrs = feat.attributes() - outFeat.setAttributes(attrs) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - if outFeat.geometry().isEmpty(): - feedback.reportError(self.tr("One or more line ignored due to geometry not having a minimum of three vertices.")) - else: - sink.addFeature(feat, QgsFeatureSink.FastInsert) - - count += 1 - feedback.setProgress(int(count * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + if feature.hasGeometry(): + feature.setGeometry(QgsGeometry(self.convertToPolygons(feature.geometry()))) + if feature.geometry().isEmpty(): + feedback.reportError(self.tr("One or more line ignored due to geometry not having a minimum of three vertices.")) + return feature def convertWkbToPolygons(self, wkb): multi_wkb = None diff --git a/python/plugins/processing/algs/qgis/OffsetLine.py b/python/plugins/processing/algs/qgis/OffsetLine.py index 64ed774907b..66c2c7f4c77 100644 --- a/python/plugins/processing/algs/qgis/OffsetLine.py +++ b/python/plugins/processing/algs/qgis/OffsetLine.py @@ -35,14 +35,13 @@ from qgis.core import (QgsFeatureSink, QgsProcessingParameterEnum, QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class OffsetLine(QgisAlgorithm): - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' +class OffsetLine(QgisFeatureBasedAlgorithm): + DISTANCE = 'DISTANCE' SEGMENTS = 'SEGMENTS' JOIN_STYLE = 'JOIN_STYLE' @@ -54,9 +53,12 @@ class OffsetLine(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), - [QgsProcessing.TypeVectorLine])) + self.distance = None + self.segments = None + self.join_style = None + self.miter_limit = None + + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterNumber(self.DISTANCE, self.tr('Distance'), type=QgsProcessingParameterNumber.Double, @@ -76,43 +78,33 @@ class OffsetLine(QgisAlgorithm): self.tr('Mitre limit'), type=QgsProcessingParameterNumber.Double, minValue=1, defaultValue=2)) - self.addParameter( - QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Offset'), QgsProcessing.TypeVectorLine)) - def name(self): return 'offsetline' def displayName(self): return self.tr('Offset line') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + def outputName(self): + return self.tr('Offset') - distance = self.parameterAsDouble(parameters, self.DISTANCE, context) - segments = self.parameterAsInt(parameters, self.SEGMENTS, context) - join_style = self.parameterAsEnum(parameters, self.JOIN_STYLE, context) + 1 - miter_limit = self.parameterAsDouble(parameters, self.MITRE_LIMIT, context) + def outputType(self): + return QgsProcessing.TypeVectorLine - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def prepareAlgorithm(self, parameters, context, feedback): + self.distance = self.parameterAsDouble(parameters, self.DISTANCE, context) + self.segments = self.parameterAsInt(parameters, self.SEGMENTS, context) + self.join_style = self.parameterAsEnum(parameters, self.JOIN_STYLE, context) + 1 + self.miter_limit = self.parameterAsDouble(parameters, self.MITRE_LIMIT, context) + return True - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = input_geometry.offsetCurve(self.distance, self.segments, self.join_style, self.miter_limit) + if not output_geometry: + raise QgsProcessingException( + self.tr('Error calculating line offset')) - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = input_geometry.offsetCurve(distance, segments, join_style, miter_limit) - if not output_geometry: - raise QgsProcessingException( - self.tr('Error calculating line offset')) + feature.setGeometry(output_geometry) - output_feature.setGeometry(output_geometry) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + return feature diff --git a/python/plugins/processing/algs/qgis/Orthogonalize.py b/python/plugins/processing/algs/qgis/Orthogonalize.py index c5d6759b77c..00038a52050 100644 --- a/python/plugins/processing/algs/qgis/Orthogonalize.py +++ b/python/plugins/processing/algs/qgis/Orthogonalize.py @@ -25,19 +25,14 @@ __copyright__ = '(C) 2016, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import (QgsFeatureSink, - QgsProcessingException, - QgsProcessing, +from qgis.core import (QgsProcessingException, QgsProcessingParameterDefinition, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterNumber, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm + QgsProcessingParameterNumber) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class Orthogonalize(QgisAlgorithm): - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' +class Orthogonalize(QgisFeatureBasedAlgorithm): + MAX_ITERATIONS = 'MAX_ITERATIONS' DISTANCE_THRESHOLD = 'DISTANCE_THRESHOLD' ANGLE_TOLERANCE = 'ANGLE_TOLERANCE' @@ -50,12 +45,10 @@ class Orthogonalize(QgisAlgorithm): def __init__(self): super().__init__() + self.max_iterations = None + self.angle_tolerance = None - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), - [QgsProcessing.TypeVectorLine, - QgsProcessing.TypeVectorPolygon])) - + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterNumber(self.ANGLE_TOLERANCE, self.tr('Maximum angle tolerance (degrees)'), type=QgsProcessingParameterNumber.Double, @@ -68,41 +61,27 @@ class Orthogonalize(QgisAlgorithm): max_iterations.setFlags(max_iterations.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(max_iterations) - self.addParameter( - QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Orthogonalized'))) - def name(self): return 'orthogonalize' def displayName(self): return self.tr('Orthogonalize') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - max_iterations = self.parameterAsInt(parameters, self.MAX_ITERATIONS, context) - angle_tolerance = self.parameterAsDouble(parameters, self.ANGLE_TOLERANCE, context) + def outputName(self): + return self.tr('Orthogonalized') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + def prepareAlgorithm(self, parameters, context, feedback): + self.max_iterations = self.parameterAsInt(parameters, self.MAX_ITERATIONS, context) + self.angle_tolerance = self.parameterAsDouble(parameters, self.ANGLE_TOLERANCE, context) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = input_geometry.orthogonalize(1.0e-8, self.max_iterations, self.angle_tolerance) + if not output_geometry: + raise QgsProcessingException( + self.tr('Error orthogonalizing geometry')) - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = input_geometry.orthogonalize(1.0e-8, max_iterations, angle_tolerance) - if not output_geometry: - raise QgsProcessingException( - self.tr('Error orthogonalizing geometry')) - - output_feature.setGeometry(output_geometry) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + feature.setGeometry(output_geometry) + return feature diff --git a/python/plugins/processing/algs/qgis/PointOnSurface.py b/python/plugins/processing/algs/qgis/PointOnSurface.py index 882d377208e..ad62d884774 100644 --- a/python/plugins/processing/algs/qgis/PointOnSurface.py +++ b/python/plugins/processing/algs/qgis/PointOnSurface.py @@ -27,24 +27,18 @@ __revision__ = '$Format:%H$' import os -from qgis.core import (QgsWkbTypes, - QgsFeatureSink, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink) +from qgis.core import (QgsProcessing, + QgsProcessingException, + QgsWkbTypes) from qgis.PyQt.QtGui import QIcon -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.tools import dataobjects +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class PointOnSurface(QgisAlgorithm): - - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' +class PointOnSurface(QgisFeatureBasedAlgorithm): def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'centroids.png')) @@ -55,39 +49,27 @@ class PointOnSurface(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, - self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Point'), QgsProcessing.TypeVectorPoint)) - def name(self): return 'pointonsurface' def displayName(self): return self.tr('Point on surface') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def outputName(self): + return self.tr('Point') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, source.fields(), QgsWkbTypes.Point, source.sourceCrs()) + def outputType(self): + return QgsProcessing.TypeVectorPoint - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def outputWkbType(self, input_wkb_type): + return QgsWkbTypes.Point - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = input_geometry.pointOnSurface() + if not output_geometry: + raise QgsProcessingException(self.tr('Error calculating point on surface: `{error_message}`'.format(error_message=output_geometry.error()))) - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = input_geometry.pointOnSurface() - if not output_geometry: - raise QgsProcessingException(self.tr('Error calculating point on surface: `{error_message}`'.format(error_message=output_geometry.error()))) - - output_feature.setGeometry(output_geometry) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT_LAYER: dest_id} + feature.setGeometry(output_geometry) + return feature diff --git a/python/plugins/processing/algs/qgis/PolygonsToLines.py b/python/plugins/processing/algs/qgis/PolygonsToLines.py index 7a7e141d19b..c8e1ef96dfd 100644 --- a/python/plugins/processing/algs/qgis/PolygonsToLines.py +++ b/python/plugins/processing/algs/qgis/PolygonsToLines.py @@ -29,28 +29,19 @@ import os from qgis.PyQt.QtGui import QIcon -from qgis.core import (QgsFeature, - QgsGeometry, +from qgis.core import (QgsGeometry, QgsGeometryCollection, QgsMultiLineString, QgsMultiCurve, QgsWkbTypes, - QgsFeatureSink, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink, - QgsProcessingUtils) + QgsProcessing) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.tools import dataobjects +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class PolygonsToLines(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' +class PolygonsToLines(QgisFeatureBasedAlgorithm): def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'to_lines.png')) @@ -64,50 +55,25 @@ class PolygonsToLines(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), - [QgsProcessing.TypeVectorPolygon])) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, - self.tr('Polygons to lines'), - QgsProcessing.TypeVectorLine)) - def name(self): return 'polygonstolines' def displayName(self): return self.tr('Polygons to lines') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) + def outputName(self): + return self.tr('Lines') - geomType = self.convertWkbToLines(source.wkbType()) + def outputType(self): + return QgsProcessing.TypeVectorLine - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), geomType, source.sourceCrs()) + def outputWkbType(self, input_wkb_type): + return self.convertWkbToLines(input_wkb_type) - outFeat = QgsFeature() - - total = 100.0 / source.featureCount() if source.featureCount() else 0 - count = 0 - - for feat in source.getFeatures(): - if feedback.isCanceled(): - break - - if feat.hasGeometry(): - outFeat.setGeometry(QgsGeometry(self.convertToLines(feat.geometry()))) - attrs = feat.attributes() - outFeat.setAttributes(attrs) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - else: - sink.addFeature(feat, QgsFeatureSink.FastInsert) - - count += 1 - feedback.setProgress(int(count * total)) - - return {self.OUTPUT: dest_id} + def processFeature(self, feature, feedback): + if feature.hasGeometry(): + feature.setGeometry(QgsGeometry(self.convertToLines(feature.geometry()))) + return feature def convertWkbToLines(self, wkb): multi_wkb = None diff --git a/python/plugins/processing/algs/qgis/ReverseLineDirection.py b/python/plugins/processing/algs/qgis/ReverseLineDirection.py index b8ed34524c0..9a6e901ce1d 100644 --- a/python/plugins/processing/algs/qgis/ReverseLineDirection.py +++ b/python/plugins/processing/algs/qgis/ReverseLineDirection.py @@ -26,19 +26,12 @@ __copyright__ = '(C) 2015, Nyall Dawson' __revision__ = '$Format:%H$' from qgis.core import (QgsGeometry, - QgsFeature, - QgsFeatureSink, QgsProcessingException, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm + QgsProcessing) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class ReverseLineDirection(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' +class ReverseLineDirection(QgisFeatureBasedAlgorithm): def group(self): return self.tr('Vector geometry tools') @@ -46,41 +39,26 @@ class ReverseLineDirection(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), - [QgsProcessing.TypeVectorLine])) - self.addParameter( - QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Reversed'), QgsProcessing.TypeVectorLine)) - def name(self): return 'reverselinedirection' def displayName(self): return self.tr('Reverse line direction') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) + def outputName(self): + return self.tr('Reversed') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + def outputType(self): + return QgsProcessing.TypeVectorLine - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - for current, inFeat in enumerate(features): - if feedback.isCanceled(): - break + def processFeature(self, feature, feedback): + if feature.geometry(): + inGeom = feature.geometry() + reversedLine = inGeom.geometry().reversed() + if not reversedLine: + raise QgsProcessingException( + self.tr('Error reversing line')) + outGeom = QgsGeometry(reversedLine) - outFeat = inFeat - if inFeat.geometry(): - inGeom = inFeat.geometry() - reversedLine = inGeom.geometry().reversed() - if not reversedLine: - raise QgsProcessingException( - self.tr('Error reversing line')) - outGeom = QgsGeometry(reversedLine) - - outFeat.setGeometry(outGeom) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + feature.setGeometry(outGeom) + return feature diff --git a/python/plugins/processing/algs/qgis/SimplifyGeometries.py b/python/plugins/processing/algs/qgis/SimplifyGeometries.py index 1507bfbbf87..8dec5872498 100644 --- a/python/plugins/processing/algs/qgis/SimplifyGeometries.py +++ b/python/plugins/processing/algs/qgis/SimplifyGeometries.py @@ -30,24 +30,17 @@ import os from qgis.PyQt.QtGui import QIcon from qgis.core import (QgsMapToPixelSimplifier, - QgsMessageLog, - QgsFeatureSink, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink, QgsProcessingParameterEnum, QgsProcessingParameterNumber) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class SimplifyGeometries(QgisAlgorithm): +class SimplifyGeometries(QgisFeatureBasedAlgorithm): - INPUT = 'INPUT' TOLERANCE = 'TOLERANCE' - OUTPUT = 'OUTPUT' METHOD = 'METHOD' def icon(self): @@ -58,11 +51,11 @@ class SimplifyGeometries(QgisAlgorithm): def __init__(self): super().__init__() + self.tolerance = None + self.method = None + self.simplifier = None - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), - [QgsProcessing.TypeVectorPolygon, QgsProcessing.TypeVectorLine])) + def initParameters(self, config=None): self.methods = [self.tr('Distance (Douglas-Peucker)'), 'Snap to grid', 'Area (Visvalingam)'] @@ -73,51 +66,31 @@ class SimplifyGeometries(QgisAlgorithm): self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, self.tr('Tolerance'), minValue=0.0, maxValue=10000000.0, defaultValue=1.0)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Simplified'))) - def name(self): return 'simplifygeometries' def displayName(self): return self.tr('Simplify geometries') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) - method = self.parameterAsEnum(parameters, self.METHOD, context) + def outputName(self): + return self.tr('Simplified') - pointsBefore = 0 - pointsAfter = 0 + def prepareAlgorithm(self, parameters, context, feedback): + self.tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) + self.method = self.parameterAsEnum(parameters, self.METHOD, context) + if self.method != 0: + self.simplifier = QgsMapToPixelSimplifier(QgsMapToPixelSimplifier.SimplifyGeometry, self.tolerance, self.method) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processFeature(self, feature, feedback): + if feature.hasGeometry(): + input_geometry = feature.geometry() - if method != 0: - simplifier = QgsMapToPixelSimplifier(QgsMapToPixelSimplifier.SimplifyGeometry, tolerance, method) + if self.method == 0: # distance + output_geometry = input_geometry.simplify(self.tolerance) + else: + output_geometry = self.simplifier.simplify(input_geometry) - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - out_feature = input_feature - if input_feature.geometry(): - input_geometry = input_feature.geometry() - pointsBefore += input_geometry.geometry().nCoordinates() - - if method == 0: # distance - output_geometry = input_geometry.simplify(tolerance) - else: - output_geometry = simplifier.simplify(input_geometry) - - pointsAfter += output_geometry.geometry().nCoordinates() - out_feature.setGeometry(output_geometry) - - sink.addFeature(out_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - QgsMessageLog.logMessage(self.tr('Simplify: Input geometries have been simplified from {0} to {1} points').format(pointsBefore, pointsAfter), - self.tr('Processing'), QgsMessageLog.INFO) - - return {self.OUTPUT: dest_id} + feature.setGeometry(output_geometry) + return feature diff --git a/python/plugins/processing/algs/qgis/Smooth.py b/python/plugins/processing/algs/qgis/Smooth.py index 2267702c69b..7abcb46f453 100644 --- a/python/plugins/processing/algs/qgis/Smooth.py +++ b/python/plugins/processing/algs/qgis/Smooth.py @@ -25,20 +25,14 @@ __copyright__ = '(C) 2015, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import (QgsFeatureSink, - QgsProcessing, - QgsProcessingException, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterFeatureSink, +from qgis.core import (QgsProcessingException, QgsProcessingParameterNumber) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class Smooth(QgisAlgorithm): +class Smooth(QgisFeatureBasedAlgorithm): - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' ITERATIONS = 'ITERATIONS' MAX_ANGLE = 'MAX_ANGLE' OFFSET = 'OFFSET' @@ -49,9 +43,7 @@ class Smooth(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), [QgsProcessing.TypeVectorPolygon, QgsProcessing.TypeVectorLine])) + def initParameters(self, config=None): self.addParameter(QgsProcessingParameterNumber(self.ITERATIONS, self.tr('Iterations'), defaultValue=1, minValue=1, maxValue=10)) @@ -62,39 +54,27 @@ class Smooth(QgisAlgorithm): self.tr('Maximum node angle to smooth'), QgsProcessingParameterNumber.Double, defaultValue=180.0, minValue=0.0, maxValue=180.0)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Smoothed'))) - def name(self): return 'smoothgeometry' def displayName(self): return self.tr('Smooth geometry') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - iterations = self.parameterAsInt(parameters, self.ITERATIONS, context) - offset = self.parameterAsDouble(parameters, self.OFFSET, context) - max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) + def outputName(self): + return self.tr('Smoothed') - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + def prepareAlgorithm(self, parameters, context, feedback): + self.iterations = self.parameterAsInt(parameters, self.ITERATIONS, context) + self.offset = self.parameterAsDouble(parameters, self.OFFSET, context) + self.max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processFeature(self, feature, feedback): + if feature.hasGeometry(): + output_geometry = feature.geometry().smooth(self.iterations, self.offset, -1, self.max_angle) + if not output_geometry: + raise QgsProcessingException( + self.tr('Error smoothing geometry')) - for current, input_feature in enumerate(features): - if feedback.isCanceled(): - break - output_feature = input_feature - if input_feature.geometry(): - output_geometry = input_feature.geometry().smooth(iterations, offset, -1, max_angle) - if not output_geometry: - raise QgsProcessingException( - self.tr('Error smoothing geometry')) - - output_feature.setGeometry(output_geometry) - - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - return {self.OUTPUT: dest_id} + feature.setGeometry(output_geometry) + return feature diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 09e562f193c..269d8538520 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -481,44 +481,44 @@ tests: - algorithm: qgis:boundary name: Polygon boundary params: - INPUT_LAYER: + INPUT: name: polys.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/poly_boundary.gml type: vector - algorithm: qgis:boundary name: Multipoly boundary params: - INPUT_LAYER: + INPUT: name: multipolys.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/multipoly_boundary.gml type: vector - algorithm: qgis:boundary name: Line boundary params: - INPUT_LAYER: + INPUT: name: lines.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/lines_boundary.gml type: vector - algorithm: qgis:boundary name: Multiline boundary params: - INPUT_LAYER: + INPUT: name: multilines.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/multiline_boundary.gml type: vector @@ -588,33 +588,33 @@ tests: - algorithm: qgis:pointonsurface name: Point on polygon surface params: - INPUT_LAYER: + INPUT: name: polys.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/point_on_poly.gml type: vector - algorithm: qgis:pointonsurface name: Point on multipoint surface params: - INPUT_LAYER: + INPUT: name: multipoints.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/point_on_multipoint.gml type: vector - algorithm: qgis:pointonsurface name: Point on line surface params: - INPUT_LAYER: + INPUT: name: lines.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/point_on_line.gml type: vector From 4a935c1090d9236a00f28916c8cadf404ac4c771 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 14:42:17 +1000 Subject: [PATCH 091/266] Resurrect Translate algorithm --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../plugins/processing/algs/qgis/Translate.py | 75 +++++++------------ .../tests/testdata/qgis_algorithm_tests.yaml | 26 +++---- 3 files changed, 43 insertions(+), 63 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 06b84cdb565..b6e406f7bd1 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -102,6 +102,7 @@ from .SnapGeometries import SnapGeometriesToLayer from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SumLines import SumLines from .SymmetricalDifference import SymmetricalDifference +from .Translate import Translate from .Union import Union from .UniqueValues import UniqueValues from .VectorSplit import VectorSplit @@ -156,7 +157,6 @@ from .ZonalStatistics import ZonalStatistics # from .RectanglesOvalsDiamondsVariable import RectanglesOvalsDiamondsVariable # from .RectanglesOvalsDiamondsFixed import RectanglesOvalsDiamondsFixed # from .MergeLines import MergeLines -# from .Translate import Translate # from .SingleSidedBuffer import SingleSidedBuffer # from .PointsAlongGeometry import PointsAlongGeometry # from .Relief import Relief @@ -217,7 +217,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # SpatialIndex(), DefineProjection(), # RectanglesOvalsDiamondsVariable(), # RectanglesOvalsDiamondsFixed(), MergeLines(), - # Translate(), + # # SingleSidedBuffer(), PointsAlongGeometry(), # Relief(), # IdwInterpolation(), TinInterpolation(), @@ -292,6 +292,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): SpatialiteExecuteSQL(), SumLines(), SymmetricalDifference(), + Translate(), Union(), UniqueValues(), VectorSplit(), diff --git a/python/plugins/processing/algs/qgis/Translate.py b/python/plugins/processing/algs/qgis/Translate.py index c9168fa60d0..96b5ac99fba 100644 --- a/python/plugins/processing/algs/qgis/Translate.py +++ b/python/plugins/processing/algs/qgis/Translate.py @@ -25,23 +25,13 @@ __copyright__ = '(C) 2016, Nyall Dawson' __revision__ = '$Format:%H$' -import os - -from qgis.core import (QgsApplication, - QgsFeatureSink, - QgsProcessingUtils) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector, ParameterNumber -from processing.core.outputs import OutputVector - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] +from qgis.core import (QgsProcessingException, + QgsProcessingParameterNumber) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class Translate(QgisAlgorithm): +class Translate(QgisFeatureBasedAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' DELTA_X = 'DELTA_X' DELTA_Y = 'DELTA_Y' @@ -50,16 +40,14 @@ class Translate(QgisAlgorithm): def __init__(self): super().__init__() + self.delta_x = 0 + self.delta_y = 0 - def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'))) - self.addParameter(ParameterNumber(self.DELTA_X, - self.tr('Offset distance (x-axis)'), default=1.0)) - self.addParameter(ParameterNumber(self.DELTA_Y, - self.tr('Offset distance (y-axis)'), default=0.0)) - - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Translated'))) + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterNumber(self.DELTA_X, + self.tr('Offset distance (x-axis)'), QgsProcessingParameterNumber.Double, defaultValue=0.0)) + self.addParameter(QgsProcessingParameterNumber(self.DELTA_Y, + self.tr('Offset distance (y-axis)'), QgsProcessingParameterNumber.Double, defaultValue=0.0)) def name(self): return 'translategeometry' @@ -67,31 +55,22 @@ class Translate(QgisAlgorithm): def displayName(self): return self.tr('Translate geometry') - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + def outputName(self): + return self.tr('Translated') - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context) + def prepareAlgorithm(self, parameters, context, feedback): + self.delta_x = self.parameterAsDouble(parameters, self.DELTA_X, context) + self.delta_y = self.parameterAsDouble(parameters, self.DELTA_Y, context) + return True - delta_x = self.getParameterValue(self.DELTA_X) - delta_y = self.getParameterValue(self.DELTA_Y) + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = input_geometry + output_geometry.translate(self.delta_x, self.delta_y) + if not output_geometry: + raise QgsProcessingException( + self.tr('Error translating geometry')) - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 - - for current, input_feature in enumerate(features): - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = input_geometry - output_geometry.translate(delta_x, delta_y) - if not output_geometry: - raise GeoAlgorithmExecutionException( - self.tr('Error translating geometry')) - - output_feature.setGeometry(output_geometry) - - writer.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - del writer + feature.setGeometry(output_geometry) + return feature diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 269d8538520..fb303a6c7aa 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -900,19 +900,19 @@ tests: geometry: precision: 7 -# - algorithm: qgis:translategeometry -# name: Lines translated -# params: -# DELTA_X: 0.1 -# DELTA_Y: -0.2 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# results: -# OUTPUT_LAYER: -# name: expected/lines_translated.gml -# type: vector -# + - algorithm: qgis:translategeometry + name: Lines translated + params: + DELTA_X: 0.1 + DELTA_Y: -0.2 + INPUT: + name: lines.gml + type: vector + results: + OUTPUT: + name: expected/lines_translated.gml + type: vector + # - algorithm: qgis:singlesidedbuffer # name: Single sided buffer lines (left, round) # params: From 1cac3bb635d429de2250e8e564638b6538325122 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 14:53:02 +1000 Subject: [PATCH 092/266] Port single sided buffer to new API --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../processing/algs/qgis/SingleSidedBuffer.py | 120 ++++++++---------- .../tests/testdata/qgis_algorithm_tests.yaml | 106 ++++++++-------- 3 files changed, 111 insertions(+), 120 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index b6e406f7bd1..9029bc2565d 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -96,6 +96,7 @@ from .ShortestPathLayerToPoint import ShortestPathLayerToPoint from .ShortestPathPointToLayer import ShortestPathPointToLayer from .ShortestPathPointToPoint import ShortestPathPointToPoint from .SimplifyGeometries import SimplifyGeometries +from .SingleSidedBuffer import SingleSidedBuffer from .Slope import Slope from .Smooth import Smooth from .SnapGeometries import SnapGeometriesToLayer @@ -157,7 +158,6 @@ from .ZonalStatistics import ZonalStatistics # from .RectanglesOvalsDiamondsVariable import RectanglesOvalsDiamondsVariable # from .RectanglesOvalsDiamondsFixed import RectanglesOvalsDiamondsFixed # from .MergeLines import MergeLines -# from .SingleSidedBuffer import SingleSidedBuffer # from .PointsAlongGeometry import PointsAlongGeometry # from .Relief import Relief # from .IdwInterpolation import IdwInterpolation @@ -218,7 +218,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # RectanglesOvalsDiamondsVariable(), # RectanglesOvalsDiamondsFixed(), MergeLines(), # - # SingleSidedBuffer(), PointsAlongGeometry(), + # PointsAlongGeometry(), # Relief(), # IdwInterpolation(), TinInterpolation(), # ExtendLines(), ExtractSpecificNodes(), @@ -286,6 +286,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): ShortestPathPointToLayer(), ShortestPathPointToPoint(), SimplifyGeometries(), + SingleSidedBuffer(), Slope(), Smooth(), SnapGeometriesToLayer(), diff --git a/python/plugins/processing/algs/qgis/SingleSidedBuffer.py b/python/plugins/processing/algs/qgis/SingleSidedBuffer.py index ef05bc8d572..ab55fdec8fc 100644 --- a/python/plugins/processing/algs/qgis/SingleSidedBuffer.py +++ b/python/plugins/processing/algs/qgis/SingleSidedBuffer.py @@ -25,27 +25,17 @@ __copyright__ = '(C) 2016, Nyall Dawson' __revision__ = '$Format:%H$' -import os - -from qgis.core import (QgsApplication, - QgsGeometry, - QgsFeatureSink, +from qgis.core import (QgsGeometry, QgsWkbTypes, - QgsProcessingUtils) + QgsProcessing, + QgsProcessingParameterNumber, + QgsProcessingParameterEnum, + QgsProcessingException) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector, ParameterSelection, ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class SingleSidedBuffer(QgisAlgorithm): - - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' +class SingleSidedBuffer(QgisFeatureBasedAlgorithm): DISTANCE = 'DISTANCE' SIDE = 'SIDE' SEGMENTS = 'SEGMENTS' @@ -57,33 +47,35 @@ class SingleSidedBuffer(QgisAlgorithm): def __init__(self): super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterNumber(self.DISTANCE, - self.tr('Distance'), default=10.0)) + self.distance = None + self.segments = None + self.join_style = None + self.side = None + self.miter_limit = None self.sides = [self.tr('Left'), 'Right'] - self.addParameter(ParameterSelection( - self.SIDE, - self.tr('Side'), - self.sides)) - - self.addParameter(ParameterNumber(self.SEGMENTS, - self.tr('Segments'), 1, default=8)) - self.join_styles = [self.tr('Round'), 'Mitre', 'Bevel'] - self.addParameter(ParameterSelection( + + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterNumber(self.DISTANCE, + self.tr('Distance'), defaultValue=10.0)) + self.addParameter(QgsProcessingParameterEnum( + self.SIDE, + self.tr('Side'), + options=self.sides)) + + self.addParameter(QgsProcessingParameterNumber(self.SEGMENTS, + self.tr('Segments'), QgsProcessingParameterNumber.Integer, + minValue=1, defaultValue=8)) + + self.addParameter(QgsProcessingParameterEnum( self.JOIN_STYLE, self.tr('Join style'), - self.join_styles)) - self.addParameter(ParameterNumber(self.MITRE_LIMIT, - self.tr('Mitre limit'), 1, default=2)) - - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Single sided buffers'))) + options=self.join_styles)) + self.addParameter(QgsProcessingParameterNumber(self.MITRE_LIMIT, + self.tr('Mitre limit'), minValue=1, defaultValue=2)) def name(self): return 'singlesidedbuffer' @@ -91,37 +83,35 @@ class SingleSidedBuffer(QgisAlgorithm): def displayName(self): return self.tr('Single sided buffer') - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + def outputName(self): + return self.tr('Buffers') - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), QgsWkbTypes.Polygon, layer.crs(), context) + def outputType(self): + return QgsProcessing.TypeVectorPolygon - distance = self.getParameterValue(self.DISTANCE) - segments = int(self.getParameterValue(self.SEGMENTS)) - join_style = self.getParameterValue(self.JOIN_STYLE) + 1 - if self.getParameterValue(self.SIDE) == 0: - side = QgsGeometry.SideLeft + def outputWkbType(self, input_wkb_type): + return QgsWkbTypes.Polygon + + def prepareAlgorithm(self, parameters, context, feedback): + self.distance = self.parameterAsDouble(parameters, self.DISTANCE, context) + self.segments = self.parameterAsInt(parameters, self.SEGMENTS, context) + self.join_style = self.parameterAsEnum(parameters, self.JOIN_STYLE, context) + 1 + if self.parameterAsEnum(parameters, self.SIDE, context) == 0: + self.side = QgsGeometry.SideLeft else: - side = QgsGeometry.SideRight - miter_limit = self.getParameterValue(self.MITRE_LIMIT) + self.side = QgsGeometry.SideRight + self.miter_limit = self.parameterAsDouble(parameters, self.MITRE_LIMIT, context) + return True - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = input_geometry.singleSidedBuffer(self.distance, self.segments, + self.side, self.join_style, self.miter_limit) + if not output_geometry: + raise QgsProcessingException( + self.tr('Error calculating single sided buffer')) - for current, input_feature in enumerate(features): - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = input_geometry.singleSidedBuffer(distance, segments, - side, join_style, miter_limit) - if not output_geometry: - raise GeoAlgorithmExecutionException( - self.tr('Error calculating single sided buffer')) + feature.setGeometry(output_geometry) - output_feature.setGeometry(output_geometry) - - writer.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - del writer + return feature diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index fb303a6c7aa..86afc425173 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -913,59 +913,59 @@ tests: name: expected/lines_translated.gml type: vector -# - algorithm: qgis:singlesidedbuffer -# name: Single sided buffer lines (left, round) -# params: -# DISTANCE: 1.0 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# JOIN_STYLE: '0' -# MITRE_LIMIT: 2 -# SEGMENTS: 8 -# SIDE: '0' -# results: -# OUTPUT_LAYER: -# name: expected/single_sided_buffer_line.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:singlesidedbuffer -# name: Single sided buffer lines (Right, mitre) -# params: -# DISTANCE: 1.0 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# JOIN_STYLE: '1' -# MITRE_LIMIT: 2 -# SEGMENTS: 8 -# SIDE: '1' -# results: -# OUTPUT_LAYER: -# name: expected/single_sided_buffer_line_mitre.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:singlesidedbuffer -# name: Single sided buffer multiline (bevel) -# params: -# DISTANCE: 1.0 -# INPUT_LAYER: -# name: multilines.gml -# type: vector -# JOIN_STYLE: '2' -# MITRE_LIMIT: 2 -# SEGMENTS: 8 -# SIDE: '0' -# results: -# OUTPUT_LAYER: -# name: expected/single_sided_buffer_multiline_bevel.gml -# type: vector + - algorithm: qgis:singlesidedbuffer + name: Single sided buffer lines (left, round) + params: + DISTANCE: 1.0 + INPUT: + name: lines.gml + type: vector + JOIN_STYLE: '0' + MITRE_LIMIT: 2 + SEGMENTS: 8 + SIDE: '0' + results: + OUTPUT: + name: expected/single_sided_buffer_line.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:singlesidedbuffer + name: Single sided buffer lines (Right, mitre) + params: + DISTANCE: 1.0 + INPUT: + name: lines.gml + type: vector + JOIN_STYLE: '1' + MITRE_LIMIT: 2 + SEGMENTS: 8 + SIDE: '1' + results: + OUTPUT: + name: expected/single_sided_buffer_line_mitre.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:singlesidedbuffer + name: Single sided buffer multiline (bevel) + params: + DISTANCE: 1.0 + INPUT: + name: multilines.gml + type: vector + JOIN_STYLE: '2' + MITRE_LIMIT: 2 + SEGMENTS: 8 + SIDE: '0' + results: + OUTPUT: + name: expected/single_sided_buffer_multiline_bevel.gml + type: vector - algorithm: qgis:extractnodes name: Test (qgis:extractnodes) From c0669d4fd24a1a2d64270285dd3ba5eef70c8326 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 15:04:45 +1000 Subject: [PATCH 093/266] Port extend lines to new API --- .../processing/algs/qgis/ExtendLines.py | 69 +++++++------------ .../algs/qgis/QGISAlgorithmProvider.py | 6 +- .../tests/testdata/qgis_algorithm_tests.yaml | 58 ++++++++-------- 3 files changed, 58 insertions(+), 75 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ExtendLines.py b/python/plugins/processing/algs/qgis/ExtendLines.py index 142b0da72c4..95f13a6c00f 100644 --- a/python/plugins/processing/algs/qgis/ExtendLines.py +++ b/python/plugins/processing/algs/qgis/ExtendLines.py @@ -25,20 +25,13 @@ __copyright__ = '(C) 2016, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import (QgsApplication, - QgsFeatureSink, - QgsProcessingUtils) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector, ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects +from qgis.core import (QgsProcessingParameterNumber, + QgsProcessingException) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class ExtendLines(QgisAlgorithm): +class ExtendLines(QgisFeatureBasedAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' START_DISTANCE = 'START_DISTANCE' END_DISTANCE = 'END_DISTANCE' @@ -47,16 +40,14 @@ class ExtendLines(QgisAlgorithm): def __init__(self): super().__init__() + self.start_distance = None + self.end_distance = None - def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterNumber(self.START_DISTANCE, - self.tr('Start distance'), default=0.0)) - self.addParameter(ParameterNumber(self.END_DISTANCE, - self.tr('End distance'), default=0.0)) - - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Extended lines'))) + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterNumber(self.START_DISTANCE, + self.tr('Start distance'), defaultValue=0.0)) + self.addParameter(QgsProcessingParameterNumber(self.END_DISTANCE, + self.tr('End distance'), defaultValue=0.0)) def name(self): return 'extendlines' @@ -64,30 +55,22 @@ class ExtendLines(QgisAlgorithm): def displayName(self): return self.tr('Extend lines') - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + def outputName(self): + return self.tr('Extended') - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context) + def prepareAlgorithm(self, parameters, context, feedback): + self.start_distance = self.parameterAsDouble(parameters, self.START_DISTANCE, context) + self.end_distance = self.parameterAsDouble(parameters, self.END_DISTANCE, context) + return True - start_distance = self.getParameterValue(self.START_DISTANCE) - end_distance = self.getParameterValue(self.END_DISTANCE) + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = input_geometry.extendLine(self.start_distance, self.end_distance) + if not output_geometry: + raise QgsProcessingException( + self.tr('Error calculating extended line')) - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + feature.setGeometry(output_geometry) - for current, input_feature in enumerate(features): - output_feature = input_feature - input_geometry = input_feature.geometry() - if input_geometry: - output_geometry = input_geometry.extendLine(start_distance, end_distance) - if not output_geometry: - raise GeoAlgorithmExecutionException( - self.tr('Error calculating extended line')) - - output_feature.setGeometry(output_geometry) - - writer.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - del writer + return feature diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 9029bc2565d..6f654125a8c 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -57,6 +57,7 @@ from .DensifyGeometriesInterval import DensifyGeometriesInterval from .Difference import Difference from .DropGeometry import DropGeometry from .DropMZValues import DropMZValues +from .ExtendLines import ExtendLines from .ExtentFromLayer import ExtentFromLayer from .ExtractNodes import ExtractNodes from .FixGeometry import FixGeometry @@ -162,7 +163,6 @@ from .ZonalStatistics import ZonalStatistics # from .Relief import Relief # from .IdwInterpolation import IdwInterpolation # from .TinInterpolation import TinInterpolation -# from .ExtendLines import ExtendLines # from .ExtractSpecificNodes import ExtractSpecificNodes # from .GeometryByExpression import GeometryByExpression # from .RasterCalculator import RasterCalculator @@ -217,11 +217,10 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # SpatialIndex(), DefineProjection(), # RectanglesOvalsDiamondsVariable(), # RectanglesOvalsDiamondsFixed(), MergeLines(), - # # PointsAlongGeometry(), # Relief(), # IdwInterpolation(), TinInterpolation(), - # ExtendLines(), ExtractSpecificNodes(), + # ExtractSpecificNodes(), # GeometryByExpression(), # RasterCalculator(), # ShortestPathPointToPoint(), ShortestPathPointToLayer(), @@ -247,6 +246,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): Difference(), DropGeometry(), DropMZValues(), + ExtendLines(), ExtentFromLayer(), ExtractNodes(), FixGeometry(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 86afc425173..766c5c90958 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1346,7 +1346,7 @@ tests: OUTPUT: name: expected/remove_null_polys.gml type: vector -# + - algorithm: native:extractbyexpression name: Extract by Expression params: @@ -1359,34 +1359,34 @@ tests: name: expected/extract_expression.gml type: vector -# - algorithm: qgis:extendlines -# name: Extend lines -# params: -# END_DISTANCE: 0.2 -# INPUT_LAYER: -# name: lines.gml -# type: vector -# START_DISTANCE: 0.1 -# results: -# OUTPUT_LAYER: -# name: expected/extend_lines.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# - algorithm: qgis:extendlines -# name: Extend multilines -# params: -# END_DISTANCE: 0.4 -# INPUT_LAYER: -# name: multilines.gml -# type: vector -# START_DISTANCE: 0.2 -# results: -# OUTPUT_LAYER: -# name: expected/extend_multilines.gml -# type: vector -# + - algorithm: qgis:extendlines + name: Extend lines + params: + END_DISTANCE: 0.2 + INPUT: + name: lines.gml + type: vector + START_DISTANCE: 0.1 + results: + OUTPUT: + name: expected/extend_lines.gml + type: vector + compare: + geometry: + precision: 7 + - algorithm: qgis:extendlines + name: Extend multilines + params: + END_DISTANCE: 0.4 + INPUT: + name: multilines.gml + type: vector + START_DISTANCE: 0.2 + results: + OUTPUT: + name: expected/extend_multilines.gml + type: vector + # - algorithm: qgis:extractspecificnodes # name: Extract specific nodes lines # params: From 96cf6612d39077e5e57e5d72fca594ba3b11ccdc Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 15:25:55 +1000 Subject: [PATCH 094/266] Port geometry by expression to new API --- .../algs/qgis/GeometryByExpression.py | 126 ++++++++---------- .../algs/qgis/QGISAlgorithmProvider.py | 4 +- .../tests/testdata/qgis_algorithm_tests.yaml | 96 ++++++------- 3 files changed, 106 insertions(+), 120 deletions(-) diff --git a/python/plugins/processing/algs/qgis/GeometryByExpression.py b/python/plugins/processing/algs/qgis/GeometryByExpression.py index 6297f7dacbc..6be1fbaf73c 100644 --- a/python/plugins/processing/algs/qgis/GeometryByExpression.py +++ b/python/plugins/processing/algs/qgis/GeometryByExpression.py @@ -27,23 +27,17 @@ __revision__ = '$Format:%H$' from qgis.core import (QgsWkbTypes, QgsExpression, - QgsFeatureSink, - QgsExpressionContext, - QgsExpressionContextUtils, QgsGeometry, - QgsApplication, - QgsProcessingUtils) + QgsProcessingException, + QgsProcessingParameterBoolean, + QgsProcessingParameterEnum, + QgsProcessingParameterExpression) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector, ParameterSelection, ParameterBoolean, ParameterExpression -from processing.core.outputs import OutputVector +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class GeometryByExpression(QgisAlgorithm): +class GeometryByExpression(QgisFeatureBasedAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' OUTPUT_GEOMETRY = 'OUTPUT_GEOMETRY' WITH_Z = 'WITH_Z' WITH_M = 'WITH_M' @@ -54,27 +48,22 @@ class GeometryByExpression(QgisAlgorithm): def __init__(self): super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'))) - self.geometry_types = [self.tr('Polygon'), 'Line', 'Point'] - self.addParameter(ParameterSelection( + + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterEnum( self.OUTPUT_GEOMETRY, self.tr('Output geometry type'), - self.geometry_types, default=0)) - self.addParameter(ParameterBoolean(self.WITH_Z, - self.tr('Output geometry has z dimension'), False)) - self.addParameter(ParameterBoolean(self.WITH_M, - self.tr('Output geometry has m values'), False)) + options=self.geometry_types, defaultValue=0)) + self.addParameter(QgsProcessingParameterBoolean(self.WITH_Z, + self.tr('Output geometry has z dimension'), defaultValue=False)) + self.addParameter(QgsProcessingParameterBoolean(self.WITH_M, + self.tr('Output geometry has m values'), defaultValue=False)) - self.addParameter(ParameterExpression(self.EXPRESSION, - self.tr("Geometry expression"), '$geometry', parent_layer=self.INPUT_LAYER)) - - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Modified geometry'))) + self.addParameter(QgsProcessingParameterExpression(self.EXPRESSION, + self.tr("Geometry expression"), defaultValue='$geometry', parentLayerParameterName='INPUT')) def name(self): return 'geometrybyexpression' @@ -82,55 +71,52 @@ class GeometryByExpression(QgisAlgorithm): def displayName(self): return self.tr('Geometry by expression') - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + def outputName(self): + return self.tr('Modified geometry') - geometry_type = self.getParameterValue(self.OUTPUT_GEOMETRY) - wkb_type = None - if geometry_type == 0: - wkb_type = QgsWkbTypes.Polygon - elif geometry_type == 1: - wkb_type = QgsWkbTypes.LineString + def prepareAlgorithm(self, parameters, context, feedback): + self.geometry_type = self.parameterAsEnum(parameters, self.OUTPUT_GEOMETRY, context) + self.wkb_type = None + if self.geometry_type == 0: + self.wkb_type = QgsWkbTypes.Polygon + elif self.geometry_type == 1: + self.wkb_type = QgsWkbTypes.LineString else: - wkb_type = QgsWkbTypes.Point - if self.getParameterValue(self.WITH_Z): - wkb_type = QgsWkbTypes.addZ(wkb_type) - if self.getParameterValue(self.WITH_M): - wkb_type = QgsWkbTypes.addM(wkb_type) + self.wkb_type = QgsWkbTypes.Point + if self.parameterAsBool(parameters, self.WITH_Z, context): + self.wkb_type = QgsWkbTypes.addZ(self.wkb_type) + if self.parameterAsBool(parameters, self.WITH_M, context): + self.wkb_type = QgsWkbTypes.addM(self.wkb_type) - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), wkb_type, layer.crs(), context) + self.expression = QgsExpression(self.parameterAsString(parameters, self.EXPRESSION, context)) + if self.expression.hasParserError(): + feedback.reportError(self.expression.parserErrorString()) + return False - expression = QgsExpression(self.getParameterValue(self.EXPRESSION)) - if expression.hasParserError(): - raise GeoAlgorithmExecutionException(expression.parserErrorString()) + self.expression_context = self.createExpressionContext(parameters, context) - exp_context = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(layer)) + if not self.expression.prepare(self.expression_context): + feedback.reportErro( + self.tr('Evaluation error: {0}').format(self.expression.evalErrorString())) + return False - if not expression.prepare(exp_context): - raise GeoAlgorithmExecutionException( - self.tr('Evaluation error: {0}').format(expression.evalErrorString())) + return True - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 - for current, input_feature in enumerate(features): - output_feature = input_feature + def outputWkbType(self, input_wkb_type): + return self.wkb_type - exp_context.setFeature(input_feature) - value = expression.evaluate(exp_context) - if expression.hasEvalError(): - raise GeoAlgorithmExecutionException( - self.tr('Evaluation error: {0}').format(expression.evalErrorString())) + def processFeature(self, feature, feedback): + self.expression_context.setFeature(feature) + value = self.expression.evaluate(self.expression_context) + if self.expression.hasEvalError(): + raise QgsProcessingException( + self.tr('Evaluation error: {0}').format(self.expression.evalErrorString())) - if not value: - output_feature.setGeometry(QgsGeometry()) - else: - if not isinstance(value, QgsGeometry): - raise GeoAlgorithmExecutionException( - self.tr('{} is not a geometry').format(value)) - output_feature.setGeometry(value) - - writer.addFeature(output_feature, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - del writer + if not value: + feature.setGeometry(QgsGeometry()) + else: + if not isinstance(value, QgsGeometry): + raise QgsProcessingException( + self.tr('{} is not a geometry').format(value)) + feature.setGeometry(value) + return feature diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 6f654125a8c..ab06ea33ab2 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -61,6 +61,7 @@ from .ExtendLines import ExtendLines from .ExtentFromLayer import ExtentFromLayer from .ExtractNodes import ExtractNodes from .FixGeometry import FixGeometry +from .GeometryByExpression import GeometryByExpression from .GridPolygon import GridPolygon from .Heatmap import Heatmap from .Hillshade import Hillshade @@ -164,7 +165,6 @@ from .ZonalStatistics import ZonalStatistics # from .IdwInterpolation import IdwInterpolation # from .TinInterpolation import TinInterpolation # from .ExtractSpecificNodes import ExtractSpecificNodes -# from .GeometryByExpression import GeometryByExpression # from .RasterCalculator import RasterCalculator # from .TruncateTable import TruncateTable # from .Polygonize import Polygonize @@ -221,7 +221,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # Relief(), # IdwInterpolation(), TinInterpolation(), # ExtractSpecificNodes(), - # GeometryByExpression(), # RasterCalculator(), # ShortestPathPointToPoint(), ShortestPathPointToLayer(), # ShortestPathLayerToPoint(), ServiceAreaFromPoint(), @@ -250,6 +249,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): ExtentFromLayer(), ExtractNodes(), FixGeometry(), + GeometryByExpression(), GridPolygon(), Heatmap(), Hillshade(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 766c5c90958..7e4b04a9916 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1416,54 +1416,54 @@ tests: # compare: # fields: # fid: skip -# -# - algorithm: qgis:geometrybyexpression -# name: Geometry by expression (point) -# params: -# EXPRESSION: 'translate( $geometry,1,1)' -# INPUT_LAYER: -# name: points.gml -# type: vector -# OUTPUT_GEOMETRY: '0' -# WITH_M: false -# WITH_Z: false -# results: -# OUTPUT_LAYER: -# name: expected/geometry_by_expression_point.gml -# type: vector -# -# - algorithm: qgis:geometrybyexpression -# name: Geometry by expression (polygon) -# params: -# EXPRESSION: ' translate( centroid($geometry),1,1)' -# INPUT_LAYER: -# name: polys.gml -# type: vector -# OUTPUT_GEOMETRY: '2' -# WITH_M: false -# WITH_Z: false -# results: -# OUTPUT_LAYER: -# name: expected/geometry_by_expression_poly.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:geometrybyexpression -# name: Geometry by expression (line) -# params: -# EXPRESSION: ' translate( $geometry,1,1)' -# INPUT_LAYER: -# name: lines.gml -# type: vector -# OUTPUT_GEOMETRY: '1' -# WITH_M: false -# WITH_Z: false -# results: -# OUTPUT_LAYER: -# name: expected/geometry_by_expression_line.gml -# type: vector + + - algorithm: qgis:geometrybyexpression + name: Geometry by expression (point) + params: + EXPRESSION: 'translate( $geometry,1,1)' + INPUT: + name: points.gml + type: vector + OUTPUT_GEOMETRY: '0' + WITH_M: false + WITH_Z: false + results: + OUTPUT: + name: expected/geometry_by_expression_point.gml + type: vector + + - algorithm: qgis:geometrybyexpression + name: Geometry by expression (polygon) + params: + EXPRESSION: ' translate( centroid($geometry),1,1)' + INPUT: + name: polys.gml + type: vector + OUTPUT_GEOMETRY: '2' + WITH_M: false + WITH_Z: false + results: + OUTPUT: + name: expected/geometry_by_expression_poly.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:geometrybyexpression + name: Geometry by expression (line) + params: + EXPRESSION: ' translate( $geometry,1,1)' + INPUT: + name: lines.gml + type: vector + OUTPUT_GEOMETRY: '1' + WITH_M: false + WITH_Z: false + results: + OUTPUT: + name: expected/geometry_by_expression_line.gml + type: vector - algorithm: qgis:snapgeometries name: Snap lines to lines From 5241c74c6ccaeea5a85165350c67762747684b65 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 16:15:29 +1000 Subject: [PATCH 095/266] Correctly create raster for heatmap output Incorrect creation of geo transform was leading to invalid raster outputs and many "creating warped vrt" log messages --- src/analysis/raster/qgskde.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/raster/qgskde.cpp b/src/analysis/raster/qgskde.cpp index aad9abdd8be..8d0b45393aa 100644 --- a/src/analysis/raster/qgskde.cpp +++ b/src/analysis/raster/qgskde.cpp @@ -227,7 +227,7 @@ int QgsKernelDensityEstimation::radiusSizeInPixels( double radius ) const bool QgsKernelDensityEstimation::createEmptyLayer( GDALDriverH driver, const QgsRectangle &bounds, int rows, int columns ) const { - double geoTransform[6] = { bounds.xMinimum(), mPixelSize, 0, bounds.yMinimum(), 0, mPixelSize }; + double geoTransform[6] = { bounds.xMinimum(), mPixelSize, 0, bounds.yMaximum(), 0, -mPixelSize }; GDALDatasetH emptyDataset = GDALCreate( driver, mOutputFile.toUtf8(), columns, rows, 1, GDT_Float32, nullptr ); if ( !emptyDataset ) return false; From 5e03f579d12e3694df6b53a3e212e04b791644ff Mon Sep 17 00:00:00 2001 From: Giovanni Manghi Date: Thu, 20 Jul 2017 12:15:09 +0100 Subject: [PATCH 096/266] mater: fix SAGA LTR Catchement Area tools --- .../FlowAccumulation(FlowTracing).txt | 15 ++++++++------- .../description/FlowAccumulation(Recursive).txt | 17 ++++++++++------- .../description/FlowAccumulation(Top-Down).txt | 2 +- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/python/plugins/processing/algs/saga/description/FlowAccumulation(FlowTracing).txt b/python/plugins/processing/algs/saga/description/FlowAccumulation(FlowTracing).txt index ac9e521f676..ee4f914a3af 100644 --- a/python/plugins/processing/algs/saga/description/FlowAccumulation(FlowTracing).txt +++ b/python/plugins/processing/algs/saga/description/FlowAccumulation(FlowTracing).txt @@ -1,17 +1,18 @@ Flow Accumulation (Flow Tracing) ta_hydrology ParameterRaster|ELEVATION|Elevation|False +ParameterSelection|FLOW_UNIT|Flow Accumulation Units|[0] Number of Cells;[1] Cell Area|1 ParameterRaster|SINKROUTE|Sink Routes|True -ParameterRaster|WEIGHT|Weight|True -ParameterRaster|MATERIAL|Material|True -ParameterRaster|VAL_INPUT|Input for Mean over Catchment Calculation|True -ParameterRaster|TARGET|Target|True +ParameterRaster|WEIGHTS|Weights|True +ParameterRaster|ACCU_MATERIAL|Material for Accumulation|True +ParameterRaster|VAL_INPUT|Input for Mean over Catchment|True +ParameterRaster|ACCU_TARGET|Accumulation Target|True ParameterNumber|STEP|Step|None|None|1 ParameterSelection|METHOD|Method|[0] Rho 8;[1] Kinematic Routing Algorithm;[2] DEMON ParameterNumber|MINDQV|DEMON - Min. DQV|None|None|0.0 -ParameterBoolean|CORRECT|Flow Correction|True +ParameterBoolean|CORRECT|Flow Correction|False OutputRaster|FLOW|Flow Accumulation OutputRaster|VAL_MEAN|Mean over Catchment -OutputRaster|ACCU_TOTAL|Total accumulated Material +OutputRaster|ACCU_TOTAL|Accumulated Material OutputRaster|ACCU_LEFT|Accumulated Material from left side -OutputRaster|ACCU_RIGHT|Accumulated Material from right side \ No newline at end of file +OutputRaster|ACCU_RIGHT|Accumulated Material from right side diff --git a/python/plugins/processing/algs/saga/description/FlowAccumulation(Recursive).txt b/python/plugins/processing/algs/saga/description/FlowAccumulation(Recursive).txt index 9bdab3eccd5..a8113d14943 100644 --- a/python/plugins/processing/algs/saga/description/FlowAccumulation(Recursive).txt +++ b/python/plugins/processing/algs/saga/description/FlowAccumulation(Recursive).txt @@ -1,18 +1,21 @@ Flow Accumulation (Recursive) ta_hydrology ParameterRaster|ELEVATION|Elevation|False +ParameterSelection|FLOW_UNIT|Flow Accumulation Units|[0] Number of Cells;[1] Cell Area|1 ParameterRaster|SINKROUTE|Sink Routes|True -ParameterRaster|WEIGHT|Weight|True -ParameterRaster|MATERIAL|Material|True -ParameterRaster|VAL_INPUT|Input for Mean over Catchment Calculation|True -ParameterRaster|TARGET|Target|True +ParameterRaster|WEIGHTS|Weights|True +ParameterRaster|ACCU_MATERIAL|Material for Accumulation|True +ParameterRaster|VAL_INPUT|Input for Mean over Catchment|True +ParameterRaster|ACCU_TARGET|Accumulation Target|True ParameterNumber|STEP|Step|None|None|1 ParameterRaster|TARGETS|Target Areas|True ParameterSelection|METHOD|Method|[0] Deterministic 8;[1] Rho 8;[2] Deterministic Infinity;[3] Multiple Flow Direction ParameterNumber|CONVERGENCE|Convergence|None|None|1.1 -OutputRaster|CAREA|Catchment Area +ParameterBoolean|NO_NEGATIVES|Prevent Negative Flow Accumulation|True +OutputRaster|FLOW|Catchment Area OutputRaster|VAL_MEAN|Mean over Catchment -OutputRaster|ACCU_TOT|Total accumulated Material +OutputRaster|ACCU_TOTAL|Accumulated Material OutputRaster|ACCU_LEFT|Accumulated Material from left side OutputRaster|ACCU_RIGHT|Accumulated Material from right side -OutputRaster|FLOWLEN|Flow Path Length +OutputRaster|FLOW_LENGTH|Flow Path Length +OutputRaster|WEIGHT_LOSS|Loss through Negative Weights diff --git a/python/plugins/processing/algs/saga/description/FlowAccumulation(Top-Down).txt b/python/plugins/processing/algs/saga/description/FlowAccumulation(Top-Down).txt index f575a7fea4c..41062c2ed2d 100644 --- a/python/plugins/processing/algs/saga/description/FlowAccumulation(Top-Down).txt +++ b/python/plugins/processing/algs/saga/description/FlowAccumulation(Top-Down).txt @@ -2,4 +2,4 @@ Flow Accumulation (Top-Down) ta_hydrology ParameterRaster|ELEVATION|Elevation|False ParameterSelection|METHOD|Method|[0] Deterministic 8;[1] Rho 8;[2] Braunschweiger Reliefmodell;[3] Deterministic Infinity;[4] Multiple Flow Direction;[5] Multiple Triangular Flow Directon -OutputRaster|CAREA|Catchment Area +OutputRaster|FLOW|Catchment Area From 67f7b3a2df21a8d58662aefafb6126b04cdfd652 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Thu, 20 Jul 2017 12:20:25 +0200 Subject: [PATCH 097/266] crash report dialog: * get current context without an exception (eg. Ctrl+\) * use
 around stack * make report details
 expandable * what about threads?

---
 src/app/qgscrashdialog.cpp | 1 +
 src/app/qgscrashreport.cpp | 4 ++++
 src/core/qgsstacktrace.cpp | 8 ++++++--
 src/ui/qgscrashdialog.ui   | 4 ++--
 4 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/app/qgscrashdialog.cpp b/src/app/qgscrashdialog.cpp
index 1a9bcd8f10b..37a5b62fbf7 100644
--- a/src/app/qgscrashdialog.cpp
+++ b/src/app/qgscrashdialog.cpp
@@ -66,6 +66,7 @@ QString QgsCrashDialog::htmlToMarkdown( const QString &html )
   markdown.replace( "
", "\n" ); markdown.replace( "", "*" ); markdown.replace( "", "*" ); + markdown.replace( "QGIS code revision: ", "QGIS code revision: commit:"); return markdown; } diff --git a/src/app/qgscrashreport.cpp b/src/app/qgscrashreport.cpp index be2dc133383..c1a8a55f7b0 100644 --- a/src/app/qgscrashreport.cpp +++ b/src/app/qgscrashreport.cpp @@ -48,15 +48,18 @@ const QString QgsCrashReport::toHtml() const } else { + reportData.append( "
" );
       Q_FOREACH ( const QgsStackTrace::StackLine &line, mStackTrace )
       {
         QFileInfo fileInfo( line.fileName );
         QString filename( fileInfo.fileName() );
         reportData.append( QString( "(%1) %2 %3:%4" ).arg( line.moduleName, line.symbolName, filename, line.lineNumber ) );
       }
+      reportData.append( "
" ); } } +#if 0 if ( flags().testFlag( QgsCrashReport::Plugins ) ) { reportData.append( "
" ); @@ -70,6 +73,7 @@ const QString QgsCrashReport::toHtml() const reportData.append( "Project Info" ); // TODO Get project details } +#endif if ( flags().testFlag( QgsCrashReport::QgisInfo ) ) { diff --git a/src/core/qgsstacktrace.cpp b/src/core/qgsstacktrace.cpp index bd1bab30174..4b2da27ee89 100644 --- a/src/core/qgsstacktrace.cpp +++ b/src/core/qgsstacktrace.cpp @@ -53,7 +53,12 @@ QVector QgsStackTrace::trace( _EXCEPTION_POINTERS *Exc // StackWalk64() may modify context record passed to it, so we will // use a copy. - CONTEXT context_record = *ExceptionInfo->ContextRecord; + CONTEXT context_record; + if (ExceptionInfo) + context_record = *ExceptionInfo->ContextRecord; + else + RtlCaptureContext(&context_record); + // Initialize stack walking. STACKFRAME64 stack_frame; memset( &stack_frame, 0, sizeof( stack_frame ) ); @@ -133,7 +138,6 @@ QVector QgsStackTrace::trace( _EXCEPTION_POINTERS *Exc qgsFree( module ); SymCleanup( process ); return stack; - } QString QgsStackTrace::mSymbolPaths; diff --git a/src/ui/qgscrashdialog.ui b/src/ui/qgscrashdialog.ui index ae2558e7f09..f5d925196f5 100644 --- a/src/ui/qgscrashdialog.ui +++ b/src/ui/qgscrashdialog.ui @@ -536,14 +536,14 @@ - + Qt::Vertical - 20 + 0 0 From f78157be7dd0eef977df7168359ca600c1d9a88a Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Thu, 20 Jul 2017 16:16:16 +0200 Subject: [PATCH 098/266] fix 67f7b3a2 --- src/app/qgscrashdialog.cpp | 2 +- src/core/qgsstacktrace.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/qgscrashdialog.cpp b/src/app/qgscrashdialog.cpp index 37a5b62fbf7..7a4fa221ec2 100644 --- a/src/app/qgscrashdialog.cpp +++ b/src/app/qgscrashdialog.cpp @@ -66,7 +66,7 @@ QString QgsCrashDialog::htmlToMarkdown( const QString &html ) markdown.replace( "
", "\n" ); markdown.replace( "", "*" ); markdown.replace( "", "*" ); - markdown.replace( "QGIS code revision: ", "QGIS code revision: commit:"); + markdown.replace( "QGIS code revision: ", "QGIS code revision: commit:" ); return markdown; } diff --git a/src/core/qgsstacktrace.cpp b/src/core/qgsstacktrace.cpp index 4b2da27ee89..0b7c9fce44b 100644 --- a/src/core/qgsstacktrace.cpp +++ b/src/core/qgsstacktrace.cpp @@ -54,10 +54,10 @@ QVector QgsStackTrace::trace( _EXCEPTION_POINTERS *Exc // StackWalk64() may modify context record passed to it, so we will // use a copy. CONTEXT context_record; - if (ExceptionInfo) + if ( ExceptionInfo ) context_record = *ExceptionInfo->ContextRecord; else - RtlCaptureContext(&context_record); + RtlCaptureContext( &context_record ); // Initialize stack walking. STACKFRAME64 stack_frame; From e7744bdcd4708b3d61376ce9aab5ee6a8fe3656a Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Thu, 20 Jul 2017 19:05:30 +0200 Subject: [PATCH 099/266] [FEATURE] fill ring tool: by pressing shift an existing ring is filled --- src/app/qgsmaptoolfillring.cpp | 180 ++++++++++++++++++++++----------- src/app/qgsmaptoolfillring.h | 7 ++ 2 files changed, 127 insertions(+), 60 deletions(-) diff --git a/src/app/qgsmaptoolfillring.cpp b/src/app/qgsmaptoolfillring.cpp index 024f33c5c50..3c14caca0f1 100644 --- a/src/app/qgsmaptoolfillring.cpp +++ b/src/app/qgsmaptoolfillring.cpp @@ -22,8 +22,8 @@ #include "qgsattributedialog.h" #include "qgisapp.h" #include "qgsvectorlayerutils.h" -#include +#include #include QgsMapToolFillRing::QgsMapToolFillRing( QgsMapCanvas *canvas ) @@ -52,38 +52,49 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) return; } - //add point to list and to rubber band - if ( e->button() == Qt::LeftButton ) + if ( e->button() == Qt::LeftButton && QApplication::keyboardModifiers() == Qt::ShiftModifier && !isCapturing() ) { + // left button with shift fills an existing ring + } + else if ( e->button() == Qt::LeftButton ) + { + // add point to list and to rubber band + int error = addVertex( e->mapPoint() ); if ( error == 1 ) { - //current layer is not a vector layer + // current layer is not a vector layer return; } else if ( error == 2 ) { - //problem with coordinate transformation + // problem with coordinate transformation emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), QgsMessageBar::WARNING ); return; } startCapturing(); + return; } - else if ( e->button() == Qt::RightButton ) + else if ( e->button() != Qt::RightButton || !isCapturing() ) { - if ( !isCapturing() ) - return; + return; + } + QgsGeometry g; + QgsFeatureId fid; + + if ( isCapturing() ) + { deleteTempRubberBand(); closePolygon(); vlayer->beginEditCommand( tr( "Ring added and filled" ) ); + QList< QgsPointXY > pointList = points(); - QgsFeatureId modifiedFid; - int addRingReturnCode = vlayer->addRing( pointList, &modifiedFid ); + int addRingReturnCode = vlayer->addRing( pointList, &fid ); if ( addRingReturnCode != 0 ) { QString errorMessage; @@ -114,65 +125,114 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) } emit messageEmitted( tr( "could not add ring since %1." ).arg( errorMessage ), QgsMessageBar::CRITICAL ); vlayer->destroyEditCommand(); + + return; + } + + g = QgsGeometry::fromPolygon( QgsPolygon() << pointList.toVector() ); + } + else + { + vlayer->beginEditCommand( tr( "Ring filled" ) ); + + g = ringUnderPoint( e->mapPoint(), fid ); + + if ( fid == -1 ) + { + emit messageEmitted( tr( "No ring found to fill." ), QgsMessageBar::CRITICAL ); + vlayer->destroyEditCommand(); + return; + } + } + + QgsRectangle bBox = g.boundingBox(); + + QgsExpressionContext context = vlayer->createExpressionContext(); + + QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ); + + QgsFeature f; + if ( fit.nextFeature( f ) ) + { + //create QgsFeature with wkb representation + QgsFeature ft = QgsVectorLayerUtils::createFeature( vlayer, g, f.attributes().toMap(), &context ); + + bool res = false; + if ( QApplication::keyboardModifiers() == Qt::ControlModifier ) + { + res = vlayer->addFeature( ft ); } else { - // find parent feature and get it attributes - double xMin, xMax, yMin, yMax; - QgsRectangle bBox; + QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, &ft, false, nullptr, true ); + dialog->setMode( QgsAttributeForm::AddFeatureMode ); + res = dialog->exec(); // will also add the feature + } - xMin = std::numeric_limits::max(); - xMax = -std::numeric_limits::max(); - yMin = std::numeric_limits::max(); - yMax = -std::numeric_limits::max(); + if ( res ) + { + vlayer->endEditCommand(); + } + else + { + vlayer->destroyEditCommand(); + } + } - Q_FOREACH ( const QgsPointXY &point, pointList ) + if ( isCapturing() ) + stopCapturing(); +} + +// TODO refactor - shamelessly copied from QgsMapToolDeleteRing::ringUnderPoint +QgsGeometry QgsMapToolFillRing::ringUnderPoint( const QgsPointXY &p, QgsFeatureId &fid ) +{ + //check if we operate on a vector layer + QgsVectorLayer *vlayer = qobject_cast( mCanvas->currentLayer() ); + + //There is no clean way to find if we are inside the ring of a feature, + //so we iterate over all the features visible in the canvas + //If several rings are found at this position, the smallest one is chosen, + //in order to be able to delete a ring inside another ring + double area = std::numeric_limits::max(); + + QgsGeometry ringGeom; + QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( toLayerCoordinates( vlayer, mCanvas->extent() ) ) ); + + QgsFeature f; + while ( fit.nextFeature( f ) ) + { + QgsGeometry g = f.geometry(); + if ( g.isNull() ) + continue; + + QgsMultiPolygon pol; + if ( g.wkbType() == QgsWkbTypes::Polygon || g.wkbType() == QgsWkbTypes::Polygon25D ) + { + pol = QgsMultiPolygon() << g.asPolygon(); + } + else + { + pol = g.asMultiPolygon(); + } + + for ( int i = 0; i < pol.size() ; ++i ) + { + //for each part + if ( pol[i].size() > 1 ) { - xMin = qMin( xMin, point.x() ); - xMax = qMax( xMax, point.x() ); - yMin = qMin( yMin, point.y() ); - yMax = qMax( yMax, point.y() ); - } - - bBox.setXMinimum( xMin ); - bBox.setYMinimum( yMin ); - bBox.setXMaximum( xMax ); - bBox.setYMaximum( yMax ); - - QgsExpressionContext context = vlayer->createExpressionContext(); - - QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterFid( modifiedFid ) ); - - QgsFeature f; - if ( fit.nextFeature( f ) ) - { - QgsGeometry g = QgsGeometry::fromPolygon( QgsPolygon() << pointList.toVector() ); - - //create QgsFeature with wkb representation - QgsFeature ft = QgsVectorLayerUtils::createFeature( vlayer, g, f.attributes().toMap(), &context ); - - bool res = false; - if ( QApplication::keyboardModifiers() == Qt::ControlModifier ) + for ( int j = 1; j < pol[i].size(); ++j ) { - res = vlayer->addFeature( ft ); - } - else - { - QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, &ft, false, nullptr, true ); - dialog->setMode( QgsAttributeForm::AddFeatureMode ); - res = dialog->exec(); // will also add the feature - } - - if ( res ) - { - vlayer->endEditCommand(); - } - else - { - vlayer->destroyEditCommand(); + QgsPolygon tempPol = QgsPolygon() << pol[i][j]; + QgsGeometry tempGeom = QgsGeometry::fromPolygon( tempPol ); + if ( tempGeom.area() < area && tempGeom.contains( &p ) ) + { + fid = f.id(); + area = tempGeom.area(); + ringGeom = tempGeom; + } } } } - stopCapturing(); } + return ringGeom; } diff --git a/src/app/qgsmaptoolfillring.h b/src/app/qgsmaptoolfillring.h index 4bb5f3435a0..a4cc066ab79 100644 --- a/src/app/qgsmaptoolfillring.h +++ b/src/app/qgsmaptoolfillring.h @@ -27,4 +27,11 @@ class APP_EXPORT QgsMapToolFillRing: public QgsMapToolCapture QgsMapToolFillRing( QgsMapCanvas *canvas ); virtual ~QgsMapToolFillRing(); void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) override; + + private: + + /** Return the geometry of the ring under the point p and sets fid to the feature id + */ + QgsGeometry ringUnderPoint( const QgsPointXY &p, QgsFeatureId &fid ); + }; From aa65af5e1abe89322803d35c93491cd8db698ab8 Mon Sep 17 00:00:00 2001 From: tcoupin Date: Thu, 20 Jul 2017 22:42:23 +0200 Subject: [PATCH 100/266] Fix srsDimension parsing in GML --- src/core/qgsgml.cpp | 17 +++++++++++++---- src/core/qgsgml.h | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/core/qgsgml.cpp b/src/core/qgsgml.cpp index 21292d76c01..30ffb698aa9 100644 --- a/src/core/qgsgml.cpp +++ b/src/core/qgsgml.cpp @@ -480,6 +480,7 @@ void QgsGmlStreamingParser::startElement( const XML_Char *el, const XML_Char **a const int nsLen = ( pszSep ) ? ( int )( pszSep - el ) : 0; const int localNameLen = ( pszSep ) ? ( int )( elLen - nsLen ) - 1 : elLen; ParseMode parseMode( mParseModeStack.isEmpty() ? None : mParseModeStack.top() ); + int elDimension = 0; // Figure out if the GML namespace is GML_NAMESPACE or GML32_NAMESPACE if ( !mGMLNameSpaceURIPtr && pszSep ) @@ -538,14 +539,14 @@ void QgsGmlStreamingParser::startElement( const XML_Char *el, const XML_Char **a mParseModeStack.push( QgsGmlStreamingParser::PosList ); mCoorMode = QgsGmlStreamingParser::PosList; mStringCash.clear(); - if ( mDimension == 0 ) + if ( elDimension == 0 ) { QString srsDimension = readAttribute( QStringLiteral( "srsDimension" ), attr ); bool ok; int dimension = srsDimension.toInt( &ok ); if ( ok ) { - mDimension = dimension; + elDimension = dimension; } } } @@ -803,7 +804,7 @@ void QgsGmlStreamingParser::startElement( const XML_Char *el, const XML_Char **a if ( !mGeometryString.empty() ) isGeom = true; - if ( mDimension == 0 && isGeom ) + if ( elDimension == 0 && isGeom ) { // srsDimension can also be set on the top geometry element // e.g. https://data.linz.govt.nz/services;key=XXXXXXXX/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=data.linz.govt.nz:layer-524 @@ -812,10 +813,16 @@ void QgsGmlStreamingParser::startElement( const XML_Char *el, const XML_Char **a int dimension = srsDimension.toInt( &ok ); if ( ok ) { - mDimension = dimension; + elDimension = dimension; } } + if ( elDimension != 0 ) + { + mDimension = elDimension; + } + mDimensionStack.push( mDimension ); + if ( mEpsg == 0 && isGeom ) { if ( readEpsgFromAttribute( mEpsg, attr ) != 0 ) @@ -842,6 +849,8 @@ void QgsGmlStreamingParser::endElement( const XML_Char *el ) const int localNameLen = ( pszSep ) ? ( int )( elLen - nsLen ) - 1 : elLen; ParseMode parseMode( mParseModeStack.isEmpty() ? None : mParseModeStack.top() ); + mDimension = mDimensionStack.isEmpty() ? 0 : mDimensionStack.top() ; + const bool isGMLNS = ( nsLen == mGMLNameSpaceURI.size() && mGMLNameSpaceURIPtr && memcmp( el, mGMLNameSpaceURIPtr, nsLen ) == 0 ); if ( parseMode == Coordinate && isGMLNS && LOCALNAME_EQUALS( "coordinates" ) ) diff --git a/src/core/qgsgml.h b/src/core/qgsgml.h index 0e43ecea5b5..6b4d0821184 100644 --- a/src/core/qgsgml.h +++ b/src/core/qgsgml.h @@ -298,7 +298,9 @@ class CORE_EXPORT QgsGmlStreamingParser QString mCoordinateSeparator; //! Tuple separator for coordinate strings. Usually " " QString mTupleSeparator; - //! Number of dimensions in pos or posList + //! Keep track about number of dimensions in pos or posList + QStack mDimensionStack; + //! Number of dimensions in pos or posList for the current geometry int mDimension; //! Coordinates mode, coordinate or posList ParseMode mCoorMode; From 74407dc6976bc63e826adf6e5a130598f80823e6 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Fri, 21 Jul 2017 00:02:37 +0200 Subject: [PATCH 101/266] crash report: fix report on 64bit --- src/app/main.cpp | 20 ++++++++++----- src/app/qgsmaptoolfillring.cpp | 2 -- src/core/qgsstacktrace.cpp | 46 ++++++++++++++++------------------ 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index a092b701117..50f5e649d38 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -471,12 +471,6 @@ int main( int argc, char *argv[] ) #endif #ifdef Q_OS_WIN - if ( !QgsApplication::isRunningFromBuildDir() ) - { - QString symbolPath( getenv( "QGIS_PREFIX_PATH" ) ); - symbolPath = symbolPath + "\\pdb;http://msdl.microsoft.com/download/symbols;http://download.osgeo.org/osgeo4w/symstore"; - QgsStackTrace::setSymbolPath( symbolPath ); - } SetUnhandledExceptionFilter( QgsCrashHandler::handle ); #endif @@ -800,6 +794,20 @@ int main( int argc, char *argv[] ) myApp.setWindowIcon( QIcon( QgsApplication::appIconPath() ) ); #endif +#ifdef Q_OS_WIN + if ( !QgsApplication::isRunningFromBuildDir() ) + { + QString symbolPath( getenv( "QGIS_PREFIX_PATH" ) ); + symbolPath = symbolPath + "\\pdb;http://msdl.microsoft.com/download/symbols;http://download.osgeo.org/osgeo4w/symstore"; + QgsStackTrace::setSymbolPath( symbolPath ); + } + else + { + QString symbolPath( getenv( "QGIS_PDB_PATH" ) ); + symbolPath = symbolPath + ";http://msdl.microsoft.com/download/symbols;http://download.osgeo.org/osgeo4w/symstore"; + QgsStackTrace::setSymbolPath( symbolPath ); + } +#endif // // Set up the QSettings environment must be done after qapp is created diff --git a/src/app/qgsmaptoolfillring.cpp b/src/app/qgsmaptoolfillring.cpp index 3c14caca0f1..ce4825efbc6 100644 --- a/src/app/qgsmaptoolfillring.cpp +++ b/src/app/qgsmaptoolfillring.cpp @@ -145,8 +145,6 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) } } - QgsRectangle bBox = g.boundingBox(); - QgsExpressionContext context = vlayer->createExpressionContext(); QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ); diff --git a/src/core/qgsstacktrace.cpp b/src/core/qgsstacktrace.cpp index 0b7c9fce44b..a0bafc8315c 100644 --- a/src/core/qgsstacktrace.cpp +++ b/src/core/qgsstacktrace.cpp @@ -103,32 +103,30 @@ QVector QgsStackTrace::trace( _EXCEPTION_POINTERS *Exc if ( SymFromAddr( process, ( DWORD64 )stack_frame.AddrPC.Offset, &displacement, symbol ) ) { DWORD dwDisplacement; - QString fileName; - QString lineNumber; - QString moduleName; - if ( SymGetLineFromAddr( process, ( DWORD )( stack_frame.AddrPC.Offset ), &dwDisplacement, line ) ) - { - fileName = QString( line->FileName ); - lineNumber = QString::number( line->LineNumber ); - } - else - { - fileName = "(unknown file)"; - lineNumber = "(unknown line)"; - } - if ( SymGetModuleInfo( process, ( DWORD )( stack_frame.AddrPC.Offset ), module ) ) - { - moduleName = QString( module->ModuleName ); - } - else - { - moduleName = "(unknown module)"; - } + QgsStackTrace::StackLine stackline; - stackline.moduleName = moduleName; - stackline.fileName = fileName; - stackline.lineNumber = lineNumber; stackline.symbolName = QString( symbol->Name ); + + if ( SymGetLineFromAddr( process, ( DWORD64 ) stack_frame.AddrPC.Offset, &dwDisplacement, line ) ) + { + stackline.fileName = QString( line->FileName ); + stackline.lineNumber = QString::number( line->LineNumber ); + } + else + { + stackline.fileName = "(unknown file)"; + stackline.lineNumber = "(unknown line)"; + } + + if ( SymGetModuleInfo( process, ( DWORD64 ) stack_frame.AddrPC.Offset, module ) ) + { + stackline.moduleName = module->ModuleName; + } + else + { + stackline.moduleName = "(unknown module)"; + } + stack.append( stackline ); } } From 9ab4184af260f9f8025172da993db2dfd7dc39a1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 08:55:17 +1000 Subject: [PATCH 102/266] Fix memory leak in canvas preview images --- src/gui/qgsmapcanvas.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index e4258e25a4e..38fbbca0422 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -648,6 +648,8 @@ void QgsMapCanvas::previewJobFinished() if ( mMap ) { mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() ); + mPreviewJobs.removeAll( job ); + delete job; } } From 2f0c9d955b1ea497ea14df4e55bf3d640991302d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 11:09:49 +1000 Subject: [PATCH 103/266] Followup 5241c74, fix calculation of heatmaps --- src/analysis/raster/qgskde.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/analysis/raster/qgskde.cpp b/src/analysis/raster/qgskde.cpp index 8d0b45393aa..21c9a2abb63 100644 --- a/src/analysis/raster/qgskde.cpp +++ b/src/analysis/raster/qgskde.cpp @@ -163,10 +163,12 @@ QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::addFeature( const // calculate the pixel position unsigned int xPosition = ( ( ( *pointIt ).x() - mBounds.xMinimum() ) / mPixelSize ) - buffer; unsigned int yPosition = ( ( ( *pointIt ).y() - mBounds.yMinimum() ) / mPixelSize ) - buffer; + unsigned int yPositionIO = ( ( mBounds.yMaximum() - ( *pointIt ).y() ) / mPixelSize ) - buffer; + // get the data float *dataBuffer = ( float * ) CPLMalloc( sizeof( float ) * blockSize * blockSize ); - if ( GDALRasterIO( mRasterBandH, GF_Read, xPosition, yPosition, blockSize, blockSize, + if ( GDALRasterIO( mRasterBandH, GF_Read, xPosition, yPositionIO, blockSize, blockSize, dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None ) { result = RasterIoError; @@ -196,7 +198,7 @@ QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::addFeature( const dataBuffer[ pos ] += pixelValue; } } - if ( GDALRasterIO( mRasterBandH, GF_Write, xPosition, yPosition, blockSize, blockSize, + if ( GDALRasterIO( mRasterBandH, GF_Write, xPosition, yPositionIO, blockSize, blockSize, dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None ) { result = RasterIoError; From eb5ac44b27ed46c2bdacfbaaf3cf435a77788e92 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 11:28:43 +1000 Subject: [PATCH 104/266] Update test --- .../plugins/processing/tests/testdata/qgis_algorithm_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 7e4b04a9916..2931ff75d96 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2699,5 +2699,5 @@ tests: RADIUS: 0.5 results: OUTPUT: - hash: a594d4fa994b81641bfadde54447dc548fa6e23f630c6a4c383487c5 + hash: f09384c64f56286ec4146a7b9a679cea7c6711ec4c7d77eec054e364 type: rasterhash From 38c82684009ff62ac501b0b7de0cc9542ef68cdb Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 14:14:06 +1000 Subject: [PATCH 105/266] Allow setting width/height spin boxes to link to QgsRatioLockButton When set, these spin boxes will automatically be updated when their accompanying spin box changes value so that the ratio is maintained. --- python/gui/qgsratiolockbutton.sip | 25 +++++ src/gui/qgsratiolockbutton.cpp | 47 +++++++++ src/gui/qgsratiolockbutton.h | 36 +++++++ tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_qgsratiolockbutton.py | 104 ++++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 tests/src/python/test_qgsratiolockbutton.py diff --git a/python/gui/qgsratiolockbutton.sip b/python/gui/qgsratiolockbutton.sip index 22c1daa9054..e73af56986d 100644 --- a/python/gui/qgsratiolockbutton.sip +++ b/python/gui/qgsratiolockbutton.sip @@ -9,6 +9,7 @@ + class QgsRatioLockButton : QToolButton { %Docstring @@ -42,6 +43,30 @@ class QgsRatioLockButton : QToolButton :rtype: bool %End + void setWidthSpinBox( QDoubleSpinBox *widget ); +%Docstring + Registers a spin box ``widget`` as the linked "width" spin box. + + If both a width and height spin box are linked to the button, they will automatically + have their values updates when if the other spin box value is changed. I.e. changing the + width spin box will automatically update the height spin box to a value which keeps the + same locked ratio. + +.. seealso:: setHeightSpinBox() +%End + + void setHeightSpinBox( QDoubleSpinBox *widget ); +%Docstring + Registers a spin box ``widget`` as the linked "height" spin box. + + If both a width and height spin box are linked to the button, they will automatically + have their values updates when if the other spin box value is changed. I.e. changing the + width spin box will automatically update the height spin box to a value which keeps the + same locked ratio. + +.. seealso:: setWidthSpinBox() +%End + signals: void lockChanged( const bool locked ); diff --git a/src/gui/qgsratiolockbutton.cpp b/src/gui/qgsratiolockbutton.cpp index 98f9b1a1431..e13a9bab15d 100644 --- a/src/gui/qgsratiolockbutton.cpp +++ b/src/gui/qgsratiolockbutton.cpp @@ -20,6 +20,7 @@ #include #include #include +#include QgsRatioLockButton::QgsRatioLockButton( QWidget *parent ) : QToolButton( parent ) @@ -48,6 +49,38 @@ void QgsRatioLockButton::buttonClicked() drawButton(); } +void QgsRatioLockButton::widthSpinBoxChanged( double value ) +{ + if ( mUpdatingRatio || qgsDoubleNear( value, 0.0 ) || qgsDoubleNear( mPrevWidth, 0.0 ) + || qgsDoubleNear( mPrevHeight, 0.0 ) || !mHeightSpinBox || !mLocked ) + { + mPrevWidth = value; + return; + } + + double oldRatio = mPrevHeight / mPrevWidth; + mUpdatingRatio = true; + mHeightSpinBox->setValue( oldRatio * value ); + mUpdatingRatio = false; + mPrevWidth = value; +} + +void QgsRatioLockButton::heightSpinBoxChanged( double value ) +{ + if ( mUpdatingRatio || qgsDoubleNear( value, 0.0 ) || qgsDoubleNear( mPrevWidth, 0.0 ) + || qgsDoubleNear( mPrevHeight, 0.0 ) || !mWidthSpinBox || !mLocked ) + { + mPrevHeight = value; + return; + } + + double oldRatio = mPrevWidth / mPrevHeight; + mUpdatingRatio = true; + mWidthSpinBox->setValue( oldRatio * value ); + mUpdatingRatio = false; + mPrevHeight = value; +} + void QgsRatioLockButton::changeEvent( QEvent *e ) { if ( e->type() == QEvent::EnabledChange ) @@ -108,3 +141,17 @@ void QgsRatioLockButton::drawButton() setIconSize( currentIconSize ); setIcon( pm ); } + +void QgsRatioLockButton::setWidthSpinBox( QDoubleSpinBox *widget ) +{ + mWidthSpinBox = widget; + mPrevWidth = widget->value(); + connect( mWidthSpinBox, static_cast( &QDoubleSpinBox::valueChanged ), this, &QgsRatioLockButton::widthSpinBoxChanged ); +} + +void QgsRatioLockButton::setHeightSpinBox( QDoubleSpinBox *widget ) +{ + mHeightSpinBox = widget; + mPrevHeight = widget->value(); + connect( mHeightSpinBox, static_cast( &QDoubleSpinBox::valueChanged ), this, &QgsRatioLockButton::heightSpinBoxChanged ); +} diff --git a/src/gui/qgsratiolockbutton.h b/src/gui/qgsratiolockbutton.h index a49ca88a92d..96a8e676a3b 100644 --- a/src/gui/qgsratiolockbutton.h +++ b/src/gui/qgsratiolockbutton.h @@ -22,6 +22,9 @@ #include "qgis_gui.h" #include "qgis.h" +#include +class QDoubleSpinBox; + /** \ingroup gui * \class QgsRatioLockButton * A cross platform button subclass used to represent a locked / unlocked ratio state. @@ -51,6 +54,30 @@ class GUI_EXPORT QgsRatioLockButton : public QToolButton */ bool locked() const { return mLocked; } + /** + * Registers a spin box \a widget as the linked "width" spin box. + * + * If both a width and height spin box are linked to the button, they will automatically + * have their values updates when if the other spin box value is changed. I.e. changing the + * width spin box will automatically update the height spin box to a value which keeps the + * same locked ratio. + * + * \see setHeightSpinBox() + */ + void setWidthSpinBox( QDoubleSpinBox *widget ); + + /** + * Registers a spin box \a widget as the linked "height" spin box. + * + * If both a width and height spin box are linked to the button, they will automatically + * have their values updates when if the other spin box value is changed. I.e. changing the + * width spin box will automatically update the height spin box to a value which keeps the + * same locked ratio. + * + * \see setWidthSpinBox() + */ + void setHeightSpinBox( QDoubleSpinBox *widget ); + signals: /** Emitted whenever the lock state changes. @@ -69,10 +96,19 @@ class GUI_EXPORT QgsRatioLockButton : public QToolButton bool mLocked = false; + QPointer< QDoubleSpinBox > mWidthSpinBox; + double mPrevWidth = 0; + QPointer< QDoubleSpinBox > mHeightSpinBox; + double mPrevHeight = 0; + bool mUpdatingRatio = false; + private slots: void buttonClicked(); + void widthSpinBoxChanged( double value ); + void heightSpinBoxChanged( double value ); + }; #endif diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 52602906698..d1cd5bd701f 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -115,6 +115,7 @@ ADD_PYTHON_TEST(PyQgsRasterFileWriter test_qgsrasterfilewriter.py) ADD_PYTHON_TEST(PyQgsRasterFileWriterTask test_qgsrasterfilewritertask.py) ADD_PYTHON_TEST(PyQgsRasterLayer test_qgsrasterlayer.py) ADD_PYTHON_TEST(PyQgsRasterColorRampShader test_qgsrastercolorrampshader.py) +ADD_PYTHON_TEST(PyQgsRatioLockButton test_qgsratiolockbutton.py) ADD_PYTHON_TEST(PyQgsRectangle test_qgsrectangle.py) ADD_PYTHON_TEST(PyQgsRelation test_qgsrelation.py) ADD_PYTHON_TEST(PyQgsRelationManager test_qgsrelationmanager.py) diff --git a/tests/src/python/test_qgsratiolockbutton.py b/tests/src/python/test_qgsratiolockbutton.py new file mode 100644 index 00000000000..64514b3b17a --- /dev/null +++ b/tests/src/python/test_qgsratiolockbutton.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsRatioLockButton + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '18/07/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.gui import QgsRatioLockButton + +from qgis.PyQt.QtWidgets import QDoubleSpinBox + +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsRatioLockButton(unittest.TestCase): + + def testLinkedWidgets(self): + """ test linking spin boxes to combobox""" + w = qgis.gui.QgsRatioLockButton() + + spin_width = QDoubleSpinBox() + spin_width.setMaximum(100000) + spin_height = QDoubleSpinBox() + spin_height.setMaximum(100000) + + w.setWidthSpinBox(spin_width) + spin_width.setValue(1000) + self.assertEqual(spin_width.value(), 1000) + + w.setLocked(True) + spin_width.setValue(2000) + self.assertEqual(spin_width.value(), 2000) + w.setLocked(False) + + w.setHeightSpinBox(spin_height) + spin_width.setValue(1000) + self.assertEqual(spin_width.value(), 1000) + self.assertEqual(spin_height.value(), 0) + + w.setLocked(True) + spin_width.setValue(2000) + self.assertEqual(spin_width.value(), 2000) + self.assertEqual(spin_height.value(), 0) + + spin_height.setValue(1000) + self.assertEqual(spin_width.value(), 2000) + self.assertEqual(spin_height.value(), 1000) + + # ok, that was all setup tests... let's check the real thing now + spin_width.setValue(1000) + self.assertEqual(spin_width.value(), 1000) + self.assertEqual(spin_height.value(), 500) + spin_height.setValue(1000) + self.assertEqual(spin_width.value(), 2000) + self.assertEqual(spin_height.value(), 1000) + + w.setLocked(False) + spin_width.setValue(1000) + self.assertEqual(spin_width.value(), 1000) + self.assertEqual(spin_height.value(), 1000) + spin_height.setValue(2000) + self.assertEqual(spin_width.value(), 1000) + self.assertEqual(spin_height.value(), 2000) + + w.setLocked(True) + spin_height.setValue(1000) + self.assertEqual(spin_width.value(), 500) + self.assertEqual(spin_height.value(), 1000) + + # setting to 0 should "break" lock + spin_height.setValue(0) + self.assertEqual(spin_width.value(), 500) + self.assertEqual(spin_height.value(), 0) + spin_width.setValue(1000) + self.assertEqual(spin_width.value(), 1000) + self.assertEqual(spin_height.value(), 0) + spin_height.setValue(100) + self.assertEqual(spin_width.value(), 1000) + self.assertEqual(spin_height.value(), 100) + + spin_width.setValue(0) + self.assertEqual(spin_width.value(), 0) + self.assertEqual(spin_height.value(), 100) + spin_height.setValue(1000) + self.assertEqual(spin_width.value(), 0) + self.assertEqual(spin_height.value(), 1000) + spin_width.setValue(200) + self.assertEqual(spin_width.value(), 200) + self.assertEqual(spin_height.value(), 1000) + + +if __name__ == '__main__': + unittest.main() From 00ac9f51edef12efda01f5643ada48047819cfd7 Mon Sep 17 00:00:00 2001 From: nirvn Date: Fri, 21 Jul 2017 12:21:51 +0700 Subject: [PATCH 106/266] fix expanding ratio lock button under kbuntu qt theme --- src/gui/qgsratiolockbutton.cpp | 1 + src/ui/qgsmapsavedialog.ui | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/gui/qgsratiolockbutton.cpp b/src/gui/qgsratiolockbutton.cpp index e13a9bab15d..1f39f92ab73 100644 --- a/src/gui/qgsratiolockbutton.cpp +++ b/src/gui/qgsratiolockbutton.cpp @@ -28,6 +28,7 @@ QgsRatioLockButton::QgsRatioLockButton( QWidget *parent ) { setMinimumSize( QSize( 24, 24 ) ); + setMaximumWidth( fontMetrics().width( "000" ) ); setCheckable( true ); setAutoRaise( true ); connect( this, &QPushButton::clicked, this, &QgsRatioLockButton::buttonClicked ); diff --git a/src/ui/qgsmapsavedialog.ui b/src/ui/qgsmapsavedialog.ui index bd0bc3adcc3..2855832e87d 100644 --- a/src/ui/qgsmapsavedialog.ui +++ b/src/ui/qgsmapsavedialog.ui @@ -94,6 +94,12 @@ Rasterizing the map is recommended when such effects are used. + + + 0 + 0 + + px From 78ee9f7e7d625078cf7311e2241e3ab0d137a2c8 Mon Sep 17 00:00:00 2001 From: nirvn Date: Fri, 21 Jul 2017 13:03:21 +0700 Subject: [PATCH 107/266] fix locked ratio width adjustment in save as image/PDF --- src/app/qgsmapsavedialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index a0b5f390b22..8d4c789bea5 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -164,7 +164,7 @@ void QgsMapSaveDialog::updateOutputHeight( int height ) double scale = ( double )width / mSize.width(); double adjustment = ( ( mExtent.width() * scale ) - mExtent.width() ) / 2; - whileBlocking( mOutputWidthSpinBox )->setValue( height ); + whileBlocking( mOutputWidthSpinBox )->setValue( width ); mSize.setWidth( width ); mExtent.setXMinimum( mExtent.xMinimum() - adjustment ); From 0cba29cc09b8a1b8789c83fdfc72a118c35e3eea Mon Sep 17 00:00:00 2001 From: nirvn Date: Fri, 21 Jul 2017 13:31:05 +0700 Subject: [PATCH 108/266] improve extent and scale/dpi relation in save as image/PDF --- src/app/qgsmapsavedialog.cpp | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index 8d4c789bea5..dde9607fb43 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -54,7 +54,7 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QL mDpi = ms.outputDpi(); mSize = ms.outputSize(); - mResolutionSpinBox->setValue( qt_defaultDpiX() ); + mResolutionSpinBox->setValue( mDpi ); mExtentGroupBox->setOutputCrs( ms.destinationCrs() ); mExtentGroupBox->setCurrentExtent( mExtent, ms.destinationCrs() ); @@ -176,16 +176,39 @@ void QgsMapSaveDialog::updateOutputHeight( int height ) void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent ) { - mSize.setWidth( mSize.width() * extent.width() / mExtent.width() ); - mSize.setHeight( mSize.height() * extent.height() / mExtent.height() ); - mExtent = extent; + int currentDpi = 0; + // reset scale to properly sync output width and height when extent set using + // current map view, layer extent, or drawn on canvas buttons + if ( mExtentGroupBox->extentState() != QgsExtentGroupBox::UserExtent ) + { + currentDpi = mDpi; + + QgsMapSettings ms = mMapCanvas->mapSettings(); + ms.setRotation( 0 ); + mDpi = ms.outputDpi(); + mSize.setWidth( ms.outputSize().width() * extent.width() / ms.visibleExtent().width() ); + mSize.setHeight( ms.outputSize().height() * extent.height() / ms.visibleExtent().height() ); + + whileBlocking( mScaleWidget )->setScale( ms.scale() ); + + if ( currentDpi != mDpi ) + { + updateDpi( currentDpi ); + } + } + else + { + mSize.setWidth( mSize.width() * extent.width() / mExtent.width() ); + mSize.setHeight( mSize.height() * extent.height() / mExtent.height() ); + } + updateOutputSize(); + + mExtent = extent; if ( mLockAspectRatio->locked() ) { mExtentGroupBox->setRatio( QSize( mSize.width(), mSize.height() ) ); } - - updateOutputSize(); } void QgsMapSaveDialog::updateScale( double scale ) From dfc9285121cc9e0e896293498a2129bd8f1a548a Mon Sep 17 00:00:00 2001 From: Giovanni Manghi Date: Fri, 21 Jul 2017 14:42:14 +0100 Subject: [PATCH 109/266] master: fix SAGA LTR mosaiking tool --- python/plugins/processing/algs/saga/SagaAlgorithm.py | 4 ++-- .../plugins/processing/algs/saga/description/Mosaicking.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/plugins/processing/algs/saga/SagaAlgorithm.py b/python/plugins/processing/algs/saga/SagaAlgorithm.py index 12677df4e3f..fe8832bbf40 100644 --- a/python/plugins/processing/algs/saga/SagaAlgorithm.py +++ b/python/plugins/processing/algs/saga/SagaAlgorithm.py @@ -181,7 +181,7 @@ class SagaAlgorithm(GeoAlgorithm): layers = param.value.split(';') if layers is None or len(layers) == 0: continue - if param.datatype == dataobjects.TYPE_RASTER: + if param.datatype == ParameterMultipleInput.TYPE_RASTER: for i, layerfile in enumerate(layers): if layerfile.endswith('sdat'): layerfile = param.value[:-4] + "sgrd" @@ -358,7 +358,7 @@ class SagaAlgorithm(GeoAlgorithm): if isinstance(param, ParameterRaster): files = [parameters[param.name()]] elif (isinstance(param, ParameterMultipleInput) and - param.datatype == dataobjects.TYPE_RASTER): + param.datatype == ParameterMultipleInput.TYPE_RASTER): if param.value is not None: files = param.value.split(";") for f in files: diff --git a/python/plugins/processing/algs/saga/description/Mosaicking.txt b/python/plugins/processing/algs/saga/description/Mosaicking.txt index 16f0439b464..b956b8245d9 100644 --- a/python/plugins/processing/algs/saga/description/Mosaicking.txt +++ b/python/plugins/processing/algs/saga/description/Mosaicking.txt @@ -4,7 +4,7 @@ AllowUnmatching ParameterMultipleInput|GRIDS|Input Grids|3|False ParameterString|NAME|Name|Mosaic ParameterSelection|TYPE|Preferred data storage type|[0] 1 bit;[1] 1 byte unsigned integer;[2] 1 byte signed integer;[3] 2 byte unsigned integer;[4] 2 byte signed integer;[5] 4 byte unsigned integer;[6] 4 byte signed integer;[7] 4 byte floating point;[8] 8 byte floating point|7 -ParameterSelection|INTERPOL|Interpolation|[0] Nearest Neighbor;[1] Bilinear Interpolation;[2] Inverse Distance Interpolation;[3] Bicubic Spline Interpolation;[4] B-Spline Interpolation|0 +ParameterSelection|RESAMPLING|Interpolation|[0] Nearest Neighbor;[1] Bilinear Interpolation;[2] Inverse Distance Interpolation;[3] Bicubic Spline Interpolation;[4] B-Spline Interpolation|0 ParameterSelection|OVERLAP|Overlapping Areas|[0] first;[1] last;[2] minimum;[3] maximum;[4] mean;[5] blend boundary;[6] feathering|1 ParameterNumber|BLEND_DIST|Blending Distance|0.0|None|10.0 ParameterSelection|MATCH|Match|[0] none;[1] regression|0 From 7e4f1f8c95da96f4deb212a8935e3ae3d2200992 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 21 Jul 2017 16:58:03 +0100 Subject: [PATCH 110/266] Fixes value relation widget to always keep scrollbar activated. Fixes #16654 --- .../core/qgseditorwidgetwrapper.h | 2 +- .../qgsvaluerelationwidgetwrapper.cpp | 24 +++++++++++++++++++ .../qgsvaluerelationwidgetwrapper.h | 4 ++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index e77b0d9ef64..b8bbc72ceae 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -122,7 +122,7 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper * * \param enabled Enable or Disable? */ - void setEnabled( bool enabled ) override; + virtual void setEnabled( bool enabled ) override; /** Sets the widget to display in an indeterminate "mixed value" state. * \since QGIS 2.16 diff --git a/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.cpp b/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.cpp index 56f18cf64fc..d4202ec555a 100644 --- a/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.cpp @@ -33,6 +33,7 @@ QgsValueRelationWidgetWrapper::QgsValueRelationWidgetWrapper( QgsVectorLayer *vl , mListWidget( nullptr ) , mLineEdit( nullptr ) , mLayer( nullptr ) + , mUpdating( false ) { } @@ -198,3 +199,26 @@ void QgsValueRelationWidgetWrapper::showIndeterminateState() whileBlocking( mLineEdit )->clear(); } } + +void QgsValueRelationWidgetWrapper::setEnabled( bool enabled ) +{ + if ( mUpdating ) + return; + + if ( mListWidget ) + { + mUpdating = true; + for ( int i = 0; i < mListWidget->count(); ++i ) + { + QListWidgetItem *item = mListWidget->item( i ); + + if ( enabled ) + item->setFlags( item->flags() | Qt::ItemIsEnabled ); + else + item->setFlags( item->flags() & ~Qt::ItemIsEnabled ); + } + mUpdating = false; + } + else + QgsEditorWidgetWrapper::setEnabled( enabled ); +} diff --git a/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h b/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h index 9ab9c21496d..cb9a2c96b82 100644 --- a/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h +++ b/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h @@ -59,6 +59,8 @@ class GUI_EXPORT QgsValueRelationWidgetWrapper : public QgsEditorWidgetWrapper void showIndeterminateState() override; + void setEnabled( bool enabled ) override; + protected: QWidget *createWidget( QWidget *parent ) override; void initWidget( QWidget *editor ) override; @@ -75,6 +77,8 @@ class GUI_EXPORT QgsValueRelationWidgetWrapper : public QgsEditorWidgetWrapper QgsValueRelationFieldFormatter::ValueRelationCache mCache; QgsVectorLayer *mLayer = nullptr; + bool mUpdating; + friend class QgsValueRelationWidgetFactory; }; From e6eeacf7fadd17ddeeaf1c2a8bfff9efa43048e4 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 21 Jul 2017 16:58:18 +0100 Subject: [PATCH 111/266] Update sip binding --- python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip | 1 - 1 file changed, 1 deletion(-) diff --git a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip index c30b487e1c1..90596f6fc47 100644 --- a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip +++ b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip @@ -104,7 +104,6 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper %End virtual void setEnabled( bool enabled ); - %Docstring Is used to enable or disable the edit functionality of the managed widget. By default this will enable or disable the whole widget From cdfca0a45f0903e6da6f30405275e7eec93a0a4f Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 21 Jul 2017 16:58:40 +0100 Subject: [PATCH 112/266] Add tests --- .../qgsvaluerelationwidgetwrapper.h | 1 + tests/src/gui/CMakeLists.txt | 2 + .../gui/testqgsvaluerelationwidgetwrapper.cpp | 110 ++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 tests/src/gui/testqgsvaluerelationwidgetwrapper.cpp diff --git a/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h b/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h index cb9a2c96b82..abf4d09dd1c 100644 --- a/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h +++ b/src/gui/editorwidgets/qgsvaluerelationwidgetwrapper.h @@ -80,6 +80,7 @@ class GUI_EXPORT QgsValueRelationWidgetWrapper : public QgsEditorWidgetWrapper bool mUpdating; friend class QgsValueRelationWidgetFactory; + friend class TestQgsValueRelationWidgetWrapper; }; #endif // QGSVALUERELATIONWIDGETWRAPPER_H diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt index 52a2effc296..5be12347d04 100644 --- a/tests/src/gui/CMakeLists.txt +++ b/tests/src/gui/CMakeLists.txt @@ -22,6 +22,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/core/raster ${CMAKE_SOURCE_DIR}/src/core/symbology-ng + ${CMAKE_SOURCE_DIR}/src/core/fieldformatter ${CMAKE_SOURCE_DIR}/src/test ${CMAKE_SOURCE_DIR}/src/native @@ -134,3 +135,4 @@ ADD_QGIS_TEST(listwidgettest testqgslistwidget.cpp) ADD_QGIS_TEST(filedownloader testqgsfiledownloader.cpp) ADD_QGIS_TEST(composergui testqgscomposergui.cpp) ADD_QGIS_TEST(layoutview testqgslayoutview.cpp) +ADD_QGIS_TEST(valuerelationwidgetwrapper testqgsvaluerelationwidgetwrapper.cpp) diff --git a/tests/src/gui/testqgsvaluerelationwidgetwrapper.cpp b/tests/src/gui/testqgsvaluerelationwidgetwrapper.cpp new file mode 100644 index 00000000000..63c90ec93a0 --- /dev/null +++ b/tests/src/gui/testqgsvaluerelationwidgetwrapper.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + testqgsvaluerelationwidgetwrapper.cpp + -------------------------------------- + Date : 21 07 2017 + Copyright : (C) 2017 Paul Blottiere + Email : paul dot blottiere at oslandia 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 "qgstest.h" +#include + +#include +#include +#include +#include +#include "qgseditorwidgetwrapper.h" +#include +#include "qgsgui.h" + +class TestQgsValueRelationWidgetWrapper : public QObject +{ + Q_OBJECT + public: + TestQgsValueRelationWidgetWrapper() {} + + private slots: + void initTestCase(); // will be called before the first testfunction is executed. + void cleanupTestCase(); // will be called after the last testfunction was executed. + void init(); // will be called before each testfunction is executed. + void cleanup(); // will be called after every testfunction. + + void testScrollBarUnlocked(); +}; + +void TestQgsValueRelationWidgetWrapper::initTestCase() +{ + QgsApplication::init(); + QgsApplication::initQgis(); + QgsGui::editorWidgetRegistry()->initEditors(); +} + +void TestQgsValueRelationWidgetWrapper::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsValueRelationWidgetWrapper::init() +{ +} + +void TestQgsValueRelationWidgetWrapper::cleanup() +{ +} + +void TestQgsValueRelationWidgetWrapper::testScrollBarUnlocked() +{ + // create a vector layer + QgsVectorLayer vl1( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=fk|:int" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) ); + QgsProject::instance()->addMapLayer( &vl1, false, false ); + + // build a value relation widget wrapper + QListWidget lw; + QWidget editor; + QgsValueRelationWidgetWrapper w( &vl1, 0, &editor, nullptr ); + w.setEnabled( true ); + w.initWidget( &lw ); + + // add an item virtually + QListWidgetItem item; + item.setText( "MyText" ); + w.mListWidget->addItem( &item ); + QCOMPARE( w.mListWidget->item( 0 )->text(), QString( "MyText" ) ); + + // when the widget wrapper is enabled, the container should be enabled + // as well as items + w.setEnabled( true ); + + QCOMPARE( w.widget()->isEnabled(), true ); + + bool itemEnabled = w.mListWidget->item( 0 )->flags() & Qt::ItemIsEnabled; + QCOMPARE( itemEnabled, true ); + + // when the widget wrapper is disabled, the container should still be enabled + // to keep the scrollbar available but items should be disabled to avoid + // edition + w.setEnabled( false ); + + itemEnabled = w.mListWidget->item( 0 )->flags() & Qt::ItemIsEnabled; + QCOMPARE( itemEnabled, false ); + + QCOMPARE( w.widget()->isEnabled(), true ); + + // recheck after re-enabled + w.setEnabled( true ); + + QCOMPARE( w.widget()->isEnabled(), true ); + itemEnabled = w.mListWidget->item( 0 )->flags() & Qt::ItemIsEnabled; + QCOMPARE( itemEnabled, true ); +} + +QGSTEST_MAIN( TestQgsValueRelationWidgetWrapper ) +#include "testqgsvaluerelationwidgetwrapper.moc" From 90fa6c2e9124b555428ab083bf936d28b043539f Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Sat, 22 Jul 2017 13:35:07 +0200 Subject: [PATCH 113/266] sub layer dialog: sort by layer id and feature count numerically (fixes #16917) --- python/gui/qgssublayersdialog.sip | 6 ++++++ src/gui/qgssublayersdialog.cpp | 20 +++++++++++++++++++- src/gui/qgssublayersdialog.h | 4 ++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/python/gui/qgssublayersdialog.sip b/python/gui/qgssublayersdialog.sip index 739949a61ca..33504d4d577 100644 --- a/python/gui/qgssublayersdialog.sip +++ b/python/gui/qgssublayersdialog.sip @@ -70,6 +70,12 @@ class QgsSublayersDialog : QDialog :rtype: bool %End + int countColumn() const; +%Docstring +.. versionadded:: 3.0 + :rtype: int +%End + public slots: void on_buttonBox_helpRequested(); int exec(); diff --git a/src/gui/qgssublayersdialog.cpp b/src/gui/qgssublayersdialog.cpp index 270c4f77b4d..daf02499005 100644 --- a/src/gui/qgssublayersdialog.cpp +++ b/src/gui/qgssublayersdialog.cpp @@ -20,6 +20,24 @@ #include #include +class SubLayerItem : public QTreeWidgetItem +{ + public: + SubLayerItem( const QStringList &strings, int type = QTreeWidgetItem::Type ) + : QTreeWidgetItem( strings, type ) + {} + + bool operator <( const QTreeWidgetItem &other ) const + { + QgsSublayersDialog *d = qobject_cast( treeWidget()->parent() ); + int col = treeWidget()->sortColumn(); + + if ( col == 0 || ( col > 0 && d->countColumn() == col ) ) + return text( col ).toInt() < other.text( col ).toInt(); + else + return text( col ) < other.text( col ); + } +}; QgsSublayersDialog::QgsSublayersDialog( ProviderType providerType, const QString &name, QWidget *parent, Qt::WindowFlags fl ) @@ -123,7 +141,7 @@ void QgsSublayersDialog::populateLayerTable( const QgsSublayersDialog::LayerDefi elements << QString::number( item.count ); if ( mShowType ) elements << item.type; - layersTable->addTopLevelItem( new QTreeWidgetItem( elements ) ); + layersTable->addTopLevelItem( new SubLayerItem( elements ) ); } // resize columns diff --git a/src/gui/qgssublayersdialog.h b/src/gui/qgssublayersdialog.h index 4db12a86366..d0067308289 100644 --- a/src/gui/qgssublayersdialog.h +++ b/src/gui/qgssublayersdialog.h @@ -80,6 +80,10 @@ class GUI_EXPORT QgsSublayersDialog : public QDialog, private Ui::QgsSublayersDi //! \since QGIS 3.0 bool addToGroupCheckbox() const { return mCheckboxAddToGroup->isChecked(); } + //! Return column with count or -1 + //! \since QGIS 3.0 + int countColumn() const { return mShowCount ? 2 : -1; } + public slots: void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); } int exec(); From b95d432a8c47a03b63a855f3d45f076d9997a760 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Sat, 22 Jul 2017 17:22:03 +0200 Subject: [PATCH 114/266] huh? of course SubLayerItem is private --- src/gui/qgssublayersdialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/qgssublayersdialog.cpp b/src/gui/qgssublayersdialog.cpp index daf02499005..aaccf9d1165 100644 --- a/src/gui/qgssublayersdialog.cpp +++ b/src/gui/qgssublayersdialog.cpp @@ -20,6 +20,7 @@ #include #include +//! @cond class SubLayerItem : public QTreeWidgetItem { public: @@ -38,6 +39,7 @@ class SubLayerItem : public QTreeWidgetItem return text( col ) < other.text( col ); } }; +//! @endcond QgsSublayersDialog::QgsSublayersDialog( ProviderType providerType, const QString &name, QWidget *parent, Qt::WindowFlags fl ) From 99bf32bafbfbc41b50e1d65bdf227d6f73b7c23b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 14:15:01 +1000 Subject: [PATCH 115/266] Fix crash in displacement/distance renderers Individual symbol instances were being rendered multiple times concurrently --- .../symbology-ng/qgspointdistancerenderer.sip | 14 +++--- .../symbology-ng/qgspointclusterrenderer.cpp | 4 +- .../qgspointdisplacementrenderer.cpp | 8 ++-- .../symbology-ng/qgspointdistancerenderer.cpp | 10 ++--- .../symbology-ng/qgspointdistancerenderer.h | 43 ++++++++++--------- 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/python/core/symbology-ng/qgspointdistancerenderer.sip b/python/core/symbology-ng/qgspointdistancerenderer.sip index b532321b609..0782308b57c 100644 --- a/python/core/symbology-ng/qgspointdistancerenderer.sip +++ b/python/core/symbology-ng/qgspointdistancerenderer.sip @@ -29,34 +29,36 @@ class QgsPointDistanceRenderer: QgsFeatureRenderer struct GroupedFeature { - GroupedFeature( const QgsFeature &feature, QgsMarkerSymbol *symbol, bool isSelected, const QString &label = QString() ); + GroupedFeature( const QgsFeature &feature, QgsMarkerSymbol *symbol /Transfer/, bool isSelected, const QString &label = QString() ); %Docstring Constructor for GroupedFeature. \param feature feature - \param symbol base symbol for rendering feature + \param symbol base symbol for rendering feature (owned by GroupedFeature) \param isSelected set to true if feature is selected and should be rendered in a selected state \param label optional label text, or empty string for no label %End - QgsFeature feature; + QgsFeature feature; %Docstring Feature %End - QgsMarkerSymbol *symbol; + QgsMarkerSymbol *symbol() const; %Docstring Base symbol for rendering feature + :rtype: QgsMarkerSymbol %End - bool isSelected; + bool isSelected; %Docstring True if feature is selected and should be rendered in a selected state %End - QString label; + QString label; %Docstring Optional label text %End + }; typedef QList< QgsPointDistanceRenderer::GroupedFeature > ClusteredGroup; diff --git a/src/core/symbology-ng/qgspointclusterrenderer.cpp b/src/core/symbology-ng/qgspointclusterrenderer.cpp index a934c159158..22ca8ac09da 100644 --- a/src/core/symbology-ng/qgspointclusterrenderer.cpp +++ b/src/core/symbology-ng/qgspointclusterrenderer.cpp @@ -72,8 +72,10 @@ void QgsPointClusterRenderer::drawGroup( QPointF centerPoint, QgsRenderContext & else { //single isolated symbol, draw it untouched - QgsMarkerSymbol *symbol = group.at( 0 ).symbol; + QgsMarkerSymbol *symbol = group.at( 0 ).symbol(); + symbol->startRender( context ); symbol->renderPoint( centerPoint, &( group.at( 0 ).feature ), context, -1, group.at( 0 ).isSelected ); + symbol->stopRender( context ); } } diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp index 3901c680db5..fa3d3e237f0 100644 --- a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp +++ b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp @@ -70,7 +70,7 @@ void QgsPointDisplacementRenderer::drawGroup( QPointF centerPoint, QgsRenderCont Q_FOREACH ( const GroupedFeature &feature, group ) { - if ( QgsMarkerSymbol *symbol = feature.symbol ) + if ( QgsMarkerSymbol *symbol = feature.symbol() ) { diagonal = qMax( diagonal, context.convertToPainterUnits( M_SQRT2 * symbol->size(), symbol->sizeUnit(), symbol->sizeMapUnitScale() ) ); @@ -327,9 +327,9 @@ void QgsPointDisplacementRenderer::drawSymbols( const ClusteredGroup &group, Qgs ++symbolPosIt, ++groupIt ) { context.expressionContext().setFeature( groupIt->feature ); - groupIt->symbol->startRender( context ); - groupIt->symbol->renderPoint( *symbolPosIt, &( groupIt->feature ), context, -1, groupIt->isSelected ); - groupIt->symbol->stopRender( context ); + groupIt->symbol()->startRender( context ); + groupIt->symbol()->renderPoint( *symbolPosIt, &( groupIt->feature ), context, -1, groupIt->isSelected ); + groupIt->symbol()->stopRender( context ); } } diff --git a/src/core/symbology-ng/qgspointdistancerenderer.cpp b/src/core/symbology-ng/qgspointdistancerenderer.cpp index 23cfacb7f66..da174780a46 100644 --- a/src/core/symbology-ng/qgspointdistancerenderer.cpp +++ b/src/core/symbology-ng/qgspointdistancerenderer.cpp @@ -96,7 +96,7 @@ bool QgsPointDistanceRenderer::renderFeature( QgsFeature &feature, QgsRenderCont mSpatialIndex->insertFeature( transformedFeature ); // create new group ClusteredGroup newGroup; - newGroup << GroupedFeature( transformedFeature, symbol, selected, label ); + newGroup << GroupedFeature( transformedFeature, symbol->clone(), selected, label ); mClusteredGroups.push_back( newGroup ); // add to group index mGroupIndex.insert( transformedFeature.id(), mClusteredGroups.count() - 1 ); @@ -127,7 +127,7 @@ bool QgsPointDistanceRenderer::renderFeature( QgsFeature &feature, QgsRenderCont ( oldCenter.y() * group.size() + point.y() ) / ( group.size() + 1.0 ) ); // add to a group - group << GroupedFeature( transformedFeature, symbol, selected, label ); + group << GroupedFeature( transformedFeature, symbol->clone(), selected, label ); // add to group index mGroupIndex.insert( transformedFeature.id(), groupIdx ); } @@ -425,16 +425,16 @@ QgsExpressionContextScope *QgsPointDistanceRenderer::createGroupScope( const Clu ClusteredGroup::const_iterator groupIt = group.constBegin(); for ( ; groupIt != group.constEnd(); ++groupIt ) { - if ( !groupIt->symbol ) + if ( !groupIt->symbol() ) continue; if ( !groupColor.isValid() ) { - groupColor = groupIt->symbol->color(); + groupColor = groupIt->symbol()->color(); } else { - if ( groupColor != groupIt->symbol->color() ) + if ( groupColor != groupIt->symbol()->color() ) { groupColor = QColor(); break; diff --git a/src/core/symbology-ng/qgspointdistancerenderer.h b/src/core/symbology-ng/qgspointdistancerenderer.h index b058c5bd789..01e984b7286 100644 --- a/src/core/symbology-ng/qgspointdistancerenderer.h +++ b/src/core/symbology-ng/qgspointdistancerenderer.h @@ -42,30 +42,33 @@ class CORE_EXPORT QgsPointDistanceRenderer: public QgsFeatureRenderer struct GroupedFeature { - /** Constructor for GroupedFeature. - * \param feature feature - * \param symbol base symbol for rendering feature - * \param isSelected set to true if feature is selected and should be rendered in a selected state - * \param label optional label text, or empty string for no label - */ - GroupedFeature( const QgsFeature &feature, QgsMarkerSymbol *symbol, bool isSelected, const QString &label = QString() ) - : feature( feature ) - , symbol( symbol ) - , isSelected( isSelected ) - , label( label ) - {} + /** Constructor for GroupedFeature. + * \param feature feature + * \param symbol base symbol for rendering feature (owned by GroupedFeature) + * \param isSelected set to true if feature is selected and should be rendered in a selected state + * \param label optional label text, or empty string for no label + */ + GroupedFeature( const QgsFeature &feature, QgsMarkerSymbol *symbol SIP_TRANSFER, bool isSelected, const QString &label = QString() ) + : feature( feature ) + , isSelected( isSelected ) + , label( label ) + , mSymbol( symbol ) + {} - //! Feature - QgsFeature feature; + //! Feature + QgsFeature feature; - //! Base symbol for rendering feature - QgsMarkerSymbol *symbol = nullptr; + //! Base symbol for rendering feature + QgsMarkerSymbol *symbol() const { return mSymbol.get(); } - //! True if feature is selected and should be rendered in a selected state - bool isSelected; + //! True if feature is selected and should be rendered in a selected state + bool isSelected; - //! Optional label text - QString label; + //! Optional label text + QString label; + + private: + std::shared_ptr< QgsMarkerSymbol > mSymbol; }; //! A group of clustered points (ie features within the distance tolerance). From eb94207e6dd6c049f382c8c0fc83e4aec3f7bf37 Mon Sep 17 00:00:00 2001 From: Alexandre Neto Date: Tue, 20 Jun 2017 16:14:39 +0100 Subject: [PATCH 116/266] Removes frames from data manager dialog Removes extra margin from mOptionsFrame --- src/ui/qgsdatasourcemanagerdialog.ui | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ui/qgsdatasourcemanagerdialog.ui b/src/ui/qgsdatasourcemanagerdialog.ui index 4114a93ef89..d9f4cfc80a0 100644 --- a/src/ui/qgsdatasourcemanagerdialog.ui +++ b/src/ui/qgsdatasourcemanagerdialog.ui @@ -45,22 +45,13 @@ - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised - - 0 - - - 0 - - - 0 - - + 0 @@ -83,6 +74,9 @@ 16777215 + + QFrame::StyledPanel + Qt::ScrollBarAlwaysOff @@ -131,12 +125,18 @@ - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised + + 6 + + + 0 + From e9cb34836e6d92db339438b10fa1ff9e83e12925 Mon Sep 17 00:00:00 2001 From: Alexandre Neto Date: Wed, 21 Jun 2017 10:57:46 +0100 Subject: [PATCH 117/266] Enforce margins in cpp file --- src/gui/qgsdatasourcemanagerdialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index c596b257329..2b16d7a3ea1 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -34,6 +34,9 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QWidget *parent, QgsMapC { ui->setupUi( this ); + ui->verticalLayout_2->setSpacing(6); + ui->verticalLayout_2->setMargin(0); + ui->verticalLayout_2->setContentsMargins(0,0,0,0); // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states, // switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left), // and connecting QDialogButtonBox's accepted/rejected signals to dialog's accept/reject slots From 90e68771ec4599436c0763f223fcade3cf6f1479 Mon Sep 17 00:00:00 2001 From: Alexandre Neto Date: Sat, 22 Jul 2017 16:28:43 +0100 Subject: [PATCH 118/266] Fix code style --- src/gui/qgsdatasourcemanagerdialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/qgsdatasourcemanagerdialog.cpp b/src/gui/qgsdatasourcemanagerdialog.cpp index 2b16d7a3ea1..90e170ee4fd 100644 --- a/src/gui/qgsdatasourcemanagerdialog.cpp +++ b/src/gui/qgsdatasourcemanagerdialog.cpp @@ -34,9 +34,9 @@ QgsDataSourceManagerDialog::QgsDataSourceManagerDialog( QWidget *parent, QgsMapC { ui->setupUi( this ); - ui->verticalLayout_2->setSpacing(6); - ui->verticalLayout_2->setMargin(0); - ui->verticalLayout_2->setContentsMargins(0,0,0,0); + ui->verticalLayout_2->setSpacing( 6 ); + ui->verticalLayout_2->setMargin( 0 ); + ui->verticalLayout_2->setContentsMargins( 0, 0, 0, 0 ); // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states, // switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left), // and connecting QDialogButtonBox's accepted/rejected signals to dialog's accept/reject slots From 9a12249b02b34f1076cab13540b0a3cf5863c26b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Jul 2017 11:04:08 +1000 Subject: [PATCH 119/266] Improve string wording, capitalization and plural consistency --- src/core/qgsunittypes.cpp | 2 +- src/gui/qgsunitselectionwidget.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 src/core/qgsunittypes.cpp mode change 100644 => 100755 src/gui/qgsunitselectionwidget.cpp diff --git a/src/core/qgsunittypes.cpp b/src/core/qgsunittypes.cpp old mode 100644 new mode 100755 index 56836fbed3b..4fb868e9e51 --- a/src/core/qgsunittypes.cpp +++ b/src/core/qgsunittypes.cpp @@ -1759,7 +1759,7 @@ QString QgsUnitTypes::toString( QgsUnitTypes::RenderUnit unit ) return QObject::tr( "millimeters", "render" ); case RenderMetersInMapUnits: - return QObject::tr( "meters (in map units)", "render" ); + return QObject::tr( "meters (at map scale)", "render" ); case RenderMapUnits: return QObject::tr( "map units", "render" ); diff --git a/src/gui/qgsunitselectionwidget.cpp b/src/gui/qgsunitselectionwidget.cpp old mode 100644 new mode 100755 index 002451c0bc4..6406a89fd44 --- a/src/gui/qgsunitselectionwidget.cpp +++ b/src/gui/qgsunitselectionwidget.cpp @@ -172,11 +172,11 @@ void QgsUnitSelectionWidget::setUnits( const QgsUnitTypes::RenderUnitList &units } if ( units.contains( QgsUnitTypes::RenderMetersInMapUnits ) ) { - mUnitCombo->addItem( tr( "Meters in Map unit" ), QgsUnitTypes::RenderMetersInMapUnits ); + mUnitCombo->addItem( tr( "Meters (at Map Scale)" ), QgsUnitTypes::RenderMetersInMapUnits ); } if ( units.contains( QgsUnitTypes::RenderMapUnits ) ) { - mUnitCombo->addItem( tr( "Map unit" ), QgsUnitTypes::RenderMapUnits ); + mUnitCombo->addItem( tr( "Map Units" ), QgsUnitTypes::RenderMapUnits ); } if ( units.contains( QgsUnitTypes::RenderPercentage ) ) { From 22c4740f63c55b8dd461438953d6e3558dada6df Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 13:17:24 +1000 Subject: [PATCH 120/266] [FEATURE] New standard widget for symbol buttons Button widgets for configuring symbol properties were reimplemented multiple times throughout the codebase. This commit creates a new standard QgsSymbolButton widget which should be used whenever a button for configuring symbol properties is required. Features include: - automatic use of inline panels whenever possible - dropdown menu with shortcuts to color settings, copy/pasting colors - accepts drag and dropped colors to set symbol color --- python/gui/gui_auto.sip | 1 + python/gui/qgssymbolbutton.sip | 159 ++++++++ src/gui/CMakeLists.txt | 2 + src/gui/qgssymbolbutton.cpp | 446 +++++++++++++++++++++++ src/gui/qgssymbolbutton.h | 218 +++++++++++ tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_qgssymbolbutton.py | 77 ++++ 7 files changed, 904 insertions(+) create mode 100644 python/gui/qgssymbolbutton.sip create mode 100644 src/gui/qgssymbolbutton.cpp create mode 100644 src/gui/qgssymbolbutton.h create mode 100644 tests/src/python/test_qgssymbolbutton.py diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 3a4b9138d8e..93d4816f782 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -188,6 +188,7 @@ %Include qgsstatusbar.sip %Include qgssublayersdialog.sip %Include qgssubstitutionlistwidget.sip +%Include qgssymbolbutton.sip %Include qgstablewidgetbase.sip %Include qgstabwidget.sip %Include qgstaskmanagerwidget.sip diff --git a/python/gui/qgssymbolbutton.sip b/python/gui/qgssymbolbutton.sip new file mode 100644 index 00000000000..8c5566fdfdf --- /dev/null +++ b/python/gui/qgssymbolbutton.sip @@ -0,0 +1,159 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgssymbolbutton.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsSymbolButton : QToolButton +{ +%Docstring + A button for creating and modifying QgsSymbol settings. + + The button shows a preview icon for the current symbol, and will open a detailed symbol editor dialog (or + panel widget) when clicked. + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgssymbolbutton.h" +%End + public: + + QgsSymbolButton( QWidget *parent /TransferThis/ = 0, const QString &dialogTitle = QString() ); +%Docstring + Construct a new symbol button. + Use ``dialogTitle`` string to define the title to show in the symbol settings dialog. +%End + + virtual QSize minimumSizeHint() const; + + void setDialogTitle( const QString &title ); +%Docstring + Sets the ``title`` for the symbol settings dialog window. +.. seealso:: dialogTitle() +%End + + QString dialogTitle() const; +%Docstring + Returns the title for the symbol settings dialog window. +.. seealso:: setDialogTitle() + :rtype: str +%End + + QgsSymbol *symbol(); +%Docstring + Returns the current symbol defined by the button. +.. seealso:: setSymbol() +.. seealso:: changed() + :rtype: QgsSymbol +%End + + QgsMapCanvas *mapCanvas() const; +%Docstring + Returns the map canvas associated with the widget. +.. seealso:: setMapCanvas() + :rtype: QgsMapCanvas +%End + + void setMapCanvas( QgsMapCanvas *canvas ); +%Docstring + Sets a map ``canvas`` to associate with the widget. This allows the + widget to fetch current settings from the map canvas, such as current scale. +.. seealso:: mapCanvas() +%End + + QgsVectorLayer *layer() const; +%Docstring + Returns the layer associated with the widget. +.. seealso:: setLayer() + :rtype: QgsVectorLayer +%End + + void setLayer( QgsVectorLayer *layer ); +%Docstring + Sets a ``layer`` to associate with the widget. This allows the + widget to setup layer related settings within the symbol settings dialog, + such as correctly populating data defined override buttons. +.. seealso:: layer() +%End + + void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator ); +%Docstring + Register an expression context generator class that will be used to retrieve + an expression context for the button when required. +%End + + public slots: + + void setSymbol( QgsSymbol *symbol /Transfer/ ); +%Docstring + Sets the ``symbol`` for the button. Ownership of ``symbol`` is transferred to the + button. +.. seealso:: symbol() +.. seealso:: changed() +%End + + void setColor( const QColor &color ); +%Docstring + Sets the current ``color`` for the symbol. Will emit a changed() signal if the color is different + to the previous symbol color. +%End + + void copyColor(); +%Docstring + Copies the current symbol color to the clipboard. +.. seealso:: pasteColor() +%End + + void pasteColor(); +%Docstring + Pastes a color from the clipboard to the symbol. If clipboard does not contain a valid + color or string representation of a color, then no change is applied. +.. seealso:: copyColor() +%End + + signals: + + void changed(); +%Docstring + Emitted when the symbol's settings are changed. +.. seealso:: symbol() +.. seealso:: setSymbol() +%End + + protected: + + virtual void changeEvent( QEvent *e ); + + virtual void showEvent( QShowEvent *e ); + + virtual void resizeEvent( QResizeEvent *event ); + + + virtual void mousePressEvent( QMouseEvent *e ); + + virtual void mouseMoveEvent( QMouseEvent *e ); + + virtual void dragEnterEvent( QDragEnterEvent *e ); + + + virtual void dragLeaveEvent( QDragLeaveEvent *e ); + + + virtual void dropEvent( QDropEvent *e ); + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgssymbolbutton.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index d6f4ad57731..5dacf06a4ad 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -336,6 +336,7 @@ SET(QGIS_GUI_SRCS qgssubstitutionlistwidget.cpp qgssqlcomposerdialog.cpp qgsstatusbar.cpp + qgssymbolbutton.cpp qgstablewidgetbase.cpp qgstabwidget.cpp qgstablewidgetitem.cpp @@ -492,6 +493,7 @@ SET(QGIS_GUI_MOC_HDRS qgsstatusbar.h qgssublayersdialog.h qgssubstitutionlistwidget.h + qgssymbolbutton.h qgstablewidgetbase.h qgstabwidget.h qgstaskmanagerwidget.h diff --git a/src/gui/qgssymbolbutton.cpp b/src/gui/qgssymbolbutton.cpp new file mode 100644 index 00000000000..b658f5b2f3a --- /dev/null +++ b/src/gui/qgssymbolbutton.cpp @@ -0,0 +1,446 @@ +/*************************************************************************** + qgssymbolbutton.h + ----------------- + Date : July 2017 + Copyright : (C) 2017 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 "qgssymbolbutton.h" +#include "qgspanelwidget.h" +#include "qgsexpressioncontext.h" +#include "qgsexpressioncontextgenerator.h" +#include "qgsvectorlayer.h" +#include "qgssymbolselectordialog.h" +#include "qgsstyle.h" +#include "qgscolorwidgets.h" +#include "qgscolorschemeregistry.h" +#include "qgscolorswatchgrid.h" +#include "qgssymbollayerutils.h" +#include +#include +#include + +QgsSymbolButton::QgsSymbolButton( QWidget *parent, const QString &dialogTitle ) + : QToolButton( parent ) + , mDialogTitle( dialogTitle.isEmpty() ? tr( "Symbol Settings" ) : dialogTitle ) +{ + mSymbol.reset( QgsFillSymbol::createSimple( QgsStringMap() ) ); + + setAcceptDrops( true ); + connect( this, &QAbstractButton::clicked, this, &QgsSymbolButton::showSettingsDialog ); + + //setup dropdown menu + mMenu = new QMenu( this ); + connect( mMenu, &QMenu::aboutToShow, this, &QgsSymbolButton::prepareMenu ); + setMenu( mMenu ); + setPopupMode( QToolButton::MenuButtonPopup ); +} + +QSize QgsSymbolButton::minimumSizeHint() const +{ + //make sure height of button looks good under different platforms + QSize size = QToolButton::minimumSizeHint(); + int fontHeight = fontMetrics().height() * 1.4; + return QSize( size.width(), qMax( size.height(), fontHeight ) ); +} + +void QgsSymbolButton::showSettingsDialog() +{ + QgsExpressionContext context; + if ( mExpressionContextGenerator ) + context = mExpressionContextGenerator->createExpressionContext(); + else + { + context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer.data() ) ); + } + QgsSymbol *newSymbol = mSymbol->clone(); + + QgsSymbolWidgetContext symbolContext; + symbolContext.setExpressionContext( &context ); + symbolContext.setMapCanvas( mMapCanvas ); + + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); + if ( panel && panel->dockMode() ) + { + QgsSymbolSelectorWidget *d = new QgsSymbolSelectorWidget( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr ); + d->setContext( symbolContext ); + connect( d, &QgsPanelWidget::widgetChanged, this, &QgsSymbolButton::updateSymbolFromWidget ); + connect( d, &QgsPanelWidget::panelAccepted, this, &QgsSymbolButton::cleanUpSymbolSelector ); + panel->openPanel( d ); + } + else + { + QgsSymbolSelectorDialog dialog( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr ); + dialog.setWindowTitle( mDialogTitle ); + dialog.setContext( symbolContext ); + if ( dialog.exec() ) + { + setSymbol( newSymbol ); + } + else + { + delete newSymbol; + } + + // reactivate button's window + activateWindow(); + } +} + +void QgsSymbolButton::updateSymbolFromWidget() +{ + if ( QgsSymbolSelectorWidget *w = qobject_cast( sender() ) ) + setSymbol( w->symbol()->clone() ); +} + +void QgsSymbolButton::cleanUpSymbolSelector( QgsPanelWidget *container ) +{ + QgsSymbolSelectorWidget *w = qobject_cast( container ); + if ( !w ) + return; + + delete w->symbol(); +} + +QgsMapCanvas *QgsSymbolButton::mapCanvas() const +{ + return mMapCanvas; +} + +void QgsSymbolButton::setMapCanvas( QgsMapCanvas *mapCanvas ) +{ + mMapCanvas = mapCanvas; +} + +QgsVectorLayer *QgsSymbolButton::layer() const +{ + return mLayer; +} + +void QgsSymbolButton::setLayer( QgsVectorLayer *layer ) +{ + mLayer = layer; +} + +void QgsSymbolButton::registerExpressionContextGenerator( QgsExpressionContextGenerator *generator ) +{ + mExpressionContextGenerator = generator; +} + +void QgsSymbolButton::setSymbol( QgsSymbol *symbol ) +{ + mSymbol.reset( symbol ); + updatePreview(); + emit changed(); +} + +void QgsSymbolButton::setColor( const QColor &color ) +{ + QColor opaque = color; + opaque.setAlphaF( 1.0 ); + + if ( opaque == mSymbol->color() ) + return; + + mSymbol->setColor( opaque ); + updatePreview(); + emit changed(); +} + +void QgsSymbolButton::copyColor() +{ + QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) ); +} + +void QgsSymbolButton::pasteColor() +{ + QColor clipColor; + bool hasAlpha = false; + if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) ) + { + //paste color + setColor( clipColor ); + QgsRecentColorScheme::addRecentColor( clipColor ); + } +} + +void QgsSymbolButton::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::RightButton ) + { + QToolButton::showMenu(); + return; + } + else if ( e->button() == Qt::LeftButton ) + { + mDragStartPosition = e->pos(); + } + QToolButton::mousePressEvent( e ); +} + +void QgsSymbolButton::mouseMoveEvent( QMouseEvent *e ) +{ + //handle dragging colors/symbols from button + + if ( !( e->buttons() & Qt::LeftButton ) ) + { + //left button not depressed, so not a drag + QToolButton::mouseMoveEvent( e ); + return; + } + + if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() ) + { + //mouse not moved, so not a drag + QToolButton::mouseMoveEvent( e ); + return; + } + + //user is dragging + QDrag *drag = new QDrag( this ); + drag->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) ); + drag->setPixmap( QgsColorWidget::createDragIcon( mSymbol->color() ) ); + drag->exec( Qt::CopyAction ); + setDown( false ); +} + +void QgsSymbolButton::dragEnterEvent( QDragEnterEvent *e ) +{ + //is dragged data valid color data? + QColor mimeColor; + bool hasAlpha = false; + + if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) ) + { + //if so, we accept the drag, and temporarily change the button's color + //to match the dragged color. This gives immediate feedback to the user + //that colors can be dropped here + e->acceptProposedAction(); + updatePreview( mimeColor ); + } +} + +void QgsSymbolButton::dragLeaveEvent( QDragLeaveEvent *e ) +{ + Q_UNUSED( e ); + //reset button color + updatePreview(); +} + +void QgsSymbolButton::dropEvent( QDropEvent *e ) +{ + //is dropped data valid format data? + QColor mimeColor; + bool hasAlpha = false; + if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) ) + { + //accept drop and set new color + e->acceptProposedAction(); + + if ( hasAlpha ) + { + mSymbol->setOpacity( mimeColor.alphaF() ); + } + mimeColor.setAlphaF( 1.0 ); + mSymbol->setColor( mimeColor ); + QgsRecentColorScheme::addRecentColor( mimeColor ); + updatePreview(); + emit changed(); + } + updatePreview(); +} + +void QgsSymbolButton::prepareMenu() +{ + //we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any + //QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size + //for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this + //menu is opened, otherwise color schemes like the recent color scheme grid are meaningless + mMenu->clear(); + + QAction *configureAction = new QAction( tr( "Configure symbol..." ), this ); + mMenu->addAction( configureAction ); + connect( configureAction, &QAction::triggered, this, &QgsSymbolButton::showSettingsDialog ); + + mMenu->addSeparator(); + + QgsColorWheel *colorWheel = new QgsColorWheel( mMenu ); + colorWheel->setColor( mSymbol->color() ); + QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu ); + colorAction->setDismissOnColorSelection( false ); + connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsSymbolButton::setColor ); + mMenu->addAction( colorAction ); + + //get schemes with ShowInColorButtonMenu flag set + QList< QgsColorScheme * > schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorButtonMenu ); + QList< QgsColorScheme * >::iterator it = schemeList.begin(); + for ( ; it != schemeList.end(); ++it ) + { + QgsColorSwatchGridAction *colorAction = new QgsColorSwatchGridAction( *it, mMenu, QStringLiteral( "symbology" ), this ); + colorAction->setBaseColor( mSymbol->color() ); + mMenu->addAction( colorAction ); + connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsSymbolButton::setColor ); + connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsSymbolButton::addRecentColor ); + } + + mMenu->addSeparator(); + + QAction *copyColorAction = new QAction( tr( "Copy color" ), this ); + mMenu->addAction( copyColorAction ); + connect( copyColorAction, &QAction::triggered, this, &QgsSymbolButton::copyColor ); + + QAction *pasteColorAction = new QAction( tr( "Paste color" ), this ); + //enable or disable paste action based on current clipboard contents. We always show the paste + //action, even if it's disabled, to give hint to the user that pasting colors is possible + QColor clipColor; + bool hasAlpha = false; + if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) ) + { + pasteColorAction->setIcon( createColorIcon( clipColor ) ); + } + else + { + pasteColorAction->setEnabled( false ); + } + mMenu->addAction( pasteColorAction ); + connect( pasteColorAction, &QAction::triggered, this, &QgsSymbolButton::pasteColor ); +} + +void QgsSymbolButton::addRecentColor( const QColor &color ) +{ + QgsRecentColorScheme::addRecentColor( color ); +} + + +void QgsSymbolButton::changeEvent( QEvent *e ) +{ + if ( e->type() == QEvent::EnabledChange ) + { + updatePreview(); + } + QToolButton::changeEvent( e ); +} + +void QgsSymbolButton::showEvent( QShowEvent *e ) +{ + updatePreview(); + QToolButton::showEvent( e ); +} + +void QgsSymbolButton::resizeEvent( QResizeEvent *event ) +{ + QToolButton::resizeEvent( event ); + //recalculate icon size and redraw icon + mIconSize = QSize(); + updatePreview(); +} + +void QgsSymbolButton::updatePreview( const QColor &color, QgsSymbol *tempSymbol ) +{ + std::unique_ptr< QgsSymbol > previewSymbol; + + if ( tempSymbol ) + previewSymbol.reset( tempSymbol->clone() ); + else + previewSymbol.reset( mSymbol->clone() ); + + if ( color.isValid() ) + previewSymbol->setColor( color ); + + QSize currentIconSize; + //icon size is button size with a small margin + if ( menu() ) + { + if ( !mIconSize.isValid() ) + { + //calculate size of push button part of widget (ie, without the menu dropdown button part) + QStyleOptionToolButton opt; + initStyleOption( &opt ); + QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton, + this ); + //make sure height of icon looks good under different platforms +#ifdef Q_OS_WIN + mIconSize = QSize( buttonSize.width() - 10, height() - 6 ); +#else + mIconSize = QSize( buttonSize.width() - 10, height() - 12 ); +#endif + } + currentIconSize = mIconSize; + } + else + { + //no menu +#ifdef Q_OS_WIN + currentIconSize = QSize( width() - 10, height() - 6 ); +#else + currentIconSize = QSize( width() - 10, height() - 12 ); +#endif + } + + if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 ) + { + return; + } + + //create an icon pixmap + QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( previewSymbol.get(), currentIconSize ); + setIconSize( currentIconSize ); + setIcon( icon ); +} + +bool QgsSymbolButton::colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha ) +{ + hasAlpha = false; + QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( mimeData, hasAlpha ); + + if ( mimeColor.isValid() ) + { + resultColor = mimeColor; + return true; + } + + //could not get color from mime data + return false; +} + +QPixmap QgsSymbolButton::createColorIcon( const QColor &color ) const +{ + //create an icon pixmap + QPixmap pixmap( 16, 16 ); + pixmap.fill( Qt::transparent ); + + QPainter p; + p.begin( &pixmap ); + + //draw color over pattern + p.setBrush( QBrush( color ) ); + + //draw border + p.setPen( QColor( 197, 197, 197 ) ); + p.drawRect( 0, 0, 15, 15 ); + p.end(); + return pixmap; +} + +void QgsSymbolButton::setDialogTitle( const QString &title ) +{ + mDialogTitle = title; +} + +QString QgsSymbolButton::dialogTitle() const +{ + return mDialogTitle; +} + +QgsSymbol *QgsSymbolButton::symbol() +{ + return mSymbol.get(); +} diff --git a/src/gui/qgssymbolbutton.h b/src/gui/qgssymbolbutton.h new file mode 100644 index 00000000000..972de290a0b --- /dev/null +++ b/src/gui/qgssymbolbutton.h @@ -0,0 +1,218 @@ +/*************************************************************************** + qgssymbolbutton.h + ----------------- + Date : July 2017 + Copyright : (C) 2017 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 QGSSYMBOLBUTTON_H +#define QGSSYMBOLBUTTON_H + +#include "qgis_gui.h" +#include "qgis.h" +#include "qgssymbol.h" +#include +#include +#include + +class QgsMapCanvas; +class QgsVectorLayer; +class QgsExpressionContextGenerator; +class QgsPanelWidget; + +/** + * \ingroup gui + * \class QgsSymbolButton + * A button for creating and modifying QgsSymbol settings. + * + * The button shows a preview icon for the current symbol, and will open a detailed symbol editor dialog (or + * panel widget) when clicked. + * + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsSymbolButton : public QToolButton +{ + Q_OBJECT + + Q_PROPERTY( QString dialogTitle READ dialogTitle WRITE setDialogTitle ) + + public: + + /** + * Construct a new symbol button. + * Use \a dialogTitle string to define the title to show in the symbol settings dialog. + */ + QgsSymbolButton( QWidget *parent SIP_TRANSFERTHIS = nullptr, const QString &dialogTitle = QString() ); + + virtual QSize minimumSizeHint() const override; + + /** + * Sets the \a title for the symbol settings dialog window. + * \see dialogTitle() + */ + void setDialogTitle( const QString &title ); + + /** + * Returns the title for the symbol settings dialog window. + * \see setDialogTitle() + */ + QString dialogTitle() const; + + /** + * Returns the current symbol defined by the button. + * \see setSymbol() + * \see changed() + */ + QgsSymbol *symbol(); + + /** + * Returns the map canvas associated with the widget. + * \see setMapCanvas() + */ + QgsMapCanvas *mapCanvas() const; + + /** + * Sets a map \a canvas to associate with the widget. This allows the + * widget to fetch current settings from the map canvas, such as current scale. + * \see mapCanvas() + */ + void setMapCanvas( QgsMapCanvas *canvas ); + + /** + * Returns the layer associated with the widget. + * \see setLayer() + */ + QgsVectorLayer *layer() const; + + /** + * Sets a \a layer to associate with the widget. This allows the + * widget to setup layer related settings within the symbol settings dialog, + * such as correctly populating data defined override buttons. + * \see layer() + */ + void setLayer( QgsVectorLayer *layer ); + + /** + * Register an expression context generator class that will be used to retrieve + * an expression context for the button when required. + */ + void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator ); + + public slots: + + /** + * Sets the \a symbol for the button. Ownership of \a symbol is transferred to the + * button. + * \see symbol() + * \see changed() + */ + void setSymbol( QgsSymbol *symbol SIP_TRANSFER ); + + /** + * Sets the current \a color for the symbol. Will emit a changed() signal if the color is different + * to the previous symbol color. + */ + void setColor( const QColor &color ); + + /** + * Copies the current symbol color to the clipboard. + * \see pasteColor() + */ + void copyColor(); + + /** + * Pastes a color from the clipboard to the symbol. If clipboard does not contain a valid + * color or string representation of a color, then no change is applied. + * \see copyColor() + */ + void pasteColor(); + + signals: + + /** + * Emitted when the symbol's settings are changed. + * \see symbol() + * \see setSymbol() + */ + void changed(); + + protected: + + void changeEvent( QEvent *e ) override; + void showEvent( QShowEvent *e ) override; + void resizeEvent( QResizeEvent *event ) override; + + // Reimplemented to detect right mouse button clicks on the color button and allow dragging colors + void mousePressEvent( QMouseEvent *e ) override; + // Reimplemented to allow dragging colors/symbols from button + void mouseMoveEvent( QMouseEvent *e ) override; + // Reimplemented to accept dragged colors + void dragEnterEvent( QDragEnterEvent *e ) override; + + // Reimplemented to reset button appearance after drag leave + void dragLeaveEvent( QDragLeaveEvent *e ) override; + + // Reimplemented to accept dropped colors + void dropEvent( QDropEvent *e ) override; + + private slots: + + void showSettingsDialog(); + void updateSymbolFromWidget(); + void cleanUpSymbolSelector( QgsPanelWidget *container ); + + /** Creates the drop-down menu entries + */ + void prepareMenu(); + + void addRecentColor( const QColor &color ); + + private: + + QString mDialogTitle; + + QgsMapCanvas *mMapCanvas = nullptr; + + QPoint mDragStartPosition; + + QMenu *mMenu = nullptr; + + QPointer< QgsVectorLayer > mLayer; + + QSize mIconSize; + + std::unique_ptr< QgsSymbol > mSymbol; + + QgsExpressionContextGenerator *mExpressionContextGenerator = nullptr; + + /** + * Regenerates the text preview. If \a color is specified, a temporary color preview + * is shown instead. + */ + void updatePreview( const QColor &color = QColor(), QgsSymbol *tempSymbol = nullptr ); + + /** Attempts to parse mimeData as a color, either via the mime data's color data or by + * parsing a textual representation of a color. + * \returns true if mime data could be intrepreted as a color + * \param mimeData mime data + * \param resultColor QColor to store evaluated color + * \param hasAlpha will be set to true if mime data also included an alpha component + * \see formatFromMimeData + */ + bool colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha ); + + /** + * Create a \a color icon for display in the drop-down menu. + */ + QPixmap createColorIcon( const QColor &color ) const; + +}; + +#endif // QGSSYMBOLBUTTON_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 0bef93f7235..03131951c2f 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -126,6 +126,7 @@ ADD_PYTHON_TEST(PyQgsRenderer test_qgsrenderer.py) ADD_PYTHON_TEST(PyQgsRulebasedRenderer test_qgsrulebasedrenderer.py) ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py) ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py) +ADD_PYTHON_TEST(PyQgsSymbolButton test_qgssymbolbutton.py) ADD_PYTHON_TEST(PyQgsTabfileProvider test_provider_tabfile.py) ADD_PYTHON_TEST(PyQgsTabWidget test_qgstabwidget.py) ADD_PYTHON_TEST(PyQgsTextRenderer test_qgstextrenderer.py) diff --git a/tests/src/python/test_qgssymbolbutton.py b/tests/src/python/test_qgssymbolbutton.py new file mode 100644 index 00000000000..ca53e1f8fe2 --- /dev/null +++ b/tests/src/python/test_qgssymbolbutton.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsSymbolButton. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '23/07/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.core import QgsFillSymbol, QgsMarkerSymbol +from qgis.gui import QgsSymbolButton, QgsMapCanvas +from qgis.testing import start_app, unittest +from qgis.PyQt.QtGui import QColor, QFont +from qgis.PyQt.QtTest import QSignalSpy +from utilities import getTestFont + +start_app() + + +class TestQgsSymbolButton(unittest.TestCase): + + def testGettersSetters(self): + button = QgsSymbolButton() + canvas = QgsMapCanvas() + + button.setDialogTitle('test title') + self.assertEqual(button.dialogTitle(), 'test title') + + button.setMapCanvas(canvas) + self.assertEqual(button.mapCanvas(), canvas) + + def testSetGetSymbol(self): + button = QgsSymbolButton() + symbol = QgsMarkerSymbol.createSimple({}) + symbol.setColor(QColor(255, 0, 0)) + + signal_spy = QSignalSpy(button.changed) + button.setSymbol(symbol) + self.assertEqual(len(signal_spy), 1) + + r = button.symbol() + self.assertEqual(r.color(), QColor(255, 0, 0)) + + def testSetColor(self): + button = QgsSymbolButton() + + symbol = QgsMarkerSymbol.createSimple({}) + symbol.setColor(QColor(255, 255, 0)) + + button.setSymbol(symbol) + + signal_spy = QSignalSpy(button.changed) + button.setColor(QColor(0, 255, 0)) + self.assertEqual(len(signal_spy), 1) + + r = button.symbol() + self.assertEqual(r.color().name(), QColor(0, 255, 0).name()) + + # set same color, should not emit signal + button.setColor(QColor(0, 255, 0)) + self.assertEqual(len(signal_spy), 1) + + # color with transparency - should be stripped + button.setColor(QColor(0, 255, 0, 100)) + r = button.symbol() + self.assertEqual(r.color(), QColor(0, 255, 0)) + + +if __name__ == '__main__': + unittest.main() From 46f6f83fb973d4f09b67ad6893c99e845d42881d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 14:48:07 +1000 Subject: [PATCH 121/266] Port some symbol buttons across to QgsSymbolButton - composer shape style button (no other composer ones for now- they're all getting removed with layouts anyway) - point cluster/displacement renderer buttons --- python/gui/qgssymbolbutton.sip | 1 + .../qgspointclusterrendererwidget.sip | 5 +- .../qgspointdisplacementrendererwidget.sip | 5 +- src/app/composer/qgscomposershapewidget.cpp | 53 ++---------- src/app/composer/qgscomposershapewidget.h | 6 +- src/gui/qgssymbolbutton.h | 22 +++++ .../qgspointclusterrendererwidget.cpp | 72 ++++++---------- .../qgspointclusterrendererwidget.h | 11 +-- .../qgspointdisplacementrendererwidget.cpp | 84 +++++++------------ .../qgspointdisplacementrendererwidget.h | 10 +-- src/ui/composer/qgscomposershapewidgetbase.ui | 19 +++-- src/ui/composer/qgscompositionwidgetbase.ui | 10 +-- .../qgspointdisplacementrendererwidgetbase.ui | 41 +++++---- .../qgspointclusterrendererwidgetbase.ui | 27 ++++-- 14 files changed, 167 insertions(+), 199 deletions(-) diff --git a/python/gui/qgssymbolbutton.sip b/python/gui/qgssymbolbutton.sip index 8c5566fdfdf..af8b1e4f9e1 100644 --- a/python/gui/qgssymbolbutton.sip +++ b/python/gui/qgssymbolbutton.sip @@ -53,6 +53,7 @@ class QgsSymbolButton : QToolButton :rtype: QgsSymbol %End + QgsMapCanvas *mapCanvas() const; %Docstring Returns the map canvas associated with the widget. diff --git a/python/gui/symbology-ng/qgspointclusterrendererwidget.sip b/python/gui/symbology-ng/qgspointclusterrendererwidget.sip index 90eb36b629f..84c1c659178 100644 --- a/python/gui/symbology-ng/qgspointclusterrendererwidget.sip +++ b/python/gui/symbology-ng/qgspointclusterrendererwidget.sip @@ -11,7 +11,7 @@ -class QgsPointClusterRendererWidget: QgsRendererWidget +class QgsPointClusterRendererWidget: QgsRendererWidget, QgsExpressionContextGenerator { %Docstring A widget which allows configuration of the properties for a QgsPointClusterRenderer. @@ -48,6 +48,9 @@ class QgsPointClusterRendererWidget: QgsRendererWidget virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + + }; /************************************************************************ diff --git a/python/gui/symbology-ng/qgspointdisplacementrendererwidget.sip b/python/gui/symbology-ng/qgspointdisplacementrendererwidget.sip index 4b67f1eaee4..6a22045526b 100644 --- a/python/gui/symbology-ng/qgspointdisplacementrendererwidget.sip +++ b/python/gui/symbology-ng/qgspointdisplacementrendererwidget.sip @@ -10,7 +10,7 @@ -class QgsPointDisplacementRendererWidget: QgsRendererWidget +class QgsPointDisplacementRendererWidget: QgsRendererWidget, QgsExpressionContextGenerator { %TypeHeaderCode @@ -29,6 +29,9 @@ class QgsPointDisplacementRendererWidget: QgsRendererWidget virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + + }; /************************************************************************ diff --git a/src/app/composer/qgscomposershapewidget.cpp b/src/app/composer/qgscomposershapewidget.cpp index c41874f0bbc..355c48b3c48 100644 --- a/src/app/composer/qgscomposershapewidget.cpp +++ b/src/app/composer/qgscomposershapewidget.cpp @@ -52,7 +52,10 @@ QgsComposerShapeWidget::QgsComposerShapeWidget( QgsComposerShape *composerShape if ( mComposerShape ) { connect( mComposerShape, &QgsComposerObject::itemChanged, this, &QgsComposerShapeWidget::setGuiElementValues ); + mShapeStyleButton->registerExpressionContextGenerator( mComposerShape ); } + connect( mShapeStyleButton, &QgsSymbolButton::changed, this, &QgsComposerShapeWidget::symbolChanged ); + mShapeStyleButton->setLayer( atlasCoverageLayer() ); } QgsComposerShapeWidget::~QgsComposerShapeWidget() @@ -76,7 +79,7 @@ void QgsComposerShapeWidget::setGuiElementValues() blockAllSignals( true ); - updateShapeStyle(); + mShapeStyleButton->setSymbol( mComposerShape->shapeStyleSymbol()->clone() ); mCornerRadiusSpinBox->setValue( mComposerShape->cornerRadius() ); if ( mComposerShape->shapeType() == QgsComposerShape::Ellipse ) @@ -98,38 +101,16 @@ void QgsComposerShapeWidget::setGuiElementValues() blockAllSignals( false ); } -void QgsComposerShapeWidget::on_mShapeStyleButton_clicked() +void QgsComposerShapeWidget::symbolChanged() { if ( !mComposerShape ) { return; } - // use the atlas coverage layer, if any - QgsVectorLayer *coverageLayer = atlasCoverageLayer(); - - QgsFillSymbol *newSymbol = mComposerShape->shapeStyleSymbol()->clone(); - QgsExpressionContext context = mComposerShape->createExpressionContext(); - - QgsSymbolSelectorWidget *d = new QgsSymbolSelectorWidget( newSymbol, QgsStyle::defaultStyle(), coverageLayer, nullptr ); - QgsSymbolWidgetContext symbolContext; - symbolContext.setExpressionContext( &context ); - d->setContext( symbolContext ); - - connect( d, &QgsPanelWidget::widgetChanged, this, &QgsComposerShapeWidget::updateSymbolFromWidget ); - connect( d, &QgsPanelWidget::panelAccepted, this, &QgsComposerShapeWidget::cleanUpSymbolSelector ); - openPanel( d ); mComposerShape->beginCommand( tr( "Shape style changed" ) ); -} - -void QgsComposerShapeWidget::updateShapeStyle() -{ - if ( mComposerShape ) - { - mComposerShape->refreshSymbol(); - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mComposerShape->shapeStyleSymbol(), mShapeStyleButton->iconSize() ); - mShapeStyleButton->setIcon( icon ); - } + mComposerShape->setShapeStyleSymbol( mShapeStyleButton->clonedSymbol() ); + mComposerShape->endCommand(); } void QgsComposerShapeWidget::on_mCornerRadiusSpinBox_valueChanged( double val ) @@ -179,23 +160,3 @@ void QgsComposerShapeWidget::toggleRadiusSpin( const QString &shapeText ) mCornerRadiusSpinBox->setEnabled( false ); } } - -void QgsComposerShapeWidget::updateSymbolFromWidget() -{ - if ( QgsSymbolSelectorWidget *w = qobject_cast( sender() ) ) - mComposerShape->setShapeStyleSymbol( static_cast< QgsFillSymbol * >( w->symbol() ) ); -} - -void QgsComposerShapeWidget::cleanUpSymbolSelector( QgsPanelWidget *container ) -{ - QgsSymbolSelectorWidget *w = qobject_cast( container ); - if ( !w ) - return; - - delete w->symbol(); - updateShapeStyle(); - mComposerShape->endCommand(); -} - - - diff --git a/src/app/composer/qgscomposershapewidget.h b/src/app/composer/qgscomposershapewidget.h index 8aa62b8c73f..21aaccefdd7 100644 --- a/src/app/composer/qgscomposershapewidget.h +++ b/src/app/composer/qgscomposershapewidget.h @@ -40,17 +40,13 @@ class QgsComposerShapeWidget: public QgsComposerItemBaseWidget, private Ui::QgsC private slots: void on_mShapeComboBox_currentIndexChanged( const QString &text ); void on_mCornerRadiusSpinBox_valueChanged( double val ); - void on_mShapeStyleButton_clicked(); + void symbolChanged(); //! Sets the GUI elements to the currentValues of mComposerShape void setGuiElementValues(); - void updateShapeStyle(); - //! Enables or disables the rounded radius spin box based on shape type void toggleRadiusSpin( const QString &shapeText ); - void updateSymbolFromWidget(); - void cleanUpSymbolSelector( QgsPanelWidget *container ); }; #endif // QGSCOMPOSERSHAPEWIDGET_H diff --git a/src/gui/qgssymbolbutton.h b/src/gui/qgssymbolbutton.h index 972de290a0b..8df81616981 100644 --- a/src/gui/qgssymbolbutton.h +++ b/src/gui/qgssymbolbutton.h @@ -72,6 +72,28 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton */ QgsSymbol *symbol(); + /** + * Returns a clone of the current symbol (as the specified template type) defined by the button. + * \see setSymbol() + * \see changed() + * \note Not available in Python bindings. + */ + template SymbolType *clonedSymbol() SIP_SKIP + { + QgsSymbol *tmpSymbol = mSymbol.get(); + SymbolType *symbolCastToType = dynamic_cast( tmpSymbol ); + + if ( symbolCastToType ) + { + return symbolCastToType->clone(); + } + else + { + //could not cast + return nullptr; + } + } + /** * Returns the map canvas associated with the widget. * \see setMapCanvas() diff --git a/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp b/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp index 350f670e70c..0a91a9a5700 100644 --- a/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp +++ b/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp @@ -79,6 +79,7 @@ QgsPointClusterRendererWidget::QgsPointClusterRendererWidget( QgsVectorLayer *la mDistanceSpinBox->setValue( mRenderer->tolerance() ); mDistanceUnitWidget->setUnit( mRenderer->toleranceUnit() ); mDistanceUnitWidget->setMapUnitScale( mRenderer->toleranceMapUnitScale() ); + mCenterSymbolToolButton->setSymbol( mRenderer->clusterSymbol()->clone() ); blockAllSignals( false ); @@ -94,7 +95,10 @@ QgsPointClusterRendererWidget::QgsPointClusterRendererWidget( QgsVectorLayer *la } } - updateCenterIcon(); + connect( mCenterSymbolToolButton, &QgsSymbolButton::changed, this, &QgsPointClusterRendererWidget::centerSymbolChanged ); + mCenterSymbolToolButton->setDialogTitle( tr( "Cluster symbol" ) ); + mCenterSymbolToolButton->setLayer( mLayer ); + mCenterSymbolToolButton->registerExpressionContextGenerator( this ); } QgsPointClusterRendererWidget::~QgsPointClusterRendererWidget() @@ -112,6 +116,8 @@ void QgsPointClusterRendererWidget::setContext( const QgsSymbolWidgetContext &co QgsRendererWidget::setContext( context ); if ( mDistanceUnitWidget ) mDistanceUnitWidget->setMapCanvas( context.mapCanvas() ); + if ( mCenterSymbolToolButton ) + mCenterSymbolToolButton->setMapCanvas( context.mapCanvas() ); } void QgsPointClusterRendererWidget::on_mRendererComboBox_currentIndexChanged( int index ) @@ -175,53 +181,34 @@ void QgsPointClusterRendererWidget::on_mDistanceUnitWidget_changed() void QgsPointClusterRendererWidget::blockAllSignals( bool block ) { mRendererComboBox->blockSignals( block ); - mCenterSymbolPushButton->blockSignals( block ); + mCenterSymbolToolButton->blockSignals( block ); mDistanceSpinBox->blockSignals( block ); mDistanceUnitWidget->blockSignals( block ); } -void QgsPointClusterRendererWidget::on_mCenterSymbolPushButton_clicked() +QgsExpressionContext QgsPointClusterRendererWidget::createExpressionContext() const { - if ( !mRenderer || !mRenderer->clusterSymbol() ) - { - return; - } - QgsMarkerSymbol *markerSymbol = mRenderer->clusterSymbol()->clone(); - QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( markerSymbol, QgsStyle::defaultStyle(), mLayer, this ); - dlg->setPanelTitle( tr( "Cluster symbol" ) ); - dlg->setDockMode( this->dockMode() ); - - QgsSymbolWidgetContext context = mContext; + QgsExpressionContext context; + if ( mContext.expressionContext() ) + context = *mContext.expressionContext(); + else + context.appendScopes( mContext.globalProjectAtlasMapLayerScopes( mLayer ) ); QgsExpressionContextScope scope; scope.addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_CLUSTER_COLOR, "", true ) ); scope.addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_CLUSTER_SIZE, 0, true ) ); - QList< QgsExpressionContextScope > scopes = context.additionalExpressionContextScopes(); + QList< QgsExpressionContextScope > scopes = mContext.additionalExpressionContextScopes(); scopes << scope; - context.setAdditionalExpressionContextScopes( scopes ); - - dlg->setContext( context ); - - connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsPointClusterRendererWidget::updateCenterSymbolFromWidget ); - connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsPointClusterRendererWidget::cleanUpSymbolSelector ); - openPanel( dlg ); -} - -void QgsPointClusterRendererWidget::updateCenterSymbolFromWidget() -{ - QgsSymbolSelectorWidget *dlg = qobject_cast( sender() ); - QgsSymbol *symbol = dlg->symbol()->clone(); - mRenderer->setClusterSymbol( static_cast< QgsMarkerSymbol * >( symbol ) ); - updateCenterIcon(); - emit widgetChanged(); -} - -void QgsPointClusterRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container ) -{ - if ( container ) + Q_FOREACH ( const QgsExpressionContextScope &s, scopes ) { - QgsSymbolSelectorWidget *dlg = qobject_cast( container ); - delete dlg->symbol(); + context << new QgsExpressionContextScope( s ); } + return context; +} + +void QgsPointClusterRendererWidget::centerSymbolChanged() +{ + mRenderer->setClusterSymbol( mCenterSymbolToolButton->clonedSymbol< QgsMarkerSymbol >() ); + emit widgetChanged(); } void QgsPointClusterRendererWidget::updateRendererFromWidget() @@ -234,17 +221,6 @@ void QgsPointClusterRendererWidget::updateRendererFromWidget() emit widgetChanged(); } -void QgsPointClusterRendererWidget::updateCenterIcon() -{ - QgsMarkerSymbol *symbol = mRenderer->clusterSymbol(); - if ( !symbol ) - { - return; - } - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( symbol, mCenterSymbolPushButton->iconSize() ); - mCenterSymbolPushButton->setIcon( icon ); -} - void QgsPointClusterRendererWidget::setupBlankUi( const QString &layerName ) { QGridLayout *layout = new QGridLayout( this ); diff --git a/src/gui/symbology-ng/qgspointclusterrendererwidget.h b/src/gui/symbology-ng/qgspointclusterrendererwidget.h index fa3207e30e3..4d11872165d 100644 --- a/src/gui/symbology-ng/qgspointclusterrendererwidget.h +++ b/src/gui/symbology-ng/qgspointclusterrendererwidget.h @@ -21,6 +21,7 @@ #include "ui_qgspointclusterrendererwidgetbase.h" #include "qgis.h" #include "qgsrendererwidget.h" +#include "qgsexpressioncontextgenerator.h" #include "qgis_gui.h" class QgsPointClusterRenderer; @@ -31,9 +32,10 @@ class QgsPointClusterRenderer; * \since QGIS 3.0 */ -class GUI_EXPORT QgsPointClusterRendererWidget: public QgsRendererWidget, private Ui::QgsPointClusterRendererWidgetBase +class GUI_EXPORT QgsPointClusterRendererWidget: public QgsRendererWidget, public QgsExpressionContextGenerator, private Ui::QgsPointClusterRendererWidgetBase { Q_OBJECT + public: /** Returns a new QgsPointClusterRendererWidget. @@ -56,11 +58,12 @@ class GUI_EXPORT QgsPointClusterRendererWidget: public QgsRendererWidget, privat QgsFeatureRenderer *renderer() override; void setContext( const QgsSymbolWidgetContext &context ) override; + QgsExpressionContext createExpressionContext() const override; + private: QgsPointClusterRenderer *mRenderer = nullptr; void blockAllSignals( bool block ); - void updateCenterIcon(); void setupBlankUi( const QString &layerName ); private slots: @@ -68,10 +71,8 @@ class GUI_EXPORT QgsPointClusterRendererWidget: public QgsRendererWidget, privat void on_mRendererComboBox_currentIndexChanged( int index ); void on_mDistanceSpinBox_valueChanged( double d ); void on_mDistanceUnitWidget_changed(); - void on_mCenterSymbolPushButton_clicked(); void on_mRendererSettingsButton_clicked(); - void updateCenterSymbolFromWidget(); - void cleanUpSymbolSelector( QgsPanelWidget *container ); + void centerSymbolChanged(); void updateRendererFromWidget(); }; diff --git a/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp b/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp index 8e5a771acba..c457a02c17a 100644 --- a/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp +++ b/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp @@ -118,6 +118,7 @@ QgsPointDisplacementRendererWidget::QgsPointDisplacementRendererWidget( QgsVecto mDistanceSpinBox->setValue( mRenderer->tolerance() ); mDistanceUnitWidget->setUnit( mRenderer->toleranceUnit() ); mDistanceUnitWidget->setMapUnitScale( mRenderer->toleranceMapUnitScale() ); + mCenterSymbolToolButton->setSymbol( mRenderer->centerSymbol()->clone() ); mPlacementComboBox->setCurrentIndex( mPlacementComboBox->findData( mRenderer->placement() ) ); @@ -150,8 +151,10 @@ QgsPointDisplacementRendererWidget::QgsPointDisplacementRendererWidget( QgsVecto connect( mMinLabelScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsPointDisplacementRendererWidget::minLabelScaleChanged ); connect( mLabelFontButton, &QgsFontButton::changed, this, &QgsPointDisplacementRendererWidget::labelFontChanged ); - - updateCenterIcon(); + connect( mCenterSymbolToolButton, &QgsSymbolButton::changed, this, &QgsPointDisplacementRendererWidget::centerSymbolChanged ); + mCenterSymbolToolButton->setDialogTitle( tr( "Center symbol" ) ); + mCenterSymbolToolButton->setLayer( mLayer ); + mCenterSymbolToolButton->registerExpressionContextGenerator( this ); } QgsPointDisplacementRendererWidget::~QgsPointDisplacementRendererWidget() @@ -174,6 +177,29 @@ void QgsPointDisplacementRendererWidget::setContext( const QgsSymbolWidgetContex mMinLabelScaleWidget->setMapCanvas( context.mapCanvas() ); mMinLabelScaleWidget->setShowCurrentScaleButton( true ); } + if ( mCenterSymbolToolButton ) + { + mCenterSymbolToolButton->setMapCanvas( context.mapCanvas() ); + } +} + +QgsExpressionContext QgsPointDisplacementRendererWidget::createExpressionContext() const +{ + QgsExpressionContext context; + if ( mContext.expressionContext() ) + context = *mContext.expressionContext(); + else + context.appendScopes( mContext.globalProjectAtlasMapLayerScopes( mLayer ) ); + QgsExpressionContextScope scope; + scope.addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_CLUSTER_COLOR, "", true ) ); + scope.addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_CLUSTER_SIZE, 0, true ) ); + QList< QgsExpressionContextScope > scopes = mContext.additionalExpressionContextScopes(); + scopes << scope; + Q_FOREACH ( const QgsExpressionContextScope &s, scopes ) + { + context << new QgsExpressionContextScope( s ); + } + return context; } void QgsPointDisplacementRendererWidget::on_mLabelFieldComboBox_currentIndexChanged( const QString &text ) @@ -348,55 +374,18 @@ void QgsPointDisplacementRendererWidget::blockAllSignals( bool block ) mCircleModificationSpinBox->blockSignals( block ); mScaleDependentLabelsCheckBox->blockSignals( block ); mMinLabelScaleWidget->blockSignals( block ); - mCenterSymbolPushButton->blockSignals( block ); + mCenterSymbolToolButton->blockSignals( block ); mDistanceSpinBox->blockSignals( block ); mDistanceUnitWidget->blockSignals( block ); mPlacementComboBox->blockSignals( block ); } -void QgsPointDisplacementRendererWidget::on_mCenterSymbolPushButton_clicked() +void QgsPointDisplacementRendererWidget::centerSymbolChanged() { - if ( !mRenderer || !mRenderer->centerSymbol() ) - { - return; - } - QgsMarkerSymbol *markerSymbol = mRenderer->centerSymbol()->clone(); - QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( markerSymbol, QgsStyle::defaultStyle(), mLayer, this ); - dlg->setPanelTitle( tr( "Center symbol" ) ); - - QgsSymbolWidgetContext context = mContext; - - QgsExpressionContextScope scope; - scope.addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_CLUSTER_COLOR, "", true ) ); - scope.addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_CLUSTER_SIZE, 0, true ) ); - QList< QgsExpressionContextScope > scopes = context.additionalExpressionContextScopes(); - scopes << scope; - context.setAdditionalExpressionContextScopes( scopes ); - dlg->setContext( context ); - - connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsPointDisplacementRendererWidget::updateCenterSymbolFromWidget ); - connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsPointDisplacementRendererWidget::cleanUpSymbolSelector ); - openPanel( dlg ); -} - -void QgsPointDisplacementRendererWidget::updateCenterSymbolFromWidget() -{ - QgsSymbolSelectorWidget *dlg = qobject_cast( sender() ); - QgsSymbol *symbol = dlg->symbol()->clone(); - mRenderer->setCenterSymbol( static_cast< QgsMarkerSymbol * >( symbol ) ); - updateCenterIcon(); + mRenderer->setCenterSymbol( mCenterSymbolToolButton->clonedSymbol< QgsMarkerSymbol >() ); emit widgetChanged(); } -void QgsPointDisplacementRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container ) -{ - if ( container ) - { - QgsSymbolSelectorWidget *dlg = qobject_cast( container ); - delete dlg->symbol(); - } -} - void QgsPointDisplacementRendererWidget::updateRendererFromWidget() { QgsRendererWidget *w = qobject_cast( sender() ); @@ -407,17 +396,6 @@ void QgsPointDisplacementRendererWidget::updateRendererFromWidget() emit widgetChanged(); } -void QgsPointDisplacementRendererWidget::updateCenterIcon() -{ - QgsMarkerSymbol *symbol = mRenderer->centerSymbol(); - if ( !symbol ) - { - return; - } - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( symbol, mCenterSymbolPushButton->iconSize() ); - mCenterSymbolPushButton->setIcon( icon ); -} - void QgsPointDisplacementRendererWidget::setupBlankUi( const QString &layerName ) { QLabel *label = new QLabel( tr( "The point displacement renderer only applies to (single) point layers. \n'%1' is not a point layer and cannot be displayed by the point displacement renderer" ).arg( layerName ), this ); diff --git a/src/gui/symbology-ng/qgspointdisplacementrendererwidget.h b/src/gui/symbology-ng/qgspointdisplacementrendererwidget.h index 498299a13a3..e339f97fc35 100644 --- a/src/gui/symbology-ng/qgspointdisplacementrendererwidget.h +++ b/src/gui/symbology-ng/qgspointdisplacementrendererwidget.h @@ -21,6 +21,7 @@ #include "ui_qgspointdisplacementrendererwidgetbase.h" #include "qgis.h" #include "qgsrendererwidget.h" +#include "qgsexpressioncontextgenerator.h" #include "qgis_gui.h" class QgsPointDisplacementRenderer; @@ -28,7 +29,7 @@ class QgsPointDisplacementRenderer; /** \ingroup gui * \class QgsPointDisplacementRendererWidget */ -class GUI_EXPORT QgsPointDisplacementRendererWidget: public QgsRendererWidget, private Ui::QgsPointDisplacementRendererWidgetBase +class GUI_EXPORT QgsPointDisplacementRendererWidget: public QgsRendererWidget, public QgsExpressionContextGenerator, private Ui::QgsPointDisplacementRendererWidgetBase { Q_OBJECT public: @@ -39,11 +40,12 @@ class GUI_EXPORT QgsPointDisplacementRendererWidget: public QgsRendererWidget, p QgsFeatureRenderer *renderer() override; void setContext( const QgsSymbolWidgetContext &context ) override; + QgsExpressionContext createExpressionContext() const override; + private: QgsPointDisplacementRenderer *mRenderer = nullptr; void blockAllSignals( bool block ); - void updateCenterIcon(); void setupBlankUi( const QString &layerName ); private slots: @@ -59,10 +61,8 @@ class GUI_EXPORT QgsPointDisplacementRendererWidget: public QgsRendererWidget, p void on_mCircleModificationSpinBox_valueChanged( double d ); void on_mScaleDependentLabelsCheckBox_stateChanged( int state ); void minLabelScaleChanged( double scale ); - void on_mCenterSymbolPushButton_clicked(); void on_mRendererSettingsButton_clicked(); - void updateCenterSymbolFromWidget(); - void cleanUpSymbolSelector( QgsPanelWidget *container ); + void centerSymbolChanged(); void updateRendererFromWidget(); }; diff --git a/src/ui/composer/qgscomposershapewidgetbase.ui b/src/ui/composer/qgscomposershapewidgetbase.ui index b6f1428600b..442c4e4b255 100644 --- a/src/ui/composer/qgscomposershapewidgetbase.ui +++ b/src/ui/composer/qgscomposershapewidgetbase.ui @@ -96,8 +96,11 @@ + + + - + 0 @@ -109,9 +112,6 @@ - - - @@ -122,6 +122,11 @@ + + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
QgsScrollArea QScrollArea @@ -135,9 +140,9 @@ 1 - QgsDoubleSpinBox - QDoubleSpinBox -
qgsdoublespinbox.h
+ QgsSymbolButton + QToolButton +
qgssymbolbutton.h
diff --git a/src/ui/composer/qgscompositionwidgetbase.ui b/src/ui/composer/qgscompositionwidgetbase.ui index a366e3d94eb..98fffd9d545 100644 --- a/src/ui/composer/qgscompositionwidgetbase.ui +++ b/src/ui/composer/qgscompositionwidgetbase.ui @@ -663,6 +663,11 @@ + + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
QgsScrollArea QScrollArea @@ -681,11 +686,6 @@
qgscollapsiblegroupbox.h
1
- - QgsDoubleSpinBox - QDoubleSpinBox -
qgsdoublespinbox.h
-
QgsSpinBox QSpinBox diff --git a/src/ui/qgspointdisplacementrendererwidgetbase.ui b/src/ui/qgspointdisplacementrendererwidgetbase.ui index 2f5e5ab5bee..07fd8057935 100755 --- a/src/ui/qgspointdisplacementrendererwidgetbase.ui +++ b/src/ui/qgspointdisplacementrendererwidgetbase.ui @@ -136,13 +136,6 @@
- - - - - - - @@ -272,14 +265,26 @@ + + + + + 0 + 0 + + + + + + + - QgsColorButton - QToolButton -
qgscolorbutton.h
- 1 + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
QgsCollapsibleGroupBoxBasic @@ -288,9 +293,15 @@ 1 - QgsDoubleSpinBox - QDoubleSpinBox -
qgsdoublespinbox.h
+ QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
+ + QgsColorButton + QToolButton +
qgscolorbutton.h
+ 1
QgsFontButton @@ -310,7 +321,7 @@
- mCenterSymbolPushButton + mCenterSymbolToolButton mRendererComboBox mRendererSettingsButton mDistanceSpinBox diff --git a/src/ui/symbollayer/qgspointclusterrendererwidgetbase.ui b/src/ui/symbollayer/qgspointclusterrendererwidgetbase.ui index e93debe9256..650b909e953 100644 --- a/src/ui/symbollayer/qgspointclusterrendererwidgetbase.ui +++ b/src/ui/symbollayer/qgspointclusterrendererwidgetbase.ui @@ -26,13 +26,6 @@ 0 - - - - - - - @@ -98,6 +91,19 @@
+ + + + + 0 + 0 + + + + + + + @@ -106,6 +112,11 @@ QDoubleSpinBox
qgsdoublespinbox.h
+ + QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
QgsUnitSelectionWidget QWidget @@ -114,7 +125,7 @@
- mCenterSymbolPushButton + mCenterSymbolToolButton mRendererComboBox mRendererSettingsButton mDistanceSpinBox From 041479478756f4bb1f129a9ba552312e8bb2cb5e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 14:58:52 +1000 Subject: [PATCH 122/266] Use symbol button in annotation config widgets --- src/app/qgsannotationwidget.cpp | 71 ++--------------------------- src/app/qgsannotationwidget.h | 6 --- src/app/qgsformannotationdialog.cpp | 1 + src/app/qgshtmlannotationdialog.cpp | 1 + src/app/qgssvgannotationdialog.cpp | 1 + src/ui/qgsannotationwidgetbase.ui | 21 ++++++++- 6 files changed, 26 insertions(+), 75 deletions(-) diff --git a/src/app/qgsannotationwidget.cpp b/src/app/qgsannotationwidget.cpp index 04071d1c1c0..98363a5b4b9 100644 --- a/src/app/qgsannotationwidget.cpp +++ b/src/app/qgsannotationwidget.cpp @@ -28,7 +28,6 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsMapCanvasAnnotationItem *item, QWidget *parent, Qt::WindowFlags f ) : QWidget( parent, f ) , mItem( item ) - , mMarkerSymbol( nullptr ) { setupUi( this ); mLayerComboBox->setAllowEmptyLayer( true ); @@ -57,14 +56,12 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsMapCanvasAnnotationItem *item, QWid const QgsMarkerSymbol *symbol = annotation->markerSymbol(); if ( symbol ) { - mMarkerSymbol.reset( symbol->clone() ); - updateCenterIcon(); + mMapMarkerButton->setSymbol( symbol->clone() ); } const QgsFillSymbol *fill = annotation->fillSymbol(); if ( fill ) { - mFillSymbol.reset( fill->clone() ); - updateFillIcon(); + mFrameStyleButton->setSymbol( fill->clone() ); } blockAllSignals( false ); @@ -83,8 +80,8 @@ void QgsAnnotationWidget::apply() if ( annotation ) { annotation->setHasFixedMapPosition( mMapPositionFixedCheckBox->checkState() == Qt::Checked ); - annotation->setFillSymbol( mFillSymbol->clone() ); - annotation->setMarkerSymbol( mMarkerSymbol->clone() ); + annotation->setFillSymbol( mFrameStyleButton->clonedSymbol() ); + annotation->setMarkerSymbol( mMapMarkerButton->clonedSymbol() ); annotation->setMapLayer( mLayerComboBox->currentLayer() ); annotation->setContentsMargin( QgsMargins( mSpinLeftMargin->value(), mSpinTopMargin->value(), @@ -101,63 +98,3 @@ void QgsAnnotationWidget::blockAllSignals( bool block ) mMapMarkerButton->blockSignals( block ); mLayerComboBox->blockSignals( block ); } - -void QgsAnnotationWidget::on_mMapMarkerButton_clicked() -{ - if ( !mMarkerSymbol ) - { - return; - } - QgsMarkerSymbol *markerSymbol = mMarkerSymbol->clone(); - QgsSymbolSelectorDialog dlg( markerSymbol, QgsStyle::defaultStyle(), nullptr, this ); - if ( dlg.exec() == QDialog::Rejected ) - { - delete markerSymbol; - } - else - { - mMarkerSymbol.reset( markerSymbol ); - updateCenterIcon(); - } -} - -void QgsAnnotationWidget::on_mFrameStyleButton_clicked() -{ - if ( !mFillSymbol ) - { - return; - } - QgsFillSymbol *fillSymbol = mFillSymbol->clone(); - QgsSymbolSelectorDialog dlg( fillSymbol, QgsStyle::defaultStyle(), nullptr, this ); - if ( dlg.exec() == QDialog::Rejected ) - { - delete fillSymbol; - } - else - { - mFillSymbol.reset( fillSymbol ); - updateFillIcon(); - backgroundColorChanged( fillSymbol->color() ); - } -} - -void QgsAnnotationWidget::updateCenterIcon() -{ - if ( !mMarkerSymbol ) - { - return; - } - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mMarkerSymbol.get(), mMapMarkerButton->iconSize() ); - mMapMarkerButton->setIcon( icon ); -} - -void QgsAnnotationWidget::updateFillIcon() -{ - if ( !mFillSymbol ) - { - return; - } - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mFillSymbol.get(), mFrameStyleButton->iconSize() ); - mFrameStyleButton->setIcon( icon ); -} - diff --git a/src/app/qgsannotationwidget.h b/src/app/qgsannotationwidget.h index 52ccb1808b1..2e499165ce3 100644 --- a/src/app/qgsannotationwidget.h +++ b/src/app/qgsannotationwidget.h @@ -42,14 +42,8 @@ class APP_EXPORT QgsAnnotationWidget: public QWidget, private Ui::QgsAnnotationW //! Emitted when the background color of the annotation is changed void backgroundColorChanged( const QColor &color ); - private slots: - void on_mMapMarkerButton_clicked(); - void on_mFrameStyleButton_clicked(); - private: QgsMapCanvasAnnotationItem *mItem = nullptr; - std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol; - std::unique_ptr< QgsFillSymbol > mFillSymbol; void blockAllSignals( bool block ); void updateCenterIcon(); diff --git a/src/app/qgsformannotationdialog.cpp b/src/app/qgsformannotationdialog.cpp index 90ee0b3f2e9..e292d166b01 100644 --- a/src/app/qgsformannotationdialog.cpp +++ b/src/app/qgsformannotationdialog.cpp @@ -22,6 +22,7 @@ #include #include #include +#include QgsFormAnnotationDialog::QgsFormAnnotationDialog( QgsMapCanvasAnnotationItem *item, QWidget *parent, Qt::WindowFlags f ) : QDialog( parent, f ) diff --git a/src/app/qgshtmlannotationdialog.cpp b/src/app/qgshtmlannotationdialog.cpp index ccad90fdddb..7c1f4488600 100644 --- a/src/app/qgshtmlannotationdialog.cpp +++ b/src/app/qgshtmlannotationdialog.cpp @@ -22,6 +22,7 @@ #include #include #include +#include QgsHtmlAnnotationDialog::QgsHtmlAnnotationDialog( QgsMapCanvasAnnotationItem *item, QWidget *parent, Qt::WindowFlags f ) : QDialog( parent, f ) diff --git a/src/app/qgssvgannotationdialog.cpp b/src/app/qgssvgannotationdialog.cpp index 27ad40e1452..89bad2b72d4 100644 --- a/src/app/qgssvgannotationdialog.cpp +++ b/src/app/qgssvgannotationdialog.cpp @@ -24,6 +24,7 @@ #include #include #include +#include QgsSvgAnnotationDialog::QgsSvgAnnotationDialog( QgsMapCanvasAnnotationItem *item, QWidget *parent, Qt::WindowFlags f ) : QDialog( parent, f ) diff --git a/src/ui/qgsannotationwidgetbase.ui b/src/ui/qgsannotationwidgetbase.ui index 0c512f1385d..002b7b5dff9 100644 --- a/src/ui/qgsannotationwidgetbase.ui +++ b/src/ui/qgsannotationwidgetbase.ui @@ -15,7 +15,13 @@ - + + + + 0 + 0 + + @@ -206,7 +212,13 @@ - + + + + 0 + 0 + + @@ -241,6 +253,11 @@
qgscollapsiblegroupbox.h
1 + + QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
mMapPositionFixedCheckBox From 6398b74426527754822c5ee634d53e1cfa4db090 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 17:35:55 +1000 Subject: [PATCH 123/266] Add opacity slider to symbol button menu --- src/gui/qgssymbolbutton.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/gui/qgssymbolbutton.cpp b/src/gui/qgssymbolbutton.cpp index b658f5b2f3a..3a640b8742e 100644 --- a/src/gui/qgssymbolbutton.cpp +++ b/src/gui/qgssymbolbutton.cpp @@ -244,11 +244,6 @@ void QgsSymbolButton::dropEvent( QDropEvent *e ) { //accept drop and set new color e->acceptProposedAction(); - - if ( hasAlpha ) - { - mSymbol->setOpacity( mimeColor.alphaF() ); - } mimeColor.setAlphaF( 1.0 ); mSymbol->setColor( mimeColor ); QgsRecentColorScheme::addRecentColor( mimeColor ); @@ -279,6 +274,23 @@ void QgsSymbolButton::prepareMenu() connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsSymbolButton::setColor ); mMenu->addAction( colorAction ); + QgsColorRampWidget *alphaRamp = new QgsColorRampWidget( mMenu, QgsColorWidget::Alpha, QgsColorRampWidget::Horizontal ); + QColor alphaColor = mSymbol->color(); + alphaColor.setAlphaF( mSymbol->opacity() ); + alphaRamp->setColor( alphaColor ); + QgsColorWidgetAction *alphaAction = new QgsColorWidgetAction( alphaRamp, mMenu, mMenu ); + alphaAction->setDismissOnColorSelection( false ); + connect( alphaAction, &QgsColorWidgetAction::colorChanged, this, [ = ]( const QColor & color ) + { + double opacity = color.alphaF(); + mSymbol->setOpacity( opacity ); + updatePreview(); + emit changed(); + } ); + connect( colorAction, &QgsColorWidgetAction::colorChanged, alphaRamp, [alphaRamp]( const QColor & color ) { alphaRamp->setColor( color, false ); } + ); + mMenu->addAction( alphaAction ); + //get schemes with ShowInColorButtonMenu flag set QList< QgsColorScheme * > schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorButtonMenu ); QList< QgsColorScheme * >::iterator it = schemeList.begin(); From 67b272406eddf0dbae4475b79dfee5cb280c8b13 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 17:50:33 +1000 Subject: [PATCH 124/266] Port decoration buttons to QgsSymbolButton --- src/app/qgsannotationwidget.cpp | 3 + src/app/qgsdecorationgriddialog.cpp | 85 ++--------------- src/app/qgsdecorationgriddialog.h | 4 - src/app/qgsdecorationlayoutextentdialog.cpp | 39 +------- src/app/qgsdecorationlayoutextentdialog.h | 2 - .../qgsnullsymbolrendererwidget.h | 1 - src/gui/symbology-ng/qgsrendererwidget.h | 1 - src/ui/qgsdecorationgriddialog.ui | 93 ++++++++++--------- src/ui/qgsdecorationlayoutextentdialog.ui | 51 +++++----- 9 files changed, 90 insertions(+), 189 deletions(-) diff --git a/src/app/qgsannotationwidget.cpp b/src/app/qgsannotationwidget.cpp index 98363a5b4b9..95014b8d095 100644 --- a/src/app/qgsannotationwidget.cpp +++ b/src/app/qgsannotationwidget.cpp @@ -22,6 +22,7 @@ #include "qgssymbollayerutils.h" #include "qgssymbol.h" #include "qgssymbolselectordialog.h" +#include "qgisapp.h" #include @@ -66,6 +67,8 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsMapCanvasAnnotationItem *item, QWid blockAllSignals( false ); } + mMapMarkerButton->setMapCanvas( QgisApp::instance()->mapCanvas() ); + mFrameStyleButton->setMapCanvas( QgisApp::instance()->mapCanvas() ); } QgsAnnotationWidget::~QgsAnnotationWidget() diff --git a/src/app/qgsdecorationgriddialog.cpp b/src/app/qgsdecorationgriddialog.cpp index 5aa8e256aad..070c6094956 100644 --- a/src/app/qgsdecorationgriddialog.cpp +++ b/src/app/qgsdecorationgriddialog.cpp @@ -31,8 +31,6 @@ QgsDecorationGridDialog::QgsDecorationGridDialog( QgsDecorationGrid &deco, QWidget *parent ) : QDialog( parent ) , mDeco( deco ) - , mLineSymbol( nullptr ) - , mMarkerSymbol( nullptr ) { setupUi( this ); @@ -65,6 +63,8 @@ QgsDecorationGridDialog::QgsDecorationGridDialog( QgsDecorationGrid &deco, QWidg connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsDecorationGridDialog::apply ); connect( mAnnotationFontButton, &QgsFontButton::changed, this, &QgsDecorationGridDialog::annotationFontChanged ); + + mMarkerSymbolButton->setMapCanvas( QgisApp::instance()->mapCanvas() ); } void QgsDecorationGridDialog::updateGuiElements() @@ -88,22 +88,8 @@ void QgsDecorationGridDialog::updateGuiElements() // mLineWidthSpinBox->setValue( gridPen.widthF() ); // mLineColorButton->setColor( gridPen.color() ); - if ( mLineSymbol ) - delete mLineSymbol; - if ( mDeco.lineSymbol() ) - { - mLineSymbol = static_cast( mDeco.lineSymbol()->clone() ); - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mLineSymbol, mLineSymbolButton->iconSize() ); - mLineSymbolButton->setIcon( icon ); - } - if ( mMarkerSymbol ) - delete mMarkerSymbol; - if ( mDeco.markerSymbol() ) - { - mMarkerSymbol = static_cast( mDeco.markerSymbol()->clone() ); - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mMarkerSymbol, mMarkerSymbolButton->iconSize() ); - mMarkerSymbolButton->setIcon( icon ); - } + mLineSymbolButton->setSymbol( mDeco.lineSymbol()->clone() ); + mMarkerSymbolButton->setSymbol( mDeco.markerSymbol()->clone() ); whileBlocking( mAnnotationFontButton )->setCurrentFont( mDeco.gridAnnotationFont() ); @@ -157,26 +143,14 @@ void QgsDecorationGridDialog::updateDecoFromGui() mDeco.setGridAnnotationDirection( QgsDecorationGrid::BoundaryDirection ); } mDeco.setGridAnnotationPrecision( mCoordinatePrecisionSpinBox->value() ); - if ( mLineSymbol ) - { - mDeco.setLineSymbol( mLineSymbol ); - mLineSymbol = mDeco.lineSymbol()->clone(); - } - if ( mMarkerSymbol ) - { - mDeco.setMarkerSymbol( mMarkerSymbol ); - mMarkerSymbol = mDeco.markerSymbol()->clone(); - } + mDeco.setLineSymbol( mLineSymbolButton->clonedSymbol< QgsLineSymbol >() ); + mDeco.setMarkerSymbol( mMarkerSymbolButton->clonedSymbol< QgsMarkerSymbol >() ); } QgsDecorationGridDialog::~QgsDecorationGridDialog() { QgsSettings settings; settings.setValue( QStringLiteral( "/Windows/DecorationGrid/geometry" ), saveGeometry() ); - if ( mLineSymbol ) - delete mLineSymbol; - if ( mMarkerSymbol ) - delete mMarkerSymbol; } void QgsDecorationGridDialog::on_buttonBox_helpRequested() @@ -210,53 +184,6 @@ void QgsDecorationGridDialog::on_mGridTypeComboBox_currentIndexChanged( int inde mMarkerSymbolButton->setEnabled( index == QgsDecorationGrid::Marker ); } - -void QgsDecorationGridDialog::on_mLineSymbolButton_clicked() -{ - if ( ! mLineSymbol ) - return; - - QgsLineSymbol *lineSymbol = mLineSymbol->clone(); - QgsSymbolSelectorDialog dlg( lineSymbol, QgsStyle::defaultStyle(), nullptr, this ); - if ( dlg.exec() == QDialog::Rejected ) - { - delete lineSymbol; - } - else - { - delete mLineSymbol; - mLineSymbol = lineSymbol; - if ( mLineSymbol ) - { - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mLineSymbol, mLineSymbolButton->iconSize() ); - mLineSymbolButton->setIcon( icon ); - } - } -} - -void QgsDecorationGridDialog::on_mMarkerSymbolButton_clicked() -{ - if ( ! mMarkerSymbol ) - return; - - QgsMarkerSymbol *markerSymbol = mMarkerSymbol->clone(); - QgsSymbolSelectorDialog dlg( markerSymbol, QgsStyle::defaultStyle(), nullptr, this ); - if ( dlg.exec() == QDialog::Rejected ) - { - delete markerSymbol; - } - else - { - delete mMarkerSymbol; - mMarkerSymbol = markerSymbol; - if ( mMarkerSymbol ) - { - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mMarkerSymbol, mMarkerSymbolButton->iconSize() ); - mMarkerSymbolButton->setIcon( icon ); - } - } -} - void QgsDecorationGridDialog::on_mPbtnUpdateFromExtents_clicked() { updateInterval( true ); diff --git a/src/app/qgsdecorationgriddialog.h b/src/app/qgsdecorationgriddialog.h index 510dea44276..5c81a4d6283 100644 --- a/src/app/qgsdecorationgriddialog.h +++ b/src/app/qgsdecorationgriddialog.h @@ -40,8 +40,6 @@ class APP_EXPORT QgsDecorationGridDialog : public QDialog, private Ui::QgsDecora void on_buttonBox_rejected(); void on_buttonBox_helpRequested(); void on_mGridTypeComboBox_currentIndexChanged( int index ); - void on_mLineSymbolButton_clicked(); - void on_mMarkerSymbolButton_clicked(); void on_mPbtnUpdateFromExtents_clicked(); void on_mPbtnUpdateFromLayer_clicked(); @@ -51,8 +49,6 @@ class APP_EXPORT QgsDecorationGridDialog : public QDialog, private Ui::QgsDecora private: QgsDecorationGrid &mDeco; - QgsLineSymbol *mLineSymbol = nullptr; - QgsMarkerSymbol *mMarkerSymbol = nullptr; void updateGuiElements(); void updateDecoFromGui(); diff --git a/src/app/qgsdecorationlayoutextentdialog.cpp b/src/app/qgsdecorationlayoutextentdialog.cpp index 29179e20a05..1db51351f41 100644 --- a/src/app/qgsdecorationlayoutextentdialog.cpp +++ b/src/app/qgsdecorationlayoutextentdialog.cpp @@ -44,19 +44,14 @@ QgsDecorationLayoutExtentDialog::QgsDecorationLayoutExtentDialog( QgsDecorationL updateGuiElements(); connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsDecorationLayoutExtentDialog::apply ); - connect( mSymbolButton, &QPushButton::clicked, this, &QgsDecorationLayoutExtentDialog::changeSymbol ); + + mSymbolButton->setMapCanvas( QgisApp::instance()->mapCanvas() ); } void QgsDecorationLayoutExtentDialog::updateGuiElements() { grpEnable->setChecked( mDeco.enabled() ); - - if ( mDeco.symbol() ) - { - mSymbol.reset( static_cast( mDeco.symbol()->clone() ) ); - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSymbol.get(), mSymbolButton->iconSize() ); - mSymbolButton->setIcon( icon ); - } + mSymbolButton->setSymbol( mDeco.symbol()->clone() ); mButtonFontStyle->setTextFormat( mDeco.textFormat() ); mCheckBoxLabelExtents->setChecked( mDeco.labelExtents() ); } @@ -64,11 +59,7 @@ void QgsDecorationLayoutExtentDialog::updateGuiElements() void QgsDecorationLayoutExtentDialog::updateDecoFromGui() { mDeco.setEnabled( grpEnable->isChecked() ); - - if ( mSymbol ) - { - mDeco.setSymbol( mSymbol->clone() ); - } + mDeco.setSymbol( mSymbolButton->clonedSymbol< QgsFillSymbol >() ); mDeco.setTextFormat( mButtonFontStyle->textFormat() ); mDeco.setLabelExtents( mCheckBoxLabelExtents->isChecked() ); } @@ -95,25 +86,3 @@ void QgsDecorationLayoutExtentDialog::on_buttonBox_rejected() { reject(); } - -void QgsDecorationLayoutExtentDialog::changeSymbol() -{ - if ( !mSymbol ) - return; - - QgsFillSymbol *symbol = mSymbol->clone(); - QgsSymbolSelectorDialog dlg( symbol, QgsStyle::defaultStyle(), nullptr, this ); - if ( dlg.exec() == QDialog::Rejected ) - { - delete symbol; - } - else - { - mSymbol.reset( symbol ); - if ( mSymbol ) - { - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSymbol.get(), mSymbolButton->iconSize() ); - mSymbolButton->setIcon( icon ); - } - } -} diff --git a/src/app/qgsdecorationlayoutextentdialog.h b/src/app/qgsdecorationlayoutextentdialog.h index 06505c62ec8..5b87ae9a8e6 100644 --- a/src/app/qgsdecorationlayoutextentdialog.h +++ b/src/app/qgsdecorationlayoutextentdialog.h @@ -39,11 +39,9 @@ class APP_EXPORT QgsDecorationLayoutExtentDialog : public QDialog, private Ui::Q void on_buttonBox_accepted(); void on_buttonBox_rejected(); - void changeSymbol(); private: QgsDecorationLayoutExtent &mDeco; - std::unique_ptr< QgsFillSymbol > mSymbol; void updateGuiElements(); void updateDecoFromGui(); diff --git a/src/gui/symbology-ng/qgsnullsymbolrendererwidget.h b/src/gui/symbology-ng/qgsnullsymbolrendererwidget.h index ec74027413f..4ce0e8bd04e 100644 --- a/src/gui/symbology-ng/qgsnullsymbolrendererwidget.h +++ b/src/gui/symbology-ng/qgsnullsymbolrendererwidget.h @@ -20,7 +20,6 @@ #include "qgis_gui.h" class QgsNullSymbolRenderer; -class QgsSymbolSelectorDialog; class QMenu; diff --git a/src/gui/symbology-ng/qgsrendererwidget.h b/src/gui/symbology-ng/qgsrendererwidget.h index e5a01683df9..bb82da9c3f3 100644 --- a/src/gui/symbology-ng/qgsrendererwidget.h +++ b/src/gui/symbology-ng/qgsrendererwidget.h @@ -28,7 +28,6 @@ class QgsDataDefinedSizeLegendWidget; class QgsVectorLayer; class QgsStyle; class QgsFeatureRenderer; -class QgsSymbolSelectorDialog; class QgsMapCanvas; /** \ingroup gui diff --git a/src/ui/qgsdecorationgriddialog.ui b/src/ui/qgsdecorationgriddialog.ui index b1fbe0fc3dc..6e6e2a4ecaa 100755 --- a/src/ui/qgsdecorationgriddialog.ui +++ b/src/ui/qgsdecorationgriddialog.ui @@ -180,28 +180,6 @@
- - - - false - - - - 0 - 0 - - - - - 100 - 0 - - - - - - - @@ -209,28 +187,6 @@ - - - - false - - - - 0 - 0 - - - - - 100 - 0 - - - - - - - @@ -300,6 +256,50 @@ + + + + false + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + + + + false + + + + 0 + 0 + + + + + 100 + 0 + + + + + + +
mOffsetYEdit mOffsetXEdit @@ -334,6 +334,11 @@ + + QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
QgsFontButton QToolButton diff --git a/src/ui/qgsdecorationlayoutextentdialog.ui b/src/ui/qgsdecorationlayoutextentdialog.ui index 62b00518e07..2dd8a18672c 100755 --- a/src/ui/qgsdecorationlayoutextentdialog.ui +++ b/src/ui/qgsdecorationlayoutextentdialog.ui @@ -62,28 +62,6 @@ - - - - false - - - - 0 - 0 - - - - - 100 - 0 - - - - - - - @@ -110,6 +88,28 @@ + + + + false + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + @@ -126,6 +126,11 @@ + + QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
QgsFontButton QToolButton @@ -142,7 +147,7 @@ grpEnable toggled(bool) - mSymbolButton + mLineSymbolLabel setEnabled(bool) From f7ada8138ff91085127005f48be6f97f76f3e52c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 18:10:13 +1000 Subject: [PATCH 125/266] Port conditional formatting symbol button to QgsSymbolButton --- .../qgsfieldconditionalformatwidget.cpp | 37 +++---------------- .../qgsfieldconditionalformatwidget.h | 2 - src/ui/qgsfieldconditionalformatwidget.ui | 20 +++------- 3 files changed, 11 insertions(+), 48 deletions(-) diff --git a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp index 0efe7ebfa9a..7c0a85cdf61 100644 --- a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp +++ b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp @@ -26,7 +26,6 @@ QgsFieldConditionalFormatWidget::QgsFieldConditionalFormatWidget( QWidget *paren , mLayer( nullptr ) , mEditIndex( 0 ) , mEditing( false ) - , mSymbol( nullptr ) { setupUi( this ); mDeleteButton->hide(); @@ -38,7 +37,6 @@ QgsFieldConditionalFormatWidget::QgsFieldConditionalFormatWidget( QWidget *paren connect( mCancelButton, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::cancelRule ); connect( mDeleteButton, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::deleteRule ); connect( listView, &QAbstractItemView::clicked, this, &QgsFieldConditionalFormatWidget::ruleClicked ); - connect( btnChangeIcon, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::updateIcon ); connect( btnBuildExpression, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::setExpression ); connect( mPresetsList, static_cast( &QComboBox::currentIndexChanged ), this, &QgsFieldConditionalFormatWidget::presetSet ); btnBackgroundColor->setAllowOpacity( true ); @@ -49,27 +47,13 @@ QgsFieldConditionalFormatWidget::QgsFieldConditionalFormatWidget( QWidget *paren mModel = new QStandardItemModel( listView ); listView->setModel( mModel ); mPresetsList->setModel( mPresetsModel ); + btnChangeIcon->setSymbol( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ); setPresets( defaultPresets() ); } QgsFieldConditionalFormatWidget::~QgsFieldConditionalFormatWidget() { - delete mSymbol; -} - -void QgsFieldConditionalFormatWidget::updateIcon() -{ - mSymbol = QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ); - - QgsSymbolSelectorDialog dlg( mSymbol, QgsStyle::defaultStyle(), nullptr, this ); - if ( !dlg.exec() ) - { - return; - } - - QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSymbol, btnChangeIcon->iconSize() ); - btnChangeIcon->setIcon( icon ); } void QgsFieldConditionalFormatWidget::setExpression() @@ -130,24 +114,14 @@ void QgsFieldConditionalFormatWidget::setFormattingFromStyle( const QgsCondition { btnBackgroundColor->setColor( style.backgroundColor() ); btnTextColor->setColor( style.textColor() ); - if ( !style.icon().isNull() ) + if ( style.symbol() ) { + btnChangeIcon->setSymbol( style.symbol()->clone() ); checkIcon->setChecked( true ); - QIcon icon( style.icon() ); - btnChangeIcon->setIcon( icon ); } else { checkIcon->setChecked( false ); - btnChangeIcon->setIcon( QIcon() ); - } - if ( style.symbol() ) - { - mSymbol = style.symbol()->clone(); - } - else - { - mSymbol = nullptr; } QFont font = style.font(); mFontBoldBtn->setChecked( font.bold() ); @@ -206,7 +180,6 @@ void QgsFieldConditionalFormatWidget::addNewRule() void QgsFieldConditionalFormatWidget::reset() { - mSymbol = nullptr; mNameEdit->clear(); mRuleEdit->clear(); if ( fieldRadio->isChecked() ) @@ -297,9 +270,9 @@ void QgsFieldConditionalFormatWidget::saveRule() style.setFont( font ); style.setBackgroundColor( backColor ); style.setTextColor( fontColor ); - if ( mSymbol && checkIcon->isChecked() ) + if ( checkIcon->isChecked() ) { - style.setSymbol( mSymbol ); + style.setSymbol( btnChangeIcon->clonedSymbol< QgsMarkerSymbol >() ); } else { diff --git a/src/gui/attributetable/qgsfieldconditionalformatwidget.h b/src/gui/attributetable/qgsfieldconditionalformatwidget.h index b839df14308..26794335a24 100644 --- a/src/gui/attributetable/qgsfieldconditionalformatwidget.h +++ b/src/gui/attributetable/qgsfieldconditionalformatwidget.h @@ -96,7 +96,6 @@ class GUI_EXPORT QgsFieldConditionalFormatWidget : public QWidget, private Ui::Q bool mEditing; QStandardItemModel *mModel = nullptr; QStandardItemModel *mPresetsModel = nullptr; - QgsSymbol *mSymbol = nullptr; QList mPresets; QList getStyles(); @@ -105,7 +104,6 @@ class GUI_EXPORT QgsFieldConditionalFormatWidget : public QWidget, private Ui::Q private slots: void setExpression(); - void updateIcon(); void presetSet( int index ); bool isCustomSet(); void ruleClicked( const QModelIndex &index ); diff --git a/src/ui/qgsfieldconditionalformatwidget.ui b/src/ui/qgsfieldconditionalformatwidget.ui index c67ee3efdb8..42a53dbe768 100644 --- a/src/ui/qgsfieldconditionalformatwidget.ui +++ b/src/ui/qgsfieldconditionalformatwidget.ui @@ -357,7 +357,7 @@ - + false @@ -384,19 +384,6 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -599,6 +586,11 @@ + + QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
QgsColorButton QToolButton From 43d094eab3815ec43fe909d013527cb15ae09b83 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 23 Jul 2017 18:23:02 +1000 Subject: [PATCH 126/266] Custom widget plugin for QgsSymbolButton --- src/customwidgets/CMakeLists.txt | 2 + src/customwidgets/qgiscustomwidgets.cpp | 3 +- src/customwidgets/qgssymbolbuttonplugin.cpp | 97 +++++++++++++++++++++ src/customwidgets/qgssymbolbuttonplugin.h | 51 +++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/customwidgets/qgssymbolbuttonplugin.cpp create mode 100644 src/customwidgets/qgssymbolbuttonplugin.h diff --git a/src/customwidgets/CMakeLists.txt b/src/customwidgets/CMakeLists.txt index 253a5615bc5..4aeb0f9b2e5 100644 --- a/src/customwidgets/CMakeLists.txt +++ b/src/customwidgets/CMakeLists.txt @@ -38,6 +38,7 @@ SET (QGIS_CUSTOMWIDGETS_SRCS qgsscalewidgetplugin.cpp qgsscrollareawidgetplugin.cpp qgsspinboxplugin.cpp + qgssymbolbuttonplugin.cpp ) SET (QGIS_CUSTOMWIDGETS_MOC_HDRS @@ -68,6 +69,7 @@ SET (QGIS_CUSTOMWIDGETS_MOC_HDRS qgsscalewidgetplugin.h qgsscrollareawidgetplugin.h qgsspinboxplugin.h + qgssymbolbuttonplugin.h ) IF(MSVC) diff --git a/src/customwidgets/qgiscustomwidgets.cpp b/src/customwidgets/qgiscustomwidgets.cpp index c3387701f68..d97ce1d307b 100644 --- a/src/customwidgets/qgiscustomwidgets.cpp +++ b/src/customwidgets/qgiscustomwidgets.cpp @@ -41,7 +41,7 @@ #include "qgsscalewidgetplugin.h" #include "qgsscrollareawidgetplugin.h" #include "qgsspinboxplugin.h" - +#include "qgssymbolbuttonplugin.h" QgisCustomWidgets::QgisCustomWidgets( QObject *parent ) : QObject( parent ) @@ -71,6 +71,7 @@ QgisCustomWidgets::QgisCustomWidgets( QObject *parent ) mWidgets.append( new QgsScaleWidgetPlugin( this ) ); mWidgets.append( new QgsScrollAreaWidgetPlugin( this ) ); mWidgets.append( new QgsSpinBoxPlugin( this ) ); + mWidgets.append( new QgsSymbolButtonPlugin( this ) ); } QList QgisCustomWidgets::customWidgets() const diff --git a/src/customwidgets/qgssymbolbuttonplugin.cpp b/src/customwidgets/qgssymbolbuttonplugin.cpp new file mode 100644 index 00000000000..0ecee3c9c48 --- /dev/null +++ b/src/customwidgets/qgssymbolbuttonplugin.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + qgssymbolbuttonplugin.cpp + ------------------------ + Date : 23.07.2017 + Copyright : (C) 2017 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 "qgiscustomwidgets.h" +#include "qgssymbolbuttonplugin.h" +#include "qgssymbolbutton.h" + + +QgsSymbolButtonPlugin::QgsSymbolButtonPlugin( QObject *parent ) + : QObject( parent ) + , mInitialized( false ) +{ +} + + +QString QgsSymbolButtonPlugin::name() const +{ + return "QgsSymbolButton"; +} + +QString QgsSymbolButtonPlugin::group() const +{ + return QgisCustomWidgets::groupName(); +} + +QString QgsSymbolButtonPlugin::includeFile() const +{ + return "qgssymbolbutton.h"; +} + +QIcon QgsSymbolButtonPlugin::icon() const +{ + return QIcon( ":/images/icons/qgis-icon-60x60.png" ); +} + +bool QgsSymbolButtonPlugin::isContainer() const +{ + return false; +} + +QWidget *QgsSymbolButtonPlugin::createWidget( QWidget *parent ) +{ + return new QgsSymbolButton( parent ); +} + +bool QgsSymbolButtonPlugin::isInitialized() const +{ + return mInitialized; +} + +void QgsSymbolButtonPlugin::initialize( QDesignerFormEditorInterface *core ) +{ + Q_UNUSED( core ); + if ( mInitialized ) + return; + mInitialized = true; +} + + +QString QgsSymbolButtonPlugin::toolTip() const +{ + return tr( "Select symbol" ); +} + +QString QgsSymbolButtonPlugin::whatsThis() const +{ + return ""; +} + +QString QgsSymbolButtonPlugin::domXml() const +{ + return QString( "\n" + " \n" + " \n" + " \n" + " 0\n" + " 0\n" + " 27\n" + " 27\n" + " \n" + " \n" + " \n" + "\n" ) + .arg( name() ); +} diff --git a/src/customwidgets/qgssymbolbuttonplugin.h b/src/customwidgets/qgssymbolbuttonplugin.h new file mode 100644 index 00000000000..3f4c7b09500 --- /dev/null +++ b/src/customwidgets/qgssymbolbuttonplugin.h @@ -0,0 +1,51 @@ +/*************************************************************************** + qgssymbolbuttonplugin.h + ---------------------- + Date : 23.07.2017 + Copyright : (C) 2017 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 QGSSYMBOLBUTTONPLUGIN_H +#define QGSSYMBOLBUTTONPLUGIN_H + + +#include +#include +#include +#include "qgis_customwidgets.h" + + +class CUSTOMWIDGETS_EXPORT QgsSymbolButtonPlugin : public QObject, public QDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + explicit QgsSymbolButtonPlugin( QObject *parent = 0 ); + + private: + bool mInitialized; + + // QDesignerCustomWidgetInterface interface + public: + QString name() const override; + QString group() const override; + QString includeFile() const override; + QIcon icon() const override; + bool isContainer() const override; + QWidget *createWidget( QWidget *parent ) override; + bool isInitialized() const override; + void initialize( QDesignerFormEditorInterface *core ) override; + QString toolTip() const override; + QString whatsThis() const override; + QString domXml() const override; +}; +#endif // QGSSYMBOLBUTTONPLUGIN_H From e304662a4f466ff0590f399000289b537f7c5bb2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Jul 2017 10:34:59 +1000 Subject: [PATCH 127/266] Use standard QgsUnitTypes::RenderUnit throughout labeling Instead of duplicate labeling specific unit enum, reuse the QgsUnitTypes::RenderUnit enum in labeling. This brings several improvements, including: - label offset/distance/repeat units now works correctly in all available unit types (inc pixels, map unit meters, points, inches, etc) - less duplicate code - labeling can use the robust QgsRenderContext methods for converting between different units and painter coordinates Also change comments for members to doxygen comments, so that these get included in the API docs. --- python/core/qgspallabeling.sip | 287 +++++++++++++++++++--- src/app/qgslabelinggui.cpp | 12 +- src/core/qgspallabeling.cpp | 156 +++++++----- src/core/qgspallabeling.h | 299 +++++++++++++++++++---- tests/src/core/testqgslabelingengine.cpp | 1 - 5 files changed, 607 insertions(+), 148 deletions(-) diff --git a/python/core/qgspallabeling.sip b/python/core/qgspallabeling.sip index 4da69383435..40db812ab7b 100644 --- a/python/core/qgspallabeling.sip +++ b/python/core/qgspallabeling.sip @@ -136,15 +136,6 @@ class QgsPalLayerSettings PolygonWhole }; - - enum SizeUnit - { - Points, - MM, - MapUnits, - Percent - }; - enum Property { // text @@ -282,15 +273,21 @@ class QgsPalLayerSettings QString fieldName; +%Docstring + Name of field (or an expression) to use for label text. + If fieldName is an expression, then isExpression should be set to true. +.. seealso:: isExpression +%End bool isExpression; %Docstring - Is this label made from a expression string, e.g., FieldName || 'mm' + True if this label is made from a expression string, e.g., FieldName || 'mm' +.. seealso:: fieldName %End QgsExpression *getLabelExpression(); %Docstring - Returns the QgsExpression for this label settings. + Returns the QgsExpression for this label settings. May be None if isExpression is false. :rtype: QgsExpression %End @@ -307,70 +304,228 @@ True if substitutions should be applied QString wrapChar; - MultiLineAlign multilineAlign; // horizontal alignment of multi-line labels +%Docstring + Wrapping character string. If set, any occurrences of this string in the calculated + label text will be replaced with new line characters. +%End + + MultiLineAlign multilineAlign; +%Docstring +Horizontal alignment of multi-line labels. +%End bool addDirectionSymbol; +%Docstring + If true, '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) + will be automatically added to the label text, pointing in the + direction of the line or polygon ring. + This setting only affects line or perimeter based labels. +.. seealso:: leftDirectionSymbol +.. seealso:: rightDirectionSymbol +.. seealso:: placeDirectionSymbol +.. seealso:: reverseDirectionSymbol +%End + QString leftDirectionSymbol; +%Docstring + String to use for left direction arrows. +.. seealso:: addDirectionSymbol +.. seealso:: rightDirectionSymbol +%End + QString rightDirectionSymbol; - DirectionSymbols placeDirectionSymbol; // whether to place left/right, above or below label +%Docstring + String to use for right direction arrows. +.. seealso:: addDirectionSymbol +.. seealso:: leftDirectionSymbol +%End + + DirectionSymbols placeDirectionSymbol; +%Docstring + Placement option for direction symbols. Controls whether to place symbols to the left/right, above or below label. +.. seealso:: addDirectionSymbol +%End + bool reverseDirectionSymbol; +%Docstring +True if direction symbols should be reversed +%End bool formatNumbers; +%Docstring + Set to true to format numeric label text as numbers (e.g. inserting thousand separators + and fixed number of decimal places). +.. seealso:: decimals +.. seealso:: plusSign +%End + int decimals; +%Docstring + Number of decimal places to show for numeric labels. formatNumbers must be true for this + setting to have an effect. +.. seealso:: formatNumbers +%End + bool plusSign; +%Docstring + Whether '+' signs should be prepended to positive numeric labels. formatNumbers must be true for this + setting to have an effect. +.. seealso:: formatNumbers +%End Placement placement; unsigned int placementFlags; - bool centroidWhole; // whether centroid calculated from whole or visible polygon - bool centroidInside; // whether centroid-point calculated must be inside polygon + bool centroidWhole; +%Docstring + True if feature centroid should be calculated from the whole feature, or + false if only the visible part of the feature should be considered. +%End + + bool centroidInside; +%Docstring + True if centroid positioned labels must be placed inside their corresponding + feature polygon, or false if centroids which fall outside the polygon + are permitted. +%End bool fitInPolygonOnly; %Docstring True if only labels which completely fit within a polygon are allowed. %End - double dist; // distance from the feature (in mm) - bool distInMapUnits; //true if distance is in map units (otherwise in mm) + + double dist; +%Docstring + Distance from feature to the label. Units are specified via distUnits. +.. seealso:: distUnits +.. seealso:: distMapUnitScale +%End + + QgsUnitTypes::RenderUnit distUnits; +%Docstring + Units the distance from feature to the label. +.. seealso:: dist +.. seealso:: distMapUnitScale +%End + QgsMapUnitScale distMapUnitScale; +%Docstring + Map unit scale for label feature distance. +.. seealso:: dist +.. seealso:: distUnits +%End + OffsetType offsetType; %Docstring Offset type for layer (only applies in certain placement modes) %End double repeatDistance; - SizeUnit repeatDistanceUnit; +%Docstring + Distance for repeating labels for a single feature. +.. seealso:: repeatDistanceUnit +.. seealso:: repeatDistanceMapUnitScale +%End + + QgsUnitTypes::RenderUnit repeatDistanceUnit; +%Docstring + Units for repeating labels for a single feature. +.. seealso:: repeatDistance +.. seealso:: repeatDistanceMapUnitScale +%End + QgsMapUnitScale repeatDistanceMapUnitScale; +%Docstring + Map unit scale for repeating labels for a single feature. +.. seealso:: repeatDistance +.. seealso:: repeatDistanceUnit +%End QuadrantPosition quadOffset; +%Docstring + Sets the quadrant in which to offset labels from feature. +%End + + double xOffset; +%Docstring + Horizontal offset of label. Units are specified via offsetUnits. +.. seealso:: yOffset +.. seealso:: offsetUnits +.. seealso:: labelOffsetMapUnitScale +%End + + double yOffset; +%Docstring + Vertical offset of label. Units are specified via offsetUnits. +.. seealso:: xOffset +.. seealso:: offsetUnits +.. seealso:: labelOffsetMapUnitScale +%End + + QgsUnitTypes::RenderUnit offsetUnits; +%Docstring + Units for offsets of label. +.. seealso:: xOffset +.. seealso:: yOffset +.. seealso:: labelOffsetMapUnitScale +%End - double xOffset; // offset from point in mm or map units - double yOffset; // offset from point in mm or map units - bool labelOffsetInMapUnits; //true if label offset is in map units (otherwise in mm) QgsMapUnitScale labelOffsetMapUnitScale; +%Docstring + Map unit scale for label offset. +.. seealso:: xOffset +.. seealso:: yOffset +.. seealso:: offsetUnits +%End double angleOffset; %Docstring Label rotation, in degrees clockwise %End - bool preserveRotation; // preserve predefined rotation data during label pin/unpin operations + bool preserveRotation; +%Docstring +True if label rotation should be preserved during label pin/unpin operations. +%End - double maxCurvedCharAngleIn; // maximum angle between inside curved label characters (defaults to 20.0, range 20.0 to 60.0) - double maxCurvedCharAngleOut; // maximum angle between outside curved label characters (defaults to -20.0, range -20.0 to -95.0) + double maxCurvedCharAngleIn; +%Docstring + Maximum angle between inside curved label characters (valid range 20.0 to 60.0). +.. seealso:: maxCurvedCharAngleOut +%End - int priority; // 0 = low, 10 = high + double maxCurvedCharAngleOut; +%Docstring + Maximum angle between outside curved label characters (valid range -20.0 to -95.0) +.. seealso:: maxCurvedCharAngleIn +%End + + int priority; +%Docstring + Label priority. Valid ranges are from 0 to 10, where 0 = lowest priority + and 10 = highest priority. +%End bool scaleVisibility; +%Docstring + Set to true to limit label visibility to a range of scales. +.. seealso:: maximumScale +.. seealso:: minimumScale +%End double maximumScale; %Docstring The maximum map scale (i.e. most "zoomed in" scale) at which the labels will be visible. The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. A scale of 0 indicates no maximum scale visibility. + + This setting is only considered if scaleVisibility is true. + .. seealso:: minimumScale +.. seealso:: scaleVisibility %End double minimumScale; @@ -378,34 +533,94 @@ Label rotation, in degrees clockwise The minimum map scale (i.e. most "zoomed out" scale) at which the labels will be visible. The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. A scale of 0 indicates no minimum scale visibility. + + This setting is only considered if scaleVisibility is true. + .. seealso:: maximumScale +.. seealso:: scaleVisibility %End - bool fontLimitPixelSize; // true is label should be limited by fontMinPixelSize/fontMaxPixelSize - int fontMinPixelSize; // minimum pixel size for showing rendered map unit labels (1 - 1000) - int fontMaxPixelSize; // maximum pixel size for showing rendered map unit labels (1 - 10000) + bool fontLimitPixelSize; +%Docstring + True if label sizes should be limited by pixel size. +.. seealso:: fontMinPixelSize +.. seealso:: fontMaxPixelSize +%End - bool displayAll; // if true, all features will be labelled even though overlaps occur - UpsideDownLabels upsidedownLabels; // whether, or how, to show upsidedown labels + int fontMinPixelSize; +%Docstring + Minimum pixel size for showing rendered map unit labels (1 - 1000). +.. seealso:: fontLimitPixelSize +.. seealso:: fontMaxPixelSize +%End + + int fontMaxPixelSize; +%Docstring + Maximum pixel size for showing rendered map unit labels (1 - 10000). +.. seealso:: fontLimitPixelSize +.. seealso:: fontMinPixelSize +%End + + bool displayAll; +%Docstring +If true, all features will be labelled even when overlaps occur. +%End + + UpsideDownLabels upsidedownLabels; +%Docstring +Controls whether upside down labels are displayed and how they are handled. +%End + + bool labelPerPart; +%Docstring + True if every part of a multi-part feature should be labeled. If false, + only the largest part will be labeled. +%End - bool labelPerPart; // whether to label every feature's part or only the biggest one bool mergeLines; +%Docstring + True if connected line features with identical label text should be merged + prior to generating label positions. +%End - bool limitNumLabels; // whether to limit the number of labels to be drawn - int maxNumLabels; // maximum number of labels to be drawn + bool limitNumLabels; +%Docstring + True if the number of labels drawn should be limited. +.. seealso:: maxNumLabels +%End - double minFeatureSize; // minimum feature size to be labelled (in mm) - bool obstacle; // whether features for layer are obstacles to labels of other layers + int maxNumLabels; +%Docstring + The maximum number of labels which should be drawn for this layer. + This only has an effect if limitNumLabels is true. +.. seealso:: limitNumLabels +%End + + double minFeatureSize; +%Docstring + Minimum feature size (in millimeters) for a feature to be labelled. +%End + + bool obstacle; +%Docstring + True if features for layer are obstacles to labels of other layers. +.. seealso:: obstacleFactor +.. seealso:: obstacleType +%End double obstacleFactor; %Docstring Obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels, > 1.0 less likely to be covered +.. seealso:: obstacle +.. seealso:: obstacleType %End ObstacleType obstacleType; %Docstring - Controls how features act as obstacles for labels + Controls how features act as obstacles for labels. +.. seealso:: obstacle +.. seealso:: obstacleFactor %End double zIndex; diff --git a/src/app/qgslabelinggui.cpp b/src/app/qgslabelinggui.cpp index 46d79faf5f8..1d7441c862e 100644 --- a/src/app/qgslabelinggui.cpp +++ b/src/app/qgslabelinggui.cpp @@ -160,13 +160,13 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer ) mCentroidInsideCheckBox->setChecked( lyr.centroidInside ); mFitInsidePolygonCheckBox->setChecked( lyr.fitInPolygonOnly ); mLineDistanceSpnBx->setValue( lyr.dist ); - mLineDistanceUnitWidget->setUnit( lyr.distInMapUnits ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters ); + mLineDistanceUnitWidget->setUnit( lyr.distUnits ); mLineDistanceUnitWidget->setMapUnitScale( lyr.distMapUnitScale ); mOffsetTypeComboBox->setCurrentIndex( mOffsetTypeComboBox->findData( lyr.offsetType ) ); mQuadrantBtnGrp->button( ( int )lyr.quadOffset )->setChecked( true ); mPointOffsetXSpinBox->setValue( lyr.xOffset ); mPointOffsetYSpinBox->setValue( lyr.yOffset ); - mPointOffsetUnitWidget->setUnit( lyr.labelOffsetInMapUnits ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters ); + mPointOffsetUnitWidget->setUnit( lyr.offsetUnits ); mPointOffsetUnitWidget->setMapUnitScale( lyr.labelOffsetMapUnitScale ); mPointAngleSpinBox->setValue( lyr.angleOffset ); chkLineAbove->setChecked( lyr.placementFlags & QgsPalLayerSettings::AboveLine ); @@ -209,7 +209,7 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer ) // Label repeat distance mRepeatDistanceSpinBox->setValue( lyr.repeatDistance ); - mRepeatDistanceUnitWidget->setUnit( lyr.repeatDistanceUnit == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters ); + mRepeatDistanceUnitWidget->setUnit( lyr.repeatDistanceUnit ); mRepeatDistanceUnitWidget->setMapUnitScale( lyr.repeatDistanceMapUnitScale ); mPrioritySlider->setValue( lyr.priority ); @@ -314,7 +314,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings() lyr.centroidInside = mCentroidInsideCheckBox->isChecked(); lyr.fitInPolygonOnly = mFitInsidePolygonCheckBox->isChecked(); lyr.dist = mLineDistanceSpnBx->value(); - lyr.distInMapUnits = ( mLineDistanceUnitWidget->unit() == QgsUnitTypes::RenderMapUnits ); + lyr.distUnits = mLineDistanceUnitWidget->unit(); lyr.distMapUnitScale = mLineDistanceUnitWidget->getMapUnitScale(); lyr.offsetType = static_cast< QgsPalLayerSettings::OffsetType >( mOffsetTypeComboBox->currentData().toInt() ); if ( mQuadrantBtnGrp ) @@ -323,7 +323,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings() } lyr.xOffset = mPointOffsetXSpinBox->value(); lyr.yOffset = mPointOffsetYSpinBox->value(); - lyr.labelOffsetInMapUnits = ( mPointOffsetUnitWidget->unit() == QgsUnitTypes::RenderMapUnits ); + lyr.offsetUnits = mPointOffsetUnitWidget->unit(); lyr.labelOffsetMapUnitScale = mPointOffsetUnitWidget->getMapUnitScale(); lyr.angleOffset = mPointAngleSpinBox->value(); if ( chkLineAbove->isChecked() ) @@ -376,7 +376,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings() } lyr.repeatDistance = mRepeatDistanceSpinBox->value(); - lyr.repeatDistanceUnit = mRepeatDistanceUnitWidget->unit() == QgsUnitTypes::RenderMapUnits ? QgsPalLayerSettings::MapUnits : QgsPalLayerSettings::MM; + lyr.repeatDistanceUnit = mRepeatDistanceUnitWidget->unit(); lyr.repeatDistanceMapUnitScale = mRepeatDistanceUnitWidget->getMapUnitScale(); lyr.previewBkgrdColor = mPreviewBackgroundBtn->color(); diff --git a/src/core/qgspallabeling.cpp b/src/core/qgspallabeling.cpp index 4f2b7001f9b..989506e0db8 100644 --- a/src/core/qgspallabeling.cpp +++ b/src/core/qgspallabeling.cpp @@ -269,9 +269,9 @@ QgsPalLayerSettings::QgsPalLayerSettings() quadOffset = QuadrantOver; xOffset = 0; yOffset = 0; - labelOffsetInMapUnits = true; + offsetUnits = QgsUnitTypes::RenderMillimeters; dist = 0; - distInMapUnits = false; + distUnits = QgsUnitTypes::RenderMillimeters; offsetType = FromPoint; angleOffset = 0; preserveRotation = true; @@ -279,7 +279,7 @@ QgsPalLayerSettings::QgsPalLayerSettings() maxCurvedCharAngleOut = -25.0; priority = 5; repeatDistance = 0; - repeatDistanceUnit = MM; + repeatDistanceUnit = QgsUnitTypes::RenderMillimeters; // rendering scaleVisibility = false; @@ -354,11 +354,11 @@ QgsPalLayerSettings &QgsPalLayerSettings::operator=( const QgsPalLayerSettings & quadOffset = s.quadOffset; xOffset = s.xOffset; yOffset = s.yOffset; - labelOffsetInMapUnits = s.labelOffsetInMapUnits; + offsetUnits = s.offsetUnits; labelOffsetMapUnitScale = s.labelOffsetMapUnitScale; dist = s.dist; offsetType = s.offsetType; - distInMapUnits = s.distInMapUnits; + distUnits = s.distUnits; distMapUnitScale = s.distMapUnitScale; angleOffset = s.angleOffset; preserveRotation = s.preserveRotation; @@ -419,16 +419,6 @@ QgsExpression *QgsPalLayerSettings::getLabelExpression() return expression; } -static QgsPalLayerSettings::SizeUnit _decodeUnits( const QString &str ) -{ - if ( str.compare( QLatin1String( "Point" ), Qt::CaseInsensitive ) == 0 - || str.compare( QLatin1String( "Points" ), Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::Points; - if ( str.compare( QLatin1String( "MapUnit" ), Qt::CaseInsensitive ) == 0 - || str.compare( QLatin1String( "MapUnits" ), Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::MapUnits; - if ( str.compare( QLatin1String( "Percent" ), Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::Percent; - return QgsPalLayerSettings::MM; // "MM" -} - static Qt::PenJoinStyle _decodePenJoinStyle( const QString &str ) { if ( str.compare( QLatin1String( "Miter" ), Qt::CaseInsensitive ) == 0 ) return Qt::MiterJoin; @@ -575,7 +565,7 @@ void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer ) predefinedPositionOrder = DEFAULT_PLACEMENT_ORDER; fitInPolygonOnly = layer->customProperty( QStringLiteral( "labeling/fitInPolygonOnly" ), QVariant( false ) ).toBool(); dist = layer->customProperty( QStringLiteral( "labeling/dist" ) ).toDouble(); - distInMapUnits = layer->customProperty( QStringLiteral( "labeling/distInMapUnits" ) ).toBool(); + distUnits = layer->customProperty( QStringLiteral( "labeling/distInMapUnits" ) ).toBool() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters; if ( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString().isEmpty() ) { //fallback to older property @@ -592,7 +582,11 @@ void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer ) quadOffset = static_cast< QuadrantPosition >( layer->customProperty( QStringLiteral( "labeling/quadOffset" ), QVariant( QuadrantOver ) ).toUInt() ); xOffset = layer->customProperty( QStringLiteral( "labeling/xOffset" ), QVariant( 0.0 ) ).toDouble(); yOffset = layer->customProperty( QStringLiteral( "labeling/yOffset" ), QVariant( 0.0 ) ).toDouble(); - labelOffsetInMapUnits = layer->customProperty( QStringLiteral( "labeling/labelOffsetInMapUnits" ), QVariant( true ) ).toBool(); + if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetInMapUnits" ), QVariant( true ) ).toBool() ) + offsetUnits = QgsUnitTypes::RenderMapUnits; + else + offsetUnits = QgsUnitTypes::RenderMillimeters; + if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString().isEmpty() ) { //fallback to older property @@ -622,7 +616,21 @@ void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer ) maxCurvedCharAngleOut = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleOut" ), QVariant( -25.0 ) ).toDouble(); priority = layer->customProperty( QStringLiteral( "labeling/priority" ) ).toInt(); repeatDistance = layer->customProperty( QStringLiteral( "labeling/repeatDistance" ), 0.0 ).toDouble(); - repeatDistanceUnit = static_cast< SizeUnit >( layer->customProperty( QStringLiteral( "labeling/repeatDistanceUnit" ), QVariant( MM ) ).toUInt() ); + switch ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceUnit" ), QVariant( 1 ) ).toUInt() ) + { + case 0: + repeatDistanceUnit = QgsUnitTypes::RenderPoints; + break; + case 1: + repeatDistanceUnit = QgsUnitTypes::RenderMillimeters; + break; + case 2: + repeatDistanceUnit = QgsUnitTypes::RenderMapUnits; + break; + case 3: + repeatDistanceUnit = QgsUnitTypes::RenderPercentage; + break; + } if ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString().isEmpty() ) { //fallback to older property @@ -765,7 +773,17 @@ void QgsPalLayerSettings::readXml( QDomElement &elem, const QgsReadWriteContext predefinedPositionOrder = DEFAULT_PLACEMENT_ORDER; fitInPolygonOnly = placementElem.attribute( QStringLiteral( "fitInPolygonOnly" ), QStringLiteral( "0" ) ).toInt(); dist = placementElem.attribute( QStringLiteral( "dist" ) ).toDouble(); - distInMapUnits = placementElem.attribute( QStringLiteral( "distInMapUnits" ) ).toInt(); + if ( !placementElem.hasAttribute( QStringLiteral( "distUnits" ) ) ) + { + if ( placementElem.attribute( QStringLiteral( "distInMapUnits" ) ).toInt() ) + distUnits = QgsUnitTypes::RenderMapUnits; + else + distUnits = QgsUnitTypes::RenderMillimeters; + } + else + { + distUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "distUnits" ) ) ); + } if ( !placementElem.hasAttribute( QStringLiteral( "distMapUnitScale" ) ) ) { //fallback to older property @@ -782,7 +800,14 @@ void QgsPalLayerSettings::readXml( QDomElement &elem, const QgsReadWriteContext quadOffset = static_cast< QuadrantPosition >( placementElem.attribute( QStringLiteral( "quadOffset" ), QString::number( QuadrantOver ) ).toUInt() ); xOffset = placementElem.attribute( QStringLiteral( "xOffset" ), QStringLiteral( "0" ) ).toDouble(); yOffset = placementElem.attribute( QStringLiteral( "yOffset" ), QStringLiteral( "0" ) ).toDouble(); - labelOffsetInMapUnits = placementElem.attribute( QStringLiteral( "labelOffsetInMapUnits" ), QStringLiteral( "1" ) ).toInt(); + if ( !placementElem.hasAttribute( QStringLiteral( "offsetUnits" ) ) ) + { + offsetUnits = placementElem.attribute( QStringLiteral( "labelOffsetInMapUnits" ), QStringLiteral( "1" ) ).toInt() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters; + } + else + { + offsetUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "offsetUnits" ) ) ); + } if ( !placementElem.hasAttribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) ) { //fallback to older property @@ -811,7 +836,29 @@ void QgsPalLayerSettings::readXml( QDomElement &elem, const QgsReadWriteContext maxCurvedCharAngleOut = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleOut" ), QStringLiteral( "-25" ) ).toDouble(); priority = placementElem.attribute( QStringLiteral( "priority" ) ).toInt(); repeatDistance = placementElem.attribute( QStringLiteral( "repeatDistance" ), QStringLiteral( "0" ) ).toDouble(); - repeatDistanceUnit = static_cast< SizeUnit >( placementElem.attribute( QStringLiteral( "repeatDistanceUnit" ), QString::number( MM ) ).toUInt() ); + if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceUnits" ) ) ) + { + // upgrade old setting + switch ( placementElem.attribute( QStringLiteral( "repeatDistanceUnit" ), QString::number( 1 ) ).toUInt() ) + { + case 0: + repeatDistanceUnit = QgsUnitTypes::RenderPoints; + break; + case 1: + repeatDistanceUnit = QgsUnitTypes::RenderMillimeters; + break; + case 2: + repeatDistanceUnit = QgsUnitTypes::RenderMapUnits; + break; + case 3: + repeatDistanceUnit = QgsUnitTypes::RenderPercentage; + break; + } + } + else + { + repeatDistanceUnit = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "repeatDistanceUnits" ) ) ); + } if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) ) { //fallback to older property @@ -938,13 +985,13 @@ QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWrite placementElem.setAttribute( QStringLiteral( "predefinedPositionOrder" ), QgsLabelingUtils::encodePredefinedPositionOrder( predefinedPositionOrder ) ); placementElem.setAttribute( QStringLiteral( "fitInPolygonOnly" ), fitInPolygonOnly ); placementElem.setAttribute( QStringLiteral( "dist" ), dist ); - placementElem.setAttribute( QStringLiteral( "distInMapUnits" ), distInMapUnits ); + placementElem.setAttribute( QStringLiteral( "distUnits" ), QgsUnitTypes::encodeUnit( distUnits ) ); placementElem.setAttribute( QStringLiteral( "distMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( distMapUnitScale ) ); placementElem.setAttribute( QStringLiteral( "offsetType" ), static_cast< unsigned int >( offsetType ) ); placementElem.setAttribute( QStringLiteral( "quadOffset" ), static_cast< unsigned int >( quadOffset ) ); placementElem.setAttribute( QStringLiteral( "xOffset" ), xOffset ); placementElem.setAttribute( QStringLiteral( "yOffset" ), yOffset ); - placementElem.setAttribute( QStringLiteral( "labelOffsetInMapUnits" ), labelOffsetInMapUnits ); + placementElem.setAttribute( QStringLiteral( "offsetUnits" ), QgsUnitTypes::encodeUnit( offsetUnits ) ); placementElem.setAttribute( QStringLiteral( "labelOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) ); placementElem.setAttribute( QStringLiteral( "rotationAngle" ), angleOffset ); placementElem.setAttribute( QStringLiteral( "preserveRotation" ), preserveRotation ); @@ -952,7 +999,7 @@ QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWrite placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleOut" ), maxCurvedCharAngleOut ); placementElem.setAttribute( QStringLiteral( "priority" ), priority ); placementElem.setAttribute( QStringLiteral( "repeatDistance" ), repeatDistance ); - placementElem.setAttribute( QStringLiteral( "repeatDistanceUnit" ), repeatDistanceUnit ); + placementElem.setAttribute( QStringLiteral( "repeatDistanceUnits" ), QgsUnitTypes::encodeUnit( repeatDistanceUnit ) ); placementElem.setAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) ); // rendering @@ -1605,7 +1652,7 @@ void QgsPalLayerSettings::registerFeature( QgsFeature &f, QgsRenderContext &cont } // data defined label offset units? - bool offinmapunits = labelOffsetInMapUnits; + QgsUnitTypes::RenderUnit offUnit = offsetUnits; exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetUnits, context.expressionContext() ); if ( exprVal.isValid() ) { @@ -1613,29 +1660,20 @@ void QgsPalLayerSettings::registerFeature( QgsFeature &f, QgsRenderContext &cont QgsDebugMsgLevel( QString( "exprVal OffsetUnits:%1" ).arg( units ), 4 ); if ( !units.isEmpty() ) { - offinmapunits = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits ); + bool ok = false; + QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + { + offUnit = decodedUnits; + } } } // adjust offset of labels to match chosen unit and map scale // offsets match those of symbology: -x = left, -y = up - double mapUntsPerMM = labelOffsetMapUnitScale.computeMapUnitsPerPixel( context ) * context.scaleFactor(); - if ( !qgsDoubleNear( xOff, 0.0 ) ) - { - offsetX = xOff; // must be positive to match symbology offset direction - if ( !offinmapunits ) - { - offsetX *= mapUntsPerMM; //convert offset from mm to map units - } - } - if ( !qgsDoubleNear( yOff, 0.0 ) ) - { - offsetY = -yOff; // must be negative to match symbology offset direction - if ( !offinmapunits ) - { - offsetY *= mapUntsPerMM; //convert offset from mm to map units - } - } + offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale ); + // must be negative to match symbology offset direction + offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale ); // layer defined rotation? // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature @@ -1782,7 +1820,7 @@ void QgsPalLayerSettings::registerFeature( QgsFeature &f, QgsRenderContext &cont double repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::RepeatDistance, context.expressionContext(), repeatDistance ); // data defined label-repeat distance units? - bool repeatdistinmapunit = repeatDistanceUnit == QgsPalLayerSettings::MapUnits; + QgsUnitTypes::RenderUnit repeatUnits = repeatDistanceUnit; exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::RepeatDistanceUnit, context.expressionContext() ); if ( exprVal.isValid() ) { @@ -1790,15 +1828,20 @@ void QgsPalLayerSettings::registerFeature( QgsFeature &f, QgsRenderContext &cont QgsDebugMsgLevel( QString( "exprVal RepeatDistanceUnits:%1" ).arg( units ), 4 ); if ( !units.isEmpty() ) { - repeatdistinmapunit = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits ); + bool ok = false; + QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + { + repeatUnits = decodedUnits; + } } } if ( !qgsDoubleNear( repeatDist, 0.0 ) ) { - if ( !repeatdistinmapunit ) + if ( repeatUnits != QgsUnitTypes::RenderMapUnits ) { - repeatDist *= mapUntsPerMM; //convert repeat distance from mm to map units + repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale ); } } @@ -1856,7 +1899,7 @@ void QgsPalLayerSettings::registerFeature( QgsFeature &f, QgsRenderContext &cont double distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::LabelDistance, context.expressionContext(), dist ); // data defined label-feature distance units? - bool distinmapunit = distInMapUnits; + QgsUnitTypes::RenderUnit distUnit = distUnits; exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DistanceUnits, context.expressionContext() ); if ( exprVal.isValid() ) { @@ -1864,18 +1907,15 @@ void QgsPalLayerSettings::registerFeature( QgsFeature &f, QgsRenderContext &cont QgsDebugMsgLevel( QString( "exprVal DistanceUnits:%1" ).arg( units ), 4 ); if ( !units.isEmpty() ) { - distinmapunit = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits ); + bool ok = false; + QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + { + distUnit = decodedUnits; + } } } - - if ( distinmapunit ) //convert distance from mm/map units to pixels - { - distance /= distMapUnitScale.computeMapUnitsPerPixel( context ); - } - else //mm - { - distance *= context.scaleFactor(); - } + distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale ); // when using certain placement modes, we force a tiny minimum distance. This ensures that // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours diff --git a/src/core/qgspallabeling.h b/src/core/qgspallabeling.h index 1ddcd22f844..04d193375b6 100644 --- a/src/core/qgspallabeling.h +++ b/src/core/qgspallabeling.h @@ -249,16 +249,6 @@ class CORE_EXPORT QgsPalLayerSettings placing labels over any part of the polygon is avoided.*/ }; - - //! Units used for option sizes, before being converted to rendered sizes - enum SizeUnit - { - Points = 0, - MM, - MapUnits, - Percent - }; - //! Data definable properties. enum Property { @@ -395,14 +385,22 @@ class CORE_EXPORT QgsPalLayerSettings //-- text style + /** + * Name of field (or an expression) to use for label text. + * If fieldName is an expression, then isExpression should be set to true. + * \see isExpression + */ QString fieldName; - /** Is this label made from a expression string, e.g., FieldName || 'mm' - */ + /** + * True if this label is made from a expression string, e.g., FieldName || 'mm' + * \see fieldName + */ bool isExpression; - /** Returns the QgsExpression for this label settings. - */ + /** + * Returns the QgsExpression for this label settings. May be nullptr if isExpression is false. + */ QgsExpression *getLabelExpression(); QColor previewBkgrdColor; @@ -414,20 +412,70 @@ class CORE_EXPORT QgsPalLayerSettings //-- text formatting + /** + * Wrapping character string. If set, any occurrences of this string in the calculated + * label text will be replaced with new line characters. + */ QString wrapChar; - MultiLineAlign multilineAlign; // horizontal alignment of multi-line labels - // Adds '<' or '>', or user-defined symbol to the label string pointing to the - // direction of the line / polygon ring - // Works only if Placement == Line + //! Horizontal alignment of multi-line labels. + MultiLineAlign multilineAlign; + + /** + * If true, '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) + * will be automatically added to the label text, pointing in the + * direction of the line or polygon ring. + * This setting only affects line or perimeter based labels. + * \see leftDirectionSymbol + * \see rightDirectionSymbol + * \see placeDirectionSymbol + * \see reverseDirectionSymbol + */ bool addDirectionSymbol; + + /** + * String to use for left direction arrows. + * \see addDirectionSymbol + * \see rightDirectionSymbol + */ QString leftDirectionSymbol; + + /** + * String to use for right direction arrows. + * \see addDirectionSymbol + * \see leftDirectionSymbol + */ QString rightDirectionSymbol; - DirectionSymbols placeDirectionSymbol; // whether to place left/right, above or below label + + /** + * Placement option for direction symbols. Controls whether to place symbols to the left/right, above or below label. + * \see addDirectionSymbol + */ + DirectionSymbols placeDirectionSymbol; + + //! True if direction symbols should be reversed bool reverseDirectionSymbol; + /** + * Set to true to format numeric label text as numbers (e.g. inserting thousand separators + * and fixed number of decimal places). + * \see decimals + * \see plusSign + */ bool formatNumbers; + + /** + * Number of decimal places to show for numeric labels. formatNumbers must be true for this + * setting to have an effect. + * \see formatNumbers + */ int decimals; + + /** + * Whether '+' signs should be prepended to positive numeric labels. formatNumbers must be true for this + * setting to have an effect. + * \see formatNumbers + */ bool plusSign; //-- placement @@ -435,57 +483,156 @@ class CORE_EXPORT QgsPalLayerSettings Placement placement; unsigned int placementFlags; - bool centroidWhole; // whether centroid calculated from whole or visible polygon - bool centroidInside; // whether centroid-point calculated must be inside polygon + /** + * True if feature centroid should be calculated from the whole feature, or + * false if only the visible part of the feature should be considered. + */ + bool centroidWhole; - /** Ordered list of predefined label positions for points. Positions earlier + /** + * True if centroid positioned labels must be placed inside their corresponding + * feature polygon, or false if centroids which fall outside the polygon + * are permitted. + */ + bool centroidInside; + + /** + * Ordered list of predefined label positions for points. Positions earlier * in the list will be prioritized over later positions. Only used when the placement * is set to QgsPalLayerSettings::OrderedPositionsAroundPoint. * \note not available in Python bindings */ QVector< PredefinedPointPosition > predefinedPositionOrder SIP_SKIP; - /** True if only labels which completely fit within a polygon are allowed. + /** + * True if only labels which completely fit within a polygon are allowed. */ bool fitInPolygonOnly; - double dist; // distance from the feature (in mm) - bool distInMapUnits; //true if distance is in map units (otherwise in mm) + + /** + * Distance from feature to the label. Units are specified via distUnits. + * \see distUnits + * \see distMapUnitScale + */ + double dist; + + /** + * Units the distance from feature to the label. + * \see dist + * \see distMapUnitScale + */ + QgsUnitTypes::RenderUnit distUnits; + + /** + * Map unit scale for label feature distance. + * \see dist + * \see distUnits + */ QgsMapUnitScale distMapUnitScale; + //! Offset type for layer (only applies in certain placement modes) OffsetType offsetType; + /** + * Distance for repeating labels for a single feature. + * \see repeatDistanceUnit + * \see repeatDistanceMapUnitScale + */ double repeatDistance; - SizeUnit repeatDistanceUnit; + + /** + * Units for repeating labels for a single feature. + * \see repeatDistance + * \see repeatDistanceMapUnitScale + */ + QgsUnitTypes::RenderUnit repeatDistanceUnit; + + /** + * Map unit scale for repeating labels for a single feature. + * \see repeatDistance + * \see repeatDistanceUnit + */ QgsMapUnitScale repeatDistanceMapUnitScale; - // offset labels of point/centroid features default to center - // move label to quadrant: left/down, don't move, right/up (-1, 0, 1) + /** + * Sets the quadrant in which to offset labels from feature. + */ QuadrantPosition quadOffset; - double xOffset; // offset from point in mm or map units - double yOffset; // offset from point in mm or map units - bool labelOffsetInMapUnits; //true if label offset is in map units (otherwise in mm) + /** + * Horizontal offset of label. Units are specified via offsetUnits. + * \see yOffset + * \see offsetUnits + * \see labelOffsetMapUnitScale + */ + double xOffset; + + /** + * Vertical offset of label. Units are specified via offsetUnits. + * \see xOffset + * \see offsetUnits + * \see labelOffsetMapUnitScale + */ + double yOffset; + + /** + * Units for offsets of label. + * \see xOffset + * \see yOffset + * \see labelOffsetMapUnitScale + */ + QgsUnitTypes::RenderUnit offsetUnits; + + /** + * Map unit scale for label offset. + * \see xOffset + * \see yOffset + * \see offsetUnits + */ QgsMapUnitScale labelOffsetMapUnitScale; //! Label rotation, in degrees clockwise double angleOffset; - bool preserveRotation; // preserve predefined rotation data during label pin/unpin operations + //! True if label rotation should be preserved during label pin/unpin operations. + bool preserveRotation; - double maxCurvedCharAngleIn; // maximum angle between inside curved label characters (defaults to 20.0, range 20.0 to 60.0) - double maxCurvedCharAngleOut; // maximum angle between outside curved label characters (defaults to -20.0, range -20.0 to -95.0) + /** + * Maximum angle between inside curved label characters (valid range 20.0 to 60.0). + * \see maxCurvedCharAngleOut + */ + double maxCurvedCharAngleIn; - int priority; // 0 = low, 10 = high + /** + * Maximum angle between outside curved label characters (valid range -20.0 to -95.0) + * \see maxCurvedCharAngleIn + */ + double maxCurvedCharAngleOut; + + /** + * Label priority. Valid ranges are from 0 to 10, where 0 = lowest priority + * and 10 = highest priority. + */ + int priority; //-- rendering + /** + * Set to true to limit label visibility to a range of scales. + * \see maximumScale + * \see minimumScale + */ bool scaleVisibility; /** * The maximum map scale (i.e. most "zoomed in" scale) at which the labels will be visible. * The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. * A scale of 0 indicates no maximum scale visibility. + * + * This setting is only considered if scaleVisibility is true. + * * \see minimumScale + * \see scaleVisibility */ double maximumScale; @@ -493,32 +640,90 @@ class CORE_EXPORT QgsPalLayerSettings * The minimum map scale (i.e. most "zoomed out" scale) at which the labels will be visible. * The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. * A scale of 0 indicates no minimum scale visibility. + * + * This setting is only considered if scaleVisibility is true. + * * \see maximumScale + * \see scaleVisibility */ double minimumScale; - bool fontLimitPixelSize; // true is label should be limited by fontMinPixelSize/fontMaxPixelSize - int fontMinPixelSize; // minimum pixel size for showing rendered map unit labels (1 - 1000) - int fontMaxPixelSize; // maximum pixel size for showing rendered map unit labels (1 - 10000) + /** + * True if label sizes should be limited by pixel size. + * \see fontMinPixelSize + * \see fontMaxPixelSize + */ + bool fontLimitPixelSize; - bool displayAll; // if true, all features will be labelled even though overlaps occur - UpsideDownLabels upsidedownLabels; // whether, or how, to show upsidedown labels + /** + * Minimum pixel size for showing rendered map unit labels (1 - 1000). + * \see fontLimitPixelSize + * \see fontMaxPixelSize + */ + int fontMinPixelSize; - bool labelPerPart; // whether to label every feature's part or only the biggest one + /** + * Maximum pixel size for showing rendered map unit labels (1 - 10000). + * \see fontLimitPixelSize + * \see fontMinPixelSize + */ + int fontMaxPixelSize; + + //! If true, all features will be labelled even when overlaps occur. + bool displayAll; + + //! Controls whether upside down labels are displayed and how they are handled. + UpsideDownLabels upsidedownLabels; + + /** + * True if every part of a multi-part feature should be labeled. If false, + * only the largest part will be labeled. + */ + bool labelPerPart; + + /** + * True if connected line features with identical label text should be merged + * prior to generating label positions. + */ bool mergeLines; - bool limitNumLabels; // whether to limit the number of labels to be drawn - int maxNumLabels; // maximum number of labels to be drawn + /** + * True if the number of labels drawn should be limited. + * \see maxNumLabels + */ + bool limitNumLabels; - double minFeatureSize; // minimum feature size to be labelled (in mm) - bool obstacle; // whether features for layer are obstacles to labels of other layers + /** + * The maximum number of labels which should be drawn for this layer. + * This only has an effect if limitNumLabels is true. + * \see limitNumLabels + */ + int maxNumLabels; - /** Obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels, + /** + * Minimum feature size (in millimeters) for a feature to be labelled. + */ + double minFeatureSize; + + /** + * True if features for layer are obstacles to labels of other layers. + * \see obstacleFactor + * \see obstacleType + */ + bool obstacle; + + /** + * Obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels, * > 1.0 less likely to be covered + * \see obstacle + * \see obstacleType */ double obstacleFactor; - /** Controls how features act as obstacles for labels + /** + * Controls how features act as obstacles for labels. + * \see obstacle + * \see obstacleFactor */ ObstacleType obstacleType; diff --git a/tests/src/core/testqgslabelingengine.cpp b/tests/src/core/testqgslabelingengine.cpp index 02d9730f796..64270165f2c 100644 --- a/tests/src/core/testqgslabelingengine.cpp +++ b/tests/src/core/testqgslabelingengine.cpp @@ -216,7 +216,6 @@ void TestQgsLabelingEngine::testRuleBased() s1.fieldName = QStringLiteral( "Class" ); s1.obstacle = false; s1.dist = 2; - s1.distInMapUnits = false; QgsTextFormat format = s1.format(); format.setColor( QColor( 200, 0, 200 ) ); format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) ); From 196179589197785affbbf1128eaf8baac4e8fc05 Mon Sep 17 00:00:00 2001 From: nirvn Date: Mon, 24 Jul 2017 10:01:28 +0700 Subject: [PATCH 128/266] [welcome page] improve rounded rect background color --- src/app/qgswelcomepageitemsmodel.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/qgswelcomepageitemsmodel.cpp b/src/app/qgswelcomepageitemsmodel.cpp index c1091fbd394..55cdc73433c 100644 --- a/src/app/qgswelcomepageitemsmodel.cpp +++ b/src/app/qgswelcomepageitemsmodel.cpp @@ -42,10 +42,10 @@ void QgsWelcomePageItemDelegate::paint( QPainter *painter, const QStyleOptionVie QAbstractTextDocumentLayout::PaintContext ctx; QStyleOptionViewItem optionV4 = option; - QColor color; + QColor color = optionV4.palette.color( QPalette::Active, QPalette::Window ); if ( option.state & QStyle::State_Selected && option.state & QStyle::State_HasFocus ) { - color = QColor( 255, 255, 255, 60 ); + color.setAlpha( 40 ); ctx.palette.setColor( QPalette::Text, optionV4.palette.color( QPalette::Active, QPalette::HighlightedText ) ); QStyle *style = QApplication::style(); @@ -53,7 +53,10 @@ void QgsWelcomePageItemDelegate::paint( QPainter *painter, const QStyleOptionVie } else if ( option.state & QStyle::State_Enabled ) { - color = QColor( 100, 100, 100, 30 ); + if ( option.state & QStyle::State_Selected ) + { + color.setAlpha( 40 ); + } ctx.palette.setColor( QPalette::Text, optionV4.palette.color( QPalette::Active, QPalette::Text ) ); QStyle *style = QApplication::style(); @@ -61,7 +64,6 @@ void QgsWelcomePageItemDelegate::paint( QPainter *painter, const QStyleOptionVie } else { - color = QColor( 100, 100, 100, 30 ); ctx.palette.setColor( QPalette::Text, optionV4.palette.color( QPalette::Disabled, QPalette::Text ) ); } From 15f3bbf9c805e038fde89736d91f37cd8c2a6e23 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Jul 2017 13:02:20 +1000 Subject: [PATCH 129/266] Add some missing /Factory/ annotations --- python/core/qgscolorramp.sip | 8 ++++---- python/core/symbology-ng/qgssymbollayerregistry.sip | 10 +++++----- python/core/symbology-ng/qgssymbollayerutils.sip | 12 ++++++------ src/core/qgscolorramp.h | 8 ++++---- src/core/symbology-ng/qgssymbollayerregistry.h | 10 +++++----- src/core/symbology-ng/qgssymbollayerutils.h | 12 ++++++------ 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/python/core/qgscolorramp.sip b/python/core/qgscolorramp.sip index d88365c3ef5..a266ebfb531 100644 --- a/python/core/qgscolorramp.sip +++ b/python/core/qgscolorramp.sip @@ -144,7 +144,7 @@ class QgsGradientColorRamp : QgsColorRamp \param stops optional list of additional color stops %End - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; %Docstring Creates a new QgsColorRamp from a map of properties :rtype: QgsColorRamp @@ -477,7 +477,7 @@ class QgsPresetSchemeColorRamp : QgsColorRamp, QgsColorScheme not available in Python bindings - use setColors instead %End - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; %Docstring Returns a new QgsPresetSchemeColorRamp color ramp created using the properties encoded in a string map. @@ -541,7 +541,7 @@ class QgsColorBrewerColorRamp : QgsColorRamp \param inverted invert ramp ordering %End - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; %Docstring Returns a new QgsColorBrewerColorRamp color ramp created using the properties encoded in a string map. @@ -647,7 +647,7 @@ class QgsCptCityColorRamp : QgsGradientColorRamp \param doLoadFile load cpt-city ramp from file %End - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; %Docstring :rtype: QgsColorRamp %End diff --git a/python/core/symbology-ng/qgssymbollayerregistry.sip b/python/core/symbology-ng/qgssymbollayerregistry.sip index addd7f864f4..ee62f32b649 100644 --- a/python/core/symbology-ng/qgssymbollayerregistry.sip +++ b/python/core/symbology-ng/qgssymbollayerregistry.sip @@ -52,7 +52,7 @@ Create a symbol layer of this type given the map of properties. Create widget for symbol layer of this type. Can return NULL if there's no GUI :rtype: QgsSymbolLayerWidget %End - virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement & ); + virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement & ) /Factory/; %Docstring Create a symbol layer of this type given the map of properties. :rtype: QgsSymbolLayer @@ -85,9 +85,9 @@ Convenience metadata class that uses static functions to create symbol layer and - virtual QgsSymbolLayer *createSymbolLayer( const QgsStringMap &map ); - virtual QgsSymbolLayerWidget *createSymbolLayerWidget( const QgsVectorLayer *vl ); - virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement &elem ); + virtual QgsSymbolLayer *createSymbolLayer( const QgsStringMap &map ) /Factory/; + virtual QgsSymbolLayerWidget *createSymbolLayerWidget( const QgsVectorLayer *vl ) /Factory/; + virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement &elem ) /Factory/; virtual void resolvePaths( QgsStringMap &properties, const QgsPathResolver &pathResolver, bool saving ); protected: @@ -133,7 +133,7 @@ create a new instance of symbol layer given symbol layer name and properties :rtype: QgsSymbolLayer %End - QgsSymbolLayer *createSymbolLayerFromSld( const QString &name, QDomElement &element ) const; + QgsSymbolLayer *createSymbolLayerFromSld( const QString &name, QDomElement &element ) const /Factory/; %Docstring create a new instance of symbol layer given symbol layer name and SLD :rtype: QgsSymbolLayer diff --git a/python/core/symbology-ng/qgssymbollayerutils.sip b/python/core/symbology-ng/qgssymbollayerutils.sip index b62581308f1..97701625a1f 100644 --- a/python/core/symbology-ng/qgssymbollayerutils.sip +++ b/python/core/symbology-ng/qgssymbollayerutils.sip @@ -337,15 +337,15 @@ Writes a symbol definition to XML :rtype: bool %End - static QgsSymbolLayer *createFillLayerFromSld( QDomElement &element ); + static QgsSymbolLayer *createFillLayerFromSld( QDomElement &element ) /Factory/; %Docstring :rtype: QgsSymbolLayer %End - static QgsSymbolLayer *createLineLayerFromSld( QDomElement &element ); + static QgsSymbolLayer *createLineLayerFromSld( QDomElement &element ) /Factory/; %Docstring :rtype: QgsSymbolLayer %End - static QgsSymbolLayer *createMarkerLayerFromSld( QDomElement &element ); + static QgsSymbolLayer *createMarkerLayerFromSld( QDomElement &element ) /Factory/; %Docstring :rtype: QgsSymbolLayer %End @@ -577,7 +577,7 @@ Writes a collection of symbols to XML with specified tagName for the top-level e :rtype: QVariant %End - static QgsColorRamp *loadColorRamp( const QVariant &value ); + static QgsColorRamp *loadColorRamp( const QVariant &value ) /Factory/; %Docstring Load a color ramp from a QVariantMap, wrapped in a QVariant. You can use QgsXmlUtils.readVariant to load it from an XML document. @@ -605,7 +605,7 @@ Writes a collection of symbols to XML with specified tagName for the top-level e :rtype: list of QColor %End - static QMimeData *colorToMimeData( const QColor &color ); + static QMimeData *colorToMimeData( const QColor &color ) /Factory/; %Docstring Creates mime data from a color. Sets both the mime data's color data, and the mime data's text with the color's hex code. @@ -636,7 +636,7 @@ Writes a collection of symbols to XML with specified tagName for the top-level e :rtype: QgsNamedColorList %End - static QMimeData *colorListToMimeData( const QgsNamedColorList &colorList, const bool allFormats = true ); + static QMimeData *colorListToMimeData( const QgsNamedColorList &colorList, const bool allFormats = true ) /Factory/; %Docstring Creates mime data from a list of named colors \param colorList list of named colors diff --git a/src/core/qgscolorramp.h b/src/core/qgscolorramp.h index ee0b81641b1..3bacf474ddc 100644 --- a/src/core/qgscolorramp.h +++ b/src/core/qgscolorramp.h @@ -142,7 +142,7 @@ class CORE_EXPORT QgsGradientColorRamp : public QgsColorRamp const QgsGradientStopsList &stops = QgsGradientStopsList() ); //! Creates a new QgsColorRamp from a map of properties - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY; virtual int count() const override { return mStops.count() + 2; } virtual double value( int index ) const override; @@ -439,7 +439,7 @@ class CORE_EXPORT QgsPresetSchemeColorRamp : public QgsColorRamp, public QgsColo * \param properties color ramp properties * \see properties() */ - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY; /** Sets the list of colors used by the ramp. * \param colors list of colors @@ -496,7 +496,7 @@ class CORE_EXPORT QgsColorBrewerColorRamp : public QgsColorRamp * \param properties color ramp properties * \see properties() */ - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY; virtual double value( int index ) const override; virtual QColor color( double value ) const override; @@ -586,7 +586,7 @@ class CORE_EXPORT QgsCptCityColorRamp : public QgsGradientColorRamp const QString &variantName = QString(), bool inverted = false, bool doLoadFile = true ); - static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ); + static QgsColorRamp *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY; virtual QString type() const override { return QStringLiteral( "cpt-city" ); } diff --git a/src/core/symbology-ng/qgssymbollayerregistry.h b/src/core/symbology-ng/qgssymbollayerregistry.h index 80e914f3b06..b75cd7aaa4f 100644 --- a/src/core/symbology-ng/qgssymbollayerregistry.h +++ b/src/core/symbology-ng/qgssymbollayerregistry.h @@ -50,7 +50,7 @@ class CORE_EXPORT QgsSymbolLayerAbstractMetadata //! Create widget for symbol layer of this type. Can return NULL if there's no GUI virtual QgsSymbolLayerWidget *createSymbolLayerWidget( const QgsVectorLayer * ) SIP_FACTORY { return nullptr; } //! Create a symbol layer of this type given the map of properties. - virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement & ) { return nullptr; } + virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement & ) SIP_FACTORY { return nullptr; } /** Resolve paths in symbol layer's properties (if there are any paths). * When saving is true, paths are converted from absolute to relative, @@ -109,9 +109,9 @@ class CORE_EXPORT QgsSymbolLayerMetadata : public QgsSymbolLayerAbstractMetadata //! \note not available in Python bindings void setWidgetFunction( QgsSymbolLayerWidgetFunc f ) { mWidgetFunc = f; } SIP_SKIP - virtual QgsSymbolLayer *createSymbolLayer( const QgsStringMap &map ) override { return mCreateFunc ? mCreateFunc( map ) : nullptr; } SIP_FACTORY - virtual QgsSymbolLayerWidget *createSymbolLayerWidget( const QgsVectorLayer *vl ) override { return mWidgetFunc ? mWidgetFunc( vl ) : nullptr; } SIP_FACTORY - virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement &elem ) override { return mCreateFromSldFunc ? mCreateFromSldFunc( elem ) : nullptr; } SIP_FACTORY + virtual QgsSymbolLayer *createSymbolLayer( const QgsStringMap &map ) override SIP_FACTORY { return mCreateFunc ? mCreateFunc( map ) : nullptr; } + virtual QgsSymbolLayerWidget *createSymbolLayerWidget( const QgsVectorLayer *vl ) override SIP_FACTORY { return mWidgetFunc ? mWidgetFunc( vl ) : nullptr; } + virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement &elem ) override SIP_FACTORY { return mCreateFromSldFunc ? mCreateFromSldFunc( elem ) : nullptr; } virtual void resolvePaths( QgsStringMap &properties, const QgsPathResolver &pathResolver, bool saving ) override { if ( mPathResolverFunc ) @@ -159,7 +159,7 @@ class CORE_EXPORT QgsSymbolLayerRegistry QgsSymbolLayer *createSymbolLayer( const QString &name, const QgsStringMap &properties = QgsStringMap() ) const SIP_FACTORY; //! create a new instance of symbol layer given symbol layer name and SLD - QgsSymbolLayer *createSymbolLayerFromSld( const QString &name, QDomElement &element ) const; + QgsSymbolLayer *createSymbolLayerFromSld( const QString &name, QDomElement &element ) const SIP_FACTORY; /** Resolve paths in properties of a particular symbol layer. * This normally means converting relative paths to absolute paths when loading diff --git a/src/core/symbology-ng/qgssymbollayerutils.h b/src/core/symbology-ng/qgssymbollayerutils.h index 38ac75fb5f8..f8c2f72f1f4 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.h +++ b/src/core/symbology-ng/qgssymbollayerutils.h @@ -254,9 +254,9 @@ class CORE_EXPORT QgsSymbolLayerUtils static bool createSymbolLayerListFromSld( QDomElement &element, QgsWkbTypes::GeometryType geomType, QgsSymbolLayerList &layers ); - static QgsSymbolLayer *createFillLayerFromSld( QDomElement &element ); - static QgsSymbolLayer *createLineLayerFromSld( QDomElement &element ); - static QgsSymbolLayer *createMarkerLayerFromSld( QDomElement &element ); + static QgsSymbolLayer *createFillLayerFromSld( QDomElement &element ) SIP_FACTORY; + static QgsSymbolLayer *createLineLayerFromSld( QDomElement &element ) SIP_FACTORY; + static QgsSymbolLayer *createMarkerLayerFromSld( QDomElement &element ) SIP_FACTORY; static bool convertPolygonSymbolizerToPointMarker( QDomElement &element, QgsSymbolLayerList &layerList ); static bool hasExternalGraphic( QDomElement &element ); @@ -395,7 +395,7 @@ class CORE_EXPORT QgsSymbolLayerUtils * * \see colorRampToVariant() */ - static QgsColorRamp *loadColorRamp( const QVariant &value ); + static QgsColorRamp *loadColorRamp( const QVariant &value ) SIP_FACTORY; /** * Returns a friendly display name for a color @@ -421,7 +421,7 @@ class CORE_EXPORT QgsSymbolLayerUtils * \see colorFromMimeData * \since QGIS 2.5 */ - static QMimeData *colorToMimeData( const QColor &color ); + static QMimeData *colorToMimeData( const QColor &color ) SIP_FACTORY; /** * Attempts to parse mime data as a color @@ -449,7 +449,7 @@ class CORE_EXPORT QgsSymbolLayerUtils * \returns mime data containing encoded colors * \since QGIS 2.5 */ - static QMimeData *colorListToMimeData( const QgsNamedColorList &colorList, const bool allFormats = true ); + static QMimeData *colorListToMimeData( const QgsNamedColorList &colorList, const bool allFormats = true ) SIP_FACTORY; /** * Exports colors to a gpl GIMP palette file From 7b92f1f1e445e069db0a6fb454318ad789c98778 Mon Sep 17 00:00:00 2001 From: Nathan Woodrow Date: Mon, 10 Apr 2017 08:42:28 +1000 Subject: [PATCH 130/266] [FEATURE] Add user profiles. All user settings/plugins, etc are now loaded from APPDATA for each platform and no longer .qgis3 and are isolated from each other. This allows for different profiles depending on what the user of QGIS needs, e.g test, prod, demo, etc Profile menu allows for switching between profiles, or creating new ones. --- .ci/travis/linux/blacklist.txt | 1 + python/core/core_auto.sip | 2 + python/core/qgsapplication.sip | 4 +- python/core/qgsuserprofile.sip | 88 +++++++++ python/core/qgsuserprofilemanager.sip | 178 ++++++++++++++++++ src/app/main.cpp | 87 +++++---- src/app/qgisapp.cpp | 78 +++++++- src/app/qgisapp.h | 25 ++- src/core/CMakeLists.txt | 4 + src/core/qgsapplication.cpp | 29 +-- src/core/qgsapplication.h | 11 +- src/core/qgsuserprofile.cpp | 129 +++++++++++++ src/core/qgsuserprofile.h | 88 +++++++++ src/core/qgsuserprofilemanager.cpp | 197 ++++++++++++++++++++ src/core/qgsuserprofilemanager.h | 182 ++++++++++++++++++ src/ui/qgsuserprofilemanagerwidget.ui | 107 +++++++++++ tests/code_layout/acceptable_missing_doc.py | 2 +- 17 files changed, 1158 insertions(+), 54 deletions(-) create mode 100644 python/core/qgsuserprofile.sip create mode 100644 python/core/qgsuserprofilemanager.sip create mode 100644 src/core/qgsuserprofile.cpp create mode 100644 src/core/qgsuserprofile.h create mode 100644 src/core/qgsuserprofilemanager.cpp create mode 100644 src/core/qgsuserprofilemanager.h create mode 100644 src/ui/qgsuserprofilemanagerwidget.ui diff --git a/.ci/travis/linux/blacklist.txt b/.ci/travis/linux/blacklist.txt index b12438654f8..2995027c493 100755 --- a/.ci/travis/linux/blacklist.txt +++ b/.ci/travis/linux/blacklist.txt @@ -7,6 +7,7 @@ qgis_composerutils ProcessingGrass7AlgorithmsImageryTest ProcessingGrass7AlgorithmsRasterTest PyQgsDBManagerGpkg +PyQgsAppStartup # temporary during processing refactoring ProcessingParametersTest diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 73ff541cbcd..152d0308006 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -395,5 +395,7 @@ %Include layertree/qgslayertreemodellegendnode.sip %Include layertree/qgslayertreenode.sip %Include layertree/qgslayertreeregistrybridge.sip +%Include qgsuserprofilemanager.sip %Include symbology-ng/qgsarrowsymbollayer.sip %Include composer/qgscomposerutils.sip +%Include qgsuserprofile.sip diff --git a/python/core/qgsapplication.sip b/python/core/qgsapplication.sip index 512d6de0094..a6d1825cd78 100644 --- a/python/core/qgsapplication.sip +++ b/python/core/qgsapplication.sip @@ -80,7 +80,7 @@ to change due to centralization. static const char *QGIS_ORGANIZATION_NAME; static const char *QGIS_ORGANIZATION_DOMAIN; static const char *QGIS_APPLICATION_NAME; - QgsApplication( SIP_PYLIST argv, bool GUIenabled, QString customConfigPath = QString() ) / PostHook = __pyQtQAppHook__ / [( int &argc, char **argv, bool GUIenabled, const QString &customConfigPath = QString() )]; + QgsApplication( SIP_PYLIST argv, bool GUIenabled, QString profileFolder = QString(), QString platformName = "desktop" ) / PostHook = __pyQtQAppHook__ / [( int &argc, char **argv, bool GUIenabled, const QString &profileFolder = QString(), const QString &platformName = "desktop" )]; %MethodCode // The Python interface is a list of argument strings that is modified. @@ -95,7 +95,7 @@ to change due to centralization. // Create it now the arguments are right. static int nargc = argc; - sipCpp = new sipQgsApplication( nargc, argv, a1, *a2 ); + sipCpp = new sipQgsApplication( nargc, argv, a1, *a2, *a3 ); // Now modify the original list. qtgui_UpdatePyArgv( a0, argc, argv ); diff --git a/python/core/qgsuserprofile.sip b/python/core/qgsuserprofile.sip new file mode 100644 index 00000000000..fb5c1d95fad --- /dev/null +++ b/python/core/qgsuserprofile.sip @@ -0,0 +1,88 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/qgsuserprofile.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + +class QgsUserProfile +{ +%Docstring + User profile contains information about the user profile folders on the machine. + In QGIS 3 all settings, plugins, etc were moved into a %APPDATA%/profiles folder for each platform. + This allows for manage different user profiles per machine vs the single default one that was allowed in the + past. + + A user profile is all settings and anything that used to be found in .qgis3 in the users home folder. + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsuserprofile.h" +%End + public: + + QgsUserProfile( const QString &folder ); +%Docstring + Reference to a existing user profile folder. + Profile folder should be created using QgsProfileManager. + \param folder An existing profile folder as the base of the user profile. +%End + + const QString folder() const; +%Docstring + The base folder for the user profile. + :rtype: str +%End + + QgsError validate() const; +%Docstring + Check of the profile is in a valid state. + :rtype: QgsError +%End + + const QString name() const; +%Docstring + The name for the user profile. + :rtype: str +%End + + void initSettings() const; +%Docstring + Init the settings from the user folder. +%End + + const QString alias() const; +%Docstring + Return the alias for the user profile. + :return: If no alias is set name() is returned. + :rtype: str +%End + + QgsError setAlias( const QString &alias ); +%Docstring + Set the alias of the profile. The alias is a user friendly name. + \param alias A user friendly name for the profile. + :return: True of setting the alias was successful. + :rtype: QgsError +%End + + const QIcon icon() const; +%Docstring + The icon for the user profile. + :return: A QIcon for the users + :rtype: QIcon +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/qgsuserprofile.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/qgsuserprofilemanager.sip b/python/core/qgsuserprofilemanager.sip new file mode 100644 index 00000000000..71e503f4f42 --- /dev/null +++ b/python/core/qgsuserprofilemanager.sip @@ -0,0 +1,178 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/qgsuserprofilemanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsUserProfileManager : QObject +{ +%Docstring + User profile manager is used to manager list, and manage user profiles on the users machine. + + In QGIS 3 all settings, plugins, etc were moved into a %APPDATA%/profiles folder for each platform. + This allows for manage different user profiles per machine vs the single default one that was allowed in the + past. + + A user profile is all settings and anything that used to be found in .qgis3 in the users home folder. + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsuserprofilemanager.h" +%End + public: + + QgsUserProfileManager( const QString &rootLocation = QString(), QObject *parent = 0 ); +%Docstring + User profile manager used to manage user profiles for the instance of QGIS. +%End + + static QString resolveProfilesFolder( const QString &basePath = QString() ); +%Docstring + Resolves the profiles folder for the given path. Path will have \\profiles appended to the path + \param basePath The base path to resolve the path from to append the \\profiles folder to. + :return: The root path to store user profiles. + :rtype: str +%End + + QgsUserProfile *getProfile( const QString &defaultProfile = "default", bool createNew = true, bool initSettings = true ) /Factory/; +%Docstring + Return the profile from the given root profile location. + If no name is given it returns a profile called "default". + By default will create the profile folder if not found. + By default will init the user settings. +.. note:: + + Returns a new QgsUserProfile. Ownership transferred to caller. + \param defaultProfile The profile name to find. Empty profile name will return "default" for the name. + \param createNew Create the profile folder if it doesn't exist. + :return: The user profile + :rtype: QgsUserProfile +%End + + void setRootLocation( QString rootProfileLocation ); +%Docstring + Set the root profile location for the profile manager. All profiles are loaded from this + location. Will also contain a profiles.ini for holding profile settings. + \param rootProfileLocation Path to the top level profile folder which contains folders for each profile. +%End + + QString rootLocation(); +%Docstring + Returns the path to the root profiles location. + :return: The root path to the profiles folder. + :rtype: str +%End + + bool rootLocationIsSet() const; +%Docstring + Check if the root location has been set for the manager. + :return: True if the root location has been set. + :rtype: bool +%End + + QStringList allProfiles() const; +%Docstring + A list of all found profile names. + :return: + :rtype: list of str +%End + + bool profileExists( const QString &name ) const; +%Docstring + Check if a profile exists. + :return: False if the profile can't be found. + :rtype: bool +%End + + QString defaultProfileName() const; +%Docstring + Returns the name of the default profile that has been set in .default. + :return: The name of the default profile. + :rtype: str +%End + + void setDefaultProfileName( const QString &name ); +%Docstring + Sets the default profile name. The default profile name is used when loading QGIS + with no arguments. + \param name The name of the profile to save. +%End + + void setDefaultFromActive(); +%Docstring + Set the default profile name from the current active profile. +%End + + QgsUserProfile *profileForName( const QString name ) const; +%Docstring + Return the profile found for a given name. + \param name The name of the profile to return. + :return: A QgsUserprofile pointing to the location of the user profile. + :rtype: QgsUserProfile +%End + + QgsError createUserProfile( const QString &name ); +%Docstring + Create a user profile given by the name + \param name + :return: A QgsError which report if there was any error creating the user profile. + :rtype: QgsError +%End + + QgsError deleteProfile( const QString name ); +%Docstring + Deletes a profile from the root profiles folder. +.. note:: + + There is no undo on this as it deletes the folder from the machine. + \param name The name of the profile to delete. + :return: A QgsError with a message if the profile failed to be deleted. + :rtype: QgsError +%End + + QgsUserProfile *userProfile(); +%Docstring + The currently active user profile. + :return: The currently active user profile. + :rtype: QgsUserProfile +%End + + void setActiveUserProfile( const QString &profile ); +%Docstring + Sets the active profile in the manager. + This can only be set once. + Setting this again does nothing. + + \param profile The name of the active profile +%End + + void loadUserProfile( const QString &name ); +%Docstring + Starts a new instance of QGIS for the given profile. + \param name The profile to start QGIS with. +%End + + signals: + + void profilesChanged( ); +%Docstring + Emitted when the list of profiles is changed. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/qgsuserprofilemanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/app/main.cpp b/src/app/main.cpp index 50f5e649d38..a07fcecf10a 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -99,6 +99,9 @@ typedef SInt32 SRefCon; #include "qgis_app.h" #include "qgscrashhandler.h" +#include "qgsuserprofilemanager.h" +#include "qgsuserprofile.h" + /** Print usage text */ void usage( const QString &appName ) @@ -123,8 +126,6 @@ void usage( const QString &appName ) << QStringLiteral( "\t[--nocustomization]\tdon't apply GUI customization\n" ) << QStringLiteral( "\t[--customizationfile path]\tuse the given ini file as GUI customization\n" ) << QStringLiteral( "\t[--globalsettingsfile path]\tuse the given ini file as Global Settings (defaults)\n" ) - << QStringLiteral( "\t[--optionspath path]\tuse the given QgsSettings path\n" ) - << QStringLiteral( "\t[--configpath path]\tuse the given path for all user configuration\n" ) << QStringLiteral( "\t[--authdbdirectory path] use the given directory for authentication database\n" ) << QStringLiteral( "\t[--code path]\trun the given python file on load\n" ) << QStringLiteral( "\t[--defaultui]\tstart by resetting user ui settings to default\n" ) @@ -134,6 +135,8 @@ void usage( const QString &appName ) << QStringLiteral( "\t[--dxf-scale-denom scale]\tscale for dxf output\n" ) << QStringLiteral( "\t[--dxf-encoding encoding]\tencoding to use for dxf output\n" ) << QStringLiteral( "\t[--dxf-preset maptheme]\tmap theme to use for dxf output\n" ) + << QStringLiteral( "\t[--profile name]\tload a named profile from the users profiles folder.\n" ) + << QStringLiteral( "\t[--profiles-path path]\tpath to store user profile folders. Will create profiles inside a {path}\\profiles folder \n" ) << QStringLiteral( "\t[--help]\t\tthis text\n" ) << QStringLiteral( "\t[--]\t\ttreat all following arguments as FILEs\n\n" ) << QStringLiteral( " FILE:\n" ) @@ -489,7 +492,9 @@ int main( int argc, char *argv[] ) // This behavior is used to load the app, snapshot the map, // save the image to disk and then exit - QString mySnapshotFileName = QLatin1String( "" ); + QString mySnapshotFileName = ""; + QString configLocalStorageLocation = ""; + QString profileName; int mySnapshotWidth = 800; int mySnapshotHeight = 600; @@ -526,7 +531,6 @@ int main( int argc, char *argv[] ) // The user can specify a path which will override the default path of custom // user settings (~/.qgis) and it will be used for QgsSettings INI file QString configpath; - QString optionpath; QString authdbdirectory; QString pythonfile; @@ -534,6 +538,7 @@ int main( int argc, char *argv[] ) QString customizationfile; QString globalsettingsfile; +// TODO Fix android #if defined(ANDROID) QgsDebugMsg( QString( "Android: All params stripped" ) );// Param %1" ).arg( argv[0] ) ); //put all QGIS settings in the same place @@ -575,6 +580,14 @@ int main( int argc, char *argv[] ) { myCustomization = false; } + else if ( i + 1 < argc && ( arg == QLatin1String( "--profile" ) ) ) + { + profileName = args[++i]; + } + else if ( i + 1 < argc && ( arg == QLatin1String( "--profiles-path" ) || arg == QLatin1String( "-s" ) ) ) + { + configLocalStorageLocation = QDir::toNativeSeparators( QFileInfo( args[++i] ).absoluteFilePath() ); + } else if ( i + 1 < argc && ( arg == QLatin1String( "--snapshot" ) || arg == QLatin1String( "-s" ) ) ) { mySnapshotFileName = QDir::toNativeSeparators( QFileInfo( args[++i] ).absoluteFilePath() ); @@ -599,14 +612,6 @@ int main( int argc, char *argv[] ) { myInitialExtent = args[++i]; } - else if ( i + 1 < argc && ( arg == QLatin1String( "--optionspath" ) || arg == QLatin1String( "-o" ) ) ) - { - optionpath = QDir::toNativeSeparators( QDir( args[++i] ).absolutePath() ); - } - else if ( i + 1 < argc && ( arg == QLatin1String( "--configpath" ) || arg == QLatin1String( "-c" ) ) ) - { - configpath = QDir::toNativeSeparators( QDir( args[++i] ).absolutePath() ); - } else if ( i + 1 < argc && ( arg == QLatin1String( "--authdbdirectory" ) || arg == QLatin1String( "-a" ) ) ) { authdbdirectory = QDir::toNativeSeparators( QDir( args[++i] ).absolutePath() ); @@ -768,13 +773,6 @@ int main( int argc, char *argv[] ) exit( 1 ); //exit for now until a version of qgis is capabable of running non interactive } - if ( !optionpath.isEmpty() || !configpath.isEmpty() ) - { - // tell QgsSettings to use INI format and save the file in custom config path - QSettings::setDefaultFormat( QSettings::IniFormat ); - QSettings::setPath( QSettings::IniFormat, QSettings::UserScope, optionpath.isEmpty() ? configpath : optionpath ); - } - // GUI customization is enabled according to settings (loaded when instance is created) // we force disabled here if --nocustomization argument is used if ( !myCustomization ) @@ -782,7 +780,36 @@ int main( int argc, char *argv[] ) QgsCustomization::instance()->setEnabled( false ); } - QgsApplication myApp( argc, argv, myUseGuiFlag, configpath ); + QCoreApplication::setOrganizationName( QgsApplication::QGIS_ORGANIZATION_NAME ); + QCoreApplication::setOrganizationDomain( QgsApplication::QGIS_ORGANIZATION_DOMAIN ); + QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME ); + QCoreApplication::setAttribute( Qt::AA_DontShowIconsInMenus, false ); + + if ( getenv( "QGIS_CUSTOM_CONFIG_PATH" ) ) + { + QString envProfileFolder = getenv( "QGIS_CUSTOM_CONFIG_PATH" ); + configLocalStorageLocation = envProfileFolder + QDir::separator() + "profiles"; + } + else + { + // TODO Get from global settings. + configLocalStorageLocation = QStandardPaths::standardLocations( QStandardPaths::AppDataLocation ).value( 0 ); + } + + + QString rootProfileFolder = QgsUserProfileManager::resolveProfilesFolder( configLocalStorageLocation ); + QgsUserProfileManager manager( rootProfileFolder ); + QgsUserProfile *profile = manager.getProfile( profileName, true ); + QString profileFolder = profile->folder(); + profileName = profile->name(); + delete profile; + + QgsDebugMsg( "User profile details:" ); + QgsDebugMsg( QString( "\t - %1" ).arg( profileName ) ); + QgsDebugMsg( QString( "\t - %1" ).arg( profileFolder ) ); + QgsDebugMsg( QString( "\t - %1" ).arg( rootProfileFolder ) ); + + QgsApplication myApp( argc, argv, myUseGuiFlag, profileFolder ); #ifdef Q_OS_MAC // Set hidpi icons; use SVG icons, as PNGs will be relatively too small @@ -846,18 +873,6 @@ int main( int argc, char *argv[] ) // TODO: use QgsSettings QSettings *customizationsettings = nullptr; - if ( !optionpath.isEmpty() || !configpath.isEmpty() ) - { - // tell QgsSettings to use INI format and save the file in custom config path - QSettings::setDefaultFormat( QSettings::IniFormat ); - QString path = optionpath.isEmpty() ? configpath : optionpath; - QSettings::setPath( QSettings::IniFormat, QSettings::UserScope, path ); - customizationsettings = new QSettings( QSettings::IniFormat, QSettings::UserScope, QStringLiteral( "QGIS" ), QStringLiteral( "QGISCUSTOMIZATION2" ) ); - } - else - { - customizationsettings = new QSettings( QStringLiteral( "QGIS" ), QStringLiteral( "QGISCUSTOMIZATION2" ) ); - } // Using the customizationfile option always overrides the option and config path options. if ( !customizationfile.isEmpty() ) @@ -865,8 +880,12 @@ int main( int argc, char *argv[] ) customizationsettings = new QSettings( customizationfile, QSettings::IniFormat ); QgsCustomization::instance()->setEnabled( true ); } + else + { + customizationsettings = new QSettings( QStringLiteral( "QGIS" ), QStringLiteral( "QGISCUSTOMIZATION2" ) ); + } - // Load and set possible default customization, must be done afterQgsApplication init and QgsSettings ( QCoreApplication ) init + // Load and set possible default customization, must be done after QgsApplication init and QgsSettings ( QCoreApplication ) init QgsCustomization::instance()->setSettings( customizationsettings ); QgsCustomization::instance()->loadDefault(); @@ -1124,7 +1143,7 @@ int main( int argc, char *argv[] ) // this should be done in QgsApplication::init() but it doesn't know the settings dir. QgsApplication::setMaxThreads( mySettings.value( QStringLiteral( "qgis/max_threads" ), -1 ).toInt() ); - QgisApp *qgis = new QgisApp( mypSplash, myRestorePlugins, mySkipVersionCheck ); // "QgisApp" used to find canonical instance + QgisApp *qgis = new QgisApp( mypSplash, myRestorePlugins, mySkipVersionCheck, rootProfileFolder, profileName ); // "QgisApp" used to find canonical instance qgis->setObjectName( QStringLiteral( "QgisApp" ) ); myApp.connect( diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index b0d60b26daa..1b3134dc1ae 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -288,6 +288,9 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsgui.h" #include "qgsdatasourcemanagerdialog.h" +#include "qgsuserprofilemanager.h" +#include "qgsuserprofile.h" + #include "qgssublayersdialog.h" #include "ogr/qgsopenvectorlayerdialog.h" #include "ogr/qgsvectorlayersaveasdialog.h" @@ -400,6 +403,8 @@ extern "C" #endif class QTreeWidgetItem; +class QgsUserProfileManager; +class QgsUserProfile; /** Set the application title bar text @@ -598,7 +603,7 @@ static bool cmpByText_( QAction *a, QAction *b ) QgisApp *QgisApp::sInstance = nullptr; // constructor starts here -QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCheck, QWidget *parent, Qt::WindowFlags fl ) +QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCheck, const QString rootProfileLocation, const QString activeProfile, QWidget *parent, Qt::WindowFlags fl ) : QMainWindow( parent, fl ) , mNonEditMapTool( nullptr ) , mScaleWidget( nullptr ) @@ -646,6 +651,13 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh sInstance = this; QgsRuntimeProfiler *profiler = QgsApplication::profiler(); + startProfile( QStringLiteral( "User profile manager" ) ); + mUserProfileManager = new QgsUserProfileManager( "", this ); + mUserProfileManager->setRootLocation( rootProfileLocation ); + mUserProfileManager->setActiveUserProfile( activeProfile ); + connect( mUserProfileManager, &QgsUserProfileManager::profilesChanged, this, &QgisApp::refreshProfileMenu ); + endProfile(); + namSetup(); // load GUI: actions, menus, toolbars @@ -1270,6 +1282,8 @@ QgisApp::QgisApp() , mPopupMenu( nullptr ) , mDatabaseMenu( nullptr ) , mWebMenu( nullptr ) + , mConfigMenu( nullptr ) + , mConfigMenuBar( nullptr ) , mToolPopupOverviews( nullptr ) , mToolPopupDisplay( nullptr ) , mMapCanvas( nullptr ) @@ -2273,12 +2287,57 @@ void QgisApp::createMenus() mWebMenu = new QMenu( tr( "&Web" ), menuBar() ); mWebMenu->setObjectName( QStringLiteral( "mWebMenu" ) ); + // Help menu // add What's this button to it QAction *before = mActionHelpAPI; QAction *actionWhatsThis = QWhatsThis::createAction( this ); actionWhatsThis->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionWhatsThis.svg" ) ) ); mHelpMenu->insertAction( before, actionWhatsThis ); + + createProfileMenu(); +} + +void QgisApp::refreshProfileMenu() +{ + mConfigMenu->clear(); + QgsUserProfile *profile = userProfileManager()->userProfile(); + mConfigMenu->setTitle( tr( "&User Profile: %1" ).arg( profile->alias() ) ); + + Q_FOREACH ( const QString &name, userProfileManager()->allProfiles() ) + { + profile = userProfileManager()->profileForName( name ); + QAction *action = mConfigMenu->addAction( profile->icon(), profile->alias() ); + action->setToolTip( profile->folder() ); + delete profile; + connect( action, &QAction::triggered, this, [this, name]() + { + userProfileManager()->loadUserProfile( name ); + } ); + } + mConfigMenu->addSeparator(); + QAction *openProfileFolderAction = mConfigMenu->addAction( tr( "Open profile folder" ) ); + connect( openProfileFolderAction, &QAction::triggered, this, [this]() + { + QDesktopServices::openUrl( QUrl::fromLocalFile( userProfileManager()->userProfile()->folder() ) ); + } ); + + QAction *newProfileAction = mConfigMenu->addAction( tr( "New profile" ) ); + connect( newProfileAction, &QAction::triggered, this, &QgisApp::newProfile ); +} + +void QgisApp::createProfileMenu() +{ + mConfigMenu = new QMenu(); + mConfigMenu->addSeparator(); + mConfigMenu->addAction( tr( "Manage Configs" ) ); + + mConfigMenuBar = new QMenuBar( menuBar() ); + mConfigMenuBar->addMenu( mConfigMenu ); + menuBar()->setCornerWidget( mConfigMenuBar ); + mConfigMenuBar->show(); + + refreshProfileMenu(); } void QgisApp::createToolBars() @@ -3289,6 +3348,12 @@ QgsPluginManager *QgisApp::pluginManager() return mPluginManager; } +QgsUserProfileManager *QgisApp::userProfileManager() +{ + Q_ASSERT( mUserProfileManager ); + return mUserProfileManager; +} + QgsMapCanvas *QgisApp::mapCanvas() { Q_ASSERT( mMapCanvas ); @@ -4735,6 +4800,7 @@ void QgisApp::fileExit() if ( saveDirty() ) { closeProject(); + userProfileManager()->setDefaultFromActive(); qApp->exit( 0 ); } } @@ -11726,6 +11792,16 @@ void QgisApp::keyPressEvent( QKeyEvent *e ) } } +void QgisApp::newProfile() +{ + QString text = QInputDialog::getText( this, tr( "New profile name" ), tr( "New profile name" ) ); + if ( text.isEmpty() ) + return; + + userProfileManager()->createUserProfile( text ); + userProfileManager()->loadUserProfile( text ); +} + void QgisApp::onTaskCompleteShowNotify( long taskId, int status ) { if ( status == QgsTask::Complete && !this->isActiveWindow() ) diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 4dfb97c1740..a2c99e899fe 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -97,6 +97,8 @@ class QgsWelcomePage; class QgsOptionsWidgetFactory; class QgsStatusBar; +class QgsUserProfileManagerWidgetFactory; + class QDomDocument; class QNetworkReply; class QNetworkProxy; @@ -162,7 +164,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow Q_OBJECT public: //! Constructor - QgisApp( QSplashScreen *splash, bool restorePlugins = true, bool skipVersionCheck = false, QWidget *parent = nullptr, Qt::WindowFlags fl = Qt::Window ); + QgisApp( QSplashScreen *splash, bool restorePlugins = true, + bool skipVersionCheck = false, const QString rootProfileLocation = QString(), + const QString activeProfile = QString(), + QWidget *parent = nullptr, Qt::WindowFlags fl = Qt::Window ); //! Constructor for unit tests QgisApp(); @@ -569,6 +574,11 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow //! returns pointer to plugin manager QgsPluginManager *pluginManager(); + /** + * The applications user profile manager. + */ + QgsUserProfileManager *userProfileManager(); + /** Return vector layers in edit mode * \param modified whether to return only layers that have been modified * \returns list of layers in legend order, or empty list */ @@ -858,6 +868,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow #endif private slots: + void newProfile(); + void onTaskCompleteShowNotify( long taskId, int status ); void onTransactionGroupsChanged(); @@ -1661,6 +1673,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void createActions(); void createActionGroups(); void createMenus(); + void createProfileMenu(); void createToolBars(); void createStatusBar(); void setupConnections(); @@ -1670,6 +1683,11 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void createMapTips(); void createDecorations(); + /** + * Refresh the user profile menu. + */ + void refreshProfileMenu(); + //! Do histogram stretch for singleband gray / multiband color rasters void histogramStretch( bool visibleAreaOnly = false, QgsRasterMinMaxOrigin::Limits limits = QgsRasterMinMaxOrigin::MinMax ); @@ -1871,6 +1889,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QMenu *mDatabaseMenu = nullptr; //! Top level web menu QMenu *mWebMenu = nullptr; + + QMenu *mConfigMenu = nullptr; + QMenuBar *mConfigMenuBar = nullptr; + //! Popup menu for the map overview tools QMenu *mToolPopupOverviews = nullptr; //! Popup menu for the display tools @@ -1966,6 +1988,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsSnappingWidget *mSnappingDialog = nullptr; QgsPluginManager *mPluginManager = nullptr; + QgsUserProfileManager *mUserProfileManager = nullptr; QgsDockWidget *mMapStylingDock = nullptr; QgsLayerStylingWidget *mMapStyleWidget = nullptr; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f5d58c8803d..7009a8326cb 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -457,6 +457,9 @@ SET(QGIS_CORE_SRCS ${CMAKE_CURRENT_BINARY_DIR}/qgscontexthelp_texts.cpp ${CMAKE_CURRENT_BINARY_DIR}/qgsexpression_texts.cpp + + qgsuserprofile.cpp + qgsuserprofilemanager.cpp ) @@ -692,6 +695,7 @@ SET(QGIS_CORE_MOC_HDRS layertree/qgslayertreemodellegendnode.h layertree/qgslayertreenode.h layertree/qgslayertreeregistrybridge.h + qgsuserprofilemanager.h ) IF (NOT WITH_QTWEBKIT) diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 17f05552d15..49dda787965 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -39,6 +39,8 @@ #include "qgsannotationregistry.h" #include "qgssettings.h" #include "qgsunittypes.h" +#include "qgsuserprofile.h" +#include "qgsuserprofilemanager.h" #include "gps/qgsgpsconnectionregistry.h" #include "processing/qgsprocessingregistry.h" @@ -109,28 +111,36 @@ const char *QgsApplication::QGIS_APPLICATION_NAME = "QGIS3"; QgsApplication::ApplicationMembers *QgsApplication::sApplicationMembers = nullptr; -QgsApplication::QgsApplication( int &argc, char **argv, bool GUIenabled, const QString &customConfigPath, const QString &platformName ) +QgsApplication::QgsApplication( int &argc, char **argv, bool GUIenabled, const QString &profileFolder, const QString &platformName ) : QApplication( argc, argv, GUIenabled ) { sPlatformName = platformName; mApplicationMembers = new ApplicationMembers(); - init( customConfigPath ); // init can also be called directly by e.g. unit tests that don't inherit QApplication. + init( profileFolder ); // init can also be called directly by e.g. unit tests that don't inherit QApplication. } -void QgsApplication::init( QString customConfigPath ) +void QgsApplication::init( QString profileFolder ) { - if ( customConfigPath.isEmpty() ) + if ( profileFolder.isEmpty() ) { if ( getenv( "QGIS_CUSTOM_CONFIG_PATH" ) ) { - customConfigPath = getenv( "QGIS_CUSTOM_CONFIG_PATH" ); + QString envProfileFolder = getenv( "QGIS_CUSTOM_CONFIG_PATH" ); + profileFolder = envProfileFolder + QDir::separator() + "profiles"; } else { - customConfigPath = QStringLiteral( "%1/.qgis3/" ).arg( QDir::homePath() ); + profileFolder = QStandardPaths::standardLocations( QStandardPaths::AppDataLocation ).value( 0 ); } + // This will normally get here for custom scripts that use QgsApplication. + // This doesn't get this hit for QGIS Desktop because we setup the profile via main + QString rootProfileFolder = QgsUserProfileManager::resolveProfilesFolder( profileFolder ); + QgsUserProfileManager manager( rootProfileFolder ); + QgsUserProfile *profile = manager.getProfile(); + profileFolder = profile->folder(); + delete profile; } qRegisterMetaType( "QgsGeometry::Error" ); @@ -207,11 +217,7 @@ void QgsApplication::init( QString customConfigPath ) } } - if ( !customConfigPath.isEmpty() ) - { - ABISYM( mConfigPath ) = customConfigPath + '/'; // make sure trailing slash is included - } - + ABISYM( mConfigPath ) = profileFolder + '/'; // make sure trailing slash is included ABISYM( mDefaultSvgPaths ) << qgisSettingsDirPath() + QStringLiteral( "svg/" ); ABISYM( mAuthDbDirPath ) = qgisSettingsDirPath(); @@ -1316,6 +1322,7 @@ void QgsApplication::setCustomVariable( const QString &name, const QVariant &val emit instance()->customVariablesChanged(); } + QString QgsApplication::nullRepresentation() { ApplicationMembers *appMembers = members(); diff --git a/src/core/qgsapplication.h b/src/core/qgsapplication.h index ffcbdb1813c..f9219ba39da 100644 --- a/src/core/qgsapplication.h +++ b/src/core/qgsapplication.h @@ -39,6 +39,8 @@ class QgsPluginLayerRegistry; class QgsMessageLog; class QgsProcessingRegistry; class QgsAnnotationRegistry; +class QgsUserProfile; +class QgsUserProfileManager; class QgsPageSizeRegistry; class QgsLayoutItemRegistry; @@ -115,9 +117,9 @@ class CORE_EXPORT QgsApplication : public QApplication static const char *QGIS_ORGANIZATION_DOMAIN; static const char *QGIS_APPLICATION_NAME; #ifndef SIP_RUN - QgsApplication( int &argc, char **argv, bool GUIenabled, const QString &customConfigPath = QString(), const QString &platformName = "desktop" ); + QgsApplication( int &argc, char **argv, bool GUIenabled, const QString &profileFolder = QString(), const QString &platformName = "desktop" ); #else - QgsApplication( SIP_PYLIST argv, bool GUIenabled, QString customConfigPath = QString() ) / PostHook = __pyQtQAppHook__ / [( int &argc, char **argv, bool GUIenabled, const QString &customConfigPath = QString() )]; + QgsApplication( SIP_PYLIST argv, bool GUIenabled, QString profileFolder = QString(), QString platformName = "desktop" ) / PostHook = __pyQtQAppHook__ / [( int &argc, char **argv, bool GUIenabled, const QString &profileFolder = QString(), const QString &platformName = "desktop" )]; % MethodCode // The Python interface is a list of argument strings that is modified. @@ -132,7 +134,7 @@ class CORE_EXPORT QgsApplication : public QApplication // Create it now the arguments are right. static int nargc = argc; - sipCpp = new sipQgsApplication( nargc, argv, a1, *a2 ); + sipCpp = new sipQgsApplication( nargc, argv, a1, *a2, *a3 ); // Now modify the original list. qtgui_UpdatePyArgv( a0, argc, argv ); @@ -155,7 +157,7 @@ class CORE_EXPORT QgsApplication : public QApplication the above case. \note not available in Python bindings */ - static void init( QString customConfigPath = QString() ) SIP_SKIP; + static void init( QString profileFolder = QString() ) SIP_SKIP; //! Watch for QFileOpenEvent. virtual bool event( QEvent *event ) override; @@ -716,6 +718,7 @@ class CORE_EXPORT QgsApplication : public QApplication QgsSymbolLayerRegistry *mSymbolLayerRegistry = nullptr; QgsTaskManager *mTaskManager = nullptr; QgsLayoutItemRegistry *mLayoutItemRegistry = nullptr; + QgsUserProfileManager *mUserConfigManager = nullptr; QString mNullRepresentation; ApplicationMembers(); diff --git a/src/core/qgsuserprofile.cpp b/src/core/qgsuserprofile.cpp new file mode 100644 index 00000000000..dd5dc4fe90f --- /dev/null +++ b/src/core/qgsuserprofile.cpp @@ -0,0 +1,129 @@ +/*************************************************************************** + qgsuserprofile.h + -------------------------------------- + Date : Jul-2017 + Copyright : (C) 2017 by Nathan Woodrow + Email : woodrow.nathan 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 "qgsuserprofile.h" +#include "qgsapplication.h" + +#include +#include +#include +#include +#include + +QgsUserProfile::QgsUserProfile( const QString &folder ) +{ + mProfileFolder = folder; +} + +const QString QgsUserProfile::folder() const +{ + return mProfileFolder; +} + +QgsError QgsUserProfile::validate() const +{ + QgsError error; + if ( !QDir( mProfileFolder ).exists() ) + { + error.append( QObject::tr( "Profile folder doesn't exist" ) ); + } + return error; +} + +const QString QgsUserProfile::name() const +{ + QDir dir( mProfileFolder ); + return dir.dirName(); +} + +void QgsUserProfile::initSettings() const +{ + // tell QSettings to use INI format and save the file in custom config path + QSettings::setDefaultFormat( QSettings::IniFormat ); + QSettings::setPath( QSettings::IniFormat, QSettings::UserScope, folder() ); +} + +const QString QgsUserProfile::alias() const +{ + QFile qgisPrivateDbFile( qgisDB() ); + + // Looks for qgis.db + // If it's not there we can just return name. + if ( !qgisPrivateDbFile.exists() ) + { + return name(); + } + + QSqlDatabase db = QSqlDatabase::addDatabase( "QSQLITE" ); + db.setDatabaseName( qgisDB() ); + if ( !db.open() ) + return name(); + + QSqlQuery query; + query.prepare( "SELECT value FROM tbl_config_variables WHERE variable = 'ALIAS'" ); + QString profileAlias = name(); + if ( query.exec() ) + { + query.next(); + QString alias = query.value( 0 ).toString(); + if ( !alias.isEmpty() ) + profileAlias = alias; + } + db.close(); + return profileAlias; +} + +QgsError QgsUserProfile::setAlias( const QString &alias ) +{ + QgsError error; + QFile qgisPrivateDbFile( qgisDB() ); + + if ( !qgisPrivateDbFile.exists() ) + { + error.append( QObject::tr( "qgis.db doesn't exist in the uers profile folder" ) ); + return error; + } + + QSqlDatabase db = QSqlDatabase::addDatabase( "QSQLITE", "userprofile" ); + db.setDatabaseName( qgisDB() ); + if ( !db.open() ) + { + error.append( QObject::tr( "Unable to open qgis.db for update." ) ); + return error; + } + + QSqlQuery query; + QString sql = "INSERT OR REPLACE INTO tbl_config_variables VALUES ('ALIAS', :alias);"; + query.prepare( sql ); + query.bindValue( ":alias", alias ); + query.exec(); + db.close(); + return error; +} + +const QIcon QgsUserProfile::icon() const +{ + QString path = mProfileFolder + QDir::separator() + "icon.svg"; + if ( !QDir( path ).exists() ) + { + return QgsApplication::getThemeIcon( "user.svg" ); + } + return QIcon( path ); +} + +QString QgsUserProfile::qgisDB() const +{ + return mProfileFolder + QDir::separator() + "qgis.db" ; +} diff --git a/src/core/qgsuserprofile.h b/src/core/qgsuserprofile.h new file mode 100644 index 00000000000..0bc840bb9b6 --- /dev/null +++ b/src/core/qgsuserprofile.h @@ -0,0 +1,88 @@ +/*************************************************************************** + qgsuserprofile.h + -------------------------------------- + Date : Jul-2017 + Copyright : (C) 2017 by Nathan Woodrow + Email : woodrow.nathan 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 QGSUSERPROFILE_H +#define QGSUSERPROFILE_H + +#include "qgis_core.h" +#include "qgserror.h" +#include + +/** \ingroup core + * User profile contains information about the user profile folders on the machine. + * In QGIS 3 all settings, plugins, etc were moved into a %APPDATA%/profiles folder for each platform. + * This allows for manage different user profiles per machine vs the single default one that was allowed in the + * past. + * + * A user profile is all settings and anything that used to be found in .qgis3 in the users home folder. + * + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsUserProfile +{ + public: + + /** + * Reference to a existing user profile folder. + * Profile folder should be created using QgsProfileManager. + * \param folder An existing profile folder as the base of the user profile. + */ + QgsUserProfile( const QString &folder ); + + /** + * The base folder for the user profile. + */ + const QString folder() const; + + /** + * Check of the profile is in a valid state. + */ + QgsError validate() const; + + /** + * The name for the user profile. + */ + const QString name() const; + + /** + * Init the settings from the user folder. + */ + void initSettings() const; + + /** + * Return the alias for the user profile. + * \return If no alias is set name() is returned. + */ + const QString alias() const; + + /** + * Set the alias of the profile. The alias is a user friendly name. + * \param alias A user friendly name for the profile. + * \return True of setting the alias was successful. + */ + QgsError setAlias( const QString &alias ); + + /** + * The icon for the user profile. + * \return A QIcon for the users + */ + const QIcon icon() const; + + private: + QString qgisDB() const; + QString mProfileFolder; +}; + +#endif diff --git a/src/core/qgsuserprofilemanager.cpp b/src/core/qgsuserprofilemanager.cpp new file mode 100644 index 00000000000..d002837ff02 --- /dev/null +++ b/src/core/qgsuserprofilemanager.cpp @@ -0,0 +1,197 @@ +/*************************************************************************** + qgsuserprofilemanager.cpp + -------------------------------------- + Date : Jul-2017 + Copyright : (C) 2017 by Nathan Woodrow + Email : woodrow.nathan 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 "qgsuserprofilemanager.h" +#include "qgsuserprofile.h" +#include "qgsapplication.h" +#include "qgslogger.h" + +#include +#include +#include +#include +#include + + +QgsUserProfileManager::QgsUserProfileManager( const QString &rootLocation, QObject *parent ) + : QObject( parent ) +{ + mWatcher.reset( new QFileSystemWatcher() ); + setRootLocation( rootLocation ); + connect( mWatcher.get(), &QFileSystemWatcher::directoryChanged, this, [this] + { + emit profilesChanged(); + } ); +} + +QString QgsUserProfileManager::resolveProfilesFolder( const QString &basePath ) +{ + return basePath + QDir::separator() + "profiles"; +} + +QgsUserProfile *QgsUserProfileManager::getProfile( const QString &defaultProfile, bool createNew, bool initSettings ) +{ + QString profileName = defaultProfile.isEmpty() ? defaultProfileName() : defaultProfile; + + if ( createNew && !profileExists( defaultProfile ) ) + { + createUserProfile( profileName ); + } + + QgsUserProfile *profile = profileForName( profileName ); + if ( initSettings ) + profile->initSettings(); + + return profile; +} + +void QgsUserProfileManager::setRootLocation( QString rootProfileLocation ) +{ + mRootProfilePath = rootProfileLocation; + + if ( !mWatcher->directories().isEmpty() ) + { + mWatcher->removePaths( mWatcher->directories() ); + } + + if ( QDir( mRootProfilePath ).exists() ) + { + mWatcher->addPath( mRootProfilePath ); + } + mSettings = new QSettings( settingsFile(), QSettings::IniFormat ); +} + +bool QgsUserProfileManager::rootLocationIsSet() const +{ + return !mRootProfilePath.isEmpty(); +} + +QString QgsUserProfileManager::defaultProfileName() const +{ + QString profileName = "default"; + mSettings->value( "/defaultProfile", "default" ); + return profileName; +} + +void QgsUserProfileManager::setDefaultProfileName( const QString &name ) +{ + mSettings->setValue( "/defaultProfile", name ); + mSettings->sync(); +} + +void QgsUserProfileManager::setDefaultFromActive() +{ + setDefaultProfileName( userProfile()->name() ); +} + +QStringList QgsUserProfileManager::allProfiles() const +{ + return QDir( mRootProfilePath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot ); +} + +bool QgsUserProfileManager::profileExists( const QString &name ) const +{ + return allProfiles().contains( name ); +} + +QgsUserProfile *QgsUserProfileManager::profileForName( const QString name ) const +{ + QString profilePath = mRootProfilePath + QDir::separator() + name; + return new QgsUserProfile( profilePath ); +} + +QgsError QgsUserProfileManager::createUserProfile( const QString &name ) +{ + QgsError error; + + // TODO Replace with safe folder name + + QDir folder( mRootProfilePath + QDir::separator() + name ); + if ( !folder.exists() ) + { + QDir().mkdir( folder.absolutePath() ); + } + + QFile qgisPrivateDbFile( folder.absolutePath() + QDir::separator() + "qgis.db" ); + + // first we look for ~/.qgis/qgis.db + if ( !qgisPrivateDbFile.exists() ) + { + // if it doesn't exist we copy it from the global resources dir + QString qgisMasterDbFileName = QgsApplication::qgisMasterDatabaseFilePath(); + QFile masterFile( qgisMasterDbFileName ); + + //now copy the master file into the users .qgis dir + masterFile.copy( qgisPrivateDbFile.fileName() ); + } + + if ( error.isEmpty() ) + { + emit profilesChanged(); + } + + return error; +} + +QgsError QgsUserProfileManager::deleteProfile( const QString name ) +{ + QgsError error; + QDir folder( mRootProfilePath + QDir::separator() + name ); + + // This might have to be changed to something better. + bool deleted = folder.removeRecursively(); + if ( !deleted ) + { + error.append( ( tr( "Unable to fully delete user profile folder" ) ) ); + } + else + { + emit profilesChanged(); + } + return error; +} + +QString QgsUserProfileManager::settingsFile() +{ + return mRootProfilePath + QDir::separator() + "profiles.ini"; +} + +QgsUserProfile *QgsUserProfileManager::userProfile() +{ + return mUserProfile.get(); +} + +void QgsUserProfileManager::loadUserProfile( const QString &name ) +{ + QString path = QDir::toNativeSeparators( QCoreApplication::applicationFilePath() ); + QStringList arguments; + arguments << QCoreApplication::arguments(); + // The first is the path to the application + // on Windows this might not be case so we need to handle that + // http://doc.qt.io/qt-5/qcoreapplication.html#arguments + arguments.removeFirst(); + + arguments << "--profile" << name; + QgsDebugMsg( QString( "Starting instance from %1 with %2" ).arg( path ).arg( arguments.join( " " ) ) ); + QProcess::startDetached( path, arguments, QDir::toNativeSeparators( QCoreApplication::applicationDirPath() ) ); +} + +void QgsUserProfileManager::setActiveUserProfile( const QString &profile ) +{ + if ( ! mUserProfile.get() ) + { + mUserProfile.reset( profileForName( profile ) ); + } +} diff --git a/src/core/qgsuserprofilemanager.h b/src/core/qgsuserprofilemanager.h new file mode 100644 index 00000000000..71342a65f10 --- /dev/null +++ b/src/core/qgsuserprofilemanager.h @@ -0,0 +1,182 @@ +/*************************************************************************** + qgsuserprofilemanager.h + -------------------------------------- + Date : Jul-2017 + Copyright : (C) 2017 by Nathan Woodrow + Email : woodrow.nathan 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 QGSUSERPROFILEMANAGER_H +#define QGSUSERPROFILEMANAGER_H + +#include +#include + + +#include "qgis.h" +#include "qgis_core.h" +#include "qgserror.h" +#include "qgsuserprofile.h" + +#include + +/** \ingroup core + * User profile manager is used to manager list, and manage user profiles on the users machine. + * + * In QGIS 3 all settings, plugins, etc were moved into a %APPDATA%/profiles folder for each platform. + * This allows for manage different user profiles per machine vs the single default one that was allowed in the + * past. + * + * A user profile is all settings and anything that used to be found in .qgis3 in the users home folder. + * + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsUserProfileManager : public QObject +{ + Q_OBJECT + + public: + + /** + * User profile manager used to manage user profiles for the instance of QGIS. + */ + QgsUserProfileManager( const QString &rootLocation = QString(), QObject *parent = nullptr ); + + /** + * Resolves the profiles folder for the given path. Path will have \\profiles appended to the path + * \param basePath The base path to resolve the path from to append the \\profiles folder to. + * \return The root path to store user profiles. + */ + static QString resolveProfilesFolder( const QString &basePath = QString() ); + + /** + * Return the profile from the given root profile location. + * If no name is given it returns a profile called "default". + * By default will create the profile folder if not found. + * By default will init the user settings. + * \note Returns a new QgsUserProfile. Ownership transferred to caller. + * \param defaultProfile The profile name to find. Empty profile name will return "default" for the name. + * \param createNew Create the profile folder if it doesn't exist. + * \return The user profile + */ + QgsUserProfile *getProfile( const QString &defaultProfile = "default", bool createNew = true, bool initSettings = true ) SIP_FACTORY; + + /** + * Set the root profile location for the profile manager. All profiles are loaded from this + * location. Will also contain a profiles.ini for holding profile settings. + * \param rootProfileLocation Path to the top level profile folder which contains folders for each profile. + */ + void setRootLocation( QString rootProfileLocation ); + + /** + * Returns the path to the root profiles location. + * \return The root path to the profiles folder. + */ + QString rootLocation() { return mRootProfilePath; } + + /** + * Check if the root location has been set for the manager. + * \return True if the root location has been set. + */ + bool rootLocationIsSet() const; + + /** + * A list of all found profile names. + * \return + */ + QStringList allProfiles() const; + + /** + * Check if a profile exists. + * \return False if the profile can't be found. + */ + bool profileExists( const QString &name ) const; + + /** + * Returns the name of the default profile that has been set in .default. + * \return The name of the default profile. + */ + QString defaultProfileName() const; + + /** + * Sets the default profile name. The default profile name is used when loading QGIS + * with no arguments. + * \param name The name of the profile to save. + */ + void setDefaultProfileName( const QString &name ); + + /** + * Set the default profile name from the current active profile. + */ + void setDefaultFromActive(); + + /** + * Return the profile found for a given name. + * \param name The name of the profile to return. + * \return A QgsUserprofile pointing to the location of the user profile. + */ + QgsUserProfile *profileForName( const QString name ) const; + + /** + * Create a user profile given by the name + * \param name + * \return A QgsError which report if there was any error creating the user profile. + */ + QgsError createUserProfile( const QString &name ); + + /** + * Deletes a profile from the root profiles folder. + * \note There is no undo on this as it deletes the folder from the machine. + * \param name The name of the profile to delete. + * \return A QgsError with a message if the profile failed to be deleted. + */ + QgsError deleteProfile( const QString name ); + + /** + * The currently active user profile. + * \return The currently active user profile. + */ + QgsUserProfile *userProfile(); + + /** + * Sets the active profile in the manager. + * This can only be set once. + * Setting this again does nothing. + * + * \param profile The name of the active profile + */ + void setActiveUserProfile( const QString &profile ); + + /** + * Starts a new instance of QGIS for the given profile. + * \param name The profile to start QGIS with. + */ + void loadUserProfile( const QString &name ); + + signals: + + /** + * Emitted when the list of profiles is changed. + */ + void profilesChanged( ); + + private: + + std::unique_ptr mWatcher; + + QString mRootProfilePath; + + std::unique_ptr mUserProfile; + + QString settingsFile(); + + QSettings *mSettings = nullptr; +}; + +#endif // QGSUSERPROFILEMANAGER_H diff --git a/src/ui/qgsuserprofilemanagerwidget.ui b/src/ui/qgsuserprofilemanagerwidget.ui new file mode 100644 index 00000000000..534e5d0b8ed --- /dev/null +++ b/src/ui/qgsuserprofilemanagerwidget.ui @@ -0,0 +1,107 @@ + + + QgsUserProfileManagerWidget + + + + 0 + 0 + 314 + 380 + + + + Form + + + + + + + + Add + + + + :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg + + + + + + + Add + + + + :/images/themes/default/symbologyEdit.png:/images/themes/default/symbologyEdit.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Remove + + + + :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Profiles Folder + + + + + + + + + Profiles + + + + + + + + + + + + + diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index c0e240b750d..6e05c1171de 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -288,7 +288,7 @@ ACCEPTABLE_MISSING_DOCS = { "QgsLayerTreeRegistryBridge": ["groupWillRemoveChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)", "QgsLayerTreeRegistryBridge(QgsLayerTreeGroup *root, QObject *parent=nullptr)", "removeLayersFromRegistry(const QStringList &layerIds)", "isEnabled() const ", "layersAdded(const QList< QgsMapLayer * > &layers)", "newLayersVisible() const ", "setEnabled(bool enabled)", "layersWillBeRemoved(const QStringList &layerIds)", "setNewLayersVisible(bool enabled)", "groupRemovedChildren()"], "QgsLabelComponent": ["setUseCenter(const bool use)", "setPictureBuffer(const double buffer)", "offset() const ", "dpiRatio() const ", "setSize(const QgsPointXY &point)", "setRotation(const double rotation)", "picture() const ", "rotationOffset() const ", "setRotationOffset(const double rotation)", "setCenter(const QgsPointXY &point)", "pictureBuffer() const ", "setDpiRatio(const double ratio)", "size() const ", "setUseRotation(const bool use)", "setPicture(QPicture *picture)", "text() const ", "center() const ", "setOrigin(const QgsPointXY &point)", "setUseOrigin(const bool use)", "setText(const QString &text)", "useOrigin() const ", "useCenter() const ", "origin() const ", "setOffset(const QgsPointXY &point)", "rotation() const ", "useRotation() const "], "QgsMarkerSymbolLayer": ["setAngle(double angle)", "sizeMapUnitScale() const ", "scaleMethod() const ", "VerticalAnchorPoint", "offsetMapUnitScale() const ", "renderPoint(QPointF point, QgsSymbolRenderContext &context)=0", "setOffsetUnit(QgsSymbol::OutputUnit unit)", "setSizeUnit(QgsSymbol::OutputUnit unit)", "setVerticalAnchorPoint(VerticalAnchorPoint v)", "setHorizontalAnchorPoint(HorizontalAnchorPoint h)", "_rotatedOffset(QPointF offset, double angle)", "writeSldMarker(QDomDocument &doc, QDomElement &element, const QgsStringMap &props) const ", "offsetUnit() const ", "offset() const ", "sizeUnit() const ", "QgsMarkerSymbolLayer(bool locked=false)", "setSize(double size)", "setSizeMapUnitScale(const QgsMapUnitScale &scale)", "size() const ", "horizontalAnchorPoint() const ", "setScaleMethod(QgsSymbol::ScaleMethod scaleMethod)", "setOffset(QPointF offset)", "markerOffset(QgsSymbolRenderContext &context, double &offsetX, double &offsetY) const ", "verticalAnchorPoint() const ", "HorizontalAnchorPoint", "setOffsetMapUnitScale(const QgsMapUnitScale &scale)", "angle() const "], - "QgsApplication": ["QgsApplication(int &argc, char **argv, bool GUIenabled, const QString &customConfigPath=QString(), const QString &platformName=\"desktop\")"], + "QgsApplication": ["QgsApplication(int &argc, char **argv, bool GUIenabled, const QString &profileFolder=QString(), const QString &platformName=\"desktop\")"], "QgsExpression::Visitor": ["visit(const NodeLiteral &n)=0", "visit(const NodeFunction &n)=0", "visit(const NodeCondition &n)=0", "visit(const NodeUnaryOperator &n)=0", "visit(const NodeInOperator &n)=0", "visit(const NodeBinaryOperator &n)=0", "visit(const NodeColumnRef &n)=0"], "QgsComposerTable": ["QgsComposerTable(QgsComposition *composition)"], "QgsFillSymbol": ["setAngle(double angle)", "QgsFillSymbol(const QgsSymbolLayerList &layers=QgsSymbolLayerList())", "renderPolygon(const QPolygonF &points, QList< QPolygonF > *rings, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)"], From e37f682cca3b21ba1742146fb6decd02fdc76e49 Mon Sep 17 00:00:00 2001 From: Nathan Woodrow Date: Mon, 24 Jul 2017 14:20:09 +1000 Subject: [PATCH 131/266] Also load profiles-path from global settings --- python/core/qgsuserprofile.sip | 2 + src/app/main.cpp | 84 +++++++++++++++++----------------- src/core/qgsuserprofile.h | 1 + 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/python/core/qgsuserprofile.sip b/python/core/qgsuserprofile.sip index fb5c1d95fad..39a070e698c 100644 --- a/python/core/qgsuserprofile.sip +++ b/python/core/qgsuserprofile.sip @@ -7,6 +7,8 @@ ************************************************************************/ + + class QgsUserProfile { %Docstring diff --git a/src/app/main.cpp b/src/app/main.cpp index a07fcecf10a..0e38bd0d07b 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -785,17 +785,54 @@ int main( int argc, char *argv[] ) QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME ); QCoreApplication::setAttribute( Qt::AA_DontShowIconsInMenus, false ); - if ( getenv( "QGIS_CUSTOM_CONFIG_PATH" ) ) + + // SetUp the QgsSettings Global Settings: + // - use the path specified with --globalsettings path, + // - use the environment if not found + // - use a default location as a fallback + if ( globalsettingsfile.isEmpty() ) { - QString envProfileFolder = getenv( "QGIS_CUSTOM_CONFIG_PATH" ); - configLocalStorageLocation = envProfileFolder + QDir::separator() + "profiles"; + globalsettingsfile = getenv( "QGIS_GLOBAL_SETTINGS_FILE" ); } - else + if ( globalsettingsfile.isEmpty() ) { - // TODO Get from global settings. - configLocalStorageLocation = QStandardPaths::standardLocations( QStandardPaths::AppDataLocation ).value( 0 ); + QString default_globalsettingsfile = QgsApplication::pkgDataPath() + "/qgis_global_settings.ini"; + if ( QFile::exists( default_globalsettingsfile ) ) + { + globalsettingsfile = default_globalsettingsfile; + } + } + if ( !globalsettingsfile.isEmpty() ) + { + if ( ! QgsSettings::setGlobalSettingsPath( globalsettingsfile ) ) + { + QgsMessageLog::logMessage( QString( "Invalid globalsettingsfile path: %1" ).arg( globalsettingsfile ), QStringLiteral( "QGIS" ) ); + } + else + { + QgsMessageLog::logMessage( QString( "Successfully loaded globalsettingsfile path: %1" ).arg( globalsettingsfile ), QStringLiteral( "QGIS" ) ); + } } + QgsSettings settings; + if ( configLocalStorageLocation.isEmpty() ) + { + if ( getenv( "QGIS_CUSTOM_CONFIG_PATH" ) ) + { + configLocalStorageLocation = getenv( "QGIS_CUSTOM_CONFIG_PATH" ); + } + else if ( settings.contains( "profiles-path", QgsSettings::Core ) ) + { + configLocalStorageLocation = settings.value( "profiles-path", "", QgsSettings::Core ).toString(); + QgsDebugMsg( QString( "Loading profiles path from global config at %1" ).arg( configLocalStorageLocation ) ); + } + + // If it is still empty at this point we get it from the standard location. + if ( configLocalStorageLocation.isEmpty() ) + { + configLocalStorageLocation = QStandardPaths::standardLocations( QStandardPaths::AppDataLocation ).value( 0 ); + } + } QString rootProfileFolder = QgsUserProfileManager::resolveProfilesFolder( configLocalStorageLocation ); QgsUserProfileManager manager( rootProfileFolder ); @@ -836,41 +873,6 @@ int main( int argc, char *argv[] ) } #endif - // - // Set up the QSettings environment must be done after qapp is created - QCoreApplication::setOrganizationName( QgsApplication::QGIS_ORGANIZATION_NAME ); - QCoreApplication::setOrganizationDomain( QgsApplication::QGIS_ORGANIZATION_DOMAIN ); - QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME ); - QCoreApplication::setAttribute( Qt::AA_DontShowIconsInMenus, false ); - - // SetUp the QgsSettings Global Settings: - // - use the path specified with --globalsettings path, - // - use the environment if not found - // - use a default location as a fallback - if ( globalsettingsfile.isEmpty() ) - { - globalsettingsfile = getenv( "QGIS_GLOBAL_SETTINGS_FILE" ); - } - if ( globalsettingsfile.isEmpty() ) - { - QString default_globalsettingsfile = QgsApplication::pkgDataPath() + "/qgis_global_settings.ini"; - if ( QFile::exists( default_globalsettingsfile ) ) - { - globalsettingsfile = default_globalsettingsfile; - } - } - if ( !globalsettingsfile.isEmpty() ) - { - if ( ! QgsSettings::setGlobalSettingsPath( globalsettingsfile ) ) - { - QgsMessageLog::logMessage( QString( "Invalid globalsettingsfile path: %1" ).arg( globalsettingsfile ), QStringLiteral( "QGIS" ) ); - } - else - { - QgsMessageLog::logMessage( QString( "Successfully loaded globalsettingsfile path: %1" ).arg( globalsettingsfile ), QStringLiteral( "QGIS" ) ); - } - } - // TODO: use QgsSettings QSettings *customizationsettings = nullptr; diff --git a/src/core/qgsuserprofile.h b/src/core/qgsuserprofile.h index 0bc840bb9b6..90d24bcb705 100644 --- a/src/core/qgsuserprofile.h +++ b/src/core/qgsuserprofile.h @@ -1,3 +1,4 @@ + /*************************************************************************** qgsuserprofile.h -------------------------------------- From 12c634cef3a11d139f9437970ceeaab43caddcce Mon Sep 17 00:00:00 2001 From: Nathan Woodrow Date: Mon, 24 Jul 2017 14:52:09 +1000 Subject: [PATCH 132/266] Load default profile name from global Also adds overrideLocalProfile to ignore local profiles.ini file --- python/core/qgsuserprofile.sip | 1 - python/core/qgsuserprofilemanager.sip | 6 ++++++ src/app/main.cpp | 4 ++-- src/core/qgsuserprofile.h | 1 - src/core/qgsuserprofilemanager.cpp | 17 +++++++++++++---- src/core/qgsuserprofilemanager.h | 4 ++++ 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/python/core/qgsuserprofile.sip b/python/core/qgsuserprofile.sip index 39a070e698c..cc828d947d8 100644 --- a/python/core/qgsuserprofile.sip +++ b/python/core/qgsuserprofile.sip @@ -8,7 +8,6 @@ - class QgsUserProfile { %Docstring diff --git a/python/core/qgsuserprofilemanager.sip b/python/core/qgsuserprofilemanager.sip index 71e503f4f42..1d7acb38a20 100644 --- a/python/core/qgsuserprofilemanager.sip +++ b/python/core/qgsuserprofilemanager.sip @@ -95,6 +95,12 @@ class QgsUserProfileManager : QObject QString defaultProfileName() const; %Docstring Returns the name of the default profile that has been set in .default. + First checks profile.ini in \\profiles folder + Then checks defaultProfile in global settings + Finally returns "default" if all else fails +.. note:: + + Setting overrideLocalProfile in global settings will always ignore profiles.ini :return: The name of the default profile. :rtype: str %End diff --git a/src/app/main.cpp b/src/app/main.cpp index 0e38bd0d07b..c090a0655af 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -821,9 +821,9 @@ int main( int argc, char *argv[] ) { configLocalStorageLocation = getenv( "QGIS_CUSTOM_CONFIG_PATH" ); } - else if ( settings.contains( "profiles-path", QgsSettings::Core ) ) + else if ( settings.contains( "profilesPath", QgsSettings::Core ) ) { - configLocalStorageLocation = settings.value( "profiles-path", "", QgsSettings::Core ).toString(); + configLocalStorageLocation = settings.value( "profilesPath", "", QgsSettings::Core ).toString(); QgsDebugMsg( QString( "Loading profiles path from global config at %1" ).arg( configLocalStorageLocation ) ); } diff --git a/src/core/qgsuserprofile.h b/src/core/qgsuserprofile.h index 90d24bcb705..0bc840bb9b6 100644 --- a/src/core/qgsuserprofile.h +++ b/src/core/qgsuserprofile.h @@ -1,4 +1,3 @@ - /*************************************************************************** qgsuserprofile.h -------------------------------------- diff --git a/src/core/qgsuserprofilemanager.cpp b/src/core/qgsuserprofilemanager.cpp index d002837ff02..0e69be28dc9 100644 --- a/src/core/qgsuserprofilemanager.cpp +++ b/src/core/qgsuserprofilemanager.cpp @@ -17,6 +17,7 @@ #include "qgsuserprofile.h" #include "qgsapplication.h" #include "qgslogger.h" +#include "qgssettings.h" #include #include @@ -80,14 +81,22 @@ bool QgsUserProfileManager::rootLocationIsSet() const QString QgsUserProfileManager::defaultProfileName() const { - QString profileName = "default"; - mSettings->value( "/defaultProfile", "default" ); - return profileName; + QString defaultName = "default"; + // If the profiles.ini doesn't have the default profile we grab it from + // global settings as it might be set by the admin. + // If the overrideProfile flag is set then no matter what the profiles.ini says we always take the + // global profile. + QgsSettings globalSettings; + if ( !mSettings->contains( "/core/defaultProfile" ) || globalSettings.value( "overrideLocalProfile", false, QgsSettings::Core ).toBool() ) + { + return globalSettings.value( "defaultProfile", defaultName, QgsSettings::Core ).toString(); + } + return mSettings->value( "/core/defaultProfile", defaultName ).toString(); } void QgsUserProfileManager::setDefaultProfileName( const QString &name ) { - mSettings->setValue( "/defaultProfile", name ); + mSettings->setValue( "/core/defaultProfile", name ); mSettings->sync(); } diff --git a/src/core/qgsuserprofilemanager.h b/src/core/qgsuserprofilemanager.h index 71342a65f10..502128debf2 100644 --- a/src/core/qgsuserprofilemanager.h +++ b/src/core/qgsuserprofilemanager.h @@ -100,6 +100,10 @@ class CORE_EXPORT QgsUserProfileManager : public QObject /** * Returns the name of the default profile that has been set in .default. + * First checks profile.ini in \\profiles folder + * Then checks defaultProfile in global settings + * Finally returns "default" if all else fails + * \note Setting overrideLocalProfile in global settings will always ignore profiles.ini * \return The name of the default profile. */ QString defaultProfileName() const; From 391712d2d42a761acb4bacce81cbe9ed8da52f6b Mon Sep 17 00:00:00 2001 From: Nathan Woodrow Date: Mon, 24 Jul 2017 15:19:06 +1000 Subject: [PATCH 133/266] Always log message bar messages to message log Mainly so they don't just disappear on the user --- python/gui/qgsmessagebaritem.sip | 30 ++++++++++++++++++++++++++++++ src/gui/qgsmessagebar.cpp | 22 ++++++++++++++++++++++ src/gui/qgsmessagebaritem.cpp | 25 +++++++++++++++++++++++++ src/gui/qgsmessagebaritem.h | 25 +++++++++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/python/gui/qgsmessagebaritem.sip b/python/gui/qgsmessagebaritem.sip index 4efb31c5402..c613bc727d0 100644 --- a/python/gui/qgsmessagebaritem.sip +++ b/python/gui/qgsmessagebaritem.sip @@ -41,26 +41,56 @@ make out a widget containing a widget to be displayed on the bar :rtype: QgsMessageBarItem %End + QString text() const; +%Docstring + Returns the text for the message. + :rtype: str +%End + QgsMessageBarItem *setTitle( const QString &title ); %Docstring :rtype: QgsMessageBarItem %End + QString title() const; +%Docstring + Returns the title for the message. + :rtype: str +%End + QgsMessageBarItem *setLevel( QgsMessageBar::MessageLevel level ); %Docstring :rtype: QgsMessageBarItem %End + QgsMessageBar::MessageLevel level() const; +%Docstring + Returns the message level for the message. + :rtype: QgsMessageBar.MessageLevel +%End + QgsMessageBarItem *setWidget( QWidget *widget ); %Docstring :rtype: QgsMessageBarItem %End + QWidget *widget() const; +%Docstring + Returns the widget for the message. + :rtype: QWidget +%End + QgsMessageBarItem *setIcon( const QIcon &icon ); %Docstring :rtype: QgsMessageBarItem %End + QIcon icon() const; +%Docstring + Returns the icon for the message. + :rtype: QIcon +%End + QgsMessageBarItem *setDuration( int duration ); %Docstring :rtype: QgsMessageBarItem diff --git a/src/gui/qgsmessagebar.cpp b/src/gui/qgsmessagebar.cpp index fb6a62e3560..116b23817c7 100644 --- a/src/gui/qgsmessagebar.cpp +++ b/src/gui/qgsmessagebar.cpp @@ -18,6 +18,7 @@ #include "qgsmessagebar.h" #include "qgsmessagebaritem.h" #include "qgsapplication.h" +#include "qgsmessagelog.h" #include #include @@ -268,6 +269,27 @@ void QgsMessageBar::pushItem( QgsMessageBarItem *item ) // avoid duplicated widget popWidget( item ); showItem( item ); + + // Log all messages that are sent to the message bar into the message log so the + // user can get them back easier. + QString formattedTitle = QString( "%1 : %2" ).arg( item->title() ).arg( item->text() ); + QgsMessageLog::MessageLevel level; + switch ( item->level() ) + { + case QgsMessageBar::INFO: + level = QgsMessageLog::INFO; + break; + case QgsMessageBar::WARNING: + level = QgsMessageLog::WARNING; + break; + case QgsMessageBar::CRITICAL: + level = QgsMessageLog::CRITICAL; + break; + default: + level = QgsMessageLog::NONE; + break; + } + QgsMessageLog::logMessage( formattedTitle, tr( "Messages" ), level ); } QgsMessageBarItem *QgsMessageBar::pushWidget( QWidget *widget, QgsMessageBar::MessageLevel level, int duration ) diff --git a/src/gui/qgsmessagebaritem.cpp b/src/gui/qgsmessagebaritem.cpp index 88e4e305e2b..10d842ce71b 100644 --- a/src/gui/qgsmessagebaritem.cpp +++ b/src/gui/qgsmessagebaritem.cpp @@ -196,6 +196,11 @@ QgsMessageBarItem *QgsMessageBarItem::setText( const QString &text ) return this; } +QString QgsMessageBarItem::text() const +{ + return mText; +} + QgsMessageBarItem *QgsMessageBarItem::setTitle( const QString &title ) { mTitle = title; @@ -203,6 +208,11 @@ QgsMessageBarItem *QgsMessageBarItem::setTitle( const QString &title ) return this; } +QString QgsMessageBarItem::title() const +{ + return mTitle; +} + QgsMessageBarItem *QgsMessageBarItem::setLevel( QgsMessageBar::MessageLevel level ) { mLevel = level; @@ -211,6 +221,11 @@ QgsMessageBarItem *QgsMessageBarItem::setLevel( QgsMessageBar::MessageLevel leve return this; } +QgsMessageBar::MessageLevel QgsMessageBarItem::level() const +{ + return mLevel; +} + QgsMessageBarItem *QgsMessageBarItem::setWidget( QWidget *widget ) { if ( mWidget ) @@ -227,12 +242,22 @@ QgsMessageBarItem *QgsMessageBarItem::setWidget( QWidget *widget ) return this; } +QWidget *QgsMessageBarItem::widget() const +{ + return mWidget; +} + QgsMessageBarItem *QgsMessageBarItem::setIcon( const QIcon &icon ) { mUserIcon = icon; return this; } +QIcon QgsMessageBarItem::icon() const +{ + return mUserIcon; +} + QgsMessageBarItem *QgsMessageBarItem::setDuration( int duration ) { diff --git a/src/gui/qgsmessagebaritem.h b/src/gui/qgsmessagebaritem.h index f5b05c65579..55dc1858f0f 100644 --- a/src/gui/qgsmessagebaritem.h +++ b/src/gui/qgsmessagebaritem.h @@ -48,14 +48,39 @@ class GUI_EXPORT QgsMessageBarItem : public QWidget QgsMessageBarItem *setText( const QString &text ); + /** + * Returns the text for the message. + */ + QString text() const; + QgsMessageBarItem *setTitle( const QString &title ); + /** + * Returns the title for the message. + */ + QString title() const; + QgsMessageBarItem *setLevel( QgsMessageBar::MessageLevel level ); + /** + * Returns the message level for the message. + */ + QgsMessageBar::MessageLevel level() const; + QgsMessageBarItem *setWidget( QWidget *widget ); + /** + * Returns the widget for the message. + */ + QWidget *widget() const; + QgsMessageBarItem *setIcon( const QIcon &icon ); + /** + * Returns the icon for the message. + */ + QIcon icon() const; + QgsMessageBarItem *setDuration( int duration ); //! returns the duration in second of the message From 2723f4f199b1c41dd8fd0572b0253b53ab826647 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Mon, 24 Jul 2017 07:30:48 +0200 Subject: [PATCH 134/266] remove duplicate method followup 391712d2d42a761acb4bacce81cbe9ed8da52f6b --- python/gui/qgsmessagebaritem.sip | 6 ------ src/gui/qgsmessagebaritem.h | 3 --- 2 files changed, 9 deletions(-) diff --git a/python/gui/qgsmessagebaritem.sip b/python/gui/qgsmessagebaritem.sip index c613bc727d0..cbf155d4b63 100644 --- a/python/gui/qgsmessagebaritem.sip +++ b/python/gui/qgsmessagebaritem.sip @@ -102,12 +102,6 @@ returns the duration in second of the message :rtype: int %End - QgsMessageBar::MessageLevel level(); -%Docstring -returns the level - :rtype: QgsMessageBar.MessageLevel -%End - QString getStyleSheet(); %Docstring returns the styleSheet diff --git a/src/gui/qgsmessagebaritem.h b/src/gui/qgsmessagebaritem.h index 55dc1858f0f..88cd73112be 100644 --- a/src/gui/qgsmessagebaritem.h +++ b/src/gui/qgsmessagebaritem.h @@ -86,9 +86,6 @@ class GUI_EXPORT QgsMessageBarItem : public QWidget //! returns the duration in second of the message int duration() const { return mDuration; } - //! returns the level - QgsMessageBar::MessageLevel level() { return mLevel; } - //! returns the styleSheet QString getStyleSheet() { return mStyleSheet; } From eb9f45cbb09ce77b03fe210eaf505fc9121ff628 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 21 Jul 2017 10:29:01 +0300 Subject: [PATCH 135/266] [processing] port Random points within extent --- .../algs/qgis/QGISAlgorithmProvider.py | 3 +- .../algs/qgis/RandomPointsExtent.py | 128 ++++++++++++------ 2 files changed, 85 insertions(+), 46 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index ab06ea33ab2..c3477ec6e39 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -84,6 +84,7 @@ from .PolygonsToLines import PolygonsToLines from .PostGISExecuteSQL import PostGISExecuteSQL from .RandomExtract import RandomExtract from .RandomExtractWithinSubsets import RandomExtractWithinSubsets +from .RandomPointsExtent import RandomPointsExtent from .RegularPoints import RegularPoints from .ReverseLineDirection import ReverseLineDirection from .Ruggedness import Ruggedness @@ -141,7 +142,6 @@ from .ZonalStatistics import ZonalStatistics # from .PointsDisplacement import PointsDisplacement # from .PointsFromPolygons import PointsFromPolygons # from .PointsFromLines import PointsFromLines -# from .RandomPointsExtent import RandomPointsExtent # from .RandomPointsLayer import RandomPointsLayer # from .RandomPointsPolygonsFixed import RandomPointsPolygonsFixed # from .RandomPointsPolygonsVariable import RandomPointsPolygonsVariable @@ -272,6 +272,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): PostGISExecuteSQL(), RandomExtract(), RandomExtractWithinSubsets(), + RandomPointsExtent(), RegularPoints(), ReverseLineDirection(), Ruggedness(), diff --git a/python/plugins/processing/algs/qgis/RandomPointsExtent.py b/python/plugins/processing/algs/qgis/RandomPointsExtent.py index ead6c09b5b4..d3e6fab03cc 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsExtent.py +++ b/python/plugins/processing/algs/qgis/RandomPointsExtent.py @@ -31,18 +31,26 @@ import random from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsGeometry, QgsFeatureSink, QgsRectangle, QgsFeature, QgsFields, QgsWkbTypes, - QgsField, QgsSpatialIndex, QgsPointXY, - QgsCoordinateReferenceSystem, - QgsMessageLog, - QgsProcessingUtils) +from qgis.core import (QgsField, + QgsFeatureSink, + QgsFeature, + QgsFields, + QgsGeometry, + QgsPoint, + QgsPointXY, + QgsWkbTypes, + QgsSpatialIndex, + QgsProcessing, + QgsProcessingException, + QgsProcessingParameterExtent, + QgsProcessingParameterNumber, + QgsProcessingParameterCrs, + QgsProcessingParameterBoolean, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterDefinition) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterExtent -from processing.core.parameters import ParameterNumber -from processing.core.parameters import ParameterCrs -from processing.core.outputs import OutputVector -from processing.tools import vector, dataobjects +from processing.tools import vector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -50,10 +58,12 @@ pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class RandomPointsExtent(QgisAlgorithm): EXTENT = 'EXTENT' - POINT_NUMBER = 'POINT_NUMBER' + POINTS_NUMBER = 'POINTS_NUMBER' MIN_DISTANCE = 'MIN_DISTANCE' + TARGET_CRS = 'TARGET_CRS' + ADD_Z = 'ADD_Z' + ADD_M = 'ADD_M' OUTPUT = 'OUTPUT' - CRS = 'CRS' def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'random_points.png')) @@ -65,15 +75,33 @@ class RandomPointsExtent(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterExtent(self.EXTENT, - self.tr('Input extent'), optional=False)) - self.addParameter(ParameterNumber(self.POINT_NUMBER, - self.tr('Points number'), 1, None, 1)) - self.addParameter(ParameterNumber(self.MIN_DISTANCE, - self.tr('Minimum distance'), 0.0, None, 0.0)) - self.addParameter(ParameterCrs(self.CRS, - self.tr('Output layer CRS'), 'ProjectCrs')) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Random points'), datatype=[dataobjects.TYPE_VECTOR_POINT])) + self.addParameter(QgsProcessingParameterExtent(self.EXTENT, self.tr('Input extent'))) + self.addParameter(QgsProcessingParameterNumber(self.POINTS_NUMBER, + self.tr('Number of points'), + QgsProcessingParameterNumber.Integer, + 1, False, 1, 1000000000)) + self.addParameter(QgsProcessingParameterNumber(self.MIN_DISTANCE, + self.tr('Minimum distance between points'), + QgsProcessingParameterNumber.Double, + 0, False, 0, 1000000000)) + self.addParameter(QgsProcessingParameterCrs(self.TARGET_CRS, + self.tr('Target CRS'), + 'ProjectCrs')) + + params = [] + params.append(QgsProcessingParameterBoolean(self.ADD_Z, + self.tr('Add Z coordinate'), + False)) + params.append(QgsProcessingParameterBoolean(self.ADD_M, + self.tr('Add M coordinate'), + False)) + for p in params: + p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) + self.addParameter(p) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('Random points'), + type=QgsProcessing.TypeVectorPoint)) def name(self): return 'randompointsinextent' @@ -82,24 +110,26 @@ class RandomPointsExtent(QgisAlgorithm): return self.tr('Random points in extent') def processAlgorithm(self, parameters, context, feedback): - pointCount = int(self.getParameterValue(self.POINT_NUMBER)) - minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) - extent = str(self.getParameterValue(self.EXTENT)).split(',') + bbox = self.parameterAsExtent(parameters, self.EXTENT, context) + pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) + minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) + crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context) + addZ = self.parameterAsBool(parameters, self.ADD_Z, context) + addM = self.parameterAsBool(parameters, self.ADD_M, context) - crsId = self.getParameterValue(self.CRS) - crs = QgsCoordinateReferenceSystem() - crs.createFromUserInput(crsId) - - xMin = float(extent[0]) - xMax = float(extent[1]) - yMin = float(extent[2]) - yMax = float(extent[3]) - extent = QgsGeometry().fromRect( - QgsRectangle(xMin, yMin, xMax, yMax)) + extent = QgsGeometry().fromRect(bbox) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, crs, context) + + wkbType = QgsWkbTypes.Point + if addZ: + wkbType = QgsWkbTypes.addZ(wkbType) + if addM: + wkbType = QgsWkbTypes.addM(wkbType) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, wkbType, crs) nPoints = 0 nIterations = 0 @@ -112,27 +142,35 @@ class RandomPointsExtent(QgisAlgorithm): random.seed() while nIterations < maxIterations and nPoints < pointCount: - rx = xMin + (xMax - xMin) * random.random() - ry = yMin + (yMax - yMin) * random.random() + if feedback.isCanceled(): + break - pnt = QgsPointXY(rx, ry) - geom = QgsGeometry.fromPoint(pnt) + rx = bbox.xMinimum() + bbox.width() * random.random() + ry = bbox.yMinimum() + bbox.height() * random.random() + + pnt = QgsPoint(rx, ry) + p = QgsPointXY(rx, ry) + if addZ: + pnt.addZValue(0.0) + if addM: + pnt.addMValue(0.0) + geom = QgsGeometry(pnt) if geom.within(extent) and \ - vector.checkMinDistance(pnt, index, minDistance, points): + vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) - writer.addFeature(f, QgsFeatureSink.FastInsert) + sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) - points[nPoints] = pnt + points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: - QgsMessageLog.logMessage(self.tr('Can not generate requested number of random points. ' - 'Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Could not generate requested number of random points. ' + 'Maximum number of attempts exceeded.')) - del writer + return {self.OUTPUT: dest_id} From ae2e32b36e24a6a0a02c1efccb072bfa579e644d Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 21 Jul 2017 11:15:31 +0300 Subject: [PATCH 136/266] [processing] restore Random points in layer bounds --- .../algs/qgis/QGISAlgorithmProvider.py | 3 +- .../processing/algs/qgis/RandomPointsLayer.py | 117 +++++++++++++----- 2 files changed, 86 insertions(+), 34 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index c3477ec6e39..e2cf153f283 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -85,6 +85,7 @@ from .PostGISExecuteSQL import PostGISExecuteSQL from .RandomExtract import RandomExtract from .RandomExtractWithinSubsets import RandomExtractWithinSubsets from .RandomPointsExtent import RandomPointsExtent +from .RandomPointsLayer import RandomPointsLayer from .RegularPoints import RegularPoints from .ReverseLineDirection import ReverseLineDirection from .Ruggedness import Ruggedness @@ -142,7 +143,6 @@ from .ZonalStatistics import ZonalStatistics # from .PointsDisplacement import PointsDisplacement # from .PointsFromPolygons import PointsFromPolygons # from .PointsFromLines import PointsFromLines -# from .RandomPointsLayer import RandomPointsLayer # from .RandomPointsPolygonsFixed import RandomPointsPolygonsFixed # from .RandomPointsPolygonsVariable import RandomPointsPolygonsVariable # from .RandomPointsAlongLines import RandomPointsAlongLines @@ -273,6 +273,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): RandomExtract(), RandomExtractWithinSubsets(), RandomPointsExtent(), + RandomPointsLayer(), RegularPoints(), ReverseLineDirection(), Ruggedness(), diff --git a/python/plugins/processing/algs/qgis/RandomPointsLayer.py b/python/plugins/processing/algs/qgis/RandomPointsLayer.py index 4f810d4ab15..e5daf4047b1 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsLayer.py +++ b/python/plugins/processing/algs/qgis/RandomPointsLayer.py @@ -30,25 +30,37 @@ import random from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsGeometry, QgsFeatureSink, QgsFields, QgsField, QgsSpatialIndex, QgsWkbTypes, - QgsPointXY, QgsFeature, QgsFeatureRequest, - QgsMessageLog, - QgsProcessingUtils) +from qgis.core import (QgsField, + QgsFeatureSink, + QgsFeature, + QgsFields, + QgsGeometry, + QgsPoint, + QgsPointXY, + QgsWkbTypes, + QgsSpatialIndex, + QgsFeatureRequest, + QgsProcessing, + QgsProcessingException, + QgsProcessingParameterNumber, + QgsProcessingParameterBoolean, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterDefinition) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects, vector +from processing.tools import vector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class RandomPointsLayer(QgisAlgorithm): - VECTOR = 'VECTOR' - POINT_NUMBER = 'POINT_NUMBER' + INPUT = 'INPUT' + POINTS_NUMBER = 'POINTS_NUMBER' MIN_DISTANCE = 'MIN_DISTANCE' + ADD_Z = 'ADD_Z' + ADD_M = 'ADD_M' OUTPUT = 'OUTPUT' def icon(self): @@ -61,13 +73,31 @@ class RandomPointsLayer(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.VECTOR, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON])) - self.addParameter(ParameterNumber(self.POINT_NUMBER, - self.tr('Points number'), 1, None, 1)) - self.addParameter(ParameterNumber(self.MIN_DISTANCE, - self.tr('Minimum distance'), 0.0, None, 0.0)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Random points'), datatype=[dataobjects.TYPE_VECTOR_POINT])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), + [QgsProcessing.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterNumber(self.POINTS_NUMBER, + self.tr('Number of points'), + QgsProcessingParameterNumber.Integer, + 1, False, 1, 1000000000)) + self.addParameter(QgsProcessingParameterNumber(self.MIN_DISTANCE, + self.tr('Minimum distance between points'), + QgsProcessingParameterNumber.Double, + 0, False, 0, 1000000000)) + params = [] + params.append(QgsProcessingParameterBoolean(self.ADD_Z, + self.tr('Add Z coordinate'), + False)) + params.append(QgsProcessingParameterBoolean(self.ADD_M, + self.tr('Add M coordinate'), + False)) + for p in params: + p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) + self.addParameter(p) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('Random points'), + type=QgsProcessing.TypeVectorPoint)) def name(self): return 'randompointsinlayerbounds' @@ -76,16 +106,26 @@ class RandomPointsLayer(QgisAlgorithm): return self.tr('Random points in layer bounds') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) - pointCount = int(self.getParameterValue(self.POINT_NUMBER)) - minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) + source = self.parameterAsSource(parameters, self.INPUT, context) + pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) + minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) + addZ = self.parameterAsBool(parameters, self.ADD_Z, context) + addM = self.parameterAsBool(parameters, self.ADD_M, context) - bbox = layer.extent() - idxLayer = QgsProcessingUtils.createSpatialIndex(layer, context) + bbox = source.sourceExtent() + sourceIndex = QgsSpatialIndex(source, feedback) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) + + wkbType = QgsWkbTypes.Point + if addZ: + wkbType = QgsWkbTypes.addZ(wkbType) + if addM: + wkbType = QgsWkbTypes.addM(wkbType) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, wkbType, source.sourceCrs()) nPoints = 0 nIterations = 0 @@ -98,16 +138,27 @@ class RandomPointsLayer(QgisAlgorithm): random.seed() while nIterations < maxIterations and nPoints < pointCount: + if feedback.isCanceled(): + break + rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() - pnt = QgsPointXY(rx, ry) - geom = QgsGeometry.fromPoint(pnt) - ids = idxLayer.intersects(geom.buffer(5, 5).boundingBox()) + pnt = QgsPoint(rx, ry) + p = QgsPointXY(rx, ry) + if addZ: + pnt.addZValue(0.0) + if addM: + pnt.addMValue(0.0) + geom = QgsGeometry(pnt) + ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ - vector.checkMinDistance(pnt, index, minDistance, points): + vector.checkMinDistance(p, index, minDistance, points): request = QgsFeatureRequest().setFilterFids(ids).setSubsetOfAttributes([]) - for f in layer.getFeatures(request): + for f in source.getFeatures(request): + if feedback.isCanceled(): + break + tmpGeom = f.geometry() if geom.within(tmpGeom): f = QgsFeature(nPoints) @@ -115,15 +166,15 @@ class RandomPointsLayer(QgisAlgorithm): f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) - writer.addFeature(f, QgsFeatureSink.FastInsert) + sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) - points[nPoints] = pnt + points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: - QgsMessageLog.logMessage(self.tr('Can not generate requested number of random points. ' - 'Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Could not generate requested number of random points. ' + 'Maximum number of attempts exceeded.')) - del writer + return {self.OUTPUT: dest_id} From f8b0c06942f6d7a39e61d6041c79233b56a13839 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 21 Jul 2017 14:29:42 +0300 Subject: [PATCH 137/266] [processing] port Random points in polygons --- .../algs/qgis/QGISAlgorithmProvider.py | 4 +- .../algs/qgis/RandomPointsPolygons.py | 220 ++++++++++++++++++ .../algs/qgis/RandomPointsPolygonsFixed.py | 150 ------------ .../algs/qgis/RandomPointsPolygonsVariable.py | 151 ------------ 4 files changed, 222 insertions(+), 303 deletions(-) create mode 100644 python/plugins/processing/algs/qgis/RandomPointsPolygons.py delete mode 100644 python/plugins/processing/algs/qgis/RandomPointsPolygonsFixed.py delete mode 100644 python/plugins/processing/algs/qgis/RandomPointsPolygonsVariable.py diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index e2cf153f283..6349729559e 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -86,6 +86,7 @@ from .RandomExtract import RandomExtract from .RandomExtractWithinSubsets import RandomExtractWithinSubsets from .RandomPointsExtent import RandomPointsExtent from .RandomPointsLayer import RandomPointsLayer +from .RandomPointsPolygons import RandomPointsPolygons from .RegularPoints import RegularPoints from .ReverseLineDirection import ReverseLineDirection from .Ruggedness import Ruggedness @@ -143,8 +144,6 @@ from .ZonalStatistics import ZonalStatistics # from .PointsDisplacement import PointsDisplacement # from .PointsFromPolygons import PointsFromPolygons # from .PointsFromLines import PointsFromLines -# from .RandomPointsPolygonsFixed import RandomPointsPolygonsFixed -# from .RandomPointsPolygonsVariable import RandomPointsPolygonsVariable # from .RandomPointsAlongLines import RandomPointsAlongLines # from .PointsToPaths import PointsToPaths # from .SetVectorStyle import SetVectorStyle @@ -274,6 +273,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): RandomExtractWithinSubsets(), RandomPointsExtent(), RandomPointsLayer(), + RandomPointsPolygons(), RegularPoints(), ReverseLineDirection(), Ruggedness(), diff --git a/python/plugins/processing/algs/qgis/RandomPointsPolygons.py b/python/plugins/processing/algs/qgis/RandomPointsPolygons.py new file mode 100644 index 00000000000..cc6956d12b9 --- /dev/null +++ b/python/plugins/processing/algs/qgis/RandomPointsPolygons.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + RandomPointsPolygons.py + --------------------- + Date : April 2014 + Copyright : (C) 2014 by Alexander Bruy + Email : alexander dot bruy 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. * +* * +*************************************************************************** +""" + +__author__ = 'Alexander Bruy' +__date__ = 'April 2014' +__copyright__ = '(C) 2014, Alexander Bruy' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + +import os +import random + +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtCore import QVariant +from qgis.core import (QgsField, + QgsFeatureSink, + QgsFeature, + QgsFields, + QgsGeometry, + QgsPoint, + QgsPointXY, + QgsWkbTypes, + QgsSpatialIndex, + QgsFeatureRequest, + QgsExpression, + QgsDistanceArea, + QgsProject, + QgsProcessing, + QgsProcessingException, + QgsProcessingParameterNumber, + QgsProcessingParameterBoolean, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterExpression, + QgsProcessingParameterEnum, + QgsProcessingParameterDefinition) + +from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm +from processing.tools import vector + +pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] + + +class RandomPointsPolygons(QgisAlgorithm): + + INPUT = 'INPUT' + EXPRESSION = 'EXPRESSION' + MIN_DISTANCE = 'MIN_DISTANCE' + STRATEGY = 'STRATEGY' + ADD_Z = 'ADD_Z' + ADD_M = 'ADD_M' + OUTPUT = 'OUTPUT' + + def icon(self): + return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'random_points.png')) + + def group(self): + return self.tr('Vector creation tools') + + def __init__(self): + super().__init__() + + def initAlgorithm(self, config=None): + self.strategies = [self.tr('Points count'), + self.tr('Points density')] + + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), + [QgsProcessing.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterEnum(self.STRATEGY, + self.tr('Sampling strategy'), + self.strategies, + False, + 0)) + self.addParameter(QgsProcessingParameterExpression(self.EXPRESSION, + self.tr('Expression'), + parentLayerParameterName=self.INPUT)) + self.addParameter(QgsProcessingParameterNumber(self.MIN_DISTANCE, + self.tr('Minimum distance between points'), + QgsProcessingParameterNumber.Double, + 0, False, 0, 1000000000)) + params = [] + params.append(QgsProcessingParameterBoolean(self.ADD_Z, + self.tr('Add Z coordinate'), + False)) + params.append(QgsProcessingParameterBoolean(self.ADD_M, + self.tr('Add M coordinate'), + False)) + for p in params: + p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) + self.addParameter(p) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('Random points'), + type=QgsProcessing.TypeVectorPoint)) + + def name(self): + return 'randompointsinsidepolygons' + + def displayName(self): + return self.tr('Random points inside polygons') + + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) + minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) + addZ = self.parameterAsBool(parameters, self.ADD_Z, context) + addM = self.parameterAsBool(parameters, self.ADD_M, context) + + expression = QgsExpression(self.parameterAsString(parameters, self.EXPRESSION, context)) + if expression.hasParserError(): + raise ProcessingException(expression.parserErrorString()) + + expressionContext = self.createExpressionContext(parameters, context) + if not expression.prepare(expressionContext): + raise ProcessingException( + self.tr('Evaluation error: {0}').format(expression.evalErrorString())) + + fields = QgsFields() + fields.append(QgsField('id', QVariant.Int, '', 10, 0)) + + wkbType = QgsWkbTypes.Point + if addZ: + wkbType = QgsWkbTypes.addZ(wkbType) + if addM: + wkbType = QgsWkbTypes.addM(wkbType) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, wkbType, source.sourceCrs()) + + da = QgsDistanceArea() + da.setSourceCrs(source.sourceCrs()) + da.setEllipsoid(QgsProject.instance().ellipsoid()) + + total = 100.0 / source.featureCount() if source.featureCount() else 0 + for current, f in enumerate(source.getFeatures()): + if feedback.isCanceled(): + break + + expressionContext.setFeature(f) + value = expression.evaluate(expressionContext) + if expression.hasEvalError(): + feedback.pushInfo( + self.tr('Evaluation error for feature ID {}: {}').format(f.id(), expression.evalErrorString())) + continue + + fGeom = f.geometry() + bbox = fGeom.boundingBox() + if strategy == 0: + pointCount = int(value) + else: + pointCount = int(round(value * da.measureArea(fGeom))) + + if pointCount == 0: + feedback.pushInfo("Skip feature {} as number of points for it is 0.") + continue + + index = QgsSpatialIndex() + points = dict() + + nPoints = 0 + nIterations = 0 + maxIterations = pointCount * 200 + total = 100.0 / pointCount if pointCount else 1 + + random.seed() + + while nIterations < maxIterations and nPoints < pointCount: + if feedback.isCanceled(): + break + + rx = bbox.xMinimum() + bbox.width() * random.random() + ry = bbox.yMinimum() + bbox.height() * random.random() + + pnt = QgsPoint(rx, ry) + p = QgsPointXY(rx, ry) + if addZ: + pnt.addZValue(0.0) + if addM: + pnt.addMValue(0.0) + geom = QgsGeometry(pnt) + if geom.within(fGeom) and \ + vector.checkMinDistance(p, index, minDistance, points): + f = QgsFeature(nPoints) + f.initAttributes(1) + f.setFields(fields) + f.setAttribute('id', nPoints) + f.setGeometry(geom) + sink.addFeature(f, QgsFeatureSink.FastInsert) + index.insertFeature(f) + points[nPoints] = p + nPoints += 1 + feedback.setProgress(int(nPoints * total)) + nIterations += 1 + + if nPoints < pointCount: + feedback.pushInfo(self.tr('Could not generate requested number of random ' + 'points. Maximum number of attempts exceeded.')) + + feedback.setProgress(0) + + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/RandomPointsPolygonsFixed.py b/python/plugins/processing/algs/qgis/RandomPointsPolygonsFixed.py deleted file mode 100644 index b9bd4234c49..00000000000 --- a/python/plugins/processing/algs/qgis/RandomPointsPolygonsFixed.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - RandomPointsPolygonsFixed.py - --------------------- - Date : April 2014 - Copyright : (C) 2014 by Alexander Bruy - Email : alexander dot bruy 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. * -* * -*************************************************************************** -""" - -__author__ = 'Alexander Bruy' -__date__ = 'April 2014' -__copyright__ = '(C) 2014, Alexander Bruy' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -import os -import random - -from qgis.PyQt.QtGui import QIcon -from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsFields, QgsFeatureSink, QgsField, QgsDistanceArea, QgsGeometry, QgsWkbTypes, - QgsSpatialIndex, QgsPointXY, QgsFeature, - QgsMessageLog, - QgsProcessingUtils, - QgsProject) - -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.parameters import ParameterSelection -from processing.core.outputs import OutputVector -from processing.tools import dataobjects, vector - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class RandomPointsPolygonsFixed(QgisAlgorithm): - - VECTOR = 'VECTOR' - VALUE = 'VALUE' - MIN_DISTANCE = 'MIN_DISTANCE' - STRATEGY = 'STRATEGY' - OUTPUT = 'OUTPUT' - - def icon(self): - return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'random_points.png')) - - def group(self): - return self.tr('Vector creation tools') - - def __init__(self): - super().__init__() - - def initAlgorithm(self, config=None): - self.strategies = [self.tr('Points count'), - self.tr('Points density')] - - self.addParameter(ParameterVector(self.VECTOR, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON])) - self.addParameter(ParameterSelection(self.STRATEGY, - self.tr('Sampling strategy'), self.strategies, 0)) - self.addParameter(ParameterNumber(self.VALUE, - self.tr('Number or density of points'), 0.0001, None, 1.0)) - self.addParameter(ParameterNumber(self.MIN_DISTANCE, - self.tr('Minimum distance'), 0.0, None, 0.0)) - - self.addOutput(OutputVector(self.OUTPUT, self.tr('Random points'), datatype=[dataobjects.TYPE_VECTOR_POINT])) - - def name(self): - return 'randompointsinsidepolygonsfixed' - - def displayName(self): - return self.tr('Random points inside polygons (fixed)') - - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) - value = float(self.getParameterValue(self.VALUE)) - minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) - strategy = self.getParameterValue(self.STRATEGY) - - fields = QgsFields() - fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) - - da = QgsDistanceArea() - da.setSourceCrs(layer.sourceCrs()) - da.setEllipsoid(QgsProject.instance().ellipsoid()) - - features = QgsProcessingUtils.getFeatures(layer, context) - for current, f in enumerate(features): - fGeom = f.geometry() - bbox = fGeom.boundingBox() - if strategy == 0: - pointCount = int(value) - else: - pointCount = int(round(value * da.measureArea(fGeom))) - - if pointCount == 0: - feedback.pushInfo("Skip feature {} as number of points for it is 0.") - continue - - index = QgsSpatialIndex() - points = dict() - - nPoints = 0 - nIterations = 0 - maxIterations = pointCount * 200 - total = 100.0 / pointCount if pointCount else 1 - - random.seed() - - while nIterations < maxIterations and nPoints < pointCount: - rx = bbox.xMinimum() + bbox.width() * random.random() - ry = bbox.yMinimum() + bbox.height() * random.random() - - pnt = QgsPointXY(rx, ry) - geom = QgsGeometry.fromPoint(pnt) - if geom.within(fGeom) and \ - vector.checkMinDistance(pnt, index, minDistance, points): - f = QgsFeature(nPoints) - f.initAttributes(1) - f.setFields(fields) - f.setAttribute('id', nPoints) - f.setGeometry(geom) - writer.addFeature(f, QgsFeatureSink.FastInsert) - index.insertFeature(f) - points[nPoints] = pnt - nPoints += 1 - feedback.setProgress(int(nPoints * total)) - nIterations += 1 - - if nPoints < pointCount: - QgsMessageLog.logMessage(self.tr('Can not generate requested number of random ' - 'points. Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) - - feedback.setProgress(0) - - del writer diff --git a/python/plugins/processing/algs/qgis/RandomPointsPolygonsVariable.py b/python/plugins/processing/algs/qgis/RandomPointsPolygonsVariable.py deleted file mode 100644 index 648bda67d77..00000000000 --- a/python/plugins/processing/algs/qgis/RandomPointsPolygonsVariable.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - RandomPointsPolygonsVariable.py - --------------------- - Date : April 2014 - Copyright : (C) 2014 by Alexander Bruy - Email : alexander dot bruy 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. * -* * -*************************************************************************** -""" - -__author__ = 'Alexander Bruy' -__date__ = 'April 2014' -__copyright__ = '(C) 2014, Alexander Bruy' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -import os -import random - -from qgis.PyQt.QtGui import QIcon -from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsFields, QgsFeatureSink, QgsField, QgsFeature, QgsPointXY, QgsWkbTypes, - QgsGeometry, QgsSpatialIndex, QgsDistanceArea, - QgsMessageLog, - QgsProject, - QgsProcessingUtils) - -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterTableField -from processing.core.parameters import ParameterNumber -from processing.core.parameters import ParameterSelection -from processing.core.outputs import OutputVector -from processing.tools import dataobjects, vector - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class RandomPointsPolygonsVariable(QgisAlgorithm): - - VECTOR = 'VECTOR' - FIELD = 'FIELD' - MIN_DISTANCE = 'MIN_DISTANCE' - STRATEGY = 'STRATEGY' - OUTPUT = 'OUTPUT' - - def icon(self): - return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'random_points.png')) - - def group(self): - return self.tr('Vector creation tools') - - def __init__(self): - super().__init__() - - def initAlgorithm(self, config=None): - self.strategies = [self.tr('Points count'), - self.tr('Points density')] - - self.addParameter(ParameterVector(self.VECTOR, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON])) - self.addParameter(ParameterSelection(self.STRATEGY, - self.tr('Sampling strategy'), self.strategies, 0)) - self.addParameter(ParameterTableField(self.FIELD, - self.tr('Number field'), - self.VECTOR, ParameterTableField.DATA_TYPE_NUMBER)) - self.addParameter(ParameterNumber(self.MIN_DISTANCE, - self.tr('Minimum distance'), 0.0, None, 0.0)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Random points'), datatype=[dataobjects.TYPE_VECTOR_POINT])) - - def name(self): - return 'randompointsinsidepolygonsvariable' - - def displayName(self): - return self.tr('Random points inside polygons (variable)') - - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) - fieldName = self.getParameterValue(self.FIELD) - minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) - strategy = self.getParameterValue(self.STRATEGY) - - fields = QgsFields() - fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) - - da = QgsDistanceArea() - da.setSourceCrs(layer.sourceCrs()) - da.setEllipsoid(QgsProject.instance().ellipsoid()) - - features = QgsProcessingUtils.getFeatures(layer, context) - for current, f in enumerate(features): - fGeom = f.geometry() - bbox = fGeom.boundingBox() - if strategy == 0: - pointCount = int(f[fieldName]) - else: - pointCount = int(round(f[fieldName] * da.measureArea(fGeom))) - - if pointCount == 0: - feedback.pushInfo("Skip feature {} as number of points for it is 0.") - continue - - index = QgsSpatialIndex() - points = dict() - - nPoints = 0 - nIterations = 0 - maxIterations = pointCount * 200 - total = 100.0 / pointCount if pointCount else 1 - - random.seed() - - while nIterations < maxIterations and nPoints < pointCount: - rx = bbox.xMinimum() + bbox.width() * random.random() - ry = bbox.yMinimum() + bbox.height() * random.random() - - pnt = QgsPointXY(rx, ry) - geom = QgsGeometry.fromPoint(pnt) - if geom.within(fGeom) and \ - vector.checkMinDistance(pnt, index, minDistance, points): - f = QgsFeature(nPoints) - f.initAttributes(1) - f.setFields(fields) - f.setAttribute('id', nPoints) - f.setGeometry(geom) - writer.addFeature(f, QgsFeatureSink.FastInsert) - index.insertFeature(f) - points[nPoints] = pnt - nPoints += 1 - feedback.setProgress(int(nPoints * total)) - nIterations += 1 - - if nPoints < pointCount: - QgsMessageLog.logMessage(self.tr('Can not generate requested number of random ' - 'points. Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) - - feedback.setProgress(0) - - del writer From c7645a3884a3e9b98dd0669fd324c18578d7490a Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 21 Jul 2017 15:22:37 +0300 Subject: [PATCH 138/266] [processing] port Random points along lines --- .../algs/qgis/QGISAlgorithmProvider.py | 3 +- .../algs/qgis/RandomPointsAlongLines.py | 116 ++++++++++++------ 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 6349729559e..cf9f871a1bc 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -84,6 +84,7 @@ from .PolygonsToLines import PolygonsToLines from .PostGISExecuteSQL import PostGISExecuteSQL from .RandomExtract import RandomExtract from .RandomExtractWithinSubsets import RandomExtractWithinSubsets +from .RandomPointsAlongLines import RandomPointsAlongLines from .RandomPointsExtent import RandomPointsExtent from .RandomPointsLayer import RandomPointsLayer from .RandomPointsPolygons import RandomPointsPolygons @@ -144,7 +145,6 @@ from .ZonalStatistics import ZonalStatistics # from .PointsDisplacement import PointsDisplacement # from .PointsFromPolygons import PointsFromPolygons # from .PointsFromLines import PointsFromLines -# from .RandomPointsAlongLines import RandomPointsAlongLines # from .PointsToPaths import PointsToPaths # from .SetVectorStyle import SetVectorStyle # from .SetRasterStyle import SetRasterStyle @@ -271,6 +271,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): PostGISExecuteSQL(), RandomExtract(), RandomExtractWithinSubsets(), + RandomPointsAlongLines(), RandomPointsExtent(), RandomPointsLayer(), RandomPointsPolygons(), diff --git a/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py b/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py index 638b90ca461..fd56b8de702 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py +++ b/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py @@ -29,33 +29,37 @@ __revision__ = '$Format:%H$' import random from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsApplication, +from qgis.core import (QgsField, QgsFeatureSink, - QgsFields, - QgsField, - QgsGeometry, - QgsSpatialIndex, - QgsWkbTypes, - QgsDistanceArea, - QgsFeatureRequest, QgsFeature, + QgsFields, + QgsGeometry, + QgsPoint, QgsPointXY, - QgsMessageLog, + QgsWkbTypes, + QgsSpatialIndex, + QgsFeatureRequest, + QgsDistanceArea, QgsProject, - QgsProcessingUtils) + QgsProcessing, + QgsProcessingException, + QgsProcessingParameterNumber, + QgsProcessingParameterBoolean, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterDefinition) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects, vector +from processing.tools import vector class RandomPointsAlongLines(QgisAlgorithm): - VECTOR = 'VECTOR' - POINT_NUMBER = 'POINT_NUMBER' + INPUT = 'INPUT' + POINTS_NUMBER = 'POINTS_NUMBER' MIN_DISTANCE = 'MIN_DISTANCE' + ADD_Z = 'ADD_Z' + ADD_M = 'ADD_M' OUTPUT = 'OUTPUT' def group(self): @@ -65,13 +69,31 @@ class RandomPointsAlongLines(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.VECTOR, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterNumber(self.POINT_NUMBER, - self.tr('Number of points'), 1, None, 1)) - self.addParameter(ParameterNumber(self.MIN_DISTANCE, - self.tr('Minimum distance'), 0.0, None, 0.0)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Random points'), datatype=[dataobjects.TYPE_VECTOR_POINT])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), + [QgsProcessing.TypeVectorLine])) + self.addParameter(QgsProcessingParameterNumber(self.POINTS_NUMBER, + self.tr('Number of points'), + QgsProcessingParameterNumber.Integer, + 1, False, 1, 1000000000)) + self.addParameter(QgsProcessingParameterNumber(self.MIN_DISTANCE, + self.tr('Minimum distance between points'), + QgsProcessingParameterNumber.Double, + 0, False, 0, 1000000000)) + params = [] + params.append(QgsProcessingParameterBoolean(self.ADD_Z, + self.tr('Add Z coordinate'), + False)) + params.append(QgsProcessingParameterBoolean(self.ADD_M, + self.tr('Add M coordinate'), + False)) + for p in params: + p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) + self.addParameter(p) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('Random points'), + type=QgsProcessing.TypeVectorPoint)) def name(self): return 'randompointsalongline' @@ -80,25 +102,35 @@ class RandomPointsAlongLines(QgisAlgorithm): return self.tr('Random points along line') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) - pointCount = float(self.getParameterValue(self.POINT_NUMBER)) - minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) + source = self.parameterAsSource(parameters, self.INPUT, context) + pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) + minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) + addZ = self.parameterAsBool(parameters, self.ADD_Z, context) + addM = self.parameterAsBool(parameters, self.ADD_M, context) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) + + wkbType = QgsWkbTypes.Point + if addZ: + wkbType = QgsWkbTypes.addZ(wkbType) + if addM: + wkbType = QgsWkbTypes.addM(wkbType) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, wkbType, source.sourceCrs()) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 - featureCount = layer.featureCount() + featureCount = source.featureCount() total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = dict() da = QgsDistanceArea() - da.setSourceCrs(layer.sourceCrs()) + da.setSourceCrs(source.sourceCrs()) da.setEllipsoid(QgsProject.instance().ellipsoid()) request = QgsFeatureRequest() @@ -106,9 +138,12 @@ class RandomPointsAlongLines(QgisAlgorithm): random.seed() while nIterations < maxIterations and nPoints < pointCount: + if feedback.isCanceled(): + break + # pick random feature fid = random.randint(0, featureCount - 1) - f = next(layer.getFeatures(request.setFilterFid(fid).setSubsetOfAttributes([]))) + f = next(source.getFeatures(request.setFilterFid(fid).setSubsetOfAttributes([]))) fGeom = f.geometry() if fGeom.isMultipart(): @@ -135,23 +170,28 @@ class RandomPointsAlongLines(QgisAlgorithm): ry = (startPoint.y() + d * endPoint.y()) / (1 + d) # generate random point - pnt = QgsPointXY(rx, ry) - geom = QgsGeometry.fromPoint(pnt) - if vector.checkMinDistance(pnt, index, minDistance, points): + pnt = QgsPoint(rx, ry) + p = QgsPointXY(rx, ry) + if addZ: + pnt.addZValue(0.0) + if addM: + pnt.addMValue(0.0) + geom = QgsGeometry(pnt) + if vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) - writer.addFeature(f, QgsFeatureSink.FastInsert) + sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) - points[nPoints] = pnt + points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: - QgsMessageLog.logMessage(self.tr('Can not generate requested number of random points. ' - 'Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) + feedback.pushInfo(self.tr('Could not generate requested number of random points. ' + 'Maximum number of attempts exceeded.')) - del writer + return {self.OUTPUT: dest_id} From c440ade2d83336fbba24ed1efc043ae4e75931c7 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Mon, 24 Jul 2017 09:20:15 +0300 Subject: [PATCH 139/266] [processing] remove Z/M addition from "random" algorithms --- .../algs/qgis/RandomPointsAlongLines.py | 32 ++---------------- .../algs/qgis/RandomPointsExtent.py | 33 ++----------------- .../processing/algs/qgis/RandomPointsLayer.py | 32 ++---------------- .../algs/qgis/RandomPointsPolygons.py | 32 ++---------------- 4 files changed, 8 insertions(+), 121 deletions(-) diff --git a/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py b/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py index fd56b8de702..4daca14296e 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py +++ b/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py @@ -34,7 +34,6 @@ from qgis.core import (QgsField, QgsFeature, QgsFields, QgsGeometry, - QgsPoint, QgsPointXY, QgsWkbTypes, QgsSpatialIndex, @@ -44,7 +43,6 @@ from qgis.core import (QgsField, QgsProcessing, QgsProcessingException, QgsProcessingParameterNumber, - QgsProcessingParameterBoolean, QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSink, QgsProcessingParameterDefinition) @@ -58,8 +56,6 @@ class RandomPointsAlongLines(QgisAlgorithm): INPUT = 'INPUT' POINTS_NUMBER = 'POINTS_NUMBER' MIN_DISTANCE = 'MIN_DISTANCE' - ADD_Z = 'ADD_Z' - ADD_M = 'ADD_M' OUTPUT = 'OUTPUT' def group(self): @@ -80,17 +76,6 @@ class RandomPointsAlongLines(QgisAlgorithm): self.tr('Minimum distance between points'), QgsProcessingParameterNumber.Double, 0, False, 0, 1000000000)) - params = [] - params.append(QgsProcessingParameterBoolean(self.ADD_Z, - self.tr('Add Z coordinate'), - False)) - params.append(QgsProcessingParameterBoolean(self.ADD_M, - self.tr('Add M coordinate'), - False)) - for p in params: - p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) - self.addParameter(p) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Random points'), type=QgsProcessing.TypeVectorPoint)) @@ -105,20 +90,12 @@ class RandomPointsAlongLines(QgisAlgorithm): source = self.parameterAsSource(parameters, self.INPUT, context) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) - addZ = self.parameterAsBool(parameters, self.ADD_Z, context) - addM = self.parameterAsBool(parameters, self.ADD_M, context) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - wkbType = QgsWkbTypes.Point - if addZ: - wkbType = QgsWkbTypes.addZ(wkbType) - if addM: - wkbType = QgsWkbTypes.addM(wkbType) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, wkbType, source.sourceCrs()) + fields, QgsWkbTypes.Point, source.sourceCrs()) nPoints = 0 nIterations = 0 @@ -170,13 +147,8 @@ class RandomPointsAlongLines(QgisAlgorithm): ry = (startPoint.y() + d * endPoint.y()) / (1 + d) # generate random point - pnt = QgsPoint(rx, ry) p = QgsPointXY(rx, ry) - if addZ: - pnt.addZValue(0.0) - if addM: - pnt.addMValue(0.0) - geom = QgsGeometry(pnt) + geom = QgsGeometry.fromPoint(p) if vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) diff --git a/python/plugins/processing/algs/qgis/RandomPointsExtent.py b/python/plugins/processing/algs/qgis/RandomPointsExtent.py index d3e6fab03cc..6f26bc5b881 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsExtent.py +++ b/python/plugins/processing/algs/qgis/RandomPointsExtent.py @@ -36,7 +36,6 @@ from qgis.core import (QgsField, QgsFeature, QgsFields, QgsGeometry, - QgsPoint, QgsPointXY, QgsWkbTypes, QgsSpatialIndex, @@ -45,7 +44,6 @@ from qgis.core import (QgsField, QgsProcessingParameterExtent, QgsProcessingParameterNumber, QgsProcessingParameterCrs, - QgsProcessingParameterBoolean, QgsProcessingParameterFeatureSink, QgsProcessingParameterDefinition) @@ -61,8 +59,6 @@ class RandomPointsExtent(QgisAlgorithm): POINTS_NUMBER = 'POINTS_NUMBER' MIN_DISTANCE = 'MIN_DISTANCE' TARGET_CRS = 'TARGET_CRS' - ADD_Z = 'ADD_Z' - ADD_M = 'ADD_M' OUTPUT = 'OUTPUT' def icon(self): @@ -87,18 +83,6 @@ class RandomPointsExtent(QgisAlgorithm): self.addParameter(QgsProcessingParameterCrs(self.TARGET_CRS, self.tr('Target CRS'), 'ProjectCrs')) - - params = [] - params.append(QgsProcessingParameterBoolean(self.ADD_Z, - self.tr('Add Z coordinate'), - False)) - params.append(QgsProcessingParameterBoolean(self.ADD_M, - self.tr('Add M coordinate'), - False)) - for p in params: - p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) - self.addParameter(p) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Random points'), type=QgsProcessing.TypeVectorPoint)) @@ -114,22 +98,14 @@ class RandomPointsExtent(QgisAlgorithm): pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context) - addZ = self.parameterAsBool(parameters, self.ADD_Z, context) - addM = self.parameterAsBool(parameters, self.ADD_M, context) extent = QgsGeometry().fromRect(bbox) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - wkbType = QgsWkbTypes.Point - if addZ: - wkbType = QgsWkbTypes.addZ(wkbType) - if addM: - wkbType = QgsWkbTypes.addM(wkbType) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, wkbType, crs) + fields, QgsWkbTypes.Point, crs) nPoints = 0 nIterations = 0 @@ -148,13 +124,8 @@ class RandomPointsExtent(QgisAlgorithm): rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() - pnt = QgsPoint(rx, ry) p = QgsPointXY(rx, ry) - if addZ: - pnt.addZValue(0.0) - if addM: - pnt.addMValue(0.0) - geom = QgsGeometry(pnt) + geom = QgsGeometry.fromPoint(p) if geom.within(extent) and \ vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) diff --git a/python/plugins/processing/algs/qgis/RandomPointsLayer.py b/python/plugins/processing/algs/qgis/RandomPointsLayer.py index e5daf4047b1..a16fc8235b7 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsLayer.py +++ b/python/plugins/processing/algs/qgis/RandomPointsLayer.py @@ -35,7 +35,6 @@ from qgis.core import (QgsField, QgsFeature, QgsFields, QgsGeometry, - QgsPoint, QgsPointXY, QgsWkbTypes, QgsSpatialIndex, @@ -43,7 +42,6 @@ from qgis.core import (QgsField, QgsProcessing, QgsProcessingException, QgsProcessingParameterNumber, - QgsProcessingParameterBoolean, QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSink, QgsProcessingParameterDefinition) @@ -59,8 +57,6 @@ class RandomPointsLayer(QgisAlgorithm): INPUT = 'INPUT' POINTS_NUMBER = 'POINTS_NUMBER' MIN_DISTANCE = 'MIN_DISTANCE' - ADD_Z = 'ADD_Z' - ADD_M = 'ADD_M' OUTPUT = 'OUTPUT' def icon(self): @@ -84,17 +80,6 @@ class RandomPointsLayer(QgisAlgorithm): self.tr('Minimum distance between points'), QgsProcessingParameterNumber.Double, 0, False, 0, 1000000000)) - params = [] - params.append(QgsProcessingParameterBoolean(self.ADD_Z, - self.tr('Add Z coordinate'), - False)) - params.append(QgsProcessingParameterBoolean(self.ADD_M, - self.tr('Add M coordinate'), - False)) - for p in params: - p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) - self.addParameter(p) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Random points'), type=QgsProcessing.TypeVectorPoint)) @@ -109,8 +94,6 @@ class RandomPointsLayer(QgisAlgorithm): source = self.parameterAsSource(parameters, self.INPUT, context) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) - addZ = self.parameterAsBool(parameters, self.ADD_Z, context) - addM = self.parameterAsBool(parameters, self.ADD_M, context) bbox = source.sourceExtent() sourceIndex = QgsSpatialIndex(source, feedback) @@ -118,14 +101,8 @@ class RandomPointsLayer(QgisAlgorithm): fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - wkbType = QgsWkbTypes.Point - if addZ: - wkbType = QgsWkbTypes.addZ(wkbType) - if addM: - wkbType = QgsWkbTypes.addM(wkbType) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, wkbType, source.sourceCrs()) + fields, QgsWkbTypes.Point, source.sourceCrs()) nPoints = 0 nIterations = 0 @@ -144,13 +121,8 @@ class RandomPointsLayer(QgisAlgorithm): rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() - pnt = QgsPoint(rx, ry) p = QgsPointXY(rx, ry) - if addZ: - pnt.addZValue(0.0) - if addM: - pnt.addMValue(0.0) - geom = QgsGeometry(pnt) + geom = QgsGeometry.fromPoint(p) ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ vector.checkMinDistance(p, index, minDistance, points): diff --git a/python/plugins/processing/algs/qgis/RandomPointsPolygons.py b/python/plugins/processing/algs/qgis/RandomPointsPolygons.py index cc6956d12b9..6e1e7f15e64 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsPolygons.py +++ b/python/plugins/processing/algs/qgis/RandomPointsPolygons.py @@ -35,7 +35,6 @@ from qgis.core import (QgsField, QgsFeature, QgsFields, QgsGeometry, - QgsPoint, QgsPointXY, QgsWkbTypes, QgsSpatialIndex, @@ -46,7 +45,6 @@ from qgis.core import (QgsField, QgsProcessing, QgsProcessingException, QgsProcessingParameterNumber, - QgsProcessingParameterBoolean, QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSink, QgsProcessingParameterExpression, @@ -65,8 +63,6 @@ class RandomPointsPolygons(QgisAlgorithm): EXPRESSION = 'EXPRESSION' MIN_DISTANCE = 'MIN_DISTANCE' STRATEGY = 'STRATEGY' - ADD_Z = 'ADD_Z' - ADD_M = 'ADD_M' OUTPUT = 'OUTPUT' def icon(self): @@ -97,17 +93,6 @@ class RandomPointsPolygons(QgisAlgorithm): self.tr('Minimum distance between points'), QgsProcessingParameterNumber.Double, 0, False, 0, 1000000000)) - params = [] - params.append(QgsProcessingParameterBoolean(self.ADD_Z, - self.tr('Add Z coordinate'), - False)) - params.append(QgsProcessingParameterBoolean(self.ADD_M, - self.tr('Add M coordinate'), - False)) - for p in params: - p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced) - self.addParameter(p) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Random points'), type=QgsProcessing.TypeVectorPoint)) @@ -122,8 +107,6 @@ class RandomPointsPolygons(QgisAlgorithm): source = self.parameterAsSource(parameters, self.INPUT, context) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) - addZ = self.parameterAsBool(parameters, self.ADD_Z, context) - addM = self.parameterAsBool(parameters, self.ADD_M, context) expression = QgsExpression(self.parameterAsString(parameters, self.EXPRESSION, context)) if expression.hasParserError(): @@ -137,14 +120,8 @@ class RandomPointsPolygons(QgisAlgorithm): fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - wkbType = QgsWkbTypes.Point - if addZ: - wkbType = QgsWkbTypes.addZ(wkbType) - if addM: - wkbType = QgsWkbTypes.addM(wkbType) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, wkbType, source.sourceCrs()) + fields, QgsWkbTypes.Point, source.sourceCrs()) da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs()) @@ -190,13 +167,8 @@ class RandomPointsPolygons(QgisAlgorithm): rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() - pnt = QgsPoint(rx, ry) p = QgsPointXY(rx, ry) - if addZ: - pnt.addZValue(0.0) - if addM: - pnt.addMValue(0.0) - geom = QgsGeometry(pnt) + geom = QgsGeometry.fromPoint(p) if geom.within(fGeom) and \ vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) From 78b05c1a7fece0241701d0b4854f8c29af195438 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Jul 2017 14:52:22 +1000 Subject: [PATCH 140/266] Allow copying and pasting symbols between QgsSymbolButtons --- python/core/symbology-ng/qgssymbol.sip | 2 +- python/core/symbology-ng/qgssymbollayer.sip | 2 + .../core/symbology-ng/qgssymbollayerutils.sip | 19 +++++++ python/gui/qgssymbolbutton.sip | 28 ++++++++++ src/app/composer/qgscomposershapewidget.cpp | 2 + src/app/qgsannotationwidget.cpp | 3 ++ src/app/qgsdecorationgriddialog.cpp | 4 ++ src/app/qgsdecorationlayoutextentdialog.cpp | 2 + src/core/symbology-ng/qgssymbol.h | 2 +- src/core/symbology-ng/qgssymbollayer.h | 4 ++ src/core/symbology-ng/qgssymbollayerutils.cpp | 42 +++++++++++++++ src/core/symbology-ng/qgssymbollayerutils.h | 17 ++++++ .../qgsfieldconditionalformatwidget.cpp | 1 + src/gui/qgssymbolbutton.cpp | 53 +++++++++++++++++++ src/gui/qgssymbolbutton.h | 28 ++++++++++ .../qgspointclusterrendererwidget.cpp | 2 + .../qgspointdisplacementrendererwidget.cpp | 1 + tests/src/python/test_qgssymbolbutton.py | 46 +++++++++++++++- tests/src/python/test_qgssymbollayerutils.py | 16 +++++- 19 files changed, 270 insertions(+), 4 deletions(-) diff --git a/python/core/symbology-ng/qgssymbol.sip b/python/core/symbology-ng/qgssymbol.sip index 9d0ea813837..9399b01b1d2 100644 --- a/python/core/symbology-ng/qgssymbol.sip +++ b/python/core/symbology-ng/qgssymbol.sip @@ -88,7 +88,7 @@ return new default symbol for specified geometry type :rtype: QgsSymbolLayer %End - int symbolLayerCount(); + int symbolLayerCount() const; %Docstring Returns total number of symbol layers contained in the symbol. :return: count of symbol layers diff --git a/python/core/symbology-ng/qgssymbollayer.sip b/python/core/symbology-ng/qgssymbollayer.sip index 767651d5d1d..11548c8ec5e 100644 --- a/python/core/symbology-ng/qgssymbollayer.sip +++ b/python/core/symbology-ng/qgssymbollayer.sip @@ -231,8 +231,10 @@ class QgsSymbolLayer virtual QgsSymbol *subSymbol(); %Docstring + Returns the symbol's sub symbol, if present. :rtype: QgsSymbol %End + virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ ); %Docstring set layer's subsymbol. takes ownership of the passed symbol diff --git a/python/core/symbology-ng/qgssymbollayerutils.sip b/python/core/symbology-ng/qgssymbollayerutils.sip index 97701625a1f..0a0d68a29f8 100644 --- a/python/core/symbology-ng/qgssymbollayerutils.sip +++ b/python/core/symbology-ng/qgssymbollayerutils.sip @@ -548,6 +548,25 @@ Writes a collection of symbols to XML with specified tagName for the top-level e static void clearSymbolMap( QgsSymbolMap &symbols ); + static QMimeData *symbolToMimeData( QgsSymbol *symbol ) /Factory/; +%Docstring + Creates new mime data from a ``symbol``. + This also sets the mime color data to match the symbol's color, so that copied symbols + can be paste in places where a color is expected. +.. seealso:: symbolFromMimeData() +.. versionadded:: 3.0 + :rtype: QMimeData +%End + + static QgsSymbol *symbolFromMimeData( const QMimeData *data ) /Factory/; +%Docstring + Attempts to parse ``mime`` data as a symbol. A new symbol instance will be returned + if the data was successfully converted to a symbol. +.. seealso:: symbolToMimeData() +.. versionadded:: 3.0 + :rtype: QgsSymbol +%End + static QgsColorRamp *loadColorRamp( QDomElement &element ) /Factory/; %Docstring Creates a color ramp from the settings encoded in an XML element diff --git a/python/gui/qgssymbolbutton.sip b/python/gui/qgssymbolbutton.sip index af8b1e4f9e1..5f2a04258ed 100644 --- a/python/gui/qgssymbolbutton.sip +++ b/python/gui/qgssymbolbutton.sip @@ -32,6 +32,21 @@ class QgsSymbolButton : QToolButton virtual QSize minimumSizeHint() const; + void setSymbolType( QgsSymbol::SymbolType type ); +%Docstring + Sets the symbol ``type`` which the button requires. + If the type differs from the current symbol type, the symbol will be reset + to a default symbol style of the new type. +.. seealso:: symbolType() +%End + + QgsSymbol::SymbolType symbolType() const; +%Docstring + Returns the symbol type which the button requires. +.. seealso:: setSymbolType() + :rtype: QgsSymbol.SymbolType +%End + void setDialogTitle( const QString &title ); %Docstring Sets the ``title`` for the symbol settings dialog window. @@ -105,6 +120,19 @@ class QgsSymbolButton : QToolButton to the previous symbol color. %End + void copySymbol(); +%Docstring + Copies the current symbol to the clipboard. +.. seealso:: pasteSymbol() +%End + + void pasteSymbol(); +%Docstring + Pastes a symbol from the clipboard. If clipboard does not contain a valid + symbol then no change is applied. +.. seealso:: copySymbol() +%End + void copyColor(); %Docstring Copies the current symbol color to the clipboard. diff --git a/src/app/composer/qgscomposershapewidget.cpp b/src/app/composer/qgscomposershapewidget.cpp index 355c48b3c48..ec928db0ef3 100644 --- a/src/app/composer/qgscomposershapewidget.cpp +++ b/src/app/composer/qgscomposershapewidget.cpp @@ -45,6 +45,8 @@ QgsComposerShapeWidget::QgsComposerShapeWidget( QgsComposerShape *composerShape mShapeComboBox->addItem( tr( "Rectangle" ) ); mShapeComboBox->addItem( tr( "Triangle" ) ); + mShapeStyleButton->setSymbolType( QgsSymbol::Fill ); + setGuiElementValues(); blockAllSignals( false ); diff --git a/src/app/qgsannotationwidget.cpp b/src/app/qgsannotationwidget.cpp index 95014b8d095..6b4a32901a7 100644 --- a/src/app/qgsannotationwidget.cpp +++ b/src/app/qgsannotationwidget.cpp @@ -33,6 +33,9 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsMapCanvasAnnotationItem *item, QWid setupUi( this ); mLayerComboBox->setAllowEmptyLayer( true ); + mMapMarkerButton->setSymbolType( QgsSymbol::Marker ); + mFrameStyleButton->setSymbolType( QgsSymbol::Fill ); + if ( mItem && mItem->annotation() ) { QgsAnnotation *annotation = mItem->annotation(); diff --git a/src/app/qgsdecorationgriddialog.cpp b/src/app/qgsdecorationgriddialog.cpp index 070c6094956..a568ceaad63 100644 --- a/src/app/qgsdecorationgriddialog.cpp +++ b/src/app/qgsdecorationgriddialog.cpp @@ -34,6 +34,9 @@ QgsDecorationGridDialog::QgsDecorationGridDialog( QgsDecorationGrid &deco, QWidg { setupUi( this ); + mMarkerSymbolButton->setSymbolType( QgsSymbol::Marker ); + mLineSymbolButton->setSymbolType( QgsSymbol::Line ); + mAnnotationFontButton->setMode( QgsFontButton::ModeQFont ); QgsSettings settings; @@ -65,6 +68,7 @@ QgsDecorationGridDialog::QgsDecorationGridDialog( QgsDecorationGrid &deco, QWidg connect( mAnnotationFontButton, &QgsFontButton::changed, this, &QgsDecorationGridDialog::annotationFontChanged ); mMarkerSymbolButton->setMapCanvas( QgisApp::instance()->mapCanvas() ); + mLineSymbolButton->setMapCanvas( QgisApp::instance()->mapCanvas() ); } void QgsDecorationGridDialog::updateGuiElements() diff --git a/src/app/qgsdecorationlayoutextentdialog.cpp b/src/app/qgsdecorationlayoutextentdialog.cpp index 1db51351f41..f605e66b14c 100644 --- a/src/app/qgsdecorationlayoutextentdialog.cpp +++ b/src/app/qgsdecorationlayoutextentdialog.cpp @@ -35,6 +35,8 @@ QgsDecorationLayoutExtentDialog::QgsDecorationLayoutExtentDialog( QgsDecorationL { setupUi( this ); + mSymbolButton->setSymbolType( QgsSymbol::Fill ); + QgsSettings settings; restoreGeometry( settings.value( "/Windows/DecorationLayoutExtent/geometry" ).toByteArray() ); diff --git a/src/core/symbology-ng/qgssymbol.h b/src/core/symbology-ng/qgssymbol.h index d09c34af93f..71c3fb65895 100644 --- a/src/core/symbology-ng/qgssymbol.h +++ b/src/core/symbology-ng/qgssymbol.h @@ -136,7 +136,7 @@ class CORE_EXPORT QgsSymbol * \see symbolLayers * \see symbolLayer */ - int symbolLayerCount() { return mLayers.count(); } + int symbolLayerCount() const { return mLayers.count(); } /** * Insert symbol layer to specified index diff --git a/src/core/symbology-ng/qgssymbollayer.h b/src/core/symbology-ng/qgssymbollayer.h index 95f565d0eb5..2a370b23d36 100644 --- a/src/core/symbology-ng/qgssymbollayer.h +++ b/src/core/symbology-ng/qgssymbollayer.h @@ -252,7 +252,11 @@ class CORE_EXPORT QgsSymbolLayer virtual void drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ) = 0; + /** + * Returns the symbol's sub symbol, if present. + */ virtual QgsSymbol *subSymbol() { return nullptr; } + //! set layer's subsymbol. takes ownership of the passed symbol virtual bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) { delete symbol; return false; } diff --git a/src/core/symbology-ng/qgssymbollayerutils.cpp b/src/core/symbology-ng/qgssymbollayerutils.cpp index a9c20e8f601..eebd35a3b5c 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.cpp +++ b/src/core/symbology-ng/qgssymbollayerutils.cpp @@ -2809,6 +2809,48 @@ void QgsSymbolLayerUtils::clearSymbolMap( QgsSymbolMap &symbols ) symbols.clear(); } +QMimeData *QgsSymbolLayerUtils::symbolToMimeData( QgsSymbol *symbol ) +{ + if ( !symbol ) + return nullptr; + + std::unique_ptr< QMimeData >mimeData( new QMimeData ); + + QDomDocument symbolDoc; + QDomElement symbolElem = saveSymbol( QStringLiteral( "symbol" ), symbol, symbolDoc, QgsReadWriteContext() ); + symbolDoc.appendChild( symbolElem ); + mimeData->setText( symbolDoc.toString() ); + + mimeData->setImageData( symbolPreviewPixmap( symbol, QSize( 100, 100 ), 18 ).toImage() ); + mimeData->setColorData( symbol->color() ); + + return mimeData.release(); +} + +QgsSymbol *QgsSymbolLayerUtils::symbolFromMimeData( const QMimeData *data ) +{ + if ( !data ) + return nullptr; + + QString text = data->text(); + if ( !text.isEmpty() ) + { + QDomDocument doc; + QDomElement elem; + + if ( doc.setContent( text ) ) + { + elem = doc.documentElement(); + + if ( elem.nodeName() != QStringLiteral( "symbol" ) ) + elem = elem.firstChildElement( QStringLiteral( "symbol" ) ); + + return loadSymbol( elem, QgsReadWriteContext() ); + } + } + return nullptr; +} + QgsColorRamp *QgsSymbolLayerUtils::loadColorRamp( QDomElement &element ) { diff --git a/src/core/symbology-ng/qgssymbollayerutils.h b/src/core/symbology-ng/qgssymbollayerutils.h index f8c2f72f1f4..45435e04887 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.h +++ b/src/core/symbology-ng/qgssymbollayerutils.h @@ -365,6 +365,23 @@ class CORE_EXPORT QgsSymbolLayerUtils static void clearSymbolMap( QgsSymbolMap &symbols ); + /** + * Creates new mime data from a \a symbol. + * This also sets the mime color data to match the symbol's color, so that copied symbols + * can be paste in places where a color is expected. + * \see symbolFromMimeData() + * \since QGIS 3.0 + */ + static QMimeData *symbolToMimeData( QgsSymbol *symbol ) SIP_FACTORY; + + /** + * Attempts to parse \a mime data as a symbol. A new symbol instance will be returned + * if the data was successfully converted to a symbol. + * \see symbolToMimeData() + * \since QGIS 3.0 + */ + static QgsSymbol *symbolFromMimeData( const QMimeData *data ) SIP_FACTORY; + /** Creates a color ramp from the settings encoded in an XML element * \param element DOM element * \returns new color ramp. Caller takes responsibility for deleting the returned value. diff --git a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp index 7c0a85cdf61..3f230be0958 100644 --- a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp +++ b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp @@ -47,6 +47,7 @@ QgsFieldConditionalFormatWidget::QgsFieldConditionalFormatWidget( QWidget *paren mModel = new QStandardItemModel( listView ); listView->setModel( mModel ); mPresetsList->setModel( mPresetsModel ); + btnChangeIcon->setSymbolType( QgsSymbol::Marker ); btnChangeIcon->setSymbol( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ); setPresets( defaultPresets() ); diff --git a/src/gui/qgssymbolbutton.cpp b/src/gui/qgssymbolbutton.cpp index 3a640b8742e..248bcd55782 100644 --- a/src/gui/qgssymbolbutton.cpp +++ b/src/gui/qgssymbolbutton.cpp @@ -52,6 +52,29 @@ QSize QgsSymbolButton::minimumSizeHint() const return QSize( size.width(), qMax( size.height(), fontHeight ) ); } +void QgsSymbolButton::setSymbolType( QgsSymbol::SymbolType type ) +{ + if ( type != mType ) + { + switch ( type ) + { + case QgsSymbol::Marker: + mSymbol.reset( QgsMarkerSymbol::createSimple( QgsStringMap() ) ); + break; + + case QgsSymbol::Line: + mSymbol.reset( QgsLineSymbol::createSimple( QgsStringMap() ) ); + break; + + case QgsSymbol::Fill: + mSymbol.reset( QgsFillSymbol::createSimple( QgsStringMap() ) ); + break; + } + } + updatePreview(); + mType = type; +} + void QgsSymbolButton::showSettingsDialog() { QgsExpressionContext context; @@ -155,6 +178,18 @@ void QgsSymbolButton::setColor( const QColor &color ) emit changed(); } +void QgsSymbolButton::copySymbol() +{ + QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( mSymbol.get() ) ); +} + +void QgsSymbolButton::pasteSymbol() +{ + std::unique_ptr< QgsSymbol > symbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) ); + if ( symbol && symbol->type() == mType ) + setSymbol( symbol.release() ); +} + void QgsSymbolButton::copyColor() { QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) ); @@ -265,6 +300,24 @@ void QgsSymbolButton::prepareMenu() mMenu->addAction( configureAction ); connect( configureAction, &QAction::triggered, this, &QgsSymbolButton::showSettingsDialog ); + QAction *copySymbolAction = new QAction( tr( "Copy symbol" ), this ); + mMenu->addAction( copySymbolAction ); + connect( copySymbolAction, &QAction::triggered, this, &QgsSymbolButton::copySymbol ); + QAction *pasteSymbolAction = new QAction( tr( "Paste symbol" ), this ); + //enable or disable paste action based on current clipboard contents. We always show the paste + //action, even if it's disabled, to give hint to the user that pasting symbols is possible + std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) ); + if ( tempSymbol && tempSymbol->type() == mType ) + { + pasteSymbolAction->setIcon( QgsSymbolLayerUtils::symbolPreviewIcon( tempSymbol.get(), QSize( 16, 16 ), 1 ) ); + } + else + { + pasteSymbolAction->setEnabled( false ); + } + mMenu->addAction( pasteSymbolAction ); + connect( pasteSymbolAction, &QAction::triggered, this, &QgsSymbolButton::pasteSymbol ); + mMenu->addSeparator(); QgsColorWheel *colorWheel = new QgsColorWheel( mMenu ); diff --git a/src/gui/qgssymbolbutton.h b/src/gui/qgssymbolbutton.h index 8df81616981..f19cb5a2e1e 100644 --- a/src/gui/qgssymbolbutton.h +++ b/src/gui/qgssymbolbutton.h @@ -53,6 +53,20 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton virtual QSize minimumSizeHint() const override; + /** + * Sets the symbol \a type which the button requires. + * If the type differs from the current symbol type, the symbol will be reset + * to a default symbol style of the new type. + * \see symbolType() + */ + void setSymbolType( QgsSymbol::SymbolType type ); + + /** + * Returns the symbol type which the button requires. + * \see setSymbolType() + */ + QgsSymbol::SymbolType symbolType() const { return mType; } + /** * Sets the \a title for the symbol settings dialog window. * \see dialogTitle() @@ -143,6 +157,18 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton */ void setColor( const QColor &color ); + /** Copies the current symbol to the clipboard. + * \see pasteSymbol() + */ + void copySymbol(); + + /** + * Pastes a symbol from the clipboard. If clipboard does not contain a valid + * symbol then no change is applied. + * \see copySymbol() + */ + void pasteSymbol(); + /** * Copies the current symbol color to the clipboard. * \see pasteColor() @@ -200,6 +226,8 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton QString mDialogTitle; + QgsSymbol::SymbolType mType = QgsSymbol::Fill; + QgsMapCanvas *mMapCanvas = nullptr; QPoint mDragStartPosition; diff --git a/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp b/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp index 0a91a9a5700..e2914e042cf 100644 --- a/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp +++ b/src/gui/symbology-ng/qgspointclusterrendererwidget.cpp @@ -53,6 +53,8 @@ QgsPointClusterRendererWidget::QgsPointClusterRendererWidget( QgsVectorLayer *la mDistanceUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches ); + mCenterSymbolToolButton->setSymbolType( QgsSymbol::Marker ); + if ( renderer ) { mRenderer = QgsPointClusterRenderer::convertFromRenderer( renderer ); diff --git a/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp b/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp index c457a02c17a..acd7dc50c5d 100644 --- a/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp +++ b/src/gui/symbology-ng/qgspointdisplacementrendererwidget.cpp @@ -53,6 +53,7 @@ QgsPointDisplacementRendererWidget::QgsPointDisplacementRendererWidget( QgsVecto mLabelFontButton->setMode( QgsFontButton::ModeQFont ); mDistanceUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches ); + mCenterSymbolToolButton->setSymbolType( QgsSymbol::Marker ); if ( renderer ) { diff --git a/tests/src/python/test_qgssymbolbutton.py b/tests/src/python/test_qgssymbolbutton.py index ca53e1f8fe2..66ac2d0ddad 100644 --- a/tests/src/python/test_qgssymbolbutton.py +++ b/tests/src/python/test_qgssymbolbutton.py @@ -14,7 +14,7 @@ __revision__ = '$Format:%H$' import qgis # NOQA -from qgis.core import QgsFillSymbol, QgsMarkerSymbol +from qgis.core import QgsFillSymbol, QgsMarkerSymbol, QgsSymbol from qgis.gui import QgsSymbolButton, QgsMapCanvas from qgis.testing import start_app, unittest from qgis.PyQt.QtGui import QColor, QFont @@ -36,6 +36,50 @@ class TestQgsSymbolButton(unittest.TestCase): button.setMapCanvas(canvas) self.assertEqual(button.mapCanvas(), canvas) + button.setSymbolType(QgsSymbol.Line) + self.assertEqual(button.symbolType(), QgsSymbol.Line) + + def testSettingSymbolType(self) + button = QgsSymbolButton() + button.setSymbolType(QgsSymbol.Marker) + symbol = QgsMarkerSymbol.createSimple({}) + symbol.setColor(QColor(255, 0, 0)) + button.setSymbol(symbol) + + # if same symbol type, existing symbol should be kept + button.setSymbolType(QgsSymbol.Marker) + self.assertEqual(button.symbol(), symbol) + + # if setting different symbol type, symbol should be reset to new type + button.setSymbolType(QgsSymbol.Fill) + self.assertTrue(isinstance(button.symbol(), QgsFillSymbol)) + + def testPasteSymbol(self): + button = QgsSymbolButton() + button.setSymbolType(QgsSymbol.Marker) + symbol = QgsMarkerSymbol.createSimple({}) + symbol.setColor(QColor(255, 0, 0)) + button.setSymbol(symbol) + + button2 = QgsSymbolButton() + button2.setSymbolType(QgsSymbol.Marker) + symbol2 = QgsMarkerSymbol.createSimple({}) + symbol2.setColor(QColor(0, 255, 0)) + button2.setSymbol(symbol2) + + button.copySymbol() + button2.pasteSymbol() + self.assertEqual(button2.symbol().color(), QColor(255, 0, 0)) + + # try pasting incompatible symbol + button2.setSymbolType(QgsSymbol.Fill) + fill_symbol = QgsFillSymbol.createSimple({}) + fill_symbol.setColor(QColor(0,0,255)) + button2.setSymbol(fill_symbol) + button.copySymbol() # copied a marker symbol + button2.pasteSymbol() # should have no effect + self.assertEqual(button2.symbol(), fill_symbol) + def testSetGetSymbol(self): button = QgsSymbolButton() symbol = QgsMarkerSymbol.createSimple({}) diff --git a/tests/src/python/test_qgssymbollayerutils.py b/tests/src/python/test_qgssymbollayerutils.py index 79309e654a1..966029d0c35 100644 --- a/tests/src/python/test_qgssymbollayerutils.py +++ b/tests/src/python/test_qgssymbollayerutils.py @@ -14,7 +14,7 @@ __revision__ = '$Format:%H$' import qgis # NOQA -from qgis.core import QgsSymbolLayerUtils +from qgis.core import QgsSymbolLayerUtils, QgsMarkerSymbol from qgis.PyQt.QtCore import QSizeF, QPointF from qgis.testing import unittest @@ -49,6 +49,20 @@ class PyQgsSymbolLayerUtils(unittest.TestCase): s2 = QgsSymbolLayerUtils.decodePoint('') self.assertEqual(s2, QPointF()) + def testSymbolToFromMimeData(self): + """ + Test converting symbols to and from mime data + """ + symbol = QgsMarkerSymbol.createSimple({}) + symbol.setColor(QColor(255, 0, 255)) + self.assertFalse(QgsSymbolLayerUtils.symbolFromMimeData(None)) + self.assertFalse(QgsSymbolLayerUtils.symbolToMimeData(None)) + mime = QgsSymbolLayerUtils.symbolToMimeData(symbol) + self.assertTrue(mime is not None) + symbol2 = QgsSymbolLayerUtils.symbolFromMimeData(mime) + self.assertTrue(symbol2 is not None) + self.assertEqual(symbol2.color().name(), symbol.color().name()) + if __name__ == '__main__': unittest.main() From 500175b8e8aaff73d0ce8caac6006fd79bff9752 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Mon, 24 Jul 2017 10:45:32 +0200 Subject: [PATCH 141/266] remove QgsScrollAreaWidgetPlugin from custom widgets it had to be removed form the widget list in uic plugin. It might be better to provide the real list of implemented widgets rather than the full gui list. fixes #16428 --- python/custom_widgets/qgis_customwidgets.py | 4 +++- src/customwidgets/CMakeLists.txt | 4 ++-- src/customwidgets/qgiscustomwidgets.cpp | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/python/custom_widgets/qgis_customwidgets.py b/python/custom_widgets/qgis_customwidgets.py index 3207fb71d0c..eeed7bb663e 100644 --- a/python/custom_widgets/qgis_customwidgets.py +++ b/python/custom_widgets/qgis_customwidgets.py @@ -50,6 +50,8 @@ pluginType = MODULE def moduleInformation(): try: import qgis.gui - return "qgis.gui", dir(qgis.gui) + widget_list=dir(qgis.gui) + widget_list.remove('QgsScrollArea') + return "qgis.gui", widget_list except ImportError: return "", [] diff --git a/src/customwidgets/CMakeLists.txt b/src/customwidgets/CMakeLists.txt index 253a5615bc5..6838ace9352 100644 --- a/src/customwidgets/CMakeLists.txt +++ b/src/customwidgets/CMakeLists.txt @@ -36,7 +36,7 @@ SET (QGIS_CUSTOMWIDGETS_SRCS qgsrelationreferencewidgetplugin.cpp qgsscalerangewidgetplugin.cpp qgsscalewidgetplugin.cpp - qgsscrollareawidgetplugin.cpp +# qgsscrollareawidgetplugin.cpp qgsspinboxplugin.cpp ) @@ -66,7 +66,7 @@ SET (QGIS_CUSTOMWIDGETS_MOC_HDRS qgsrelationreferencewidgetplugin.h qgsscalerangewidgetplugin.h qgsscalewidgetplugin.h - qgsscrollareawidgetplugin.h +# qgsscrollareawidgetplugin.h qgsspinboxplugin.h ) diff --git a/src/customwidgets/qgiscustomwidgets.cpp b/src/customwidgets/qgiscustomwidgets.cpp index c3387701f68..c9d26ab9346 100644 --- a/src/customwidgets/qgiscustomwidgets.cpp +++ b/src/customwidgets/qgiscustomwidgets.cpp @@ -39,7 +39,7 @@ #include "qgsrelationreferencewidgetplugin.h" #include "qgsscalerangewidgetplugin.h" #include "qgsscalewidgetplugin.h" -#include "qgsscrollareawidgetplugin.h" +//#include "qgsscrollareawidgetplugin.h" #include "qgsspinboxplugin.h" @@ -69,7 +69,7 @@ QgisCustomWidgets::QgisCustomWidgets( QObject *parent ) mWidgets.append( new QgsRelationReferenceWidgetPlugin( this ) ); mWidgets.append( new QgsScaleRangeWidgetPlugin( this ) ); mWidgets.append( new QgsScaleWidgetPlugin( this ) ); - mWidgets.append( new QgsScrollAreaWidgetPlugin( this ) ); +// mWidgets.append( new QgsScrollAreaWidgetPlugin( this ) ); // this is causing troubles at the moment mWidgets.append( new QgsSpinBoxPlugin( this ) ); } From db745df4a4dc4f2acc28522fe497c3493bd99898 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Mon, 24 Jul 2017 11:36:38 +0200 Subject: [PATCH 142/266] fix indentation --- python/custom_widgets/qgis_customwidgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/custom_widgets/qgis_customwidgets.py b/python/custom_widgets/qgis_customwidgets.py index eeed7bb663e..379c2928bc9 100644 --- a/python/custom_widgets/qgis_customwidgets.py +++ b/python/custom_widgets/qgis_customwidgets.py @@ -50,7 +50,7 @@ pluginType = MODULE def moduleInformation(): try: import qgis.gui - widget_list=dir(qgis.gui) + widget_list = dir(qgis.gui) widget_list.remove('QgsScrollArea') return "qgis.gui", widget_list except ImportError: From 7e749a5afb6eadafb4cc85ecbba6c17f795701c2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Jul 2017 19:46:26 +1000 Subject: [PATCH 143/266] Fix failing tests --- src/core/qgsunittypes.cpp | 0 src/gui/qgssymbolbutton.cpp | 3 +++ src/gui/qgsunitselectionwidget.cpp | 0 tests/src/python/test_qgssymbolbutton.py | 4 ++-- tests/src/python/test_qgssymbollayerutils.py | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) mode change 100755 => 100644 src/core/qgsunittypes.cpp mode change 100755 => 100644 src/gui/qgsunitselectionwidget.cpp diff --git a/src/core/qgsunittypes.cpp b/src/core/qgsunittypes.cpp old mode 100755 new mode 100644 diff --git a/src/gui/qgssymbolbutton.cpp b/src/gui/qgssymbolbutton.cpp index 248bcd55782..d6072da0f0e 100644 --- a/src/gui/qgssymbolbutton.cpp +++ b/src/gui/qgssymbolbutton.cpp @@ -69,6 +69,9 @@ void QgsSymbolButton::setSymbolType( QgsSymbol::SymbolType type ) case QgsSymbol::Fill: mSymbol.reset( QgsFillSymbol::createSimple( QgsStringMap() ) ); break; + + case QgsSymbol::Hybrid: + break; } } updatePreview(); diff --git a/src/gui/qgsunitselectionwidget.cpp b/src/gui/qgsunitselectionwidget.cpp old mode 100755 new mode 100644 diff --git a/tests/src/python/test_qgssymbolbutton.py b/tests/src/python/test_qgssymbolbutton.py index 66ac2d0ddad..410d0459cc9 100644 --- a/tests/src/python/test_qgssymbolbutton.py +++ b/tests/src/python/test_qgssymbolbutton.py @@ -39,7 +39,7 @@ class TestQgsSymbolButton(unittest.TestCase): button.setSymbolType(QgsSymbol.Line) self.assertEqual(button.symbolType(), QgsSymbol.Line) - def testSettingSymbolType(self) + def testSettingSymbolType(self): button = QgsSymbolButton() button.setSymbolType(QgsSymbol.Marker) symbol = QgsMarkerSymbol.createSimple({}) @@ -74,7 +74,7 @@ class TestQgsSymbolButton(unittest.TestCase): # try pasting incompatible symbol button2.setSymbolType(QgsSymbol.Fill) fill_symbol = QgsFillSymbol.createSimple({}) - fill_symbol.setColor(QColor(0,0,255)) + fill_symbol.setColor(QColor(0, 0, 255)) button2.setSymbol(fill_symbol) button.copySymbol() # copied a marker symbol button2.pasteSymbol() # should have no effect diff --git a/tests/src/python/test_qgssymbollayerutils.py b/tests/src/python/test_qgssymbollayerutils.py index 966029d0c35..3328baf6c69 100644 --- a/tests/src/python/test_qgssymbollayerutils.py +++ b/tests/src/python/test_qgssymbollayerutils.py @@ -15,6 +15,7 @@ __revision__ = '$Format:%H$' import qgis # NOQA from qgis.core import QgsSymbolLayerUtils, QgsMarkerSymbol +from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtCore import QSizeF, QPointF from qgis.testing import unittest From 6202f06e985125aa20f1c99b7daea3c9acb395b5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Jul 2017 20:06:36 +1000 Subject: [PATCH 144/266] Fix more tests --- tests/src/python/test_qgssymbollayerutils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/src/python/test_qgssymbollayerutils.py b/tests/src/python/test_qgssymbollayerutils.py index 3328baf6c69..488cfb12063 100644 --- a/tests/src/python/test_qgssymbollayerutils.py +++ b/tests/src/python/test_qgssymbollayerutils.py @@ -17,7 +17,9 @@ import qgis # NOQA from qgis.core import QgsSymbolLayerUtils, QgsMarkerSymbol from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtCore import QSizeF, QPointF -from qgis.testing import unittest +from qgis.testing import unittest, start_app + +start_app() class PyQgsSymbolLayerUtils(unittest.TestCase): From 601dd412133ca99155fad5db14221c4887f2b3c7 Mon Sep 17 00:00:00 2001 From: Nathan Woodrow Date: Mon, 24 Jul 2017 15:32:06 +1000 Subject: [PATCH 145/266] Nicer level string in message log window --- src/gui/qgsmessagelogviewer.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/gui/qgsmessagelogviewer.cpp b/src/gui/qgsmessagelogviewer.cpp index 6ecdb4db71b..534fc0ab682 100644 --- a/src/gui/qgsmessagelogviewer.cpp +++ b/src/gui/qgsmessagelogviewer.cpp @@ -75,9 +75,26 @@ void QgsMessageLogViewer::logMessage( const QString &message, const QString &tag tabWidget->setTabsClosable( true ); } + QString levelString; + switch ( level ) + { + case QgsMessageLog::INFO: + levelString = "INFO"; + break; + case QgsMessageLog::WARNING: + levelString = "WARNING"; + break; + case QgsMessageLog::CRITICAL: + levelString = "CRITICAL"; + break; + case QgsMessageLog::NONE: + levelString = "NONE"; + break; + } + QString prefix = QStringLiteral( "%1\t%2\t" ) .arg( QDateTime::currentDateTime().toString( Qt::ISODate ) ) - .arg( level ); + .arg( levelString ); QString cleanedMessage = message; cleanedMessage = cleanedMessage.prepend( prefix ).replace( '\n', QLatin1String( "\n\t\t\t" ) ); w->appendPlainText( cleanedMessage ); From b9e545a465791ea3f9571f265e266ae2c3255078 Mon Sep 17 00:00:00 2001 From: Nathan Woodrow Date: Mon, 24 Jul 2017 21:06:30 +1000 Subject: [PATCH 146/266] Remove corner widget for profiles. Make it normal menu Hopefully for now until I work out better UX --- src/app/qgisapp.cpp | 53 +++++++++++++++++++++++++++------------------ src/app/qgisapp.h | 1 - 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 1b3134dc1ae..79761ce1c77 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1283,7 +1283,6 @@ QgisApp::QgisApp() , mDatabaseMenu( nullptr ) , mWebMenu( nullptr ) , mConfigMenu( nullptr ) - , mConfigMenuBar( nullptr ) , mToolPopupOverviews( nullptr ) , mToolPopupDisplay( nullptr ) , mMapCanvas( nullptr ) @@ -2302,21 +2301,16 @@ void QgisApp::refreshProfileMenu() { mConfigMenu->clear(); QgsUserProfile *profile = userProfileManager()->userProfile(); - mConfigMenu->setTitle( tr( "&User Profile: %1" ).arg( profile->alias() ) ); + QString activeName = profile->name(); + mConfigMenu->setTitle( tr( "&User Profiles" ) ); - Q_FOREACH ( const QString &name, userProfileManager()->allProfiles() ) - { - profile = userProfileManager()->profileForName( name ); - QAction *action = mConfigMenu->addAction( profile->icon(), profile->alias() ); - action->setToolTip( profile->folder() ); - delete profile; - connect( action, &QAction::triggered, this, [this, name]() - { - userProfileManager()->loadUserProfile( name ); - } ); - } - mConfigMenu->addSeparator(); - QAction *openProfileFolderAction = mConfigMenu->addAction( tr( "Open profile folder" ) ); + mConfigMenu->addSection( tr( "Active Profile" ) ); + + QAction *profileSection = mConfigMenu->addSection( tr( "Profiles" ) ); + + QAction *configSection = mConfigMenu->addSection( tr( "Config" ) ); + + QAction *openProfileFolderAction = mConfigMenu->addAction( tr( "Open current profile folder" ) ); connect( openProfileFolderAction, &QAction::triggered, this, [this]() { QDesktopServices::openUrl( QUrl::fromLocalFile( userProfileManager()->userProfile()->folder() ) ); @@ -2324,18 +2318,35 @@ void QgisApp::refreshProfileMenu() QAction *newProfileAction = mConfigMenu->addAction( tr( "New profile" ) ); connect( newProfileAction, &QAction::triggered, this, &QgisApp::newProfile ); + + Q_FOREACH ( const QString &name, userProfileManager()->allProfiles() ) + { + profile = userProfileManager()->profileForName( name ); + QAction *action = new QAction( profile->icon(), profile->alias() ); + action->setToolTip( profile->folder() ); + action->setToolTip( profile->folder() ); + delete profile; + + if ( name == activeName ) + { + mConfigMenu->insertAction( profileSection, action ); + } + else + { + mConfigMenu->insertAction( configSection, action ); + } + connect( action, &QAction::triggered, this, [this, name]() + { + userProfileManager()->loadUserProfile( name ); + } ); + } } void QgisApp::createProfileMenu() { mConfigMenu = new QMenu(); - mConfigMenu->addSeparator(); - mConfigMenu->addAction( tr( "Manage Configs" ) ); - mConfigMenuBar = new QMenuBar( menuBar() ); - mConfigMenuBar->addMenu( mConfigMenu ); - menuBar()->setCornerWidget( mConfigMenuBar ); - mConfigMenuBar->show(); + menuBar()->addMenu( mConfigMenu ); refreshProfileMenu(); } diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index a2c99e899fe..38f7138114e 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -1891,7 +1891,6 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QMenu *mWebMenu = nullptr; QMenu *mConfigMenu = nullptr; - QMenuBar *mConfigMenuBar = nullptr; //! Popup menu for the map overview tools QMenu *mToolPopupOverviews = nullptr; From fcc0d7dde38d9500aa562f22e638167981a24b5d Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Mon, 24 Jul 2017 11:10:26 +0200 Subject: [PATCH 147/266] enhanced output for code_layout build on Travis --- .ci/travis/code_layout/script.sh | 3 ++- .travis.yml | 1 + cmake_templates/Doxyfile.in | 14 -------------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/.ci/travis/code_layout/script.sh b/.ci/travis/code_layout/script.sh index 1c5a2198217..c04aae36d18 100755 --- a/.ci/travis/code_layout/script.sh +++ b/.ci/travis/code_layout/script.sh @@ -15,5 +15,6 @@ set -e pushd build -xvfb-run ctest -V --output-on-failure +export CTEST_BUILD_COMMAND="/usr/bin/make -j3 -i -k" +python ${TRAVIS_BUILD_DIR}/.ci/travis/scripts/ctest2travis.py xvfb-run ctest -V --output-on-failure -S ${TRAVIS_BUILD_DIR}/.ci/travis/travis.ctest popd diff --git a/.travis.yml b/.travis.yml index 3ae53a7b701..fa981a9c1d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,6 +50,7 @@ matrix: apt: sources: - sourceline: 'ppa:jonathonf/backports' # silversearcher-ag backport + - sourceline: 'ppa:sergey-dryabzhinsky/packages' # doxygen packages: - doxygen - graphviz diff --git a/cmake_templates/Doxyfile.in b/cmake_templates/Doxyfile.in index 2b88c205119..7135b6cf4da 100644 --- a/cmake_templates/Doxyfile.in +++ b/cmake_templates/Doxyfile.in @@ -303,15 +303,6 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 0 - # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -2388,11 +2379,6 @@ DIAFILE_DIRS = PLANTUML_JAR_PATH = -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. - -PLANTUML_CFG_FILE = - # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. From 33071f4720281cf6901c4063145a53b4dcfdbe2f Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Mon, 24 Jul 2017 13:38:15 +0200 Subject: [PATCH 148/266] fix doxymentation --- python/core/qgsdatumtransformstore.sip | 1 + python/core/qgsuserprofilemanager.sip | 1 + python/gui/qgsattributeform.sip | 1 + src/core/qgsdatumtransformstore.h | 1 + src/core/qgsuserprofilemanager.h | 1 + src/gui/qgsattributeform.h | 1 + src/gui/qgsdatasourcemanagerdialog.h | 7 ++++--- 7 files changed, 10 insertions(+), 3 deletions(-) diff --git a/python/core/qgsdatumtransformstore.sip b/python/core/qgsdatumtransformstore.sip index fe7936e5c01..5500913d6d2 100644 --- a/python/core/qgsdatumtransformstore.sip +++ b/python/core/qgsdatumtransformstore.sip @@ -41,6 +41,7 @@ class QgsDatumTransformStore Will return transform from layer's CRS to current destination CRS. :return: transformation associated with layer, or an invalid QgsCoordinateTransform if no transform is associated with the layer + \param layer the associated layer \param srcAuthId source CRS (defaults to layer crs) \param dstAuthId destination CRS (defaults to store's crs) :rtype: QgsCoordinateTransform diff --git a/python/core/qgsuserprofilemanager.sip b/python/core/qgsuserprofilemanager.sip index 1d7acb38a20..9321123e5f4 100644 --- a/python/core/qgsuserprofilemanager.sip +++ b/python/core/qgsuserprofilemanager.sip @@ -53,6 +53,7 @@ class QgsUserProfileManager : QObject Returns a new QgsUserProfile. Ownership transferred to caller. \param defaultProfile The profile name to find. Empty profile name will return "default" for the name. \param createNew Create the profile folder if it doesn't exist. + \param initSettings if the settings should be initialized :return: The user profile :rtype: QgsUserProfile %End diff --git a/python/gui/qgsattributeform.sip b/python/gui/qgsattributeform.sip index f73c193fc68..fe06866b4f4 100644 --- a/python/gui/qgsattributeform.sip +++ b/python/gui/qgsattributeform.sip @@ -173,6 +173,7 @@ class QgsAttributeForm : QWidget \param field The field to change \param value The new value + \param hintText A hint text for non existent joined features %End void setFeature( const QgsFeature &feature ); diff --git a/src/core/qgsdatumtransformstore.h b/src/core/qgsdatumtransformstore.h index 1adb91046ee..d1f8f923a29 100644 --- a/src/core/qgsdatumtransformstore.h +++ b/src/core/qgsdatumtransformstore.h @@ -47,6 +47,7 @@ class CORE_EXPORT QgsDatumTransformStore * Will return transform from layer's CRS to current destination CRS. * \returns transformation associated with layer, or an invalid QgsCoordinateTransform * if no transform is associated with the layer + * \param layer the associated layer * \param srcAuthId source CRS (defaults to layer crs) * \param dstAuthId destination CRS (defaults to store's crs) */ diff --git a/src/core/qgsuserprofilemanager.h b/src/core/qgsuserprofilemanager.h index 502128debf2..d991d635dde 100644 --- a/src/core/qgsuserprofilemanager.h +++ b/src/core/qgsuserprofilemanager.h @@ -63,6 +63,7 @@ class CORE_EXPORT QgsUserProfileManager : public QObject * \note Returns a new QgsUserProfile. Ownership transferred to caller. * \param defaultProfile The profile name to find. Empty profile name will return "default" for the name. * \param createNew Create the profile folder if it doesn't exist. + * \param initSettings if the settings should be initialized * \return The user profile */ QgsUserProfile *getProfile( const QString &defaultProfile = "default", bool createNew = true, bool initSettings = true ) SIP_FACTORY; diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index 405535d8272..ab019952300 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -208,6 +208,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget * * \param field The field to change * \param value The new value + * \param hintText A hint text for non existent joined features */ void changeAttribute( const QString &field, const QVariant &value, const QString &hintText = QString() ); diff --git a/src/gui/qgsdatasourcemanagerdialog.h b/src/gui/qgsdatasourcemanagerdialog.h index 9e997048e63..c52af56e7e7 100644 --- a/src/gui/qgsdatasourcemanagerdialog.h +++ b/src/gui/qgsdatasourcemanagerdialog.h @@ -48,15 +48,16 @@ class GUI_EXPORT QgsDataSourceManagerDialog : public QgsOptionsDialogBase, priva public: /** QgsDataSourceManagerDialog constructor - * @param parent the object - * @param fl window flags + * \param parent the object + * \param canvas a pointer to the map canvas + * \param fl window flags */ explicit QgsDataSourceManagerDialog( QWidget *parent = nullptr, QgsMapCanvas *canvas = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags ); ~QgsDataSourceManagerDialog(); /** * @brief openPage open a given page in the dialog - * @param pageName the page name, usually the provider name or "browser" (for the browser panel) + * \param pageName the page name, usually the provider name or "browser" (for the browser panel) * or "ogr" (vector layers) or "raster" (raster layers) */ void openPage( QString pageName ); From dcec6bbf3f6a74e3d22dc15d86dd3aff847b48ab Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Mon, 24 Jul 2017 14:58:37 +0200 Subject: [PATCH 149/266] Qt 5.5 has no default value for parent in QAction constructor see http://code.qt.io/cgit/qt/qtbase.git/commit/src/widgets/kernel/qaction.h?id=536da5eb6ab1b4b07c13ad9f4c0f69dba5ca1624 --- src/app/qgisapp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 79761ce1c77..f8f4a529e02 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -2322,8 +2322,8 @@ void QgisApp::refreshProfileMenu() Q_FOREACH ( const QString &name, userProfileManager()->allProfiles() ) { profile = userProfileManager()->profileForName( name ); - QAction *action = new QAction( profile->icon(), profile->alias() ); - action->setToolTip( profile->folder() ); + // Qt 5.5 has no parent default as nullptr + QAction *action = new QAction( profile->icon(), profile->alias(), nullptr ); action->setToolTip( profile->folder() ); delete profile; From 1811a396541f498d0ddf66550118543a36709f62 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 10 Jul 2017 06:14:07 +0100 Subject: [PATCH 150/266] [FEATURE] Constraints are updated on joined fields when dynamic form option is enabled --- src/core/qgsvectorlayerjoinbuffer.cpp | 7 +++-- .../core/qgseditorwidgetwrapper.cpp | 13 ++++++-- .../core/qgseditorwidgetwrapper.h | 12 +++++++ src/gui/qgsattributeform.cpp | 31 ++++++++++++++++--- src/gui/qgsattributeform.h | 2 ++ 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/core/qgsvectorlayerjoinbuffer.cpp b/src/core/qgsvectorlayerjoinbuffer.cpp index ba778caac0e..d25899130cf 100644 --- a/src/core/qgsvectorlayerjoinbuffer.cpp +++ b/src/core/qgsvectorlayerjoinbuffer.cpp @@ -416,10 +416,11 @@ QgsFeature QgsVectorLayerJoinBuffer::joinedFeatureOf( const QgsVectorLayerJoinIn if ( info->joinLayer() ) { + joinedFeature.setFields( info->joinLayer()->fields() ); + + QString joinFieldName = info->joinFieldName(); const QVariant targetValue = feature.attribute( info->targetFieldName() ); - QString fieldRef = QgsExpression::quotedColumnRef( info->joinFieldName() ); - QString quotedVal = QgsExpression::quotedValue( targetValue.toString() ); - const QString filter = QStringLiteral( "%1 = %2" ).arg( fieldRef, quotedVal ); + QString filter = QgsExpression::createFieldEqualityExpression( joinFieldName, targetValue ); QgsFeatureRequest request; request.setFilterExpression( filter ); diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index 8d8fcf8a791..9bca1a308f6 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -18,6 +18,8 @@ #include "qgsvectordataprovider.h" #include "qgsfields.h" #include "qgsvectorlayerutils.h" +#include "qgsvectorlayerjoinbuffer.h" +#include "qgsvectorlayerjoininfo.h" #include @@ -118,9 +120,14 @@ void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult cons } void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldConstraints::ConstraintOrigin constraintOrigin ) +{ + updateConstraint( layer(), mFieldIdx, ft, constraintOrigin ); +} + +void QgsEditorWidgetWrapper::updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &ft, QgsFieldConstraints::ConstraintOrigin constraintOrigin ) { bool toEmit( false ); - QgsField field = layer()->fields().at( mFieldIdx ); + QgsField field = layer->fields().at( index ); QString expression = field.constraints().constraintExpression(); QStringList expressions, descriptions; @@ -161,10 +168,10 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldCon } QStringList errors; - bool hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin ); + bool hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin ); QStringList softErrors; - bool softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin ); + bool softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin ); errors << softErrors; mValidConstraint = hardConstraintsOk && softConstraintsOk; diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index e77b0d9ef64..1365706f4a7 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -138,6 +138,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper */ void updateConstraint( const QgsFeature &featureContext, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet ); + /** + * Update constraint on a feature coming from a specific layer. + * \param layer The vector layer where the feature is defined + * \param index The index of the field to check + * \param feature The feature to use to evaluate the constraint + * \param constraintOrigin Optional origin for constraints to check. This + * can be used to limit the constraints tested to only provider or layer + * based constraints. + * \since QGIS 3.0 + */ + void updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &feature, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet ); + /** * Get the current constraint status. * \returns true if the constraint is valid or if there's no constraint, diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index b53c7167259..1f4e3aa5271 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -720,17 +720,15 @@ void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww ) // to test, but they are unlikely to have any control over provider-side constraints // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint // and there's no point rechecking! - QgsFieldConstraints::ConstraintOrigin constraintOrigin = mLayer->isEditable() ? QgsFieldConstraints::ConstraintOriginNotSet - : QgsFieldConstraints::ConstraintOriginLayer; // update eww constraint - eww->updateConstraint( ft, constraintOrigin ); + updateConstraint( ft, eww ); // update eww dependencies constraint QList deps = constraintDependencies( eww ); Q_FOREACH ( QgsEditorWidgetWrapper *depsEww, deps ) - depsEww->updateConstraint( ft, constraintOrigin ); + updateConstraint( ft, depsEww ); // sync OK button status synchronizeEnabledState(); @@ -745,6 +743,26 @@ void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww ) } } +void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww ) +{ + QgsFieldConstraints::ConstraintOrigin constraintOrigin = mLayer->isEditable() ? QgsFieldConstraints::ConstraintOriginNotSet : QgsFieldConstraints::ConstraintOriginLayer; + + if ( eww->layer()->fields().fieldOrigin( QgsFields::OriginJoin ) ) + { + int srcFieldIdx; + const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx ); + + if ( info && info->joinLayer() && mJoinedFeatures.contains( info ) && info->isDynamicFormEnabled() ) + { + eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin ); + return; + } + } + + // default constraint update + eww->updateConstraint( ft, constraintOrigin ); +} + bool QgsAttributeForm::currentFormFeature( QgsFeature &feature ) { bool rc = true; @@ -765,7 +783,7 @@ bool QgsAttributeForm::currentFormFeature( QgsFeature &feature ) QVariant srcVar = eww->value(); // need to check dstVar.isNull() != srcVar.isNull() // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar - if ( ( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() && !mLayer->editFormConfig().readOnly( eww->fieldIdx() ) ) + if ( ( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() ) dst[eww->fieldIdx()] = srcVar; } else @@ -1954,6 +1972,9 @@ void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww ) QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature ); + if ( joinFeature.isValid() ) + mJoinedFeatures[info] = joinFeature; + QStringList *subsetFields = info->joinFieldNamesSubset(); if ( subsetFields ) { diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index ab019952300..69000a4df0b 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -318,6 +318,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget //! constraints management void updateAllConstraints(); void updateConstraints( QgsEditorWidgetWrapper *w ); + void updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww ); bool currentFormFeature( QgsFeature &feature ); bool currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ); QList constraintDependencies( QgsEditorWidgetWrapper *w ); @@ -340,6 +341,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget QList mInterfaces; QMap< int, QgsAttributeFormEditorWidget * > mFormEditorWidgets; QgsExpressionContext mExpressionContext; + QMap mJoinedFeatures; struct ContainerInformation { From 0dca126c55e74235fa5a9da30b3d81710fc3e5ed Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 10 Jul 2017 06:14:52 +0100 Subject: [PATCH 151/266] Update sip binding --- .../editorwidgets/core/qgseditorwidgetwrapper.sip | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip index c30b487e1c1..4a405bd8d8f 100644 --- a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip +++ b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip @@ -125,6 +125,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper \param constraintOrigin optional origin for constraints to check. This can be used to limit the constraints tested to only provider or layer based constraints. .. versionadded:: 2.16 +%End + + void updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &feature, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet ); +%Docstring + Update constraint on a feature coming from a specific layer. + \param layer The vector layer where the feature is defined + \param index The index of the field to check + \param feature The feature to use to evaluate the constraint + \param constraintOrigin Optional origin for constraints to check. This + can be used to limit the constraints tested to only provider or layer + based constraints. +.. versionadded:: 3.0 %End bool isValidConstraint() const; From 94034c52aea28f108a6a8363a8cb94568057e7a3 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 10 Jul 2017 06:15:13 +0100 Subject: [PATCH 152/266] Add tests --- tests/src/gui/testqgsattributeform.cpp | 71 ++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp index ddbc939ca25..7a634e96f00 100644 --- a/tests/src/gui/testqgsattributeform.cpp +++ b/tests/src/gui/testqgsattributeform.cpp @@ -43,6 +43,7 @@ class TestQgsAttributeForm : public QObject void testFieldMultiConstraints(); void testOKButtonStatus(); void testDynamicForm(); + void testConstraintsOnJoinedFields(); }; void TestQgsAttributeForm::initTestCase() @@ -440,5 +441,75 @@ void TestQgsAttributeForm::testDynamicForm() delete layerC; } +void TestQgsAttributeForm::testConstraintsOnJoinedFields() +{ + QString validLabel = QStringLiteral( "col0✔" ); + QString warningLabel = QStringLiteral( "col0✘" ); + + // make temporary layers + QString defA = QStringLiteral( "Point?field=id_a:integer" ); + QgsVectorLayer *layerA = new QgsVectorLayer( defA, QStringLiteral( "layerA" ), QStringLiteral( "memory" ) ); + + QString defB = QStringLiteral( "Point?field=id_b:integer&field=col0:integer" ); + QgsVectorLayer *layerB = new QgsVectorLayer( defB, QStringLiteral( "layerB" ), QStringLiteral( "memory" ) ); + + // set constraints on joined layer + layerB->setConstraintExpression( 1, QStringLiteral( "col0 < 10" ) ); + layerB->setFieldConstraint( 1, QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthSoft ); + + // join configuration + QgsVectorLayerJoinInfo infoJoinAB; + infoJoinAB.setTargetFieldName( "id_a" ); + infoJoinAB.setJoinLayer( layerB ); + infoJoinAB.setJoinFieldName( "id_b" ); + infoJoinAB.setDynamicFormEnabled( true ); + + layerA->addJoin( infoJoinAB ); + + // add features for main layer + QgsFeature ftA( layerA->fields() ); + ftA.setAttribute( QStringLiteral( "id_a" ), 1 ); + layerA->startEditing(); + layerA->addFeature( ftA ); + layerA->commitChanges(); + + // add features for joined layer + QgsFeature ft0B( layerB->fields() ); + ft0B.setAttribute( QStringLiteral( "id_b" ), 30 ); + ft0B.setAttribute( QStringLiteral( "col0" ), 9 ); + layerB->startEditing(); + layerB->addFeature( ft0B ); + layerB->commitChanges(); + + QgsFeature ft1B( layerB->fields() ); + ft1B.setAttribute( QStringLiteral( "id_b" ), 31 ); + ft1B.setAttribute( QStringLiteral( "col0" ), 11 ); + layerB->startEditing(); + layerB->addFeature( ft1B ); + layerB->commitChanges(); + + // build a form for this feature + QgsAttributeForm form( layerA ); + form.setMode( QgsAttributeForm::AddFeatureMode ); + form.setFeature( ftA ); + + // change layerA join id field + form.changeAttribute( "id_a", QVariant( 30 ) ); + + // compare + QgsEditorWidgetWrapper *ww = nullptr; + ww = qobject_cast( form.mWidgets[1] ); + QLabel *label = form.mBuddyMap.value( ww->widget() ); + QCOMPARE( label->text(), "layerB_" + validLabel ); + + // change layerA join id field + form.changeAttribute( "id_a", QVariant( 31 ) ); + + // compare + ww = qobject_cast( form.mWidgets[1] ); + label = form.mBuddyMap.value( ww->widget() ); + QCOMPARE( label->text(), "layerB_" + warningLabel ); +} + QGSTEST_MAIN( TestQgsAttributeForm ) #include "testqgsattributeform.moc" From 588fe49aee5f7d3aa637f2d15a6d9196e7f503b4 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 11 Jul 2017 13:44:02 +0100 Subject: [PATCH 153/266] Constraints fail on non existent joined fields --- .../core/qgseditorwidgetwrapper.cpp | 108 ++++++++++-------- src/gui/qgsattributeform.cpp | 19 ++- 2 files changed, 76 insertions(+), 51 deletions(-) diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index 9bca1a308f6..38764515f19 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -126,53 +126,71 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldCon void QgsEditorWidgetWrapper::updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &ft, QgsFieldConstraints::ConstraintOrigin constraintOrigin ) { - bool toEmit( false ); - QgsField field = layer->fields().at( index ); - - QString expression = field.constraints().constraintExpression(); - QStringList expressions, descriptions; - - if ( ! expression.isEmpty() ) - { - expressions << expression; - descriptions << field.constraints().constraintDescription(); - toEmit = true; - } - - if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintNotNull ) - { - descriptions << tr( "Not NULL" ); - if ( !expression.isEmpty() ) - { - expressions << field.name() + QStringLiteral( " IS NOT NULL" ); - } - else - { - expressions << QStringLiteral( "IS NOT NULL" ); - } - toEmit = true; - } - - if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintUnique ) - { - descriptions << tr( "Unique" ); - if ( !expression.isEmpty() ) - { - expressions << field.name() + QStringLiteral( " IS UNIQUE" ); - } - else - { - expressions << QStringLiteral( "IS UNIQUE" ); - } - toEmit = true; - } - QStringList errors; - bool hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin ); - QStringList softErrors; - bool softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin ); - errors << softErrors; + QStringList expressions; + QStringList descriptions; + bool toEmit( false ); + bool hardConstraintsOk( true ); + bool softConstraintsOk( true ); + + QgsField field = layer->fields().at( index ); + QString expression = field.constraints().constraintExpression(); + + if ( ft.isValid() ) + { + if ( ! expression.isEmpty() ) + { + expressions << expression; + descriptions << field.constraints().constraintDescription(); + toEmit = true; + } + + if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintNotNull ) + { + descriptions << tr( "Not NULL" ); + if ( !expression.isEmpty() ) + { + expressions << field.name() + QStringLiteral( " IS NOT NULL" ); + } + else + { + expressions << QStringLiteral( "IS NOT NULL" ); + } + toEmit = true; + } + + if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintUnique ) + { + descriptions << tr( "Unique" ); + if ( !expression.isEmpty() ) + { + expressions << field.name() + QStringLiteral( " IS UNIQUE" ); + } + else + { + expressions << QStringLiteral( "IS UNIQUE" ); + } + toEmit = true; + } + + hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin ); + + softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin ); + errors << softErrors; + } + else // invalid feature + { + if ( ! expression.isEmpty() ) + { + hardConstraintsOk = true; + softConstraintsOk = false; + + errors << "Invalid feature"; + + toEmit = true; + } + } mValidConstraint = hardConstraintsOk && softConstraintsOk; mIsBlockingCommit = !hardConstraintsOk; diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 1f4e3aa5271..ba107f091e4 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -747,15 +747,23 @@ void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWr { QgsFieldConstraints::ConstraintOrigin constraintOrigin = mLayer->isEditable() ? QgsFieldConstraints::ConstraintOriginNotSet : QgsFieldConstraints::ConstraintOriginLayer; - if ( eww->layer()->fields().fieldOrigin( QgsFields::OriginJoin ) ) + if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin ) { int srcFieldIdx; const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx ); - if ( info && info->joinLayer() && mJoinedFeatures.contains( info ) && info->isDynamicFormEnabled() ) + if ( info && info->joinLayer() && info->isDynamicFormEnabled() ) { - eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin ); - return; + if ( mJoinedFeatures.contains( info ) ) + { + eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin ); + return; + } + else // if we are here, it means there's not joined field for this feature + { + eww->updateConstraint( QgsFeature() ); + return; + } } } @@ -1972,8 +1980,7 @@ void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww ) QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature ); - if ( joinFeature.isValid() ) - mJoinedFeatures[info] = joinFeature; + mJoinedFeatures[info] = joinFeature; QStringList *subsetFields = info->joinFieldNamesSubset(); if ( subsetFields ) From ecd2c650ca0220828ec8dd08b240af9b3af7ddc2 Mon Sep 17 00:00:00 2001 From: Giovanni Manghi Date: Mon, 24 Jul 2017 14:56:41 +0100 Subject: [PATCH 154/266] master: fix SAGA cluster analysis for rasters --- .../plugins/processing/algs/saga/SagaNameDecorator.py | 2 +- .../algs/saga/description/ClusterAnalysisforGrids.txt | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/python/plugins/processing/algs/saga/SagaNameDecorator.py b/python/plugins/processing/algs/saga/SagaNameDecorator.py index 866fd52c9eb..6f292c7c010 100644 --- a/python/plugins/processing/algs/saga/SagaNameDecorator.py +++ b/python/plugins/processing/algs/saga/SagaNameDecorator.py @@ -87,7 +87,7 @@ algorithms = {'Add Grid Values to Points': 'Add raster values to points', 'Add Grid Values to Shapes': 'Add raster values to features', 'Change Grid Values': 'Reclassify values (simple)', 'Clip Grid with Polygon': 'Clip raster with polygon', - 'Cluster Analysis for Grids': 'Cluster Analysis', + 'K-Means Clustering for Grids': 'Cluster Analysis', 'Contour Lines from Grid': 'Contour Lines', 'Cubic Spline Approximation': 'Interpolate (Cubic spline)', 'Cut Shapes Layer': 'Cut vector Layer', diff --git a/python/plugins/processing/algs/saga/description/ClusterAnalysisforGrids.txt b/python/plugins/processing/algs/saga/description/ClusterAnalysisforGrids.txt index 0c477e9e46e..6e891c96518 100644 --- a/python/plugins/processing/algs/saga/description/ClusterAnalysisforGrids.txt +++ b/python/plugins/processing/algs/saga/description/ClusterAnalysisforGrids.txt @@ -1,9 +1,11 @@ -Cluster Analysis for Grids +K-Means Clustering for Grids imagery_classification ParameterMultipleInput|GRIDS|Grids|3.0|False ParameterSelection|METHOD|Method|[0] Iterative Minimum Distance (Forgy 1965);[1] Hill-Climbing (Rubin 1967);[2] Combined Minimum Distance / Hillclimbing -ParameterNumber|NCLUSTER|Clusters|None|None|5 -ParameterBoolean|NORMALISE |Normalise|True -ParameterBoolean|OLDVERSION |Old Version|True +ParameterNumber|NCLUSTER|Clusters|2|None|10 +ParameterNumber|MAXITER|Maximum Iterations|0|None|0 +ParameterBoolean|NORMALISE|Normalise|True +ParameterBoolean|OLDVERSION|Old Version|False +ParameterBoolean|UPDATEVIEW|Update View|False OutputRaster|CLUSTER|Clusters OutputTable|STATISTICS|Statistics From cb088a2a0cb6224600504574f749348cc4df8bf2 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Mon, 24 Jul 2017 22:26:14 +0200 Subject: [PATCH 155/266] vector layer save as: adjust our to OGR's defaults * GPX_USE_EXTENSIONS default is false not true (fixes #16811) * CSV GEOMETRY is none not AS_XY (fixes #16819) (followup 9a6235db) --- src/core/qgsvectorfilewriter.cpp | 135 ++++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 27 deletions(-) diff --git a/src/core/qgsvectorfilewriter.cpp b/src/core/qgsvectorfilewriter.cpp index 3f97bdb9319..3c73d6ee365 100644 --- a/src/core/qgsvectorfilewriter.cpp +++ b/src/core/qgsvectorfilewriter.cpp @@ -764,7 +764,7 @@ QMap QgsVectorFileWriter::initMetaData() << QStringLiteral( "AS_XYZ" ) << QStringLiteral( "AS_XY" ) << QStringLiteral( "AS_YX" ), - QStringLiteral( "AS_XY" ), // Default value + QStringLiteral( "" ), // Default value true // Allow None ) ); @@ -807,9 +807,21 @@ QMap QgsVectorFileWriter::initMetaData() QObject::tr( "Override the type of shapefile created. " "Can be one of NULL for a simple .dbf file with no .shp file, POINT, " "ARC, POLYGON or MULTIPOINT for 2D, or POINTZ, ARCZ, POLYGONZ or " - "MULTIPOINTZ for 3D. Shapefiles with measure values are not supported, " - "nor are MULTIPATCH files." ), - QStringList() + "MULTIPOINTZ for 3D;" ) + +#if defined(GDAL_COMPUTE_VERSION) && GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,1,0) + QObject::tr( " Shapefiles with measure values are not supported," + " nor are MULTIPATCH files." ) + +#endif +#if defined(GDAL_COMPUTE_VERSION) && GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,1,0) + QObject::tr( " POINTM, ARCM, POLYGONM or MULTIPOINTM for measured geometries" + " and POINTZM, ARCZM, POLYGONZM or MULTIPOINTZM for 3D measured" + " geometries." ) + +#endif +#if defined(GDAL_COMPUTE_VERSION) && GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,2,0) + QObject::tr( " MULTIPATCH files are supported since GDAL 2.2." ) + +#endif + "" + , QStringList() << QStringLiteral( "NULL" ) << QStringLiteral( "POINT" ) << QStringLiteral( "ARC" ) @@ -818,7 +830,21 @@ QMap QgsVectorFileWriter::initMetaData() << QStringLiteral( "POINTZ" ) << QStringLiteral( "ARCZ" ) << QStringLiteral( "POLYGONZ" ) - << QStringLiteral( "MULTIPOINTZ" ), + << QStringLiteral( "MULTIPOINTZ" ) +#if defined(GDAL_COMPUTE_VERSION) && GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,1,0) + << QStringLiteral( "POINTM" ) + << QStringLiteral( "ARCM" ) + << QStringLiteral( "POLYGONM" ) + << QStringLiteral( "MULTIPOINTM" ) + << QStringLiteral( "POINTZM" ) + << QStringLiteral( "ARCZM" ) + << QStringLiteral( "POLYGONZM" ) + << QStringLiteral( "MULTIPOINTZM" ) +#endif +#if defined(GDAL_COMPUTE_VERSION) && GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,2,0) + << QStringLiteral( "MULTIPATCH" ) +#endif + << QStringLiteral( "" ), QString(), // Default value true // Allow None ) ); @@ -940,7 +966,7 @@ QMap QgsVectorFileWriter::initMetaData() "the foo_bar pattern, foo will be considered as the namespace " "of the element, and a element will be written. " "Otherwise, elements will be written in the namespace." ), - true // Default value + false // Default value ) ); datasetOptions.insert( QStringLiteral( "WRITE_HEADER_AND_FOOTER" ), new BoolOption( @@ -1116,7 +1142,7 @@ QMap QgsVectorFileWriter::initMetaData() layerOptions.insert( QStringLiteral( "GEOMETRY_NAME" ), new StringOption( QObject::tr( "Name for the geometry column" ), - QStringLiteral( "geometry" ) // Default value + QStringLiteral( "geom" ) // Default value ) ); layerOptions.insert( "SPATIAL_INDEX", new BoolOption( @@ -1175,7 +1201,7 @@ QMap QgsVectorFileWriter::initMetaData() datasetOptions.insert( QStringLiteral( "GPX_USE_EXTENSIONS" ), new BoolOption( QObject::tr( "If GPX_USE_EXTENSIONS=YES is specified, " "extra fields will be written inside the tag." ), - true // Default value + false // Default value ) ); datasetOptions.insert( QStringLiteral( "GPX_EXTENSIONS_NS" ), new StringOption( @@ -1266,9 +1292,17 @@ QMap QgsVectorFileWriter::initMetaData() << QStringLiteral( "clampToGround" ) << QStringLiteral( "relativeToGround" ) << QStringLiteral( "absolute" ), - QStringLiteral( "clampToGround" ) // Default value + QStringLiteral( "relativeToGround" ) // Default value ) ); +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,2,0) + datasetOptions.insert( QStringLiteral( "DOCUMENT_ID" ), new StringOption( + QObject::tr( "The DOCUMENT_ID datasource creation option can be used to specified " + "the id of the root node. The default value is root_doc." ), + QStringLiteral( "root_doc" ) // Default value + ) ); +#endif + driverMetadata.insert( QStringLiteral( "KML" ), MetaData( QStringLiteral( "Keyhole Markup Language [KML]" ), @@ -1285,15 +1319,34 @@ QMap QgsVectorFileWriter::initMetaData() datasetOptions.clear(); layerOptions.clear(); - layerOptions.insert( QStringLiteral( "SPATIAL_INDEX_MODE" ), new SetOption( - QObject::tr( "Use this to turn on 'quick spatial index mode'. " - "In this mode writing files can be about 5 times faster, " - "but spatial queries can be up to 30 times slower." ), - QStringList() - << QStringLiteral( "QUICK" ), - QLatin1String( "" ), // Default value - true // Allow None + datasetOptions.insert( QStringLiteral( "SPATIAL_INDEX_MODE" ), new SetOption( + QObject::tr( "Use this to turn on 'quick spatial index mode'. " + "In this mode writing files can be about 5 times faster, " + "but spatial queries can be up to 30 times slower." ), + QStringList() + << QStringLiteral( "QUICK" ) + << QStringLiteral( "OPTIMIZED" ), + QLatin1String( "QUICK" ), // Default value + true // Allow None + ) ); + +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,0,2) + datasetOptions.insert( QStringLiteral( "BLOCK_SIZE" ), new IntOption( + QObject::tr( "(multiples of 512): Block size for .map files. Defaults " + "to 512. MapInfo 15.2 and above creates .tab files with a " + "blocksize of 16384 bytes. Any MapInfo version should be " + "able to handle block sizes from 512 to 32256." ), + 512 + ) ); +#endif +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,0,0) + layerOptions.insert( QStringLiteral( "BOUNDS" ), new StringOption( + QObject::tr( "xmin,ymin,xmax,ymax: Define custom layer bounds to increase the " + "accuracy of the coordinates. Note: the geometry of written " + "features must be within the defined box." ), + QLatin1String( "" ) // Default value ) ); +#endif driverMetadata.insert( QStringLiteral( "MapInfo File" ), MetaData( @@ -1415,7 +1468,7 @@ QMap QgsVectorFileWriter::initMetaData() datasetOptions.insert( QStringLiteral( "RETURN_PRIMITIVES" ), new BoolOption( QObject::tr( "Should all the low level geometry primitives be returned as special " "IsolatedNode, ConnectedNode, Edge and Face layers." ), - true // Default value + false // Default value ) ); datasetOptions.insert( QStringLiteral( "PRESERVE_EMPTY_NUMBERS" ), new BoolOption( @@ -1435,7 +1488,7 @@ QMap QgsVectorFileWriter::initMetaData() QObject::tr( "Should additional attributes relating features to their underlying " "geometric primitives be attached. These are the values of the FSPT group, " "and are primarily needed when doing S-57 to S-57 translations." ), - true // Default value + false // Default value ) ); datasetOptions.insert( QStringLiteral( "RECODE_BY_DSSI" ), new BoolOption( @@ -1632,17 +1685,15 @@ QMap QgsVectorFileWriter::initMetaData() datasetOptions.clear(); layerOptions.clear(); -#if 0 datasetOptions.insert( "HEADER", new StringOption( QObject::tr( "Override the header file used - in place of header.dxf." ), - "" // Default value + QLatin1String( "" ) // Default value ) ); datasetOptions.insert( "TRAILER", new StringOption( QObject::tr( "Override the trailer file used - in place of trailer.dxf." ), - "" // Default value + QLatin1String( "" ) // Default value ) ); -#endif driverMetadata.insert( QStringLiteral( "DXF" ), MetaData( @@ -1668,14 +1719,20 @@ QMap QgsVectorFileWriter::initMetaData() QStringLiteral( "GXT" ) // Default value ) ); -#if 0 datasetOptions.insert( "CONFIG", new StringOption( QObject::tr( "path to the GCT : the GCT file describe the GeoConcept types definitions: " "In this file, every line must start with //# followed by a keyword. " "Lines starting with // are comments." ), - "" // Default value + QLatin1String( "" ) // Default value + ) ); + + datasetOptions.insert( "FEATURETYPE", new StringOption( + QObject::tr( "defines the feature to be created. The TYPE corresponds to one of the Name " + "found in the GCT file for a type section. The SUBTYPE corresponds to one of " + "the Name found in the GCT file for a sub-type section within the previous " + "type section." ), + QLatin1String( "" ) // Default value ) ); -#endif driverMetadata.insert( QStringLiteral( "Geoconcept" ), MetaData( @@ -1703,7 +1760,7 @@ QMap QgsVectorFileWriter::initMetaData() QStringLiteral( "SHAPE" ) // Default value ) ); - layerOptions.insert( QStringLiteral( "OID_NAME" ), new StringOption( + layerOptions.insert( QStringLiteral( "FID" ), new StringOption( QObject::tr( "Name of the OID column to create. Defaults to 'OBJECTID'." ), QStringLiteral( "OBJECTID" ) // Default value ) ); @@ -1734,6 +1791,18 @@ QMap QgsVectorFileWriter::initMetaData() false // Allow None ) ); + layerOptions.insert( QStringLiteral( "OGR_XLSX_HEADERS" ), new SetOption( + QObject::tr( "By default, the driver will read the first lines of each sheet to detect " + "if the first line might be the name of columns. If set to FORCE, the " + "driver will consider the first default" ), + QStringList() + << QStringLiteral( "FORCE" ) + << QStringLiteral( "DISABLE" ) + << QStringLiteral( "AUTO" ), + QStringLiteral( "AUTO" ), // Default value + false // Allow None + ) ); + driverMetadata.insert( QStringLiteral( "XLSX" ), MetaData( QStringLiteral( "MS Office Open XML spreadsheet" ), @@ -1760,6 +1829,18 @@ QMap QgsVectorFileWriter::initMetaData() false // Allow None ) ); + layerOptions.insert( QStringLiteral( "OGR_ODS_HEADERS" ), new SetOption( + QObject::tr( "By default, the driver will read the first lines of each sheet to detect " + "if the first line might be the name of columns. If set to FORCE, the " + "driver will consider the first default" ), + QStringList() + << QStringLiteral( "FORCE" ) + << QStringLiteral( "DISABLE" ) + << QStringLiteral( "AUTO" ), + QStringLiteral( "AUTO" ), // Default value + false // Allow None + ) ); + driverMetadata.insert( QStringLiteral( "ODS" ), MetaData( QStringLiteral( "Open Document Spreadsheet" ), From 778e84ba7b1065a430d7d7ee1c0ce7bb62f6c134 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Tue, 25 Jul 2017 08:50:03 +0700 Subject: [PATCH 156/266] [FEATURE] Add copy to clipboard function to save as image (#4914) Sponsored by Andreas Neumann. --- python/core/qgsmaprenderertask.sip | 2 +- src/app/qgsmapsavedialog.cpp | 67 +++++++++++++++++++++++++++++- src/app/qgsmapsavedialog.h | 1 + src/core/qgsmaprenderertask.h | 2 +- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/python/core/qgsmaprenderertask.sip b/python/core/qgsmaprenderertask.sip index 26429a2b048..03f093e08a5 100644 --- a/python/core/qgsmaprenderertask.sip +++ b/python/core/qgsmaprenderertask.sip @@ -40,7 +40,7 @@ class QgsMapRendererTask : QgsTask QgsMapRendererTask( const QgsMapSettings &ms, QPainter *p ); %Docstring - Constructor for QgsMapRendererTask to render a map to a painter object. + Constructor for QgsMapRendererTask to render a map to a QPainter object. %End void addAnnotations( QList< QgsAnnotation * > annotations ); diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index dde9607fb43..982cee49421 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -29,6 +29,7 @@ #include "qgsproject.h" #include "qgssettings.h" +#include #include #include #include @@ -110,6 +111,12 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QL this->setWindowTitle( tr( "Save map as PDF" ) ); } + else + { + QPushButton *button = new QPushButton( tr( "Copy to clipboard" ) ); + buttonBox->addButton( button, QDialogButtonBox::ResetRole ); + connect( button, &QPushButton::clicked, this, &QgsMapSaveDialog::copyToClipboard ); + } connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsMapSaveDialog::accepted ); } @@ -309,6 +316,64 @@ void QgsMapSaveDialog::lockChanged( const bool locked ) } } +void QgsMapSaveDialog::copyToClipboard() +{ + QgsMapSettings ms = QgsMapSettings(); + applyMapSettings( ms ); + + QPainter *p; + QImage *img; + + img = new QImage( ms.outputSize(), QImage::Format_ARGB32 ); + if ( img->isNull() ) + { + QgisApp::instance()->messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) ); + return; + } + + img->setDotsPerMeterX( 1000 * ms.outputDpi() / 25.4 ); + img->setDotsPerMeterY( 1000 * ms.outputDpi() / 25.4 ); + + p = new QPainter( img ); + + QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, p ); + + if ( drawAnnotations() ) + { + mapRendererTask->addAnnotations( mAnnotations ); + } + + if ( drawDecorations() ) + { + mapRendererTask->addDecorations( mDecorations ); + } + + connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, [ = ] + { + QApplication::clipboard()->setImage( *img, QClipboard::Clipboard ); + QApplication::restoreOverrideCursor(); + QgisApp::instance()->messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully copied map to clipboard" ) ); + + delete p; + delete img; + setEnabled( true ); + } ); + connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, [ = ]( int ) + { + QApplication::restoreOverrideCursor(); + QgisApp::instance()->messageBar()->pushWarning( tr( "Save as PDF" ), tr( "Could not copy the map to clipboard" ) ); + + delete p; + delete img; + setEnabled( true ); + } ); + + setEnabled( false ); + + QApplication::setOverrideCursor( Qt::WaitCursor ); + QgsApplication::taskManager()->addTask( mapRendererTask ); +} + void QgsMapSaveDialog::accepted() { if ( mDialogType == Image ) @@ -387,7 +452,7 @@ void QgsMapSaveDialog::accepted() } ); connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, [ = ]( int ) { - QgisApp::instance()->messageBar()->pushWarning( tr( "Save as PDF" ), tr( "Could not save the map to PDF..." ) ); + QgisApp::instance()->messageBar()->pushWarning( tr( "Save as PDF" ), tr( "Could not save the map to PDF" ) ); } ); QgsApplication::taskManager()->addTask( mapRendererTask ); diff --git a/src/app/qgsmapsavedialog.h b/src/app/qgsmapsavedialog.h index e2518315b78..418dc052a9c 100644 --- a/src/app/qgsmapsavedialog.h +++ b/src/app/qgsmapsavedialog.h @@ -79,6 +79,7 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog void lockChanged( const bool locked ); void accepted(); + void copyToClipboard(); void updateDpi( int dpi ); void updateOutputWidth( int width ); diff --git a/src/core/qgsmaprenderertask.h b/src/core/qgsmaprenderertask.h index 4484a84a3ce..3ba52c4ef72 100644 --- a/src/core/qgsmaprenderertask.h +++ b/src/core/qgsmaprenderertask.h @@ -59,7 +59,7 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask const bool forceRaster = false ); /** - * Constructor for QgsMapRendererTask to render a map to a painter object. + * Constructor for QgsMapRendererTask to render a map to a QPainter object. */ QgsMapRendererTask( const QgsMapSettings &ms, QPainter *p ); From 53c6308d05555e83e43491117801e0bf3b2ef0c1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 12:06:55 +1000 Subject: [PATCH 157/266] Use QgsMenuHeaderWidgetAction instead of QMenu::addSection Because addSection adds really ugly separators and throws out the menu alignment --- src/app/qgisapp.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index f8f4a529e02..8dfae20a369 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -204,6 +204,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsmapoverviewcanvas.h" #include "qgsmapsettings.h" #include "qgsmaptip.h" +#include "qgsmenuheader.h" #include "qgsmergeattributesdialog.h" #include "qgsmessageviewer.h" #include "qgsmessagebar.h" @@ -2304,11 +2305,13 @@ void QgisApp::refreshProfileMenu() QString activeName = profile->name(); mConfigMenu->setTitle( tr( "&User Profiles" ) ); - mConfigMenu->addSection( tr( "Active Profile" ) ); + mConfigMenu->addAction( new QgsMenuHeaderWidgetAction( tr( "Active Profile" ), mConfigMenu ) ); - QAction *profileSection = mConfigMenu->addSection( tr( "Profiles" ) ); + mConfigMenu->addAction( new QgsMenuHeaderWidgetAction( tr( "Profiles" ), mConfigMenu ) ); + QAction *profileSection = mConfigMenu->actions().at( 1 ); - QAction *configSection = mConfigMenu->addSection( tr( "Config" ) ); + mConfigMenu->addAction( new QgsMenuHeaderWidgetAction( tr( "Config" ), mConfigMenu ) ); + QAction *configSection = mConfigMenu->actions().at( 2 ); QAction *openProfileFolderAction = mConfigMenu->addAction( tr( "Open current profile folder" ) ); connect( openProfileFolderAction, &QAction::triggered, this, [this]() From ca4c34d219c1282fc818bb2e5869094aff3fcfa8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 12:28:46 +1000 Subject: [PATCH 158/266] Fix menu header appearance on hidpi displays --- src/gui/qgscolorswatchgrid.cpp | 17 +++++++++-------- src/gui/qgscolorswatchgrid.h | 4 ++++ src/gui/qgsmenuheader.cpp | 15 +++++++-------- src/gui/qgsmenuheader.h | 2 ++ 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/gui/qgscolorswatchgrid.cpp b/src/gui/qgscolorswatchgrid.cpp index 4a79592754c..94da0af69f5 100644 --- a/src/gui/qgscolorswatchgrid.cpp +++ b/src/gui/qgscolorswatchgrid.cpp @@ -27,8 +27,6 @@ #define RIGHT_MARGIN 6 //margin between right edge and last swatch #define TOP_MARGIN 6 //margin between label and first swatch #define BOTTOM_MARGIN 6 //margin between last swatch row and end of widget -#define LABEL_SIZE 20 //label rect height -#define LABEL_MARGIN 4 //spacing between label box and text QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme *scheme, const QString &context, QWidget *parent ) : QWidget( parent ) @@ -46,6 +44,9 @@ QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme *scheme, const QString &c setFocusPolicy( Qt::StrongFocus ); setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + mLabelHeight = fontMetrics().height(); + mLabelMargin = fontMetrics().width( QStringLiteral( "." ) ); + //calculate widget width mWidth = NUMBER_COLORS_PER_ROW * SWATCH_SIZE + ( NUMBER_COLORS_PER_ROW - 1 ) * SWATCH_SPACING + LEFT_MARGIN + RIGHT_MARGIN; @@ -240,7 +241,7 @@ void QgsColorSwatchGrid::focusOutEvent( QFocusEvent *event ) int QgsColorSwatchGrid::calculateHeight() const { int numberRows = ceil( ( double )mColors.length() / NUMBER_COLORS_PER_ROW ); - return numberRows * ( SWATCH_SIZE ) + ( numberRows - 1 ) * SWATCH_SPACING + TOP_MARGIN + LABEL_SIZE + BOTTOM_MARGIN; + return numberRows * ( SWATCH_SIZE ) + ( numberRows - 1 ) * SWATCH_SPACING + TOP_MARGIN + mLabelHeight + 0.5 * mLabelMargin + BOTTOM_MARGIN; } void QgsColorSwatchGrid::draw( QPainter &painter ) @@ -253,11 +254,11 @@ void QgsColorSwatchGrid::draw( QPainter &painter ) //draw header background painter.setBrush( headerBgColor ); painter.setPen( Qt::NoPen ); - painter.drawRect( QRect( 0, 0, width(), LABEL_SIZE ) ); + painter.drawRect( QRect( 0, 0, width(), mLabelHeight + 0.5 * mLabelMargin ) ); //draw header text painter.setPen( headerTextColor ); - painter.drawText( QRect( LABEL_MARGIN, 0, width() - 2 * LABEL_MARGIN, LABEL_SIZE ), + painter.drawText( QRect( mLabelMargin, 0.25 * mLabelMargin, width() - 2 * mLabelMargin, mLabelHeight ), Qt::AlignLeft | Qt::AlignVCenter, mScheme->schemeName() ); //draw color swatches @@ -269,7 +270,7 @@ void QgsColorSwatchGrid::draw( QPainter &painter ) int column = index % NUMBER_COLORS_PER_ROW; QRect swatchRect = QRect( column * ( SWATCH_SIZE + SWATCH_SPACING ) + LEFT_MARGIN, - row * ( SWATCH_SIZE + SWATCH_SPACING ) + TOP_MARGIN + LABEL_SIZE, + row * ( SWATCH_SIZE + SWATCH_SPACING ) + TOP_MARGIN + mLabelHeight + 0.5 * mLabelMargin, SWATCH_SIZE, SWATCH_SIZE ); if ( mCurrentHoverBox == index ) @@ -336,8 +337,8 @@ int QgsColorSwatchGrid::swatchForPosition( QPoint position ) const int box = -1; int column = ( position.x() - LEFT_MARGIN ) / ( SWATCH_SIZE + SWATCH_SPACING ); int xRem = ( position.x() - LEFT_MARGIN ) % ( SWATCH_SIZE + SWATCH_SPACING ); - int row = ( position.y() - TOP_MARGIN - LABEL_SIZE ) / ( SWATCH_SIZE + SWATCH_SPACING ); - int yRem = ( position.y() - TOP_MARGIN - LABEL_SIZE ) % ( SWATCH_SIZE + SWATCH_SPACING ); + int row = ( position.y() - TOP_MARGIN - mLabelHeight ) / ( SWATCH_SIZE + SWATCH_SPACING ); + int yRem = ( position.y() - TOP_MARGIN - mLabelHeight ) % ( SWATCH_SIZE + SWATCH_SPACING ); if ( xRem <= SWATCH_SIZE + 1 && yRem <= SWATCH_SIZE + 1 && column < NUMBER_COLORS_PER_ROW ) { diff --git a/src/gui/qgscolorswatchgrid.h b/src/gui/qgscolorswatchgrid.h index 135a20d1e76..4e4ae60a423 100644 --- a/src/gui/qgscolorswatchgrid.h +++ b/src/gui/qgscolorswatchgrid.h @@ -117,6 +117,10 @@ class GUI_EXPORT QgsColorSwatchGrid : public QWidget int mCurrentFocusBox; int mWidth; + //! Label rect height + int mLabelHeight = 0; + //! Spacing between label box and text + int mLabelMargin = 0; bool mPressedOnWidget; diff --git a/src/gui/qgsmenuheader.cpp b/src/gui/qgsmenuheader.cpp index e09f58f3917..c68ba4edd91 100644 --- a/src/gui/qgsmenuheader.cpp +++ b/src/gui/qgsmenuheader.cpp @@ -19,27 +19,26 @@ #include #include -#define LABEL_SIZE 20 //label rect height -#define LABEL_MARGIN 4 //spacing between label box and text - QgsMenuHeader::QgsMenuHeader( const QString &text, QWidget *parent ) : QWidget( parent ) , mText( text ) { int textMinWidth = fontMetrics().width( mText ); - mMinWidth = 2 * LABEL_MARGIN + textMinWidth; + mTextHeight = fontMetrics().height(); + mLabelMargin = fontMetrics().width( QStringLiteral( "." ) ); + mMinWidth = 2 * mLabelMargin + textMinWidth; setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ); updateGeometry(); } QSize QgsMenuHeader::minimumSizeHint() const { - return QSize( mMinWidth, LABEL_SIZE ); + return QSize( mMinWidth, mTextHeight + 0.5 * mLabelMargin ); } QSize QgsMenuHeader::sizeHint() const { - return QSize( mMinWidth, LABEL_SIZE ); + return QSize( mMinWidth, mTextHeight + 0.5 * mLabelMargin ); } void QgsMenuHeader::paintEvent( QPaintEvent * ) @@ -52,11 +51,11 @@ void QgsMenuHeader::paintEvent( QPaintEvent * ) //draw header background painter.setBrush( headerBgColor ); painter.setPen( Qt::NoPen ); - painter.drawRect( QRect( 0, 0, width(), LABEL_SIZE ) ); + painter.drawRect( QRect( 0, 0, width(), mTextHeight + 0.5 * mLabelMargin ) ); //draw header text painter.setPen( headerTextColor ); - painter.drawText( QRect( LABEL_MARGIN, 0, width() - 2 * LABEL_MARGIN, LABEL_SIZE ), + painter.drawText( QRect( mLabelMargin, 0.25 * mLabelMargin, width() - 2 * mLabelMargin, mTextHeight ), Qt::AlignLeft | Qt::AlignVCenter, mText ); painter.end(); } diff --git a/src/gui/qgsmenuheader.h b/src/gui/qgsmenuheader.h index 6935964bc54..402ea248741 100644 --- a/src/gui/qgsmenuheader.h +++ b/src/gui/qgsmenuheader.h @@ -49,6 +49,8 @@ class GUI_EXPORT QgsMenuHeader : public QWidget private: int mMinWidth = 0; QString mText; + int mTextHeight = 0; + int mLabelMargin = 0; }; From aacc316eed55e0b78fb4ee5bc3b0268e3e9b6f32 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 12:43:32 +1000 Subject: [PATCH 159/266] Fix size of color swatch grid in color buttons on hidpi displays --- src/gui/qgscolorswatchgrid.cpp | 40 +++++++++++++++++----------------- src/gui/qgscolorswatchgrid.h | 11 ++++++++++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/gui/qgscolorswatchgrid.cpp b/src/gui/qgscolorswatchgrid.cpp index 94da0af69f5..4bc9da7366b 100644 --- a/src/gui/qgscolorswatchgrid.cpp +++ b/src/gui/qgscolorswatchgrid.cpp @@ -21,12 +21,6 @@ #include #define NUMBER_COLORS_PER_ROW 10 //number of color swatches per row -#define SWATCH_SIZE 14 //width/height of color swatches -#define SWATCH_SPACING 4 //horizontal/vertical gap between swatches -#define LEFT_MARGIN 6 //margin between left edge and first swatch -#define RIGHT_MARGIN 6 //margin between right edge and last swatch -#define TOP_MARGIN 6 //margin between label and first swatch -#define BOTTOM_MARGIN 6 //margin between last swatch row and end of widget QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme *scheme, const QString &context, QWidget *parent ) : QWidget( parent ) @@ -47,8 +41,14 @@ QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme *scheme, const QString &c mLabelHeight = fontMetrics().height(); mLabelMargin = fontMetrics().width( QStringLiteral( "." ) ); + mSwatchSize = fontMetrics().width( QStringLiteral( "X" ) ) * 1.75; + mSwatchOutlineSize = qMax( mLabelMargin * 0.4, 1.0 ); + + mSwatchSpacing = mSwatchSize * 0.3; + mSwatchMargin = mLabelMargin * 1.5; + //calculate widget width - mWidth = NUMBER_COLORS_PER_ROW * SWATCH_SIZE + ( NUMBER_COLORS_PER_ROW - 1 ) * SWATCH_SPACING + LEFT_MARGIN + RIGHT_MARGIN; + mWidth = NUMBER_COLORS_PER_ROW * mSwatchSize + ( NUMBER_COLORS_PER_ROW - 1 ) * mSwatchSpacing + mSwatchMargin + mSwatchMargin; refreshColors(); } @@ -241,7 +241,7 @@ void QgsColorSwatchGrid::focusOutEvent( QFocusEvent *event ) int QgsColorSwatchGrid::calculateHeight() const { int numberRows = ceil( ( double )mColors.length() / NUMBER_COLORS_PER_ROW ); - return numberRows * ( SWATCH_SIZE ) + ( numberRows - 1 ) * SWATCH_SPACING + TOP_MARGIN + mLabelHeight + 0.5 * mLabelMargin + BOTTOM_MARGIN; + return numberRows * ( mSwatchSize ) + ( numberRows - 1 ) * mSwatchSpacing + mSwatchMargin + mLabelHeight + 0.5 * mLabelMargin + mSwatchMargin; } void QgsColorSwatchGrid::draw( QPainter &painter ) @@ -269,9 +269,9 @@ void QgsColorSwatchGrid::draw( QPainter &painter ) int row = index / NUMBER_COLORS_PER_ROW; int column = index % NUMBER_COLORS_PER_ROW; - QRect swatchRect = QRect( column * ( SWATCH_SIZE + SWATCH_SPACING ) + LEFT_MARGIN, - row * ( SWATCH_SIZE + SWATCH_SPACING ) + TOP_MARGIN + mLabelHeight + 0.5 * mLabelMargin, - SWATCH_SIZE, SWATCH_SIZE ); + QRect swatchRect = QRect( column * ( mSwatchSize + mSwatchSpacing ) + mSwatchMargin, + row * ( mSwatchSize + mSwatchSpacing ) + mSwatchMargin + mLabelHeight + 0.5 * mLabelMargin, + mSwatchSize, mSwatchSize ); if ( mCurrentHoverBox == index ) { @@ -292,12 +292,12 @@ void QgsColorSwatchGrid::draw( QPainter &painter ) { if ( mDrawBoxDepressed ) { - painter.setPen( QColor( 100, 100, 100 ) ); + painter.setPen( QPen( QColor( 100, 100, 100 ), mSwatchOutlineSize ) ); } else { //hover color - painter.setPen( QColor( 220, 220, 220 ) ); + painter.setPen( QPen( QColor( 220, 220, 220 ), mSwatchOutlineSize ) ); } } else if ( mFocused && index == mCurrentFocusBox ) @@ -307,11 +307,11 @@ void QgsColorSwatchGrid::draw( QPainter &painter ) else if ( ( *colorIt ).first.name() == mBaseColor.name() ) { //currently active color - painter.setPen( QColor( 75, 75, 75 ) ); + painter.setPen( QPen( QColor( 75, 75, 75 ), mSwatchOutlineSize ) ); } else { - painter.setPen( QColor( 197, 197, 197 ) ); + painter.setPen( QPen( QColor( 197, 197, 197 ), mSwatchOutlineSize ) ); } painter.setBrush( ( *colorIt ).first ); @@ -335,12 +335,12 @@ int QgsColorSwatchGrid::swatchForPosition( QPoint position ) const { //calculate box for position int box = -1; - int column = ( position.x() - LEFT_MARGIN ) / ( SWATCH_SIZE + SWATCH_SPACING ); - int xRem = ( position.x() - LEFT_MARGIN ) % ( SWATCH_SIZE + SWATCH_SPACING ); - int row = ( position.y() - TOP_MARGIN - mLabelHeight ) / ( SWATCH_SIZE + SWATCH_SPACING ); - int yRem = ( position.y() - TOP_MARGIN - mLabelHeight ) % ( SWATCH_SIZE + SWATCH_SPACING ); + int column = ( position.x() - mSwatchMargin ) / ( mSwatchSize + mSwatchSpacing ); + int xRem = ( position.x() - mSwatchMargin ) % ( mSwatchSize + mSwatchSpacing ); + int row = ( position.y() - mSwatchMargin - mLabelHeight ) / ( mSwatchSize + mSwatchSpacing ); + int yRem = ( position.y() - mSwatchMargin - mLabelHeight ) % ( mSwatchSize + mSwatchSpacing ); - if ( xRem <= SWATCH_SIZE + 1 && yRem <= SWATCH_SIZE + 1 && column < NUMBER_COLORS_PER_ROW ) + if ( xRem <= mSwatchSize + 1 && yRem <= mSwatchSize + 1 && column < NUMBER_COLORS_PER_ROW ) { //if pos is actually inside a valid box, calculate which box box = column + row * NUMBER_COLORS_PER_ROW; diff --git a/src/gui/qgscolorswatchgrid.h b/src/gui/qgscolorswatchgrid.h index 4e4ae60a423..e7ec563e5be 100644 --- a/src/gui/qgscolorswatchgrid.h +++ b/src/gui/qgscolorswatchgrid.h @@ -122,6 +122,17 @@ class GUI_EXPORT QgsColorSwatchGrid : public QWidget //! Spacing between label box and text int mLabelMargin = 0; + //! Width/height of color swatches + int mSwatchSize = 0; + //! Swatch outline size + int mSwatchOutlineSize = 0; + + //! Margins between edges of grid widget and swatches + int mSwatchMargin = 0; + + //! Horizontal/vertical gap between swatches + int mSwatchSpacing = 0; + bool mPressedOnWidget; /** Calculate height of widget based on number of colors From f121286e38e5180e47775c53921d6d8d5fae8b82 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 12:52:23 +1000 Subject: [PATCH 160/266] Fix size of some color widgets on hidpi displays --- src/gui/qgscolorwidgets.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/gui/qgscolorwidgets.cpp b/src/gui/qgscolorwidgets.cpp index 377afd79795..1b852d0c52f 100644 --- a/src/gui/qgscolorwidgets.cpp +++ b/src/gui/qgscolorwidgets.cpp @@ -397,7 +397,8 @@ QgsColorWheel::~QgsColorWheel() QSize QgsColorWheel::sizeHint() const { - return QSize( 200, 200 ); + int size = fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ); + return QSize( size, size ); } void QgsColorWheel::paintEvent( QPaintEvent *event ) @@ -757,7 +758,8 @@ QgsColorBox::~QgsColorBox() QSize QgsColorBox::sizeHint() const { - return QSize( 200, 200 ); + int size = fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ); + return QSize( size, size ); } void QgsColorBox::paintEvent( QPaintEvent *event ) @@ -989,12 +991,12 @@ QSize QgsColorRampWidget::sizeHint() const if ( mOrientation == QgsColorRampWidget::Horizontal ) { //horizontal - return QSize( 200, 28 ); + return QSize( fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), fontMetrics().height() * 1.3 ); } else { //vertical - return QSize( 18, 200 ); + return QSize( fontMetrics().height() * 1.3, fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) ); } } @@ -1596,7 +1598,7 @@ void QgsColorPreviewWidget::paintEvent( QPaintEvent *event ) QSize QgsColorPreviewWidget::sizeHint() const { - return QSize( 200, 150 ); + return QSize( fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) * 0.75 ); } void QgsColorPreviewWidget::setColor2( const QColor &color ) From 09dd6db97b5b19979669677e53baf675d976d62f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 14:53:41 +1000 Subject: [PATCH 161/266] Add API to save/restore QgsLayoutObject properties to XML --- python/core/layout/qgslayoutobject.sip | 20 ++++ src/core/layout/qgslayoutobject.cpp | 48 ++++++++++ src/core/layout/qgslayoutobject.h | 19 ++++ tests/src/core/testqgslayoutobject.cpp | 126 +++++++++++++++++++++++++ 4 files changed, 213 insertions(+) diff --git a/python/core/layout/qgslayoutobject.sip b/python/core/layout/qgslayoutobject.sip index e60d5673ddf..cfe1fd3de18 100644 --- a/python/core/layout/qgslayoutobject.sip +++ b/python/core/layout/qgslayoutobject.sip @@ -168,6 +168,26 @@ class QgsLayoutObject: QObject, QgsExpressionContextGenerator protected: + bool writeObjectPropertiesToElement( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const; +%Docstring + Stores object properties within an XML DOM element. + \param parentElement is the parent DOM element to store the object's properties in + \param document DOM document + :return: true if write was successful +.. seealso:: readObjectPropertiesFromElement() + :rtype: bool +%End + + bool readObjectPropertiesFromElement( const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext &context ); +%Docstring + Sets object properties from a DOM element + \param parentElement is the parent DOM element for the object + \param document DOM document + :return: true if read was successful +.. seealso:: writeObjectPropertiesToElement() + :rtype: bool +%End + diff --git a/src/core/layout/qgslayoutobject.cpp b/src/core/layout/qgslayoutobject.cpp index d13163771c0..4c4462794c9 100644 --- a/src/core/layout/qgslayoutobject.cpp +++ b/src/core/layout/qgslayoutobject.cpp @@ -122,3 +122,51 @@ QgsExpressionContext QgsLayoutObject::createExpressionContext() const return QgsExpressionContext() << QgsExpressionContextUtils::globalScope(); } } + +bool QgsLayoutObject::writeObjectPropertiesToElement( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const +{ + if ( parentElement.isNull() ) + { + return false; + } + + //create object element + QDomElement objectElement = document.createElement( "LayoutObject" ); + + QDomElement ddPropsElement = document.createElement( QStringLiteral( "dataDefinedProperties" ) ); + mDataDefinedProperties.writeXml( ddPropsElement, sPropertyDefinitions ); + objectElement.appendChild( ddPropsElement ); + + //custom properties + mCustomProperties.writeXml( objectElement, document ); + + parentElement.appendChild( objectElement ); + return true; +} + +bool QgsLayoutObject::readObjectPropertiesFromElement( const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext & ) +{ + Q_UNUSED( document ); + if ( parentElement.isNull() ) + { + return false; + } + + QDomNodeList objectNodeList = parentElement.elementsByTagName( "LayoutObject" ); + if ( objectNodeList.size() < 1 ) + { + return false; + } + QDomElement objectElement = objectNodeList.at( 0 ).toElement(); + + QDomNode propsNode = objectElement.namedItem( QStringLiteral( "dataDefinedProperties" ) ); + if ( !propsNode.isNull() ) + { + mDataDefinedProperties.readXml( propsNode.toElement(), sPropertyDefinitions ); + } + + //custom properties + mCustomProperties.readXml( objectElement ); + + return true; +} diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h index 4064d216722..a9ae4a8cc3d 100644 --- a/src/core/layout/qgslayoutobject.h +++ b/src/core/layout/qgslayoutobject.h @@ -28,6 +28,7 @@ class QgsLayout; class QPainter; +class QgsReadWriteContext; /** * \ingroup core @@ -188,6 +189,24 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe protected: + /** + * Stores object properties within an XML DOM element. + * \param parentElement is the parent DOM element to store the object's properties in + * \param document DOM document + * \returns true if write was successful + * \see readObjectPropertiesFromElement() + */ + bool writeObjectPropertiesToElement( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const; + + /** + * Sets object properties from a DOM element + * \param parentElement is the parent DOM element for the object + * \param document DOM document + * \returns true if read was successful + * \see writeObjectPropertiesToElement() + */ + bool readObjectPropertiesFromElement( const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext &context ); + QgsLayout *mLayout = nullptr; QgsPropertyCollection mDataDefinedProperties; diff --git a/tests/src/core/testqgslayoutobject.cpp b/tests/src/core/testqgslayoutobject.cpp index ff0ec46836b..63d5c429c58 100644 --- a/tests/src/core/testqgslayoutobject.cpp +++ b/tests/src/core/testqgslayoutobject.cpp @@ -19,6 +19,7 @@ #include "qgslayout.h" #include "qgstest.h" #include "qgsproject.h" +#include "qgsreadwritecontext.h" class TestQgsLayoutObject: public QObject { @@ -33,6 +34,10 @@ class TestQgsLayoutObject: public QObject void layout(); //test fetching layout from QgsLayoutObject void customProperties(); void context(); + void writeReadXml(); + void writeRetrieveDDProperty(); //test writing and retrieving dd properties from xml + void writeRetrieveCustomProperties(); //test writing/retreiving custom properties from xml + private: QString mReport; @@ -138,6 +143,127 @@ void TestQgsLayoutObject::context() delete object; } +void TestQgsLayoutObject::writeReadXml() +{ + QgsProject p; + QgsLayout l( &p ); + + QgsLayoutObject *object = new QgsLayoutObject( &l ); + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + "qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" ); + QDomDocument doc( documentType ); + + //test writing with no parent node + QDomElement rootNode = doc.createElement( "qgis" ); + QDomElement noNode; + QCOMPARE( object->writeObjectPropertiesToElement( noNode, doc, QgsReadWriteContext() ), false ); + + //test writing with node + QDomElement layoutObjectElem = doc.createElement( "item" ); + rootNode.appendChild( layoutObjectElem ); + QVERIFY( object->writeObjectPropertiesToElement( layoutObjectElem, doc, QgsReadWriteContext() ) ); + + //check if object node was written + QDomNodeList evalNodeList = rootNode.elementsByTagName( "LayoutObject" ); + QCOMPARE( evalNodeList.count(), 1 ); + + //test reading node + QgsLayoutObject *readObject = new QgsLayoutObject( &l ); + + //test reading with no node + QCOMPARE( readObject->readObjectPropertiesFromElement( noNode, doc, QgsReadWriteContext() ), false ); + + //test node with no layout object child + QDomElement badLayoutObjectElem = doc.createElement( "item" ); + rootNode.appendChild( badLayoutObjectElem ); + QCOMPARE( readObject->readObjectPropertiesFromElement( badLayoutObjectElem, doc, QgsReadWriteContext() ), false ); + + //test reading node + QVERIFY( readObject->readObjectPropertiesFromElement( layoutObjectElem, doc, QgsReadWriteContext() ) ); + + delete object; + delete readObject; +} + +void TestQgsLayoutObject::writeRetrieveDDProperty() +{ + QgsProject p; + QgsLayout l( &p ); + + QgsLayoutObject *object = new QgsLayoutObject( &l ); + object->dataDefinedProperties().setProperty( QgsLayoutObject::TestProperty, QgsProperty::fromExpression( QStringLiteral( "10 + 40" ) ) ); + + //test writing object with dd settings + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) ); + QDomDocument doc( documentType ); + QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) ); + QVERIFY( object->writeObjectPropertiesToElement( rootNode, doc, QgsReadWriteContext() ) ); + + //check if object node was written + QDomNodeList evalNodeList = rootNode.elementsByTagName( QStringLiteral( "LayoutObject" ) ); + QCOMPARE( evalNodeList.count(), 1 ); + + //test reading node containing dd settings + QgsLayoutObject *readObject = new QgsLayoutObject( &l ); + QVERIFY( readObject->readObjectPropertiesFromElement( rootNode, doc, QgsReadWriteContext() ) ); + + //test getting not set dd from restored object + QgsProperty dd = readObject->dataDefinedProperties().property( QgsLayoutObject::BlendMode ); + QVERIFY( !dd ); + + //test getting good property + dd = readObject->dataDefinedProperties().property( QgsLayoutObject::TestProperty ); + QVERIFY( dd ); + QVERIFY( dd.isActive() ); + QCOMPARE( dd.propertyType(), QgsProperty::ExpressionBasedProperty ); + + delete object; + delete readObject; +} + +void TestQgsLayoutObject::writeRetrieveCustomProperties() +{ + QgsProject p; + QgsLayout l( &p ); + + QgsLayoutObject *object = new QgsLayoutObject( &l ); + + object->setCustomProperty( QStringLiteral( "testprop" ), "testval" ); + object->setCustomProperty( QStringLiteral( "testprop2" ), 5 ); + + //test writing object with custom properties + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) ); + QDomDocument doc( documentType ); + QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) ); + QVERIFY( object->writeObjectPropertiesToElement( rootNode, doc, QgsReadWriteContext() ) ); + + //check if object node was written + QDomNodeList evalNodeList = rootNode.elementsByTagName( QStringLiteral( "LayoutObject" ) ); + QCOMPARE( evalNodeList.count(), 1 ); + + //test reading node containing custom properties + QgsLayoutObject *readObject = new QgsLayoutObject( &l ); + QVERIFY( readObject->readObjectPropertiesFromElement( rootNode, doc, QgsReadWriteContext() ) ); + + //test retrieved custom properties + QCOMPARE( readObject->customProperties().length(), 2 ); + QVERIFY( readObject->customProperties().contains( QString( "testprop" ) ) ); + QVERIFY( readObject->customProperties().contains( QString( "testprop2" ) ) ); + QCOMPARE( readObject->customProperty( "testprop" ).toString(), QString( "testval" ) ); + QCOMPARE( readObject->customProperty( "testprop2" ).toInt(), 5 ); + + delete object; + delete readObject; +} + QGSTEST_MAIN( TestQgsLayoutObject ) #include "testqgslayoutobject.moc" From a515e953b8f6c1fa66622661f2bba116f9a4c8da Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 15:48:38 +1000 Subject: [PATCH 162/266] Add support for reading/writing item properties to XML --- python/core/layout/qgslayoutitem.sip | 70 +++++++++++ python/core/layout/qgslayoutitemmap.sip | 2 + python/core/layout/qgslayoutitemregistry.sip | 1 + python/core/layout/qgslayoutitemshape.sip | 3 + python/core/layout/qgslayoutobject.sip | 2 + src/core/layout/qgslayoutitem.cpp | 95 +++++++++++++++ src/core/layout/qgslayoutitem.h | 56 +++++++++ src/core/layout/qgslayoutitemmap.h | 3 + src/core/layout/qgslayoutitemregistry.h | 2 + src/core/layout/qgslayoutitemshape.h | 5 +- src/core/layout/qgslayoutobject.h | 2 + tests/src/core/testqgslayoutitem.cpp | 115 +++++++++++++++++++ 12 files changed, 355 insertions(+), 1 deletion(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index b717e36065f..01c1d1a2ff0 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -39,6 +39,22 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem Constructor for QgsLayoutItem, with the specified parent ``layout``. %End + virtual int type() const = 0; +%Docstring + Return correct graphics item type +.. seealso:: stringType() + :rtype: int +%End + + virtual QString stringType() const = 0; +%Docstring + Return the item type as a string. + + This string must be a unique, single word, character only representation of the item type, eg "LayoutScaleBar" +.. seealso:: type() + :rtype: str +%End + virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ); %Docstring @@ -130,6 +146,32 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem :rtype: float %End + virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const; +%Docstring + Stores the item state in a DOM element. + \param parentElement parent DOM element (e.g. 'Layout' element) + \param document DOM document + \param context read write context +.. seealso:: readXml() +.. note:: + + Subclasses should ensure that they call writePropertiesToElement() in their implementation. + :rtype: bool +%End + + virtual bool readXml( const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context ); +%Docstring + Sets the item state from a DOM element. + \param itemElement is the DOM node corresponding to item (e.g. 'LayoutItem' element) + \param document DOM document + \param context read write context +.. seealso:: writeXml() +.. note:: + + Subclasses should ensure that they call readPropertiesFromElement() in their implementation. + :rtype: bool +%End + public slots: virtual void refresh(); @@ -230,6 +272,34 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem :rtype: QPointF %End + virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; +%Docstring + Stores item state within an XML DOM element. + \param element is the DOM element to store the item's properties in + \param document DOM document + \param context read write context +.. seealso:: writeXml() +.. seealso:: readPropertiesFromElement() +.. note:: + + derived classes must call this base implementation when overriding this method + :rtype: bool +%End + + virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ); +%Docstring + Sets item state from a DOM element. + \param element is the DOM element for the item + \param document DOM document + \param context read write context +.. seealso:: writePropertiesToElement() +.. seealso:: readXml() +.. note:: + + derived classes must call this base implementation when overriding this method + :rtype: bool +%End + }; diff --git a/python/core/layout/qgslayoutitemmap.sip b/python/core/layout/qgslayoutitemmap.sip index 29a837c3577..c29f7a7bfe2 100644 --- a/python/core/layout/qgslayoutitemmap.sip +++ b/python/core/layout/qgslayoutitemmap.sip @@ -24,6 +24,8 @@ class QgsLayoutItemMap : QgsLayoutItem %Docstring Constructor for QgsLayoutItemMap, with the specified parent ``layout``. %End + virtual int type() const; + virtual QString stringType() const; protected: diff --git a/python/core/layout/qgslayoutitemregistry.sip b/python/core/layout/qgslayoutitemregistry.sip index afad7f19e66..a2d7c5e5509 100644 --- a/python/core/layout/qgslayoutitemregistry.sip +++ b/python/core/layout/qgslayoutitemregistry.sip @@ -101,6 +101,7 @@ class QgsLayoutItemRegistry : QObject // known LayoutPage, + LayoutMap, LayoutRectangle, LayoutEllipse, LayoutTriangle, diff --git a/python/core/layout/qgslayoutitemshape.sip b/python/core/layout/qgslayoutitemshape.sip index 8eb1008a893..909181c5d47 100644 --- a/python/core/layout/qgslayoutitemshape.sip +++ b/python/core/layout/qgslayoutitemshape.sip @@ -61,6 +61,7 @@ class QgsLayoutItemRectangularShape : QgsLayoutItemShape Constructor for QgsLayoutItemRectangularShape, with the specified parent ``layout``. %End virtual int type() const; + virtual QString stringType() const; static QgsLayoutItemRectangularShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; %Docstring @@ -107,6 +108,7 @@ class QgsLayoutItemEllipseShape : QgsLayoutItemShape Constructor for QgsLayoutItemEllipseShape, with the specified parent ``layout``. %End virtual int type() const; + virtual QString stringType() const; static QgsLayoutItemEllipseShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; %Docstring @@ -140,6 +142,7 @@ class QgsLayoutItemTriangleShape : QgsLayoutItemShape Constructor for QgsLayoutItemTriangleShape, with the specified parent ``layout``. %End virtual int type() const; + virtual QString stringType() const; static QgsLayoutItemTriangleShape *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/; %Docstring diff --git a/python/core/layout/qgslayoutobject.sip b/python/core/layout/qgslayoutobject.sip index cfe1fd3de18..f89417a0fdb 100644 --- a/python/core/layout/qgslayoutobject.sip +++ b/python/core/layout/qgslayoutobject.sip @@ -173,6 +173,7 @@ class QgsLayoutObject: QObject, QgsExpressionContextGenerator Stores object properties within an XML DOM element. \param parentElement is the parent DOM element to store the object's properties in \param document DOM document + \param context read write context :return: true if write was successful .. seealso:: readObjectPropertiesFromElement() :rtype: bool @@ -183,6 +184,7 @@ class QgsLayoutObject: QObject, QgsExpressionContextGenerator Sets object properties from a DOM element \param parentElement is the parent DOM element for the object \param document DOM document + \param context read write context :return: true if read was successful .. seealso:: writeObjectPropertiesToElement() :rtype: bool diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index da1e78fc25d..9aacc038df6 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -208,6 +208,27 @@ double QgsLayoutItem::itemRotation() const return rotation(); } +bool QgsLayoutItem::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context ) const +{ + QDomElement element = doc.createElement( "LayoutItem" ); + element.setAttribute( "type", stringType() ); + + writePropertiesToElement( element, doc, context ); + parentElement.appendChild( element ); + + return true; +} + +bool QgsLayoutItem::readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context ) +{ + if ( itemElem.nodeName() != QString( "LayoutItem" ) || itemElem.attribute( "type" ) != stringType() ) + { + return false; + } + + return readPropertiesFromElement( itemElem, doc, context ); +} + QgsLayoutPoint QgsLayoutItem::applyDataDefinedPosition( const QgsLayoutPoint &position ) { if ( !mLayout ) @@ -387,6 +408,80 @@ QPointF QgsLayoutItem::positionAtReferencePoint( const QgsLayoutItem::ReferenceP return mapToScene( pointWithinItem ); } +bool QgsLayoutItem::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const +{ +// element.setAttribute( "uuid", mUuid ); +// element.setAttribute( "id", mId ); + element.setAttribute( QStringLiteral( "referencePoint" ), QString::number( static_cast< int >( mReferencePoint ) ) ); + element.setAttribute( QStringLiteral( "position" ), mItemPosition.encodePoint() ); + element.setAttribute( QStringLiteral( "size" ), mItemSize.encodeSize() ); + element.setAttribute( QStringLiteral( "rotation" ), QString::number( rotation() ) ); + + //TODO + /* + composerItemElem.setAttribute( "zValue", QString::number( zValue() ) ); + composerItemElem.setAttribute( "visibility", isVisible() ); + //position lock for mouse moves/resizes + if ( mItemPositionLocked ) + { + composerItemElem.setAttribute( "positionLock", "true" ); + } + else + { + composerItemElem.setAttribute( "positionLock", "false" ); + } + */ + + //blend mode + // composerItemElem.setAttribute( "blendMode", QgsMapRenderer::getBlendModeEnum( mBlendMode ) ); + + //transparency + // composerItemElem.setAttribute( "transparency", QString::number( mTransparency ) ); + + // composerItemElem.setAttribute( "excludeFromExports", mExcludeFromExports ); + + writeObjectPropertiesToElement( element, document, context ); + return true; +} + +bool QgsLayoutItem::readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) +{ + readObjectPropertiesFromElement( element, document, context ); + +// mUuid = element.attribute( "uuid", QUuid::createUuid().toString() ); +// setId( element.attribute( "id" ) ); + mReferencePoint = static_cast< ReferencePoint >( element.attribute( QStringLiteral( "referencePoint" ) ).toInt() ); + attemptMove( QgsLayoutPoint::decodePoint( element.attribute( QStringLiteral( "position" ) ) ) ); + attemptResize( QgsLayoutSize::decodeSize( element.attribute( QStringLiteral( "size" ) ) ) ); + setItemRotation( element.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble() ); + + //TODO + /* + // temporary for groups imported from templates + mTemplateUuid = itemElem.attribute( "templateUuid" ); + //position lock for mouse moves/resizes + QString positionLock = itemElem.attribute( "positionLock" ); + if ( positionLock.compare( "true", Qt::CaseInsensitive ) == 0 ) + { + setPositionLock( true ); + } + else + { + setPositionLock( false ); + } + //visibility + setVisibility( itemElem.attribute( "visibility", "1" ) != "0" ); + setZValue( itemElem.attribute( "zValue" ).toDouble() ); + //blend mode + setBlendMode( QgsMapRenderer::getCompositionMode(( QgsMapRenderer::BlendMode ) itemElem.attribute( "blendMode", "0" ).toUInt() ) ); + //transparency + setTransparency( itemElem.attribute( "transparency", "0" ).toInt() ); + mExcludeFromExports = itemElem.attribute( "excludeFromExports", "0" ).toInt(); + mEvaluatedExcludeFromExports = mExcludeFromExports; + */ + return true; +} + void QgsLayoutItem::initConnectionsToLayout() { if ( !mLayout ) diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 537b89529f6..3d869cb62c9 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -59,6 +59,20 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ explicit QgsLayoutItem( QgsLayout *layout ); + /** + * Return correct graphics item type + * \see stringType() + */ + virtual int type() const = 0; + + /** + * Return the item type as a string. + * + * This string must be a unique, single word, character only representation of the item type, eg "LayoutScaleBar" + * \see type() + */ + virtual QString stringType() const = 0; + /** * Handles preparing a paint surface for the layout item and painting the item's * content. Derived classes must not override this method, but instead implement @@ -146,6 +160,26 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt //TODO double itemRotation() const; + /** + * Stores the item state in a DOM element. + * \param parentElement parent DOM element (e.g. 'Layout' element) + * \param document DOM document + * \param context read write context + * \see readXml() + * \note Subclasses should ensure that they call writePropertiesToElement() in their implementation. + */ + virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const; + + /** + * Sets the item state from a DOM element. + * \param itemElement is the DOM node corresponding to item (e.g. 'LayoutItem' element) + * \param document DOM document + * \param context read write context + * \see writeXml() + * \note Subclasses should ensure that they call readPropertiesFromElement() in their implementation. + */ + virtual bool readXml( const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context ); + public slots: /** @@ -242,6 +276,28 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ QPointF positionAtReferencePoint( const ReferencePoint &reference ) const; + /** + * Stores item state within an XML DOM element. + * \param element is the DOM element to store the item's properties in + * \param document DOM document + * \param context read write context + * \see writeXml() + * \see readPropertiesFromElement() + * \note derived classes must call this base implementation when overriding this method + */ + virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; + + /** + * Sets item state from a DOM element. + * \param element is the DOM element for the item + * \param document DOM document + * \param context read write context + * \see writePropertiesToElement() + * \see readXml() + * \note derived classes must call this base implementation when overriding this method + */ + virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ); + private: ReferencePoint mReferencePoint = UpperLeft; diff --git a/src/core/layout/qgslayoutitemmap.h b/src/core/layout/qgslayoutitemmap.h index 05af3636942..4167de789b0 100644 --- a/src/core/layout/qgslayoutitemmap.h +++ b/src/core/layout/qgslayoutitemmap.h @@ -19,6 +19,7 @@ #include "qgis_core.h" #include "qgslayoutitem.h" +#include "qgslayoutitemregistry.h" /** * \ingroup core @@ -37,6 +38,8 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem * Constructor for QgsLayoutItemMap, with the specified parent \a layout. */ explicit QgsLayoutItemMap( QgsLayout *layout ); + int type() const override { return QgsLayoutItemRegistry::LayoutMap; } + QString stringType() const override { return QStringLiteral( "ItemMap" ); } protected: diff --git a/src/core/layout/qgslayoutitemregistry.h b/src/core/layout/qgslayoutitemregistry.h index 58c0d04448d..18cbc842870 100644 --- a/src/core/layout/qgslayoutitemregistry.h +++ b/src/core/layout/qgslayoutitemregistry.h @@ -183,6 +183,7 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject // known item types LayoutPage, //!< Page items + LayoutMap, //!< Map item LayoutRectangle, //!< Rectangular shape item LayoutEllipse, //!< Ellipse shape item LayoutTriangle, //!< Triangle shape item @@ -271,6 +272,7 @@ class TestLayoutItem : public QgsLayoutItem //implement pure virtual methods int type() const { return QgsLayoutItemRegistry::LayoutItem + 102; } + QString stringType() const override { return QStringLiteral( "ItemTest" ); } void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ); private: diff --git a/src/core/layout/qgslayoutitemshape.h b/src/core/layout/qgslayoutitemshape.h index 66476eb44e7..6e1c8cd6864 100644 --- a/src/core/layout/qgslayoutitemshape.h +++ b/src/core/layout/qgslayoutitemshape.h @@ -77,7 +77,8 @@ class CORE_EXPORT QgsLayoutItemRectangularShape : public QgsLayoutItemShape * Constructor for QgsLayoutItemRectangularShape, with the specified parent \a layout. */ explicit QgsLayoutItemRectangularShape( QgsLayout *layout ); - virtual int type() const override { return QgsLayoutItemRegistry::LayoutRectangle; } + int type() const override { return QgsLayoutItemRegistry::LayoutRectangle; } + QString stringType() const override { return QStringLiteral( "ItemRect" ); } /** * Returns a new rectangular item for the specified \a layout. @@ -124,6 +125,7 @@ class CORE_EXPORT QgsLayoutItemEllipseShape : public QgsLayoutItemShape */ explicit QgsLayoutItemEllipseShape( QgsLayout *layout ); virtual int type() const override { return QgsLayoutItemRegistry::LayoutEllipse; } + QString stringType() const override { return QStringLiteral( "ItemEllipse" ); } /** * Returns a new ellipse item for the specified \a layout. @@ -156,6 +158,7 @@ class CORE_EXPORT QgsLayoutItemTriangleShape : public QgsLayoutItemShape */ explicit QgsLayoutItemTriangleShape( QgsLayout *layout ); virtual int type() const override { return QgsLayoutItemRegistry::LayoutTriangle; } + QString stringType() const override { return QStringLiteral( "ItemTriangle" ); } /** * Returns a new triangle item for the specified \a layout. diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h index a9ae4a8cc3d..99e67f27eb4 100644 --- a/src/core/layout/qgslayoutobject.h +++ b/src/core/layout/qgslayoutobject.h @@ -193,6 +193,7 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe * Stores object properties within an XML DOM element. * \param parentElement is the parent DOM element to store the object's properties in * \param document DOM document + * \param context read write context * \returns true if write was successful * \see readObjectPropertiesFromElement() */ @@ -202,6 +203,7 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe * Sets object properties from a DOM element * \param parentElement is the parent DOM element for the object * \param document DOM document + * \param context read write context * \returns true if read was successful * \see writeObjectPropertiesToElement() */ diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 4e46aec3244..0fa791fad81 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -21,6 +21,7 @@ #include "qgsmultirenderchecker.h" #include "qgstest.h" #include "qgsproject.h" +#include "qgsreadwritecontext.h" #include #include #include @@ -56,6 +57,9 @@ class TestQgsLayoutItem: public QObject void dataDefinedSize(); void combinedDataDefinedPositionAndSize(); void rotation(); + void writeXml(); + void readXml(); + void writeReadXmlProperties(); private: @@ -69,6 +73,7 @@ class TestQgsLayoutItem: public QObject //implement pure virtual methods int type() const override { return QgsLayoutItemRegistry::LayoutItem + 101; } + QString stringType() const override { return QStringLiteral( "TestItemType" ); } protected: void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * = nullptr ) override @@ -134,6 +139,8 @@ class TestQgsLayoutItem: public QObject bool renderCheck( QString testName, QImage &image, int mismatchCount ); + QgsLayoutItem *createCopyViaXml( QgsLayout *layout, QgsLayoutItem *original ); + }; void TestQgsLayoutItem::initTestCase() @@ -1152,5 +1159,113 @@ void TestQgsLayoutItem::rotation() //restoring item from xml respects rotation/position //rotate item around layout point + +void TestQgsLayoutItem::writeXml() +{ + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + "qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" ); + QDomDocument doc( documentType ); + QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) ); + + QgsProject proj; + QgsLayout l( &proj ); + TestItem *item = new TestItem( &l ); + QVERIFY( item->writeXml( rootNode, doc, QgsReadWriteContext() ) ); + + //make sure type was written + QDomElement element = rootNode.firstChildElement(); + + QCOMPARE( element.nodeName(), QString( "LayoutItem" ) ); + QCOMPARE( element.attribute( "type", "" ), item->stringType() ); + + //check that element has an object node + QDomNodeList objectNodeList = element.elementsByTagName( "LayoutObject" ); + QCOMPARE( objectNodeList.count(), 1 ); + + delete item; +} + +void TestQgsLayoutItem::readXml() +{ + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + "qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" ); + QDomDocument doc( documentType ); + + QgsProject proj; + QgsLayout l( &proj ); + TestItem *item = new TestItem( &l ); + + //try reading bad elements + QDomElement badElement = doc.createElement( "bad" ); + QDomElement noNode; + QVERIFY( !item->readXml( badElement, doc, QgsReadWriteContext() ) ); + QVERIFY( !item->readXml( noNode, doc, QgsReadWriteContext() ) ); + + //element with wrong type + QDomElement wrongType = doc.createElement( "LayoutItem" ); + wrongType.setAttribute( QString( "type" ), "bad" ); + QVERIFY( !item->readXml( wrongType, doc, QgsReadWriteContext() ) ); + + //try good element + QDomElement goodElement = doc.createElement( "LayoutItem" ); + goodElement.setAttribute( QString( "type" ), "TestItemType" ); + QVERIFY( item->readXml( goodElement, doc, QgsReadWriteContext() ) ); + delete item; +} + +void TestQgsLayoutItem::writeReadXmlProperties() +{ + QgsProject proj; + QgsLayout l( &proj ); + TestItem *original = new TestItem( &l ); + + original->dataDefinedProperties().setProperty( QgsLayoutObject::TestProperty, QgsProperty::fromExpression( QStringLiteral( "10 + 40" ) ) ); + + original->setReferencePoint( QgsLayoutItem::Middle ); + original->attemptResize( QgsLayoutSize( 6, 8, QgsUnitTypes::LayoutCentimeters ) ); + original->attemptMove( QgsLayoutPoint( 0.05, 0.09, QgsUnitTypes::LayoutMeters ) ); + original->setItemRotation( 45.0 ); + //original->setId( QString( "test" ) ); + + QgsLayoutItem *copy = createCopyViaXml( &l, original ); + + //QCOMPARE( copy->uuid(), original->uuid() ); + //QCOMPARE( copy->id(), original->id() ); + QgsProperty dd = copy->dataDefinedProperties().property( QgsLayoutObject::TestProperty ); + QVERIFY( dd ); + QVERIFY( dd.isActive() ); + QCOMPARE( dd.propertyType(), QgsProperty::ExpressionBasedProperty ); + QCOMPARE( copy->referencePoint(), original->referencePoint() ); + QCOMPARE( copy->sizeWithUnits(), original->sizeWithUnits() ); + QCOMPARE( copy->positionWithUnits(), original->positionWithUnits() ); + QCOMPARE( copy->itemRotation(), original->itemRotation() ); + + delete copy; + delete original; +} + +QgsLayoutItem *TestQgsLayoutItem::createCopyViaXml( QgsLayout *layout, QgsLayoutItem *original ) +{ + //save original item to xml + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + "qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" ); + QDomDocument doc( documentType ); + QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) ); + + original->writeXml( rootNode, doc, QgsReadWriteContext() ); + + //create new item and restore settings from xml + TestItem *copy = new TestItem( layout ); + copy->readXml( rootNode.firstChildElement(), doc, QgsReadWriteContext() ); + + return copy; +} + QGSTEST_MAIN( TestQgsLayoutItem ) #include "testqgslayoutitem.moc" From 3cf06db467858169bc9b5ecdb2f1682cf15d0676 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 15:56:34 +1000 Subject: [PATCH 163/266] Port item id and uuid code --- python/core/layout/qgslayoutitem.sip | 29 +++++++++++++++++++++++++ src/core/layout/qgslayoutitem.cpp | 32 ++++++++++++++++++++++++---- src/core/layout/qgslayoutitem.h | 31 +++++++++++++++++++++++++++ src/core/layout/qgslayoutobject.cpp | 4 ++-- tests/src/core/testqgslayoutitem.cpp | 28 +++++++++++++++++++++--- 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 01c1d1a2ff0..2bde5e1ede7 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -55,6 +55,35 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem :rtype: str %End + QString uuid() const; +%Docstring + Returns the item identification string. This is a unique random string set for the item + upon creation. +.. note:: + + There is no corresponding setter for the uuid - it's created automatically. +.. seealso:: id() +.. seealso:: setId() + :rtype: str +%End + + QString id() const; +%Docstring + Returns the item's ID name. This is not necessarily unique, and duplicate ID names may exist + for a layout. +.. seealso:: setId() +.. seealso:: uuid() + :rtype: str +%End + + virtual void setId( const QString &id ); +%Docstring + Set the item's ``id`` name. This is not necessarily unique, and duplicate ID names may exist + for a layout. +.. seealso:: id() +.. seealso:: uuid() +%End + virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ); %Docstring diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 9aacc038df6..53abde383ed 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -19,12 +19,14 @@ #include "qgslayoututils.h" #include #include +#include #define CACHE_SIZE_LIMIT 5000 QgsLayoutItem::QgsLayoutItem( QgsLayout *layout ) : QgsLayoutObject( layout ) , QGraphicsRectItem( 0 ) + , mUuid( QUuid::createUuid().toString() ) { // needed to access current view transform during paint operations setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption ); @@ -38,6 +40,28 @@ QgsLayoutItem::QgsLayoutItem( QgsLayout *layout ) initConnectionsToLayout(); } +void QgsLayoutItem::setId( const QString &id ) +{ + if ( id == mId ) + { + return; + } + + mId = id; + setToolTip( id ); + + //TODO +#if 0 + //inform model that id data has changed + if ( mComposition ) + { + mComposition->itemsModel()->updateItemDisplayName( this ); + } + + emit itemChanged(); +#endif +} + void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * ) { if ( !painter || !painter->device() ) @@ -410,8 +434,8 @@ QPointF QgsLayoutItem::positionAtReferencePoint( const QgsLayoutItem::ReferenceP bool QgsLayoutItem::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const { -// element.setAttribute( "uuid", mUuid ); -// element.setAttribute( "id", mId ); + element.setAttribute( QStringLiteral( "uuid" ), mUuid ); + element.setAttribute( QStringLiteral( "id" ), mId ); element.setAttribute( QStringLiteral( "referencePoint" ), QString::number( static_cast< int >( mReferencePoint ) ) ); element.setAttribute( QStringLiteral( "position" ), mItemPosition.encodePoint() ); element.setAttribute( QStringLiteral( "size" ), mItemSize.encodeSize() ); @@ -448,8 +472,8 @@ bool QgsLayoutItem::readPropertiesFromElement( const QDomElement &element, const { readObjectPropertiesFromElement( element, document, context ); -// mUuid = element.attribute( "uuid", QUuid::createUuid().toString() ); -// setId( element.attribute( "id" ) ); + mUuid = element.attribute( QStringLiteral( "uuid" ), QUuid::createUuid().toString() ); + setId( element.attribute( QStringLiteral( "id" ) ) ); mReferencePoint = static_cast< ReferencePoint >( element.attribute( QStringLiteral( "referencePoint" ) ).toInt() ); attemptMove( QgsLayoutPoint::decodePoint( element.attribute( QStringLiteral( "position" ) ) ) ); attemptResize( QgsLayoutSize::decodeSize( element.attribute( QStringLiteral( "size" ) ) ) ); diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 3d869cb62c9..1362f5d4534 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -73,6 +73,31 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ virtual QString stringType() const = 0; + /** + * Returns the item identification string. This is a unique random string set for the item + * upon creation. + * \note There is no corresponding setter for the uuid - it's created automatically. + * \see id() + * \see setId() + */ + QString uuid() const { return mUuid; } + + /** + * Returns the item's ID name. This is not necessarily unique, and duplicate ID names may exist + * for a layout. + * \see setId() + * \see uuid() + */ + QString id() const { return mId; } + + /** + * Set the item's \a id name. This is not necessarily unique, and duplicate ID names may exist + * for a layout. + * \see id() + * \see uuid() + */ + virtual void setId( const QString &id ); + /** * Handles preparing a paint surface for the layout item and painting the item's * content. Derived classes must not override this method, but instead implement @@ -300,6 +325,12 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt private: + //! id (not necessarily unique) + QString mId; + + //! Unique id + QString mUuid; + ReferencePoint mReferencePoint = UpperLeft; QgsLayoutSize mFixedSize; QgsLayoutSize mMinimumSize; diff --git a/src/core/layout/qgslayoutobject.cpp b/src/core/layout/qgslayoutobject.cpp index 4c4462794c9..27645bc4335 100644 --- a/src/core/layout/qgslayoutobject.cpp +++ b/src/core/layout/qgslayoutobject.cpp @@ -131,7 +131,7 @@ bool QgsLayoutObject::writeObjectPropertiesToElement( QDomElement &parentElement } //create object element - QDomElement objectElement = document.createElement( "LayoutObject" ); + QDomElement objectElement = document.createElement( QStringLiteral( "LayoutObject" ) ); QDomElement ddPropsElement = document.createElement( QStringLiteral( "dataDefinedProperties" ) ); mDataDefinedProperties.writeXml( ddPropsElement, sPropertyDefinitions ); @@ -152,7 +152,7 @@ bool QgsLayoutObject::readObjectPropertiesFromElement( const QDomElement &parent return false; } - QDomNodeList objectNodeList = parentElement.elementsByTagName( "LayoutObject" ); + QDomNodeList objectNodeList = parentElement.elementsByTagName( QStringLiteral( "LayoutObject" ) ); if ( objectNodeList.size() < 1 ) { return false; diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 0fa791fad81..151f99b51b6 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -37,6 +37,8 @@ class TestQgsLayoutItem: public QObject void init();// will be called before each testfunction is executed. void cleanup();// will be called after every testfunction. void creation(); //test creation of QgsLayoutItem + void uuid(); + void id(); void registry(); void shouldDrawDebug(); void shouldDrawAntialiased(); @@ -180,6 +182,26 @@ void TestQgsLayoutItem::creation() delete layout; } +void TestQgsLayoutItem::uuid() +{ + QgsProject p; + QgsLayout l( &p ); + + //basic test of uuid + TestItem item( &l ); + TestItem item2( &l ); + QVERIFY( item.uuid() != item2.uuid() ); +} + +void TestQgsLayoutItem::id() +{ + QgsProject p; + QgsLayout l( &p ); + TestItem item( &l ); + item.setId( QStringLiteral( "test" ) ); + QCOMPARE( item.id(), QStringLiteral( "test" ) ); +} + void TestQgsLayoutItem::registry() { // test QgsLayoutItemRegistry @@ -1229,12 +1251,12 @@ void TestQgsLayoutItem::writeReadXmlProperties() original->attemptResize( QgsLayoutSize( 6, 8, QgsUnitTypes::LayoutCentimeters ) ); original->attemptMove( QgsLayoutPoint( 0.05, 0.09, QgsUnitTypes::LayoutMeters ) ); original->setItemRotation( 45.0 ); - //original->setId( QString( "test" ) ); + original->setId( QStringLiteral( "test" ) ); QgsLayoutItem *copy = createCopyViaXml( &l, original ); - //QCOMPARE( copy->uuid(), original->uuid() ); - //QCOMPARE( copy->id(), original->id() ); + QCOMPARE( copy->uuid(), original->uuid() ); + QCOMPARE( copy->id(), original->id() ); QgsProperty dd = copy->dataDefinedProperties().property( QgsLayoutObject::TestProperty ); QVERIFY( dd ); QVERIFY( dd.isActive() ); From 9df4a67d6d0457bfe72d4977362bc3e92c26acd8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 16:45:09 +1000 Subject: [PATCH 164/266] Add a page collection to layouts, and start porting composer page items --- python/core/core_auto.sip | 2 + python/core/layout/qgslayout.sip | 7 ++ python/core/layout/qgslayoutitempage.sip | 42 ++++++++++ .../core/layout/qgslayoutpagecollection.sip | 58 +++++++++++++ src/core/CMakeLists.txt | 4 + src/core/layout/qgslayout.cpp | 7 ++ src/core/layout/qgslayout.h | 9 ++ src/core/layout/qgslayoutitempage.cpp | 83 +++++++++++++++++++ src/core/layout/qgslayoutitempage.h | 49 +++++++++++ src/core/layout/qgslayoutpagecollection.cpp | 48 +++++++++++ src/core/layout/qgslayoutpagecollection.h | 75 +++++++++++++++++ tests/src/python/CMakeLists.txt | 1 + .../python/test_qgslayoutpagecollection.py | 54 ++++++++++++ 13 files changed, 439 insertions(+) create mode 100644 python/core/layout/qgslayoutitempage.sip create mode 100644 python/core/layout/qgslayoutpagecollection.sip create mode 100644 src/core/layout/qgslayoutitempage.cpp create mode 100644 src/core/layout/qgslayoutitempage.h create mode 100644 src/core/layout/qgslayoutpagecollection.cpp create mode 100644 src/core/layout/qgslayoutpagecollection.h create mode 100644 tests/src/python/test_qgslayoutpagecollection.py diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 152d0308006..eab43cc9702 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -382,8 +382,10 @@ %Include layout/qgslayout.sip %Include layout/qgslayoutitem.sip %Include layout/qgslayoutitemmap.sip +%Include layout/qgslayoutitempage.sip %Include layout/qgslayoutitemregistry.sip %Include layout/qgslayoutitemshape.sip +%Include layout/qgslayoutpagecollection.sip %Include layout/qgslayoutobject.sip %Include symbology-ng/qgscptcityarchive.sip %Include symbology-ng/qgssvgcache.sip diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index ed584efa664..67bea024d1a 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -183,6 +183,13 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator void setReferenceMap( QgsLayoutItemMap *map ); + QgsLayoutPageCollection *pageCollection(); +%Docstring + Returns a pointer to the layout's page collection, which stores and manages + page items in the layout. + :rtype: QgsLayoutPageCollection +%End + signals: void variablesChanged(); diff --git a/python/core/layout/qgslayoutitempage.sip b/python/core/layout/qgslayoutitempage.sip new file mode 100644 index 00000000000..a47da41c5cb --- /dev/null +++ b/python/core/layout/qgslayoutitempage.sip @@ -0,0 +1,42 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitempage.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsLayoutItemPage : QgsLayoutItem +{ +%Docstring + Item representing the paper in a layout. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitempage.h" +%End + public: + + explicit QgsLayoutItemPage( QgsLayout *layout /TransferThis/ ); +%Docstring + Constructor for QgsLayoutItemPage, with the specified parent ``layout``. +%End + virtual int type() const; + virtual QString stringType() const; + + protected: + + virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutitempage.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip new file mode 100644 index 00000000000..df86585e1b8 --- /dev/null +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -0,0 +1,58 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutpagecollection.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsLayoutPageCollection : QObject +{ +%Docstring + A manager for a collection of pages in a layout. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutpagecollection.h" +%End + public: + + explicit QgsLayoutPageCollection( QgsLayout *layout /TransferThis/ ); +%Docstring + Constructor for QgsLayoutItemPage, with the specified parent ``layout``. +%End + + QgsLayout *layout() const; +%Docstring + Returns the layout this collection belongs to. + :rtype: QgsLayout +%End + + void setPageStyleSymbol( QgsFillSymbol *symbol ); +%Docstring + Sets the ``symbol`` to use for drawing pages in the collection. + + Ownership is not transferred, and a copy of the symbol is created internally. +.. seealso:: pageStyleSymbol() +%End + + const QgsFillSymbol *pageStyleSymbol() const; +%Docstring + Returns the symbol to use for drawing pages in the collection. +.. seealso:: setPageStyleSymbol() + :rtype: QgsFillSymbol +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutpagecollection.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7009a8326cb..b529ef36066 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -353,11 +353,13 @@ SET(QGIS_CORE_SRCS layout/qgslayoutcontext.cpp layout/qgslayoutitem.cpp layout/qgslayoutitemmap.cpp + layout/qgslayoutitempage.cpp layout/qgslayoutitemregistry.cpp layout/qgslayoutitemshape.cpp layout/qgslayoutmeasurement.cpp layout/qgslayoutmeasurementconverter.cpp layout/qgslayoutobject.cpp + layout/qgslayoutpagecollection.cpp layout/qgslayoututils.cpp layout/qgspagesizeregistry.cpp layout/qgslayoutpoint.cpp @@ -680,8 +682,10 @@ SET(QGIS_CORE_MOC_HDRS layout/qgslayout.h layout/qgslayoutitem.h layout/qgslayoutitemmap.h + layout/qgslayoutitempage.h layout/qgslayoutitemregistry.h layout/qgslayoutitemshape.h + layout/qgslayoutpagecollection.h layout/qgslayoutobject.h symbology-ng/qgscptcityarchive.h diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index d569ee5cdba..1f071d46799 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -15,10 +15,12 @@ ***************************************************************************/ #include "qgslayout.h" +#include "qgslayoutpagecollection.h" QgsLayout::QgsLayout( QgsProject *project ) : QGraphicsScene() , mProject( project ) + , mPageCollection( new QgsLayoutPageCollection( this ) ) {} QgsProject *QgsLayout::project() const @@ -103,3 +105,8 @@ void QgsLayout::setReferenceMap( QgsLayoutItemMap *map ) { Q_UNUSED( map ); } + +QgsLayoutPageCollection *QgsLayout::pageCollection() +{ + return mPageCollection.get(); +} diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index adfce6f0f9e..ffbf1bf9373 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -20,6 +20,7 @@ #include #include "qgslayoutcontext.h" #include "qgsexpressioncontextgenerator.h" +#include "qgslayoutpagecollection.h" class QgsLayoutItemMap; @@ -203,6 +204,12 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext //TODO void setReferenceMap( QgsLayoutItemMap *map ); + /** + * Returns a pointer to the layout's page collection, which stores and manages + * page items in the layout. + */ + QgsLayoutPageCollection *pageCollection(); + signals: /** @@ -221,6 +228,8 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext QgsUnitTypes::LayoutUnit mUnits = QgsUnitTypes::LayoutMillimeters; QgsLayoutContext mContext; + std::unique_ptr< QgsLayoutPageCollection > mPageCollection; + }; #endif //QGSLAYOUT_H diff --git a/src/core/layout/qgslayoutitempage.cpp b/src/core/layout/qgslayoutitempage.cpp new file mode 100644 index 00000000000..b8e0e82f273 --- /dev/null +++ b/src/core/layout/qgslayoutitempage.cpp @@ -0,0 +1,83 @@ +/*************************************************************************** + qgslayoutitempage.cpp + --------------------- + begin : July 2017 + copyright : (C) 2017 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 "qgslayoutitempage.h" +#include "qgslayout.h" +#include "qgslayoututils.h" +#include + +QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) + : QgsLayoutItem( layout ) +{ + +} + +void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle ) +{ +#if 0 + Q_UNUSED( itemStyle ); + Q_UNUSED( pWidget ); + if ( !painter || !mComposition || !mComposition->pagesVisible() ) + { + return; + } + + //setup painter scaling to dots so that raster symbology is drawn to scale + double dotsPerMM = painter->device()->logicalDpiX() / 25.4; + + //setup render context + QgsRenderContext context = QgsComposerUtils::createRenderContextForComposition( mComposition, painter ); + context.setForceVectorOutput( true ); + + QgsExpressionContext expressionContext = createExpressionContext(); + context.setExpressionContext( expressionContext ); + + painter->save(); + + if ( mComposition->plotStyle() == QgsComposition::Preview ) + { + //if in preview mode, draw page border and shadow so that it's + //still possible to tell where pages with a transparent style begin and end + painter->setRenderHint( QPainter::Antialiasing, false ); + + //shadow + painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) ); + painter->setPen( Qt::NoPen ); + painter->drawRect( QRectF( 1, 1, rect().width() + 1, rect().height() + 1 ) ); + + //page area + painter->setBrush( QColor( 215, 215, 215 ) ); + QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 ); + pagePen.setCosmetic( true ); + painter->setPen( pagePen ); + painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) ); + } + + painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots + + painter->setRenderHint( QPainter::Antialiasing ); + mComposition->pageStyleSymbol()->startRender( context ); + + calculatePageMargin(); + QPolygonF pagePolygon = QPolygonF( QRectF( mPageMargin * dotsPerMM, mPageMargin * dotsPerMM, + ( rect().width() - 2 * mPageMargin ) * dotsPerMM, ( rect().height() - 2 * mPageMargin ) * dotsPerMM ) ); + QList rings; //empty list + + mComposition->pageStyleSymbol()->renderPolygon( pagePolygon, &rings, nullptr, context ); + mComposition->pageStyleSymbol()->stopRender( context ); + painter->restore(); +#endif +} diff --git a/src/core/layout/qgslayoutitempage.h b/src/core/layout/qgslayoutitempage.h new file mode 100644 index 00000000000..6b85c2bb840 --- /dev/null +++ b/src/core/layout/qgslayoutitempage.h @@ -0,0 +1,49 @@ +/*************************************************************************** + qgslayoutitempage.h + -------------------- + begin : July 2017 + copyright : (C) 2017 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 QGSLAYOUTITEMPAGE_H +#define QGSLAYOUTITEMPAGE_H + +#include "qgis_core.h" +#include "qgslayoutitem.h" +#include "qgslayoutitemregistry.h" + +/** + * \ingroup core + * \class QgsLayoutItemPage + * \brief Item representing the paper in a layout. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutItemPage, with the specified parent \a layout. + */ + explicit QgsLayoutItemPage( QgsLayout *layout SIP_TRANSFERTHIS ); + int type() const override { return QgsLayoutItemRegistry::LayoutPage; } + QString stringType() const override { return QStringLiteral( "ItemPaper" ); } + + protected: + + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; +}; + +#endif //QGSLAYOUTITEMPAGE_H diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp new file mode 100644 index 00000000000..21f82e45925 --- /dev/null +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -0,0 +1,48 @@ +/*************************************************************************** + qgslayoutpagecollection.cpp + ---------------------------- + begin : July 2017 + copyright : (C) 2017 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 "qgslayoutpagecollection.h" +#include "qgslayout.h" + +QgsLayoutPageCollection::QgsLayoutPageCollection( QgsLayout *layout ) + : QObject( layout ) + , mLayout( layout ) +{ + createDefaultPageStyleSymbol(); +} + +void QgsLayoutPageCollection::setPageStyleSymbol( QgsFillSymbol *symbol ) +{ + if ( !symbol ) + return; + + mPageStyleSymbol.reset( static_cast( symbol->clone() ) ); +} + +QgsLayout *QgsLayoutPageCollection::layout() const +{ + return mLayout; +} + +void QgsLayoutPageCollection::createDefaultPageStyleSymbol() +{ + QgsStringMap properties; + properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) ); + properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) ); + properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) ); + properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) ); + mPageStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) ); +} diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h new file mode 100644 index 00000000000..59578226765 --- /dev/null +++ b/src/core/layout/qgslayoutpagecollection.h @@ -0,0 +1,75 @@ +/*************************************************************************** + qgslayoutpagecollection.h + -------------------------- + begin : July 2017 + copyright : (C) 2017 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 QGSLAYOUTPAGECOLLECTION_H +#define QGSLAYOUTPAGECOLLECTION_H + +#include "qgis_core.h" +#include "qgis_sip.h" +#include "qgssymbol.h" +#include +#include + +class QgsLayout; + +/** + * \ingroup core + * \class QgsLayoutPageCollection + * \brief A manager for a collection of pages in a layout. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutPageCollection : public QObject +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutItemPage, with the specified parent \a layout. + */ + explicit QgsLayoutPageCollection( QgsLayout *layout SIP_TRANSFERTHIS ); + + /** + * Returns the layout this collection belongs to. + */ + QgsLayout *layout() const; + + /** + * Sets the \a symbol to use for drawing pages in the collection. + * + * Ownership is not transferred, and a copy of the symbol is created internally. + * \see pageStyleSymbol() + */ + void setPageStyleSymbol( QgsFillSymbol *symbol ); + + /** + * Returns the symbol to use for drawing pages in the collection. + * \see setPageStyleSymbol() + */ + const QgsFillSymbol *pageStyleSymbol() const { return mPageStyleSymbol.get(); } + + private: + + QgsLayout *mLayout = nullptr; + + //! Symbol for drawing pages + std::unique_ptr< QgsFillSymbol > mPageStyleSymbol; + + void createDefaultPageStyleSymbol(); +}; + +#endif //QGSLAYOUTPAGECOLLECTION_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 03131951c2f..b7f5f22eeea 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -75,6 +75,7 @@ ADD_PYTHON_TEST(PyQgsLayerMetadata test_qgslayermetadata.py) ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.py) ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py) ADD_PYTHON_TEST(PyQgsLayoutManager test_qgslayoutmanager.py) +ADD_PYTHON_TEST(PyQgsLayoutPageCollection test_qgslayoutpagecollection.py) ADD_PYTHON_TEST(PyQgsLayoutView test_qgslayoutview.py) ADD_PYTHON_TEST(PyQgsLayoutItemPropertiesDialog test_qgslayoutitempropertiesdialog.py) ADD_PYTHON_TEST(PyQgsLayoutUnitsComboBox test_qgslayoutunitscombobox.py) diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py new file mode 100644 index 00000000000..e68cdfb0e87 --- /dev/null +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsLayoutPageCollection + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '18/07/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.core import QgsUnitTypes, QgsLayout, QgsProject, QgsLayoutPageCollection, QgsSimpleFillSymbolLayer, QgsFillSymbol +from qgis.PyQt.QtCore import Qt +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsLayoutPageCollection(unittest.TestCase): + + def testLayout(self): + # test that layouts have a collection + p = QgsProject() + l = QgsLayout(p) + self.assertTrue(l.pageCollection()) + self.assertEqual(l.pageCollection().layout(), l) + + def testSymbol(self): + """ + Test setting a page symbol for the collection + """ + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + self.assertTrue(collection.pageStyleSymbol()) + + fill = QgsSimpleFillSymbolLayer() + fill_symbol = QgsFillSymbol() + fill_symbol.changeSymbolLayer(0, fill) + fill.setColor(Qt.green) + fill.setStrokeColor(Qt.red) + fill.setStrokeWidth(6) + collection.setPageStyleSymbol(fill_symbol) + self.assertEqual(collection.pageStyleSymbol().symbolLayer(0).color().name(), '#00ff00') + self.assertEqual(collection.pageStyleSymbol().symbolLayer(0).strokeColor().name(), '#ff0000') + + +if __name__ == '__main__': + unittest.main() From 534f7ab6475bc74c9d361c338624815f0880cd0a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 17:15:09 +1000 Subject: [PATCH 165/266] wip page rendering --- src/core/layout/qgslayout.cpp | 4 ++- src/core/layout/qgslayoutitem.cpp | 2 +- src/core/layout/qgslayoutitempage.cpp | 46 ++++++++++++++------------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index 1f071d46799..1cd5e582f86 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -21,7 +21,9 @@ QgsLayout::QgsLayout( QgsProject *project ) : QGraphicsScene() , mProject( project ) , mPageCollection( new QgsLayoutPageCollection( this ) ) -{} +{ + setBackgroundBrush( QColor( 215, 215, 215 ) ); +} QgsProject *QgsLayout::project() const { diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 53abde383ed..132ddf24dc2 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -78,7 +78,7 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it } double destinationDpi = itemStyle->matrix.m11() * 25.4; - bool useImageCache = true; + bool useImageCache = false; if ( useImageCache ) { diff --git a/src/core/layout/qgslayoutitempage.cpp b/src/core/layout/qgslayoutitempage.cpp index b8e0e82f273..1f1d3075b69 100644 --- a/src/core/layout/qgslayoutitempage.cpp +++ b/src/core/layout/qgslayoutitempage.cpp @@ -17,37 +17,34 @@ #include "qgslayoutitempage.h" #include "qgslayout.h" #include "qgslayoututils.h" +#include "qgssymbollayerutils.h" #include +#define SHADOW_WIDTH_PIXELS 5 QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) : QgsLayoutItem( layout ) { } -void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle ) +void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) { -#if 0 - Q_UNUSED( itemStyle ); - Q_UNUSED( pWidget ); - if ( !painter || !mComposition || !mComposition->pagesVisible() ) + if ( !context.painter() || !mLayout /*|| !mLayout->pagesVisible() */ ) { return; } - //setup painter scaling to dots so that raster symbology is drawn to scale - double dotsPerMM = painter->device()->logicalDpiX() / 25.4; - - //setup render context - QgsRenderContext context = QgsComposerUtils::createRenderContextForComposition( mComposition, painter ); - context.setForceVectorOutput( true ); + double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ); QgsExpressionContext expressionContext = createExpressionContext(); context.setExpressionContext( expressionContext ); + QPainter *painter = context.painter(); painter->save(); +#if 0 //TODO if ( mComposition->plotStyle() == QgsComposition::Preview ) +#endif { //if in preview mode, draw page border and shadow so that it's //still possible to tell where pages with a transparent style begin and end @@ -56,28 +53,33 @@ void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraph //shadow painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) ); painter->setPen( Qt::NoPen ); - painter->drawRect( QRectF( 1, 1, rect().width() + 1, rect().height() + 1 ) ); + painter->drawRect( QRectF( SHADOW_WIDTH_PIXELS, SHADOW_WIDTH_PIXELS, rect().width() * scale + SHADOW_WIDTH_PIXELS, rect().height() * scale + SHADOW_WIDTH_PIXELS ) ); //page area painter->setBrush( QColor( 215, 215, 215 ) ); QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 ); pagePen.setCosmetic( true ); painter->setPen( pagePen ); - painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) ); + painter->drawRect( QRectF( 0, 0, scale * rect().width(), scale * rect().height() ) ); } - painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots + std::unique_ptr< QgsFillSymbol > symbol( mLayout->pageCollection()->pageStyleSymbol()->clone() ); + symbol->startRender( context ); - painter->setRenderHint( QPainter::Antialiasing ); - mComposition->pageStyleSymbol()->startRender( context ); + //get max bleed from symbol + double maxBleedPixels = QgsSymbolLayerUtils::estimateMaxSymbolBleed( symbol.get(), context ); - calculatePageMargin(); - QPolygonF pagePolygon = QPolygonF( QRectF( mPageMargin * dotsPerMM, mPageMargin * dotsPerMM, - ( rect().width() - 2 * mPageMargin ) * dotsPerMM, ( rect().height() - 2 * mPageMargin ) * dotsPerMM ) ); + //Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by + //anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols, + //but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width + maxBleedPixels--; + + QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels, + ( rect().width() * scale - 2 * maxBleedPixels ), ( rect().height() * scale - 2 * maxBleedPixels ) ) ); QList rings; //empty list - mComposition->pageStyleSymbol()->renderPolygon( pagePolygon, &rings, nullptr, context ); - mComposition->pageStyleSymbol()->stopRender( context ); + symbol->renderPolygon( pagePolygon, &rings, nullptr, context ); + symbol->stopRender( context ); + painter->restore(); -#endif } From ea323912809921b95c00286ea96f1a7b0f19490c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 19 Jul 2017 07:46:15 +1000 Subject: [PATCH 166/266] Add methods for handling page size to QgsLayoutItemPage --- python/core/layout/qgslayout.sip | 1 + python/core/layout/qgslayoutitempage.sip | 48 +++++++++ src/core/layout/qgslayout.h | 3 +- src/core/layout/qgslayoutitem.cpp | 2 +- src/core/layout/qgslayoutitempage.cpp | 71 +++++++++++++ src/core/layout/qgslayoutitempage.h | 44 ++++++++ tests/src/core/CMakeLists.txt | 1 + tests/src/core/testqgslayoutpage.cpp | 129 +++++++++++++++++++++++ 8 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 tests/src/core/testqgslayoutpage.cpp diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index 67bea024d1a..e7ab07f20a2 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -22,6 +22,7 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator enum ZValues { + ZPage, ZMapTool, }; diff --git a/python/core/layout/qgslayoutitempage.sip b/python/core/layout/qgslayoutitempage.sip index a47da41c5cb..0d9e190a2f0 100644 --- a/python/core/layout/qgslayoutitempage.sip +++ b/python/core/layout/qgslayoutitempage.sip @@ -20,6 +20,12 @@ class QgsLayoutItemPage : QgsLayoutItem %End public: + enum Orientation + { + Portrait, + Landscape + }; + explicit QgsLayoutItemPage( QgsLayout *layout /TransferThis/ ); %Docstring Constructor for QgsLayoutItemPage, with the specified parent ``layout``. @@ -27,6 +33,48 @@ class QgsLayoutItemPage : QgsLayoutItem virtual int type() const; virtual QString stringType() const; + void setPageSize( const QgsLayoutSize &size ); +%Docstring + Sets the ``size`` of the page. +.. seealso:: pageSize() +%End + + bool setPageSize( const QString &size, Orientation orientation = Portrait ); +%Docstring + Sets the page size to a known page ``size``, e.g. "A4" and ``orientation``. + The known page sizes are managed by QgsPageSizeRegistry. Valid page sizes + can be retrieved via QgsPageSizeRegistry.entries(). + The function returns true if ``size`` was a valid page size and the page + size was changed. If false is returned then ``size`` could not be matched + to a known page size. +.. seealso:: pageSize() + :rtype: bool +%End + + QgsLayoutSize pageSize() const; +%Docstring + Returns the size of the page. +.. seealso:: setPageSize() + :rtype: QgsLayoutSize +%End + + Orientation orientation() const; +%Docstring + Returns the page orientiation. +.. note:: + + There is no direct setter for page orientation - use setPageSize() instead. + :rtype: Orientation +%End + + static QgsLayoutItemPage::Orientation decodePageOrientation( const QString &string, bool *ok /Out/ = 0 ); +%Docstring + Decodes a ``string`` representing a page orientation. If specified, ``ok`` + will be set to true if string could be successfully interpreted as a + page orientation. + :rtype: QgsLayoutItemPage.Orientation +%End + protected: virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index ffbf1bf9373..5c79b5e1979 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -39,7 +39,8 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext //! Preset item z-values, to ensure correct stacking enum ZValues { - ZMapTool = 10000, //!< Z-Value for temporary map tool items + ZPage = 0, //!< Z-value for page (paper) items + ZMapTool = 10000, //!< Z-value for temporary map tool items }; /** diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 132ddf24dc2..53abde383ed 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -78,7 +78,7 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it } double destinationDpi = itemStyle->matrix.m11() * 25.4; - bool useImageCache = false; + bool useImageCache = true; if ( useImageCache ) { diff --git a/src/core/layout/qgslayoutitempage.cpp b/src/core/layout/qgslayoutitempage.cpp index 1f1d3075b69..aa60909f5a8 100644 --- a/src/core/layout/qgslayoutitempage.cpp +++ b/src/core/layout/qgslayoutitempage.cpp @@ -17,6 +17,7 @@ #include "qgslayoutitempage.h" #include "qgslayout.h" #include "qgslayoututils.h" +#include "qgspagesizeregistry.h" #include "qgssymbollayerutils.h" #include @@ -24,7 +25,77 @@ QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) : QgsLayoutItem( layout ) { + setFlag( QGraphicsItem::ItemIsSelectable, false ); + setFlag( QGraphicsItem::ItemIsMovable, false ); + setZValue( QgsLayout::ZPage ); +} +void QgsLayoutItemPage::setPageSize( const QgsLayoutSize &size ) +{ + attemptResize( size ); +} + +bool QgsLayoutItemPage::setPageSize( const QString &size, Orientation orientation ) +{ + QgsPageSize newSize; + if ( QgsApplication::pageSizeRegistry()->decodePageSize( size, newSize ) ) + { + switch ( orientation ) + { + case Portrait: + break; // nothing to do + + case Landscape: + { + // flip height and width + double x = newSize.size.width(); + newSize.size.setWidth( newSize.size.height() ); + newSize.size.setHeight( x ); + break; + } + } + + setPageSize( newSize.size ); + return true; + } + else + { + return false; + } +} + +QgsLayoutSize QgsLayoutItemPage::pageSize() const +{ + return sizeWithUnits(); +} + +QgsLayoutItemPage::Orientation QgsLayoutItemPage::orientation() const +{ + if ( sizeWithUnits().width() >= sizeWithUnits().height() ) + return Landscape; + else + return Portrait; +} + +QgsLayoutItemPage::Orientation QgsLayoutItemPage::decodePageOrientation( const QString &string, bool *ok ) +{ + if ( ok ) + *ok = false; + + QString trimmedString = string.trimmed(); + if ( trimmedString.compare( QStringLiteral( "portrait" ), Qt::CaseInsensitive ) == 0 ) + { + if ( ok ) + *ok = true; + return Portrait; + } + else if ( trimmedString.compare( QStringLiteral( "landscape" ), Qt::CaseInsensitive ) == 0 ) + { + if ( ok ) + *ok = true; + return Landscape; + } + return Landscape; } void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) diff --git a/src/core/layout/qgslayoutitempage.h b/src/core/layout/qgslayoutitempage.h index 6b85c2bb840..6c062ee562e 100644 --- a/src/core/layout/qgslayoutitempage.h +++ b/src/core/layout/qgslayoutitempage.h @@ -20,6 +20,7 @@ #include "qgis_core.h" #include "qgslayoutitem.h" #include "qgslayoutitemregistry.h" +#include "qgis_sip.h" /** * \ingroup core @@ -34,6 +35,13 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem public: + //! Page orientiation + enum Orientation + { + Portrait, //!< Portrait orientation + Landscape //!< Landscape orientation + }; + /** * Constructor for QgsLayoutItemPage, with the specified parent \a layout. */ @@ -41,6 +49,42 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem int type() const override { return QgsLayoutItemRegistry::LayoutPage; } QString stringType() const override { return QStringLiteral( "ItemPaper" ); } + /** + * Sets the \a size of the page. + * \see pageSize() + */ + void setPageSize( const QgsLayoutSize &size ); + + /** + * Sets the page size to a known page \a size, e.g. "A4" and \a orientation. + * The known page sizes are managed by QgsPageSizeRegistry. Valid page sizes + * can be retrieved via QgsPageSizeRegistry::entries(). + * The function returns true if \a size was a valid page size and the page + * size was changed. If false is returned then \a size could not be matched + * to a known page size. + * \see pageSize() + */ + bool setPageSize( const QString &size, Orientation orientation = Portrait ); + + /** + * Returns the size of the page. + * \see setPageSize() + */ + QgsLayoutSize pageSize() const; + + /** + * Returns the page orientiation. + * \note There is no direct setter for page orientation - use setPageSize() instead. + */ + Orientation orientation() const; + + /** + * Decodes a \a string representing a page orientation. If specified, \a ok + * will be set to true if string could be successfully interpreted as a + * page orientation. + */ + static QgsLayoutItemPage::Orientation decodePageOrientation( const QString &string, bool *ok SIP_OUT = nullptr ); + protected: void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 50c9693bc8a..86f196183ce 100755 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -130,6 +130,7 @@ SET(TESTS testqgslayoutcontext.cpp testqgslayoutitem.cpp testqgslayoutobject.cpp + testqgslayoutpage.cpp testqgslayoutunits.cpp testqgslayoututils.cpp testqgslegendrenderer.cpp diff --git a/tests/src/core/testqgslayoutpage.cpp b/tests/src/core/testqgslayoutpage.cpp new file mode 100644 index 00000000000..10cd46d3742 --- /dev/null +++ b/tests/src/core/testqgslayoutpage.cpp @@ -0,0 +1,129 @@ +/*************************************************************************** + testqgslayoutpage.cpp + --------------------- + begin : November 2014 + copyright : (C) 2014 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 "qgslayout.h" +#include "qgslayoutitempage.h" +#include "qgslayoutitemregistry.h" +#include "qgis.h" +#include "qgsproject.h" +#include +#include "qgstest.h" + +class TestQgsLayoutPage : public QObject +{ + Q_OBJECT + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init();// will be called before each testfunction is executed. + void cleanup();// will be called after every testfunction. + void itemType(); + void pageSize(); + void decodePageOrientation(); + + private: + QString mReport; + +}; + +void TestQgsLayoutPage::initTestCase() +{ + mReport = "

Layout Page Tests

\n"; +} + +void TestQgsLayoutPage::cleanupTestCase() +{ + QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html"; + QFile myFile( myReportFile ); + if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) ) + { + QTextStream myQTextStream( &myFile ); + myQTextStream << mReport; + myFile.close(); + } +} + +void TestQgsLayoutPage::init() +{ + +} + +void TestQgsLayoutPage::cleanup() +{ + +} + +void TestQgsLayoutPage::itemType() +{ + QgsProject p; + QgsLayout l( &p ); + QgsLayoutItemPage *page = new QgsLayoutItemPage( &l ); + QCOMPARE( page->type(), static_cast< int >( QgsLayoutItemRegistry::LayoutPage ) ); +} + +void TestQgsLayoutPage::pageSize() +{ + QgsProject p; + QgsLayout l( &p ); + QgsLayoutItemPage *page = new QgsLayoutItemPage( &l ); + page->setPageSize( QgsLayoutSize( 270, 297, QgsUnitTypes::LayoutMeters ) ); + QCOMPARE( page->pageSize().width(), 270.0 ); + QCOMPARE( page->pageSize().height(), 297.0 ); + QCOMPARE( page->pageSize().units(), QgsUnitTypes::LayoutMeters ); + QCOMPARE( page->orientation(), QgsLayoutItemPage::Portrait ); + page->setPageSize( QgsLayoutSize( 297, 270, QgsUnitTypes::LayoutMeters ) ); + QCOMPARE( page->orientation(), QgsLayoutItemPage::Landscape ); + + // from registry + QVERIFY( !page->setPageSize( "hoooyeah" ) ); + // should be unchanged + QCOMPARE( page->pageSize().width(), 297.0 ); + QCOMPARE( page->pageSize().height(), 270.0 ); + QCOMPARE( page->pageSize().units(), QgsUnitTypes::LayoutMeters ); + + // good size + QVERIFY( page->setPageSize( "A5" ) ); + QCOMPARE( page->pageSize().width(), 148.0 ); + QCOMPARE( page->pageSize().height(), 210.0 ); + QCOMPARE( page->pageSize().units(), QgsUnitTypes::LayoutMillimeters ); + QCOMPARE( page->orientation(), QgsLayoutItemPage::Portrait ); + + QVERIFY( page->setPageSize( "A5", QgsLayoutItemPage::Landscape ) ); + QCOMPARE( page->pageSize().width(), 210.0 ); + QCOMPARE( page->pageSize().height(), 148.0 ); + QCOMPARE( page->pageSize().units(), QgsUnitTypes::LayoutMillimeters ); + QCOMPARE( page->orientation(), QgsLayoutItemPage::Landscape ); + +} + +void TestQgsLayoutPage::decodePageOrientation() +{ + //test good string + bool ok = false; + QCOMPARE( QgsLayoutItemPage::decodePageOrientation( QString( " porTrait " ), &ok ), QgsLayoutItemPage::Portrait ); + QVERIFY( ok ); + QCOMPARE( QgsLayoutItemPage::decodePageOrientation( QString( "landscape" ), &ok ), QgsLayoutItemPage::Landscape ); + QVERIFY( ok ); + + //test bad string + QgsLayoutItemPage::decodePageOrientation( QString(), &ok ); + QVERIFY( !ok ); +} + +QGSTEST_MAIN( TestQgsLayoutPage ) +#include "testqgslayoutpage.moc" From 79a46941771b87a6b099c9e5ebaebb61619f9ee2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 19 Jul 2017 18:08:11 +1000 Subject: [PATCH 167/266] Add support for adding/removing pages to a collection --- python/core/layout/qgslayoutitem.sip | 18 +++++ .../core/layout/qgslayoutpagecollection.sip | 60 +++++++++++++++ src/core/layout/qgslayoutitem.h | 22 ++++++ src/core/layout/qgslayoutpagecollection.cpp | 56 ++++++++++++++ src/core/layout/qgslayoutpagecollection.h | 60 +++++++++++++++ .../python/test_qgslayoutpagecollection.py | 75 ++++++++++++++++++- 6 files changed, 289 insertions(+), 2 deletions(-) diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 2bde5e1ede7..8cac4f72a5c 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -18,6 +18,24 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem %TypeHeaderCode #include "qgslayoutitem.h" + +#include +#include +%End + +%ConvertToSubClassCode + // the conversions have to be static, because they're using multiple inheritance + // (seen in PyQt4 .sip files for some QGraphicsItem classes) + switch ( sipCpp->type() ) + { + // really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that! + case QGraphicsItem::UserType + 101: + sipType = sipType_QgsLayoutItemPage; + *sipCppRet = static_cast( sipCpp ); + break; + default: + sipType = 0; + } %End public: diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index df86585e1b8..2c52d753e39 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -26,12 +26,72 @@ class QgsLayoutPageCollection : QObject Constructor for QgsLayoutItemPage, with the specified parent ``layout``. %End + ~QgsLayoutPageCollection(); + QgsLayout *layout() const; %Docstring Returns the layout this collection belongs to. :rtype: QgsLayout %End + QList< QgsLayoutItemPage * > pages(); +%Docstring + Returns a list of pages in the collection. +.. seealso:: page() +.. seealso:: pageCount() + :rtype: list of QgsLayoutItemPage +%End + + int pageCount() const; +%Docstring + Returns the number of pages in the collection. +.. seealso:: pages() + :rtype: int +%End + + QgsLayoutItemPage *page( int pageNumber ); +%Docstring + Returns a specific page (by ``pageNumber``) from the collection. + Internal page numbering starts at 0 - so a ``pageNumber`` of 0 + corresponds to the first page in the collection. + A None is returned if an invalid page number is specified. +.. seealso:: pages() + :rtype: QgsLayoutItemPage +%End + + void addPage( QgsLayoutItemPage *page /Transfer/ ); +%Docstring + Adds a ``page`` to the collection. Ownership of the ``page`` is transferred + to the collection, and the page will automatically be added to the collection's + layout() (there is no need to manually add the page item to the layout). + The page will be added after all pages currently contained in the collection. +.. seealso:: insertPage() +%End + + void insertPage( QgsLayoutItemPage *page /Transfer/, int beforePage ); +%Docstring + Inserts a ``page`` into a specific position in the collection. + + Ownership of the ``page`` is transferred + to the collection, and the page will automatically be added to the collection's + layout() (there is no need to manually add the page item to the layout). + + The page will be added after before the page number specified by ``beforePage``. + (Page numbers in collections begin at 0 - so a ``beforePage`` of 0 will insert + the page before all existing pages). + +.. seealso:: addPage() +%End + + void deletePage( int pageNumber ); +%Docstring + Deletes a page from the collection. The page will automatically be removed + from the collection's layout(). + + Page numbers in collections begin at 0 - so a ``pageNumber`` of 0 will delete + the first page in the collection. +%End + void setPageStyleSymbol( QgsFillSymbol *symbol ); %Docstring Sets the ``symbol`` to use for drawing pages in the collection. diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 1362f5d4534..002660b63cb 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -35,6 +35,28 @@ class QPainter; */ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectItem { +#ifdef SIP_RUN +#include +#include +#endif + + +#ifdef SIP_RUN + SIP_CONVERT_TO_SUBCLASS_CODE + // the conversions have to be static, because they're using multiple inheritance + // (seen in PyQt4 .sip files for some QGraphicsItem classes) + switch ( sipCpp->type() ) + { + // really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that! + case QGraphicsItem::UserType + 101: + sipType = sipType_QgsLayoutItemPage; + *sipCppRet = static_cast( sipCpp ); + break; + default: + sipType = 0; + } + SIP_END +#endif Q_OBJECT diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index 21f82e45925..ee2e5d7930c 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -24,6 +24,15 @@ QgsLayoutPageCollection::QgsLayoutPageCollection( QgsLayout *layout ) createDefaultPageStyleSymbol(); } +QgsLayoutPageCollection::~QgsLayoutPageCollection() +{ + Q_FOREACH ( QgsLayoutItemPage *page, mPages ) + { + mLayout->removeItem( page ); + page->deleteLater(); + } +} + void QgsLayoutPageCollection::setPageStyleSymbol( QgsFillSymbol *symbol ) { if ( !symbol ) @@ -37,6 +46,53 @@ QgsLayout *QgsLayoutPageCollection::layout() const return mLayout; } +QList QgsLayoutPageCollection::pages() +{ + return mPages; +} + +int QgsLayoutPageCollection::pageCount() const +{ + return mPages.count(); +} + +QgsLayoutItemPage *QgsLayoutPageCollection::page( int pageNumber ) +{ + return mPages.value( pageNumber ); +} + +void QgsLayoutPageCollection::addPage( QgsLayoutItemPage *page ) +{ + mPages.append( page ); + mLayout->addItem( page ); +} + +void QgsLayoutPageCollection::insertPage( QgsLayoutItemPage *page, int beforePage ) +{ + if ( beforePage < 0 ) + beforePage = 0; + + if ( beforePage >= mPages.count() ) + { + mPages.append( page ); + } + else + { + mPages.insert( beforePage, page ); + } + mLayout->addItem( page ); +} + +void QgsLayoutPageCollection::deletePage( int pageNumber ) +{ + if ( pageNumber < 0 || pageNumber >= mPages.count() ) + return; + + QgsLayoutItemPage *page = mPages.takeAt( pageNumber ); + mLayout->removeItem( page ); + page->deleteLater(); +} + void QgsLayoutPageCollection::createDefaultPageStyleSymbol() { QgsStringMap properties; diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index 59578226765..a1eb44e57c7 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -20,6 +20,7 @@ #include "qgis_core.h" #include "qgis_sip.h" #include "qgssymbol.h" +#include "qgslayoutitempage.h" #include #include @@ -43,11 +44,68 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject */ explicit QgsLayoutPageCollection( QgsLayout *layout SIP_TRANSFERTHIS ); + ~QgsLayoutPageCollection(); + /** * Returns the layout this collection belongs to. */ QgsLayout *layout() const; + /** + * Returns a list of pages in the collection. + * \see page() + * \see pageCount() + */ + QList< QgsLayoutItemPage * > pages(); + + /** + * Returns the number of pages in the collection. + * \see pages() + */ + int pageCount() const; + + /** + * Returns a specific page (by \a pageNumber) from the collection. + * Internal page numbering starts at 0 - so a \a pageNumber of 0 + * corresponds to the first page in the collection. + * A nullptr is returned if an invalid page number is specified. + * \see pages() + */ + QgsLayoutItemPage *page( int pageNumber ); + + /** + * Adds a \a page to the collection. Ownership of the \a page is transferred + * to the collection, and the page will automatically be added to the collection's + * layout() (there is no need to manually add the page item to the layout). + * The page will be added after all pages currently contained in the collection. + * \see insertPage() + */ + void addPage( QgsLayoutItemPage *page SIP_TRANSFER ); + + /** + * Inserts a \a page into a specific position in the collection. + * + * Ownership of the \a page is transferred + * to the collection, and the page will automatically be added to the collection's + * layout() (there is no need to manually add the page item to the layout). + * + * The page will be added after before the page number specified by \a beforePage. + * (Page numbers in collections begin at 0 - so a \a beforePage of 0 will insert + * the page before all existing pages). + * + * \see addPage() + */ + void insertPage( QgsLayoutItemPage *page SIP_TRANSFER, int beforePage ); + + /** + * Deletes a page from the collection. The page will automatically be removed + * from the collection's layout(). + * + * Page numbers in collections begin at 0 - so a \a pageNumber of 0 will delete + * the first page in the collection. + */ + void deletePage( int pageNumber ); + /** * Sets the \a symbol to use for drawing pages in the collection. * @@ -69,6 +127,8 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject //! Symbol for drawing pages std::unique_ptr< QgsFillSymbol > mPageStyleSymbol; + QList< QgsLayoutItemPage * > mPages; + void createDefaultPageStyleSymbol(); }; diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index e68cdfb0e87..fb762538de0 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -13,9 +13,10 @@ __copyright__ = 'Copyright 2017, The QGIS Project' __revision__ = '$Format:%H$' import qgis # NOQA +import sip -from qgis.core import QgsUnitTypes, QgsLayout, QgsProject, QgsLayoutPageCollection, QgsSimpleFillSymbolLayer, QgsFillSymbol -from qgis.PyQt.QtCore import Qt +from qgis.core import QgsUnitTypes, QgsLayout, QgsLayoutItemPage, QgsProject, QgsLayoutPageCollection, QgsSimpleFillSymbolLayer, QgsFillSymbol +from qgis.PyQt.QtCore import Qt, QCoreApplication, QEvent from qgis.testing import start_app, unittest start_app() @@ -49,6 +50,76 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertEqual(collection.pageStyleSymbol().symbolLayer(0).color().name(), '#00ff00') self.assertEqual(collection.pageStyleSymbol().symbolLayer(0).strokeColor().name(), '#ff0000') + def testPages(self): + """ + Test adding/retrieving/deleting pages from the collection + """ + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + + self.assertEqual(collection.pageCount(), 0) + self.assertFalse(collection.pages()) + self.assertFalse(collection.page(-1)) + self.assertFalse(collection.page(0)) + self.assertFalse(collection.page(1)) + + # add a page + page = QgsLayoutItemPage(l) + page.setPageSize('A4') + collection.addPage(page) + + self.assertTrue(page in l.items()) + + self.assertEqual(collection.pageCount(), 1) + self.assertEqual(collection.pages(), [page]) + self.assertFalse(collection.page(-1)) + self.assertEqual(collection.page(0), page) + self.assertFalse(collection.page(1)) + + # add a second page + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A5') + collection.addPage(page2) + + self.assertEqual(collection.pageCount(), 2) + self.assertEqual(collection.pages(), [page, page2]) + self.assertFalse(collection.page(-1)) + self.assertEqual(collection.page(0), page) + self.assertEqual(collection.page(1), page2) + + # insert a page + page3 = QgsLayoutItemPage(l) + page3.setPageSize('A3') + collection.insertPage(page3, 1) + self.assertTrue(page3 in l.items()) + + self.assertEqual(collection.pageCount(), 3) + self.assertEqual(collection.pages(), [page, page3, page2]) + self.assertEqual(collection.page(0), page) + self.assertEqual(collection.page(1), page3) + self.assertEqual(collection.page(2), page2) + + # delete page + collection.deletePage(-1) + self.assertEqual(collection.pageCount(), 3) + self.assertEqual(collection.pages(), [page, page3, page2]) + collection.deletePage(100) + self.assertEqual(collection.pageCount(), 3) + self.assertEqual(collection.pages(), [page, page3, page2]) + collection.deletePage(1) + self.assertEqual(collection.pageCount(), 2) + self.assertEqual(collection.pages(), [page, page2]) + + # make sure page was deleted + QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete) + self.assertTrue(sip.isdeleted(page3)) + + del l + QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete) + self.assertTrue(sip.isdeleted(page)) + self.assertTrue(sip.isdeleted(page2)) + if __name__ == '__main__': unittest.main() From 39bf23a5d58eee5454251d25c8fc10f2f3fffcd7 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 19 Jul 2017 19:21:10 +1000 Subject: [PATCH 168/266] Start on reflow support for page collections (needs tests) --- python/core/layout/qgslayout.sip | 33 +++++ .../core/layout/qgslayoutpagecollection.sip | 20 +++ src/core/layout/qgslayout.cpp | 53 ++++++++ src/core/layout/qgslayout.h | 32 +++++ src/core/layout/qgslayoutpagecollection.cpp | 28 +++++ src/core/layout/qgslayoutpagecollection.h | 19 +++ src/gui/layout/qgslayoutviewtooladditem.cpp | 2 +- tests/src/core/testqgslayout.cpp | 115 ++++++++++++++++++ .../python/test_qgslayoutpagecollection.py | 89 +++++++++++++- 9 files changed, 389 insertions(+), 2 deletions(-) diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index e7ab07f20a2..29e013baa73 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -29,6 +29,16 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator QgsLayout( QgsProject *project ); %Docstring Construct a new layout linked to the specified ``project``. + + If the layout is a "new" layout (as opposed to a layout which will + restore a previous state from XML) then initializeDefaults() should be + called on the new layout. +%End + + void initializeDefaults(); +%Docstring + Initializes an empty layout, e.g. by adding a default page to the layout. This should be called after creating + a new layout. %End QgsProject *project() const; @@ -191,6 +201,29 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator :rtype: QgsLayoutPageCollection %End + QRectF layoutBounds( bool ignorePages = false, double margin = 0.0 ) const; +%Docstring + Calculates the bounds of all non-gui items in the layout. Ignores snap lines, mouse handles + and other cosmetic items. + \param ignorePages set to true to ignore page items + \param margin optional marginal (in percent, e.g., 0.05 = 5% ) to add around items + :return: layout bounds, in layout units. + :rtype: QRectF +%End + + void addLayoutItem( QgsLayoutItem *item /Transfer/ ); +%Docstring + Adds an ``item`` to the layout. This should be called instead of the base class addItem() + method. Ownership of the item is transferred to the layout. +%End + + public slots: + + void updateBounds(); +%Docstring + Updates the scene bounds of the layout. +%End + signals: void variablesChanged(); diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index 2c52d753e39..1963a072863 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -65,6 +65,9 @@ class QgsLayoutPageCollection : QObject to the collection, and the page will automatically be added to the collection's layout() (there is no need to manually add the page item to the layout). The page will be added after all pages currently contained in the collection. + + Calling addPage() automatically triggers a reflow() of pages. + .. seealso:: insertPage() %End @@ -80,6 +83,8 @@ class QgsLayoutPageCollection : QObject (Page numbers in collections begin at 0 - so a ``beforePage`` of 0 will insert the page before all existing pages). + Calling insertPage() automatically triggers a reflow() of pages. + .. seealso:: addPage() %End @@ -90,6 +95,8 @@ class QgsLayoutPageCollection : QObject Page numbers in collections begin at 0 - so a ``pageNumber`` of 0 will delete the first page in the collection. + + Calling deletePage() automatically triggers a reflow() of pages. %End void setPageStyleSymbol( QgsFillSymbol *symbol ); @@ -107,6 +114,19 @@ class QgsLayoutPageCollection : QObject :rtype: QgsFillSymbol %End + void reflow(); +%Docstring + Forces the page collection to reflow the arrangement of pages, e.g. to account + for page size/orientation change. +%End + + double maximumPageWidth() const; +%Docstring + Returns the maximum width of pages in the collection. The returned value is + in layout units. + :rtype: float +%End + }; /************************************************************************ diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index 1cd5e582f86..ac4b7902d28 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -25,6 +25,14 @@ QgsLayout::QgsLayout( QgsProject *project ) setBackgroundBrush( QColor( 215, 215, 215 ) ); } +void QgsLayout::initializeDefaults() +{ + // default to a A4 landscape page + QgsLayoutItemPage *page = new QgsLayoutItemPage( this ); + page->setPageSize( QgsLayoutSize( 297, 210, QgsUnitTypes::LayoutMillimeters ) ); + mPageCollection->addPage( page ); +} + QgsProject *QgsLayout::project() const { return mProject; @@ -112,3 +120,48 @@ QgsLayoutPageCollection *QgsLayout::pageCollection() { return mPageCollection.get(); } + +QRectF QgsLayout::layoutBounds( bool ignorePages, double margin ) const +{ + //start with an empty rectangle + QRectF bounds; + + //add all QgsComposerItems and QgsPaperItems which are in the composition + Q_FOREACH ( const QGraphicsItem *item, items() ) + { + const QgsLayoutItem *layoutItem = dynamic_cast( item ); + if ( !layoutItem ) + continue; + + bool isPage = layoutItem->type() == QgsLayoutItemRegistry::LayoutPage; + if ( !isPage || !ignorePages ) + { + //expand bounds with current item's bounds + if ( bounds.isValid() ) + bounds = bounds.united( item->sceneBoundingRect() ); + else + bounds = item->sceneBoundingRect(); + } + } + + if ( bounds.isValid() && margin > 0.0 ) + { + //finally, expand bounds out by specified margin of page size + double maxWidth = mPageCollection->maximumPageWidth(); + bounds.adjust( -maxWidth * margin, -maxWidth * margin, maxWidth * margin, maxWidth * margin ); + } + + return bounds; + +} + +void QgsLayout::addLayoutItem( QgsLayoutItem *item ) +{ + addItem( item ); + updateBounds(); +} + +void QgsLayout::updateBounds() +{ + setSceneRect( layoutBounds( false, 0.05 ) ); +} diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index 5c79b5e1979..4e580325031 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -45,9 +45,19 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext /** * Construct a new layout linked to the specified \a project. + * + * If the layout is a "new" layout (as opposed to a layout which will + * restore a previous state from XML) then initializeDefaults() should be + * called on the new layout. */ QgsLayout( QgsProject *project ); + /** + * Initializes an empty layout, e.g. by adding a default page to the layout. This should be called after creating + * a new layout. + */ + void initializeDefaults(); + /** * The project associated with the layout. Used to get access to layers, map themes, * relations and various other bits. It is never null. @@ -211,6 +221,28 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext */ QgsLayoutPageCollection *pageCollection(); + /** + * Calculates the bounds of all non-gui items in the layout. Ignores snap lines, mouse handles + * and other cosmetic items. + * \param ignorePages set to true to ignore page items + * \param margin optional marginal (in percent, e.g., 0.05 = 5% ) to add around items + * \returns layout bounds, in layout units. + */ + QRectF layoutBounds( bool ignorePages = false, double margin = 0.0 ) const; + + /** + * Adds an \a item to the layout. This should be called instead of the base class addItem() + * method. Ownership of the item is transferred to the layout. + */ + void addLayoutItem( QgsLayoutItem *item SIP_TRANSFER ); + + public slots: + + /** + * Updates the scene bounds of the layout. + */ + void updateBounds(); + signals: /** diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index ee2e5d7930c..0e338ad448a 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -17,6 +17,8 @@ #include "qgslayoutpagecollection.h" #include "qgslayout.h" +#define SPACE_BETWEEN_PAGES 10 + QgsLayoutPageCollection::QgsLayoutPageCollection( QgsLayout *layout ) : QObject( layout ) , mLayout( layout ) @@ -41,6 +43,29 @@ void QgsLayoutPageCollection::setPageStyleSymbol( QgsFillSymbol *symbol ) mPageStyleSymbol.reset( static_cast( symbol->clone() ) ); } +void QgsLayoutPageCollection::reflow() +{ + double currentY = 0; + QgsLayoutPoint p( 0, 0, mLayout->units() ); + Q_FOREACH ( QgsLayoutItemPage *page, mPages ) + { + page->attemptMove( p ); + currentY += mLayout->convertToLayoutUnits( page->pageSize() ).height() + SPACE_BETWEEN_PAGES; + p.setY( currentY ); + } + mLayout->updateBounds(); +} + +double QgsLayoutPageCollection::maximumPageWidth() const +{ + double maxWidth = 0; + Q_FOREACH ( QgsLayoutItemPage *page, mPages ) + { + maxWidth = qMax( maxWidth, mLayout->convertToLayoutUnits( page->pageSize() ).width() ); + } + return maxWidth; +} + QgsLayout *QgsLayoutPageCollection::layout() const { return mLayout; @@ -65,6 +90,7 @@ void QgsLayoutPageCollection::addPage( QgsLayoutItemPage *page ) { mPages.append( page ); mLayout->addItem( page ); + reflow(); } void QgsLayoutPageCollection::insertPage( QgsLayoutItemPage *page, int beforePage ) @@ -81,6 +107,7 @@ void QgsLayoutPageCollection::insertPage( QgsLayoutItemPage *page, int beforePag mPages.insert( beforePage, page ); } mLayout->addItem( page ); + reflow(); } void QgsLayoutPageCollection::deletePage( int pageNumber ) @@ -91,6 +118,7 @@ void QgsLayoutPageCollection::deletePage( int pageNumber ) QgsLayoutItemPage *page = mPages.takeAt( pageNumber ); mLayout->removeItem( page ); page->deleteLater(); + reflow(); } void QgsLayoutPageCollection::createDefaultPageStyleSymbol() diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index a1eb44e57c7..978d2b9b7d5 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -78,6 +78,9 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject * to the collection, and the page will automatically be added to the collection's * layout() (there is no need to manually add the page item to the layout). * The page will be added after all pages currently contained in the collection. + * + * Calling addPage() automatically triggers a reflow() of pages. + * * \see insertPage() */ void addPage( QgsLayoutItemPage *page SIP_TRANSFER ); @@ -93,6 +96,8 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject * (Page numbers in collections begin at 0 - so a \a beforePage of 0 will insert * the page before all existing pages). * + * Calling insertPage() automatically triggers a reflow() of pages. + * * \see addPage() */ void insertPage( QgsLayoutItemPage *page SIP_TRANSFER, int beforePage ); @@ -103,6 +108,8 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject * * Page numbers in collections begin at 0 - so a \a pageNumber of 0 will delete * the first page in the collection. + * + * Calling deletePage() automatically triggers a reflow() of pages. */ void deletePage( int pageNumber ); @@ -120,6 +127,18 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject */ const QgsFillSymbol *pageStyleSymbol() const { return mPageStyleSymbol.get(); } + /** + * Forces the page collection to reflow the arrangement of pages, e.g. to account + * for page size/orientation change. + */ + void reflow(); + + /** + * Returns the maximum width of pages in the collection. The returned value is + * in layout units. + */ + double maximumPageWidth() const; + private: QgsLayout *mLayout = nullptr; diff --git a/src/gui/layout/qgslayoutviewtooladditem.cpp b/src/gui/layout/qgslayoutviewtooladditem.cpp index 5e008f509dc..b172424e3a6 100644 --- a/src/gui/layout/qgslayoutviewtooladditem.cpp +++ b/src/gui/layout/qgslayoutviewtooladditem.cpp @@ -115,7 +115,7 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even settings.setValue( QStringLiteral( "LayoutDesigner/lastItemHeight" ), item->sizeWithUnits().height() ); settings.setValue( QStringLiteral( "LayoutDesigner/lastSizeUnit" ), static_cast< int >( item->sizeWithUnits().units() ) ); - layout()->addItem( item ); + layout()->addLayoutItem( item ); } void QgsLayoutViewToolAddItem::deactivate() diff --git a/tests/src/core/testqgslayout.cpp b/tests/src/core/testqgslayout.cpp index 61421717145..545053011c4 100644 --- a/tests/src/core/testqgslayout.cpp +++ b/tests/src/core/testqgslayout.cpp @@ -19,6 +19,8 @@ #include "qgstest.h" #include "qgsproject.h" #include "qgslayoutitemmap.h" +#include "qgslayoutitemshape.h" +#include "qgstestutils.h" class TestQgsLayout: public QObject { @@ -36,6 +38,8 @@ class TestQgsLayout: public QObject void variablesEdited(); void scope(); void referenceMap(); + void bounds(); + void addItem(); private: QString mReport; @@ -246,6 +250,117 @@ void TestQgsLayout::referenceMap() } +void TestQgsLayout::bounds() +{ + //add some items to a layout + QgsProject p; + QgsLayout l( &p ); + l.initializeDefaults(); + + QgsLayoutItemRectangularShape *shape1 = new QgsLayoutItemRectangularShape( &l ); + shape1->attemptResize( QgsLayoutSize( 90, 50 ) ); + shape1->attemptMove( QgsLayoutPoint( 90, 50 ) ); + shape1->setItemRotation( 45 ); + l.addLayoutItem( shape1 ); + QgsLayoutItemRectangularShape *shape2 = new QgsLayoutItemRectangularShape( &l ); + shape2->attemptResize( QgsLayoutSize( 110, 50 ) ); + shape2->attemptMove( QgsLayoutPoint( 100, 150 ) ); + l.addLayoutItem( shape2 ); + +#if 0 + QgsLayoutItemRectangularShape *shape3 = new QgsLayoutItemRectangularShape( &l ); + l.addLayoutItem( shape3 ); + shape3->setItemPosition( 210, 30, 50, 100, QgsComposerItem::UpperLeft, false, 2 ); + QgsLayoutItemRectangularShape *shape4 = new QgsLayoutItemRectangularShape( &l ); + l.addLayoutItem( shape4 ); + shape4->setItemPosition( 10, 120, 50, 30, QgsComposerItem::UpperLeft, false, 2 ); + shape4->setVisibility( false ); +#endif + + //check bounds + QRectF layoutBounds = l.layoutBounds( false ); +#if 0 // correct values when 2nd page items are added back in + QGSCOMPARENEAR( layoutBounds.height(), 372.15, 0.01 ); + QGSCOMPARENEAR( layoutBounds.width(), 301.00, 0.01 ); + QGSCOMPARENEAR( layoutBounds.left(), -2, 0.01 ); + QGSCOMPARENEAR( layoutBounds.top(), -2, 0.01 ); + + QRectF compositionBoundsNoPage = l.layoutBounds( true ); + QGSCOMPARENEAR( compositionBoundsNoPage.height(), 320.36, 0.01 ); + QGSCOMPARENEAR( compositionBoundsNoPage.width(), 250.30, 0.01 ); + QGSCOMPARENEAR( compositionBoundsNoPage.left(), 9.85, 0.01 ); + QGSCOMPARENEAR( compositionBoundsNoPage.top(), 49.79, 0.01 ); +#endif + + QGSCOMPARENEAR( layoutBounds.height(), 211.000000, 0.01 ); + QGSCOMPARENEAR( layoutBounds.width(), 298.000000, 0.01 ); + QGSCOMPARENEAR( layoutBounds.left(), -0.500000, 0.01 ); + QGSCOMPARENEAR( layoutBounds.top(), -0.500000, 0.01 ); + + QRectF compositionBoundsNoPage = l.layoutBounds( true ); + QGSCOMPARENEAR( compositionBoundsNoPage.height(), 175.704581, 0.01 ); + QGSCOMPARENEAR( compositionBoundsNoPage.width(), 125.704581, 0.01 ); + QGSCOMPARENEAR( compositionBoundsNoPage.left(), 84.795419, 0.01 ); + QGSCOMPARENEAR( compositionBoundsNoPage.top(), 24.795419, 0.01 ); + +#if 0 + QRectF page1Bounds = composition->pageItemBounds( 0, true ); + QGSCOMPARENEAR( page1Bounds.height(), 150.36, 0.01 ); + QGSCOMPARENEAR( page1Bounds.width(), 155.72, 0.01 ); + QGSCOMPARENEAR( page1Bounds.left(), 54.43, 0.01 ); + QGSCOMPARENEAR( page1Bounds.top(), 49.79, 0.01 ); + + QRectF page2Bounds = composition->pageItemBounds( 1, true ); + QGSCOMPARENEAR( page2Bounds.height(), 100.30, 0.01 ); + QGSCOMPARENEAR( page2Bounds.width(), 50.30, 0.01 ); + QGSCOMPARENEAR( page2Bounds.left(), 209.85, 0.01 ); + QGSCOMPARENEAR( page2Bounds.top(), 249.85, 0.01 ); + + QRectF page2BoundsWithHidden = composition->pageItemBounds( 1, false ); + QGSCOMPARENEAR( page2BoundsWithHidden.height(), 120.30, 0.01 ); + QGSCOMPARENEAR( page2BoundsWithHidden.width(), 250.30, 0.01 ); + QGSCOMPARENEAR( page2BoundsWithHidden.left(), 9.85, 0.01 ); + QGSCOMPARENEAR( page2BoundsWithHidden.top(), 249.85, 0.01 ); +#endif +} + +void TestQgsLayout::addItem() +{ + QgsProject p; + QgsLayout l( &p ); + l.pageCollection()->deletePage( 0 ); + + QgsLayoutItemRectangularShape *shape1 = new QgsLayoutItemRectangularShape( &l ); + shape1->attemptResize( QgsLayoutSize( 140, 70 ) ); + shape1->attemptMove( QgsLayoutPoint( 90, 50 ) ); + + l.addLayoutItem( shape1 ); + QVERIFY( l.items().contains( shape1 ) ); + // bounds should be updated to include item + QGSCOMPARENEAR( l.sceneRect().left(), 89.5, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().top(), 49.5, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().width(), 141, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().height(), 71, 0.001 ); + + QgsLayoutItemRectangularShape *shape2 = new QgsLayoutItemRectangularShape( &l ); + shape2->attemptResize( QgsLayoutSize( 240, 170 ) ); + shape2->attemptMove( QgsLayoutPoint( 30, 20 ) ); + + // don't use addLayoutItem - we want to manually trigger a bounds update + l.addItem( shape2 ); + QGSCOMPARENEAR( l.sceneRect().left(), 89.5, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().top(), 49.5, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().width(), 141, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().height(), 71, 0.001 ); + + l.updateBounds(); + // bounds should be updated to include item + QGSCOMPARENEAR( l.sceneRect().left(), 29.5, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().top(), 19.5, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().width(), 241, 0.001 ); + QGSCOMPARENEAR( l.sceneRect().height(), 171, 0.001 ); +} + QGSTEST_MAIN( TestQgsLayout ) #include "testqgslayout.moc" diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index fb762538de0..7be0d6aace2 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -15,7 +15,14 @@ __revision__ = '$Format:%H$' import qgis # NOQA import sip -from qgis.core import QgsUnitTypes, QgsLayout, QgsLayoutItemPage, QgsProject, QgsLayoutPageCollection, QgsSimpleFillSymbolLayer, QgsFillSymbol +from qgis.core import (QgsUnitTypes, + QgsLayout, + QgsLayoutItemPage, + QgsLayoutSize, + QgsProject, + QgsLayoutPageCollection, + QgsSimpleFillSymbolLayer, + QgsFillSymbol) from qgis.PyQt.QtCore import Qt, QCoreApplication, QEvent from qgis.testing import start_app, unittest @@ -120,6 +127,86 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertTrue(sip.isdeleted(page)) self.assertTrue(sip.isdeleted(page2)) + def testMaxPageWidth(self): + """ + Test calculating maximum page width + """ + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + + # add a page + page = QgsLayoutItemPage(l) + page.setPageSize('A4') + collection.addPage(page) + self.assertEqual(collection.maximumPageWidth(), 210.0) + + # add a second page + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A3') + collection.addPage(page2) + self.assertEqual(collection.maximumPageWidth(), 297.0) + + # add a page with other units + page3 = QgsLayoutItemPage(l) + page3.setPageSize(QgsLayoutSize(100, 100, QgsUnitTypes.LayoutMeters)) + collection.addPage(page3) + self.assertEqual(collection.maximumPageWidth(), 100000.0) + + def testReflow(self): + """ + Test reflowing pages + """ + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + + #add a page + page = QgsLayoutItemPage(l) + page.setPageSize('A4') + collection.addPage(page) + + #should be positioned at origin + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + + #second page + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A5') + collection.addPage(page2) + + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + self.assertEqual(page2.pos().x(), 0) + self.assertEqual(page2.pos().y(), 307) + + #third page, slotted in middle + page3 = QgsLayoutItemPage(l) + page3.setPageSize('A3') + collection.insertPage(page3, 1) + + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + self.assertEqual(page2.pos().x(), 0) + self.assertEqual(page2.pos().y(), 737) + self.assertEqual(page3.pos().x(), 0) + self.assertEqual(page3.pos().y(), 307) + + page.setPageSize(QgsLayoutSize(100, 120)) + # no update until reflow is called + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + self.assertEqual(page2.pos().x(), 0) + self.assertEqual(page2.pos().y(), 737) + self.assertEqual(page3.pos().x(), 0) + self.assertEqual(page3.pos().y(), 307) + collection.reflow() + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + self.assertEqual(page2.pos().x(), 0) + self.assertEqual(page2.pos().y(), 560) + self.assertEqual(page3.pos().x(), 0) + self.assertEqual(page3.pos().y(), 130) if __name__ == '__main__': unittest.main() From 76b8ba711e1afcaec67fbd311d49c382a912e194 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 19 Jul 2017 22:09:01 +1000 Subject: [PATCH 169/266] Add some tests for data defined page size --- .../python/test_qgslayoutpagecollection.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index 7be0d6aace2..d6829cd5059 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -19,7 +19,9 @@ from qgis.core import (QgsUnitTypes, QgsLayout, QgsLayoutItemPage, QgsLayoutSize, + QgsLayoutObject, QgsProject, + QgsProperty, QgsLayoutPageCollection, QgsSimpleFillSymbolLayer, QgsFillSymbol) @@ -208,5 +210,49 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertEqual(page3.pos().x(), 0) self.assertEqual(page3.pos().y(), 130) + def testDataDefinedSize(self): + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + + # add some pages + page = QgsLayoutItemPage(l) + page.setPageSize('A4') + collection.addPage(page) + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A5') + collection.addPage(page2) + page3 = QgsLayoutItemPage(l) + page3.setPageSize('A5') + collection.addPage(page3) + + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + self.assertEqual(page2.pos().x(), 0) + self.assertEqual(page2.pos().y(), 307) + self.assertEqual(page3.pos().x(), 0) + self.assertEqual(page3.pos().y(), 527) + + page.dataDefinedProperties().setProperty(QgsLayoutObject.ItemHeight, QgsProperty.fromExpression('50*3')) + page.refresh() + collection.reflow() + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + self.assertEqual(page2.pos().x(), 0) + self.assertEqual(page2.pos().y(), 160) + self.assertEqual(page3.pos().x(), 0) + self.assertEqual(page3.pos().y(), 380) + + page2.dataDefinedProperties().setProperty(QgsLayoutObject.ItemHeight, QgsProperty.fromExpression('50-20')) + page2.refresh() + collection.reflow() + self.assertEqual(page.pos().x(), 0) + self.assertEqual(page.pos().y(), 0) + self.assertEqual(page2.pos().x(), 0) + self.assertEqual(page2.pos().y(), 160) + self.assertEqual(page3.pos().x(), 0) + self.assertEqual(page3.pos().y(), 200) + + if __name__ == '__main__': unittest.main() From 8fa173bda8f3a4ec087d35255eb25eca9be5dbf8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jul 2017 09:07:25 +1000 Subject: [PATCH 170/266] wip use page as item parent --- src/core/layout/qgslayoutitem.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 53abde383ed..0b308a8d6a8 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -78,7 +78,7 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it } double destinationDpi = itemStyle->matrix.m11() * 25.4; - bool useImageCache = true; + bool useImageCache = false; if ( useImageCache ) { @@ -224,7 +224,10 @@ void QgsLayoutItem::setScenePos( const QPointF &destinationPos ) //since setPos does not account for item rotation, use difference between //current scenePos (which DOES account for rotation) and destination pos //to calculate how much the item needs to move - setPos( pos() + ( destinationPos - scenePos() ) ); + if ( parentItem() ) + setPos( pos() + ( destinationPos - scenePos() ) + parentItem()->scenePos() ); + else + setPos( pos() + ( destinationPos - scenePos() ) ); } double QgsLayoutItem::itemRotation() const From 3e92e16d765bdc57c9dc234c273f1727283bb8ec Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 13:20:40 +1000 Subject: [PATCH 171/266] Fix page shadow drawing --- src/core/layout/qgslayoutitempage.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/core/layout/qgslayoutitempage.cpp b/src/core/layout/qgslayoutitempage.cpp index aa60909f5a8..9e13e971b7d 100644 --- a/src/core/layout/qgslayoutitempage.cpp +++ b/src/core/layout/qgslayoutitempage.cpp @@ -28,6 +28,14 @@ QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) setFlag( QGraphicsItem::ItemIsSelectable, false ); setFlag( QGraphicsItem::ItemIsMovable, false ); setZValue( QgsLayout::ZPage ); + + // bit hacky - we set a big hidden pen to avoid Qt clipping out the cosmetic shadow for the page + // Unfortunately it's possible to adapt the item's bounding rect based on the view's transform, so it's + // impossible to have a pixel based bounding rect. Instead we just set a big pen to force the page's + // bounding rect to be kinda large enough to handle the shadow at most zoom levels... + QPen shadowPen( QBrush( Qt::transparent ), 30 ); + shadowPen.setCosmetic( true ); + setPen( shadowPen ); } void QgsLayoutItemPage::setPageSize( const QgsLayoutSize &size ) @@ -121,17 +129,20 @@ void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraph //still possible to tell where pages with a transparent style begin and end painter->setRenderHint( QPainter::Antialiasing, false ); + QRectF pageRect = QRectF( 0, 0, scale * rect().width(), scale * rect().height() ); + //shadow painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) ); painter->setPen( Qt::NoPen ); - painter->drawRect( QRectF( SHADOW_WIDTH_PIXELS, SHADOW_WIDTH_PIXELS, rect().width() * scale + SHADOW_WIDTH_PIXELS, rect().height() * scale + SHADOW_WIDTH_PIXELS ) ); + painter->drawRect( pageRect.translated( SHADOW_WIDTH_PIXELS, SHADOW_WIDTH_PIXELS ) ); //page area painter->setBrush( QColor( 215, 215, 215 ) ); QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 ); + pagePen.setJoinStyle( Qt::MiterJoin ); pagePen.setCosmetic( true ); painter->setPen( pagePen ); - painter->drawRect( QRectF( 0, 0, scale * rect().width(), scale * rect().height() ) ); + painter->drawRect( pageRect ); } std::unique_ptr< QgsFillSymbol > symbol( mLayout->pageCollection()->pageStyleSymbol()->clone() ); From 3021fc86d26fdea0c820a84adaeb0b2bcb35d417 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 13:49:39 +1000 Subject: [PATCH 172/266] QgsLayoutUnitComboBox can be linked to spin boxes so that their values are automatically updated when the combo box unit changes This means that you can flip between units and things like the existing width and height are converted immediately to the new unit --- .../qgslayoutnewitempropertiesdialog.sip | 7 ++++ python/gui/layout/qgslayoutunitscombobox.sip | 28 +++++++++++++ .../qgslayoutnewitempropertiesdialog.cpp | 13 ++++++ .../layout/qgslayoutnewitempropertiesdialog.h | 7 ++++ src/gui/layout/qgslayoutunitscombobox.cpp | 36 ++++++++++++++-- src/gui/layout/qgslayoutunitscombobox.h | 41 +++++++++++++++++++ src/gui/layout/qgslayoutviewtooladditem.cpp | 1 + .../src/python/test_qgslayoutunitscombobox.py | 35 +++++++++++++++- 8 files changed, 163 insertions(+), 5 deletions(-) diff --git a/python/gui/layout/qgslayoutnewitempropertiesdialog.sip b/python/gui/layout/qgslayoutnewitempropertiesdialog.sip index a4c5110af04..d31481846d6 100644 --- a/python/gui/layout/qgslayoutnewitempropertiesdialog.sip +++ b/python/gui/layout/qgslayoutnewitempropertiesdialog.sip @@ -69,6 +69,13 @@ class QgsLayoutItemPropertiesDialog : QDialog .. seealso:: referencePoint() %End + void setLayout( QgsLayout *layout ); +%Docstring + Sets the ``layout`` associated with the dialog. This allows the dialog + to retrieve properties from the layout and perform tasks like automatic + conversion of units. +%End + }; /************************************************************************ diff --git a/python/gui/layout/qgslayoutunitscombobox.sip b/python/gui/layout/qgslayoutunitscombobox.sip index 5b7027d8b95..1ee3a111604 100644 --- a/python/gui/layout/qgslayoutunitscombobox.sip +++ b/python/gui/layout/qgslayoutunitscombobox.sip @@ -7,6 +7,7 @@ ************************************************************************/ + class QgsLayoutUnitsComboBox : QComboBox { %Docstring @@ -36,6 +37,33 @@ class QgsLayoutUnitsComboBox : QComboBox %Docstring Sets the ``unit`` currently selected in the combo box. .. seealso:: unit() +%End + + void linkToWidget( QDoubleSpinBox *widget ); +%Docstring + Registers a spin box ``widget`` as linked with the combo box. + + Registered spin boxes will automatically be upodated whenever the unit is changed. I.e. a + spin box with a value of 100 will be set to 1 when the unit is changed from centimeters to meters. + + A measurement converter() must be set in order for the automatic unit conversion to occur. + +.. seealso:: setConverter() +%End + + QgsLayoutMeasurementConverter *converter() const; +%Docstring + Returns the converter used when automatically converting units for linked widgets. +.. seealso:: setConverter() + :rtype: QgsLayoutMeasurementConverter +%End + + void setConverter( QgsLayoutMeasurementConverter *converter ); +%Docstring + Sets a ``converter`` to use when automatically converting units for linked widgets. + The ownership of ``converter`` is not transferred, and converter must exist for the + life of the combo box. +.. seealso:: converter() %End signals: diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp index 0aa0ba4f935..3905208b50e 100644 --- a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp @@ -15,6 +15,8 @@ #include "qgslayoutnewitempropertiesdialog.h" #include "qgssettings.h" +#include "qgslayout.h" + QgsLayoutItemPropertiesDialog::QgsLayoutItemPropertiesDialog( QWidget *parent, Qt::WindowFlags flags ) : QDialog( parent, flags ) @@ -39,6 +41,11 @@ QgsLayoutItemPropertiesDialog::QgsLayoutItemPropertiesDialog( QWidget *parent, Q double lastHeight = settings.value( QStringLiteral( "LayoutDesigner/lastItemHeight" ), QStringLiteral( "50" ) ).toDouble(); QgsUnitTypes::LayoutUnit lastSizeUnit = static_cast< QgsUnitTypes::LayoutUnit >( settings.value( QStringLiteral( "LayoutDesigner/lastSizeUnit" ) ).toInt() ); setItemSize( QgsLayoutSize( lastWidth, lastHeight, lastSizeUnit ) ); + + mPosUnitsComboBox->linkToWidget( mXPosSpin ); + mPosUnitsComboBox->linkToWidget( mYPosSpin ); + mSizeUnitsComboBox->linkToWidget( mWidthSpin ); + mSizeUnitsComboBox->linkToWidget( mHeightSpin ); } void QgsLayoutItemPropertiesDialog::setItemPosition( QgsLayoutPoint position ) @@ -147,3 +154,9 @@ void QgsLayoutItemPropertiesDialog::setReferencePoint( QgsLayoutItem::ReferenceP break; } } + +void QgsLayoutItemPropertiesDialog::setLayout( QgsLayout *layout ) +{ + mSizeUnitsComboBox->setConverter( &layout->context().measurementConverter() ); + mPosUnitsComboBox->setConverter( &layout->context().measurementConverter() ); +} diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.h b/src/gui/layout/qgslayoutnewitempropertiesdialog.h index 736125bfd21..166e61e86be 100644 --- a/src/gui/layout/qgslayoutnewitempropertiesdialog.h +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.h @@ -80,6 +80,13 @@ class GUI_EXPORT QgsLayoutItemPropertiesDialog : public QDialog, private Ui::Qgs */ void setReferencePoint( QgsLayoutItem::ReferencePoint point ); + /** + * Sets the \a layout associated with the dialog. This allows the dialog + * to retrieve properties from the layout and perform tasks like automatic + * conversion of units. + */ + void setLayout( QgsLayout *layout ); + }; #endif // QGSLAYOUTNEWITEMPROPERTIESDIALOG_H diff --git a/src/gui/layout/qgslayoutunitscombobox.cpp b/src/gui/layout/qgslayoutunitscombobox.cpp index 9517c256c64..ba6d1081751 100644 --- a/src/gui/layout/qgslayoutunitscombobox.cpp +++ b/src/gui/layout/qgslayoutunitscombobox.cpp @@ -14,6 +14,7 @@ ***************************************************************************/ #include "qgslayoutunitscombobox.h" +#include "qgslayoutmeasurementconverter.h" QgsLayoutUnitsComboBox::QgsLayoutUnitsComboBox( QWidget *parent ) : QComboBox( parent ) @@ -34,10 +35,7 @@ QgsLayoutUnitsComboBox::QgsLayoutUnitsComboBox( QWidget *parent ) setItemData( 6, tr( "Picas" ), Qt::ToolTipRole ); addItem( tr( "px" ), QgsUnitTypes::LayoutPixels ); setItemData( 7, tr( "Pixels" ), Qt::ToolTipRole ); - connect( this, static_cast( &QgsLayoutUnitsComboBox::currentIndexChanged ), this, [ = ]( int ) - { - emit changed( unit() ); - } ); + connect( this, static_cast( &QgsLayoutUnitsComboBox::currentIndexChanged ), this, &QgsLayoutUnitsComboBox::indexChanged ); } QgsUnitTypes::LayoutUnit QgsLayoutUnitsComboBox::unit() const @@ -50,4 +48,34 @@ void QgsLayoutUnitsComboBox::setUnit( QgsUnitTypes::LayoutUnit unit ) setCurrentIndex( findData( unit ) ); } +void QgsLayoutUnitsComboBox::linkToWidget( QDoubleSpinBox *widget ) +{ + mLinkedSpinBoxes << widget; +} + +void QgsLayoutUnitsComboBox::indexChanged( int ) +{ + QgsUnitTypes::LayoutUnit newUnit = unit(); + if ( mConverter ) + { + Q_FOREACH ( const QPointer< QDoubleSpinBox > &widget, mLinkedSpinBoxes ) + { + if ( widget ) + widget->setValue( mConverter->convert( QgsLayoutMeasurement( widget->value(), mOldUnit ), newUnit ).length() ); + } + } + emit changed( newUnit ); + mOldUnit = newUnit; +} + +QgsLayoutMeasurementConverter *QgsLayoutUnitsComboBox::converter() const +{ + return mConverter; +} + +void QgsLayoutUnitsComboBox::setConverter( QgsLayoutMeasurementConverter *converter ) +{ + mConverter = converter; +} + #include "qgslayoutunitscombobox.h" diff --git a/src/gui/layout/qgslayoutunitscombobox.h b/src/gui/layout/qgslayoutunitscombobox.h index 7fbfff11de9..0df9c1a74e8 100644 --- a/src/gui/layout/qgslayoutunitscombobox.h +++ b/src/gui/layout/qgslayoutunitscombobox.h @@ -19,6 +19,10 @@ #include "qgis_gui.h" #include "qgis_sip.h" #include "qgsunittypes.h" +#include +#include + +class QgsLayoutMeasurementConverter; /** * \ingroup gui @@ -50,6 +54,32 @@ class GUI_EXPORT QgsLayoutUnitsComboBox : public QComboBox */ void setUnit( QgsUnitTypes::LayoutUnit unit ); + /** + * Registers a spin box \a widget as linked with the combo box. + * + * Registered spin boxes will automatically be upodated whenever the unit is changed. I.e. a + * spin box with a value of 100 will be set to 1 when the unit is changed from centimeters to meters. + * + * A measurement converter() must be set in order for the automatic unit conversion to occur. + * + * \see setConverter() + */ + void linkToWidget( QDoubleSpinBox *widget ); + + /** + * Returns the converter used when automatically converting units for linked widgets. + * \see setConverter() + */ + QgsLayoutMeasurementConverter *converter() const; + + /** + * Sets a \a converter to use when automatically converting units for linked widgets. + * The ownership of \a converter is not transferred, and converter must exist for the + * life of the combo box. + * \see converter() + */ + void setConverter( QgsLayoutMeasurementConverter *converter ); + signals: /** @@ -57,6 +87,17 @@ class GUI_EXPORT QgsLayoutUnitsComboBox : public QComboBox */ void changed( QgsUnitTypes::LayoutUnit unit ); + private slots: + + void indexChanged( int index ); + + private: + + QgsLayoutMeasurementConverter *mConverter = nullptr; + + QgsUnitTypes::LayoutUnit mOldUnit = QgsUnitTypes::LayoutMillimeters; + + QList< QPointer< QDoubleSpinBox > > mLinkedSpinBoxes; }; #endif // QGSLAYOUTUNITSCOMBOBOX_H diff --git a/src/gui/layout/qgslayoutviewtooladditem.cpp b/src/gui/layout/qgslayoutviewtooladditem.cpp index b172424e3a6..b4d62970354 100644 --- a/src/gui/layout/qgslayoutviewtooladditem.cpp +++ b/src/gui/layout/qgslayoutviewtooladditem.cpp @@ -90,6 +90,7 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even if ( clickOnly ) { QgsLayoutItemPropertiesDialog dlg( view() ); + dlg.setLayout( layout() ); dlg.setItemPosition( QgsLayoutPoint( event->layoutPoint(), layout()->units() ) ); if ( dlg.exec() ) { diff --git a/tests/src/python/test_qgslayoutunitscombobox.py b/tests/src/python/test_qgslayoutunitscombobox.py index f81c42f390e..e7c2444cac0 100644 --- a/tests/src/python/test_qgslayoutunitscombobox.py +++ b/tests/src/python/test_qgslayoutunitscombobox.py @@ -14,9 +14,11 @@ __revision__ = '$Format:%H$' import qgis # NOQA -from qgis.core import QgsUnitTypes +from qgis.core import QgsUnitTypes, QgsLayoutMeasurementConverter from qgis.gui import QgsLayoutUnitsComboBox +from qgis.PyQt.QtWidgets import QDoubleSpinBox + from qgis.PyQt.QtTest import QSignalSpy from qgis.testing import start_app, unittest @@ -42,6 +44,37 @@ class TestQgsLayoutUnitsComboBox(unittest.TestCase): self.assertEqual(len(spy), 1) self.assertEqual(spy[0][0], QgsUnitTypes.LayoutPixels) + def testLinkedWidgets(self): + """ test linking spin boxes to combobox""" + w = qgis.gui.QgsLayoutUnitsComboBox() + self.assertFalse(w.converter()) + c = QgsLayoutMeasurementConverter() + w.setConverter(c) + self.assertEqual(w.converter(), c) + + spin = QDoubleSpinBox() + spin.setMaximum(1000000) + spin.setValue(100) + w.setUnit(QgsUnitTypes.LayoutCentimeters) + w.linkToWidget(spin) + w.setUnit(QgsUnitTypes.LayoutMeters) + self.assertAlmostEqual(spin.value(), 1.0, 2) + w.setUnit(QgsUnitTypes.LayoutMillimeters) + self.assertAlmostEqual(spin.value(), 1000.0, 2) + + spin2 = QDoubleSpinBox() + spin2.setValue(50) + spin2.setMaximum(1000000) + w.linkToWidget(spin2) + w.setUnit(QgsUnitTypes.LayoutCentimeters) + self.assertAlmostEqual(spin.value(), 100.0, 2) + self.assertAlmostEqual(spin2.value(), 5.0, 2) + + # no crash! + del spin + w.setUnit(QgsUnitTypes.LayoutMeters) + self.assertAlmostEqual(spin2.value(), 0.05, 2) + if __name__ == '__main__': unittest.main() From 36483083d671d1c83c8a9b768506ccadfee24781 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 14:20:26 +1000 Subject: [PATCH 173/266] Add ratio lock for width/height in new item properties dialog --- .../qgslayoutnewitempropertiesdialog.cpp | 3 + src/ui/layout/qgslayoutnewitemproperties.ui | 301 ++++++++++-------- 2 files changed, 173 insertions(+), 131 deletions(-) diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp index 3905208b50e..ac1905bdc6e 100644 --- a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp @@ -46,6 +46,9 @@ QgsLayoutItemPropertiesDialog::QgsLayoutItemPropertiesDialog( QWidget *parent, Q mPosUnitsComboBox->linkToWidget( mYPosSpin ); mSizeUnitsComboBox->linkToWidget( mWidthSpin ); mSizeUnitsComboBox->linkToWidget( mHeightSpin ); + + mLockAspectRatio->setWidthSpinBox( mWidthSpin ); + mLockAspectRatio->setHeightSpinBox( mHeightSpin ); } void QgsLayoutItemPropertiesDialog::setItemPosition( QgsLayoutPoint position ) diff --git a/src/ui/layout/qgslayoutnewitemproperties.ui b/src/ui/layout/qgslayoutnewitemproperties.ui index 3e5a67fdaf6..02c31800334 100644 --- a/src/ui/layout/qgslayoutnewitemproperties.ui +++ b/src/ui/layout/qgslayoutnewitemproperties.ui @@ -13,130 +13,7 @@ New Item Properties - - - - - Position and size - - - - - - - - Height - - - - - - - - - - 3 - - - 9999999.000000000000000 - - - 100.000000000000000 - - - false - - - - - - - - - - 3 - - - -9999999.000000000000000 - - - 9999999.000000000000000 - - - false - - - - - - - X - - - - - - - - - - 3 - - - -9999999.000000000000000 - - - 9999999.000000000000000 - - - false - - - - - - - Y - - - - - - - - - - 3 - - - 9999999.000000000000000 - - - 100.000000000000000 - - - false - - - - - - - Width - - - - - - - - - - - - - - + @@ -322,6 +199,16 @@
+ + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -335,19 +222,170 @@ - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + Position and size + + + + + + + + + + + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + Height + + + + + + + X + + + + + + + Width + + + + + + + + + + Y + + + + + + + 2 + + + 2 + + + 0 + + + 2 + + + + + + 0 + 0 + + + + Lock aspect ratio (including while drawing extent onto canvas) + + + 13 + + + + + + + + + + + + 3 + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + false + + + + + + + + + + 3 + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + false + + + + + + + + QgsRatioLockButton + QToolButton +
qgsratiolockbutton.h
+ 1 +
QgsDoubleSpinBox QDoubleSpinBox @@ -365,6 +403,7 @@ mPosUnitsComboBox mWidthSpin mHeightSpin + mLockAspectRatio mSizeUnitsComboBox mUpperLeftCheckBox mUpperMiddleCheckBox From 5cfc9cc65569418a0beef62b948259ffa8c24661 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 16:29:08 +1000 Subject: [PATCH 174/266] Add a dialog for inserting new pages into a layout --- python/core/layout/qgspagesizeregistry.sip | 7 +- src/app/CMakeLists.txt | 2 + src/app/layout/qgslayoutaddpagesdialog.cpp | 145 ++++++++++ src/app/layout/qgslayoutaddpagesdialog.h | 83 ++++++ src/app/layout/qgslayoutdesignerdialog.cpp | 35 +++ src/app/layout/qgslayoutdesignerdialog.h | 2 + src/core/layout/qgspagesizeregistry.cpp | 61 ++--- src/core/layout/qgspagesizeregistry.h | 5 +- src/gui/layout/qgslayoutunitscombobox.cpp | 2 +- src/ui/layout/qgslayoutdesignerbase.ui | 10 + src/ui/layout/qgslayoutnewpagedialog.ui | 293 +++++++++++++++++++++ 11 files changed, 612 insertions(+), 33 deletions(-) create mode 100644 src/app/layout/qgslayoutaddpagesdialog.cpp create mode 100644 src/app/layout/qgslayoutaddpagesdialog.h create mode 100644 src/ui/layout/qgslayoutnewpagedialog.ui diff --git a/python/core/layout/qgspagesizeregistry.sip b/python/core/layout/qgspagesizeregistry.sip index b365e08079b..c57a4bb16b9 100644 --- a/python/core/layout/qgspagesizeregistry.sip +++ b/python/core/layout/qgspagesizeregistry.sip @@ -23,7 +23,7 @@ class QgsPageSize QgsPageSize(); - QgsPageSize( const QString &name, const QgsLayoutSize &size ); + QgsPageSize( const QString &name, const QgsLayoutSize &size, const QString &displayName = QString() ); %Docstring Constructor for QgsPageSize, accepting the ``name`` of the page size and page ``size``. @@ -42,6 +42,11 @@ Name of page size QgsLayoutSize size; %Docstring Page size +%End + + QString displayName; +%Docstring +Translated page name %End bool operator==( const QgsPageSize &other ) const; diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 6d86125cc0e..697fa5e47b7 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -155,6 +155,7 @@ SET(QGIS_APP_SRCS composer/qgscompositionwidget.cpp composer/qgsatlascompositionwidget.cpp + layout/qgslayoutaddpagesdialog.cpp layout/qgslayoutdesignerdialog.cpp locator/qgsinbuiltlocatorfilters.cpp @@ -332,6 +333,7 @@ SET (QGIS_APP_MOC_HDRS composer/qgscompositionwidget.h composer/qgsatlascompositionwidget.h + layout/qgslayoutaddpagesdialog.h layout/qgslayoutdesignerdialog.h locator/qgsinbuiltlocatorfilters.h diff --git a/src/app/layout/qgslayoutaddpagesdialog.cpp b/src/app/layout/qgslayoutaddpagesdialog.cpp new file mode 100644 index 00000000000..ac949e07ef1 --- /dev/null +++ b/src/app/layout/qgslayoutaddpagesdialog.cpp @@ -0,0 +1,145 @@ +/*************************************************************************** + qgslayoutaddpagesdialog.cpp + --------------------------- + Date : July 2017 + Copyright : (C) 2017 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 "qgslayoutaddpagesdialog.h" +#include "qgspagesizeregistry.h" +#include "qgssettings.h" +#include "qgslayout.h" + + +QgsLayoutAddPagesDialog::QgsLayoutAddPagesDialog( QWidget *parent, Qt::WindowFlags flags ) + : QDialog( parent, flags ) +{ + setupUi( this ); + + mPageOrientationComboBox->addItem( tr( "Portrait" ), QgsLayoutItemPage::Portrait ); + mPageOrientationComboBox->addItem( tr( "Landscape" ), QgsLayoutItemPage::Landscape ); + mPageOrientationComboBox->setCurrentIndex( 1 ); + + Q_FOREACH ( const QgsPageSize &size, QgsApplication::pageSizeRegistry()->entries() ) + { + mPageSizeComboBox->addItem( size.displayName, size.name ); + } + mPageSizeComboBox->addItem( tr( "Custom" ) ); + mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->findData( QStringLiteral( "A4" ) ) ); + pageSizeChanged( mPageSizeComboBox->currentIndex() ); + orientationChanged( 1 ); + + mSizeUnitsComboBox->linkToWidget( mWidthSpin ); + mSizeUnitsComboBox->linkToWidget( mHeightSpin ); + + mLockAspectRatio->setWidthSpinBox( mWidthSpin ); + mLockAspectRatio->setHeightSpinBox( mHeightSpin ); + + connect( mPositionComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutAddPagesDialog::positionChanged ); + mExistingPageSpinBox->setEnabled( false ); + + connect( mPageSizeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutAddPagesDialog::pageSizeChanged ); + connect( mPageOrientationComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutAddPagesDialog::orientationChanged ); +} + +void QgsLayoutAddPagesDialog::setLayout( QgsLayout *layout ) +{ + mSizeUnitsComboBox->setConverter( &layout->context().measurementConverter() ); + mExistingPageSpinBox->setMaximum( layout->pageCollection()->pageCount() ); +} + +int QgsLayoutAddPagesDialog::numberPages() const +{ + return mPagesSpinBox->value(); +} + +QgsLayoutAddPagesDialog::PagePosition QgsLayoutAddPagesDialog::pagePosition() const +{ + return static_cast< PagePosition >( mPositionComboBox->currentIndex() ); +} + +int QgsLayoutAddPagesDialog::beforePage() const +{ + return mExistingPageSpinBox->value(); +} + +QgsLayoutSize QgsLayoutAddPagesDialog::pageSize() const +{ + return QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value(), mSizeUnitsComboBox->unit() ); +} + +void QgsLayoutAddPagesDialog::positionChanged( int index ) +{ + mExistingPageSpinBox->setEnabled( index != 2 ); +} + +void QgsLayoutAddPagesDialog::pageSizeChanged( int ) +{ + if ( mPageSizeComboBox->currentData().toString().isEmpty() ) + { + //custom size + mWidthSpin->setEnabled( true ); + mHeightSpin->setEnabled( true ); + mLockAspectRatio->setEnabled( true ); + mSizeUnitsComboBox->setEnabled( true ); + mPageOrientationComboBox->setEnabled( false ); + } + else + { + mWidthSpin->setEnabled( false ); + mHeightSpin->setEnabled( false ); + mLockAspectRatio->setEnabled( false ); + mLockAspectRatio->setLocked( false ); + mSizeUnitsComboBox->setEnabled( false ); + mPageOrientationComboBox->setEnabled( true ); + QgsPageSize size = QgsApplication::pageSizeRegistry()->find( mPageSizeComboBox->currentData().toString() ).value( 0 ); + switch ( mPageOrientationComboBox->currentData().toInt() ) + { + case QgsLayoutItemPage::Landscape: + mWidthSpin->setValue( size.size.height() ); + mHeightSpin->setValue( size.size.width() ); + break; + + case QgsLayoutItemPage::Portrait: + mWidthSpin->setValue( size.size.width() ); + mHeightSpin->setValue( size.size.height() ); + break; + } + mSizeUnitsComboBox->setUnit( size.size.units() ); + } +} + +void QgsLayoutAddPagesDialog::orientationChanged( int ) +{ + if ( mPageSizeComboBox->currentData().toString().isEmpty() ) + return; + + double width = mWidthSpin->value(); + double height = mHeightSpin->value(); + switch ( mPageOrientationComboBox->currentData().toInt() ) + { + case QgsLayoutItemPage::Landscape: + if ( width < height ) + { + whileBlocking( mWidthSpin )->setValue( height ); + whileBlocking( mHeightSpin )->setValue( width ); + } + break; + + case QgsLayoutItemPage::Portrait: + if ( width > height ) + { + whileBlocking( mWidthSpin )->setValue( height ); + whileBlocking( mHeightSpin )->setValue( width ); + } + break; + } +} diff --git a/src/app/layout/qgslayoutaddpagesdialog.h b/src/app/layout/qgslayoutaddpagesdialog.h new file mode 100644 index 00000000000..df94ec39a83 --- /dev/null +++ b/src/app/layout/qgslayoutaddpagesdialog.h @@ -0,0 +1,83 @@ +/*************************************************************************** + qgslayoutaddpagesdialog.h + ------------------------- + Date : July 2017 + Copyright : (C) 2017 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 QGSLAYOUTADDPAGESDIALOG_H +#define QGSLAYOUTADDPAGESDIALOG_H + +#include "qgis.h" +#include "qgis_gui.h" +#include "ui_qgslayoutnewpagedialog.h" + +#include "qgslayoutsize.h" +#include "qgslayoutpoint.h" +#include "qgslayoutitem.h" + +/** + * A dialog for configuring properties of new pages to be added to a layout + */ +class QgsLayoutAddPagesDialog : public QDialog, private Ui::QgsLayoutNewPageDialog +{ + Q_OBJECT + + public: + + enum PagePosition + { + BeforePage, + AfterPage, + AtEnd + }; + + /** + * Constructor for QgsLayoutAddPagesDialog. + */ + QgsLayoutAddPagesDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = 0 ); + + /** + * Sets the \a layout associated with the dialog. This allows the dialog + * to retrieve properties from the layout and perform tasks like automatic + * conversion of units. + */ + void setLayout( QgsLayout *layout ); + + /** + * Returns the number of pages to insert. + */ + int numberPages() const; + + /** + * Returns the position at which to insert the new pages. + */ + PagePosition pagePosition() const; + + /** + * Returns the page number for which new pages should be inserted before/after. + */ + int beforePage() const; + + /** + * Returns the desired page size. + */ + QgsLayoutSize pageSize() const; + + private slots: + + void positionChanged( int index ); + void pageSizeChanged( int index ); + void orientationChanged( int index ); + +}; + +#endif // QGSLAYOUTADDPAGESDIALOG_H diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 9bb9eb58fb5..62f2dd0c7af 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -29,6 +29,7 @@ #include "qgsgui.h" #include "qgslayoutitemguiregistry.h" #include "qgslayoutruler.h" +#include "qgslayoutaddpagesdialog.h" #include #include #include @@ -155,6 +156,8 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla connect( mActionZoomActual, &QAction::triggered, mView, &QgsLayoutView::zoomActual ); connect( mActionZoomToWidth, &QAction::triggered, mView, &QgsLayoutView::zoomWidth ); + connect( mActionAddPages, &QAction::triggered, this, &QgsLayoutDesignerDialog::addPages ); + //create status bar labels mStatusCursorXLabel = new QLabel( mStatusBar ); mStatusCursorXLabel->setMinimumWidth( 100 ); @@ -454,6 +457,38 @@ void QgsLayoutDesignerDialog::toggleFullScreen( bool enabled ) } } +void QgsLayoutDesignerDialog::addPages() +{ + QgsLayoutAddPagesDialog dlg( this ); + dlg.setLayout( mLayout ); + + if ( dlg.exec() ) + { + int firstPagePosition = dlg.beforePage() - 1; + switch ( dlg.pagePosition() ) + { + case QgsLayoutAddPagesDialog::BeforePage: + break; + + case QgsLayoutAddPagesDialog::AfterPage: + firstPagePosition = firstPagePosition + 1; + break; + + case QgsLayoutAddPagesDialog::AtEnd: + firstPagePosition = mLayout->pageCollection()->pageCount(); + break; + + } + + for ( int i = 0; i < dlg.numberPages(); ++i ) + { + QgsLayoutItemPage *page = new QgsLayoutItemPage( mLayout ); + page->setPageSize( dlg.pageSize() ); + mLayout->pageCollection()->insertPage( page, firstPagePosition + i ); + } + } +} + QgsLayoutView *QgsLayoutDesignerDialog::view() { return mView; diff --git a/src/app/layout/qgslayoutdesignerdialog.h b/src/app/layout/qgslayoutdesignerdialog.h index 6f919269708..402d2c1a8db 100644 --- a/src/app/layout/qgslayoutdesignerdialog.h +++ b/src/app/layout/qgslayoutdesignerdialog.h @@ -133,6 +133,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner void toggleFullScreen( bool enabled ); + void addPages(); + private: QgsAppLayoutDesignerInterface *mInterface = nullptr; diff --git a/src/core/layout/qgspagesizeregistry.cpp b/src/core/layout/qgspagesizeregistry.cpp index b41b471a196..c03140a1b5d 100644 --- a/src/core/layout/qgspagesizeregistry.cpp +++ b/src/core/layout/qgspagesizeregistry.cpp @@ -22,35 +22,35 @@ QgsPageSizeRegistry::QgsPageSizeRegistry() { - add( QgsPageSize( QStringLiteral( "A6" ), QgsLayoutSize( 105, 148 ) ) ); - add( QgsPageSize( QStringLiteral( "A5" ), QgsLayoutSize( 148, 210 ) ) ); - add( QgsPageSize( QStringLiteral( "A4" ), QgsLayoutSize( 210, 297 ) ) ); - add( QgsPageSize( QStringLiteral( "A3" ), QgsLayoutSize( 297, 420 ) ) ); - add( QgsPageSize( QStringLiteral( "A2" ), QgsLayoutSize( 420, 594 ) ) ); - add( QgsPageSize( QStringLiteral( "A1" ), QgsLayoutSize( 594, 841 ) ) ); - add( QgsPageSize( QStringLiteral( "A0" ), QgsLayoutSize( 841, 1189 ) ) ); - add( QgsPageSize( QStringLiteral( "B6" ), QgsLayoutSize( 125, 176 ) ) ); - add( QgsPageSize( QStringLiteral( "B5" ), QgsLayoutSize( 176, 250 ) ) ); - add( QgsPageSize( QStringLiteral( "B4" ), QgsLayoutSize( 250, 353 ) ) ); - add( QgsPageSize( QStringLiteral( "B3" ), QgsLayoutSize( 353, 500 ) ) ); - add( QgsPageSize( QStringLiteral( "B2" ), QgsLayoutSize( 500, 707 ) ) ); - add( QgsPageSize( QStringLiteral( "B1" ), QgsLayoutSize( 707, 1000 ) ) ); - add( QgsPageSize( QStringLiteral( "B0" ), QgsLayoutSize( 1000, 1414 ) ) ); - add( QgsPageSize( QStringLiteral( "Legal" ), QgsLayoutSize( 215.9, 355.6 ) ) ); - add( QgsPageSize( QStringLiteral( "Letter" ), QgsLayoutSize( 215.9, 279.4 ) ) ); - add( QgsPageSize( QStringLiteral( "ANSI A" ), QgsLayoutSize( 215.9, 279.4 ) ) ); - add( QgsPageSize( QStringLiteral( "ANSI B" ), QgsLayoutSize( 279.4, 431.8 ) ) ); - add( QgsPageSize( QStringLiteral( "ANSI C" ), QgsLayoutSize( 431.8, 558.8 ) ) ); - add( QgsPageSize( QStringLiteral( "ANSI D" ), QgsLayoutSize( 558.8, 863.6 ) ) ); - add( QgsPageSize( QStringLiteral( "ANSI E" ), QgsLayoutSize( 863.6, 1117.6 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch A" ), QgsLayoutSize( 228.6, 304.8 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch B" ), QgsLayoutSize( 304.8, 457.2 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch C" ), QgsLayoutSize( 457.2, 609.6 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch D" ), QgsLayoutSize( 609.6, 914.4 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch E" ), QgsLayoutSize( 914.4, 1219.2 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch E1" ), QgsLayoutSize( 762, 1066.8 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch E2" ), QgsLayoutSize( 660, 965 ) ) ); - add( QgsPageSize( QStringLiteral( "Arch E3" ), QgsLayoutSize( 686, 991 ) ) ); + add( QgsPageSize( QStringLiteral( "A6" ), QgsLayoutSize( 105, 148 ), QObject::tr( "A6" ) ) ); + add( QgsPageSize( QStringLiteral( "A5" ), QgsLayoutSize( 148, 210 ), QObject::tr( "A5" ) ) ); + add( QgsPageSize( QStringLiteral( "A4" ), QgsLayoutSize( 210, 297 ), QObject::tr( "A4" ) ) ); + add( QgsPageSize( QStringLiteral( "A3" ), QgsLayoutSize( 297, 420 ), QObject::tr( "A3" ) ) ); + add( QgsPageSize( QStringLiteral( "A2" ), QgsLayoutSize( 420, 594 ), QObject::tr( "A2" ) ) ); + add( QgsPageSize( QStringLiteral( "A1" ), QgsLayoutSize( 594, 841 ), QObject::tr( "A1" ) ) ); + add( QgsPageSize( QStringLiteral( "A0" ), QgsLayoutSize( 841, 1189 ), QObject::tr( "A0" ) ) ); + add( QgsPageSize( QStringLiteral( "B6" ), QgsLayoutSize( 125, 176 ), QObject::tr( "B6" ) ) ); + add( QgsPageSize( QStringLiteral( "B5" ), QgsLayoutSize( 176, 250 ), QObject::tr( "B5" ) ) ); + add( QgsPageSize( QStringLiteral( "B4" ), QgsLayoutSize( 250, 353 ), QObject::tr( "B4" ) ) ); + add( QgsPageSize( QStringLiteral( "B3" ), QgsLayoutSize( 353, 500 ), QObject::tr( "B3" ) ) ); + add( QgsPageSize( QStringLiteral( "B2" ), QgsLayoutSize( 500, 707 ), QObject::tr( "B2" ) ) ); + add( QgsPageSize( QStringLiteral( "B1" ), QgsLayoutSize( 707, 1000 ), QObject::tr( "B1" ) ) ); + add( QgsPageSize( QStringLiteral( "B0" ), QgsLayoutSize( 1000, 1414 ), QObject::tr( "B0" ) ) ); + add( QgsPageSize( QStringLiteral( "Legal" ), QgsLayoutSize( 215.9, 355.6 ), QObject::tr( "Legal" ) ) ); + add( QgsPageSize( QStringLiteral( "Letter" ), QgsLayoutSize( 215.9, 279.4 ), QObject::tr( "Letter" ) ) ); + add( QgsPageSize( QStringLiteral( "ANSI A" ), QgsLayoutSize( 215.9, 279.4 ), QObject::tr( "ANSI A" ) ) ); + add( QgsPageSize( QStringLiteral( "ANSI B" ), QgsLayoutSize( 279.4, 431.8 ), QObject::tr( "ANSI B" ) ) ); + add( QgsPageSize( QStringLiteral( "ANSI C" ), QgsLayoutSize( 431.8, 558.8 ), QObject::tr( "ANSI C" ) ) ); + add( QgsPageSize( QStringLiteral( "ANSI D" ), QgsLayoutSize( 558.8, 863.6 ), QObject::tr( "ANSI D" ) ) ); + add( QgsPageSize( QStringLiteral( "ANSI E" ), QgsLayoutSize( 863.6, 1117.6 ), QObject::tr( "ANSI E" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch A" ), QgsLayoutSize( 228.6, 304.8 ), QObject::tr( "Arch A" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch B" ), QgsLayoutSize( 304.8, 457.2 ), QObject::tr( "Arch B" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch C" ), QgsLayoutSize( 457.2, 609.6 ), QObject::tr( "Arch C" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch D" ), QgsLayoutSize( 609.6, 914.4 ), QObject::tr( "Arch D" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch E" ), QgsLayoutSize( 914.4, 1219.2 ), QObject::tr( "Arch E" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch E1" ), QgsLayoutSize( 762, 1066.8 ), QObject::tr( "Arch E1" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch E2" ), QgsLayoutSize( 660, 965 ), QObject::tr( "Arch E2" ) ) ); + add( QgsPageSize( QStringLiteral( "Arch E3" ), QgsLayoutSize( 686, 991 ), QObject::tr( "Arch E3" ) ) ); } void QgsPageSizeRegistry::add( const QgsPageSize &size ) @@ -103,9 +103,10 @@ QgsPageSize::QgsPageSize() { } -QgsPageSize::QgsPageSize( const QString &pageName, const QgsLayoutSize &pageSize ) +QgsPageSize::QgsPageSize( const QString &pageName, const QgsLayoutSize &pageSize, const QString &displayName ) : name( pageName ) , size( pageSize ) + , displayName( displayName ) { } diff --git a/src/core/layout/qgspagesizeregistry.h b/src/core/layout/qgspagesizeregistry.h index 1f43fced685..2061e57f58f 100644 --- a/src/core/layout/qgspagesizeregistry.h +++ b/src/core/layout/qgspagesizeregistry.h @@ -40,7 +40,7 @@ class CORE_EXPORT QgsPageSize * Constructor for QgsPageSize, accepting the \a name of the page size and * page \a size. */ - QgsPageSize( const QString &name, const QgsLayoutSize &size ); + QgsPageSize( const QString &name, const QgsLayoutSize &size, const QString &displayName = QString() ); /** * Constructor for QgsPageSize, accepting a page \a size. @@ -53,6 +53,9 @@ class CORE_EXPORT QgsPageSize //! Page size QgsLayoutSize size; + //! Translated page name + QString displayName; + bool operator==( const QgsPageSize &other ) const; bool operator!=( const QgsPageSize &other ) const; }; diff --git a/src/gui/layout/qgslayoutunitscombobox.cpp b/src/gui/layout/qgslayoutunitscombobox.cpp index ba6d1081751..6317b293854 100644 --- a/src/gui/layout/qgslayoutunitscombobox.cpp +++ b/src/gui/layout/qgslayoutunitscombobox.cpp @@ -61,7 +61,7 @@ void QgsLayoutUnitsComboBox::indexChanged( int ) Q_FOREACH ( const QPointer< QDoubleSpinBox > &widget, mLinkedSpinBoxes ) { if ( widget ) - widget->setValue( mConverter->convert( QgsLayoutMeasurement( widget->value(), mOldUnit ), newUnit ).length() ); + whileBlocking( widget.data() )->setValue( mConverter->convert( QgsLayoutMeasurement( widget->value(), mOldUnit ), newUnit ).length() ); } } emit changed( newUnit ); diff --git a/src/ui/layout/qgslayoutdesignerbase.ui b/src/ui/layout/qgslayoutdesignerbase.ui index ee5e5a17f39..6be4a439438 100644 --- a/src/ui/layout/qgslayoutdesignerbase.ui +++ b/src/ui/layout/qgslayoutdesignerbase.ui @@ -90,6 +90,7 @@ &Layout + @@ -291,6 +292,15 @@ F11 + + + + :/images/themes/default/mActionFileNew.svg:/images/themes/default/mActionFileNew.svg + + + Add Pages… + + diff --git a/src/ui/layout/qgslayoutnewpagedialog.ui b/src/ui/layout/qgslayoutnewpagedialog.ui new file mode 100644 index 00000000000..73caea7ebe2 --- /dev/null +++ b/src/ui/layout/qgslayoutnewpagedialog.ui @@ -0,0 +1,293 @@ + + + QgsLayoutNewPageDialog + + + + 0 + 0 + 415 + 321 + + + + New Item Properties + + + + + + Page size + + + + + + Size + + + + + + + Width + + + + + + + + + + Orientation + + + + + + + + + + Height + + + + + + + + + + + + 2 + + + 2 + + + 0 + + + 2 + + + + + + 0 + 0 + + + + Lock aspect ratio (including while drawing extent onto canvas) + + + 13 + + + + + + + + + + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + + + + + + + + 1 + + + + + + + page(s) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Insert + + + + + + + 2 + + + + Before Page + + + + + After Page + + + + + At End + + + + + + + + 1 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QgsRatioLockButton + QToolButton +
qgsratiolockbutton.h
+ 1 +
+ + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsLayoutUnitsComboBox + QComboBox +
qgslayoutunitscombobox.h
+
+
+ + mPagesSpinBox + mPositionComboBox + mExistingPageSpinBox + mPageSizeComboBox + mPageOrientationComboBox + mWidthSpin + mHeightSpin + mLockAspectRatio + mSizeUnitsComboBox + + + + + buttonBox + accepted() + QgsLayoutNewPageDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QgsLayoutNewPageDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
From 4cf9827ca2d9aa52b097489c05e9866d17d71932 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 13:48:28 +1000 Subject: [PATCH 175/266] Draw page shadow in layout units Simplifies code a lot, pixel based size proved to complex for small result --- python/core/layout/qgslayoutitempage.sip | 1 + python/core/layout/qgslayoutpagecollection.sip | 10 ++++++++++ src/core/layout/qgslayoutitempage.cpp | 17 +++++++++-------- src/core/layout/qgslayoutitempage.h | 5 +++++ src/core/layout/qgslayoutpagecollection.cpp | 2 -- src/core/layout/qgslayoutpagecollection.h | 6 ++++++ 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/python/core/layout/qgslayoutitempage.sip b/python/core/layout/qgslayoutitempage.sip index 0d9e190a2f0..d29e964617b 100644 --- a/python/core/layout/qgslayoutitempage.sip +++ b/python/core/layout/qgslayoutitempage.sip @@ -79,6 +79,7 @@ class QgsLayoutItemPage : QgsLayoutItem virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); + }; /************************************************************************ diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index 1963a072863..3a115f9f4e8 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -21,6 +21,16 @@ class QgsLayoutPageCollection : QObject %End public: + static const double SPACE_BETWEEN_PAGES; +%Docstring +Space between pages in the layout, in layout coordinates +%End + + static const double PAGE_SHADOW_WIDTH; +%Docstring +Size of page shadow, in layout coordinates +%End + explicit QgsLayoutPageCollection( QgsLayout *layout /TransferThis/ ); %Docstring Constructor for QgsLayoutItemPage, with the specified parent ``layout``. diff --git a/src/core/layout/qgslayoutitempage.cpp b/src/core/layout/qgslayoutitempage.cpp index 9e13e971b7d..5a54579d1b3 100644 --- a/src/core/layout/qgslayoutitempage.cpp +++ b/src/core/layout/qgslayoutitempage.cpp @@ -21,7 +21,6 @@ #include "qgssymbollayerutils.h" #include -#define SHADOW_WIDTH_PIXELS 5 QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) : QgsLayoutItem( layout ) { @@ -29,13 +28,14 @@ QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) setFlag( QGraphicsItem::ItemIsMovable, false ); setZValue( QgsLayout::ZPage ); - // bit hacky - we set a big hidden pen to avoid Qt clipping out the cosmetic shadow for the page - // Unfortunately it's possible to adapt the item's bounding rect based on the view's transform, so it's - // impossible to have a pixel based bounding rect. Instead we just set a big pen to force the page's - // bounding rect to be kinda large enough to handle the shadow at most zoom levels... - QPen shadowPen( QBrush( Qt::transparent ), 30 ); - shadowPen.setCosmetic( true ); + // use a hidden pen to specify the amount the page "bleeds" outside it's scene bounds, + // (it's a lot easier than reimplementing boundingRect() just to handle this) + QPen shadowPen( QBrush( Qt::transparent ), QgsLayoutPageCollection::PAGE_SHADOW_WIDTH * 2 ); setPen( shadowPen ); + + QFont font; + QFontMetrics fm( font ); + mMaximumShadowWidth = fm.width( "X" ); } void QgsLayoutItemPage::setPageSize( const QgsLayoutSize &size ) @@ -134,7 +134,8 @@ void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraph //shadow painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) ); painter->setPen( Qt::NoPen ); - painter->drawRect( pageRect.translated( SHADOW_WIDTH_PIXELS, SHADOW_WIDTH_PIXELS ) ); + painter->drawRect( pageRect.translated( qMin( scale * QgsLayoutPageCollection::PAGE_SHADOW_WIDTH, mMaximumShadowWidth ), + qMin( scale * QgsLayoutPageCollection::PAGE_SHADOW_WIDTH, mMaximumShadowWidth ) ) ); //page area painter->setBrush( QColor( 215, 215, 215 ) ); diff --git a/src/core/layout/qgslayoutitempage.h b/src/core/layout/qgslayoutitempage.h index 6c062ee562e..b0934f7ac2d 100644 --- a/src/core/layout/qgslayoutitempage.h +++ b/src/core/layout/qgslayoutitempage.h @@ -88,6 +88,11 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem protected: void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; + + private: + + double mMaximumShadowWidth = -1; + }; #endif //QGSLAYOUTITEMPAGE_H diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index 0e338ad448a..29ed34bba73 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -17,8 +17,6 @@ #include "qgslayoutpagecollection.h" #include "qgslayout.h" -#define SPACE_BETWEEN_PAGES 10 - QgsLayoutPageCollection::QgsLayoutPageCollection( QgsLayout *layout ) : QObject( layout ) , mLayout( layout ) diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index 978d2b9b7d5..b3f741ad202 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -39,6 +39,12 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject public: + //! Space between pages in the layout, in layout coordinates + static constexpr double SPACE_BETWEEN_PAGES = 10; + + //! Size of page shadow, in layout coordinates + static constexpr double PAGE_SHADOW_WIDTH = 5; // Must be less than SPACE_BETWEEN_PAGES + /** * Constructor for QgsLayoutItemPage, with the specified parent \a layout. */ From 416e1e4bbff90fb5c3bd488122b1ab3d9ac17092 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 21 Jul 2017 16:47:40 +1000 Subject: [PATCH 176/266] Fix drawing of rulers with multipage layouts --- .../core/layout/qgslayoutpagecollection.sip | 7 +++ python/gui/layout/qgslayoutview.sip | 2 + src/app/layout/qgslayoutdesignerdialog.cpp | 7 +-- src/core/layout/qgslayoutpagecollection.cpp | 1 + src/core/layout/qgslayoutpagecollection.h | 7 +++ src/gui/layout/qgslayoutruler.cpp | 47 +++++++++---------- src/gui/layout/qgslayoutview.cpp | 10 ++++ src/gui/layout/qgslayoutview.h | 1 + 8 files changed, 50 insertions(+), 32 deletions(-) diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index 3a115f9f4e8..0f54665065b 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -137,6 +137,13 @@ Size of page shadow, in layout coordinates :rtype: float %End + signals: + + void changed(); +%Docstring + Emitted when pages are added or removed from the collection. +%End + }; /************************************************************************ diff --git a/python/gui/layout/qgslayoutview.sip b/python/gui/layout/qgslayoutview.sip index c0c8c0a0a52..e57a229fd88 100644 --- a/python/gui/layout/qgslayoutview.sip +++ b/python/gui/layout/qgslayoutview.sip @@ -186,6 +186,8 @@ class QgsLayoutView: QGraphicsView virtual void resizeEvent( QResizeEvent *event ); + virtual void scrollContentsBy( int dx, int dy ); + }; diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 62f2dd0c7af..824d74f1903 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -257,15 +257,10 @@ void QgsLayoutDesignerDialog::open() { show(); activate(); - mView->zoomFull(); // zoomFull() does not work properly until we have called show() - -#if 0 // TODO - if ( mView ) { - mView->updateRulers(); + mView->zoomFull(); // zoomFull() does not work properly until we have called show() } -#endif } void QgsLayoutDesignerDialog::activate() diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index 29ed34bba73..c534377ccc4 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -52,6 +52,7 @@ void QgsLayoutPageCollection::reflow() p.setY( currentY ); } mLayout->updateBounds(); + emit changed(); } double QgsLayoutPageCollection::maximumPageWidth() const diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index b3f741ad202..049f0a7106a 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -145,6 +145,13 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject */ double maximumPageWidth() const; + signals: + + /** + * Emitted when pages are added or removed from the collection. + */ + void changed(); + private: QgsLayout *mLayout = nullptr; diff --git a/src/gui/layout/qgslayoutruler.cpp b/src/gui/layout/qgslayoutruler.cpp index 2fa4b7596cd..de0d737a28f 100644 --- a/src/gui/layout/qgslayoutruler.cpp +++ b/src/gui/layout/qgslayoutruler.cpp @@ -66,9 +66,8 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) { return; } -#if 0 + QgsLayout *layout = mView->currentLayout(); -#endif QPainter p( this ); QTransform t = mTransform.inverted(); @@ -137,14 +136,23 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) double startY = t.map( QPointF( 0, 0 ) ).y(); //start position in mm (total including space between pages) double endY = t.map( QPointF( 0, height() ) ).y(); //stop position in mm (total including space between pages) -#if 0 // TODO - int startPage = ( int )( startY / ( layout->paperHeight() + layout->spaceBetweenPages() ) ); -#endif - + // work out start page int startPage = 0; - if ( startPage < 0 ) + int endPage = 0; + double currentY = 0; + double currentPageY = 0; + for ( int page = 0; page < layout->pageCollection()->pageCount(); ++page ) { - startPage = 0; + if ( currentY < startY ) + { + startPage = page; + currentPageY = currentY; + } + endPage = page; + + currentY += layout->pageCollection()->page( startPage )->rect().height() + layout->pageCollection()->SPACE_BETWEEN_PAGES; + if ( currentY > endY ) + break; } if ( startY < 0 ) @@ -177,15 +185,6 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY ); } -#if 0 //TODO - int endPage = ( int )( endY / ( layout->paperHeight() + layout->spaceBetweenPages() ) ); - if ( endPage > ( mLayout->numPages() - 1 ) ) - { - endPage = mLayout->numPages() - 1; - } -#endif - int endPage = 0; - double nextPageStartPos = 0; int nextPageStartPixel = 0; @@ -193,14 +192,14 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) { double pageCoord = 0; //page coordinate in mm //total (composition) coordinate in mm, including space between pages -#if 0 //TODO - double totalCoord = i * ( layout->paperHeight() + layout->spaceBetweenPages() ); + + double totalCoord = currentPageY; //position of next page if ( i < endPage ) { //not the last page - nextPageStartPos = ( i + 1 ) * ( layout->paperHeight() + layout->spaceBetweenPages() ); + nextPageStartPos = currentPageY + layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->SPACE_BETWEEN_PAGES; nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y(); } else @@ -209,8 +208,7 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) nextPageStartPos = 0; nextPageStartPixel = 0; } -#endif - double totalCoord = 0; + while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) ) { double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y(); @@ -232,6 +230,7 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) pageCoord += mmDisplay; totalCoord += mmDisplay; } + currentPageY += layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->SPACE_BETWEEN_PAGES; } break; } @@ -407,10 +406,6 @@ int QgsLayoutRuler::optimumNumberDivisions( double rulerScale, int scaleMultiple void QgsLayoutRuler::setSceneTransform( const QTransform &transform ) { -#if 0 - QString debug = QString::number( transform.dx() ) + ',' + QString::number( transform.dy() ) + ',' - + QString::number( transform.m11() ) + ',' + QString::number( transform.m22() ); -#endif mTransform = transform; update(); } diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index 8136ce86565..dc44fe9ef30 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -53,6 +53,9 @@ void QgsLayoutView::setCurrentLayout( QgsLayout *layout ) { setScene( layout ); + connect( layout->pageCollection(), &QgsLayoutPageCollection::changed, this, &QgsLayoutView::updateRulers ); + updateRulers(); + //emit layoutSet, so that designer dialogs can update for the new layout emit layoutSet( layout ); } @@ -131,6 +134,7 @@ void QgsLayoutView::setVerticalRuler( QgsLayoutRuler *ruler ) void QgsLayoutView::zoomFull() { fitInView( scene()->sceneRect(), Qt::KeepAspectRatio ); + updateRulers(); emit zoomLevelChanged(); } @@ -305,6 +309,12 @@ void QgsLayoutView::resizeEvent( QResizeEvent *event ) emit zoomLevelChanged(); } +void QgsLayoutView::scrollContentsBy( int dx, int dy ) +{ + QGraphicsView::scrollContentsBy( dx, dy ); + updateRulers(); +} + void QgsLayoutView::updateRulers() { if ( mHorizontalRuler ) diff --git a/src/gui/layout/qgslayoutview.h b/src/gui/layout/qgslayoutview.h index a34094d557e..fd483682912 100644 --- a/src/gui/layout/qgslayoutview.h +++ b/src/gui/layout/qgslayoutview.h @@ -209,6 +209,7 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView void keyPressEvent( QKeyEvent *event ) override; void keyReleaseEvent( QKeyEvent *event ) override; void resizeEvent( QResizeEvent *event ) override; + void scrollContentsBy( int dx, int dy ) override; private slots: From 0ab3b8e0ad7c3d2a5415a81a061e2dc9d881b540 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 16:06:27 +1000 Subject: [PATCH 177/266] Fix cursor position display for multipage layouts --- .../core/layout/qgslayoutpagecollection.sip | 19 ++++++++ src/app/layout/qgslayoutdesignerdialog.cpp | 10 ++-- src/core/layout/qgslayoutpagecollection.cpp | 46 +++++++++++++++++++ src/core/layout/qgslayoutpagecollection.h | 17 +++++++ .../python/test_qgslayoutpagecollection.py | 45 +++++++++++++++++- 5 files changed, 129 insertions(+), 8 deletions(-) diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index 0f54665065b..34bc1492665 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -137,6 +137,25 @@ Size of page shadow, in layout coordinates :rtype: float %End + int pageNumberForPoint( QPointF point ) const; +%Docstring + Returns the page number corresponding to a ``point`` in the layout (in layout units). + + Page numbers in collections begin at 0 - so a page number of 0 indicates the + first page. + +.. seealso:: positionOnPage() + :rtype: int +%End + + QPointF positionOnPage( QPointF point ) const; +%Docstring + Returns the position within a page of a ``point`` in the layout (in layout units). + +.. seealso:: pageNumberForPoint() + :rtype: QPointF +%End + signals: void changed(); diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 824d74f1903..de7c518887b 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -428,16 +428,12 @@ void QgsLayoutDesignerDialog::updateStatusCursorPos( QPointF position ) } //convert cursor position to position on current page -#if 0 // TODO - QPointF pagePosition = mView->currentLayout()->positionOnPage( cursorPosition ); - int currentPage = mView->currentLayout()->pageNumberForPoint( cursorPosition ); -#endif - QPointF pagePosition = position; - int currentPage = 1; + QPointF pagePosition = mLayout->pageCollection()->positionOnPage( position ); + int currentPage = mLayout->pageCollection()->pageNumberForPoint( position ); mStatusCursorXLabel->setText( QString( tr( "x: %1 mm" ) ).arg( pagePosition.x() ) ); mStatusCursorYLabel->setText( QString( tr( "y: %1 mm" ) ).arg( pagePosition.y() ) ); - mStatusCursorPageLabel->setText( QString( tr( "page: %1" ) ).arg( currentPage ) ); + mStatusCursorPageLabel->setText( QString( tr( "page: %1" ) ).arg( currentPage + 1 ) ); } void QgsLayoutDesignerDialog::toggleFullScreen( bool enabled ) diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index c534377ccc4..3871016e71c 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -65,6 +65,52 @@ double QgsLayoutPageCollection::maximumPageWidth() const return maxWidth; } +int QgsLayoutPageCollection::pageNumberForPoint( QPointF point ) const +{ + int pageNumber = 0; + double startNextPageY = 0; + Q_FOREACH ( QgsLayoutItemPage *page, mPages ) + { + startNextPageY += page->rect().height() + SPACE_BETWEEN_PAGES; + if ( startNextPageY > point.y() ) + break; + pageNumber++; + } + + if ( pageNumber > mPages.count() - 1 ) + pageNumber = mPages.count() - 1; + return pageNumber; +} + +QPointF QgsLayoutPageCollection::positionOnPage( QPointF position ) const +{ + double startCurrentPageY = 0; + double startNextPageY = 0; + int pageNumber = 0; + Q_FOREACH ( QgsLayoutItemPage *page, mPages ) + { + startCurrentPageY = startNextPageY; + startNextPageY += page->rect().height() + SPACE_BETWEEN_PAGES; + if ( startNextPageY > position.y() ) + break; + pageNumber++; + } + + double y; + if ( pageNumber == mPages.size() ) + { + //y coordinate is greater then the end of the last page, so return distance between + //top of last page and y coordinate + y = position.y() - ( startNextPageY - SPACE_BETWEEN_PAGES ); + } + else + { + //y coordinate is less then the end of the last page + y = position.y() - startCurrentPageY; + } + return QPointF( position.x(), y ); +} + QgsLayout *QgsLayoutPageCollection::layout() const { return mLayout; diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index 049f0a7106a..b030255f71e 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -145,6 +145,23 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject */ double maximumPageWidth() const; + /** + * Returns the page number corresponding to a \a point in the layout (in layout units). + * + * Page numbers in collections begin at 0 - so a page number of 0 indicates the + * first page. + * + * \see positionOnPage() + */ + int pageNumberForPoint( QPointF point ) const; + + /** + * Returns the position within a page of a \a point in the layout (in layout units). + * + * \see pageNumberForPoint() + */ + QPointF positionOnPage( QPointF point ) const; + signals: /** diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index d6829cd5059..274e21d015d 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -25,7 +25,7 @@ from qgis.core import (QgsUnitTypes, QgsLayoutPageCollection, QgsSimpleFillSymbolLayer, QgsFillSymbol) -from qgis.PyQt.QtCore import Qt, QCoreApplication, QEvent +from qgis.PyQt.QtCore import Qt, QCoreApplication, QEvent, QPointF from qgis.testing import start_app, unittest start_app() @@ -253,6 +253,49 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertEqual(page3.pos().x(), 0) self.assertEqual(page3.pos().y(), 200) + def testPositionOnPage(self): + """ + Test pageNumberForPoint and positionOnPage + """ + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + + # add a page + page = QgsLayoutItemPage(l) + page.setPageSize('A4') + collection.addPage(page) + + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, -100)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, -1)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, 1)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, 270)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, 1270)), 0) + + self.assertEqual(collection.positionOnPage(QPointF(-100, -100)), QPointF(-100, -100)) + self.assertEqual(collection.positionOnPage(QPointF(-100, -1)), QPointF(-100, -1)) + self.assertEqual(collection.positionOnPage(QPointF(-100, 1)), QPointF(-100, 1)) + self.assertEqual(collection.positionOnPage(QPointF(-100, 270)), QPointF(-100, 270)) + self.assertEqual(collection.positionOnPage(QPointF(-100, 1270)), QPointF(-100, 973)) + + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A5') + collection.addPage(page2) + + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, -100)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, -1)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, 1)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, 270)), 0) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, 370)), 1) + self.assertEqual(collection.pageNumberForPoint(QPointF(-100, 1270)), 1) + + self.assertEqual(collection.positionOnPage(QPointF(-100, -100)), QPointF(-100, -100)) + self.assertEqual(collection.positionOnPage(QPointF(-100, -1)), QPointF(-100, -1)) + self.assertEqual(collection.positionOnPage(QPointF(-100, 1)), QPointF(-100, 1)) + self.assertEqual(collection.positionOnPage(QPointF(-100, 270)), QPointF(-100, 270)) + self.assertEqual(collection.positionOnPage(QPointF(-100, 370)), QPointF(-100, 63)) + self.assertEqual(collection.positionOnPage(QPointF(-100, 1270)), QPointF(-100, 753)) + if __name__ == '__main__': unittest.main() From 8044353aa7f219e89306a7062152616389c16164 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 16:44:59 +1000 Subject: [PATCH 178/266] Show correct unit type in status bar --- python/core/qgsunittypes.sip | 20 +++++++++ src/app/layout/qgslayoutdesignerdialog.cpp | 7 ++-- src/core/qgsunittypes.cpp | 48 ++++++++++++++++++++++ src/core/qgsunittypes.h | 18 ++++++++ src/gui/layout/qgslayoutunitscombobox.cpp | 31 +++++++------- tests/src/python/test_qgsunittypes.py | 18 ++++++++ 6 files changed, 123 insertions(+), 19 deletions(-) diff --git a/python/core/qgsunittypes.sip b/python/core/qgsunittypes.sip index 36913bf5416..6983b68b2d4 100644 --- a/python/core/qgsunittypes.sip +++ b/python/core/qgsunittypes.sip @@ -396,6 +396,7 @@ class QgsUnitTypes \param unit unit to encode :return: encoded string .. seealso:: decodeLayoutUnit() +.. versionadded:: 3.0 :rtype: str %End @@ -406,15 +407,34 @@ class QgsUnitTypes \param ok optional boolean, will be set to true if string was converted successfully :return: decoded units .. seealso:: encodeUnit() +.. versionadded:: 3.0 :rtype: LayoutUnit %End static LayoutUnitType unitType( const LayoutUnit units ); %Docstring Returns the type for a unit of measurement. + +.. versionadded:: 3.0 :rtype: LayoutUnitType %End + static QString toAbbreviatedString( LayoutUnit unit ); +%Docstring + Returns a translated abbreviation representing a layout ``unit`` (e.g. "mm"). + +.. versionadded:: 3.0 + :rtype: str +%End + + static QString toString( LayoutUnit unit ); +%Docstring + Returns a translated string representing a layout ``unit``. + +.. versionadded:: 3.0 + :rtype: str +%End + }; /************************************************************************ diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index de7c518887b..d1620dad6df 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -431,9 +431,10 @@ void QgsLayoutDesignerDialog::updateStatusCursorPos( QPointF position ) QPointF pagePosition = mLayout->pageCollection()->positionOnPage( position ); int currentPage = mLayout->pageCollection()->pageNumberForPoint( position ); - mStatusCursorXLabel->setText( QString( tr( "x: %1 mm" ) ).arg( pagePosition.x() ) ); - mStatusCursorYLabel->setText( QString( tr( "y: %1 mm" ) ).arg( pagePosition.y() ) ); - mStatusCursorPageLabel->setText( QString( tr( "page: %1" ) ).arg( currentPage + 1 ) ); + QString unit = QgsUnitTypes::toAbbreviatedString( mLayout->units() ); + mStatusCursorXLabel->setText( tr( "x: %1 %2" ).arg( pagePosition.x() ).arg( unit ) ); + mStatusCursorYLabel->setText( tr( "y: %1 %2" ).arg( pagePosition.y() ).arg( unit ) ); + mStatusCursorPageLabel->setText( tr( "page: %1" ).arg( currentPage + 1 ) ); } void QgsLayoutDesignerDialog::toggleFullScreen( bool enabled ) diff --git a/src/core/qgsunittypes.cpp b/src/core/qgsunittypes.cpp index 4fb868e9e51..0ff6c67ddaa 100644 --- a/src/core/qgsunittypes.cpp +++ b/src/core/qgsunittypes.cpp @@ -1859,3 +1859,51 @@ QgsUnitTypes::LayoutUnitType QgsUnitTypes::unitType( const QgsUnitTypes::LayoutU // avoid warnings return LayoutPaperUnits; } + +QString QgsUnitTypes::toAbbreviatedString( QgsUnitTypes::LayoutUnit unit ) +{ + switch ( unit ) + { + case LayoutPixels: + return QObject::tr( "px" ); + case LayoutMillimeters: + return QObject::tr( "mm" ); + case LayoutCentimeters: + return QObject::tr( "cm" ); + case LayoutMeters: + return QObject::tr( "m" ); + case LayoutInches: + return QObject::tr( "in" ); + case LayoutFeet: + return QObject::tr( "ft" ); + case LayoutPoints: + return QObject::tr( "pt" ); + case LayoutPicas: + return QObject::tr( "pica" ); + } + return QString(); // no warnings +} + +QString QgsUnitTypes::toString( QgsUnitTypes::LayoutUnit unit ) +{ + switch ( unit ) + { + case LayoutPixels: + return QObject::tr( "pixels" ); + case LayoutMillimeters: + return QObject::tr( "millimeters" ); + case LayoutCentimeters: + return QObject::tr( "centimeters" ); + case LayoutMeters: + return QObject::tr( "meters" ); + case LayoutInches: + return QObject::tr( "inches" ); + case LayoutFeet: + return QObject::tr( "feet" ); + case LayoutPoints: + return QObject::tr( "points" ); + case LayoutPicas: + return QObject::tr( "picas" ); + } + return QString(); // no warnings +} diff --git a/src/core/qgsunittypes.h b/src/core/qgsunittypes.h index b9ff14e7356..47b1368d4d1 100644 --- a/src/core/qgsunittypes.h +++ b/src/core/qgsunittypes.h @@ -384,6 +384,7 @@ class CORE_EXPORT QgsUnitTypes * \param unit unit to encode * \returns encoded string * \see decodeLayoutUnit() + * \since QGIS 3.0 */ Q_INVOKABLE static QString encodeUnit( LayoutUnit unit ); @@ -392,14 +393,31 @@ class CORE_EXPORT QgsUnitTypes * \param ok optional boolean, will be set to true if string was converted successfully * \returns decoded units * \see encodeUnit() + * \since QGIS 3.0 */ Q_INVOKABLE static LayoutUnit decodeLayoutUnit( const QString &string, bool *ok SIP_OUT = 0 ); /** * Returns the type for a unit of measurement. + * + * \since QGIS 3.0 */ Q_INVOKABLE static LayoutUnitType unitType( const LayoutUnit units ); + /** + * Returns a translated abbreviation representing a layout \a unit (e.g. "mm"). + * + * \since QGIS 3.0 + */ + Q_INVOKABLE static QString toAbbreviatedString( LayoutUnit unit ); + + /** + * Returns a translated string representing a layout \a unit. + * + * \since QGIS 3.0 + */ + Q_INVOKABLE static QString toString( LayoutUnit unit ); + }; #endif // QGSUNITTYPES_H diff --git a/src/gui/layout/qgslayoutunitscombobox.cpp b/src/gui/layout/qgslayoutunitscombobox.cpp index 6317b293854..ca6b21eba49 100644 --- a/src/gui/layout/qgslayoutunitscombobox.cpp +++ b/src/gui/layout/qgslayoutunitscombobox.cpp @@ -19,22 +19,21 @@ QgsLayoutUnitsComboBox::QgsLayoutUnitsComboBox( QWidget *parent ) : QComboBox( parent ) { - addItem( tr( "mm" ), QgsUnitTypes::LayoutMillimeters ); - setItemData( 0, tr( "Millimeters" ), Qt::ToolTipRole ); - addItem( tr( "cm" ), QgsUnitTypes::LayoutCentimeters ); - setItemData( 1, tr( "Centimeters" ), Qt::ToolTipRole ); - addItem( tr( "m" ), QgsUnitTypes::LayoutMeters ); - setItemData( 2, tr( "Meters" ), Qt::ToolTipRole ); - addItem( tr( "in" ), QgsUnitTypes::LayoutInches ); - setItemData( 3, tr( "Inches" ), Qt::ToolTipRole ); - addItem( tr( "ft" ), QgsUnitTypes::LayoutFeet ); - setItemData( 4, tr( "Feet" ), Qt::ToolTipRole ); - addItem( tr( "pt" ), QgsUnitTypes::LayoutPoints ); - setItemData( 5, tr( "Points" ), Qt::ToolTipRole ); - addItem( tr( "pica" ), QgsUnitTypes::LayoutPicas ); - setItemData( 6, tr( "Picas" ), Qt::ToolTipRole ); - addItem( tr( "px" ), QgsUnitTypes::LayoutPixels ); - setItemData( 7, tr( "Pixels" ), Qt::ToolTipRole ); + QList< QgsUnitTypes::LayoutUnit > units; + units << QgsUnitTypes::LayoutMillimeters + << QgsUnitTypes::LayoutCentimeters + << QgsUnitTypes::LayoutMeters + << QgsUnitTypes::LayoutInches + << QgsUnitTypes::LayoutFeet + << QgsUnitTypes::LayoutPoints + << QgsUnitTypes::LayoutPicas + << QgsUnitTypes::LayoutPixels; + + Q_FOREACH ( QgsUnitTypes::LayoutUnit u, units ) + { + addItem( QgsUnitTypes::toAbbreviatedString( u ), u ); + setItemData( count() - 1, QgsUnitTypes::toString( u ), Qt::ToolTipRole ); + } connect( this, static_cast( &QgsLayoutUnitsComboBox::currentIndexChanged ), this, &QgsLayoutUnitsComboBox::indexChanged ); } diff --git a/tests/src/python/test_qgsunittypes.py b/tests/src/python/test_qgsunittypes.py index 3818de02c2f..1b0768913e6 100644 --- a/tests/src/python/test_qgsunittypes.py +++ b/tests/src/python/test_qgsunittypes.py @@ -660,6 +660,24 @@ class TestQgsUnitTypes(unittest.TestCase): assert ok self.assertEqual(res, QgsUnitTypes.LayoutPixels) + def testAbbreviateLayoutUnits(self): + """Test abbreviating layout units""" + units = [QgsUnitTypes.LayoutMillimeters, + QgsUnitTypes.LayoutCentimeters, + QgsUnitTypes.LayoutMeters, + QgsUnitTypes.LayoutInches, + QgsUnitTypes.LayoutFeet, + QgsUnitTypes.LayoutPoints, + QgsUnitTypes.LayoutPicas, + QgsUnitTypes.LayoutPixels] + + used = set() + for u in units: + self.assertTrue(QgsUnitTypes.toString(u)) + self.assertTrue(QgsUnitTypes.toAbbreviatedString(u)) + self.assertFalse(QgsUnitTypes.toAbbreviatedString(u) in used) + used.add(QgsUnitTypes.toAbbreviatedString(u)) + if __name__ == "__main__": unittest.main() From 183a72e9265b949f6235470ac6188c22b271f633 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 16:45:09 +1000 Subject: [PATCH 179/266] Default to layout units in new page dialog --- src/app/layout/qgslayoutaddpagesdialog.cpp | 16 +++++++++------- src/app/layout/qgslayoutaddpagesdialog.h | 5 +++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/app/layout/qgslayoutaddpagesdialog.cpp b/src/app/layout/qgslayoutaddpagesdialog.cpp index ac949e07ef1..ef8962710fe 100644 --- a/src/app/layout/qgslayoutaddpagesdialog.cpp +++ b/src/app/layout/qgslayoutaddpagesdialog.cpp @@ -17,7 +17,7 @@ #include "qgspagesizeregistry.h" #include "qgssettings.h" #include "qgslayout.h" - +#include "qgslayoutmeasurementconverter.h" QgsLayoutAddPagesDialog::QgsLayoutAddPagesDialog( QWidget *parent, Qt::WindowFlags flags ) : QDialog( parent, flags ) @@ -52,8 +52,10 @@ QgsLayoutAddPagesDialog::QgsLayoutAddPagesDialog( QWidget *parent, Qt::WindowFla void QgsLayoutAddPagesDialog::setLayout( QgsLayout *layout ) { - mSizeUnitsComboBox->setConverter( &layout->context().measurementConverter() ); + mConverter = layout->context().measurementConverter(); + mSizeUnitsComboBox->setConverter( &mConverter ); mExistingPageSpinBox->setMaximum( layout->pageCollection()->pageCount() ); + mSizeUnitsComboBox->setUnit( layout->units() ); } int QgsLayoutAddPagesDialog::numberPages() const @@ -101,19 +103,19 @@ void QgsLayoutAddPagesDialog::pageSizeChanged( int ) mSizeUnitsComboBox->setEnabled( false ); mPageOrientationComboBox->setEnabled( true ); QgsPageSize size = QgsApplication::pageSizeRegistry()->find( mPageSizeComboBox->currentData().toString() ).value( 0 ); + QgsLayoutSize convertedSize = mConverter.convert( size.size, mSizeUnitsComboBox->unit() ); switch ( mPageOrientationComboBox->currentData().toInt() ) { case QgsLayoutItemPage::Landscape: - mWidthSpin->setValue( size.size.height() ); - mHeightSpin->setValue( size.size.width() ); + mWidthSpin->setValue( convertedSize.height() ); + mHeightSpin->setValue( convertedSize.width() ); break; case QgsLayoutItemPage::Portrait: - mWidthSpin->setValue( size.size.width() ); - mHeightSpin->setValue( size.size.height() ); + mWidthSpin->setValue( convertedSize.width() ); + mHeightSpin->setValue( convertedSize.height() ); break; } - mSizeUnitsComboBox->setUnit( size.size.units() ); } } diff --git a/src/app/layout/qgslayoutaddpagesdialog.h b/src/app/layout/qgslayoutaddpagesdialog.h index df94ec39a83..c3997e1094d 100644 --- a/src/app/layout/qgslayoutaddpagesdialog.h +++ b/src/app/layout/qgslayoutaddpagesdialog.h @@ -23,6 +23,7 @@ #include "qgslayoutsize.h" #include "qgslayoutpoint.h" #include "qgslayoutitem.h" +#include "qgslayoutmeasurementconverter.h" /** * A dialog for configuring properties of new pages to be added to a layout @@ -78,6 +79,10 @@ class QgsLayoutAddPagesDialog : public QDialog, private Ui::QgsLayoutNewPageDial void pageSizeChanged( int index ); void orientationChanged( int index ); + private: + + QgsLayoutMeasurementConverter mConverter; + }; #endif // QGSLAYOUTADDPAGESDIALOG_H From 1e4c95431c014cb3182411ed10f4eaa5e0f1d4b9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 16:58:43 +1000 Subject: [PATCH 180/266] Don't use constant space between pages - doesn't work well for non mm units --- .../core/layout/qgslayoutpagecollection.sip | 22 ++++++++++--------- src/core/layout/qgslayoutitempage.cpp | 6 ++--- src/core/layout/qgslayoutpagecollection.cpp | 18 +++++++++++---- src/core/layout/qgslayoutpagecollection.h | 16 +++++++++----- src/gui/layout/qgslayoutruler.cpp | 6 ++--- 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index 34bc1492665..6ad8613c54e 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -21,16 +21,6 @@ class QgsLayoutPageCollection : QObject %End public: - static const double SPACE_BETWEEN_PAGES; -%Docstring -Space between pages in the layout, in layout coordinates -%End - - static const double PAGE_SHADOW_WIDTH; -%Docstring -Size of page shadow, in layout coordinates -%End - explicit QgsLayoutPageCollection( QgsLayout *layout /TransferThis/ ); %Docstring Constructor for QgsLayoutItemPage, with the specified parent ``layout``. @@ -156,6 +146,18 @@ Size of page shadow, in layout coordinates :rtype: QPointF %End + double spaceBetweenPages() const; +%Docstring + Returns the space between pages, in layout units. + :rtype: float +%End + + double pageShadowWidth() const; +%Docstring + Returns the size of the page shadow, in layout units. + :rtype: float +%End + signals: void changed(); diff --git a/src/core/layout/qgslayoutitempage.cpp b/src/core/layout/qgslayoutitempage.cpp index 5a54579d1b3..946fd0a90b9 100644 --- a/src/core/layout/qgslayoutitempage.cpp +++ b/src/core/layout/qgslayoutitempage.cpp @@ -30,7 +30,7 @@ QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) // use a hidden pen to specify the amount the page "bleeds" outside it's scene bounds, // (it's a lot easier than reimplementing boundingRect() just to handle this) - QPen shadowPen( QBrush( Qt::transparent ), QgsLayoutPageCollection::PAGE_SHADOW_WIDTH * 2 ); + QPen shadowPen( QBrush( Qt::transparent ), layout->pageCollection()->pageShadowWidth() * 2 ); setPen( shadowPen ); QFont font; @@ -134,8 +134,8 @@ void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraph //shadow painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) ); painter->setPen( Qt::NoPen ); - painter->drawRect( pageRect.translated( qMin( scale * QgsLayoutPageCollection::PAGE_SHADOW_WIDTH, mMaximumShadowWidth ), - qMin( scale * QgsLayoutPageCollection::PAGE_SHADOW_WIDTH, mMaximumShadowWidth ) ) ); + painter->drawRect( pageRect.translated( qMin( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ), + qMin( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ) ) ); //page area painter->setBrush( QColor( 215, 215, 215 ) ); diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index 3871016e71c..aa58f1fde28 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -48,7 +48,7 @@ void QgsLayoutPageCollection::reflow() Q_FOREACH ( QgsLayoutItemPage *page, mPages ) { page->attemptMove( p ); - currentY += mLayout->convertToLayoutUnits( page->pageSize() ).height() + SPACE_BETWEEN_PAGES; + currentY += mLayout->convertToLayoutUnits( page->pageSize() ).height() + spaceBetweenPages(); p.setY( currentY ); } mLayout->updateBounds(); @@ -71,7 +71,7 @@ int QgsLayoutPageCollection::pageNumberForPoint( QPointF point ) const double startNextPageY = 0; Q_FOREACH ( QgsLayoutItemPage *page, mPages ) { - startNextPageY += page->rect().height() + SPACE_BETWEEN_PAGES; + startNextPageY += page->rect().height() + spaceBetweenPages(); if ( startNextPageY > point.y() ) break; pageNumber++; @@ -90,7 +90,7 @@ QPointF QgsLayoutPageCollection::positionOnPage( QPointF position ) const Q_FOREACH ( QgsLayoutItemPage *page, mPages ) { startCurrentPageY = startNextPageY; - startNextPageY += page->rect().height() + SPACE_BETWEEN_PAGES; + startNextPageY += page->rect().height() + spaceBetweenPages(); if ( startNextPageY > position.y() ) break; pageNumber++; @@ -101,7 +101,7 @@ QPointF QgsLayoutPageCollection::positionOnPage( QPointF position ) const { //y coordinate is greater then the end of the last page, so return distance between //top of last page and y coordinate - y = position.y() - ( startNextPageY - SPACE_BETWEEN_PAGES ); + y = position.y() - ( startNextPageY - spaceBetweenPages() ); } else { @@ -111,6 +111,16 @@ QPointF QgsLayoutPageCollection::positionOnPage( QPointF position ) const return QPointF( position.x(), y ); } +double QgsLayoutPageCollection::spaceBetweenPages() const +{ + return mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 10 ) ); +} + +double QgsLayoutPageCollection::pageShadowWidth() const +{ + return spaceBetweenPages() / 2; +} + QgsLayout *QgsLayoutPageCollection::layout() const { return mLayout; diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index b030255f71e..4656a05eec6 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -39,12 +39,6 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject public: - //! Space between pages in the layout, in layout coordinates - static constexpr double SPACE_BETWEEN_PAGES = 10; - - //! Size of page shadow, in layout coordinates - static constexpr double PAGE_SHADOW_WIDTH = 5; // Must be less than SPACE_BETWEEN_PAGES - /** * Constructor for QgsLayoutItemPage, with the specified parent \a layout. */ @@ -162,6 +156,16 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject */ QPointF positionOnPage( QPointF point ) const; + /** + * Returns the space between pages, in layout units. + */ + double spaceBetweenPages() const; + + /** + * Returns the size of the page shadow, in layout units. + */ + double pageShadowWidth() const; + signals: /** diff --git a/src/gui/layout/qgslayoutruler.cpp b/src/gui/layout/qgslayoutruler.cpp index de0d737a28f..77345518873 100644 --- a/src/gui/layout/qgslayoutruler.cpp +++ b/src/gui/layout/qgslayoutruler.cpp @@ -150,7 +150,7 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) } endPage = page; - currentY += layout->pageCollection()->page( startPage )->rect().height() + layout->pageCollection()->SPACE_BETWEEN_PAGES; + currentY += layout->pageCollection()->page( startPage )->rect().height() + layout->pageCollection()->spaceBetweenPages(); if ( currentY > endY ) break; } @@ -199,7 +199,7 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) if ( i < endPage ) { //not the last page - nextPageStartPos = currentPageY + layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->SPACE_BETWEEN_PAGES; + nextPageStartPos = currentPageY + layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->spaceBetweenPages(); nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y(); } else @@ -230,7 +230,7 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) pageCoord += mmDisplay; totalCoord += mmDisplay; } - currentPageY += layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->SPACE_BETWEEN_PAGES; + currentPageY += layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->spaceBetweenPages(); } break; } From 3d94f733498e17251946df9e4e508a1d47a57168 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 17:26:42 +1000 Subject: [PATCH 181/266] Fix scale calculation when layout is in pixels --- src/app/layout/qgslayoutdesignerdialog.cpp | 26 ++++++++++++++-------- src/gui/layout/qgslayoutview.cpp | 22 ++++++++++++------ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index d1620dad6df..d757ed50a4b 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -406,16 +406,24 @@ void QgsLayoutDesignerDialog::sliderZoomChanged( int value ) void QgsLayoutDesignerDialog::updateStatusZoom() { - double dpi = QgsApplication::desktop()->logicalDpiX(); - //monitor dpi is not always correct - so make sure the value is sane - if ( ( dpi < 60 ) || ( dpi > 1200 ) ) - dpi = 72; - - //pixel width for 1mm on screen - double scale100 = dpi / 25.4; - //current zoomLevel - double zoomLevel = mView->transform().m11() * 100 / scale100; + double zoomLevel = 0; + if ( currentLayout()->units() == QgsUnitTypes::LayoutPixels ) + { + zoomLevel = mView->transform().m11() * 100; + } + else + { + double dpi = QgsApplication::desktop()->logicalDpiX(); + //monitor dpi is not always correct - so make sure the value is sane + if ( ( dpi < 60 ) || ( dpi > 1200 ) ) + dpi = 72; + //pixel width for 1mm on screen + double scale100 = dpi / 25.4; + scale100 = currentLayout()->convertFromLayoutUnits( scale100, QgsUnitTypes::LayoutMillimeters ).length(); + //current zoomLevel + zoomLevel = mView->transform().m11() * 100 / scale100; + } whileBlocking( mStatusZoomCombo )->lineEdit()->setText( tr( "%1%" ).arg( zoomLevel, 0, 'f', 1 ) ); whileBlocking( mStatusZoomSlider )->setValue( zoomLevel ); } diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index dc44fe9ef30..24da5a77f0b 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -105,14 +105,22 @@ void QgsLayoutView::scaleSafe( double scale ) void QgsLayoutView::setZoomLevel( double level ) { - double dpi = QgsApplication::desktop()->logicalDpiX(); - //monitor dpi is not always correct - so make sure the value is sane - if ( ( dpi < 60 ) || ( dpi > 1200 ) ) - dpi = 72; + if ( currentLayout()->units() == QgsUnitTypes::LayoutPixels ) + { + setTransform( QTransform::fromScale( level, level ) ); + } + else + { + double dpi = QgsApplication::desktop()->logicalDpiX(); + //monitor dpi is not always correct - so make sure the value is sane + if ( ( dpi < 60 ) || ( dpi > 1200 ) ) + dpi = 72; - //desired pixel width for 1mm on screen - double scale = qBound( MIN_VIEW_SCALE, level * dpi / 25.4, MAX_VIEW_SCALE ); - setTransform( QTransform::fromScale( scale, scale ) ); + //desired pixel width for 1mm on screen + level = qBound( MIN_VIEW_SCALE, level, MAX_VIEW_SCALE ); + double mmLevel = currentLayout()->convertFromLayoutUnits( level, QgsUnitTypes::LayoutMillimeters ).length() * dpi / 25.4; + setTransform( QTransform::fromScale( mmLevel, mmLevel ) ); + } emit zoomLevelChanged(); updateRulers(); } From cd2c62e825d6fdb75d78755d0d2059823fb7faf1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 17:26:56 +1000 Subject: [PATCH 182/266] Fix initial position of items for non mm layout units --- src/gui/layout/qgslayoutnewitempropertiesdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp index ac1905bdc6e..234c1fabf94 100644 --- a/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp +++ b/src/gui/layout/qgslayoutnewitempropertiesdialog.cpp @@ -53,9 +53,9 @@ QgsLayoutItemPropertiesDialog::QgsLayoutItemPropertiesDialog( QWidget *parent, Q void QgsLayoutItemPropertiesDialog::setItemPosition( QgsLayoutPoint position ) { + mPosUnitsComboBox->setUnit( position.units() ); mXPosSpin->setValue( position.x() ); mYPosSpin->setValue( position.y() ); - mPosUnitsComboBox->setUnit( position.units() ); } QgsLayoutPoint QgsLayoutItemPropertiesDialog::itemPosition() const From cbc5782cd87177c9fe3f59877a8d2d4ba65dd2b3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 18:23:23 +1000 Subject: [PATCH 183/266] Add unit test for scaling layout view with layouts in pixels --- tests/src/python/test_qgslayoutview.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/src/python/test_qgslayoutview.py b/tests/src/python/test_qgslayoutview.py index 88551db2eaa..131c1cf913b 100644 --- a/tests/src/python/test_qgslayoutview.py +++ b/tests/src/python/test_qgslayoutview.py @@ -14,6 +14,7 @@ __revision__ = '$Format:%H$' import qgis # NOQA +from qgis.core import QgsProject, QgsLayout, QgsUnitTypes from qgis.gui import QgsLayoutView from qgis.PyQt.QtCore import QRectF from qgis.PyQt.QtGui import QTransform @@ -58,6 +59,18 @@ class TestQgsLayoutView(unittest.TestCase): view.scaleSafe(0.5) self.assertAlmostEqual(view.transform().m11(), scale) + def testLayoutScalePixels(self): + p = QgsProject() + l = QgsLayout(p) + l.setUnits(QgsUnitTypes.LayoutPixels) + view = QgsLayoutView() + view.setCurrentLayout(l) + view.setZoomLevel(1) + # should be no transform, since 100% with pixel units should be pixel-pixel + self.assertEqual(view.transform().m11(), 1) + view.setZoomLevel(0.5) + self.assertEqual(view.transform().m11(), 0.5) + if __name__ == '__main__': unittest.main() From f1dfd3dbe253d2b94e5cf31ceddacf44a4643e18 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 19:04:49 +1000 Subject: [PATCH 184/266] Add an interface for creation of QgsLayoutView context menus Allows display of custom right click menus when right click events are not handled by the current layout view tool. --- python/gui/layout/qgslayoutview.sip | 40 +++++++++++++++++++++++++++++ src/gui/layout/qgslayoutview.cpp | 20 +++++++++++++++ src/gui/layout/qgslayoutview.h | 37 ++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/python/gui/layout/qgslayoutview.sip b/python/gui/layout/qgslayoutview.sip index e57a229fd88..1a3b75f9b7c 100644 --- a/python/gui/layout/qgslayoutview.sip +++ b/python/gui/layout/qgslayoutview.sip @@ -90,6 +90,19 @@ class QgsLayoutView: QGraphicsView .. seealso:: setHorizontalRuler() %End + void setMenuProvider( QgsLayoutViewMenuProvider *provider /Transfer/ ); +%Docstring + Sets a ``provider`` for context menus. Ownership of the provider is transferred to the view. +.. seealso:: menuProvider() +%End + + QgsLayoutViewMenuProvider *menuProvider() const; +%Docstring + Returns the provider for context menus. Returned value may be None if no provider is set. +.. seealso:: setMenuProvider() + :rtype: QgsLayoutViewMenuProvider +%End + public slots: void zoomFull(); @@ -191,6 +204,33 @@ class QgsLayoutView: QGraphicsView }; + +class QgsLayoutViewMenuProvider +{ +%Docstring + + Interface for a QgsLayoutView context menu. + + Implementations of this interface can be made to allow QgsLayoutView + instances to provide custom context menus (opened upon right-click). + +.. seealso:: QgsLayoutView +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutview.h" +%End + public: + virtual ~QgsLayoutViewMenuProvider(); + + virtual QMenu *createContextMenu( QWidget *parent /Transfer/, QgsLayout *layout, QPointF layoutPoint ) const = 0 /Factory/; +%Docstring +Return a newly created menu instance (or null pointer on error) + :rtype: QMenu +%End +}; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index 24da5a77f0b..1f26cd95f70 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -28,6 +28,7 @@ #include "qgsapplication.h" #include #include +#include #define MIN_VIEW_SCALE 0.05 #define MAX_VIEW_SCALE 1000.0 @@ -139,6 +140,16 @@ void QgsLayoutView::setVerticalRuler( QgsLayoutRuler *ruler ) updateRulers(); } +void QgsLayoutView::setMenuProvider( QgsLayoutViewMenuProvider *provider ) +{ + mMenuProvider.reset( provider ); +} + +QgsLayoutViewMenuProvider *QgsLayoutView::menuProvider() const +{ + return mMenuProvider.get(); +} + void QgsLayoutView::zoomFull() { fitInView( scene()->sceneRect(), Qt::KeepAspectRatio ); @@ -205,6 +216,15 @@ void QgsLayoutView::mousePressEvent( QMouseEvent *event ) setTool( mMidMouseButtonPanTool ); event->accept(); } + else if ( event->button() == Qt::RightButton && mMenuProvider ) + { + QMenu *menu = mMenuProvider->createContextMenu( this, currentLayout(), mapToScene( event->pos() ) ); + if ( menu ) + { + menu->exec( event->globalPos() ); + delete menu; + } + } else { QGraphicsView::mousePressEvent( event ); diff --git a/src/gui/layout/qgslayoutview.h b/src/gui/layout/qgslayoutview.h index fd483682912..ff242046443 100644 --- a/src/gui/layout/qgslayoutview.h +++ b/src/gui/layout/qgslayoutview.h @@ -22,13 +22,16 @@ #include "qgis_gui.h" #include #include +#include +class QMenu; class QgsLayout; class QgsLayoutViewTool; class QgsLayoutViewToolTemporaryKeyPan; class QgsLayoutViewToolTemporaryKeyZoom; class QgsLayoutViewToolTemporaryMousePan; class QgsLayoutRuler; +class QgsLayoutViewMenuProvider; /** * \ingroup gui @@ -112,6 +115,18 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView */ void setVerticalRuler( QgsLayoutRuler *ruler ); + /** + * Sets a \a provider for context menus. Ownership of the provider is transferred to the view. + * \see menuProvider() + */ + void setMenuProvider( QgsLayoutViewMenuProvider *provider SIP_TRANSFER ); + + /** + * Returns the provider for context menus. Returned value may be nullptr if no provider is set. + * \see setMenuProvider() + */ + QgsLayoutViewMenuProvider *menuProvider() const; + public slots: /** @@ -228,9 +243,31 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView QgsLayoutRuler *mHorizontalRuler = nullptr; QgsLayoutRuler *mVerticalRuler = nullptr; + std::unique_ptr< QgsLayoutViewMenuProvider > mMenuProvider; friend class TestQgsLayoutView; }; + +/** + * \ingroup gui + * + * Interface for a QgsLayoutView context menu. + * + * Implementations of this interface can be made to allow QgsLayoutView + * instances to provide custom context menus (opened upon right-click). + * + * \see QgsLayoutView + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsLayoutViewMenuProvider +{ + public: + virtual ~QgsLayoutViewMenuProvider() = default; + + //! Return a newly created menu instance (or null pointer on error) + virtual QMenu *createContextMenu( QWidget *parent SIP_TRANSFER, QgsLayout *layout, QPointF layoutPoint ) const = 0 SIP_FACTORY; +}; + #endif // QGSLAYOUTVIEW_H From e885966cb6db160070da49781886e16ed00c7c75 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 19:10:59 +1000 Subject: [PATCH 185/266] Add method to retrieve page at a specified layout position --- .../core/layout/qgslayoutpagecollection.sip | 22 ++++++++++++ src/core/layout/qgslayoutpagecollection.cpp | 14 ++++++++ src/core/layout/qgslayoutpagecollection.h | 17 +++++++++ .../python/test_qgslayoutpagecollection.py | 36 +++++++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index 6ad8613c54e..61025d68079 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -134,10 +134,32 @@ class QgsLayoutPageCollection : QObject Page numbers in collections begin at 0 - so a page number of 0 indicates the first page. +.. note:: + + This is a relaxed check, which will always return a page number. For instance, + it does not consider x coordinates and vertical coordinates before the first page or + after the last page will still return the nearest page. + +.. seealso:: pageAtPoint() .. seealso:: positionOnPage() :rtype: int %End + QgsLayoutItemPage *pageAtPoint( QPointF point ) const; +%Docstring + Returns the page at a specified ``point`` (in layout coordinates). + + If no page exists at ``point``, None will be returned. + +.. note:: + + Unlike pageNumberForPoint(), this method only returns pages which + directly intersect with the specified point. + +.. seealso:: pageNumberForPoint() + :rtype: QgsLayoutItemPage +%End + QPointF positionOnPage( QPointF point ) const; %Docstring Returns the position within a page of a ``point`` in the layout (in layout units). diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index aa58f1fde28..151dbce1b41 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -82,6 +82,20 @@ int QgsLayoutPageCollection::pageNumberForPoint( QPointF point ) const return pageNumber; } +QgsLayoutItemPage *QgsLayoutPageCollection::pageAtPoint( QPointF point ) const +{ + Q_FOREACH ( QGraphicsItem *item, mLayout->items( point ) ) + { + if ( item->type() == QgsLayoutItemRegistry::LayoutPage ) + { + QgsLayoutItemPage *page = static_cast< QgsLayoutItemPage * >( item ); + if ( page->mapToScene( page->rect() ).boundingRect().contains( point ) ) + return page; + } + } + return nullptr; +} + QPointF QgsLayoutPageCollection::positionOnPage( QPointF position ) const { double startCurrentPageY = 0; diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index 4656a05eec6..86e10eddc57 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -145,10 +145,27 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject * Page numbers in collections begin at 0 - so a page number of 0 indicates the * first page. * + * \note This is a relaxed check, which will always return a page number. For instance, + * it does not consider x coordinates and vertical coordinates before the first page or + * after the last page will still return the nearest page. + * + * \see pageAtPoint() * \see positionOnPage() */ int pageNumberForPoint( QPointF point ) const; + /** + * Returns the page at a specified \a point (in layout coordinates). + * + * If no page exists at \a point, nullptr will be returned. + * + * \note Unlike pageNumberForPoint(), this method only returns pages which + * directly intersect with the specified point. + * + * \see pageNumberForPoint() + */ + QgsLayoutItemPage *pageAtPoint( QPointF point ) const; + /** * Returns the position within a page of a \a point in the layout (in layout units). * diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index 274e21d015d..1a0deb4b764 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -296,6 +296,42 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertEqual(collection.positionOnPage(QPointF(-100, 370)), QPointF(-100, 63)) self.assertEqual(collection.positionOnPage(QPointF(-100, 1270)), QPointF(-100, 753)) + def testPageAtPoint(self): + """ + Test pageAtPoint + """ + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + + self.assertFalse(collection.pageAtPoint(QPointF(0, 0))) + self.assertFalse(collection.pageAtPoint(QPointF(10, 10))) + + # add a page + page = QgsLayoutItemPage(l) + page.setPageSize('A4') + collection.addPage(page) + + self.assertFalse(collection.pageAtPoint(QPointF(10, -1))) + self.assertEqual(collection.pageAtPoint(QPointF(1, 1)), page) + self.assertEqual(collection.pageAtPoint(QPointF(10, 10)), page) + self.assertFalse(collection.pageAtPoint(QPointF(-10, 10))) + self.assertFalse(collection.pageAtPoint(QPointF(1000, 10))) + self.assertFalse(collection.pageAtPoint(QPointF(10, -10))) + self.assertFalse(collection.pageAtPoint(QPointF(10, 1000))) + + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A5') + collection.addPage(page2) + + self.assertEqual(collection.pageAtPoint(QPointF(1, 1)), page) + self.assertEqual(collection.pageAtPoint(QPointF(10, 10)), page) + self.assertFalse(collection.pageAtPoint(QPointF(-10, 10))) + self.assertFalse(collection.pageAtPoint(QPointF(1000, 10))) + self.assertFalse(collection.pageAtPoint(QPointF(10, -10))) + self.assertEqual(collection.pageAtPoint(QPointF(10, 330)), page2) + self.assertEqual(collection.pageAtPoint(QPointF(10, 500)), page2) + self.assertFalse(collection.pageAtPoint(QPointF(10, 600))) if __name__ == '__main__': unittest.main() From a3e26785f8decca17e56081f088d6d11a3ccbb4d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 20:01:29 +1000 Subject: [PATCH 186/266] [needs-docs] Start on right click menu for layouts Currently only contains option for removing the current page and (non-functional) option for setting current page properties --- .../core/layout/qgslayoutpagecollection.sip | 8 +++ src/app/CMakeLists.txt | 2 + src/app/layout/qgslayoutappmenuprovider.cpp | 57 +++++++++++++++++++ src/app/layout/qgslayoutappmenuprovider.h | 38 +++++++++++++ src/app/layout/qgslayoutdesignerdialog.cpp | 4 ++ src/app/layout/qgslayoutdesignerdialog.h | 3 + src/core/layout/qgslayoutpagecollection.cpp | 10 ++++ src/core/layout/qgslayoutpagecollection.h | 8 +++ src/gui/layout/qgslayoutruler.cpp | 3 +- .../python/test_qgslayoutpagecollection.py | 40 +++++++++++++ 10 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 src/app/layout/qgslayoutappmenuprovider.cpp create mode 100644 src/app/layout/qgslayoutappmenuprovider.h diff --git a/python/core/layout/qgslayoutpagecollection.sip b/python/core/layout/qgslayoutpagecollection.sip index 61025d68079..8700d67e88e 100644 --- a/python/core/layout/qgslayoutpagecollection.sip +++ b/python/core/layout/qgslayoutpagecollection.sip @@ -96,6 +96,14 @@ class QgsLayoutPageCollection : QObject Page numbers in collections begin at 0 - so a ``pageNumber`` of 0 will delete the first page in the collection. + Calling deletePage() automatically triggers a reflow() of pages. +%End + + void deletePage( QgsLayoutItemPage *page ); +%Docstring + Deletes a page from the collection. The page will automatically be removed + from the collection's layout(). + Calling deletePage() automatically triggers a reflow() of pages. %End diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 697fa5e47b7..06a3ac9e092 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -157,6 +157,7 @@ SET(QGIS_APP_SRCS layout/qgslayoutaddpagesdialog.cpp layout/qgslayoutdesignerdialog.cpp + layout/qgslayoutappmenuprovider.cpp locator/qgsinbuiltlocatorfilters.cpp locator/qgslocatoroptionswidget.cpp @@ -334,6 +335,7 @@ SET (QGIS_APP_MOC_HDRS composer/qgsatlascompositionwidget.h layout/qgslayoutaddpagesdialog.h + layout/qgslayoutappmenuprovider.h layout/qgslayoutdesignerdialog.h locator/qgsinbuiltlocatorfilters.h diff --git a/src/app/layout/qgslayoutappmenuprovider.cpp b/src/app/layout/qgslayoutappmenuprovider.cpp new file mode 100644 index 00000000000..308c9d3a5ea --- /dev/null +++ b/src/app/layout/qgslayoutappmenuprovider.cpp @@ -0,0 +1,57 @@ +/*************************************************************************** + qgslayoutappmenuprovider.cpp + --------------------------- + Date : July 2017 + Copyright : (C) 2017 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 "qgslayoutappmenuprovider.h" +#include "qgslayoutitempage.h" +#include "qgslayout.h" +#include +#include + +QgsLayoutAppMenuProvider::QgsLayoutAppMenuProvider( QObject *parent ) + : QObject( parent ) +{ + +} + +QMenu *QgsLayoutAppMenuProvider::createContextMenu( QWidget *parent, QgsLayout *layout, QPointF layoutPoint ) const +{ + QMenu *menu = new QMenu( parent ); + + // is a page under the mouse? + QgsLayoutItemPage *page = layout->pageCollection()->pageAtPoint( layoutPoint ); + if ( page ) + { + QAction *pagePropertiesAction = new QAction( tr( "Page Properties…" ), menu ); + connect( pagePropertiesAction, &QAction::triggered, this, [page]() + { + + + } ); + menu->addAction( pagePropertiesAction ); + QAction *removePageAction = new QAction( tr( "Remove Page" ), menu ); + connect( removePageAction, &QAction::triggered, this, [layout, page]() + { + if ( QMessageBox::question( nullptr, tr( "Remove Page" ), + tr( "Remove page from layout?" ), + QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes ) + { + layout->pageCollection()->deletePage( page ); + } + } ); + menu->addAction( removePageAction ); + } + + return menu; +} diff --git a/src/app/layout/qgslayoutappmenuprovider.h b/src/app/layout/qgslayoutappmenuprovider.h new file mode 100644 index 00000000000..feed54be451 --- /dev/null +++ b/src/app/layout/qgslayoutappmenuprovider.h @@ -0,0 +1,38 @@ +/*************************************************************************** + qgslayoutappmenuprovider.h + ------------------------- + Date : July 2017 + Copyright : (C) 2017 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 QGSLAYOUTAPPMENUPROVIDER_H +#define QGSLAYOUTAPPMENUPROVIDER_H + +#include "qgis.h" +#include "qgslayoutview.h" +#include + +/** + * A menu provider for QgsLayoutView + */ +class QgsLayoutAppMenuProvider : public QObject, public QgsLayoutViewMenuProvider +{ + Q_OBJECT + + public: + + QgsLayoutAppMenuProvider( QObject *parent = nullptr ); + + QMenu *createContextMenu( QWidget *parent, QgsLayout *layout, QPointF layoutPoint ) const override; + +}; + +#endif // QGSLAYOUTAPPMENUPROVIDER_H diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index d757ed50a4b..18572f73cdd 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -21,6 +21,7 @@ #include "qgisapp.h" #include "qgslogger.h" #include "qgslayout.h" +#include "qgslayoutappmenuprovider.h" #include "qgslayoutview.h" #include "qgslayoutviewtooladditem.h" #include "qgslayoutviewtoolpan.h" @@ -221,6 +222,9 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla connect( mActionToggleFullScreen, &QAction::toggled, this, &QgsLayoutDesignerDialog::toggleFullScreen ); + mMenuProvider = new QgsLayoutAppMenuProvider(); + mView->setMenuProvider( mMenuProvider ); + restoreWindowState(); } diff --git a/src/app/layout/qgslayoutdesignerdialog.h b/src/app/layout/qgslayoutdesignerdialog.h index 402d2c1a8db..72729768af0 100644 --- a/src/app/layout/qgslayoutdesignerdialog.h +++ b/src/app/layout/qgslayoutdesignerdialog.h @@ -31,6 +31,7 @@ class QgsLayoutRuler; class QComboBox; class QSlider; class QLabel; +class QgsLayoutAppMenuProvider; class QgsAppLayoutDesignerInterface : public QgsLayoutDesignerInterface { @@ -167,6 +168,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner QMap< QString, QToolButton * > mItemGroupToolButtons; QMap< QString, QMenu * > mItemGroupSubmenus; + QgsLayoutAppMenuProvider *mMenuProvider; + //! Save window state void saveWindowState(); diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index 151dbce1b41..7ba9f3c5881 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -190,6 +190,16 @@ void QgsLayoutPageCollection::deletePage( int pageNumber ) reflow(); } +void QgsLayoutPageCollection::deletePage( QgsLayoutItemPage *page ) +{ + if ( !mPages.contains( page ) ) + return; + + mPages.removeAll( page ); + page->deleteLater(); + reflow(); +} + void QgsLayoutPageCollection::createDefaultPageStyleSymbol() { QgsStringMap properties; diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index 86e10eddc57..98259ff92b5 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -113,6 +113,14 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject */ void deletePage( int pageNumber ); + /** + * Deletes a page from the collection. The page will automatically be removed + * from the collection's layout(). + * + * Calling deletePage() automatically triggers a reflow() of pages. + */ + void deletePage( QgsLayoutItemPage *page ); + /** * Sets the \a symbol to use for drawing pages in the collection. * diff --git a/src/gui/layout/qgslayoutruler.cpp b/src/gui/layout/qgslayoutruler.cpp index 77345518873..1b55093e8ba 100644 --- a/src/gui/layout/qgslayoutruler.cpp +++ b/src/gui/layout/qgslayoutruler.cpp @@ -230,7 +230,8 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) pageCoord += mmDisplay; totalCoord += mmDisplay; } - currentPageY += layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->spaceBetweenPages(); + if ( i < endPage ) + currentPageY += layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->spaceBetweenPages(); } break; } diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index 1a0deb4b764..4a4188598aa 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -129,6 +129,46 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertTrue(sip.isdeleted(page)) self.assertTrue(sip.isdeleted(page2)) + def testDeletePages(self): + """ + Test deleting pages from the collection + """ + p = QgsProject() + l = QgsLayout(p) + collection = l.pageCollection() + + # add a page + page = QgsLayoutItemPage(l) + page.setPageSize('A4') + collection.addPage(page) + # add a second page + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A5') + collection.addPage(page2) + + # delete page + collection.deletePage(None) + self.assertEqual(collection.pageCount(), 2) + + page3 = QgsLayoutItemPage(l) + # try deleting a page not in collection + collection.deletePage(page3) + QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete) + self.assertFalse(sip.isdeleted(page3)) + self.assertEqual(collection.pageCount(), 2) + + collection.deletePage(page) + self.assertEqual(collection.pageCount(), 1) + self.assertFalse(page in collection.pages()) + QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete) + self.assertTrue(sip.isdeleted(page)) + + collection.deletePage(page2) + self.assertEqual(collection.pageCount(), 0) + self.assertFalse(collection.pages()) + QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete) + self.assertTrue(sip.isdeleted(page2)) + def testMaxPageWidth(self): """ Test calculating maximum page width From 72bf2924d42ccf3a9ada6e3f88d083b3eb06e6f3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 21:00:12 +1000 Subject: [PATCH 187/266] Port base class for item configuration widgets --- python/gui/gui_auto.sip | 1 + python/gui/layout/qgslayoutitemwidget.sip | 106 +++++++++++++++ src/gui/CMakeLists.txt | 2 + src/gui/layout/qgslayoutitemwidget.cpp | 156 ++++++++++++++++++++++ src/gui/layout/qgslayoutitemwidget.h | 150 +++++++++++++++++++++ 5 files changed, 415 insertions(+) create mode 100644 python/gui/layout/qgslayoutitemwidget.sip create mode 100644 src/gui/layout/qgslayoutitemwidget.cpp create mode 100644 src/gui/layout/qgslayoutitemwidget.h diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 93d4816f782..be43c3e175b 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -281,6 +281,7 @@ %Include layertree/qgslayertreeviewdefaultactions.sip %Include layout/qgslayoutdesignerinterface.sip %Include layout/qgslayoutitemguiregistry.sip +%Include layout/qgslayoutitemwidget.sip %Include layout/qgslayoutnewitempropertiesdialog.sip %Include layout/qgslayoutruler.sip %Include layout/qgslayoutunitscombobox.sip diff --git a/python/gui/layout/qgslayoutitemwidget.sip b/python/gui/layout/qgslayoutitemwidget.sip new file mode 100644 index 00000000000..36539bd6011 --- /dev/null +++ b/python/gui/layout/qgslayoutitemwidget.sip @@ -0,0 +1,106 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/layout/qgslayoutitemwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsLayoutConfigObject: QObject +{ +%Docstring + + An object for property widgets for layout items. All layout config type widgets should contain + this object. + + If you are creating a new QgsLayoutItem configuration widget, you should instead + inherit from QgsLayoutItemBaseWidget (rather then directly working with QgsLayoutConfigObject). + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemwidget.h" +%End + public: + + QgsLayoutConfigObject( QWidget *parent /TransferThis/, QgsLayoutObject *layoutObject ); +%Docstring + Constructor for QgsLayoutConfigObject, linked with the specified ``layoutObject``. +%End + + void initializeDataDefinedButton( QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty key ); +%Docstring + Registers a data defined ``button``, setting up its initial value, connections and description. + The corresponding property ``key`` must be specified. +%End + + void updateDataDefinedButton( QgsPropertyOverrideButton *button ); +%Docstring + Updates a data defined button to reflect the item's current properties. +%End + + QgsVectorLayer *coverageLayer() const; +%Docstring + Returns the current layout context coverage layer (if set). + :rtype: QgsVectorLayer +%End + + +}; + +class QgsLayoutItemBaseWidget: QgsPanelWidget +{ +%Docstring + + A base class for property widgets for layout items. All layout item widgets should inherit from + this base class. + + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemwidget.h" +%End + public: + + QgsLayoutItemBaseWidget( QWidget *parent /TransferThis/, QgsLayoutObject *layoutObject ); +%Docstring + Constructor for QgsLayoutItemBaseWidget, linked with the specified ``layoutObject``. +%End + + protected: + + void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty property ); +%Docstring + Registers a data defined ``button``, setting up its initial value, connections and description. + The corresponding property ``key`` must be specified. +%End + + void updateDataDefinedButton( QgsPropertyOverrideButton *button ); +%Docstring + Updates a previously registered data defined button to reflect the item's current properties. +%End + + QgsVectorLayer *coverageLayer() const; +%Docstring + Returns the current layout context coverage layer (if set). + :rtype: QgsVectorLayer +%End + + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/layout/qgslayoutitemwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 5dacf06a4ad..0ee3783048f 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -159,6 +159,7 @@ SET(QGIS_GUI_SRCS layertree/qgslayertreeviewdefaultactions.cpp layout/qgslayoutitemguiregistry.cpp + layout/qgslayoutitemwidget.cpp layout/qgslayoutnewitempropertiesdialog.cpp layout/qgslayoutruler.cpp layout/qgslayoutunitscombobox.cpp @@ -655,6 +656,7 @@ SET(QGIS_GUI_MOC_HDRS layout/qgslayoutdesignerinterface.h layout/qgslayoutitemguiregistry.h + layout/qgslayoutitemwidget.h layout/qgslayoutnewitempropertiesdialog.h layout/qgslayoutruler.h layout/qgslayoutunitscombobox.h diff --git a/src/gui/layout/qgslayoutitemwidget.cpp b/src/gui/layout/qgslayoutitemwidget.cpp new file mode 100644 index 00000000000..1c3705ce055 --- /dev/null +++ b/src/gui/layout/qgslayoutitemwidget.cpp @@ -0,0 +1,156 @@ +/*************************************************************************** + qgslayoutitemwidget.cpp + ------------------------ + Date : July 2017 + Copyright : (C) 2017 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 "qgslayoutitemwidget.h" +#include "qgspropertyoverridebutton.h" +#include "qgslayout.h" + + +// +// QgsLayoutConfigObject +// + +QgsLayoutConfigObject::QgsLayoutConfigObject( QWidget *parent, QgsLayoutObject *layoutObject ) + : QObject( parent ) + , mLayoutObject( layoutObject ) +{ +#if 0 //TODO + connect( atlasComposition(), &QgsAtlasComposition::coverageLayerChanged, + this, [ = ] { updateDataDefinedButtons(); } ); + connect( atlasComposition(), &QgsAtlasComposition::toggled, this, &QgsComposerConfigObject::updateDataDefinedButtons ); +#endif +} + +void QgsLayoutConfigObject::updateDataDefinedProperty() +{ + //match data defined button to item's data defined property + QgsPropertyOverrideButton *ddButton = qobject_cast( sender() ); + if ( !ddButton ) + { + return; + } + QgsLayoutObject::DataDefinedProperty key = QgsLayoutObject::NoProperty; + + if ( ddButton->propertyKey() >= 0 ) + key = static_cast< QgsLayoutObject::DataDefinedProperty >( ddButton->propertyKey() ); + + if ( key == QgsLayoutObject::NoProperty ) + { + return; + } + + //set the data defined property and refresh the item + if ( mLayoutObject ) + { + mLayoutObject->dataDefinedProperties().setProperty( key, ddButton->toProperty() ); + mLayoutObject->refresh(); + } +} + +void QgsLayoutConfigObject::updateDataDefinedButtons() +{ +#if 0 //TODO + Q_FOREACH ( QgsPropertyOverrideButton *button, findChildren< QgsPropertyOverrideButton * >() ) + { + button->setVectorLayer( atlasCoverageLayer() ); + } +#endif +} + +void QgsLayoutConfigObject::initializeDataDefinedButton( QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty key ) +{ + button->blockSignals( true ); + button->init( key, mLayoutObject->dataDefinedProperties(), QgsLayoutObject::propertyDefinitions(), coverageLayer() ); + connect( button, &QgsPropertyOverrideButton::changed, this, &QgsLayoutConfigObject::updateDataDefinedProperty ); + button->registerExpressionContextGenerator( mLayoutObject ); + button->blockSignals( false ); +} + +void QgsLayoutConfigObject::updateDataDefinedButton( QgsPropertyOverrideButton *button ) +{ + if ( !button ) + return; + + if ( button->propertyKey() < 0 || !mLayoutObject ) + return; + + QgsLayoutObject::DataDefinedProperty key = static_cast< QgsLayoutObject::DataDefinedProperty >( button->propertyKey() ); + whileBlocking( button )->setToProperty( mLayoutObject->dataDefinedProperties().property( key ) ); +} + +#if 0 // TODO +QgsAtlasComposition *QgsLayoutConfigObject::atlasComposition() const +{ + if ( !mLayoutObject ) + { + return nullptr; + } + + QgsComposition *composition = mComposerObject->composition(); + + if ( !composition ) + { + return nullptr; + } + + return &composition->atlasComposition(); +} +#endif + +QgsVectorLayer *QgsLayoutConfigObject::coverageLayer() const +{ + if ( !mLayoutObject ) + return nullptr; + + QgsLayout *layout = mLayoutObject->layout(); + if ( !layout ) + return nullptr; + + return layout->context().layer(); +} + + +// +// QgsLayoutItemBaseWidget +// + +QgsLayoutItemBaseWidget::QgsLayoutItemBaseWidget( QWidget *parent, QgsLayoutObject *layoutObject ) + : QgsPanelWidget( parent ) + , mConfigObject( new QgsLayoutConfigObject( this, layoutObject ) ) +{ + +} + +void QgsLayoutItemBaseWidget::registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty property ) +{ + mConfigObject->initializeDataDefinedButton( button, property ); +} + +void QgsLayoutItemBaseWidget::updateDataDefinedButton( QgsPropertyOverrideButton *button ) +{ + mConfigObject->updateDataDefinedButton( button ); +} + +QgsVectorLayer *QgsLayoutItemBaseWidget::coverageLayer() const +{ + return mConfigObject->coverageLayer(); +} + +#if 0 //TODO +QgsAtlasComposition *QgsLayoutItemBaseWidget::atlasComposition() const +{ + return mConfigObject->atlasComposition(); +} +#endif diff --git a/src/gui/layout/qgslayoutitemwidget.h b/src/gui/layout/qgslayoutitemwidget.h new file mode 100644 index 00000000000..acc1491e0ee --- /dev/null +++ b/src/gui/layout/qgslayoutitemwidget.h @@ -0,0 +1,150 @@ +/*************************************************************************** + qgslayoutitemwidget.h + ------------------------ + Date : July 2017 + Copyright : (C) 2017 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 QGSLAYOUTITEMWIDGET_H +#define QGSLAYOUTITEMWIDGET_H + +#include "qgis_gui.h" +#include "qgslayoutobject.h" +#include "qgspanelwidget.h" +#include +#include + + +class QgsPropertyOverrideButton; + +// NOTE - the inheritance here is tricky, as we need to avoid the multiple inheritance +// diamond problem and the ideal base object (QgsLayoutConfigObject) MUST be a QObject +// because of its slots. + +// So here we go: +// QgsLayoutItemWidget is just a QWidget which is embedded inside specific item property +// widgets and contains common settings like position and rotation of the items. While the +// actual individual item type widgets MUST be QgsPanelWidgets unfortunately QgsLayoutItemWidget +// CANNOT be a QgsPanelWidget and must instead be a generic QWidget (otherwise a QgsPanelWidget +// contains a child QgsPanelWidget, which breaks lots of assumptions made in QgsPanelWidget +// and related classes). +// So QgsLayoutItemWidget HAS a QgsLayoutConfigObject to handle these common tasks. +// Specific item property widgets (e.g., QgsLayoutMapWidget) should inherit from QgsLayoutItemBaseWidget +// (which is a QgsPanelWidget) and also HAS a QgsLayoutConfigObject, with protected methods +// which are just proxied through to the QgsComposerConfigObject. +// phew! +// long story short - don't change this without good reason. If you add a new item type, inherit +// from QgsLayoutItemBaseWidget and trust that everything else has been done for you. + +/** + * \class QgsLayoutConfigObject + * \ingroup gui + * + * An object for property widgets for layout items. All layout config type widgets should contain + * this object. + * + * If you are creating a new QgsLayoutItem configuration widget, you should instead + * inherit from QgsLayoutItemBaseWidget (rather then directly working with QgsLayoutConfigObject). + * + * \since QGIS 3.0 +*/ +class GUI_EXPORT QgsLayoutConfigObject: public QObject +{ + Q_OBJECT + public: + + /** + * Constructor for QgsLayoutConfigObject, linked with the specified \a layoutObject. + */ + QgsLayoutConfigObject( QWidget *parent SIP_TRANSFERTHIS, QgsLayoutObject *layoutObject ); + + /** + * Registers a data defined \a button, setting up its initial value, connections and description. + * The corresponding property \a key must be specified. + */ + void initializeDataDefinedButton( QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty key ); + + /** + * Updates a data defined button to reflect the item's current properties. + */ + void updateDataDefinedButton( QgsPropertyOverrideButton *button ); + + /** + * Returns the current layout context coverage layer (if set). + */ + QgsVectorLayer *coverageLayer() const; + +#if 0 //TODO + //! Returns the atlas for the composition + QgsAtlasComposition *atlasComposition() const; +#endif + + private slots: + //! Must be called when a data defined button changes + void updateDataDefinedProperty(); + + //! Updates data defined buttons to reflect current state of layout (e.g., coverage layer) + void updateDataDefinedButtons(); + + private: + + QPointer< QgsLayoutObject > mLayoutObject; +}; + +/** + * \class QgsLayoutItemBaseWidget + * \ingroup gui + * + * A base class for property widgets for layout items. All layout item widgets should inherit from + * this base class. + * + * + * \since QGIS 3.0 +*/ +class GUI_EXPORT QgsLayoutItemBaseWidget: public QgsPanelWidget +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutItemBaseWidget, linked with the specified \a layoutObject. + */ + QgsLayoutItemBaseWidget( QWidget *parent SIP_TRANSFERTHIS, QgsLayoutObject *layoutObject ); + + protected: + + /** + * Registers a data defined \a button, setting up its initial value, connections and description. + * The corresponding property \a key must be specified. + */ + void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsLayoutObject::DataDefinedProperty property ); + + /** + * Updates a previously registered data defined button to reflect the item's current properties. + */ + void updateDataDefinedButton( QgsPropertyOverrideButton *button ); + + /** + * Returns the current layout context coverage layer (if set). + */ + QgsVectorLayer *coverageLayer() const; + +#if 0 //TODO + //! Returns the atlas for the composition + QgsAtlasComposition *atlasComposition() const; +#endif + + private: + + QgsLayoutConfigObject *mConfigObject = nullptr; +}; + +#endif // QGSLAYOUTITEMWIDGET_H From 0f90e23fe6f45974c676ba70e5c523b397fcecdf Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 21:24:40 +1000 Subject: [PATCH 188/266] Refine item widget creation methods in layout item gui registry --- .../gui/layout/qgslayoutitemguiregistry.sip | 12 +++++----- src/gui/layout/qgslayoutitemguiregistry.cpp | 9 +++++--- src/gui/layout/qgslayoutitemguiregistry.h | 13 ++++++----- tests/src/gui/testqgslayoutview.cpp | 23 +++++++++++-------- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/python/gui/layout/qgslayoutitemguiregistry.sip b/python/gui/layout/qgslayoutitemguiregistry.sip index 67de0cd3cb3..8f7d552b498 100644 --- a/python/gui/layout/qgslayoutitemguiregistry.sip +++ b/python/gui/layout/qgslayoutitemguiregistry.sip @@ -55,10 +55,10 @@ class QgsLayoutItemAbstractGuiMetadata :rtype: QIcon %End - virtual QWidget *createItemWidget() /Factory/; + virtual QgsLayoutItemBaseWidget *createItemWidget( QgsLayoutItem *item ) /Factory/; %Docstring - Creates a configuration widget for layout items of this type. Can return None if no configuration GUI is required. - :rtype: QWidget + Creates a configuration widget for an ``item`` of this type. Can return None if no configuration GUI is required. + :rtype: QgsLayoutItemBaseWidget %End virtual QgsLayoutViewRubberBand *createRubberBand( QgsLayoutView *view ) /Factory/; @@ -186,10 +186,10 @@ class QgsLayoutItemGuiRegistry : QObject :rtype: QgsLayoutItemGuiGroup %End - QWidget *createItemWidget( int type ) const /Factory/; + QgsLayoutItemBaseWidget *createItemWidget( QgsLayoutItem *item ) const /Factory/; %Docstring - Creates a new instance of a layout item configuration widget for the specified item ``type``. - :rtype: QWidget + Creates a new instance of a layout item configuration widget for the specified ``item``. + :rtype: QgsLayoutItemBaseWidget %End diff --git a/src/gui/layout/qgslayoutitemguiregistry.cpp b/src/gui/layout/qgslayoutitemguiregistry.cpp index 7d2022c8b82..43ad0c76dd9 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.cpp +++ b/src/gui/layout/qgslayoutitemguiregistry.cpp @@ -92,12 +92,15 @@ const QgsLayoutItemGuiGroup &QgsLayoutItemGuiRegistry::itemGroup( const QString return mItemGroups[ id ]; } -QWidget *QgsLayoutItemGuiRegistry::createItemWidget( int type ) const +QgsLayoutItemBaseWidget *QgsLayoutItemGuiRegistry::createItemWidget( QgsLayoutItem *item ) const { - if ( !mMetadata.contains( type ) ) + if ( !item ) return nullptr; - return mMetadata[type]->createItemWidget(); + if ( !mMetadata.contains( item->type() ) ) + return nullptr; + + return mMetadata[item->type()]->createItemWidget( item ); } QgsLayoutViewRubberBand *QgsLayoutItemGuiRegistry::createItemRubberBand( int type, QgsLayoutView *view ) const diff --git a/src/gui/layout/qgslayoutitemguiregistry.h b/src/gui/layout/qgslayoutitemguiregistry.h index 546f8d04a0e..fc80758ed53 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.h +++ b/src/gui/layout/qgslayoutitemguiregistry.h @@ -30,6 +30,7 @@ class QgsLayout; class QgsLayoutView; class QgsLayoutItem; class QgsLayoutViewRubberBand; +class QgsLayoutItemBaseWidget; /** * \ingroup gui @@ -73,9 +74,9 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata virtual QIcon creationIcon() const { return QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ); } /** - * Creates a configuration widget for layout items of this type. Can return nullptr if no configuration GUI is required. + * Creates a configuration widget for an \a item of this type. Can return nullptr if no configuration GUI is required. */ - virtual QWidget *createItemWidget() SIP_FACTORY { return nullptr; } + virtual QgsLayoutItemBaseWidget *createItemWidget( QgsLayoutItem *item ) SIP_FACTORY { Q_UNUSED( item ); return nullptr; } /** * Creates a rubber band for use when creating layout items of this type. Can return nullptr if no rubber band @@ -91,7 +92,7 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata }; //! Layout item configuration widget creation function -typedef std::function QgsLayoutItemWidgetFunc SIP_SKIP; +typedef std::function QgsLayoutItemWidgetFunc SIP_SKIP; //! Layout rubber band creation function typedef std::function QgsLayoutItemRubberBandFunc SIP_SKIP; @@ -149,7 +150,7 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad void setRubberBandCreationFunction( QgsLayoutItemRubberBandFunc function ) { mRubberBandFunc = function; } QIcon creationIcon() const override { return mIcon.isNull() ? QgsLayoutItemAbstractGuiMetadata::creationIcon() : mIcon; } - QWidget *createItemWidget() override { return mWidgetFunc ? mWidgetFunc() : nullptr; } + QgsLayoutItemBaseWidget *createItemWidget( QgsLayoutItem *item ) override { return mWidgetFunc ? mWidgetFunc( item ) : nullptr; } QgsLayoutViewRubberBand *createRubberBand( QgsLayoutView *view ) override { return mRubberBandFunc ? mRubberBandFunc( view ) : nullptr; } protected: @@ -275,9 +276,9 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject const QgsLayoutItemGuiGroup &itemGroup( const QString &id ); /** - * Creates a new instance of a layout item configuration widget for the specified item \a type. + * Creates a new instance of a layout item configuration widget for the specified \a item. */ - QWidget *createItemWidget( int type ) const SIP_FACTORY; + QgsLayoutItemBaseWidget *createItemWidget( QgsLayoutItem *item ) const SIP_FACTORY; /** * Creates a new rubber band item for the specified item \a type and destination \a view. diff --git a/tests/src/gui/testqgslayoutview.cpp b/tests/src/gui/testqgslayoutview.cpp index 608a3a81085..7aa23f7b1ee 100644 --- a/tests/src/gui/testqgslayoutview.cpp +++ b/tests/src/gui/testqgslayoutview.cpp @@ -22,6 +22,7 @@ #include "qgslayoutviewrubberband.h" #include "qgslayoutitemregistry.h" #include "qgslayoutitemguiregistry.h" +#include "qgslayoutitemwidget.h" #include "qgstestutils.h" #include "qgsproject.h" #include "qgsgui.h" @@ -242,6 +243,7 @@ class TestItem : public QgsLayoutItem //implement pure virtual methods int type() const override { return QgsLayoutItemRegistry::LayoutItem + 101; } + QString stringType() const override { return QStringLiteral( "testitem" ); } void draw( QgsRenderContext &, const QStyleOptionGraphicsItem * = nullptr ) override { } }; @@ -259,14 +261,17 @@ void TestQgsLayoutView::guiRegistry() // empty registry QVERIFY( !registry.itemMetadata( -1 ) ); QVERIFY( registry.itemTypes().isEmpty() ); - QVERIFY( !registry.createItemWidget( 1 ) ); + QVERIFY( !registry.createItemWidget( nullptr ) ); + QVERIFY( !registry.createItemWidget( nullptr ) ); + TestItem *testItem = new TestItem( nullptr ); + QVERIFY( !registry.createItemWidget( testItem ) ); // not in registry QSignalSpy spyTypeAdded( ®istry, &QgsLayoutItemGuiRegistry::typeAdded ); // add a dummy item to registry - auto createWidget = []()->QWidget* + auto createWidget = []( QgsLayoutItem * item )->QgsLayoutItemBaseWidget* { - return new QWidget(); + return new QgsLayoutItemBaseWidget( nullptr, item ); }; auto createRubberBand = []( QgsLayoutView * view )->QgsLayoutViewRubberBand * @@ -274,27 +279,27 @@ void TestQgsLayoutView::guiRegistry() return new QgsLayoutViewRectangularRubberBand( view ); }; - QgsLayoutItemGuiMetadata *metadata = new QgsLayoutItemGuiMetadata( 2, QIcon(), createWidget, createRubberBand ); + QgsLayoutItemGuiMetadata *metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QIcon(), createWidget, createRubberBand ); QVERIFY( registry.addLayoutItemGuiMetadata( metadata ) ); QCOMPARE( spyTypeAdded.count(), 1 ); - QCOMPARE( spyTypeAdded.value( 0 ).at( 0 ).toInt(), 2 ); + QCOMPARE( spyTypeAdded.value( 0 ).at( 0 ).toInt(), QgsLayoutItemRegistry::LayoutItem + 101 ); // duplicate type id QVERIFY( !registry.addLayoutItemGuiMetadata( metadata ) ); QCOMPARE( spyTypeAdded.count(), 1 ); //retrieve metadata QVERIFY( !registry.itemMetadata( -1 ) ); - QVERIFY( registry.itemMetadata( 2 ) ); + QVERIFY( registry.itemMetadata( QgsLayoutItemRegistry::LayoutItem + 101 ) ); QCOMPARE( registry.itemTypes().count(), 1 ); - QCOMPARE( registry.itemTypes().value( 0 ), 2 ); + QCOMPARE( registry.itemTypes().value( 0 ), QgsLayoutItemRegistry::LayoutItem + 101 ); - QWidget *widget = registry.createItemWidget( 2 ); + QWidget *widget = registry.createItemWidget( testItem ); QVERIFY( widget ); delete widget; QgsLayoutView *view = new QgsLayoutView(); //should use metadata's method - QgsLayoutViewRubberBand *band = registry.createItemRubberBand( 2, view ); + QgsLayoutViewRubberBand *band = registry.createItemRubberBand( QgsLayoutItemRegistry::LayoutItem + 101, view ); QVERIFY( band ); QVERIFY( dynamic_cast< QgsLayoutViewRectangularRubberBand * >( band ) ); QCOMPARE( band->view(), view ); From 20029c2956207a0765a225c2ade8ab9a5da41b64 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 21:58:28 +1000 Subject: [PATCH 189/266] Add a lot of framework code for showing item properties in designer And hook up a non-functional page properties widget which is shown when right clicking on a page in the view. --- .../gui/layout/qgslayoutitemguiregistry.sip | 15 +- src/app/CMakeLists.txt | 2 + src/app/layout/qgslayoutappmenuprovider.cpp | 11 +- src/app/layout/qgslayoutappmenuprovider.h | 8 +- src/app/layout/qgslayoutdesignerdialog.cpp | 60 +++++- src/app/layout/qgslayoutdesignerdialog.h | 14 ++ .../layout/qgslayoutpagepropertieswidget.cpp | 25 +++ .../layout/qgslayoutpagepropertieswidget.h | 52 +++++ src/core/layout/qgslayoutitemregistry.cpp | 2 + src/gui/layout/qgslayoutitemguiregistry.h | 20 +- src/ui/layout/qgslayoutdesignerbase.ui | 8 +- .../layout/qgslayoutpagepropertieswidget.ui | 180 ++++++++++++++++++ 12 files changed, 385 insertions(+), 12 deletions(-) create mode 100644 src/app/layout/qgslayoutpagepropertieswidget.cpp create mode 100644 src/app/layout/qgslayoutpagepropertieswidget.h create mode 100644 src/ui/layout/qgslayoutpagepropertieswidget.ui diff --git a/python/gui/layout/qgslayoutitemguiregistry.sip b/python/gui/layout/qgslayoutitemguiregistry.sip index 8f7d552b498..3de56bd3df4 100644 --- a/python/gui/layout/qgslayoutitemguiregistry.sip +++ b/python/gui/layout/qgslayoutitemguiregistry.sip @@ -28,7 +28,14 @@ class QgsLayoutItemAbstractGuiMetadata %End public: - QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString() ); + enum Flag + { + FlagNoCreationTools, + }; + typedef QFlags Flags; + + + QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString(), Flags flags = 0 ); %Docstring Constructor for QgsLayoutItemAbstractGuiMetadata with the specified class ``type``. @@ -43,6 +50,12 @@ class QgsLayoutItemAbstractGuiMetadata :rtype: int %End + Flags flags() const; +%Docstring + Returns item flags. + :rtype: Flags +%End + QString groupId() const; %Docstring Returns the item group ID, if set. diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 06a3ac9e092..997c1eae6f6 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -158,6 +158,7 @@ SET(QGIS_APP_SRCS layout/qgslayoutaddpagesdialog.cpp layout/qgslayoutdesignerdialog.cpp layout/qgslayoutappmenuprovider.cpp + layout/qgslayoutpagepropertieswidget.cpp locator/qgsinbuiltlocatorfilters.cpp locator/qgslocatoroptionswidget.cpp @@ -337,6 +338,7 @@ SET (QGIS_APP_MOC_HDRS layout/qgslayoutaddpagesdialog.h layout/qgslayoutappmenuprovider.h layout/qgslayoutdesignerdialog.h + layout/qgslayoutpagepropertieswidget.h locator/qgsinbuiltlocatorfilters.h locator/qgslocatoroptionswidget.h diff --git a/src/app/layout/qgslayoutappmenuprovider.cpp b/src/app/layout/qgslayoutappmenuprovider.cpp index 308c9d3a5ea..1c590746a2f 100644 --- a/src/app/layout/qgslayoutappmenuprovider.cpp +++ b/src/app/layout/qgslayoutappmenuprovider.cpp @@ -15,12 +15,14 @@ #include "qgslayoutappmenuprovider.h" #include "qgslayoutitempage.h" +#include "qgslayoutdesignerdialog.h" #include "qgslayout.h" #include #include -QgsLayoutAppMenuProvider::QgsLayoutAppMenuProvider( QObject *parent ) - : QObject( parent ) +QgsLayoutAppMenuProvider::QgsLayoutAppMenuProvider( QgsLayoutDesignerDialog *designer ) + : QObject( nullptr ) + , mDesigner( designer ) { } @@ -34,10 +36,9 @@ QMenu *QgsLayoutAppMenuProvider::createContextMenu( QWidget *parent, QgsLayout * if ( page ) { QAction *pagePropertiesAction = new QAction( tr( "Page Properties…" ), menu ); - connect( pagePropertiesAction, &QAction::triggered, this, [page]() + connect( pagePropertiesAction, &QAction::triggered, this, [this, page]() { - - + mDesigner->showItemOptions( page ); } ); menu->addAction( pagePropertiesAction ); QAction *removePageAction = new QAction( tr( "Remove Page" ), menu ); diff --git a/src/app/layout/qgslayoutappmenuprovider.h b/src/app/layout/qgslayoutappmenuprovider.h index feed54be451..fda8f1eb90e 100644 --- a/src/app/layout/qgslayoutappmenuprovider.h +++ b/src/app/layout/qgslayoutappmenuprovider.h @@ -20,6 +20,8 @@ #include "qgslayoutview.h" #include +class QgsLayoutDesignerDialog; + /** * A menu provider for QgsLayoutView */ @@ -29,10 +31,14 @@ class QgsLayoutAppMenuProvider : public QObject, public QgsLayoutViewMenuProvide public: - QgsLayoutAppMenuProvider( QObject *parent = nullptr ); + QgsLayoutAppMenuProvider( QgsLayoutDesignerDialog *designer ); QMenu *createContextMenu( QWidget *parent, QgsLayout *layout, QPointF layoutPoint ) const override; + private: + + QgsLayoutDesignerDialog *mDesigner = nullptr; + }; #endif // QGSLAYOUTAPPMENUPROVIDER_H diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 18572f73cdd..cc5497512fe 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -27,10 +27,15 @@ #include "qgslayoutviewtoolpan.h" #include "qgslayoutviewtoolzoom.h" #include "qgslayoutviewtoolselect.h" +#include "qgslayoutitemwidget.h" #include "qgsgui.h" #include "qgslayoutitemguiregistry.h" #include "qgslayoutruler.h" #include "qgslayoutaddpagesdialog.h" +#include "qgspanelwidgetstack.h" +#include "qgspanelwidget.h" +#include "qgsdockwidget.h" +#include "qgslayoutpagepropertieswidget.h" #include #include #include @@ -44,6 +49,8 @@ QList QgsLayoutDesignerDialog::sStatusZoomLevelsList { 0.125, 0.25, 0.5, #define FIT_LAYOUT -101 #define FIT_LAYOUT_WIDTH -102 +bool QgsLayoutDesignerDialog::sInitializedRegistry = false; + QgsAppLayoutDesignerInterface::QgsAppLayoutDesignerInterface( QgsLayoutDesignerDialog *dialog ) : QgsLayoutDesignerInterface( dialog ) , mDesigner( dialog ) @@ -70,6 +77,10 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla , mInterface( new QgsAppLayoutDesignerInterface( this ) ) , mToolsActionGroup( new QActionGroup( this ) ) { + if ( !sInitializedRegistry ) + { + initializeRegistry(); + } QgsSettings settings; int size = settings.value( QStringLiteral( "IconSize" ), QGIS_ICON_SIZE ).toInt(); setIconSize( QSize( size, size ) ); @@ -222,9 +233,22 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla connect( mActionToggleFullScreen, &QAction::toggled, this, &QgsLayoutDesignerDialog::toggleFullScreen ); - mMenuProvider = new QgsLayoutAppMenuProvider(); + mMenuProvider = new QgsLayoutAppMenuProvider( this ); mView->setMenuProvider( mMenuProvider ); + int minDockWidth( fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) ); + + mItemDock = new QgsDockWidget( tr( "Item properties" ), this ); + mItemDock->setObjectName( QStringLiteral( "ItemDock" ) ); + mItemDock->setMinimumWidth( minDockWidth ); + mItemPropertiesStack = new QgsPanelWidgetStack(); + mItemDock->setWidget( mItemPropertiesStack ); + mPanelsMenu->addAction( mItemDock->toggleViewAction() ); + + addDockWidget( Qt::RightDockWidgetArea, mItemDock ); + + mItemDock->show(); + restoreWindowState(); } @@ -257,6 +281,25 @@ void QgsLayoutDesignerDialog::setIconSizes( int size ) } } +void QgsLayoutDesignerDialog::showItemOptions( QgsLayoutItem *item ) +{ + if ( !item ) + { + delete mItemPropertiesStack->takeMainPanel(); + return; + } + + std::unique_ptr< QgsLayoutItemBaseWidget > widget( QgsGui::layoutItemGuiRegistry()->createItemWidget( item ) ); + if ( ! widget ) + { + return; + } + + delete mItemPropertiesStack->takeMainPanel(); + widget->setDockMode( true ); + mItemPropertiesStack->setMainPanel( widget.release() ); +} + void QgsLayoutDesignerDialog::open() { show(); @@ -302,6 +345,9 @@ void QgsLayoutDesignerDialog::closeEvent( QCloseEvent * ) void QgsLayoutDesignerDialog::itemTypeAdded( int type ) { + if ( QgsGui::layoutItemGuiRegistry()->itemMetadata( type )->flags() & QgsLayoutItemAbstractGuiMetadata::FlagNoCreationTools ) + return; + QString name = QgsApplication::layoutItemRegistry()->itemMetadata( type )->visibleName(); QString groupId = QgsGui::layoutItemGuiRegistry()->itemMetadata( type )->groupId(); QToolButton *groupButton = nullptr; @@ -532,4 +578,16 @@ void QgsLayoutDesignerDialog::activateNewItemCreationTool( int type ) } } +void QgsLayoutDesignerDialog::initializeRegistry() +{ + sInitializedRegistry = true; + auto createPageWidget = ( []( QgsLayoutItem * item )->QgsLayoutItemBaseWidget * + { + return new QgsLayoutPagePropertiesWidget( nullptr, item ); + } ); + + QgsGui::layoutItemGuiRegistry()->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutPage, QIcon(), createPageWidget, nullptr, QString(), QgsLayoutItemAbstractGuiMetadata::FlagNoCreationTools ) ); + +} + diff --git a/src/app/layout/qgslayoutdesignerdialog.h b/src/app/layout/qgslayoutdesignerdialog.h index 72729768af0..0a93533ae06 100644 --- a/src/app/layout/qgslayoutdesignerdialog.h +++ b/src/app/layout/qgslayoutdesignerdialog.h @@ -32,6 +32,9 @@ class QComboBox; class QSlider; class QLabel; class QgsLayoutAppMenuProvider; +class QgsLayoutItem; +class QgsPanelWidgetStack; +class QgsDockWidget; class QgsAppLayoutDesignerInterface : public QgsLayoutDesignerInterface { @@ -90,6 +93,10 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner */ void setIconSizes( int size ); + /** + * Shows the configuration widget for the specified layout \a item. + */ + void showItemOptions( QgsLayoutItem *item ); public slots: @@ -138,6 +145,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner private: + static bool sInitializedRegistry; + QgsAppLayoutDesignerInterface *mInterface = nullptr; QgsLayout *mLayout = nullptr; @@ -170,6 +179,9 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner QgsLayoutAppMenuProvider *mMenuProvider; + QgsDockWidget *mItemDock = nullptr; + QgsPanelWidgetStack *mItemPropertiesStack = nullptr; + //! Save window state void saveWindowState(); @@ -179,6 +191,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner //! Switch to new item creation tool, for a new item of the specified \a type. void activateNewItemCreationTool( int type ); + void initializeRegistry(); + }; #endif // QGSLAYOUTDESIGNERDIALOG_H diff --git a/src/app/layout/qgslayoutpagepropertieswidget.cpp b/src/app/layout/qgslayoutpagepropertieswidget.cpp new file mode 100644 index 00000000000..80f7be73f37 --- /dev/null +++ b/src/app/layout/qgslayoutpagepropertieswidget.cpp @@ -0,0 +1,25 @@ +/*************************************************************************** + qgslayoutpagepropertieswidget.cpp + --------------------------------- + Date : July 2017 + Copyright : (C) 2017 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 "qgslayoutpagepropertieswidget.h" +#include "qgslayoutitempage.h" + +QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, QgsLayoutItem *layoutItem ) + : QgsLayoutItemBaseWidget( parent, layoutItem ) + , mPage( static_cast< QgsLayoutItemPage *>( layoutItem ) ) +{ + setupUi( this ); + +} diff --git a/src/app/layout/qgslayoutpagepropertieswidget.h b/src/app/layout/qgslayoutpagepropertieswidget.h new file mode 100644 index 00000000000..2f92d8fce49 --- /dev/null +++ b/src/app/layout/qgslayoutpagepropertieswidget.h @@ -0,0 +1,52 @@ +/*************************************************************************** + qgslayoutpagepropertieswidget.h + ------------------------------- + Date : July 2017 + Copyright : (C) 2017 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 QGSLAYOUTPAGEPROPERTIESWIDGET_H +#define QGSLAYOUTPAGEPROPERTIESWIDGET_H + +#include "qgis.h" +#include "ui_qgslayoutpagepropertieswidget.h" + +#include "qgslayoutsize.h" +#include "qgslayoutpoint.h" +#include "qgslayoutitemwidget.h" +#include "qgslayoutmeasurementconverter.h" + +class QgsLayoutItem; +class QgsLayoutItemPage; + +/** + * A widget for configuring properties of pages in a layout + */ +class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui::QgsLayoutPagePropertiesWidget +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsLayoutPagePropertiesWidget. + */ + QgsLayoutPagePropertiesWidget( QWidget *parent, QgsLayoutItem *page ); + + private: + + QgsLayoutItemPage *mPage = nullptr; + + QgsLayoutMeasurementConverter mConverter; + +}; + +#endif // QGSLAYOUTPAGEPROPERTIESWIDGET_H diff --git a/src/core/layout/qgslayoutitemregistry.cpp b/src/core/layout/qgslayoutitemregistry.cpp index 0b9b8dbffea..d8840d93f73 100644 --- a/src/core/layout/qgslayoutitemregistry.cpp +++ b/src/core/layout/qgslayoutitemregistry.cpp @@ -42,6 +42,8 @@ bool QgsLayoutItemRegistry::populate() }; addLayoutItemType( new QgsLayoutItemMetadata( 101, QStringLiteral( "temp type" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), createTemporaryItem ) ); + addLayoutItemType( new QgsLayoutItemMetadata( LayoutPage, QStringLiteral( "Page" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileNew.svg" ) ), nullptr ) ); + addLayoutItemType( new QgsLayoutItemMetadata( LayoutRectangle, QStringLiteral( "Rectangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), QgsLayoutItemRectangularShape::create ) ); addLayoutItemType( new QgsLayoutItemMetadata( LayoutEllipse, QStringLiteral( "Ellipse" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), QgsLayoutItemEllipseShape::create ) ); addLayoutItemType( new QgsLayoutItemMetadata( LayoutTriangle, QStringLiteral( "Triangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), QgsLayoutItemTriangleShape::create ) ); diff --git a/src/gui/layout/qgslayoutitemguiregistry.h b/src/gui/layout/qgslayoutitemguiregistry.h index fc80758ed53..871e92ac1de 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.h +++ b/src/gui/layout/qgslayoutitemguiregistry.h @@ -46,14 +46,22 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata { public: + //! Flags for controlling how a items behave in the GUI + enum Flag + { + FlagNoCreationTools = 1 << 1, //!< Do not show item creation tools for the item type + }; + Q_DECLARE_FLAGS( Flags, Flag ) + /** * Constructor for QgsLayoutItemAbstractGuiMetadata with the specified class \a type. * * An optional \a groupId can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details. */ - QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString() ) + QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString(), Flags flags = 0 ) : mType( type ) , mGroupId( groupId ) + , mFlags( flags ) {} virtual ~QgsLayoutItemAbstractGuiMetadata() = default; @@ -63,6 +71,11 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata */ int type() const { return mType; } + /** + * Returns item flags. + */ + Flags flags() const { return mFlags; } + /** * Returns the item group ID, if set. */ @@ -88,6 +101,7 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata int mType = -1; QString mGroupId; + Flags mFlags; }; @@ -118,8 +132,8 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad */ QgsLayoutItemGuiMetadata( int type, const QIcon &creationIcon, QgsLayoutItemWidgetFunc pfWidget = nullptr, - QgsLayoutItemRubberBandFunc pfRubberBand = nullptr, const QString &groupId = QString() ) - : QgsLayoutItemAbstractGuiMetadata( type, groupId ) + QgsLayoutItemRubberBandFunc pfRubberBand = nullptr, const QString &groupId = QString(), QgsLayoutItemAbstractGuiMetadata::Flags flags = 0 ) + : QgsLayoutItemAbstractGuiMetadata( type, groupId, flags ) , mIcon( creationIcon ) , mWidgetFunc( pfWidget ) , mRubberBandFunc( pfRubberBand ) diff --git a/src/ui/layout/qgslayoutdesignerbase.ui b/src/ui/layout/qgslayoutdesignerbase.ui index 6be4a439438..980818247a0 100644 --- a/src/ui/layout/qgslayoutdesignerbase.ui +++ b/src/ui/layout/qgslayoutdesignerbase.ui @@ -83,7 +83,7 @@ 0 0 1083 - 25 + 42 @@ -107,6 +107,11 @@ &Toolbars + + + &Panels + + @@ -116,6 +121,7 @@ + diff --git a/src/ui/layout/qgslayoutpagepropertieswidget.ui b/src/ui/layout/qgslayoutpagepropertieswidget.ui new file mode 100644 index 00000000000..fb87590d9bf --- /dev/null +++ b/src/ui/layout/qgslayoutpagepropertieswidget.ui @@ -0,0 +1,180 @@ + + + QgsLayoutPagePropertiesWidget + + + + 0 + 0 + 660 + 368 + + + + New Item Properties + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Page size + + + + + + Size + + + + + + + Width + + + + + + + + + + Orientation + + + + + + + + + + Height + + + + + + + + + + + + 2 + + + 2 + + + 0 + + + 2 + + + + + + 0 + 0 + + + + Lock aspect ratio (including while drawing extent onto canvas) + + + 13 + + + + + + + + + + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + + + + 3 + + + 9999999.000000000000000 + + + 100.000000000000000 + + + false + + + + + + + + + + + + + QgsRatioLockButton + QToolButton +
qgsratiolockbutton.h
+ 1 +
+ + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsLayoutUnitsComboBox + QComboBox +
qgslayoutunitscombobox.h
+
+
+ + mPageSizeComboBox + mPageOrientationComboBox + mWidthSpin + mHeightSpin + mLockAspectRatio + mSizeUnitsComboBox + + + +
From a4113fe51dec41dc51b16fa8c99100467ec5f751 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 22:14:37 +1000 Subject: [PATCH 190/266] Functional page properties widget --- .../layout/qgslayoutpagepropertieswidget.cpp | 105 ++++++++++++++++++ .../layout/qgslayoutpagepropertieswidget.h | 6 + 2 files changed, 111 insertions(+) diff --git a/src/app/layout/qgslayoutpagepropertieswidget.cpp b/src/app/layout/qgslayoutpagepropertieswidget.cpp index 80f7be73f37..a213b1ce7e2 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.cpp +++ b/src/app/layout/qgslayoutpagepropertieswidget.cpp @@ -14,7 +14,10 @@ ***************************************************************************/ #include "qgslayoutpagepropertieswidget.h" +#include "qgsapplication.h" +#include "qgspagesizeregistry.h" #include "qgslayoutitempage.h" +#include "qgslayout.h" QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, QgsLayoutItem *layoutItem ) : QgsLayoutItemBaseWidget( parent, layoutItem ) @@ -22,4 +25,106 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q { setupUi( this ); + mPageOrientationComboBox->addItem( tr( "Portrait" ), QgsLayoutItemPage::Portrait ); + mPageOrientationComboBox->addItem( tr( "Landscape" ), QgsLayoutItemPage::Landscape ); + + Q_FOREACH ( const QgsPageSize &size, QgsApplication::pageSizeRegistry()->entries() ) + { + mPageSizeComboBox->addItem( size.displayName, size.name ); + } + mPageSizeComboBox->addItem( tr( "Custom" ) ); + mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->count() - 1 ); + //TODO - match to preset page sizes + + mWidthSpin->setValue( mPage->pageSize().width() ); + mHeightSpin->setValue( mPage->pageSize().height() ); + mSizeUnitsComboBox->setUnit( mPage->pageSize().units() ); + + mPageOrientationComboBox->setCurrentIndex( mPageOrientationComboBox->findData( mPage->orientation() ) ); + + mSizeUnitsComboBox->linkToWidget( mWidthSpin ); + mSizeUnitsComboBox->linkToWidget( mHeightSpin ); + mSizeUnitsComboBox->setConverter( &mPage->layout()->context().measurementConverter() ); + + mLockAspectRatio->setWidthSpinBox( mWidthSpin ); + mLockAspectRatio->setHeightSpinBox( mHeightSpin ); + + connect( mPageSizeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutPagePropertiesWidget::pageSizeChanged ); + connect( mPageOrientationComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutPagePropertiesWidget::orientationChanged ); + + connect( mWidthSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::updatePageSize ); + connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::updatePageSize ); + +} + +void QgsLayoutPagePropertiesWidget::pageSizeChanged( int ) +{ + if ( mPageSizeComboBox->currentData().toString().isEmpty() ) + { + //custom size + mWidthSpin->setEnabled( true ); + mHeightSpin->setEnabled( true ); + mLockAspectRatio->setEnabled( true ); + mSizeUnitsComboBox->setEnabled( true ); + mPageOrientationComboBox->setEnabled( false ); + } + else + { + mWidthSpin->setEnabled( false ); + mHeightSpin->setEnabled( false ); + mLockAspectRatio->setEnabled( false ); + mLockAspectRatio->setLocked( false ); + mSizeUnitsComboBox->setEnabled( false ); + mPageOrientationComboBox->setEnabled( true ); + QgsPageSize size = QgsApplication::pageSizeRegistry()->find( mPageSizeComboBox->currentData().toString() ).value( 0 ); + QgsLayoutSize convertedSize = mConverter.convert( size.size, mSizeUnitsComboBox->unit() ); + switch ( mPageOrientationComboBox->currentData().toInt() ) + { + case QgsLayoutItemPage::Landscape: + mWidthSpin->setValue( convertedSize.height() ); + mHeightSpin->setValue( convertedSize.width() ); + break; + + case QgsLayoutItemPage::Portrait: + mWidthSpin->setValue( convertedSize.width() ); + mHeightSpin->setValue( convertedSize.height() ); + break; + } + } + updatePageSize(); +} + +void QgsLayoutPagePropertiesWidget::orientationChanged( int ) +{ + if ( mPageSizeComboBox->currentData().toString().isEmpty() ) + return; + + double width = mWidthSpin->value(); + double height = mHeightSpin->value(); + switch ( mPageOrientationComboBox->currentData().toInt() ) + { + case QgsLayoutItemPage::Landscape: + if ( width < height ) + { + whileBlocking( mWidthSpin )->setValue( height ); + whileBlocking( mHeightSpin )->setValue( width ); + } + break; + + case QgsLayoutItemPage::Portrait: + if ( width > height ) + { + whileBlocking( mWidthSpin )->setValue( height ); + whileBlocking( mHeightSpin )->setValue( width ); + } + break; + } + + updatePageSize(); +} + +void QgsLayoutPagePropertiesWidget::updatePageSize() +{ + mPage->setPageSize( QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value(), mSizeUnitsComboBox->unit() ) ); + mPage->layout()->pageCollection()->reflow(); } diff --git a/src/app/layout/qgslayoutpagepropertieswidget.h b/src/app/layout/qgslayoutpagepropertieswidget.h index 2f92d8fce49..405c183d121 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.h +++ b/src/app/layout/qgslayoutpagepropertieswidget.h @@ -41,6 +41,12 @@ class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui */ QgsLayoutPagePropertiesWidget( QWidget *parent, QgsLayoutItem *page ); + private slots: + + void pageSizeChanged( int index ); + void orientationChanged( int index ); + void updatePageSize(); + private: QgsLayoutItemPage *mPage = nullptr; From fabfd77c2b68cf88359debae7c20908f71c6addd Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 22 Jul 2017 22:32:23 +1000 Subject: [PATCH 191/266] Show known page size when opening page properties if current page size matches --- python/core/layout/qgspagesizeregistry.sip | 10 +++++++ .../layout/qgslayoutpagepropertieswidget.cpp | 29 +++++++++++++++++-- .../layout/qgslayoutpagepropertieswidget.h | 2 ++ src/core/layout/qgspagesizeregistry.cpp | 20 +++++++++++++ src/core/layout/qgspagesizeregistry.h | 9 ++++++ tests/src/core/testqgspagesizeregistry.cpp | 12 ++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) diff --git a/python/core/layout/qgspagesizeregistry.sip b/python/core/layout/qgspagesizeregistry.sip index c57a4bb16b9..cdf52fb13b9 100644 --- a/python/core/layout/qgspagesizeregistry.sip +++ b/python/core/layout/qgspagesizeregistry.sip @@ -95,6 +95,16 @@ class QgsPageSizeRegistry :rtype: list of QgsPageSize %End + QString find( const QgsLayoutSize &size ) const; +%Docstring + Finds a matching page ``size`` from the registry. Returns the page size name, + or an empty string if no matching size could be found. + + Orientation is ignored when matching page sizes, so a landscape A4 page will + match to the portrait A4 size in the registry. + :rtype: str +%End + bool decodePageSize( const QString &string, QgsPageSize &size ); %Docstring Decodes a ``string`` representing a preset page size. diff --git a/src/app/layout/qgslayoutpagepropertieswidget.cpp b/src/app/layout/qgslayoutpagepropertieswidget.cpp index a213b1ce7e2..fd42de92860 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.cpp +++ b/src/app/layout/qgslayoutpagepropertieswidget.cpp @@ -33,8 +33,7 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q mPageSizeComboBox->addItem( size.displayName, size.name ); } mPageSizeComboBox->addItem( tr( "Custom" ) ); - mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->count() - 1 ); - //TODO - match to preset page sizes + showCurrentPageSize(); mWidthSpin->setValue( mPage->pageSize().width() ); mHeightSpin->setValue( mPage->pageSize().height() ); @@ -128,3 +127,29 @@ void QgsLayoutPagePropertiesWidget::updatePageSize() mPage->setPageSize( QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value(), mSizeUnitsComboBox->unit() ) ); mPage->layout()->pageCollection()->reflow(); } + +void QgsLayoutPagePropertiesWidget::showCurrentPageSize() +{ + QgsLayoutSize paperSize = mPage->pageSize(); + QString pageSize = QgsApplication::pageSizeRegistry()->find( paperSize ); + if ( !pageSize.isEmpty() ) + { + mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->findData( pageSize ) ); + mWidthSpin->setEnabled( false ); + mHeightSpin->setEnabled( false ); + mLockAspectRatio->setEnabled( false ); + mLockAspectRatio->setLocked( false ); + mSizeUnitsComboBox->setEnabled( false ); + mPageOrientationComboBox->setEnabled( true ); + } + else + { + // custom + mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->count() - 1 ); + mWidthSpin->setEnabled( true ); + mHeightSpin->setEnabled( true ); + mLockAspectRatio->setEnabled( true ); + mSizeUnitsComboBox->setEnabled( true ); + mPageOrientationComboBox->setEnabled( false ); + } +} diff --git a/src/app/layout/qgslayoutpagepropertieswidget.h b/src/app/layout/qgslayoutpagepropertieswidget.h index 405c183d121..79185149324 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.h +++ b/src/app/layout/qgslayoutpagepropertieswidget.h @@ -53,6 +53,8 @@ class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui QgsLayoutMeasurementConverter mConverter; + void showCurrentPageSize(); + }; #endif // QGSLAYOUTPAGEPROPERTIESWIDGET_H diff --git a/src/core/layout/qgspagesizeregistry.cpp b/src/core/layout/qgspagesizeregistry.cpp index c03140a1b5d..e2824b615dc 100644 --- a/src/core/layout/qgspagesizeregistry.cpp +++ b/src/core/layout/qgspagesizeregistry.cpp @@ -15,6 +15,7 @@ ***************************************************************************/ #include "qgspagesizeregistry.h" +#include "qgslayoutmeasurementconverter.h" // // QgsPageSizeRegistry @@ -83,6 +84,25 @@ QList QgsPageSizeRegistry::find( const QString &name ) const return result; } +QString QgsPageSizeRegistry::find( const QgsLayoutSize &size ) const +{ + //try to match to existing page size + QgsLayoutMeasurementConverter converter; + Q_FOREACH ( const QgsPageSize &pageSize, mPageSizes ) + { + // convert passed size to same units + QgsLayoutSize xSize = converter.convert( size, pageSize.size.units() ); + + //consider width and height values may be exchanged + if ( ( qgsDoubleNear( xSize.width(), pageSize.size.width() ) && qgsDoubleNear( xSize.height(), pageSize.size.height() ) ) + || ( qgsDoubleNear( xSize.height(), pageSize.size.width() ) && qgsDoubleNear( xSize.width(), pageSize.size.height() ) ) ) + { + return pageSize.name; + } + } + return QString(); +} + bool QgsPageSizeRegistry::decodePageSize( const QString &pageSizeName, QgsPageSize &pageSize ) { QList< QgsPageSize > matches = find( pageSizeName.trimmed() ); diff --git a/src/core/layout/qgspagesizeregistry.h b/src/core/layout/qgspagesizeregistry.h index 2061e57f58f..295580b15d1 100644 --- a/src/core/layout/qgspagesizeregistry.h +++ b/src/core/layout/qgspagesizeregistry.h @@ -95,6 +95,15 @@ class CORE_EXPORT QgsPageSizeRegistry */ QList< QgsPageSize > find( const QString &name ) const; + /** + * Finds a matching page \a size from the registry. Returns the page size name, + * or an empty string if no matching size could be found. + * + * Orientation is ignored when matching page sizes, so a landscape A4 page will + * match to the portrait A4 size in the registry. + */ + QString find( const QgsLayoutSize &size ) const; + /** * Decodes a \a string representing a preset page size. * The decoded page size will be stored in the \a size argument. diff --git a/tests/src/core/testqgspagesizeregistry.cpp b/tests/src/core/testqgspagesizeregistry.cpp index 153e744b846..2d127f65d45 100644 --- a/tests/src/core/testqgspagesizeregistry.cpp +++ b/tests/src/core/testqgspagesizeregistry.cpp @@ -36,6 +36,7 @@ class TestQgsPageSizeRegistry : public QObject void instanceHasDefaultSizes(); // check that global instance is populated with default page sizes void addSize(); // check adding a size to the registry void findSize(); //find a size in the registry + void findBySize(); //find a matching size in the registry void decodePageSize(); //test decoding a page size string private: @@ -122,6 +123,17 @@ void TestQgsPageSizeRegistry::findSize() QCOMPARE( results2.at( 0 ), newSize ); } +void TestQgsPageSizeRegistry::findBySize() +{ + QgsPageSizeRegistry *registry = QgsApplication::pageSizeRegistry(); + QVERIFY( registry->find( QgsLayoutSize( 1, 1 ) ).isEmpty() ); + QCOMPARE( registry->find( QgsLayoutSize( 210, 297 ) ), QStringLiteral( "A4" ) ); + QCOMPARE( registry->find( QgsLayoutSize( 297, 210 ) ), QStringLiteral( "A4" ) ); + QCOMPARE( registry->find( QgsLayoutSize( 125, 176 ) ), QStringLiteral( "B6" ) ); + QCOMPARE( registry->find( QgsLayoutSize( 21, 29.7, QgsUnitTypes::LayoutCentimeters ) ), QStringLiteral( "A4" ) ); + +} + void TestQgsPageSizeRegistry::decodePageSize() { QgsPageSizeRegistry *registry = QgsApplication::pageSizeRegistry(); From 28281ee896881332566739eff5a362d85c95aa92 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 10:52:11 +1000 Subject: [PATCH 192/266] Set the background for layouts on the view, rather then in the scene Setting the background on the scene means in applies in renders of the scene (e.g. to images). We don't want this - we always want scenes rendered on transparent backgrounds. So instead use stylesheets to only show the grey background outside of pages in the view. --- src/core/layout/qgslayout.cpp | 3 ++- src/gui/layout/qgslayoutview.cpp | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index ac4b7902d28..3eaf21967a3 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -22,7 +22,8 @@ QgsLayout::QgsLayout( QgsProject *project ) , mProject( project ) , mPageCollection( new QgsLayoutPageCollection( this ) ) { - setBackgroundBrush( QColor( 215, 215, 215 ) ); + // just to make sure - this should be the default, but maybe it'll change in some future Qt version... + setBackgroundBrush( Qt::NoBrush ); } void QgsLayout::initializeDefaults() diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index 1f26cd95f70..92093ba0b21 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -40,6 +40,12 @@ QgsLayoutView::QgsLayoutView( QWidget *parent ) setMouseTracking( true ); viewport()->setMouseTracking( true ); + // set the "scene" background on the view using stylesheets + // we don't want to use QGraphicsScene::setBackgroundBrush because we want to keep + // a transparent background for exports, and it's only a cosmetic thing for the view only + // ALSO - only set it on the viewport - we don't want scrollbars/etc affected by this + viewport()->setStyleSheet( QStringLiteral( "background-color:#d7d7d7;" ) ); + mSpacePanTool = new QgsLayoutViewToolTemporaryKeyPan( this ); mMidMouseButtonPanTool = new QgsLayoutViewToolTemporaryMousePan( this ); mSpaceZoomTool = new QgsLayoutViewToolTemporaryKeyZoom( this ); From cdb0ace28e12e39be86b2f111dd4ec5f62aac438 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 10:53:46 +1000 Subject: [PATCH 193/266] Try to fix sip item casting clashing between composer items and layout items Add a temporary hack to QgsComposerItem subclassing to handle this for now. --- python/core/composer/qgscomposeritem.sip | 129 +++++++++++++---------- src/core/composer/qgscomposeritem.h | 129 +++++++++++++---------- 2 files changed, 148 insertions(+), 110 deletions(-) diff --git a/python/core/composer/qgscomposeritem.sip b/python/core/composer/qgscomposeritem.sip index 8fdc073c13f..e9bd9966ee2 100644 --- a/python/core/composer/qgscomposeritem.sip +++ b/python/core/composer/qgscomposeritem.sip @@ -31,68 +31,87 @@ class QgsComposerItem: QgsComposerObject, QGraphicsRectItem #include #include #include +#include +#include %End %ConvertToSubClassCode // the conversions have to be static, because they're using multiple inheritance // (seen in PyQt4 .sip files for some QGraphicsItem classes) - switch ( sipCpp->type() ) + if ( dynamic_cast< QgsComposerItem * >( sipCpp ) ) { - case QgsComposerItem::ComposerItem: - sipType = sipType_QgsComposerItem; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerArrow: - sipType = sipType_QgsComposerArrow; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerItemGroup: - sipType = sipType_QgsComposerItemGroup; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerLabel: - sipType = sipType_QgsComposerLabel; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerLegend: - sipType = sipType_QgsComposerLegend; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerMap: - sipType = sipType_QgsComposerMap; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPaper: - sipType = sipType_QgsPaperItem; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPicture: - sipType = sipType_QgsComposerPicture; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerScaleBar: - sipType = sipType_QgsComposerScaleBar; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerShape: - sipType = sipType_QgsComposerShape; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPolygon: - sipType = sipType_QgsComposerPolygon; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPolyline: - sipType = sipType_QgsComposerPolyline; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerFrame: - sipType = sipType_QgsComposerFrame; - *sipCppRet = static_cast( sipCpp ); - break; - default: - sipType = 0; + switch ( sipCpp->type() ) + { + case QgsComposerItem::ComposerItem: + sipType = sipType_QgsComposerItem; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerArrow: + sipType = sipType_QgsComposerArrow; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerItemGroup: + sipType = sipType_QgsComposerItemGroup; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerLabel: + sipType = sipType_QgsComposerLabel; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerLegend: + sipType = sipType_QgsComposerLegend; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerMap: + sipType = sipType_QgsComposerMap; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPaper: + sipType = sipType_QgsPaperItem; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPicture: + sipType = sipType_QgsComposerPicture; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerScaleBar: + sipType = sipType_QgsComposerScaleBar; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerShape: + sipType = sipType_QgsComposerShape; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPolygon: + sipType = sipType_QgsComposerPolygon; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPolyline: + sipType = sipType_QgsComposerPolyline; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerFrame: + sipType = sipType_QgsComposerFrame; + *sipCppRet = static_cast( sipCpp ); + break; + default: + sipType = 0; + } } + else + { + switch ( sipCpp->type() ) + { + // really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that! + case QGraphicsItem::UserType + 101: + sipType = sipType_QgsLayoutItemPage; + *sipCppRet = static_cast( sipCpp ); + break; + default: + sipType = 0; + } + } + %End public: diff --git a/src/core/composer/qgscomposeritem.h b/src/core/composer/qgscomposeritem.h index 3da9ac792a5..f63109335a7 100644 --- a/src/core/composer/qgscomposeritem.h +++ b/src/core/composer/qgscomposeritem.h @@ -53,6 +53,8 @@ class CORE_EXPORT QgsComposerItem: public QgsComposerObject, public QGraphicsRec #include #include #include +#include +#include #endif @@ -60,63 +62,80 @@ class CORE_EXPORT QgsComposerItem: public QgsComposerObject, public QGraphicsRec SIP_CONVERT_TO_SUBCLASS_CODE // the conversions have to be static, because they're using multiple inheritance // (seen in PyQt4 .sip files for some QGraphicsItem classes) - switch ( sipCpp->type() ) + if ( dynamic_cast< QgsComposerItem * >( sipCpp ) ) { - case QgsComposerItem::ComposerItem: - sipType = sipType_QgsComposerItem; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerArrow: - sipType = sipType_QgsComposerArrow; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerItemGroup: - sipType = sipType_QgsComposerItemGroup; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerLabel: - sipType = sipType_QgsComposerLabel; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerLegend: - sipType = sipType_QgsComposerLegend; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerMap: - sipType = sipType_QgsComposerMap; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPaper: - sipType = sipType_QgsPaperItem; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPicture: - sipType = sipType_QgsComposerPicture; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerScaleBar: - sipType = sipType_QgsComposerScaleBar; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerShape: - sipType = sipType_QgsComposerShape; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPolygon: - sipType = sipType_QgsComposerPolygon; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerPolyline: - sipType = sipType_QgsComposerPolyline; - *sipCppRet = static_cast( sipCpp ); - break; - case QgsComposerItem::ComposerFrame: - sipType = sipType_QgsComposerFrame; - *sipCppRet = static_cast( sipCpp ); - break; - default: - sipType = 0; + switch ( sipCpp->type() ) + { + case QgsComposerItem::ComposerItem: + sipType = sipType_QgsComposerItem; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerArrow: + sipType = sipType_QgsComposerArrow; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerItemGroup: + sipType = sipType_QgsComposerItemGroup; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerLabel: + sipType = sipType_QgsComposerLabel; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerLegend: + sipType = sipType_QgsComposerLegend; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerMap: + sipType = sipType_QgsComposerMap; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPaper: + sipType = sipType_QgsPaperItem; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPicture: + sipType = sipType_QgsComposerPicture; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerScaleBar: + sipType = sipType_QgsComposerScaleBar; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerShape: + sipType = sipType_QgsComposerShape; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPolygon: + sipType = sipType_QgsComposerPolygon; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerPolyline: + sipType = sipType_QgsComposerPolyline; + *sipCppRet = static_cast( sipCpp ); + break; + case QgsComposerItem::ComposerFrame: + sipType = sipType_QgsComposerFrame; + *sipCppRet = static_cast( sipCpp ); + break; + default: + sipType = 0; + } } + else + { + switch ( sipCpp->type() ) + { + // really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that! + case QGraphicsItem::UserType + 101: + sipType = sipType_QgsLayoutItemPage; + *sipCppRet = static_cast( sipCpp ); + break; + default: + sipType = 0; + } + } + SIP_END #endif From e444f00cc5633c4e0616ff099960836565cb938e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 10:54:44 +1000 Subject: [PATCH 194/266] Fix calculation of layout bounds considers page shadow --- src/core/layout/qgslayout.cpp | 16 +++++++++++++--- tests/src/core/testqgslayout.cpp | 8 ++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index 3eaf21967a3..99a3b3025c6 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -138,10 +138,20 @@ QRectF QgsLayout::layoutBounds( bool ignorePages, double margin ) const if ( !isPage || !ignorePages ) { //expand bounds with current item's bounds - if ( bounds.isValid() ) - bounds = bounds.united( item->sceneBoundingRect() ); + QRectF itemBounds; + if ( isPage ) + { + // for pages we only consider the item's rect - not the bounding rect + // as the bounding rect contains extra padding + itemBounds = layoutItem->mapToScene( layoutItem->rect() ).boundingRect(); + } else - bounds = item->sceneBoundingRect(); + itemBounds = item->sceneBoundingRect(); + + if ( bounds.isValid() ) + bounds = bounds.united( itemBounds ); + else + bounds = itemBounds; } } diff --git a/tests/src/core/testqgslayout.cpp b/tests/src/core/testqgslayout.cpp index 545053011c4..6684d6fccc7 100644 --- a/tests/src/core/testqgslayout.cpp +++ b/tests/src/core/testqgslayout.cpp @@ -292,10 +292,10 @@ void TestQgsLayout::bounds() QGSCOMPARENEAR( compositionBoundsNoPage.top(), 49.79, 0.01 ); #endif - QGSCOMPARENEAR( layoutBounds.height(), 211.000000, 0.01 ); - QGSCOMPARENEAR( layoutBounds.width(), 298.000000, 0.01 ); - QGSCOMPARENEAR( layoutBounds.left(), -0.500000, 0.01 ); - QGSCOMPARENEAR( layoutBounds.top(), -0.500000, 0.01 ); + QGSCOMPARENEAR( layoutBounds.height(), 210.000000, 0.01 ); + QGSCOMPARENEAR( layoutBounds.width(), 297.000000, 0.01 ); + QGSCOMPARENEAR( layoutBounds.left(), 0.00000, 0.01 ); + QGSCOMPARENEAR( layoutBounds.top(), 0.00000, 0.01 ); QRectF compositionBoundsNoPage = l.layoutBounds( true ); QGSCOMPARENEAR( compositionBoundsNoPage.height(), 175.704581, 0.01 ); From 0bffff05b35564a887cc3bdf113f7ac36ddeacd5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 10:54:50 +1000 Subject: [PATCH 195/266] Astyle --- tests/src/python/test_qgslayoutpagecollection.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index 4a4188598aa..21f5311de91 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -203,16 +203,16 @@ class TestQgsLayoutPageCollection(unittest.TestCase): l = QgsLayout(p) collection = l.pageCollection() - #add a page + # add a page page = QgsLayoutItemPage(l) page.setPageSize('A4') collection.addPage(page) - #should be positioned at origin + # should be positioned at origin self.assertEqual(page.pos().x(), 0) self.assertEqual(page.pos().y(), 0) - #second page + # second page page2 = QgsLayoutItemPage(l) page2.setPageSize('A5') collection.addPage(page2) @@ -222,7 +222,7 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertEqual(page2.pos().x(), 0) self.assertEqual(page2.pos().y(), 307) - #third page, slotted in middle + # third page, slotted in middle page3 = QgsLayoutItemPage(l) page3.setPageSize('A3') collection.insertPage(page3, 1) @@ -373,5 +373,6 @@ class TestQgsLayoutPageCollection(unittest.TestCase): self.assertEqual(collection.pageAtPoint(QPointF(10, 500)), page2) self.assertFalse(collection.pageAtPoint(QPointF(10, 600))) + if __name__ == '__main__': unittest.main() From 213064a8af1b695c2f362b8af51c77bd079f522f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 11:21:10 +1000 Subject: [PATCH 196/266] Fix layout context flags not being respected when rendering items --- python/core/layout/qgslayoutcontext.sip | 6 ++++ python/core/layout/qgslayoututils.sip | 6 ++-- src/core/layout/qgslayoutcontext.cpp | 13 ++++++++ src/core/layout/qgslayoutcontext.h | 5 +++ src/core/layout/qgslayoutitem.cpp | 5 +-- src/core/layout/qgslayoututils.cpp | 9 +++-- src/core/layout/qgslayoututils.h | 6 ++-- tests/src/core/testqgslayoutcontext.cpp | 23 +++++++++++++ tests/src/core/testqgslayoututils.cpp | 44 +++++++++++++++++++++++-- 9 files changed, 106 insertions(+), 11 deletions(-) diff --git a/python/core/layout/qgslayoutcontext.sip b/python/core/layout/qgslayoutcontext.sip index 7f9ccd3d7a7..a1e8ea16cda 100644 --- a/python/core/layout/qgslayoutcontext.sip +++ b/python/core/layout/qgslayoutcontext.sip @@ -67,6 +67,12 @@ class QgsLayoutContext :rtype: bool %End + QgsRenderContext::Flags renderContextFlags() const; +%Docstring + Returns the combination of render context flags matched to the layout context's settings. + :rtype: QgsRenderContext.Flags +%End + void setFeature( const QgsFeature &feature ); %Docstring Sets the current ``feature`` for evaluating the layout. This feature may diff --git a/python/core/layout/qgslayoututils.sip b/python/core/layout/qgslayoututils.sip index edb168fe482..da1a50caa6f 100644 --- a/python/core/layout/qgslayoututils.sip +++ b/python/core/layout/qgslayoututils.sip @@ -33,17 +33,19 @@ class QgsLayoutUtils %Docstring Creates a render context suitable for the specified layout ``map`` and ``painter`` destination. This method returns a new QgsRenderContext which matches the scale and settings of the - target map. If the ``dpi`` argument is not specified then the dpi will be taken from the destinatation + target map. If the ``dpi`` argument is not specified then the dpi will be taken from the destination painter device. .. seealso:: createRenderContextForLayout() :rtype: QgsRenderContext %End - static QgsRenderContext createRenderContextForLayout( QgsLayout *layout, QPainter *painter ); + static QgsRenderContext createRenderContextForLayout( QgsLayout *layout, QPainter *painter, double dpi = -1 ); %Docstring Creates a render context suitable for the specified ``layout`` and ``painter`` destination. This method returns a new QgsRenderContext which matches the scale and settings from the layout's QgsLayout.referenceMap(). + If the ``dpi`` argument is not specified then the dpi will be taken from the destination + painter device. .. seealso:: createRenderContextForMap() :rtype: QgsRenderContext %End diff --git a/src/core/layout/qgslayoutcontext.cpp b/src/core/layout/qgslayoutcontext.cpp index 43782b0b47e..183955bd60e 100644 --- a/src/core/layout/qgslayoutcontext.cpp +++ b/src/core/layout/qgslayoutcontext.cpp @@ -45,6 +45,19 @@ bool QgsLayoutContext::testFlag( const QgsLayoutContext::Flag flag ) const return mFlags.testFlag( flag ); } +QgsRenderContext::Flags QgsLayoutContext::renderContextFlags() const +{ + QgsRenderContext::Flags flags = 0; + if ( mFlags & FlagAntialiasing ) + flags = flags | QgsRenderContext::Antialiasing; + if ( mFlags & FlagUseAdvancedEffects ) + flags = flags | QgsRenderContext::UseAdvancedEffects; + + // TODO - expose as layout context flag? + flags |= QgsRenderContext::ForceVectorOutput; + return flags; +} + QgsVectorLayer *QgsLayoutContext::layer() const { return mLayer; diff --git a/src/core/layout/qgslayoutcontext.h b/src/core/layout/qgslayoutcontext.h index 5e9253a8182..4aa5f43eaa5 100644 --- a/src/core/layout/qgslayoutcontext.h +++ b/src/core/layout/qgslayoutcontext.h @@ -81,6 +81,11 @@ class CORE_EXPORT QgsLayoutContext */ bool testFlag( const Flag flag ) const; + /** + * Returns the combination of render context flags matched to the layout context's settings. + */ + QgsRenderContext::Flags renderContextFlags() const; + /** * Sets the current \a feature for evaluating the layout. This feature may * be used for altering an item's content and appearance for a report diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index 0b308a8d6a8..a81e0a86396 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -128,7 +128,7 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it QPainter p( &mItemCachedImage ); preparePainter( &p ); - QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, &p, destinationDpi ); + QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( nullptr, &p, destinationDpi ); // painter is already scaled to dots // need to translate so that item origin is at 0,0 in painter coordinates (not bounding rect origin) p.translate( -boundingRect().x() * context.scaleFactor(), -boundingRect().y() * context.scaleFactor() ); @@ -147,7 +147,8 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it { // no caching or flattening painter->save(); - QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, painter, destinationDpi ); + preparePainter( painter ); + QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, painter, destinationDpi ); // scale painter from mm to dots painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() ); draw( context, itemStyle ); diff --git a/src/core/layout/qgslayoututils.cpp b/src/core/layout/qgslayoututils.cpp index 702fafefd67..387ba16cd9a 100644 --- a/src/core/layout/qgslayoututils.cpp +++ b/src/core/layout/qgslayoututils.cpp @@ -74,12 +74,17 @@ QgsRenderContext QgsLayoutUtils::createRenderContextForMap( QgsLayoutItemMap *ma QgsRenderContext context; // = QgsRenderContext::fromMapSettings( ms ); if ( painter ) context.setPainter( painter ); + + context.setFlags( map->layout()->context().renderContextFlags() ); return context; } } -QgsRenderContext QgsLayoutUtils::createRenderContextForLayout( QgsLayout *layout, QPainter *painter ) +QgsRenderContext QgsLayoutUtils::createRenderContextForLayout( QgsLayout *layout, QPainter *painter, double dpi ) { QgsLayoutItemMap *referenceMap = layout ? layout->referenceMap() : nullptr; - return createRenderContextForMap( referenceMap, painter ); + QgsRenderContext context = createRenderContextForMap( referenceMap, painter, dpi ); + if ( layout ) + context.setFlags( layout->context().renderContextFlags() ); + return context; } diff --git a/src/core/layout/qgslayoututils.h b/src/core/layout/qgslayoututils.h index 4141d5e4b9a..475cf1ee78d 100644 --- a/src/core/layout/qgslayoututils.h +++ b/src/core/layout/qgslayoututils.h @@ -43,7 +43,7 @@ class CORE_EXPORT QgsLayoutUtils /** * Creates a render context suitable for the specified layout \a map and \a painter destination. * This method returns a new QgsRenderContext which matches the scale and settings of the - * target map. If the \a dpi argument is not specified then the dpi will be taken from the destinatation + * target map. If the \a dpi argument is not specified then the dpi will be taken from the destination * painter device. * \see createRenderContextForLayout() */ @@ -53,9 +53,11 @@ class CORE_EXPORT QgsLayoutUtils * Creates a render context suitable for the specified \a layout and \a painter destination. * This method returns a new QgsRenderContext which matches the scale and settings from the layout's * QgsLayout::referenceMap(). + * If the \a dpi argument is not specified then the dpi will be taken from the destination + * painter device. * \see createRenderContextForMap() */ - static QgsRenderContext createRenderContextForLayout( QgsLayout *layout, QPainter *painter ); + static QgsRenderContext createRenderContextForLayout( QgsLayout *layout, QPainter *painter, double dpi = -1 ); }; diff --git a/tests/src/core/testqgslayoutcontext.cpp b/tests/src/core/testqgslayoutcontext.cpp index 4b97681e106..d3ca359160b 100644 --- a/tests/src/core/testqgslayoutcontext.cpp +++ b/tests/src/core/testqgslayoutcontext.cpp @@ -36,6 +36,7 @@ class TestQgsLayoutContext: public QObject void feature(); void layer(); void dpi(); + void renderContextFlags(); private: QString mReport; @@ -135,5 +136,27 @@ void TestQgsLayoutContext::dpi() QCOMPARE( context.measurementConverter().dpi(), 600.0 ); } +void TestQgsLayoutContext::renderContextFlags() +{ + QgsLayoutContext context; + context.setFlags( 0 ); + QgsRenderContext::Flags flags = context.renderContextFlags(); + QVERIFY( !( flags & QgsRenderContext::Antialiasing ) ); + QVERIFY( !( flags & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( flags & QgsRenderContext::ForceVectorOutput ) ); + + context.setFlag( QgsLayoutContext::FlagAntialiasing ); + flags = context.renderContextFlags(); + QVERIFY( ( flags & QgsRenderContext::Antialiasing ) ); + QVERIFY( !( flags & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( flags & QgsRenderContext::ForceVectorOutput ) ); + + context.setFlag( QgsLayoutContext::FlagUseAdvancedEffects ); + flags = context.renderContextFlags(); + QVERIFY( ( flags & QgsRenderContext::Antialiasing ) ); + QVERIFY( ( flags & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( flags & QgsRenderContext::ForceVectorOutput ) ); +} + QGSTEST_MAIN( TestQgsLayoutContext ) #include "testqgslayoutcontext.moc" diff --git a/tests/src/core/testqgslayoututils.cpp b/tests/src/core/testqgslayoututils.cpp index e509c5d201e..0ed5745b8a3 100644 --- a/tests/src/core/testqgslayoututils.cpp +++ b/tests/src/core/testqgslayoututils.cpp @@ -124,17 +124,17 @@ void TestQgsLayoutUtils::createRenderContextFromLayout() testImage.setDotsPerMeterY( 150 / 25.4 * 1000 ); QPainter p( &testImage ); - // no composition + // no layout QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( nullptr, &p ); QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); QCOMPARE( rc.painter(), &p ); - // no composition, no painter + // no layout, no painter rc = QgsLayoutUtils::createRenderContextForLayout( nullptr, nullptr ); QGSCOMPARENEAR( rc.scaleFactor(), 88 / 25.4, 0.001 ); QVERIFY( !rc.painter() ); - //create composition with no reference map + //create layout with no reference map QgsRectangle extent( 2000, 2800, 2500, 2900 ); QgsProject project; QgsLayout l( &project ); @@ -167,6 +167,25 @@ void TestQgsLayoutUtils::createRenderContextFromLayout() QGSCOMPARENEAR( rc.rendererScale(), map->scale(), 1000000 ); QVERIFY( !rc.painter() ); + // check render context flags are correctly set + l.context().setFlags( 0 ); + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QVERIFY( !( rc.flags() & QgsRenderContext::Antialiasing ) ); + QVERIFY( !( rc.flags() & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::ForceVectorOutput ) ); + + l.context().setFlag( QgsLayoutContext::FlagAntialiasing ); + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QVERIFY( ( rc.flags() & QgsRenderContext::Antialiasing ) ); + QVERIFY( !( rc.flags() & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::ForceVectorOutput ) ); + + l.context().setFlag( QgsLayoutContext::FlagUseAdvancedEffects ); + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QVERIFY( ( rc.flags() & QgsRenderContext::Antialiasing ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::ForceVectorOutput ) ); + p.end(); } @@ -224,6 +243,25 @@ void TestQgsLayoutUtils::createRenderContextFromMap() QGSCOMPARENEAR( rc.scaleFactor(), 150 / 25.4, 0.001 ); QGSCOMPARENEAR( rc.rendererScale(), map2->scale(), 1000000 ); QVERIFY( rc.painter() ); + + // check render context flags are correctly set + l.context().setFlags( 0 ); + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QVERIFY( !( rc.flags() & QgsRenderContext::Antialiasing ) ); + QVERIFY( !( rc.flags() & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::ForceVectorOutput ) ); + + l.context().setFlag( QgsLayoutContext::FlagAntialiasing ); + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QVERIFY( ( rc.flags() & QgsRenderContext::Antialiasing ) ); + QVERIFY( !( rc.flags() & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::ForceVectorOutput ) ); + + l.context().setFlag( QgsLayoutContext::FlagUseAdvancedEffects ); + rc = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr ); + QVERIFY( ( rc.flags() & QgsRenderContext::Antialiasing ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::UseAdvancedEffects ) ); + QVERIFY( ( rc.flags() & QgsRenderContext::ForceVectorOutput ) ); #endif p.end(); } From 30a83e1569f90d4413a251628edaddf544161edb Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 12:09:47 +1000 Subject: [PATCH 197/266] Add some missing overrides --- src/core/layout/qgslayoutitem.h | 2 +- src/core/layout/qgslayoutitemregistry.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 002660b63cb..0c8edb7fc7a 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -85,7 +85,7 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt * Return correct graphics item type * \see stringType() */ - virtual int type() const = 0; + virtual int type() const override = 0; /** * Return the item type as a string. diff --git a/src/core/layout/qgslayoutitemregistry.h b/src/core/layout/qgslayoutitemregistry.h index 18cbc842870..788c837159c 100644 --- a/src/core/layout/qgslayoutitemregistry.h +++ b/src/core/layout/qgslayoutitemregistry.h @@ -271,9 +271,9 @@ class TestLayoutItem : public QgsLayoutItem ~TestLayoutItem() {} //implement pure virtual methods - int type() const { return QgsLayoutItemRegistry::LayoutItem + 102; } + int type() const override { return QgsLayoutItemRegistry::LayoutItem + 102; } QString stringType() const override { return QStringLiteral( "ItemTest" ); } - void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ); + void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; private: QColor mColor; From 9e2673a1780b5932514dcf0ba1a3b5dcd5f65f2b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 13:25:04 +1000 Subject: [PATCH 198/266] Add some pixel sized wallpapers to the standard page sizes --- src/core/layout/qgspagesizeregistry.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/layout/qgspagesizeregistry.cpp b/src/core/layout/qgspagesizeregistry.cpp index e2824b615dc..d4fd76bf7d2 100644 --- a/src/core/layout/qgspagesizeregistry.cpp +++ b/src/core/layout/qgspagesizeregistry.cpp @@ -52,6 +52,9 @@ QgsPageSizeRegistry::QgsPageSizeRegistry() add( QgsPageSize( QStringLiteral( "Arch E1" ), QgsLayoutSize( 762, 1066.8 ), QObject::tr( "Arch E1" ) ) ); add( QgsPageSize( QStringLiteral( "Arch E2" ), QgsLayoutSize( 660, 965 ), QObject::tr( "Arch E2" ) ) ); add( QgsPageSize( QStringLiteral( "Arch E3" ), QgsLayoutSize( 686, 991 ), QObject::tr( "Arch E3" ) ) ); + add( QgsPageSize( QStringLiteral( "1920x1080" ), QgsLayoutSize( 1080, 1920, QgsUnitTypes::LayoutPixels ), QObject::tr( "1920×1080" ) ) ); + add( QgsPageSize( QStringLiteral( "1280x800" ), QgsLayoutSize( 800, 1280, QgsUnitTypes::LayoutPixels ), QObject::tr( "1280×800" ) ) ); + add( QgsPageSize( QStringLiteral( "1024x768" ), QgsLayoutSize( 768, 1024, QgsUnitTypes::LayoutPixels ), QObject::tr( "1024×768" ) ) ); } void QgsPageSizeRegistry::add( const QgsPageSize &size ) From e8514be010a6dc91a19203081145452641d12a7c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 13:57:57 +1000 Subject: [PATCH 199/266] Make matching to known page sizes less fussy Since all gui widgets for page sizes limit to 2 decimal places, we need to allow this much tolerance when checking the setting against known page sizes. --- src/core/layout/qgspagesizeregistry.cpp | 4 ++-- tests/src/core/testqgspagesizeregistry.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/layout/qgspagesizeregistry.cpp b/src/core/layout/qgspagesizeregistry.cpp index d4fd76bf7d2..745a62e1e69 100644 --- a/src/core/layout/qgspagesizeregistry.cpp +++ b/src/core/layout/qgspagesizeregistry.cpp @@ -97,8 +97,8 @@ QString QgsPageSizeRegistry::find( const QgsLayoutSize &size ) const QgsLayoutSize xSize = converter.convert( size, pageSize.size.units() ); //consider width and height values may be exchanged - if ( ( qgsDoubleNear( xSize.width(), pageSize.size.width() ) && qgsDoubleNear( xSize.height(), pageSize.size.height() ) ) - || ( qgsDoubleNear( xSize.height(), pageSize.size.width() ) && qgsDoubleNear( xSize.width(), pageSize.size.height() ) ) ) + if ( ( qgsDoubleNear( xSize.width(), pageSize.size.width(), 0.01 ) && qgsDoubleNear( xSize.height(), pageSize.size.height(), 0.01 ) ) + || ( qgsDoubleNear( xSize.height(), pageSize.size.width(), 0.01 ) && qgsDoubleNear( xSize.width(), pageSize.size.height(), 0.01 ) ) ) { return pageSize.name; } diff --git a/tests/src/core/testqgspagesizeregistry.cpp b/tests/src/core/testqgspagesizeregistry.cpp index 2d127f65d45..046d57d5f17 100644 --- a/tests/src/core/testqgspagesizeregistry.cpp +++ b/tests/src/core/testqgspagesizeregistry.cpp @@ -131,7 +131,8 @@ void TestQgsPageSizeRegistry::findBySize() QCOMPARE( registry->find( QgsLayoutSize( 297, 210 ) ), QStringLiteral( "A4" ) ); QCOMPARE( registry->find( QgsLayoutSize( 125, 176 ) ), QStringLiteral( "B6" ) ); QCOMPARE( registry->find( QgsLayoutSize( 21, 29.7, QgsUnitTypes::LayoutCentimeters ) ), QStringLiteral( "A4" ) ); - + // must have allowance of 0.01 units - because we round to this precision in all page size widgets + QCOMPARE( registry->find( QgsLayoutSize( 125.009, 175.991 ) ), QStringLiteral( "B6" ) ); } void TestQgsPageSizeRegistry::decodePageSize() From c89a15a24781e1c966c035818430e5f303193d74 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 25 Jul 2017 13:58:37 +1000 Subject: [PATCH 200/266] Don't disable page height/width controls when set to a known page size Instead leave them enabled, and flick across to custom page size if they're changed. --- src/app/layout/qgslayoutaddpagesdialog.cpp | 17 +++++++++++++---- src/app/layout/qgslayoutaddpagesdialog.h | 3 +++ .../layout/qgslayoutpagepropertieswidget.cpp | 18 +++++++++++++----- src/app/layout/qgslayoutpagepropertieswidget.h | 3 +++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/app/layout/qgslayoutaddpagesdialog.cpp b/src/app/layout/qgslayoutaddpagesdialog.cpp index ef8962710fe..1617b48af9a 100644 --- a/src/app/layout/qgslayoutaddpagesdialog.cpp +++ b/src/app/layout/qgslayoutaddpagesdialog.cpp @@ -48,6 +48,9 @@ QgsLayoutAddPagesDialog::QgsLayoutAddPagesDialog( QWidget *parent, Qt::WindowFla connect( mPageSizeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutAddPagesDialog::pageSizeChanged ); connect( mPageOrientationComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsLayoutAddPagesDialog::orientationChanged ); + + connect( mWidthSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutAddPagesDialog::setToCustomSize ); + connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutAddPagesDialog::setToCustomSize ); } void QgsLayoutAddPagesDialog::setLayout( QgsLayout *layout ) @@ -88,22 +91,19 @@ void QgsLayoutAddPagesDialog::pageSizeChanged( int ) if ( mPageSizeComboBox->currentData().toString().isEmpty() ) { //custom size - mWidthSpin->setEnabled( true ); - mHeightSpin->setEnabled( true ); mLockAspectRatio->setEnabled( true ); mSizeUnitsComboBox->setEnabled( true ); mPageOrientationComboBox->setEnabled( false ); } else { - mWidthSpin->setEnabled( false ); - mHeightSpin->setEnabled( false ); mLockAspectRatio->setEnabled( false ); mLockAspectRatio->setLocked( false ); mSizeUnitsComboBox->setEnabled( false ); mPageOrientationComboBox->setEnabled( true ); QgsPageSize size = QgsApplication::pageSizeRegistry()->find( mPageSizeComboBox->currentData().toString() ).value( 0 ); QgsLayoutSize convertedSize = mConverter.convert( size.size, mSizeUnitsComboBox->unit() ); + mSettingPresetSize = true; switch ( mPageOrientationComboBox->currentData().toInt() ) { case QgsLayoutItemPage::Landscape: @@ -116,6 +116,7 @@ void QgsLayoutAddPagesDialog::pageSizeChanged( int ) mHeightSpin->setValue( convertedSize.height() ); break; } + mSettingPresetSize = false; } } @@ -145,3 +146,11 @@ void QgsLayoutAddPagesDialog::orientationChanged( int ) break; } } + +void QgsLayoutAddPagesDialog::setToCustomSize() +{ + if ( mSettingPresetSize ) + return; + whileBlocking( mPageSizeComboBox )->setCurrentIndex( mPageSizeComboBox->count() - 1 ); + mPageOrientationComboBox->setEnabled( false ); +} diff --git a/src/app/layout/qgslayoutaddpagesdialog.h b/src/app/layout/qgslayoutaddpagesdialog.h index c3997e1094d..b8b0aef2fe4 100644 --- a/src/app/layout/qgslayoutaddpagesdialog.h +++ b/src/app/layout/qgslayoutaddpagesdialog.h @@ -78,9 +78,12 @@ class QgsLayoutAddPagesDialog : public QDialog, private Ui::QgsLayoutNewPageDial void positionChanged( int index ); void pageSizeChanged( int index ); void orientationChanged( int index ); + void setToCustomSize(); private: + bool mSettingPresetSize = false; + QgsLayoutMeasurementConverter mConverter; }; diff --git a/src/app/layout/qgslayoutpagepropertieswidget.cpp b/src/app/layout/qgslayoutpagepropertieswidget.cpp index fd42de92860..9e2216d4274 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.cpp +++ b/src/app/layout/qgslayoutpagepropertieswidget.cpp @@ -33,7 +33,6 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q mPageSizeComboBox->addItem( size.displayName, size.name ); } mPageSizeComboBox->addItem( tr( "Custom" ) ); - showCurrentPageSize(); mWidthSpin->setValue( mPage->pageSize().width() ); mHeightSpin->setValue( mPage->pageSize().height() ); @@ -53,7 +52,10 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q connect( mWidthSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::updatePageSize ); connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::updatePageSize ); + connect( mWidthSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::setToCustomSize ); + connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::setToCustomSize ); + showCurrentPageSize(); } void QgsLayoutPagePropertiesWidget::pageSizeChanged( int ) @@ -61,22 +63,19 @@ void QgsLayoutPagePropertiesWidget::pageSizeChanged( int ) if ( mPageSizeComboBox->currentData().toString().isEmpty() ) { //custom size - mWidthSpin->setEnabled( true ); - mHeightSpin->setEnabled( true ); mLockAspectRatio->setEnabled( true ); mSizeUnitsComboBox->setEnabled( true ); mPageOrientationComboBox->setEnabled( false ); } else { - mWidthSpin->setEnabled( false ); - mHeightSpin->setEnabled( false ); mLockAspectRatio->setEnabled( false ); mLockAspectRatio->setLocked( false ); mSizeUnitsComboBox->setEnabled( false ); mPageOrientationComboBox->setEnabled( true ); QgsPageSize size = QgsApplication::pageSizeRegistry()->find( mPageSizeComboBox->currentData().toString() ).value( 0 ); QgsLayoutSize convertedSize = mConverter.convert( size.size, mSizeUnitsComboBox->unit() ); + mSettingPresetSize = true; switch ( mPageOrientationComboBox->currentData().toInt() ) { case QgsLayoutItemPage::Landscape: @@ -89,6 +88,7 @@ void QgsLayoutPagePropertiesWidget::pageSizeChanged( int ) mHeightSpin->setValue( convertedSize.height() ); break; } + mSettingPresetSize = false; } updatePageSize(); } @@ -128,6 +128,14 @@ void QgsLayoutPagePropertiesWidget::updatePageSize() mPage->layout()->pageCollection()->reflow(); } +void QgsLayoutPagePropertiesWidget::setToCustomSize() +{ + if ( mSettingPresetSize ) + return; + whileBlocking( mPageSizeComboBox )->setCurrentIndex( mPageSizeComboBox->count() - 1 ); + mPageOrientationComboBox->setEnabled( false ); +} + void QgsLayoutPagePropertiesWidget::showCurrentPageSize() { QgsLayoutSize paperSize = mPage->pageSize(); diff --git a/src/app/layout/qgslayoutpagepropertieswidget.h b/src/app/layout/qgslayoutpagepropertieswidget.h index 79185149324..aec8306a5e2 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.h +++ b/src/app/layout/qgslayoutpagepropertieswidget.h @@ -46,6 +46,7 @@ class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui void pageSizeChanged( int index ); void orientationChanged( int index ); void updatePageSize(); + void setToCustomSize(); private: @@ -53,6 +54,8 @@ class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui QgsLayoutMeasurementConverter mConverter; + bool mSettingPresetSize = false; + void showCurrentPageSize(); }; From fc0bd62ece14b37061e8c88dac12639d5f2e9f2f Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Tue, 25 Jul 2017 07:05:52 +0200 Subject: [PATCH 201/266] typo fix --- src/core/qgsuserprofile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/qgsuserprofile.cpp b/src/core/qgsuserprofile.cpp index dd5dc4fe90f..979e68bc57d 100644 --- a/src/core/qgsuserprofile.cpp +++ b/src/core/qgsuserprofile.cpp @@ -92,7 +92,7 @@ QgsError QgsUserProfile::setAlias( const QString &alias ) if ( !qgisPrivateDbFile.exists() ) { - error.append( QObject::tr( "qgis.db doesn't exist in the uers profile folder" ) ); + error.append( QObject::tr( "qgis.db doesn't exist in the user's profile folder" ) ); return error; } From 4d6b980668765852e23d7ecd8d25f6331c29c2f1 Mon Sep 17 00:00:00 2001 From: Alexandre Neto Date: Fri, 2 Jun 2017 10:49:33 +0100 Subject: [PATCH 202/266] Fixes Table Background Color Dialog behavior --- .../qgscomposertablebackgroundstyles.ui | 515 +++++++++--------- 1 file changed, 257 insertions(+), 258 deletions(-) diff --git a/src/ui/composer/qgscomposertablebackgroundstyles.ui b/src/ui/composer/qgscomposertablebackgroundstyles.ui index 01fad0d132f..ede307f0e0b 100644 --- a/src/ui/composer/qgscomposertablebackgroundstyles.ui +++ b/src/ui/composer/qgscomposertablebackgroundstyles.ui @@ -6,15 +6,35 @@ 0 0 - 393 - 444 + 356 + 501 + + + 0 + 0 + + Table Background Colors - + + + + First row + + + + + + + Header row + + + + @@ -39,84 +59,7 @@ - - - - Odd rows - - - - - - - Header row - - - - - - - <html><head/><body><p>Check options to enable shading for matching cells. Options lower in this list will take precedence over higher options. For example, if both &quot;<span style=" font-style:italic;">First row</span>&quot; and &quot;<span style=" font-style:italic;">Odd rows</span>&quot; are checked, the cells in the first row will be shaded using the color specified for &quot;<span style=" font-style:italic;">First row</span>&quot;.</p></body></html> - - - true - - - - - - - Last column - - - - - - - Odd columns - - - - - - - Even rows - - - - - - - Even columns - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - - - - - + @@ -141,7 +84,166 @@ + + + + Even columns + + + + + + + First column + + + + + + + Even rows + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + + + + + + + + Odd columns + + + + + + + Last row + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + + + + + + + + Last column + + + + + + + Default cell background + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + @@ -167,182 +269,6 @@ - - - - 0 - 0 - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - - - - - - - - First row - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - First column - - - - - - - Last row - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - - - - - - - - Default cell background - - - - @@ -367,12 +293,85 @@ + + + + Odd rows + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Check options to enable shading for matching cells. Options lower in this list will take precedence over higher options. For example, if both &quot;<span style=" font-style:italic;">First row</span>&quot; and &quot;<span style=" font-style:italic;">Odd rows</span>&quot; are checked, the cells in the first row will be shaded using the color specified for &quot;<span style=" font-style:italic;">First row</span>&quot;.</p></body></html> + + + true + + + QgsColorButton - QToolButton + QPushButton
qgscolorbutton.h
1
From f8224ed6a47186e01b8b59ff6734adc02da45b19 Mon Sep 17 00:00:00 2001 From: Alexandre Neto Date: Wed, 21 Jun 2017 11:26:44 +0100 Subject: [PATCH 203/266] Restores QToolButton --- src/ui/composer/qgscomposertablebackgroundstyles.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/composer/qgscomposertablebackgroundstyles.ui b/src/ui/composer/qgscomposertablebackgroundstyles.ui index ede307f0e0b..d98ab512f4f 100644 --- a/src/ui/composer/qgscomposertablebackgroundstyles.ui +++ b/src/ui/composer/qgscomposertablebackgroundstyles.ui @@ -371,7 +371,7 @@ QgsColorButton - QPushButton + QToolButton
qgscolorbutton.h
1
From 4de141e73ff613f8a29097ac39c8b3a1d71916cc Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 25 Jul 2017 10:09:42 +0200 Subject: [PATCH 204/266] Minor fix in comment text --- src/app/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index c090a0655af..d78ff9de84d 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -787,7 +787,7 @@ int main( int argc, char *argv[] ) // SetUp the QgsSettings Global Settings: - // - use the path specified with --globalsettings path, + // - use the path specified with --globalsettingsfile path, // - use the environment if not found // - use a default location as a fallback if ( globalsettingsfile.isEmpty() ) From c5371b64cb27766a7763a2d5369d2ed8631d5749 Mon Sep 17 00:00:00 2001 From: volaya Date: Fri, 23 Jun 2017 13:36:45 +0200 Subject: [PATCH 205/266] added new SAGA descriptions (cherry picked from commit a3bb3f13902638ee38f85c25c57d68b34b33bfbc) --- .../processing/algs/saga/SagaNameDecorator.py | 9 +++ .../algs/saga/description/ANGMAP.txt | 10 +++ .../algs/saga/description/AccumulatedCost.txt | 12 ++++ .../description/AccumulationFunctions.txt | 12 ++++ .../AddIndicatorFieldsforCategories.txt | 6 ++ .../description/AngularDistanceWeighted.txt | 23 +++++++ ...ialNeuralNetworkClassification(OpenCV).txt | 22 ++++++ .../saga/description/Aspect-SlopeGrid.txt | 6 ++ .../AutomatedCloudCoverAssessment.txt | 14 ++++ .../saga/description/AverageWithMask1.txt | 8 +++ .../saga/description/AverageWithMask2.txt | 8 +++ .../description/AverageWithThereshold1.txt | 7 ++ .../description/AverageWithThereshold2.txt | 7 ++ .../description/AverageWithThereshold3.txt | 7 ++ .../saga/description/BasicTerrainAnalysis.txt | 20 ++++++ .../BinaryErosion-Reconstruction.txt | 5 ++ .../saga/description/BioclimaticVariables.txt | 26 +++++++ .../BoostingClassification(OpenCV).txt | 16 +++++ .../description/CategoricalCoincidence.txt | 8 +++ .../saga/description/ChangeDataStorage.txt | 7 ++ .../CloseGapswithStepwiseResampling.txt | 10 +++ .../description/ClusterAnalysis(Shapes).txt | 10 +++ .../algs/saga/description/ClusterAnalysis.txt | 10 +++ .../algs/saga/description/Concentration.txt | 10 +++ .../ConfusionMatrix(PolygonsGrid).txt | 12 ++++ .../description/ConfusionMatrix(TwoGrids).txt | 18 +++++ .../saga/description/ConnectivityAnalysis.txt | 10 +++ .../algs/saga/description/ConstantGrid.txt | 14 ++++ .../algs/saga/description/CoveredDistance.txt | 4 ++ .../algs/saga/description/CreatePointGrid.txt | 8 +++ .../DecisionTreeClassification(OpenCV).txt | 13 ++++ .../algs/saga/description/Destriping.txt | 9 +++ .../saga/description/DestripingwithMask.txt | 14 ++++ .../algs/saga/description/Difference.txt | 6 ++ .../saga/description/DiffusePollutionRisk.txt | 13 ++++ .../saga/description/DirectionalAverage.txt | 7 ++ .../algs/saga/description/Distance(ViGrA).txt | 5 ++ .../description/EarthsOrbitalParameters.txt | 6 ++ .../saga/description/EdgeDetection(ViGrA).txt | 7 ++ .../description/EnhancedVegetationIndex.txt | 10 +++ .../saga/description/EnumerateTableField.txt | 5 ++ .../algs/saga/description/FieldStatistics.txt | 5 ++ .../description/FindFieldofExtremeValue.txt | 9 +++ .../saga/description/FourierFilter(ViGrA).txt | 9 +++ .../FourierTransform(RealViGrA).txt | 4 ++ .../description/FourierTransform(ViGrA).txt | 6 ++ .../FourierTransformInverse(ViGrA).txt | 6 ++ .../FourierTransformation(OpenCV).txt | 5 ++ .../algs/saga/description/FunctionFit.txt | 9 +++ .../FuzzyLandformElementClassification.txt | 31 +++++++++ .../description/GWRforMultiplePredictors.txt | 16 +++++ ...forSinglePredictor(GriddedModelOutput).txt | 27 ++++++++ .../description/GWRforSinglePredictorGrid.txt | 20 ++++++ .../algs/saga/description/GenerateShapes.txt | 8 +++ .../GeodesicMorphologicalReconstruction.txt | 9 +++ .../description/GeographicCoordinateGrids.txt | 5 ++ .../algs/saga/description/GridCombination.txt | 12 ++++ .../description/GridStatisticsforPoints.txt | 17 +++++ .../description/IMCORR-FeatureTracking.txt | 11 +++ .../description/ISODATAClusteringforGrids.txt | 10 +++ .../algs/saga/description/Identity.txt | 6 ++ .../algs/saga/description/Intersect.txt | 6 ++ .../algs/saga/description/InvertGrid.txt | 4 ++ .../description/K-MeansClusteringforGrids.txt | 11 +++ ...earestNeighboursClassification(OpenCV).txt | 11 +++ .../saga/description/LS-FactorFieldBased.txt | 16 +++++ .../description/LandUseScenarioGenerator.txt | 8 +++ .../algs/saga/description/LineSmoothing.txt | 9 +++ .../MaximumEntropyPresencePrediction.txt | 18 +++++ .../description/MaximumFlowPathLength.txt | 6 ++ .../description/MeltonRuggednessNumber.txt | 6 ++ .../algs/saga/description/MeshDenoise.txt | 9 +++ .../algs/saga/description/MirrorGrid.txt | 5 ++ .../description/ModifedQuadraticShepard.txt | 12 ++++ .../description/MonthlyGlobalbyLatitude.txt | 7 ++ .../MorphologicalFilter(OpenCV).txt | 8 +++ .../MorphologicalFilter(ViGrA).txt | 8 +++ .../saga/description/MorphometricFeatures.txt | 19 +++++ ...ltipleLinearRegressionAnalysis(Shapes).txt | 12 ++++ .../MultipleLinearRegressionAnalysis.txt | 12 ++++ ...ressionAnalysis(GridandPredictorGrids).txt | 16 +++++ ...ssionAnalysis(PointsandPredictorGrids).txt | 20 ++++++ .../NormalBayesClassification(OpenCV).txt | 8 +++ .../description/PET(afterHargreavesGrid).txt | 10 +++ .../description/PET(afterHargreavesTable).txt | 8 +++ .../algs/saga/description/PointDistances.txt | 9 +++ .../description/PolygonSelf-Intersection.txt | 5 ++ .../RandomForestClassification(OpenCV).txt | 14 ++++ .../RandomForestPresencePrediction(ViGrA).txt | 20 ++++++ ...essionAnalysis(PointsandPredictorGrid).txt | 9 +++ .../saga/description/RegressionKriging.txt | 32 +++++++++ .../saga/description/ResamplingFilter.txt | 6 ++ .../algs/saga/description/RiverBasin.txt | 19 +++++ .../saga/description/RiverGridGeneration.txt | 9 +++ .../saga/description/SVMClassification.txt | 24 +++++++ .../saga/description/SeededRegionGrowing.txt | 15 ++++ .../algs/saga/description/SievingClasses.txt | 8 +++ .../description/SimpleFilterwithinshapes.txt | 8 +++ .../algs/saga/description/SimpleKriging.txt | 25 +++++++ .../SingleValueDecomposition(OpenCV).txt | 6 ++ .../SlopeLimitedFlowAccumulation.txt | 10 +++ .../saga/description/Smoothing(ViGrA).txt | 7 ++ .../saga/description/SnapPointstoGrid.txt | 9 +++ .../saga/description/SnapPointstoLines.txt | 7 ++ .../saga/description/SnapPointstoPoints.txt | 7 ++ .../saga/description/SplitLinesatPoints.txt | 7 ++ .../saga/description/SplitLineswithLines.txt | 6 ++ .../SupervisedClassificationforGrids.txt | 21 ++++++ .../SupervisedClassificationforShapes.txt | 20 ++++++ ...ortVectorMachineClassification(OpenCV).txt | 15 ++++ .../SurfaceGradientandConcentration.txt | 12 ++++ .../saga/description/SurfaceandGradient.txt | 6 ++ .../description/SymmetricalDifference.txt | 6 ++ .../algs/saga/description/TCILow.txt | 5 ++ .../description/TasseledCapTransformation.txt | 11 +++ .../algs/saga/description/TerrainMapView.txt | 11 +++ ...SurfaceClassification(IwahashiandPike).txt | 16 +++++ .../description/TerrainSurfaceConvexity.txt | 13 ++++ .../description/TerrainSurfaceTexture.txt | 11 +++ .../saga/description/ThiessenPolygons.txt | 5 ++ .../TopofAtmosphereReflectance.txt | 69 +++++++++++++++++++ .../saga/description/TopographicOpenness.txt | 9 +++ .../algs/saga/description/Union.txt | 6 ++ .../algs/saga/description/Update.txt | 6 ++ .../UpslopeandDownslopeCurvature.txt | 9 +++ .../algs/saga/description/ValleyDepth.txt | 8 +++ ...alleyandRidgeDetection(TopHatApproach).txt | 12 ++++ .../VegetationIndex(DistanceBased).txt | 12 ++++ .../algs/saga/description/WarpingShapes.txt | 10 +++ .../WatershedSegmentation(ViGrA).txt | 7 ++ .../saga/description/WindExpositionIndex.txt | 9 +++ ...ssionAnalysis(PointsandPredictorGrids).txt | 14 ++++ 132 files changed, 1493 insertions(+) create mode 100644 python/plugins/processing/algs/saga/description/ANGMAP.txt create mode 100644 python/plugins/processing/algs/saga/description/AccumulatedCost.txt create mode 100644 python/plugins/processing/algs/saga/description/AccumulationFunctions.txt create mode 100644 python/plugins/processing/algs/saga/description/AddIndicatorFieldsforCategories.txt create mode 100644 python/plugins/processing/algs/saga/description/AngularDistanceWeighted.txt create mode 100644 python/plugins/processing/algs/saga/description/ArtificialNeuralNetworkClassification(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/Aspect-SlopeGrid.txt create mode 100644 python/plugins/processing/algs/saga/description/AutomatedCloudCoverAssessment.txt create mode 100644 python/plugins/processing/algs/saga/description/AverageWithMask1.txt create mode 100644 python/plugins/processing/algs/saga/description/AverageWithMask2.txt create mode 100644 python/plugins/processing/algs/saga/description/AverageWithThereshold1.txt create mode 100644 python/plugins/processing/algs/saga/description/AverageWithThereshold2.txt create mode 100644 python/plugins/processing/algs/saga/description/AverageWithThereshold3.txt create mode 100644 python/plugins/processing/algs/saga/description/BasicTerrainAnalysis.txt create mode 100644 python/plugins/processing/algs/saga/description/BinaryErosion-Reconstruction.txt create mode 100644 python/plugins/processing/algs/saga/description/BioclimaticVariables.txt create mode 100644 python/plugins/processing/algs/saga/description/BoostingClassification(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/CategoricalCoincidence.txt create mode 100644 python/plugins/processing/algs/saga/description/ChangeDataStorage.txt create mode 100644 python/plugins/processing/algs/saga/description/CloseGapswithStepwiseResampling.txt create mode 100644 python/plugins/processing/algs/saga/description/ClusterAnalysis(Shapes).txt create mode 100644 python/plugins/processing/algs/saga/description/ClusterAnalysis.txt create mode 100644 python/plugins/processing/algs/saga/description/Concentration.txt create mode 100644 python/plugins/processing/algs/saga/description/ConfusionMatrix(PolygonsGrid).txt create mode 100644 python/plugins/processing/algs/saga/description/ConfusionMatrix(TwoGrids).txt create mode 100644 python/plugins/processing/algs/saga/description/ConnectivityAnalysis.txt create mode 100644 python/plugins/processing/algs/saga/description/ConstantGrid.txt create mode 100644 python/plugins/processing/algs/saga/description/CoveredDistance.txt create mode 100644 python/plugins/processing/algs/saga/description/CreatePointGrid.txt create mode 100644 python/plugins/processing/algs/saga/description/DecisionTreeClassification(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/Destriping.txt create mode 100644 python/plugins/processing/algs/saga/description/DestripingwithMask.txt create mode 100644 python/plugins/processing/algs/saga/description/Difference.txt create mode 100644 python/plugins/processing/algs/saga/description/DiffusePollutionRisk.txt create mode 100644 python/plugins/processing/algs/saga/description/DirectionalAverage.txt create mode 100644 python/plugins/processing/algs/saga/description/Distance(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/EarthsOrbitalParameters.txt create mode 100644 python/plugins/processing/algs/saga/description/EdgeDetection(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/EnhancedVegetationIndex.txt create mode 100644 python/plugins/processing/algs/saga/description/EnumerateTableField.txt create mode 100644 python/plugins/processing/algs/saga/description/FieldStatistics.txt create mode 100644 python/plugins/processing/algs/saga/description/FindFieldofExtremeValue.txt create mode 100644 python/plugins/processing/algs/saga/description/FourierFilter(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/FourierTransform(RealViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/FourierTransform(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/FourierTransformInverse(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/FourierTransformation(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/FunctionFit.txt create mode 100644 python/plugins/processing/algs/saga/description/FuzzyLandformElementClassification.txt create mode 100644 python/plugins/processing/algs/saga/description/GWRforMultiplePredictors.txt create mode 100644 python/plugins/processing/algs/saga/description/GWRforSinglePredictor(GriddedModelOutput).txt create mode 100644 python/plugins/processing/algs/saga/description/GWRforSinglePredictorGrid.txt create mode 100644 python/plugins/processing/algs/saga/description/GenerateShapes.txt create mode 100644 python/plugins/processing/algs/saga/description/GeodesicMorphologicalReconstruction.txt create mode 100644 python/plugins/processing/algs/saga/description/GeographicCoordinateGrids.txt create mode 100644 python/plugins/processing/algs/saga/description/GridCombination.txt create mode 100644 python/plugins/processing/algs/saga/description/GridStatisticsforPoints.txt create mode 100644 python/plugins/processing/algs/saga/description/IMCORR-FeatureTracking.txt create mode 100644 python/plugins/processing/algs/saga/description/ISODATAClusteringforGrids.txt create mode 100644 python/plugins/processing/algs/saga/description/Identity.txt create mode 100644 python/plugins/processing/algs/saga/description/Intersect.txt create mode 100644 python/plugins/processing/algs/saga/description/InvertGrid.txt create mode 100644 python/plugins/processing/algs/saga/description/K-MeansClusteringforGrids.txt create mode 100644 python/plugins/processing/algs/saga/description/K-NearestNeighboursClassification(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/LS-FactorFieldBased.txt create mode 100644 python/plugins/processing/algs/saga/description/LandUseScenarioGenerator.txt create mode 100644 python/plugins/processing/algs/saga/description/LineSmoothing.txt create mode 100644 python/plugins/processing/algs/saga/description/MaximumEntropyPresencePrediction.txt create mode 100644 python/plugins/processing/algs/saga/description/MaximumFlowPathLength.txt create mode 100644 python/plugins/processing/algs/saga/description/MeltonRuggednessNumber.txt create mode 100644 python/plugins/processing/algs/saga/description/MeshDenoise.txt create mode 100644 python/plugins/processing/algs/saga/description/MirrorGrid.txt create mode 100644 python/plugins/processing/algs/saga/description/ModifedQuadraticShepard.txt create mode 100644 python/plugins/processing/algs/saga/description/MonthlyGlobalbyLatitude.txt create mode 100644 python/plugins/processing/algs/saga/description/MorphologicalFilter(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/MorphologicalFilter(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/MorphometricFeatures.txt create mode 100644 python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis(Shapes).txt create mode 100644 python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis.txt create mode 100644 python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(GridandPredictorGrids).txt create mode 100644 python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(PointsandPredictorGrids).txt create mode 100644 python/plugins/processing/algs/saga/description/NormalBayesClassification(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/PET(afterHargreavesGrid).txt create mode 100644 python/plugins/processing/algs/saga/description/PET(afterHargreavesTable).txt create mode 100644 python/plugins/processing/algs/saga/description/PointDistances.txt create mode 100644 python/plugins/processing/algs/saga/description/PolygonSelf-Intersection.txt create mode 100644 python/plugins/processing/algs/saga/description/RandomForestClassification(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/RandomForestPresencePrediction(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/RegressionAnalysis(PointsandPredictorGrid).txt create mode 100644 python/plugins/processing/algs/saga/description/RegressionKriging.txt create mode 100644 python/plugins/processing/algs/saga/description/ResamplingFilter.txt create mode 100644 python/plugins/processing/algs/saga/description/RiverBasin.txt create mode 100644 python/plugins/processing/algs/saga/description/RiverGridGeneration.txt create mode 100644 python/plugins/processing/algs/saga/description/SVMClassification.txt create mode 100644 python/plugins/processing/algs/saga/description/SeededRegionGrowing.txt create mode 100644 python/plugins/processing/algs/saga/description/SievingClasses.txt create mode 100644 python/plugins/processing/algs/saga/description/SimpleFilterwithinshapes.txt create mode 100644 python/plugins/processing/algs/saga/description/SimpleKriging.txt create mode 100644 python/plugins/processing/algs/saga/description/SingleValueDecomposition(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/SlopeLimitedFlowAccumulation.txt create mode 100644 python/plugins/processing/algs/saga/description/Smoothing(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/SnapPointstoGrid.txt create mode 100644 python/plugins/processing/algs/saga/description/SnapPointstoLines.txt create mode 100644 python/plugins/processing/algs/saga/description/SnapPointstoPoints.txt create mode 100644 python/plugins/processing/algs/saga/description/SplitLinesatPoints.txt create mode 100644 python/plugins/processing/algs/saga/description/SplitLineswithLines.txt create mode 100644 python/plugins/processing/algs/saga/description/SupervisedClassificationforGrids.txt create mode 100644 python/plugins/processing/algs/saga/description/SupervisedClassificationforShapes.txt create mode 100644 python/plugins/processing/algs/saga/description/SupportVectorMachineClassification(OpenCV).txt create mode 100644 python/plugins/processing/algs/saga/description/SurfaceGradientandConcentration.txt create mode 100644 python/plugins/processing/algs/saga/description/SurfaceandGradient.txt create mode 100644 python/plugins/processing/algs/saga/description/SymmetricalDifference.txt create mode 100644 python/plugins/processing/algs/saga/description/TCILow.txt create mode 100644 python/plugins/processing/algs/saga/description/TasseledCapTransformation.txt create mode 100644 python/plugins/processing/algs/saga/description/TerrainMapView.txt create mode 100644 python/plugins/processing/algs/saga/description/TerrainSurfaceClassification(IwahashiandPike).txt create mode 100644 python/plugins/processing/algs/saga/description/TerrainSurfaceConvexity.txt create mode 100644 python/plugins/processing/algs/saga/description/TerrainSurfaceTexture.txt create mode 100644 python/plugins/processing/algs/saga/description/ThiessenPolygons.txt create mode 100644 python/plugins/processing/algs/saga/description/TopofAtmosphereReflectance.txt create mode 100644 python/plugins/processing/algs/saga/description/TopographicOpenness.txt create mode 100644 python/plugins/processing/algs/saga/description/Union.txt create mode 100644 python/plugins/processing/algs/saga/description/Update.txt create mode 100644 python/plugins/processing/algs/saga/description/UpslopeandDownslopeCurvature.txt create mode 100644 python/plugins/processing/algs/saga/description/ValleyDepth.txt create mode 100644 python/plugins/processing/algs/saga/description/ValleyandRidgeDetection(TopHatApproach).txt create mode 100644 python/plugins/processing/algs/saga/description/VegetationIndex(DistanceBased).txt create mode 100644 python/plugins/processing/algs/saga/description/WarpingShapes.txt create mode 100644 python/plugins/processing/algs/saga/description/WatershedSegmentation(ViGrA).txt create mode 100644 python/plugins/processing/algs/saga/description/WindExpositionIndex.txt create mode 100644 python/plugins/processing/algs/saga/description/ZonalMultipleRegressionAnalysis(PointsandPredictorGrids).txt diff --git a/python/plugins/processing/algs/saga/SagaNameDecorator.py b/python/plugins/processing/algs/saga/SagaNameDecorator.py index 6f292c7c010..83ba4b03b35 100644 --- a/python/plugins/processing/algs/saga/SagaNameDecorator.py +++ b/python/plugins/processing/algs/saga/SagaNameDecorator.py @@ -33,11 +33,17 @@ groups = {'grid_analysis': 'Raster analysis', 'grid_gridding': 'Raster creation tools', 'grid_spline': 'Raster creation tools', 'grid_tools': 'Raster tools', + 'contrib_perego': 'Raster tools', + 'climate_tools': 'Climate tools', 'grid_visualisation': 'Raster visualization', 'imagery_classification': 'Image analysis', 'imagery_rga': 'Image analysis', 'imagery_segmentation': 'Image analysis', 'imagery_tools': 'Image analysis', + 'imagery_maxent': 'Image analysis', + 'imagery_opencv': 'Image analysis', + 'imagery_vigra': 'Image analysis', + 'imagery_svm': 'Image analysis', 'io_esri_e00': 'I/O', 'io_gdal': 'I/O', 'io_gps': 'I/O', @@ -63,6 +69,8 @@ groups = {'grid_analysis': 'Raster analysis', 'sim_ecosystems_hugget': 'Simulation', 'sim_fire_spreading': 'Simulation', 'sim_hydrology': 'Simulation', + 'sim_rivflow': 'Simulation', + 'sim_qm_of_esp': 'Simulation', 'statistics_grid': 'Geostatistics', 'statistics_kriging': 'Raster creation tools', 'statistics_points': 'Geostatistics', @@ -72,6 +80,7 @@ groups = {'grid_analysis': 'Raster analysis', 'ta_hydrology': 'Terrain Analysis - Hydrology', 'ta_lighting': 'Terrain Analysis - Lighting', 'ta_morphometry': 'Terrain Analysis - Morphometry', + 'ta_slope_stability': 'Terrain Analysis - Morphometry', 'ta_preprocessor': 'Terrain Analysis - Hydrology', 'ta_profiles': 'Terrain Analysis - Profiles', 'table_calculus': 'Table tools', diff --git a/python/plugins/processing/algs/saga/description/ANGMAP.txt b/python/plugins/processing/algs/saga/description/ANGMAP.txt new file mode 100644 index 00000000000..23ce3fdf893 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ANGMAP.txt @@ -0,0 +1,10 @@ +ANGMAP +ta_slope_stability +ParameterRaster|DEM|Elevation|False +ParameterRaster|C|Dip grid (degrees)|True +ParameterRaster|D|Dip direction grid (degrees)|True +ParameterNumber|fB|Global structure dip (degrees)|None|None| 45.000000 +ParameterNumber|fC|Global structure dip direction (degrees)|None|None| 90.000000 +OutputRaster|E|Angle +OutputRaster|F|CL dipdir +OutputRaster|G|CL dip \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AccumulatedCost.txt b/python/plugins/processing/algs/saga/description/AccumulatedCost.txt new file mode 100644 index 00000000000..0798dedc8d2 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AccumulatedCost.txt @@ -0,0 +1,12 @@ +Accumulated Cost +grid_analysis +ParameterSelection|DEST_TYPE|Input Type of Destinations|[0] Point;[1] Grid| 0 +ParameterVector|DEST_POINTS|Destinations|-1|False +ParameterRaster|DEST_GRID|Destinations|False +ParameterRaster|COST|Local Cost|False +ParameterRaster|DIR_MAXCOST|Direction of Maximum Cost|True +ParameterSelection|DIR_UNIT|Units of Direction|[0] radians;[1] degree| 0 +ParameterNumber|DIR_K|K Factor|None|None| 2.000000 +OutputRaster|ACCUMULATED|Accumulated Cost +OutputRaster|ALLOCATION|Allocation +ParameterNumber|THRESHOLD|Threshold for different route| 0.000000|None| 0.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AccumulationFunctions.txt b/python/plugins/processing/algs/saga/description/AccumulationFunctions.txt new file mode 100644 index 00000000000..ef19e65fce8 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AccumulationFunctions.txt @@ -0,0 +1,12 @@ +Accumulation Functions +grid_analysis +ParameterRaster|SURFACE|Surface|False +ParameterRaster|INPUT|Input|False +ParameterRaster|STATE_IN|State t|True +ParameterRaster|CONTROL|Operation Control|True +ParameterRaster|CTRL_LINEAR|Linear Flow Control Grid|True +OutputRaster|FLUX|Flux +OutputRaster|STATE_OUT|State t + 1 +ParameterSelection|OPERATION|Operation|[0] accuflux;[1] accucapacityflux / state;[2] accufractionflux / state;[3] accuthresholdflux / state;[4] accutriggerflux / state| 0 +ParameterBoolean|LINEAR|Switch to Linear Flow|True +ParameterNumber|THRES_LINEAR|Threshold Linear Flow|None|None| 0.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AddIndicatorFieldsforCategories.txt b/python/plugins/processing/algs/saga/description/AddIndicatorFieldsforCategories.txt new file mode 100644 index 00000000000..684c96074b0 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AddIndicatorFieldsforCategories.txt @@ -0,0 +1,6 @@ +Add Indicator Fields for Categories +table_tools +ParameterTable|TABLE|Table|False +ParameterTable|FIELD|Categories|False +ParameterTable|OUT_TABLE|Output table with field(s) deleted|True +OutputVector|OUT_SHAPES|Output shapes with field(s) deleted \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AngularDistanceWeighted.txt b/python/plugins/processing/algs/saga/description/AngularDistanceWeighted.txt new file mode 100644 index 00000000000..9523f8fe5b5 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AngularDistanceWeighted.txt @@ -0,0 +1,23 @@ +Angular Distance Weighted +grid_gridding +ParameterVector|SHAPES|Points|-1|False +ParameterTable|FIELD|Attribute|False +ParameterSelection|TARGET_DEFINITION|Target Grid System|[0] user defined;[1] grid or grid system| 0 +ParameterNumber|TARGET_USER_SIZE|Cellsize| 0.000000|None| 1.000000 +ParameterNumber|TARGET_USER_XMIN|Left|None|None| 0.000000 +ParameterNumber|TARGET_USER_XMAX|Right|None|None| 100.000000 +ParameterNumber|TARGET_USER_YMIN|Bottom|None|None| 0.000000 +ParameterNumber|TARGET_USER_YMAX|Top|None|None| 100.000000 +ParameterSelection|TARGET_USER_FITS|Fit|[0] nodes;[1] cells| 0 +ParameterRaster|TARGET_TEMPLATE|Target System|True +OutputRaster|TARGET_OUT_GRID|Target Grid +ParameterSelection|SEARCH_RANGE|Search Range|[0] local;[1] global| 0 +ParameterNumber|SEARCH_RADIUS|Maximum Search Distance| 0.000000|None| 1000.000000 +ParameterSelection|SEARCH_POINTS_ALL|Number of Points|[0] maximum number of nearest points;[1] all points within search distance| 0 +ParameterNumber|SEARCH_POINTS_MIN|Minimum| 1|None| -1 +ParameterNumber|SEARCH_POINTS_MAX|Maximum| 1|None| 20 +ParameterSelection|SEARCH_DIRECTION|Direction|[0] all directions;[1] quadrants| 0 +ParameterSelection|DW_WEIGHTING|Weighting Function|[0] no distance weighting;[1] inverse distance to a power;[2] exponential;[3] gaussian weighting| 1 +ParameterNumber|DW_IDW_POWER|Inverse Distance Weighting Power| 0.000000|None| 2.000000 +ParameterBoolean|DW_IDW_OFFSET|Inverse Distance Offset|False +ParameterNumber|DW_BANDWIDTH|Gaussian and Exponential Weighting Bandwidth| 0.000000|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ArtificialNeuralNetworkClassification(OpenCV).txt b/python/plugins/processing/algs/saga/description/ArtificialNeuralNetworkClassification(OpenCV).txt new file mode 100644 index 00000000000..f32058dad19 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ArtificialNeuralNetworkClassification(OpenCV).txt @@ -0,0 +1,22 @@ +Artificial Neural Network Classification (OpenCV) +imagery_opencv +ParameterMultipleInput|FEATURES|Features|3|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterVector|TRAIN_AREAS|Training Areas|2|False +ParameterTable|TRAIN_CLASS|Class Identifier|False +OutputRaster|CLASSES|Classification +ParameterNumber|ANN_LAYERS|Number of Layers| 1|None| 3 +ParameterNumber|ANN_NEURONS|Number of Neurons| 1|None| 3 +ParameterNumber|ANN_MAXITER|Maximum Number of Iterations| 1|None| 300 +ParameterNumber|ANN_EPSILON|Error Change (Epsilon)| 0.000000|None| 0.000000 +ParameterSelection|ANN_ACTIVATION|Activation Function|[0] Identity;[1] Sigmoid;[2] Gaussian| 1 +ParameterNumber|ANN_ACT_ALPHA|Function's Alpha|None|None| 1.000000 +ParameterNumber|ANN_ACT_BETA|Function's Beta|None|None| 1.000000 +ParameterSelection|ANN_PROPAGATION|Training Method|[0] resilient propagation;[1] back propagation| 1 +ParameterNumber|ANN_RP_DW0|Initial Update Value|None|None| 0.000000 +ParameterNumber|ANN_RP_DW_PLUS|Increase Factor| 1.010000|None| 1.200000 +ParameterNumber|ANN_RP_DW_MINUS|Decrease Factor| 0.010000| 0.990000| 0.500000 +ParameterNumber|ANN_RP_DW_MIN|Lower Value Update Limit| 0.010000|None| 0.100000 +ParameterNumber|ANN_RP_DW_MAX|Upper Value Update Limit| 1.010000|None| 1.100000 +ParameterNumber|ANN_BP_DW|Weight Gradient Term|None|None| 0.100000 +ParameterNumber|ANN_BP_MOMENT|Moment Term|None|None| 0.100000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Aspect-SlopeGrid.txt b/python/plugins/processing/algs/saga/description/Aspect-SlopeGrid.txt new file mode 100644 index 00000000000..160cafc4873 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Aspect-SlopeGrid.txt @@ -0,0 +1,6 @@ +Aspect-Slope Grid +grid_visualisation +ParameterRaster|ASPECT|Aspect|False +ParameterRaster|SLOPE|Slope|False +OutputRaster|ASPECT_SLOPE|Aspect-Slope +ParameterTable|LUT|Lookup Table|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AutomatedCloudCoverAssessment.txt b/python/plugins/processing/algs/saga/description/AutomatedCloudCoverAssessment.txt new file mode 100644 index 00000000000..6d9bbf20eba --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AutomatedCloudCoverAssessment.txt @@ -0,0 +1,14 @@ +Automated Cloud Cover Assessment +imagery_tools +ParameterRaster|BAND2|Landsat Band 2|False +ParameterRaster|BAND3|Landsat Band 3|False +ParameterRaster|BAND4|Landsat Band 4|False +ParameterRaster|BAND5|Landsat Band 5|False +ParameterRaster|BAND6|Landsat Band 6|False +OutputRaster|CLOUD|Cloud Cover +ParameterBoolean|FILTER|Apply post-processing filter to remove small holes|True +ParameterNumber|B56C|B56 Composite (step 6)|None|None| 225.000000 +ParameterNumber|B45R|B45 Ratio: Desert detection (step 10)|None|None| 1.000000 +ParameterBoolean|CSIG|Always use cloud signature (step 14)|True +ParameterBoolean|PASS2|Bypass second-pass processing, and merge warm (not ambiguous) and cold clouds|True +ParameterBoolean|SHADOW|Include a category for cloud shadows|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AverageWithMask1.txt b/python/plugins/processing/algs/saga/description/AverageWithMask1.txt new file mode 100644 index 00000000000..13c570c533b --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AverageWithMask1.txt @@ -0,0 +1,8 @@ +Average With Mask 1 +contrib_perego +ParameterRaster|INPUT|Input|False +ParameterRaster|MASK|Mask Grid|False +OutputRaster|RESULT|AWM1 Grid +ParameterNumber|V|Mask value|None|None| 1.000000 +ParameterNumber|RX|Radius X| 1|None| 1 +ParameterNumber|RY|Radius Y| 1|None| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AverageWithMask2.txt b/python/plugins/processing/algs/saga/description/AverageWithMask2.txt new file mode 100644 index 00000000000..fd260f52cad --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AverageWithMask2.txt @@ -0,0 +1,8 @@ +Average With Mask 2 +contrib_perego +ParameterRaster|INPUT|Input|False +ParameterRaster|MASK|Mask Grid|False +OutputRaster|RESULT|AWM2 Grid +ParameterNumber|V|Mask value|None|None| 1.000000 +ParameterNumber|RX|Radius X| 1|None| 1 +ParameterNumber|RY|Radius Y| 1|None| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AverageWithThereshold1.txt b/python/plugins/processing/algs/saga/description/AverageWithThereshold1.txt new file mode 100644 index 00000000000..346949c3bf4 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AverageWithThereshold1.txt @@ -0,0 +1,7 @@ +Average With Thereshold 1 +contrib_perego +ParameterRaster|INPUT|Input|False +OutputRaster|RESULT|AWT Grid +ParameterNumber|RX|Radius X| 1|None| 1 +ParameterNumber|RY|Radius Y| 1|None| 1 +ParameterNumber|THRESH|Threshold|None|None| 2.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AverageWithThereshold2.txt b/python/plugins/processing/algs/saga/description/AverageWithThereshold2.txt new file mode 100644 index 00000000000..1991ead881c --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AverageWithThereshold2.txt @@ -0,0 +1,7 @@ +Average With Thereshold 2 +contrib_perego +ParameterRaster|INPUT|Input|False +OutputRaster|RESULT|AWT Grid +ParameterNumber|RX|Radius X| 1|None| 1 +ParameterNumber|RY|Radius Y| 1|None| 1 +ParameterNumber|THRESH|Threshold|None|None| 2.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/AverageWithThereshold3.txt b/python/plugins/processing/algs/saga/description/AverageWithThereshold3.txt new file mode 100644 index 00000000000..3d17c338ddd --- /dev/null +++ b/python/plugins/processing/algs/saga/description/AverageWithThereshold3.txt @@ -0,0 +1,7 @@ +Average With Thereshold 3 +contrib_perego +ParameterRaster|INPUT|Input|False +OutputRaster|RESULT|AWT Grid +ParameterNumber|RX|Radius X| 1|None| 1 +ParameterNumber|RY|Radius Y| 1|None| 1 +ParameterNumber|THRESH|Threshold|None|None| 2.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/BasicTerrainAnalysis.txt b/python/plugins/processing/algs/saga/description/BasicTerrainAnalysis.txt new file mode 100644 index 00000000000..43cd020628a --- /dev/null +++ b/python/plugins/processing/algs/saga/description/BasicTerrainAnalysis.txt @@ -0,0 +1,20 @@ +Basic Terrain Analysis +ta_compound +ParameterRaster|ELEVATION|Elevation|False +OutputRaster|SHADE|Analytical Hillshading +OutputRaster|SLOPE|Slope +OutputRaster|ASPECT|Aspect +OutputRaster|HCURV|Plan Curvature +OutputRaster|VCURV|Profile Curvature +OutputRaster|CONVERGENCE|Convergence Index +OutputRaster|SINKS|Closed Depressions +OutputRaster|FLOW|Total Catchment Area +OutputRaster|WETNESS|Topographic Wetness Index +OutputRaster|LSFACTOR|LS-Factor +OutputVector|CHANNELS|Channel Network +OutputVector|BASINS|Drainage Basins +OutputRaster|CHNL_BASE|Channel Network Base Level +OutputRaster|CHNL_DIST|Channel Network Distance +OutputRaster|VALL_DEPTH|Valley Depth +OutputRaster|RSP|Relative Slope Position +ParameterNumber|THRESHOLD|Channel Density| 1|None| 5 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/BinaryErosion-Reconstruction.txt b/python/plugins/processing/algs/saga/description/BinaryErosion-Reconstruction.txt new file mode 100644 index 00000000000..29c47b2ee35 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/BinaryErosion-Reconstruction.txt @@ -0,0 +1,5 @@ +Binary Erosion-Reconstruction +grid_filter +ParameterRaster|INPUT_GRID|Input Grid|False +OutputRaster|OUTPUT_GRID|Output Grid +ParameterNumber|RADIUS|Filter Size (Radius)|None|None| 3 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/BioclimaticVariables.txt b/python/plugins/processing/algs/saga/description/BioclimaticVariables.txt new file mode 100644 index 00000000000..32ef3239429 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/BioclimaticVariables.txt @@ -0,0 +1,26 @@ +Bioclimatic Variables +climate_tools +ParameterMultipleInput|TMEAN|Mean Temperature|3|False +ParameterMultipleInput|TMIN|Minimum Temperature|3|False +ParameterMultipleInput|TMAX|Maximum Temperature|3|False +ParameterMultipleInput|P|Precipitation|3|False +OutputRaster|BIO_01|Annual Mean Temperature +OutputRaster|BIO_02|Mean Diurnal Range +OutputRaster|BIO_03|Isothermality +OutputRaster|BIO_04|Temperature Seasonality +OutputRaster|BIO_05|Maximum Temperature of Warmest Month +OutputRaster|BIO_06|Minimum Temperature of Coldest Month +OutputRaster|BIO_07|Temperature Annual Range +OutputRaster|BIO_08|Mean Temperature of Wettest Quarter +OutputRaster|BIO_09|Mean Temperature of Driest Quarter +OutputRaster|BIO_10|Mean Temperature of Warmest Quarter +OutputRaster|BIO_11|Mean Temperature of Coldest Quarter +OutputRaster|BIO_12|Annual Precipitation +OutputRaster|BIO_13|Precipitation of Wettest Month +OutputRaster|BIO_14|Precipitation of Driest Month +OutputRaster|BIO_15|Precipitation Seasonality +OutputRaster|BIO_16|Precipitation of Wettest Quarter +OutputRaster|BIO_17|Precipitation of Driest Quarter +OutputRaster|BIO_18|Precipitation of Warmest Quarter +OutputRaster|BIO_19|Precipitation of Coldest Quarter +ParameterSelection|SEASONALITY|Temperature Seasonality|[0] Coefficient of Variation;[1] Standard Deviation| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/BoostingClassification(OpenCV).txt b/python/plugins/processing/algs/saga/description/BoostingClassification(OpenCV).txt new file mode 100644 index 00000000000..c39bb62d381 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/BoostingClassification(OpenCV).txt @@ -0,0 +1,16 @@ +Boosting Classification (OpenCV) +imagery_opencv +ParameterMultipleInput|FEATURES|Features|3|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterVector|TRAIN_AREAS|Training Areas|-1|False +ParameterTable|TRAIN_CLASS|Class Identifier|False +OutputRaster|CLASSES|Classification +ParameterNumber|MAX_DEPTH|Maximum Tree Depth| 1|None| 10 +ParameterNumber|MIN_SAMPLES|Minimum Sample Count| 2|None| 2 +ParameterNumber|MAX_CATEGRS|Maximum Categories| 1|None| 10 +ParameterBoolean|1SE_RULE|Use 1SE Rule|True +ParameterBoolean|TRUNC_PRUNED|Truncate Pruned Trees|True +ParameterNumber|REG_ACCURACY|Regression Accuracy| 0.000000|None| 0.010000 +ParameterNumber|WEAK_COUNT|Weak Count| 0|None| 100 +ParameterNumber|WGT_TRIM_RATE|Weight Trim Rate| 0.000000| 1.000000| 0.950000 +ParameterSelection|BOOST_TYPE|Boost Type|[0] Discrete AdaBoost;[1] Real AdaBoost;[2] LogitBoost;[3] Gentle AdaBoost| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/CategoricalCoincidence.txt b/python/plugins/processing/algs/saga/description/CategoricalCoincidence.txt new file mode 100644 index 00000000000..159816fe91d --- /dev/null +++ b/python/plugins/processing/algs/saga/description/CategoricalCoincidence.txt @@ -0,0 +1,8 @@ +Categorical Coincidence +statistics_grid +ParameterMultipleInput|GRIDS|Grids|3|False +OutputRaster|CATEGORIES|Number of Categories +OutputRaster|COINCIDENCE|Coincidence +OutputRaster|MAJ_COUNT|Dominance of Majority +OutputRaster|MAJ_VALUE|Value of Majority +ParameterNumber|RADIUS|Radius [Cells]| 0|None| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ChangeDataStorage.txt b/python/plugins/processing/algs/saga/description/ChangeDataStorage.txt new file mode 100644 index 00000000000..d52c9eed462 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ChangeDataStorage.txt @@ -0,0 +1,7 @@ +Change Data Storage +grid_tools +ParameterRaster|INPUT|Grid|False +OutputRaster|OUTPUT|Converted Grid +ParameterSelection|TYPE|Data storage type|[0] bit;[1] unsigned 1 byte integer;[2] signed 1 byte integer;[3] unsigned 2 byte integer;[4] signed 2 byte integer;[5] unsigned 4 byte integer;[6] signed 4 byte integer;[7] 4 byte floating point number;[8] 8 byte floating point number| 7 +ParameterNumber|OFFSET|Offset|None|None| 0.000000 +ParameterNumber|SCALE|Scale|None|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/CloseGapswithStepwiseResampling.txt b/python/plugins/processing/algs/saga/description/CloseGapswithStepwiseResampling.txt new file mode 100644 index 00000000000..e6a8d531d96 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/CloseGapswithStepwiseResampling.txt @@ -0,0 +1,10 @@ +Close Gaps with Stepwise Resampling +grid_tools +ParameterRaster|INPUT|Grid|False +ParameterRaster|MASK|Mask|True +OutputRaster|RESULT|Result +ParameterSelection|RESAMPLING|Resampling|[0] Nearest Neighbour;[1] Bilinear Interpolation;[2] Bicubic Spline Interpolation;[3] B-Spline Interpolation| 3 +ParameterNumber|GROW|Grow Factor| 1.000000|None| 2.000000 +ParameterBoolean|PYRAMIDS|Use Pyramids|False +ParameterSelection|START|Start Size|[0] grid cell size;[1] user defined size| 0 +ParameterNumber|START_SIZE|User Defined Size| 0.000000|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ClusterAnalysis(Shapes).txt b/python/plugins/processing/algs/saga/description/ClusterAnalysis(Shapes).txt new file mode 100644 index 00000000000..0967d7a100f --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ClusterAnalysis(Shapes).txt @@ -0,0 +1,10 @@ +Cluster Analysis (Shapes) +table_calculus +ParameterVector|INPUT|Shapes|-1|False +OutputVector|RESULT|Result +ParameterTable|FIELDS|Attributes|False +ParameterTable|CLUSTER|Cluster|False +ParameterTable|STATISTICS|Statistics|False +ParameterSelection|METHOD|Method|[0] Iterative Minimum Distance (Forgy 1965);[1] Hill-Climbing (Rubin 1967);[2] Combined Minimum Distance / Hillclimbing| 1 +ParameterNumber|NCLUSTER|Clusters| 2|None| 10 +ParameterBoolean|NORMALISE|Normalise|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ClusterAnalysis.txt b/python/plugins/processing/algs/saga/description/ClusterAnalysis.txt new file mode 100644 index 00000000000..702d24d6247 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ClusterAnalysis.txt @@ -0,0 +1,10 @@ +Cluster Analysis +table_calculus +ParameterTable|INPUT|Table|False +ParameterTable|RESULT|Result|True +ParameterTable|FIELDS|Attributes|False +ParameterTable|CLUSTER|Cluster|False +ParameterTable|STATISTICS|Statistics|False +ParameterSelection|METHOD|Method|[0] Iterative Minimum Distance (Forgy 1965);[1] Hill-Climbing (Rubin 1967);[2] Combined Minimum Distance / Hillclimbing| 1 +ParameterNumber|NCLUSTER|Clusters| 2|None| 10 +ParameterBoolean|NORMALISE|Normalise|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Concentration.txt b/python/plugins/processing/algs/saga/description/Concentration.txt new file mode 100644 index 00000000000..1751808cfb1 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Concentration.txt @@ -0,0 +1,10 @@ +Concentration +sim_hydrology +ParameterRaster|MASK|Mask|False +ParameterRaster|GRAD|Gradient|False +OutputRaster|CONC|Concentration +ParameterNumber|CONC_IN|Inlet Concentration| 0.000000|None| 5.000000 +ParameterNumber|CONC_OUT|Outlet Concentration| 0.000000|None| 3.000000 +ParameterNumber|CONC_E|Concentration Approximation Threshold| 0.000000|None| 0.001000 +ParameterNumber|GRAD_MIN|Minimum Gradient| 0.000000|None| 0.000000 +ParameterSelection|NEIGHBOURS|Neighbourhood|[0] Moore (8);[1] Neumann (4);[2] Optimised| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ConfusionMatrix(PolygonsGrid).txt b/python/plugins/processing/algs/saga/description/ConfusionMatrix(PolygonsGrid).txt new file mode 100644 index 00000000000..640b11dc453 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ConfusionMatrix(PolygonsGrid).txt @@ -0,0 +1,12 @@ +Confusion Matrix (Polygons / Grid) +imagery_classification +ParameterRaster|GRID|Classification|False +ParameterTable|GRID_LUT|Look-up Table|True +ParameterTable|GRID_LUT_MIN|Value|False +ParameterTable|GRID_LUT_MAX|Value (Maximum)|False +ParameterTable|GRID_LUT_NAM|Name|False +ParameterVector|POLYGONS|Polygons|-1|False +ParameterTable|FIELD|Classes|False +ParameterTable|CONFUSION|Confusion Matrix|False +ParameterTable|CLASSES|Class Values|False +ParameterTable|SUMMARY|Summary|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ConfusionMatrix(TwoGrids).txt b/python/plugins/processing/algs/saga/description/ConfusionMatrix(TwoGrids).txt new file mode 100644 index 00000000000..32002a8d739 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ConfusionMatrix(TwoGrids).txt @@ -0,0 +1,18 @@ +Confusion Matrix (Two Grids) +imagery_classification +ParameterRaster|ONE|Classification 1|False +ParameterTable|ONE_LUT|Look-up Table|True +ParameterTable|ONE_LUT_MIN|Value|False +ParameterTable|ONE_LUT_MAX|Value (Maximum)|False +ParameterTable|ONE_LUT_NAM|Name|False +ParameterRaster|TWO|Classification 2|False +ParameterTable|TWO_LUT|Look-up Table|True +ParameterTable|TWO_LUT_MIN|Value|False +ParameterTable|TWO_LUT_MAX|Value (Maximum)|False +ParameterTable|TWO_LUT_NAM|Name|False +OutputRaster|COMBINED|Combined Classes +ParameterBoolean|NOCHANGE|Report Unchanged Classes|True +ParameterTable|CONFUSION|Confusion Matrix|False +ParameterSelection|OUTPUT|Output as...|[0] cells;[1] percent;[2] area| 0 +ParameterTable|CLASSES|Class Values|False +ParameterTable|SUMMARY|Summary|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ConnectivityAnalysis.txt b/python/plugins/processing/algs/saga/description/ConnectivityAnalysis.txt new file mode 100644 index 00000000000..c3a51b43786 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ConnectivityAnalysis.txt @@ -0,0 +1,10 @@ +Connectivity Analysis +grid_filter +ParameterRaster|INPUT_GRID|Input Binary Grid|False +OutputRaster|FILTERED_MASK|Filtered Image +ParameterBoolean|FILTER|Apply Filter?|True +ParameterNumber|SIZE|Filter Size (Radius)|None|None| 3 +OutputRaster|SYMBOLIC_IMAGE|Symbolic Image +OutputVector|OUTLINES|Outlines +ParameterBoolean|BORDER_PIXEL_CENTERS|Pixel Centers?|False +ParameterBoolean|REMOVE_MARGINAL_REGIONS|Remove Border Regions?|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ConstantGrid.txt b/python/plugins/processing/algs/saga/description/ConstantGrid.txt new file mode 100644 index 00000000000..c60ba635fb7 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ConstantGrid.txt @@ -0,0 +1,14 @@ +Constant Grid +grid_tools +ParameterString|NAME|Name +ParameterNumber|CONST|Constant Value|None|None| 1.000000 +ParameterSelection|TYPE|Data Type|[0] bit;[1] unsigned 1 byte integer;[2] signed 1 byte integer;[3] unsigned 2 byte integer;[4] signed 2 byte integer;[5] unsigned 8 byte integer;[6] signed 8 byte integer;[7] 4 byte floating point number;[8] 8 byte floating point number| 7 +ParameterSelection|DEFINITION|Target Grid System|[0] user defined;[1] grid or grid system| 0 +ParameterNumber|USER_SIZE|Cellsize| 0.000000|None| 1.000000 +ParameterNumber|USER_XMIN|Left|None|None| 0.000000 +ParameterNumber|USER_XMAX|Right|None|None| 100.000000 +ParameterNumber|USER_YMIN|Bottom|None|None| 0.000000 +ParameterNumber|USER_YMAX|Top|None|None| 100.000000 +ParameterSelection|USER_FITS|Fit|[0] nodes;[1] cells| 0 +ParameterRaster|TEMPLATE|Target System|True +OutputRaster|OUT_GRID|Target Grid \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/CoveredDistance.txt b/python/plugins/processing/algs/saga/description/CoveredDistance.txt new file mode 100644 index 00000000000..66e73f65c1b --- /dev/null +++ b/python/plugins/processing/algs/saga/description/CoveredDistance.txt @@ -0,0 +1,4 @@ +Covered Distance +grid_analysis +ParameterMultipleInput|INPUT|Grids|3|False +OutputRaster|RESULT|Covered Distance \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/CreatePointGrid.txt b/python/plugins/processing/algs/saga/description/CreatePointGrid.txt new file mode 100644 index 00000000000..92751ef576d --- /dev/null +++ b/python/plugins/processing/algs/saga/description/CreatePointGrid.txt @@ -0,0 +1,8 @@ +Create Point Grid +shapes_points +OutputVector|POINTS|Point Grid +ParameterNumber|X_EXTENT_MIN|X-Extent (Min)|None|None|None +ParameterNumber|X_EXTENT_MAX|X-Extent (Max)|None|None|None +ParameterNumber|Y_EXTENT_MIN|Y-Extent (Min)|None|None|None +ParameterNumber|Y_EXTENT_MAX|Y-Extent (Max)|None|None|None +ParameterNumber|DIST|Distance|None|None| 100.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/DecisionTreeClassification(OpenCV).txt b/python/plugins/processing/algs/saga/description/DecisionTreeClassification(OpenCV).txt new file mode 100644 index 00000000000..948292883c2 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/DecisionTreeClassification(OpenCV).txt @@ -0,0 +1,13 @@ +Decision Tree Classification (OpenCV) +imagery_opencv +ParameterMultipleInput|FEATURES|Features|3|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterVector|TRAIN_AREAS|Training Areas|-1|False +ParameterTable|TRAIN_CLASS|Class Identifier|False +OutputRaster|CLASSES|Classification +ParameterNumber|MAX_DEPTH|Maximum Tree Depth| 1|None| 10 +ParameterNumber|MIN_SAMPLES|Minimum Sample Count| 2|None| 2 +ParameterNumber|MAX_CATEGRS|Maximum Categories| 1|None| 10 +ParameterBoolean|1SE_RULE|Use 1SE Rule|True +ParameterBoolean|TRUNC_PRUNED|Truncate Pruned Trees|True +ParameterNumber|REG_ACCURACY|Regression Accuracy| 0.000000|None| 0.010000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Destriping.txt b/python/plugins/processing/algs/saga/description/Destriping.txt new file mode 100644 index 00000000000..00957aee93f --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Destriping.txt @@ -0,0 +1,9 @@ +Destriping +contrib_perego +ParameterRaster|INPUT|Input|False +OutputRaster|RESULT3|Destriped Grid +OutputRaster|RESULT1|Low-pass 1 +OutputRaster|RESULT2|Low-pass 2 +ParameterNumber|ANG|Angle (in degrees)|None|None| 0.000000 +ParameterNumber|R|Radius|None|None| 10.000000 +ParameterNumber|D|Stripes distance|None|None| 2.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/DestripingwithMask.txt b/python/plugins/processing/algs/saga/description/DestripingwithMask.txt new file mode 100644 index 00000000000..87ade0dbbea --- /dev/null +++ b/python/plugins/processing/algs/saga/description/DestripingwithMask.txt @@ -0,0 +1,14 @@ +Destriping with Mask +contrib_perego +ParameterRaster|INPUT|Input|False +ParameterRaster|MASK|Mask Grid|False +OutputRaster|RESULT3|Destriped Grid +OutputRaster|RESULT1|Low-pass 1 +OutputRaster|RESULT2|Low-pass 2 +ParameterNumber|ANG|Angle (in degrees)|None|None| 0.000000 +ParameterNumber|R|Radius|None|None| 20.000000 +ParameterNumber|D|Stripes distance|None|None| 2.000000 +ParameterNumber|MIN|Min|None|None| -10.000000 +ParameterNumber|MAX|Max|None|None| 10.000000 +ParameterNumber|MMIN|Mask Min|None|None| -10000.000000 +ParameterNumber|MMAX|Mask Max|None|None| 10000.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Difference.txt b/python/plugins/processing/algs/saga/description/Difference.txt new file mode 100644 index 00000000000..8f1d07faf9a --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Difference.txt @@ -0,0 +1,6 @@ +Difference +shapes_polygons +ParameterVector|A|Layer A|-1|False +ParameterVector|B|Layer B|-1|False +OutputVector|RESULT|Difference +ParameterBoolean|SPLIT|Split Parts|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/DiffusePollutionRisk.txt b/python/plugins/processing/algs/saga/description/DiffusePollutionRisk.txt new file mode 100644 index 00000000000..df79b901b82 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/DiffusePollutionRisk.txt @@ -0,0 +1,13 @@ +Diffuse Pollution Risk +sim_hydrology +ParameterRaster|DEM|Elevation|False +ParameterRaster|CHANNEL|Channel Network|True +ParameterRaster|WEIGHT|Land Cover Weights|True +ParameterNumber|WEIGHT_DEFAULT|Default| 0.000000|None| 1.000000 +ParameterRaster|RAIN|Rainfall|True +ParameterNumber|RAIN_DEFAULT|Default| 0.000000|None| 500.000000 +OutputRaster|DELIVERY|Delivery Index +OutputRaster|RISK_POINT|Locational Risk +OutputRaster|RISK_DIFFUSE|Diffuse Pollution Risk +ParameterSelection|METHOD|Flow Direction Algorithm|[0] single;[1] multiple| 1 +ParameterNumber|CHANNEL_START|Channel Initiation Threshold| 1|None| 150 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/DirectionalAverage.txt b/python/plugins/processing/algs/saga/description/DirectionalAverage.txt new file mode 100644 index 00000000000..9a0f6f567c4 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/DirectionalAverage.txt @@ -0,0 +1,7 @@ +Directional Average +contrib_perego +ParameterRaster|INPUT|Input|False +OutputRaster|RESULT|Output Grid +ParameterNumber|ANG|Angle (in degrees)|None|None| 0.000000 +ParameterNumber|R1|Main Radius|None|None| 1.000000 +ParameterNumber|R2|Transversal radius|None|None| 0.500000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Distance(ViGrA).txt b/python/plugins/processing/algs/saga/description/Distance(ViGrA).txt new file mode 100644 index 00000000000..e4d32017f4c --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Distance(ViGrA).txt @@ -0,0 +1,5 @@ +Distance (ViGrA) +imagery_vigra +ParameterRaster|INPUT|Features|False +OutputRaster|OUTPUT|Distance +ParameterSelection|NORM|Type of distance calculation|[0] Chessboard;[1] Manhattan;[2] Euclidean| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/EarthsOrbitalParameters.txt b/python/plugins/processing/algs/saga/description/EarthsOrbitalParameters.txt new file mode 100644 index 00000000000..a086d2be4a1 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/EarthsOrbitalParameters.txt @@ -0,0 +1,6 @@ +Earth's Orbital Parameters +climate_tools +ParameterTable|ORBPAR|Earth's Orbital Parameters|False +ParameterNumber|START|Start [ka]|None|None| -200.000000 +ParameterNumber|STOP|Stop [ka]|None|None| 2.000000 +ParameterNumber|STEP|Step [ka]| 0.001000|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/EdgeDetection(ViGrA).txt b/python/plugins/processing/algs/saga/description/EdgeDetection(ViGrA).txt new file mode 100644 index 00000000000..f67e3990563 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/EdgeDetection(ViGrA).txt @@ -0,0 +1,7 @@ +Edge Detection (ViGrA) +imagery_vigra +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Edges +ParameterSelection|TYPE|Detector type|[0] Canny;[1] Shen-Castan| 0 +ParameterNumber|SCALE|Operator scale| 0.000000|None| 1.000000 +ParameterNumber|THRESHOLD|Gradient threshold| 0.000000|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/EnhancedVegetationIndex.txt b/python/plugins/processing/algs/saga/description/EnhancedVegetationIndex.txt new file mode 100644 index 00000000000..b6f39adfc78 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/EnhancedVegetationIndex.txt @@ -0,0 +1,10 @@ +Enhanced Vegetation Index +imagery_tools +ParameterRaster|BLUE|Blue Reflectance|True +ParameterRaster|RED|Red Reflectance|False +ParameterRaster|NIR|Near Infrared Reflectance|False +OutputRaster|EVI|Enhanced Vegetation Index +ParameterNumber|GAIN|Gain| 0.000000|None| 2.500000 +ParameterNumber|L|Canopy Background Adjustment| 0.000000|None| 1.000000 +ParameterNumber|CBLUE|Aerosol Resistance Coefficient (Blue)| 0.000000|None| 7.500000 +ParameterNumber|CRED|Aerosol Resistance Coefficient (Red)| 0.000000|None| 6.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/EnumerateTableField.txt b/python/plugins/processing/algs/saga/description/EnumerateTableField.txt new file mode 100644 index 00000000000..03231b6cab0 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/EnumerateTableField.txt @@ -0,0 +1,5 @@ +Enumerate Table Field +table_tools +ParameterTable|INPUT|Input|False +ParameterTable|FIELD|Attribute|False +ParameterTable|OUTPUT|Output|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FieldStatistics.txt b/python/plugins/processing/algs/saga/description/FieldStatistics.txt new file mode 100644 index 00000000000..9ba4f3d60ab --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FieldStatistics.txt @@ -0,0 +1,5 @@ +Field Statistics +table_calculus +ParameterTable|TABLE|Table|False +ParameterTable|FIELDS|Attributes|False +ParameterTable|STATISTICS|Statistics|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FindFieldofExtremeValue.txt b/python/plugins/processing/algs/saga/description/FindFieldofExtremeValue.txt new file mode 100644 index 00000000000..cba0a969f48 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FindFieldofExtremeValue.txt @@ -0,0 +1,9 @@ +Find Field of Extreme Value +table_calculus +ParameterTable|INPUT|Input|False +ParameterTable|FIELDS|Attributes|False +ParameterTable|EXTREME_ID|Attribute|False +ParameterTable|EXTREME_VALUE|Value|False +ParameterTable|OUTPUT|Output|True +ParameterSelection|TYPE|TYPE|[0] minimum;[1] maximum| 1 +ParameterSelection|IDENTIFY|Attribute Identification|[0] name;[1] index| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FourierFilter(ViGrA).txt b/python/plugins/processing/algs/saga/description/FourierFilter(ViGrA).txt new file mode 100644 index 00000000000..1c062d06172 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FourierFilter(ViGrA).txt @@ -0,0 +1,9 @@ +Fourier Filter (ViGrA) +imagery_vigra +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Output +ParameterNumber|SCALE|Size of smoothing filter| 0.000000|None| 2.000000 +ParameterNumber|POWER|Power|None|None| 0.500000 +ParameterNumber|RANGE_MIN|Range (Min)|None|None|None +ParameterNumber|RANGE_MAX|Range (Max)|None|None|None +ParameterSelection|FILTER|Filter|[0] gaussian;[1] power of distance;[2] include range;[3] exclude range| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FourierTransform(RealViGrA).txt b/python/plugins/processing/algs/saga/description/FourierTransform(RealViGrA).txt new file mode 100644 index 00000000000..cea405f5dcf --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FourierTransform(RealViGrA).txt @@ -0,0 +1,4 @@ +Fourier Transform (Real, ViGrA) +imagery_vigra +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Output \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FourierTransform(ViGrA).txt b/python/plugins/processing/algs/saga/description/FourierTransform(ViGrA).txt new file mode 100644 index 00000000000..368f152bf3c --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FourierTransform(ViGrA).txt @@ -0,0 +1,6 @@ +Fourier Transform (ViGrA) +imagery_vigra +ParameterRaster|INPUT|Input|False +OutputRaster|REAL|Real +OutputRaster|IMAG|Imaginary +ParameterBoolean|CENTER|Centered|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FourierTransformInverse(ViGrA).txt b/python/plugins/processing/algs/saga/description/FourierTransformInverse(ViGrA).txt new file mode 100644 index 00000000000..4c93e017098 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FourierTransformInverse(ViGrA).txt @@ -0,0 +1,6 @@ +Fourier Transform Inverse (ViGrA) +imagery_vigra +ParameterRaster|REAL|Real|False +ParameterRaster|IMAG|Imaginary|False +OutputRaster|OUTPUT|Output +ParameterBoolean|CENTER|Centered|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FourierTransformation(OpenCV).txt b/python/plugins/processing/algs/saga/description/FourierTransformation(OpenCV).txt new file mode 100644 index 00000000000..15a9526e303 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FourierTransformation(OpenCV).txt @@ -0,0 +1,5 @@ +Fourier Transformation (OpenCV) +imagery_opencv +ParameterRaster|INPUT|Input|False +OutputRaster|REAL|Fourier Transformation (Real) +OutputRaster|IMAG|Fourier Transformation (Imaginary) \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FunctionFit.txt b/python/plugins/processing/algs/saga/description/FunctionFit.txt new file mode 100644 index 00000000000..91f68ecb7bd --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FunctionFit.txt @@ -0,0 +1,9 @@ +Function Fit +table_calculus +ParameterTable|SOURCE|Source|False +ParameterTable|YFIELD|y - Values|False +ParameterSelection|USE_X|Use x -Values|[0] No;[1] Yes| 0 +ParameterTable|XFIELD|x - Values|False +ParameterString|FORMEL|Formula +ParameterNumber|ITER|Iterationen| 1|None| 1000 +ParameterNumber|LAMDA|Max Lamda| 1.000000|None| 10000.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/FuzzyLandformElementClassification.txt b/python/plugins/processing/algs/saga/description/FuzzyLandformElementClassification.txt new file mode 100644 index 00000000000..e9c9532924f --- /dev/null +++ b/python/plugins/processing/algs/saga/description/FuzzyLandformElementClassification.txt @@ -0,0 +1,31 @@ +Fuzzy Landform Element Classification +ta_morphometry +ParameterRaster|SLOPE|Slope|False +ParameterRaster|MINCURV|Minimum Curvature|False +ParameterRaster|MAXCURV|Maximum Curvature|False +ParameterRaster|PCURV|Profile Curvature|False +ParameterRaster|TCURV|Tangential Curvature|False +OutputRaster|PLAIN|Plain +OutputRaster|PIT|Pit +OutputRaster|PEAK|Peak +OutputRaster|RIDGE|Ridge +OutputRaster|CHANNEL|Channel +OutputRaster|SADDLE|Saddle +OutputRaster|BSLOPE|Back Slope +OutputRaster|FSLOPE|Foot Slope +OutputRaster|SSLOPE|Shoulder Slope +OutputRaster|HOLLOW|Hollow +OutputRaster|FHOLLOW|Foot Hollow +OutputRaster|SHOLLOW|Shoulder Hollow +OutputRaster|SPUR|Spur +OutputRaster|FSPUR|Foot Spur +OutputRaster|SSPUR|Shoulder Spur +OutputRaster|FORM|Landform +OutputRaster|MEM|Maximum Membership +OutputRaster|ENTROPY|Entropy +OutputRaster|CI|Confusion Index +ParameterSelection|SLOPETODEG|Slope Grid Units|[0] degree;[1] radians| 0 +ParameterNumber|T_SLOPE_MIN|Slope Thresholds [Degree] (Min)|None|None|None +ParameterNumber|T_SLOPE_MAX|Slope Thresholds [Degree] (Max)|None|None|None +ParameterNumber|T_CURVE_MIN|Curvature Thresholds [1 / m] (Min)|None|None|None +ParameterNumber|T_CURVE_MAX|Curvature Thresholds [1 / m] (Max)|None|None|None \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GWRforMultiplePredictors.txt b/python/plugins/processing/algs/saga/description/GWRforMultiplePredictors.txt new file mode 100644 index 00000000000..4f315ed0ae9 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GWRforMultiplePredictors.txt @@ -0,0 +1,16 @@ +GWR for Multiple Predictors +statistics_regression +ParameterVector|POINTS|Points|0|False +ParameterTable|DEPENDENT|Dependent Variable|False +ParameterTable|PREDICTORS|Predictors|False +OutputVector|REGRESSION|Regression +ParameterSelection|DW_WEIGHTING|Weighting Function|[0] no distance weighting;[1] inverse distance to a power;[2] exponential;[3] gaussian weighting| 3 +ParameterNumber|DW_IDW_POWER|Inverse Distance Weighting Power| 0.000000|None| 1.000000 +ParameterBoolean|DW_IDW_OFFSET|Inverse Distance Offset|True +ParameterNumber|DW_BANDWIDTH|Gaussian and Exponential Weighting Bandwidth| 0.000000|None| 1.000000 +ParameterSelection|SEARCH_RANGE|Search Range|[0] local;[1] global| 0 +ParameterNumber|SEARCH_RADIUS|Maximum Search Distance| 0.000000|None| 1000.000000 +ParameterSelection|SEARCH_POINTS_ALL|Number of Points|[0] maximum number of nearest points;[1] all points within search distance| 0 +ParameterNumber|SEARCH_POINTS_MIN|Minimum| 1|None| 16 +ParameterNumber|SEARCH_POINTS_MAX|Maximum| 1|None| 20 +ParameterSelection|SEARCH_DIRECTION|Direction|[0] all directions;[1] quadrants| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GWRforSinglePredictor(GriddedModelOutput).txt b/python/plugins/processing/algs/saga/description/GWRforSinglePredictor(GriddedModelOutput).txt new file mode 100644 index 00000000000..40351eb10d3 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GWRforSinglePredictor(GriddedModelOutput).txt @@ -0,0 +1,27 @@ +GWR for Single Predictor (Gridded Model Output) +statistics_regression +ParameterVector|POINTS|Points|0|False +ParameterTable|DEPENDENT|Dependent Variable|False +ParameterTable|PREDICTOR|Predictor|False +ParameterSelection|TARGET_DEFINITION|Target Grid System|[0] user defined;[1] grid or grid system| 0 +ParameterNumber|TARGET_USER_SIZE|Cellsize| 0.000000|None| 1.000000 +ParameterNumber|TARGET_USER_XMIN|Left|None|None| 0.000000 +ParameterNumber|TARGET_USER_XMAX|Right|None|None| 100.000000 +ParameterNumber|TARGET_USER_YMIN|Bottom|None|None| 0.000000 +ParameterNumber|TARGET_USER_YMAX|Top|None|None| 100.000000 +ParameterSelection|TARGET_USER_FITS|Fit|[0] nodes;[1] cells| 0 +ParameterRaster|TARGET_TEMPLATE|Target System|True +OutputRaster|TARGET_OUT_GRID|Target Grid +OutputRaster|INTERCEPT|Intercept +OutputRaster|SLOPE|Slope +OutputRaster|QUALITY|Quality +ParameterSelection|DW_WEIGHTING|Weighting Function|[0] no distance weighting;[1] inverse distance to a power;[2] exponential;[3] gaussian weighting| 3 +ParameterNumber|DW_IDW_POWER|Inverse Distance Weighting Power| 0.000000|None| 1.000000 +ParameterBoolean|DW_IDW_OFFSET|Inverse Distance Offset|True +ParameterNumber|DW_BANDWIDTH|Gaussian and Exponential Weighting Bandwidth| 0.000000|None| 1.000000 +ParameterSelection|SEARCH_RANGE|Search Range|[0] local;[1] global| 0 +ParameterNumber|SEARCH_RADIUS|Maximum Search Distance| 0.000000|None| 1000.000000 +ParameterSelection|SEARCH_POINTS_ALL|Number of Points|[0] maximum number of nearest points;[1] all points within search distance| 0 +ParameterNumber|SEARCH_POINTS_MIN|Minimum| 1|None| 16 +ParameterNumber|SEARCH_POINTS_MAX|Maximum| 1|None| 20 +ParameterSelection|SEARCH_DIRECTION|Direction|[0] all directions;[1] quadrants| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GWRforSinglePredictorGrid.txt b/python/plugins/processing/algs/saga/description/GWRforSinglePredictorGrid.txt new file mode 100644 index 00000000000..0689e944693 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GWRforSinglePredictorGrid.txt @@ -0,0 +1,20 @@ +GWR for Single Predictor Grid +statistics_regression +ParameterVector|POINTS|Points|0|False +ParameterTable|DEPENDENT|Dependent Variable|False +OutputVector|RESIDUALS|Residuals +ParameterRaster|PREDICTOR|Predictor|False +OutputRaster|REGRESSION|Regression +OutputRaster|QUALITY|Coefficient of Determination +OutputRaster|INTERCEPT|Intercept +OutputRaster|SLOPE|Slope +ParameterSelection|DW_WEIGHTING|Weighting Function|[0] no distance weighting;[1] inverse distance to a power;[2] exponential;[3] gaussian weighting| 3 +ParameterNumber|DW_IDW_POWER|Inverse Distance Weighting Power| 0.000000|None| 1.000000 +ParameterBoolean|DW_IDW_OFFSET|Inverse Distance Offset|True +ParameterNumber|DW_BANDWIDTH|Gaussian and Exponential Weighting Bandwidth| 0.000000|None| 1.000000 +ParameterSelection|SEARCH_RANGE|Search Range|[0] local;[1] global| 0 +ParameterNumber|SEARCH_RADIUS|Maximum Search Distance| 0.000000|None| 1000.000000 +ParameterSelection|SEARCH_POINTS_ALL|Number of Points|[0] maximum number of nearest points;[1] all points within search distance| 0 +ParameterNumber|SEARCH_POINTS_MIN|Minimum| 1|None| 16 +ParameterNumber|SEARCH_POINTS_MAX|Maximum| 1|None| 20 +ParameterSelection|SEARCH_DIRECTION|Direction|[0] all directions;[1] quadrants| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GenerateShapes.txt b/python/plugins/processing/algs/saga/description/GenerateShapes.txt new file mode 100644 index 00000000000..7ea1bf0a49a --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GenerateShapes.txt @@ -0,0 +1,8 @@ +Generate Shapes +shapes_tools +ParameterTable|INPUT|Input|False +ParameterTable|FIELD_ID|ID|False +ParameterTable|FIELD_X|X|False +ParameterTable|FIELD_Y|Y|False +ParameterSelection|SHAPE_TYPE|Shape Type|[0] Point(s);[1] Line(s);[2] Polygon(s)| 0 +OutputVector|OUTPUT|Output \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GeodesicMorphologicalReconstruction.txt b/python/plugins/processing/algs/saga/description/GeodesicMorphologicalReconstruction.txt new file mode 100644 index 00000000000..c59db3d7c5c --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GeodesicMorphologicalReconstruction.txt @@ -0,0 +1,9 @@ +Geodesic Morphological Reconstruction +grid_filter +ParameterRaster|INPUT_GRID|Input Grid|False +OutputRaster|OBJECT_GRID|Object Grid +OutputRaster|DIFFERENCE_GRID|Difference Input - Reconstruction +ParameterNumber|SHIFT_VALUE|Shift value|None|None| 5.000000 +ParameterBoolean|BORDER_YES_NO|Preserve 1px border Yes/No|True +ParameterBoolean|BIN_YES_NO|Create a binary mask Yes/No|True +ParameterNumber|THRESHOLD|Threshold|None|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GeographicCoordinateGrids.txt b/python/plugins/processing/algs/saga/description/GeographicCoordinateGrids.txt new file mode 100644 index 00000000000..1b562120c61 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GeographicCoordinateGrids.txt @@ -0,0 +1,5 @@ +Geographic Coordinate Grids +pj_proj4 +ParameterRaster|GRID|Grid|False +OutputRaster|LON|Longitude +OutputRaster|LAT|Latitude \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GridCombination.txt b/python/plugins/processing/algs/saga/description/GridCombination.txt new file mode 100644 index 00000000000..93c4bfe1b11 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GridCombination.txt @@ -0,0 +1,12 @@ +GridCombination +sim_rivflow +ParameterRaster|INPUT|Gelaendemodell (DTM)|False +ParameterFile|Folder1|Pfad WaterGap Raster|False|False +ParameterNumber|sY|Start-Jahr| 1906| 2000| 1990 +ParameterNumber|eY|End-Jahr| 1906| 2000| 1990 +ParameterBoolean|DomW|Domestic Water|True +ParameterBoolean|ElecW|Electricity Water|True +ParameterBoolean|LiveW|Livestock Water|True +ParameterBoolean|ManW|Manufacturing Water|True +ParameterBoolean|IrrW|Irrigation Water|True +ParameterSelection|FvA|Flaechenverbrauch-Auswahl (FvA)|[0] Resultierendes Raster ueber WasserENTNAHME erstellen;[1] Resultierendes Raster ueber WasserNUTZUNG erstellen| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/GridStatisticsforPoints.txt b/python/plugins/processing/algs/saga/description/GridStatisticsforPoints.txt new file mode 100644 index 00000000000..f0355fe89ea --- /dev/null +++ b/python/plugins/processing/algs/saga/description/GridStatisticsforPoints.txt @@ -0,0 +1,17 @@ +Grid Statistics for Points +shapes_grid +ParameterMultipleInput|GRIDS|Grids|3|False +ParameterVector|POINTS|Points|0|False +ParameterSelection|KERNEL_TYPE|Kernel Type|[0] square;[1] circle| 0 +ParameterNumber|KERNEL_SIZE|Kernel Size| 1|None| 1 +ParameterSelection|NAMING|Field Naming|[0] grid number;[1] grid name| 1 +OutputVector|RESULT|Statistics +ParameterBoolean|COUNT|Number of Cells|True +ParameterBoolean|MIN|Minimum|True +ParameterBoolean|MAX|Maximum|True +ParameterBoolean|RANGE|Range|True +ParameterBoolean|SUM|Sum|True +ParameterBoolean|MEAN|Mean|True +ParameterBoolean|VAR|Variance|True +ParameterBoolean|STDDEV|Standard Deviation|True +ParameterNumber|QUANTILE|Quantile| 0| 50| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/IMCORR-FeatureTracking.txt b/python/plugins/processing/algs/saga/description/IMCORR-FeatureTracking.txt new file mode 100644 index 00000000000..a00cb8f8c51 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/IMCORR-FeatureTracking.txt @@ -0,0 +1,11 @@ +IMCORR - Feature Tracking +grid_analysis +ParameterRaster|GRID_1|Grid 1|False +ParameterRaster|GRID_2|Grid 2|False +ParameterRaster|DTM_1|DTM 1|True +ParameterRaster|DTM_2|DTM 2|True +OutputVector|CORRPOINTS|Correlated Points +OutputVector|CORRLINES|Displacement Vector +ParameterSelection|SEARCH_CHIPSIZE|Search Chip Size (Cells)|[0] 16x16;[1] 32x32;[2] 64x64;[3] 128x128;[4] 256x256| 2 +ParameterSelection|REF_CHIPSIZE|Reference Chip Size (Cells)|[0] 16x16;[1] 32x32;[2] 64x64;[3] 128x128| 1 +ParameterNumber|GRID_SPACING|Grid Spacing (Map Units)| 0.100000| 256.000000| 10.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ISODATAClusteringforGrids.txt b/python/plugins/processing/algs/saga/description/ISODATAClusteringforGrids.txt new file mode 100644 index 00000000000..190539d47f3 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ISODATAClusteringforGrids.txt @@ -0,0 +1,10 @@ +ISODATA Clustering for Grids +imagery_classification +ParameterMultipleInput|FEATURES|Features|3|False +OutputRaster|CLUSTER|Clusters +ParameterTable|STATISTICS|Statistics|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterNumber|ITERATIONS|Maximum Number of Iterations| 3|None| 20 +ParameterNumber|CLUSTER_INI|Initial Number of Clusters| 0|None| 5 +ParameterNumber|CLUSTER_MAX|Maximum Number of Clusters| 3|None| 16 +ParameterNumber|SAMPLES_MIN|Minimum Number of Samples in a Cluster| 2|None| 5 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Identity.txt b/python/plugins/processing/algs/saga/description/Identity.txt new file mode 100644 index 00000000000..a9f37b4ed19 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Identity.txt @@ -0,0 +1,6 @@ +Identity +shapes_polygons +ParameterVector|A|Layer A|-1|False +ParameterVector|B|Layer B|-1|False +OutputVector|RESULT|Identity +ParameterBoolean|SPLIT|Split Parts|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Intersect.txt b/python/plugins/processing/algs/saga/description/Intersect.txt new file mode 100644 index 00000000000..44cbf417a66 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Intersect.txt @@ -0,0 +1,6 @@ +Intersect +shapes_polygons +ParameterVector|A|Layer A|-1|False +ParameterVector|B|Layer B|-1|False +OutputVector|RESULT|Intersect +ParameterBoolean|SPLIT|Split Parts|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/InvertGrid.txt b/python/plugins/processing/algs/saga/description/InvertGrid.txt new file mode 100644 index 00000000000..8132386ff3e --- /dev/null +++ b/python/plugins/processing/algs/saga/description/InvertGrid.txt @@ -0,0 +1,4 @@ +Invert Grid +grid_tools +ParameterRaster|GRID|Grid|False +OutputRaster|INVERSE|Inverse Grid \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/K-MeansClusteringforGrids.txt b/python/plugins/processing/algs/saga/description/K-MeansClusteringforGrids.txt new file mode 100644 index 00000000000..b1bee557e07 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/K-MeansClusteringforGrids.txt @@ -0,0 +1,11 @@ +K-Means Clustering for Grids +imagery_classification +ParameterMultipleInput|GRIDS|Grids|3|False +OutputRaster|CLUSTER|Clusters +ParameterTable|STATISTICS|Statistics|False +ParameterSelection|METHOD|Method|[0] Iterative Minimum Distance (Forgy 1965);[1] Hill-Climbing (Rubin 1967);[2] Combined Minimum Distance / Hillclimbing| 1 +ParameterNumber|NCLUSTER|Clusters| 2|None| 10 +ParameterNumber|MAXITER|Maximum Iterations| 0|None| 0 +ParameterBoolean|NORMALISE|Normalise|False +ParameterBoolean|OLDVERSION|Old Version|False +ParameterBoolean|UPDATEVIEW|Update View|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/K-NearestNeighboursClassification(OpenCV).txt b/python/plugins/processing/algs/saga/description/K-NearestNeighboursClassification(OpenCV).txt new file mode 100644 index 00000000000..ef2f972d02d --- /dev/null +++ b/python/plugins/processing/algs/saga/description/K-NearestNeighboursClassification(OpenCV).txt @@ -0,0 +1,11 @@ +K-Nearest Neighbours Classification (OpenCV) +imagery_opencv +ParameterMultipleInput|FEATURES|Features|3|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterVector|TRAIN_AREAS|Training Areas|-1|False +ParameterTable|TRAIN_CLASS|Class Identifier|False +OutputRaster|CLASSES|Classification +ParameterNumber|NEIGHBOURS|Default Number of Neighbours| 1|None| 3 +ParameterSelection|TRAINING|Training Method|[0] classification;[1] regression model| 0 +ParameterSelection|ALGORITHM|Algorithm Type|[0] brute force;[1] KD Tree| 0 +ParameterNumber|EMAX|Parameter for KD Tree implementation| 1|None| 1000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/LS-FactorFieldBased.txt b/python/plugins/processing/algs/saga/description/LS-FactorFieldBased.txt new file mode 100644 index 00000000000..9c59a439472 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/LS-FactorFieldBased.txt @@ -0,0 +1,16 @@ +LS-Factor, Field Based +ta_hydrology +ParameterRaster|DEM|Elevation|False +ParameterVector|FIELDS|Fields|-1|True +OutputVector|STATISTICS|Field Statistics +OutputRaster|UPSLOPE_AREA|Upslope Length Factor +OutputRaster|UPSLOPE_LENGTH|Effective Flow Length +OutputRaster|UPSLOPE_SLOPE|Upslope Slope +OutputRaster|LS_FACTOR|LS Factor +OutputRaster|BALANCE|Sediment Balance +ParameterSelection|METHOD|LS Calculation|[0] Moore & Nieber 1989;[1] Desmet & Govers 1996;[2] Wischmeier & Smith 1978| 0 +ParameterSelection|METHOD_SLOPE|Type of Slope|[0] local slope;[1] distance weighted average catchment slope| 0 +ParameterSelection|METHOD_AREA|Specific Catchment Area|[0] specific catchment area (contour length simply as cell size);[1] specific catchment area (contour length dependent on aspect);[2] catchment length (square root of catchment area);[3] effective flow length| 1 +ParameterBoolean|STOP_AT_EDGE|Stop at Edge|True +ParameterNumber|EROSIVITY|Rill/Interrill Erosivity| 0.000000|None| 1.000000 +ParameterSelection|STABILITY|Stability|[0] stable;[1] instable (thawing)| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/LandUseScenarioGenerator.txt b/python/plugins/processing/algs/saga/description/LandUseScenarioGenerator.txt new file mode 100644 index 00000000000..fb8042e5064 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/LandUseScenarioGenerator.txt @@ -0,0 +1,8 @@ +Land Use Scenario Generator +shapes_tools +ParameterVector|FIELDS|Fields|-1|False +ParameterTable|FIELD_ID|Field Identifier|False +OutputVector|SCENARIO|Land Use Scenario +ParameterSelection|OUTPUT|Output of...|[0] Identifier;[1] Name| 0 +ParameterTable|STATISTICS|Crop Statistics|False +ParameterTable|KNOWN_CROPS|Known Crops|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/LineSmoothing.txt b/python/plugins/processing/algs/saga/description/LineSmoothing.txt new file mode 100644 index 00000000000..3fd1e795b73 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/LineSmoothing.txt @@ -0,0 +1,9 @@ +Line Smoothing +shapes_lines +ParameterVector|LINES_IN|Lines|1|False +OutputVector|LINES_OUT|Smoothed Lines +ParameterSelection|METHOD|Method|[0] basic SIA model;[1] improved SIA model;[2] Gaussian Filtering| 2 +ParameterNumber|SENSITIVITY|Sensitivity| 1|None| 3 +ParameterNumber|ITERATIONS|Iterations| 1|None| 10 +ParameterNumber|PRESERVATION|Preservation| 1.000000|None| 10.000000 +ParameterNumber|SIGMA|Sigma| 0.500000|None| 2.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MaximumEntropyPresencePrediction.txt b/python/plugins/processing/algs/saga/description/MaximumEntropyPresencePrediction.txt new file mode 100644 index 00000000000..44eac6ed6c0 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MaximumEntropyPresencePrediction.txt @@ -0,0 +1,18 @@ +Maximum Entropy Presence Prediction +imagery_maxent +ParameterVector|PRESENCE|Presence Data|-1|False +ParameterMultipleInput|FEATURES_NUM|Numerical Features|3|True +ParameterMultipleInput|FEATURES_CAT|Categorical Features|3|True +OutputRaster|PREDICTION|Presence Prediction +OutputRaster|PROBABILITY|Presence Probability +ParameterNumber|BACKGROUND|Background Sample Density [Percent]| 0.000000| 100.000000| 1.000000 +ParameterSelection|METHOD|Method|[0] Yoshimasa Tsuruoka;[1] Dekang Lin| 0 +ParameterFile|YT_FILE_LOAD|Load from File...|False|False +ParameterFile|YT_FILE_SAVE|Save to File...|False|False +ParameterSelection|YT_REGUL|Regularization|[0] none;[1] L1;[2] L2| 1 +ParameterNumber|YT_REGUL_VAL|Regularization Factor| 0.000000|None| 1.000000 +ParameterBoolean|YT_NUMASREAL|Real-valued Numerical Features|True +ParameterNumber|DL_ALPHA|Alpha|None|None| 0.100000 +ParameterNumber|DL_THRESHOLD|Threshold| 0.000000|None| 0.000000 +ParameterNumber|DL_ITERATIONS|Maximum Iterations| 1|None| 100 +ParameterNumber|NUM_CLASSES|Number of Numeric Value Classes| 1|None| 32 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MaximumFlowPathLength.txt b/python/plugins/processing/algs/saga/description/MaximumFlowPathLength.txt new file mode 100644 index 00000000000..d515f146830 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MaximumFlowPathLength.txt @@ -0,0 +1,6 @@ +Maximum Flow Path Length +ta_hydrology +ParameterRaster|ELEVATION|Elevation|False +ParameterRaster|WEIGHTS|Weights|True +OutputRaster|DISTANCE|Flow Path Length +ParameterSelection|DIRECTION|Direction of Measurement|[0] downstream;[1] upstream| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MeltonRuggednessNumber.txt b/python/plugins/processing/algs/saga/description/MeltonRuggednessNumber.txt new file mode 100644 index 00000000000..397662a33d8 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MeltonRuggednessNumber.txt @@ -0,0 +1,6 @@ +Melton Ruggedness Number +ta_hydrology +ParameterRaster|DEM|Elevation|False +OutputRaster|AREA|Catchment Area +OutputRaster|ZMAX|Maximum Height +OutputRaster|MRN|Melton Ruggedness Number \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MeshDenoise.txt b/python/plugins/processing/algs/saga/description/MeshDenoise.txt new file mode 100644 index 00000000000..7a2e014c6ea --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MeshDenoise.txt @@ -0,0 +1,9 @@ +Mesh Denoise +grid_filter +ParameterRaster|INPUT|Grid|False +OutputRaster|OUTPUT|Denoised Grid +ParameterNumber|SIGMA|Threshold| 0.000000| 1.000000| 0.900000 +ParameterNumber|ITER|Number of Iterations for Normal Updating| 1|None| 5 +ParameterNumber|VITER|Number of Iterations for Vertex Updating| 1|None| 50 +ParameterSelection|NB_CV|Common Edge Type of Face Neighbourhood|[0] Common Vertex;[1] Common Edge| 0 +ParameterBoolean|ZONLY|Only Z-Direction Position is Updated|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MirrorGrid.txt b/python/plugins/processing/algs/saga/description/MirrorGrid.txt new file mode 100644 index 00000000000..7a284fba6ae --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MirrorGrid.txt @@ -0,0 +1,5 @@ +Mirror Grid +grid_tools +ParameterRaster|GRID|Grid|False +OutputRaster|MIRROR|Mirror Grid +ParameterSelection|METHOD|Method|[0] horizontally;[1] vertically;[2] both| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ModifedQuadraticShepard.txt b/python/plugins/processing/algs/saga/description/ModifedQuadraticShepard.txt new file mode 100644 index 00000000000..f61ade9f30f --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ModifedQuadraticShepard.txt @@ -0,0 +1,12 @@ +Modifed Quadratic Shepard +grid_gridding +ParameterVector|SHAPES|Points|-1|False +ParameterTable|FIELD|Attribute|False +Hardcoded|-TARGET_DEFINITION 0 +Extent TARGET_USER_XMIN TARGET_USER_XMAX TARGET_USER_YMIN TARGET_USER_YMAX +ParameterNumber|TARGET_USER_SIZE|Cellsize|0.000000|None|1.000000 +ParameterSelection|TARGET_USER_FITS|Fit|[0] nodes;[1] cells| 0 +ParameterRaster|TARGET_TEMPLATE|Target System|True +OutputRaster|TARGET_OUT_GRID|Target Grid +ParameterNumber|QUADRATIC_NEIGHBORS|Quadratic Neighbors| 5|None| 13 +ParameterNumber|WEIGHTING_NEIGHBORS|Weighting Neighbors| 3|None| 19 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MonthlyGlobalbyLatitude.txt b/python/plugins/processing/algs/saga/description/MonthlyGlobalbyLatitude.txt new file mode 100644 index 00000000000..f93cdbb34c9 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MonthlyGlobalbyLatitude.txt @@ -0,0 +1,7 @@ +Monthly Global by Latitude +climate_tools +ParameterTable|SOLARRAD|Solar Radiation|False +ParameterTable|ALBEDO|Albedo|True +ParameterTable|FIELD|Field|False +ParameterNumber|YEAR|Year [ka]|None|None| 2.000000 +ParameterNumber|DLAT|Latitude Increment [Degree]| 1| 90| 5 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MorphologicalFilter(OpenCV).txt b/python/plugins/processing/algs/saga/description/MorphologicalFilter(OpenCV).txt new file mode 100644 index 00000000000..aae6573ba52 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MorphologicalFilter(OpenCV).txt @@ -0,0 +1,8 @@ +Morphological Filter (OpenCV) +imagery_opencv +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Output +ParameterSelection|TYPE|Operation|[0] dilation;[1] erosion;[2] opening;[3] closing;[4] morpological gradient;[5] top hat;[6] black hat| 0 +ParameterSelection|SHAPE|Element Shape|[0] ellipse;[1] rectangle;[2] cross| 0 +ParameterNumber|RADIUS|Radius (cells)| 0|None| 1 +ParameterNumber|ITERATIONS|Iterations| 1|None| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MorphologicalFilter(ViGrA).txt b/python/plugins/processing/algs/saga/description/MorphologicalFilter(ViGrA).txt new file mode 100644 index 00000000000..a0bb3b0fe72 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MorphologicalFilter(ViGrA).txt @@ -0,0 +1,8 @@ +Morphological Filter (ViGrA) +imagery_vigra +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Output +ParameterSelection|TYPE|Operation|[0] Dilation;[1] Erosion;[2] Median;[3] User defined rank| 0 +ParameterNumber|RADIUS|Radius (cells)| 0|None| 1 +ParameterNumber|RANK|User defined rank| 0.000000| 1.000000| 0.500000 +ParameterBoolean|RESCALE|Rescale Values (0-255)|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MorphometricFeatures.txt b/python/plugins/processing/algs/saga/description/MorphometricFeatures.txt new file mode 100644 index 00000000000..54fe3c139df --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MorphometricFeatures.txt @@ -0,0 +1,19 @@ +Morphometric Features +ta_morphometry +ParameterRaster|DEM|Elevation|False +OutputRaster|FEATURES|Morphometric Features +OutputRaster|ELEVATION|Generalized Surface +OutputRaster|SLOPE|Slope +OutputRaster|ASPECT|Aspect +OutputRaster|PROFC|Profile Curvature +OutputRaster|PLANC|Plan Curvature +OutputRaster|LONGC|Longitudinal Curvature +OutputRaster|CROSC|Cross-Sectional Curvature +OutputRaster|MAXIC|Maximum Curvature +OutputRaster|MINIC|Minimum Curvature +ParameterNumber|SIZE|Scale Radius (Cells)| 1|None| 5 +ParameterNumber|TOL_SLOPE|Slope Tolerance|None|None| 1.000000 +ParameterNumber|TOL_CURVE|Curvature Tolerance|None|None| 0.000100 +ParameterNumber|EXPONENT|Distance Weighting Exponent| 0.000000| 4.000000| 0.000000 +ParameterNumber|ZSCALE|Vertical Scaling|None|None| 1.000000 +ParameterBoolean|CONSTRAIN|Constrain|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis(Shapes).txt b/python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis(Shapes).txt new file mode 100644 index 00000000000..69ff5006f24 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis(Shapes).txt @@ -0,0 +1,12 @@ +Multiple Linear Regression Analysis (Shapes) +statistics_regression +ParameterVector|TABLE|Shapes|-1|False +OutputVector|RESULTS|Results +ParameterTable|DEPENDENT|Dependent Variable|False +ParameterTable|INFO_COEFF|Details: Coefficients|True +ParameterTable|INFO_MODEL|Details: Model|True +ParameterTable|INFO_STEPS|Details: Steps|True +ParameterSelection|METHOD|Method|[0] include all;[1] forward;[2] backward;[3] stepwise| 3 +ParameterNumber|P_VALUE|Significance Level| 0.000000| 100.000000| 5.000000 +ParameterSelection|CROSSVAL|Cross Validation|[0] none;[1] leave one out;[2] 2-fold;[3] k-fold| 0 +ParameterNumber|CROSSVAL_K|Cross Validation Subsamples| 2|None| 10 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis.txt b/python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis.txt new file mode 100644 index 00000000000..157ec8f6be7 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MultipleLinearRegressionAnalysis.txt @@ -0,0 +1,12 @@ +Multiple Linear Regression Analysis +statistics_regression +ParameterTable|TABLE|Table|False +ParameterTable|RESULTS|Results|True +ParameterTable|DEPENDENT|Dependent Variable|False +ParameterTable|INFO_COEFF|Details: Coefficients|True +ParameterTable|INFO_MODEL|Details: Model|True +ParameterTable|INFO_STEPS|Details: Steps|True +ParameterSelection|METHOD|Method|[0] include all;[1] forward;[2] backward;[3] stepwise| 3 +ParameterNumber|P_VALUE|Significance Level| 0.000000| 100.000000| 5.000000 +ParameterSelection|CROSSVAL|Cross Validation|[0] none;[1] leave one out;[2] 2-fold;[3] k-fold| 0 +ParameterNumber|CROSSVAL_K|Cross Validation Subsamples| 2|None| 10 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(GridandPredictorGrids).txt b/python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(GridandPredictorGrids).txt new file mode 100644 index 00000000000..f837ab9ac16 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(GridandPredictorGrids).txt @@ -0,0 +1,16 @@ +Multiple Regression Analysis (Grid and Predictor Grids) +statistics_regression +ParameterRaster|DEPENDENT|Dependent Variable|False +ParameterMultipleInput|PREDICTORS|Predictors|3|False +OutputRaster|REGRESSION|Regression +OutputRaster|RESIDUALS|Residuals +ParameterTable|INFO_COEFF|Details: Coefficients|True +ParameterTable|INFO_MODEL|Details: Model|True +ParameterTable|INFO_STEPS|Details: Steps|True +ParameterSelection|RESAMPLING|Resampling|[0] Nearest Neighbour;[1] Bilinear Interpolation;[2] Bicubic Spline Interpolation;[3] B-Spline Interpolation| 3 +ParameterBoolean|COORD_X|Include X Coordinate|False +ParameterBoolean|COORD_Y|Include Y Coordinate|False +ParameterSelection|METHOD|Method|[0] include all;[1] forward;[2] backward;[3] stepwise| 3 +ParameterNumber|P_VALUE|Significance Level| 0.000000| 100.000000| 5.000000 +ParameterSelection|CROSSVAL|Cross Validation|[0] none;[1] leave one out;[2] 2-fold;[3] k-fold| 0 +ParameterNumber|CROSSVAL_K|Cross Validation Subsamples| 2|None| 10 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(PointsandPredictorGrids).txt b/python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(PointsandPredictorGrids).txt new file mode 100644 index 00000000000..8dd6acb04fb --- /dev/null +++ b/python/plugins/processing/algs/saga/description/MultipleRegressionAnalysis(PointsandPredictorGrids).txt @@ -0,0 +1,20 @@ +Multiple Regression Analysis (Points and Predictor Grids) +statistics_regression +ParameterMultipleInput|PREDICTORS|Predictors|3|False +ParameterVector|POINTS|Points|-1|False +ParameterTable|ATTRIBUTE|Dependent Variable|False +ParameterTable|INFO_COEFF|Details: Coefficients|True +ParameterTable|INFO_MODEL|Details: Model|True +ParameterTable|INFO_STEPS|Details: Steps|True +OutputVector|RESIDUALS|Residuals +OutputRaster|REGRESSION|Regression +OutputRaster|REGRESCORR|Regression with Residual Correction +ParameterSelection|RESAMPLING|Resampling|[0] Nearest Neighbour;[1] Bilinear Interpolation;[2] Bicubic Spline Interpolation;[3] B-Spline Interpolation| 3 +ParameterBoolean|COORD_X|Include X Coordinate|False +ParameterBoolean|COORD_Y|Include Y Coordinate|False +ParameterBoolean|INTERCEPT|Intercept|True +ParameterSelection|METHOD|Method|[0] include all;[1] forward;[2] backward;[3] stepwise| 3 +ParameterNumber|P_VALUE|Significance Level| 0.000000| 100.000000| 5.000000 +ParameterSelection|CROSSVAL|Cross Validation|[0] none;[1] leave one out;[2] 2-fold;[3] k-fold| 0 +ParameterNumber|CROSSVAL_K|Cross Validation Subsamples| 2|None| 10 +ParameterSelection|RESIDUAL_COR|Residual Interpolation|[0] Multleve B-Spline Interpolation;[1] Inverse Distance Weighted| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/NormalBayesClassification(OpenCV).txt b/python/plugins/processing/algs/saga/description/NormalBayesClassification(OpenCV).txt new file mode 100644 index 00000000000..4541874996e --- /dev/null +++ b/python/plugins/processing/algs/saga/description/NormalBayesClassification(OpenCV).txt @@ -0,0 +1,8 @@ +Normal Bayes Classification (OpenCV) +imagery_opencv +ParameterMultipleInput|FEATURES|Features|3|False +ParameterBoolean|NORMALIZE|Normalize|False +OutputRaster|PROBABILITY|Probability +ParameterVector|TRAIN_AREAS|Training Areas|-1|False +ParameterTable|TRAIN_CLASS|Class Identifier|False +OutputRaster|CLASSES|Classification \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/PET(afterHargreavesGrid).txt b/python/plugins/processing/algs/saga/description/PET(afterHargreavesGrid).txt new file mode 100644 index 00000000000..26427f0a4b4 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/PET(afterHargreavesGrid).txt @@ -0,0 +1,10 @@ +PET (after Hargreaves, Grid) +climate_tools +ParameterRaster|T|Mean Temperature|False +ParameterRaster|T_MIN|Minimum Temperature|False +ParameterRaster|T_MAX|Maximum Temperature|False +OutputRaster|PET|Potential Evapotranspiration +ParameterNumber|LAT|Latitude [Degree]| -90.000000| 90.000000| 53.000000 +ParameterSelection|TIME|Time|[0] day;[1] month| 0 +ParameterSelection|MONTH|Month|[0] January;[1] February;[2] March;[3] April;[4] May;[5] June;[6] July;[7] August;[8] September;[9] October;[10] November;[11] December| 5 +ParameterNumber|DAY|Day of Month| 1| 31| 30 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/PET(afterHargreavesTable).txt b/python/plugins/processing/algs/saga/description/PET(afterHargreavesTable).txt new file mode 100644 index 00000000000..5ec8e76ff58 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/PET(afterHargreavesTable).txt @@ -0,0 +1,8 @@ +PET (after Hargreaves, Table) +climate_tools +ParameterTable|TABLE|Data|False +ParameterTable|JD|Julian Day|False +ParameterTable|T|Mean Temperature|False +ParameterTable|T_MIN|Minimum Temperature|False +ParameterTable|T_MAX|Maximum Temperature|False +ParameterNumber|LAT|Latitude| -90.000000| 90.000000| 53.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/PointDistances.txt b/python/plugins/processing/algs/saga/description/PointDistances.txt new file mode 100644 index 00000000000..ed9c0717131 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/PointDistances.txt @@ -0,0 +1,9 @@ +Point Distances +shapes_points +ParameterVector|POINTS|Points|0|False +ParameterTable|ID_POINTS|Identifier|False +ParameterVector|NEAR|Near Points|-1|True +ParameterTable|ID_NEAR|Identifier|False +ParameterTable|DISTANCES|Distances|False +ParameterSelection|FORMAT|Output Format|[0] complete input times near points matrix;[1] each pair with a single record| 1 +ParameterNumber|MAX_DIST|Maximum Distance| 0.000000|None| 0.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/PolygonSelf-Intersection.txt b/python/plugins/processing/algs/saga/description/PolygonSelf-Intersection.txt new file mode 100644 index 00000000000..4ed096c24f3 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/PolygonSelf-Intersection.txt @@ -0,0 +1,5 @@ +Polygon Self-Intersection +shapes_polygons +ParameterVector|POLYGONS|Polygons|-1|False +ParameterTable|ID|Identifier|False +OutputVector|INTERSECT|Intersection \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/RandomForestClassification(OpenCV).txt b/python/plugins/processing/algs/saga/description/RandomForestClassification(OpenCV).txt new file mode 100644 index 00000000000..5a0c1a0d09b --- /dev/null +++ b/python/plugins/processing/algs/saga/description/RandomForestClassification(OpenCV).txt @@ -0,0 +1,14 @@ +Random Forest Classification (OpenCV) +imagery_opencv +ParameterMultipleInput|FEATURES|Features|3|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterVector|TRAIN_AREAS|Training Areas|-1|False +ParameterTable|TRAIN_CLASS|Class Identifier|False +OutputRaster|CLASSES|Classification +ParameterNumber|MAX_DEPTH|Maximum Tree Depth| 1|None| 10 +ParameterNumber|MIN_SAMPLES|Minimum Sample Count| 2|None| 2 +ParameterNumber|MAX_CATEGRS|Maximum Categories| 1|None| 10 +ParameterBoolean|1SE_RULE|Use 1SE Rule|True +ParameterBoolean|TRUNC_PRUNED|Truncate Pruned Trees|True +ParameterNumber|REG_ACCURACY|Regression Accuracy| 0.000000|None| 0.010000 +ParameterNumber|ACTIVE_VARS|Active Variable Count| 0|None| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/RandomForestPresencePrediction(ViGrA).txt b/python/plugins/processing/algs/saga/description/RandomForestPresencePrediction(ViGrA).txt new file mode 100644 index 00000000000..ec6bf747c6a --- /dev/null +++ b/python/plugins/processing/algs/saga/description/RandomForestPresencePrediction(ViGrA).txt @@ -0,0 +1,20 @@ +Random Forest Presence Prediction (ViGrA) +imagery_vigra +ParameterMultipleInput|FEATURES|Features|3|False +OutputRaster|PREDICTION|Presence Prediction +OutputRaster|PROBABILITY|Presence Probability +ParameterVector|PRESENCE|Presence Data|-1|False +ParameterNumber|BACKGROUND|Background Sample Density [Percent]| 0.000000| 100.000000| 1.000000 +ParameterBoolean|DO_MRMR|Minimum Redundancy Feature Selection|False +ParameterNumber|mRMR_NFEATURES|Number of Features| 1|None| 50 +ParameterBoolean|mRMR_DISCRETIZE|Discretization|True +ParameterNumber|mRMR_THRESHOLD|Discretization Threshold| 0.000000|None| 1.000000 +ParameterSelection|mRMR_METHOD|Selection Method|[0] Mutual Information Difference (MID);[1] Mutual Information Quotient (MIQ)| 0 +ParameterFile|RF_IMPORT|Import from File|False|False +ParameterFile|RF_EXPORT|Export to File|False|False +ParameterNumber|RF_TREE_COUNT|Tree Count| 1|None| 32 +ParameterNumber|RF_TREE_SAMPLES|Samples per Tree| 0.000000| 1.000000| 1.000000 +ParameterBoolean|RF_REPLACE|Sample with Replacement|True +ParameterNumber|RF_SPLIT_MIN_SIZE|Minimum Node Split Size| 1|None| 1 +ParameterSelection|RF_NODE_FEATURES|Features per Node|[0] logarithmic;[1] square root;[2] all| 1 +ParameterSelection|RF_STRATIFICATION|Stratification|[0] none;[1] equal;[2] proportional| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/RegressionAnalysis(PointsandPredictorGrid).txt b/python/plugins/processing/algs/saga/description/RegressionAnalysis(PointsandPredictorGrid).txt new file mode 100644 index 00000000000..6bca4a191b9 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/RegressionAnalysis(PointsandPredictorGrid).txt @@ -0,0 +1,9 @@ +Regression Analysis (Points and Predictor Grid) +statistics_regression +ParameterRaster|PREDICTOR|Predictor|False +ParameterVector|POINTS|Points|-1|False +ParameterTable|ATTRIBUTE|Dependent Variable|False +OutputRaster|REGRESSION|Regression +OutputVector|RESIDUAL|Residuals +ParameterSelection|RESAMPLING|Resampling|[0] Nearest Neighbour;[1] Bilinear Interpolation;[2] Bicubic Spline Interpolation;[3] B-Spline Interpolation| 3 +ParameterSelection|METHOD|Regression Function|[0] Y = a + b * X (linear);[1] Y = a + b / X;[2] Y = a / (b - X);[3] Y = a * X^b (power);[4] Y = a e^(b * X) (exponential);[5] Y = a + b * ln(X) (logarithmic)| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/RegressionKriging.txt b/python/plugins/processing/algs/saga/description/RegressionKriging.txt new file mode 100644 index 00000000000..23551eedefc --- /dev/null +++ b/python/plugins/processing/algs/saga/description/RegressionKriging.txt @@ -0,0 +1,32 @@ +Regression Kriging +statistics_kriging +ParameterVector|POINTS|Points|-1|False +ParameterTable|FIELD|Attribute|False +ParameterMultipleInput|PREDICTORS|Predictors|3|False +OutputRaster|REGRESSION|Regression +OutputRaster|PREDICTION|Prediction +OutputRaster|RESIDUALS|Residuals +OutputRaster|VARIANCE|Quality Measure +ParameterSelection|TQUALITY|Type of Quality Measure|[0] standard deviation;[1] variance| 0 +ParameterBoolean|LOG|Logarithmic Transformation|False +ParameterBoolean|BLOCK|Block Kriging|False +ParameterNumber|DBLOCK|Block Size| 0.000000|None| 100.000000 +ParameterNumber|VAR_MAXDIST|Maximum Distance|None|None| -1.000000 +ParameterNumber|VAR_NCLASSES|Lag Distance Classes| 1|None| 100 +ParameterNumber|VAR_NSKIP|Skip| 1|None| 1 +ParameterString|VAR_MODEL|Variogram Model +ParameterTable|INFO_COEFF|Regression: Coefficients|True +ParameterTable|INFO_MODEL|Regression: Model|True +ParameterTable|INFO_STEPS|Regression: Steps|True +ParameterBoolean|COORD_X|Include X Coordinate|False +ParameterBoolean|COORD_Y|Include Y Coordinate|False +ParameterBoolean|INTERCEPT|Intercept|True +ParameterSelection|METHOD|Method|[0] include all;[1] forward;[2] backward;[3] stepwise| 3 +ParameterNumber|P_VALUE|Significance Level| 0.000000| 100.000000| 5.000000 +ParameterSelection|RESAMPLING|Resampling|[0] Nearest Neighbour;[1] Bilinear Interpolation;[2] Bicubic Spline Interpolation;[3] B-Spline Interpolation| 3 +ParameterSelection|SEARCH_RANGE|Search Range|[0] local;[1] global| 0 +ParameterNumber|SEARCH_RADIUS|Maximum Search Distance| 0.000000|None| 1000.000000 +ParameterSelection|SEARCH_POINTS_ALL|Number of Points|[0] maximum number of nearest points;[1] all points within search distance| 0 +ParameterNumber|SEARCH_POINTS_MIN|Minimum| 1|None| 16 +ParameterNumber|SEARCH_POINTS_MAX|Maximum| 1|None| 20 +ParameterSelection|SEARCH_DIRECTION|Direction|[0] all directions;[1] quadrants| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ResamplingFilter.txt b/python/plugins/processing/algs/saga/description/ResamplingFilter.txt new file mode 100644 index 00000000000..9c5b1b90cba --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ResamplingFilter.txt @@ -0,0 +1,6 @@ +Resampling Filter +grid_filter +ParameterRaster|GRID|Grid|False +OutputRaster|LOPASS|Low Pass Filter +OutputRaster|HIPASS|High Pass Filter +ParameterNumber|SCALE|Scale Factor| 1.000000|None| 10.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/RiverBasin.txt b/python/plugins/processing/algs/saga/description/RiverBasin.txt new file mode 100644 index 00000000000..93992cc6130 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/RiverBasin.txt @@ -0,0 +1,19 @@ +RiverBasin +sim_rivflow +ParameterRaster|INPUT|DTM|False +ParameterRaster|INPUT2|HGGrid|False +ParameterRaster|INPUT3|statisches Entnahmeraster|True +ParameterBoolean|WCons|Anteilige Flaechenwasserentnahme|False +ParameterSelection|WCons2|Dynamische Flaechenwassernutzung...|[0] ...anteilig aus den Flussrasterzellen;[1] ...anteilig aus Rasterzellen der Teileinzugegebiete| 0 +OutputRaster|OUTPUT2|Grad +OutputRaster|OUTPUT3|Direc +OutputRaster|OUTPUT4|HGGrad +OutputRaster|OUTPUT5|RivSpeed +OutputRaster|OUTPUT6|Coordinates +OutputRaster|OUTPUT7|BasinShare +OutputRaster|OUTPUT8|statWUse +OutputRaster|OUTPUT9|NumInFlowCells +ParameterNumber|pCr|Hauptgerinne-Parameter pHG| 0.000000|None| 0.003500 +ParameterNumber|nCr|Hauptgerinne-Speicherkaskade nHG| 1|None| 1 +ParameterBoolean|EnfVmax|Maximal Geschwindigkeit des Hauptgerinnes beruecksichtigen|True +ParameterNumber|VTresh|Maximalgeschwindigkeit im Hauptgerinne in km/h| 0.000000| 10.000000| 4.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/RiverGridGeneration.txt b/python/plugins/processing/algs/saga/description/RiverGridGeneration.txt new file mode 100644 index 00000000000..1e91a036a34 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/RiverGridGeneration.txt @@ -0,0 +1,9 @@ +RiverGridGeneration +sim_rivflow +ParameterRaster|INPUT|Geländemodell (DTM)|False +OutputRaster|OUTPUT|HG Raster +ParameterNumber|SX|Abflusspfad-Quelle, x-Wert| 0|None| 0 +ParameterNumber|SY|Abflusspfad-Quelle, y-Wert| 0|None| 0 +ParameterNumber|MX|Abflusspfad-Mündung, x-Wert| 0|None| 0 +ParameterNumber|MY|Abflusspfad-Mündung, y-Wert| 0|None| 0 +ParameterBoolean|Owrite|Overwrite RiverGridCells|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SVMClassification.txt b/python/plugins/processing/algs/saga/description/SVMClassification.txt new file mode 100644 index 00000000000..358f07207f1 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SVMClassification.txt @@ -0,0 +1,24 @@ +SVM Classification +imagery_svm +ParameterMultipleInput|GRIDS|Grids|3|False +OutputRaster|CLASSES|Classification +ParameterSelection|SCALING|Scaling|[0] none;[1] normalize (0-1);[2] standardize| 2 +ParameterBoolean|MESSAGE|Verbose Messages|False +ParameterSelection|MODEL_SRC|Model Source|[0] create from training areas;[1] restore from file| 0 +ParameterFile|MODEL_LOAD|Restore Model from File|False|False +ParameterVector|ROI|Training Areas|-1|False +ParameterTable|ROI_ID|Class Identifier|False +ParameterFile|MODEL_SAVE|Store Model to File|False|False +ParameterSelection|SVM_TYPE|SVM Type|[0] C-SVC;[1] nu-SVC;[2] one-class SVM;[3] epsilon-SVR;[4] nu-SVR| 0 +ParameterSelection|KERNEL_TYPE|Kernel Type|[0] linear;[1] polynomial;[2] radial basis function;[3] sigmoid| 2 +ParameterNumber|DEGREE|Degree|None|None| 3 +ParameterNumber|GAMMA|Gamma|None|None| 0.000000 +ParameterNumber|COEF0|coef0|None|None| 0.000000 +ParameterNumber|COST|C|None|None| 1.000000 +ParameterNumber|NU|nu-SVR|None|None| 0.500000 +ParameterNumber|EPS_SVR|SVR Epsilon|None|None| 0.100000 +ParameterNumber|CACHE_SIZE|Cache Size|None|None| 100.000000 +ParameterNumber|EPS|Epsilon|None|None| 0.001000 +ParameterBoolean|SHRINKING|Shrinking|False +ParameterBoolean|PROBABILITY|Probability Estimates|False +ParameterNumber|CROSSVAL|Cross Validation| 1|None| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SeededRegionGrowing.txt b/python/plugins/processing/algs/saga/description/SeededRegionGrowing.txt new file mode 100644 index 00000000000..fc6391f4426 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SeededRegionGrowing.txt @@ -0,0 +1,15 @@ +Seeded Region Growing +imagery_segmentation +ParameterRaster|SEEDS|Seeds|False +ParameterMultipleInput|FEATURES|Features|3|False +OutputRaster|SEGMENTS|Segments +OutputRaster|SIMILARITY|Similarity +ParameterTable|TABLE|Seeds|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterSelection|NEIGHBOUR|Neighbourhood|[0] 4 (von Neumann);[1] 8 (Moore)| 0 +ParameterSelection|METHOD|Method|[0] feature space and position;[1] feature space| 0 +ParameterNumber|SIG_1|Variance in Feature Space| 0.000000|None| 1.000000 +ParameterNumber|SIG_2|Variance in Position Space| 0.000000|None| 1.000000 +ParameterNumber|THRESHOLD|Similarity Threshold| 0.000000|None| 0.000000 +ParameterBoolean|REFRESH|Refresh|False +ParameterNumber|LEAFSIZE|Leaf Size (for Speed Optimisation)| 2|None| 256 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SievingClasses.txt b/python/plugins/processing/algs/saga/description/SievingClasses.txt new file mode 100644 index 00000000000..6e17e944950 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SievingClasses.txt @@ -0,0 +1,8 @@ +Sieving Classes +grid_filter +ParameterRaster|INPUT|Classes|False +OutputRaster|OUTPUT|Sieved Classes +ParameterSelection|MODE|Neighbourhood|[0] Neumann;[1] Moore| 1 +ParameterNumber|THRESHOLD|Minimum Threshold| 2|None| 4 +ParameterSelection|ALL|Class Selection|[0] single class;[1] all classes| 1 +ParameterNumber|CLASS|Class Identifier|None|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SimpleFilterwithinshapes.txt b/python/plugins/processing/algs/saga/description/SimpleFilterwithinshapes.txt new file mode 100644 index 00000000000..95fb15b5ef7 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SimpleFilterwithinshapes.txt @@ -0,0 +1,8 @@ +Simple Filter within shapes +grid_filter +ParameterRaster|INPUT|Grid|False +OutputRaster|RESULT|Filtered Grid +ParameterVector|SHAPES|Boundaries|-1|False +ParameterSelection|MODE|Search Mode|[0] Square;[1] Circle| 1 +ParameterSelection|METHOD|Filter|[0] Smooth;[1] Sharpen;[2] Edge| 0 +ParameterNumber|RADIUS|Radius| 1|None| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SimpleKriging.txt b/python/plugins/processing/algs/saga/description/SimpleKriging.txt new file mode 100644 index 00000000000..72a76c91fb7 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SimpleKriging.txt @@ -0,0 +1,25 @@ +Simple Kriging +statistics_kriging +ParameterVector|POINTS|Points|0|False +ParameterTable|FIELD|Attribute|False +ParameterSelection|TQUALITY|Type of Quality Measure|[0] standard deviation;[1] variance| 0 +ParameterBoolean|LOG|Logarithmic Transformation|False +ParameterBoolean|BLOCK|Block Kriging|False +ParameterNumber|DBLOCK|Block Size| 0.000000|None| 100.000000 +ParameterNumber|VAR_MAXDIST|Maximum Distance|None|None| -1.000000 +ParameterNumber|VAR_NCLASSES|Lag Distance Classes| 1|None| 100 +ParameterNumber|VAR_NSKIP|Skip| 1|None| 1 +ParameterString|VAR_MODEL|Model +Hardcoded|-TARGET_DEFINITION 0 +Extent TARGET_USER_XMIN TARGET_USER_XMAX TARGET_USER_YMIN TARGET_USER_YMAX +ParameterNumber|TARGET_USER_SIZE|Cellsize|None|None|100.0 +ParameterSelection|TARGET_USER_FITS|Fit|[0] nodes;[1] cells| 0 +ParameterRaster|TARGET_TEMPLATE|Target System|True +OutputRaster|PREDICTION|Prediction +OutputRaster|VARIANCE|Quality Measure +ParameterSelection|SEARCH_RANGE|Search Range|[0] local;[1] global| 0 +ParameterNumber|SEARCH_RADIUS|Maximum Search Distance| 0.000000|None| 1000.000000 +ParameterSelection|SEARCH_POINTS_ALL|Number of Points|[0] maximum number of nearest points;[1] all points within search distance| 0 +ParameterNumber|SEARCH_POINTS_MIN|Minimum| 1|None| 16 +ParameterNumber|SEARCH_POINTS_MAX|Maximum| 1|None| 20 +ParameterSelection|SEARCH_DIRECTION|Direction|[0] all directions;[1] quadrants| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SingleValueDecomposition(OpenCV).txt b/python/plugins/processing/algs/saga/description/SingleValueDecomposition(OpenCV).txt new file mode 100644 index 00000000000..123c24912d5 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SingleValueDecomposition(OpenCV).txt @@ -0,0 +1,6 @@ +Single Value Decomposition (OpenCV) +imagery_opencv +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Output +ParameterNumber|RANGE_MIN|Range (Min)|None|None|None +ParameterNumber|RANGE_MAX|Range (Max)|None|None|None \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SlopeLimitedFlowAccumulation.txt b/python/plugins/processing/algs/saga/description/SlopeLimitedFlowAccumulation.txt new file mode 100644 index 00000000000..1e924c852ab --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SlopeLimitedFlowAccumulation.txt @@ -0,0 +1,10 @@ +Slope Limited Flow Accumulation +ta_hydrology +ParameterRaster|DEM|Elevation|False +ParameterRaster|WEIGHT|Weight|True +OutputRaster|FLOW|Flow Accumulation +ParameterNumber|SLOPE_MIN|Slope Minimum| 0.000000|None| 0.000000 +ParameterNumber|SLOPE_MAX|Slope Threshold| 0.000000| 90.000000| 5.000000 +ParameterBoolean|B_FLOW|Use Flow Threshold|False +ParameterNumber|T_FLOW_MIN|Flow Threshold (Min)|None|None|None +ParameterNumber|T_FLOW_MAX|Flow Threshold (Max)|None|None|None \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Smoothing(ViGrA).txt b/python/plugins/processing/algs/saga/description/Smoothing(ViGrA).txt new file mode 100644 index 00000000000..94d3f21fec4 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Smoothing(ViGrA).txt @@ -0,0 +1,7 @@ +Smoothing (ViGrA) +imagery_vigra +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Output +ParameterSelection|TYPE|Type of smoothing|[0] exponential;[1] nonlinear;[2] gaussian| 0 +ParameterNumber|SCALE|Size of smoothing filter| 0.000000|None| 2.000000 +ParameterNumber|EDGE|Edge threshold for nonlinear smoothing| 0.000000|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SnapPointstoGrid.txt b/python/plugins/processing/algs/saga/description/SnapPointstoGrid.txt new file mode 100644 index 00000000000..5a7e348f275 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SnapPointstoGrid.txt @@ -0,0 +1,9 @@ +Snap Points to Grid +shapes_points +ParameterVector|INPUT|Points|0|False +ParameterRaster|GRID|Grid|False +OutputVector|OUTPUT|Result +OutputVector|MOVES|Moves +ParameterNumber|DISTANCE|Search Distance (Map Units)| 0.000000|None| 0.000000 +ParameterSelection|SHAPE|Search Shape|[0] circle;[1] square| 0 +ParameterSelection|EXTREME|Extreme|[0] minimum;[1] maximum| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SnapPointstoLines.txt b/python/plugins/processing/algs/saga/description/SnapPointstoLines.txt new file mode 100644 index 00000000000..ece260c3675 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SnapPointstoLines.txt @@ -0,0 +1,7 @@ +Snap Points to Lines +shapes_points +ParameterVector|INPUT|Points|1|False +ParameterVector|SNAP|Snap Features|-1|False +OutputVector|OUTPUT|Result +OutputVector|MOVES|Moves +ParameterNumber|DISTANCE|Search Distance| 0.000000|None| 0.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SnapPointstoPoints.txt b/python/plugins/processing/algs/saga/description/SnapPointstoPoints.txt new file mode 100644 index 00000000000..30a60e92500 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SnapPointstoPoints.txt @@ -0,0 +1,7 @@ +Snap Points to Points +shapes_points +ParameterVector|INPUT|Points|-1|False +ParameterVector|SNAP|Snap Features|-1|False +OutputVector|OUTPUT|Result +OutputVector|MOVES|Moves +ParameterNumber|DISTANCE|Search Distance| 0.000000|None| 0.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SplitLinesatPoints.txt b/python/plugins/processing/algs/saga/description/SplitLinesatPoints.txt new file mode 100644 index 00000000000..f9740a1079e --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SplitLinesatPoints.txt @@ -0,0 +1,7 @@ +Split Lines at Points +shapes_lines +ParameterVector|LINES|Lines|1|False +ParameterVector|SPLIT|Split Features|0|False +OutputVector|INTERSECT|Intersection +ParameterSelection|OUTPUT|Output|[0] polylines;[1] separate lines| 1 +ParameterNumber|EPSILON|Epsilon|None|None| 0.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SplitLineswithLines.txt b/python/plugins/processing/algs/saga/description/SplitLineswithLines.txt new file mode 100644 index 00000000000..1a96df270f2 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SplitLineswithLines.txt @@ -0,0 +1,6 @@ +Split Lines with Lines +shapes_lines +ParameterVector|LINES|Lines|1|False +ParameterVector|SPLIT|Split Features|1|False +OutputVector|INTERSECT|Intersection +ParameterSelection|OUTPUT|Output|[0] polylines;[1] separate lines| 1 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SupervisedClassificationforGrids.txt b/python/plugins/processing/algs/saga/description/SupervisedClassificationforGrids.txt new file mode 100644 index 00000000000..ba6e0e7d8c3 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SupervisedClassificationforGrids.txt @@ -0,0 +1,21 @@ +Supervised Classification for Grids +imagery_classification +ParameterMultipleInput|GRIDS|Features|3|False +ParameterBoolean|NORMALISE|Normalise|False +OutputRaster|CLASSES|Classification +OutputRaster|QUALITY|Quality +ParameterVector|TRAINING|Training Areas|-1|True +ParameterTable|TRAINING_CLASS|Class Identifier|False +ParameterFile|FILE_LOAD|Load Statistics from File...|False|False +ParameterFile|FILE_SAVE|Save Statistics to File...|False|False +ParameterSelection|METHOD|Method|[0] Binary Encoding;[1] Parallelepiped;[2] Minimum Distance;[3] Mahalanobis Distance;[4] Maximum Likelihood;[5] Spectral Angle Mapping;[6] Winner Takes All| 2 +ParameterNumber|THRESHOLD_DIST|Distance Threshold| 0.000000|None| 0.000000 +ParameterNumber|THRESHOLD_ANGLE|Spectral Angle Threshold (Degree)| 0.000000| 90.000000| 0.000000 +ParameterNumber|THRESHOLD_PROB|Probability Threshold| 0.000000| 100.000000| 0.000000 +ParameterSelection|RELATIVE_PROB|Probability Reference|[0] absolute;[1] relative| 1 +ParameterBoolean|WTA_0|Binary Encoding|False +ParameterBoolean|WTA_1|Parallelepiped|False +ParameterBoolean|WTA_2|Minimum Distance|False +ParameterBoolean|WTA_3|Mahalanobis Distance|False +ParameterBoolean|WTA_4|Maximum Likelihood|False +ParameterBoolean|WTA_5|Spectral Angle Mapping|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SupervisedClassificationforShapes.txt b/python/plugins/processing/algs/saga/description/SupervisedClassificationforShapes.txt new file mode 100644 index 00000000000..fadb3b7cd53 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SupervisedClassificationforShapes.txt @@ -0,0 +1,20 @@ +Supervised Classification for Shapes +imagery_classification +ParameterVector|SHAPES|Shapes|-1|False +OutputVector|CLASSES|Classification +ParameterTable|FEATURES|Features|False +ParameterBoolean|NORMALISE|Normalise|False +ParameterTable|TRAINING|Training Classes|False +ParameterFile|FILE_LOAD|Load Statistics from File...|False|False +ParameterFile|FILE_SAVE|Save Statistics to File...|False|False +ParameterSelection|METHOD|Method|[0] Binary Encoding;[1] Parallelepiped;[2] Minimum Distance;[3] Mahalanobis Distance;[4] Maximum Likelihood;[5] Spectral Angle Mapping;[6] Winner Takes All| 2 +ParameterNumber|THRESHOLD_DIST|Distance Threshold| 0.000000|None| 0.000000 +ParameterNumber|THRESHOLD_ANGLE|Spectral Angle Threshold (Degree)| 0.000000| 90.000000| 0.000000 +ParameterNumber|THRESHOLD_PROB|Probability Threshold| 0.000000| 100.000000| 0.000000 +ParameterSelection|RELATIVE_PROB|Probability Reference|[0] absolute;[1] relative| 1 +ParameterBoolean|WTA_0|Binary Encoding|False +ParameterBoolean|WTA_1|Parallelepiped|False +ParameterBoolean|WTA_2|Minimum Distance|False +ParameterBoolean|WTA_3|Mahalanobis Distance|False +ParameterBoolean|WTA_4|Maximum Likelihood|False +ParameterBoolean|WTA_5|Spectral Angle Mapping|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SupportVectorMachineClassification(OpenCV).txt b/python/plugins/processing/algs/saga/description/SupportVectorMachineClassification(OpenCV).txt new file mode 100644 index 00000000000..5bb5d34d086 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SupportVectorMachineClassification(OpenCV).txt @@ -0,0 +1,15 @@ +Support Vector Machine Classification (OpenCV) +imagery_opencv +ParameterMultipleInput|FEATURES|Features|3|False +ParameterBoolean|NORMALIZE|Normalize|False +ParameterVector|TRAIN_AREAS|Training Areas|-1|False +ParameterTable|TRAIN_CLASS|Class Identifier|False +OutputRaster|CLASSES|Classification +ParameterSelection|SVM_TYPE|SVM Type|[0] c-support vector classification;[1] nu support vector classification;[2] distribution estimation (one class);[3] epsilon support vector regression;[4] nu support vector regression| 0 +ParameterNumber|C|C| 0.000000|None| 1.000000 +ParameterNumber|NU|Nu| 0.000000|None| 0.500000 +ParameterNumber|P|P| 0.000000|None| 0.500000 +ParameterSelection|KERNEL|Kernel Type|[0] linear;[1] polynomial;[2] radial basis function;[3] sigmoid;[4] exponential chi2;[5] histogram intersection| 1 +ParameterNumber|COEF0|Coefficient 0| 0.000000|None| 1.000000 +ParameterNumber|DEGREE|Degree| 0.000000|None| 0.500000 +ParameterNumber|GAMMA|Gamma| 0.000000|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SurfaceGradientandConcentration.txt b/python/plugins/processing/algs/saga/description/SurfaceGradientandConcentration.txt new file mode 100644 index 00000000000..da382dd36dd --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SurfaceGradientandConcentration.txt @@ -0,0 +1,12 @@ +Surface, Gradient and Concentration +sim_hydrology +ParameterRaster|MASK|Mask|False +OutputRaster|SURF|Surface +OutputRaster|GRAD|Gradient +OutputRaster|CONC|Concentration +ParameterNumber|SURF_E|Surface Approximation Threshold| 0.000000|None| 0.001000 +ParameterNumber|CONC_IN|Inlet Concentration| 0.000000|None| 5.000000 +ParameterNumber|CONC_OUT|Outlet Concentration| 0.000000|None| 3.000000 +ParameterNumber|CONC_E|Concentration Approximation Threshold| 0.000000|None| 0.001000 +ParameterNumber|GRAD_MIN|Minimum Gradient| 0.000000|None| 0.000000 +ParameterSelection|NEIGHBOURS|Neighbourhood|[0] Moore (8);[1] Neumann (4);[2] Optimised| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SurfaceandGradient.txt b/python/plugins/processing/algs/saga/description/SurfaceandGradient.txt new file mode 100644 index 00000000000..55acecac3a9 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SurfaceandGradient.txt @@ -0,0 +1,6 @@ +Surface and Gradient +sim_hydrology +ParameterRaster|MASK|Mask|False +OutputRaster|SURF|Surface +OutputRaster|GRAD|Gradient +ParameterNumber|SURF_E|Surface Approximation Threshold| 0.000000|None| 0.001000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/SymmetricalDifference.txt b/python/plugins/processing/algs/saga/description/SymmetricalDifference.txt new file mode 100644 index 00000000000..c68254c1108 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/SymmetricalDifference.txt @@ -0,0 +1,6 @@ +Symmetrical Difference +shapes_polygons +ParameterVector|A|Layer A|-1|False +ParameterVector|B|Layer B|-1|False +OutputVector|RESULT|Symmetrical Difference +ParameterBoolean|SPLIT|Split Parts|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TCILow.txt b/python/plugins/processing/algs/saga/description/TCILow.txt new file mode 100644 index 00000000000..aa68d2dd702 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TCILow.txt @@ -0,0 +1,5 @@ +TCI Low +ta_hydrology +ParameterRaster|DISTANCE|Vertical Distance to Channel Network|False +ParameterRaster|TWI|Topographic Wetness Index|False +OutputRaster|TCILOW|TCI Low \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TasseledCapTransformation.txt b/python/plugins/processing/algs/saga/description/TasseledCapTransformation.txt new file mode 100644 index 00000000000..f659e3eac72 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TasseledCapTransformation.txt @@ -0,0 +1,11 @@ +Tasseled Cap Transformation +imagery_tools +ParameterRaster|BLUE|Blue (TM 1)|True +ParameterRaster|RED|Red (TM 2)|False +ParameterRaster|GREEN|Green (TM 3)|False +ParameterRaster|NIR|Near Infrared (TM 4)|False +ParameterRaster|MIR1|Mid Infrared (TM 5)|False +ParameterRaster|MIR2|Mid Infrared (TM 7)|False +OutputRaster|BRIGHTNESS|Brightness +OutputRaster|GREENNESS|Greenness +OutputRaster|WETNESS|Wetness \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TerrainMapView.txt b/python/plugins/processing/algs/saga/description/TerrainMapView.txt new file mode 100644 index 00000000000..ea3f7f3ab46 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TerrainMapView.txt @@ -0,0 +1,11 @@ +Terrain Map View +grid_visualisation +ParameterRaster|DEM|DEM|False +OutputRaster|SHADE|Shade +OutputRaster|OPENNESS|Openness +OutputRaster|SLOPE|Slope +OutputVector|CONTOURS|Contours +ParameterSelection|METHOD|Method|[0] Topography;[1] Morphology| 0 +ParameterNumber|RADIUS|Radial Limit| 0.000000|None| 1000.000000 +ParameterBoolean|CONTOUR_LINES|Contour Lines|True +ParameterNumber|EQUIDISTANCE|Equidistance| 0.000000|None| 50.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TerrainSurfaceClassification(IwahashiandPike).txt b/python/plugins/processing/algs/saga/description/TerrainSurfaceClassification(IwahashiandPike).txt new file mode 100644 index 00000000000..77891b974fe --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TerrainSurfaceClassification(IwahashiandPike).txt @@ -0,0 +1,16 @@ +Terrain Surface Classification (Iwahashi and Pike) +ta_morphometry +ParameterRaster|DEM|Elevation|False +ParameterRaster|SLOPE|Slope|True +ParameterRaster|CONVEXITY|Convexity|True +ParameterBoolean|CONV_RECALC|Recalculate|False +ParameterRaster|TEXTURE|Texture|True +ParameterBoolean|TEXT_RECALC|Recalculate|False +OutputRaster|LANDFORMS|Landforms +ParameterSelection|TYPE|Number of Classes|[0] 8;[1] 12;[2] 16| 2 +ParameterNumber|CONV_SCALE|Scale (Cells)| 1|None| 10 +ParameterSelection|CONV_KERNEL|Laplacian Filter Kernel|[0] four-neighbourhood;[1] eight-neihbourhood;[2] eight-neihbourhood (distance based weighting)| 0 +ParameterSelection|CONV_TYPE|Type|[0] convexity;[1] concavity| 0 +ParameterNumber|CONV_EPSILON|Flat Area Threshold| 0.000000|None| 0.000000 +ParameterNumber|TEXT_SCALE|Scale (Cells)| 1|None| 10 +ParameterNumber|TEXT_EPSILON|Flat Area Threshold| 0.000000|None| 1.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TerrainSurfaceConvexity.txt b/python/plugins/processing/algs/saga/description/TerrainSurfaceConvexity.txt new file mode 100644 index 00000000000..83ee89d30ba --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TerrainSurfaceConvexity.txt @@ -0,0 +1,13 @@ +Terrain Surface Convexity +ta_morphometry +ParameterRaster|DEM|Elevation|False +OutputRaster|CONVEXITY|Convexity +ParameterSelection|KERNEL|Laplacian Filter Kernel|[0] conventional four-neighbourhood;[1] conventional eight-neihbourhood;[2] eight-neihbourhood (distance based weighting)| 0 +ParameterSelection|TYPE|Type|[0] convexity;[1] concavity| 0 +ParameterNumber|EPSILON|Flat Area Threshold| 0.000000|None| 0.000000 +ParameterNumber|SCALE|Scale (Cells)| 1|None| 10 +ParameterSelection|METHOD|Method|[0] counting cells;[1] resampling| 1 +ParameterSelection|DW_WEIGHTING|Weighting Function|[0] no distance weighting;[1] inverse distance to a power;[2] exponential;[3] gaussian weighting| 3 +ParameterNumber|DW_IDW_POWER|Inverse Distance Weighting Power| 0.000000|None| 1.000000 +ParameterBoolean|DW_IDW_OFFSET|Inverse Distance Offset|True +ParameterNumber|DW_BANDWIDTH|Gaussian and Exponential Weighting Bandwidth| 0.000000|None| 0.700000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TerrainSurfaceTexture.txt b/python/plugins/processing/algs/saga/description/TerrainSurfaceTexture.txt new file mode 100644 index 00000000000..562d6386268 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TerrainSurfaceTexture.txt @@ -0,0 +1,11 @@ +Terrain Surface Texture +ta_morphometry +ParameterRaster|DEM|Elevation|False +OutputRaster|TEXTURE|Texture +ParameterNumber|EPSILON|Flat Area Threshold| 0.000000|None| 1.000000 +ParameterNumber|SCALE|Scale (Cells)| 1|None| 10 +ParameterSelection|METHOD|Method|[0] counting cells;[1] resampling| 1 +ParameterSelection|DW_WEIGHTING|Weighting Function|[0] no distance weighting;[1] inverse distance to a power;[2] exponential;[3] gaussian weighting| 3 +ParameterNumber|DW_IDW_POWER|Inverse Distance Weighting Power| 0.000000|None| 1.000000 +ParameterBoolean|DW_IDW_OFFSET|Inverse Distance Offset|True +ParameterNumber|DW_BANDWIDTH|Gaussian and Exponential Weighting Bandwidth| 0.000000|None| 0.700000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ThiessenPolygons.txt b/python/plugins/processing/algs/saga/description/ThiessenPolygons.txt new file mode 100644 index 00000000000..b0eabe798eb --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ThiessenPolygons.txt @@ -0,0 +1,5 @@ +Thiessen Polygons +shapes_points +ParameterVector|POINTS|Points|-1|False +OutputVector|POLYGONS|Polygons +ParameterNumber|FRAME|Frame Size| 0.000000|None| 10.000000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TopofAtmosphereReflectance.txt b/python/plugins/processing/algs/saga/description/TopofAtmosphereReflectance.txt new file mode 100644 index 00000000000..702f0b8bbec --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TopofAtmosphereReflectance.txt @@ -0,0 +1,69 @@ +Top of Atmosphere Reflectance +imagery_tools +ParameterRaster|DN_MSS01|DN Band 1|True +ParameterRaster|DN_MSS02|DN Band 2|True +ParameterRaster|DN_MSS03|DN Band 3|True +ParameterRaster|DN_MSS04|DN Band 4|True +OutputRaster|RF_MSS01|Reflectance Band 1 +OutputRaster|RF_MSS02|Reflectance Band 2 +OutputRaster|RF_MSS03|Reflectance Band 3 +OutputRaster|RF_MSS04|Reflectance Band 4 +ParameterRaster|DN_ETM01|DN Band 1|True +ParameterRaster|DN_ETM02|DN Band 2|True +ParameterRaster|DN_ETM03|DN Band 3|True +ParameterRaster|DN_ETM04|DN Band 4|True +ParameterRaster|DN_ETM05|DN Band 5|True +ParameterRaster|DN_ETM07|DN Band 7|True +OutputRaster|RF_ETM01|Reflectance Band 1 +OutputRaster|RF_ETM02|Reflectance Band 2 +OutputRaster|RF_ETM03|Reflectance Band 3 +OutputRaster|RF_ETM04|Reflectance Band 4 +OutputRaster|RF_ETM05|Reflectance Band 5 +OutputRaster|RF_ETM07|Reflectance Band 7 +ParameterRaster|DN_OLI01|DN Band 1|True +ParameterRaster|DN_OLI02|DN Band 2|True +ParameterRaster|DN_OLI03|DN Band 3|True +ParameterRaster|DN_OLI04|DN Band 4|True +ParameterRaster|DN_OLI05|DN Band 5|True +ParameterRaster|DN_OLI06|DN Band 6|True +ParameterRaster|DN_OLI07|DN Band 7|True +ParameterRaster|DN_OLI09|DN Band 9|True +OutputRaster|RF_OLI01|Reflectance Band 1 +OutputRaster|RF_OLI02|Reflectance Band 2 +OutputRaster|RF_OLI03|Reflectance Band 3 +OutputRaster|RF_OLI04|Reflectance Band 4 +OutputRaster|RF_OLI05|Reflectance Band 5 +OutputRaster|RF_OLI06|Reflectance Band 6 +OutputRaster|RF_OLI07|Reflectance Band 7 +OutputRaster|RF_OLI09|Reflectance Band 9 +ParameterRaster|DN__TM06|DN Band 6|True +OutputRaster|RF__TM06|Reflectance Band 6 +ParameterRaster|DN_ETM61|DN Band 61|True +ParameterRaster|DN_ETM62|DN Band 62|True +OutputRaster|RF_ETM61|Reflectance Band 61 +OutputRaster|RF_ETM62|Reflectance Band 62 +ParameterRaster|DN_OLI10|DN Band 10|True +ParameterRaster|DN_OLI11|DN Band 11|True +OutputRaster|RF_OLI10|Reflectance Band 10 +OutputRaster|RF_OLI11|Reflectance Band 11 +ParameterRaster|DN_PAN08|DN Band 8|True +OutputRaster|RF_PAN08|Reflectance Band 8 +ParameterFile|METAFILE|Metadata File|False|False +ParameterSelection|SENSOR|Spacecraft Sensor|[0] Landsat-1 MSS;[1] Landsat-2 MSS;[2] Landsat-3 MSS;[3] Landsat-4 MSS;[4] Landsat-5 MSS;[5] Landsat-4 TM;[6] Landsat-5 TM;[7] Landsat-7 ETM+;[8] Landsat-8 OLI/TIRS| 7 +ParameterString|DATE_ACQU|Image Acquisition Date +ParameterString|DATE_PROD|Image Creation Date +ParameterNumber|SUN_HGT|Suns's Height| 0.000000| 90.000000| 45.000000 +ParameterBoolean|AS_RAD|At-Sensor Radiance|False +ParameterSelection|AC_METHOD|Atmospheric Correction|[0] uncorrected;[1] corrected;[2] dark object subtraction 1;[3] dark object subtraction 2;[4] dark object subtraction 2b;[5] dark object subtraction 3;[6] dark object subtraction 4| 0 +ParameterNumber|AC_DO_CELLS|Minimum Number of Dark Object Cells| 0|None| 1000 +ParameterNumber|AC_RAYLEIGH|Rayleigh Scattering|None|None| 0.000000 +ParameterNumber|AC_SUN_RAD|Solar Radiance| 0.000000| 100.000000| 1.000000 +ParameterSelection|ETM_GAIN_10|Band 1|[0] low;[1] high| 1 +ParameterSelection|ETM_GAIN_20|Band 2|[0] low;[1] high| 1 +ParameterSelection|ETM_GAIN_30|Band 3|[0] low;[1] high| 1 +ParameterSelection|ETM_GAIN_40|Band 4|[0] low;[1] high| 1 +ParameterSelection|ETM_GAIN_50|Band 5|[0] low;[1] high| 1 +ParameterSelection|ETM_GAIN_61|Band 61|[0] low;[1] high| 0 +ParameterSelection|ETM_GAIN_62|Band 62|[0] low;[1] high| 1 +ParameterSelection|ETM_GAIN_70|Band 7|[0] low;[1] high| 1 +ParameterSelection|ETM_GAIN_80|Band 8|[0] low;[1] high| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/TopographicOpenness.txt b/python/plugins/processing/algs/saga/description/TopographicOpenness.txt new file mode 100644 index 00000000000..bcfbd31440d --- /dev/null +++ b/python/plugins/processing/algs/saga/description/TopographicOpenness.txt @@ -0,0 +1,9 @@ +Topographic Openness +ta_lighting +ParameterRaster|DEM|Elevation|False +OutputRaster|POS|Positive Openness +OutputRaster|NEG|Negative Openness +ParameterNumber|RADIUS|Radial Limit| 0.000000|None| 10000.000000 +ParameterSelection|METHOD|Method|[0] multi scale;[1] sectors| 1 +ParameterNumber|DLEVEL|Multi Scale Factor| 1.250000|None| 3.000000 +ParameterNumber|NDIRS|Number of Sectors| 2|None| 8 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Union.txt b/python/plugins/processing/algs/saga/description/Union.txt new file mode 100644 index 00000000000..be9cc768d60 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Union.txt @@ -0,0 +1,6 @@ +Union +shapes_polygons +ParameterVector|A|Layer A|-1|False +ParameterVector|B|Layer B|-1|False +OutputVector|RESULT|Union +ParameterBoolean|SPLIT|Split Parts|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/Update.txt b/python/plugins/processing/algs/saga/description/Update.txt new file mode 100644 index 00000000000..690b7dd03b2 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/Update.txt @@ -0,0 +1,6 @@ +Update +shapes_polygons +ParameterVector|A|Layer A|-1|False +ParameterVector|B|Layer B|-1|False +OutputVector|RESULT|Update +ParameterBoolean|SPLIT|Split Parts|True \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/UpslopeandDownslopeCurvature.txt b/python/plugins/processing/algs/saga/description/UpslopeandDownslopeCurvature.txt new file mode 100644 index 00000000000..0bdeff7b89c --- /dev/null +++ b/python/plugins/processing/algs/saga/description/UpslopeandDownslopeCurvature.txt @@ -0,0 +1,9 @@ +Upslope and Downslope Curvature +ta_morphometry +ParameterRaster|DEM|Elevation|False +OutputRaster|C_LOCAL|Local Curvature +OutputRaster|C_UP|Upslope Curvature +OutputRaster|C_UP_LOCAL|Local Upslope Curvature +OutputRaster|C_DOWN|Downslope Curvature +OutputRaster|C_DOWN_LOCAL|Local Downslope Curvature +ParameterNumber|WEIGHTING|Upslope Weighting| 0.000000| 1.000000| 0.500000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ValleyDepth.txt b/python/plugins/processing/algs/saga/description/ValleyDepth.txt new file mode 100644 index 00000000000..9d2f1429c12 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ValleyDepth.txt @@ -0,0 +1,8 @@ +Valley Depth +ta_channels +ParameterRaster|ELEVATION|Elevation|False +OutputRaster|VALLEY_DEPTH|Valley Depth +OutputRaster|RIDGE_LEVEL|Ridge Level +ParameterNumber|THRESHOLD|Tension Threshold [Percentage of Cell Size]| 0.000000|None| 1.000000 +ParameterBoolean|NOUNDERGROUND|Keep Ridge Level above Surface|True +ParameterNumber|ORDER|Ridge Detection Threshold| 1| 7| 4 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ValleyandRidgeDetection(TopHatApproach).txt b/python/plugins/processing/algs/saga/description/ValleyandRidgeDetection(TopHatApproach).txt new file mode 100644 index 00000000000..d84c066ab96 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ValleyandRidgeDetection(TopHatApproach).txt @@ -0,0 +1,12 @@ +Valley and Ridge Detection (Top Hat Approach) +ta_morphometry +ParameterRaster|DEM|Elevation|False +OutputRaster|VALLEY|Valley Depth +OutputRaster|HILL|Hill Height +OutputRaster|VALLEY_IDX|Valley Index +OutputRaster|HILL_IDX|Hill Index +OutputRaster|SLOPE_IDX|Hillslope Index +ParameterNumber|RADIUS_VALLEY|Valley Radius| 0.000000|None| 1000.000000 +ParameterNumber|RADIUS_HILL|Hill Radius| 0.000000|None| 1000.000000 +ParameterNumber|THRESHOLD|Elevation Threshold| 0.000000|None| 100.000000 +ParameterSelection|METHOD|Slope Index|[0] default;[1] alternative| 0 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/VegetationIndex(DistanceBased).txt b/python/plugins/processing/algs/saga/description/VegetationIndex(DistanceBased).txt new file mode 100644 index 00000000000..f7ed226f7b4 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/VegetationIndex(DistanceBased).txt @@ -0,0 +1,12 @@ +Vegetation Index (Distance Based) +imagery_tools +ParameterRaster|RED|Red Reflectance|False +ParameterRaster|NIR|Near Infrared Reflectance|False +OutputRaster|PVI0|Perpendicular Vegetation Index (Richardson and Wiegand, 1977) +OutputRaster|PVI1|Perpendicular Vegetation Index (Perry and Lautenschlager, 1984) +OutputRaster|PVI2|Perpendicular Vegetation Index (Walther and Shabaani) +OutputRaster|PVI3|Perpendicular Vegetation Index (Qi, et al., 1994) +OutputRaster|TSAVI|Transformed Soil Adjusted Vegetation Index (Baret et al. 1989) +OutputRaster|ATSAVI|Transformed Soil Adjusted Vegetation Index (Baret and Guyot, 1991) +ParameterNumber|INTERCEPT|Intercept of Soil Line|None|None| 0.000000 +ParameterNumber|SLOPE|Slope of Soil Line|None|None| 0.500000 \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/WarpingShapes.txt b/python/plugins/processing/algs/saga/description/WarpingShapes.txt new file mode 100644 index 00000000000..fe73b58833b --- /dev/null +++ b/python/plugins/processing/algs/saga/description/WarpingShapes.txt @@ -0,0 +1,10 @@ +Warping Shapes +pj_georeference +ParameterVector|REF_SOURCE|Reference Points (Origin)|-1|False +ParameterVector|REF_TARGET|Reference Points (Projection)|-1|True +ParameterTable|XFIELD|x Position|False +ParameterTable|YFIELD|y Position|False +ParameterSelection|METHOD|Method|[0] Automatic;[1] Triangulation;[2] Spline;[3] Affine;[4] 1st Order Polynomial;[5] 2nd Order Polynomial;[6] 3rd Order Polynomial;[7] Polynomial, Order| 0 +ParameterNumber|ORDER|Polynomial Order| 1|None| 3 +ParameterVector|INPUT|Input|-1|False +OutputVector|OUTPUT|Output \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/WatershedSegmentation(ViGrA).txt b/python/plugins/processing/algs/saga/description/WatershedSegmentation(ViGrA).txt new file mode 100644 index 00000000000..153e254fc3d --- /dev/null +++ b/python/plugins/processing/algs/saga/description/WatershedSegmentation(ViGrA).txt @@ -0,0 +1,7 @@ +Watershed Segmentation (ViGrA) +imagery_vigra +ParameterRaster|INPUT|Input|False +OutputRaster|OUTPUT|Segmentation +ParameterNumber|SCALE|Width of gradient filter| 0.000000|None| 1.000000 +ParameterBoolean|RGB|RGB coded data|False +ParameterBoolean|EDGES|Edges|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/WindExpositionIndex.txt b/python/plugins/processing/algs/saga/description/WindExpositionIndex.txt new file mode 100644 index 00000000000..060bb43e42b --- /dev/null +++ b/python/plugins/processing/algs/saga/description/WindExpositionIndex.txt @@ -0,0 +1,9 @@ +Wind Exposition Index +ta_morphometry +ParameterRaster|DEM|Elevation|False +OutputRaster|EXPOSITION|Wind Exposition +ParameterNumber|MAXDIST|Search Distance [km]| 0.000000|None| 300.000000 +ParameterNumber|STEP|Angular Step Size (Degree)| 1.000000| 45.000000| 15.000000 +ParameterBoolean|OLDVER|Old Version|False +ParameterNumber|ACCEL|Acceleration| 1.000000|None| 1.500000 +ParameterBoolean|PYRAMIDS|Elevation Averaging|False \ No newline at end of file diff --git a/python/plugins/processing/algs/saga/description/ZonalMultipleRegressionAnalysis(PointsandPredictorGrids).txt b/python/plugins/processing/algs/saga/description/ZonalMultipleRegressionAnalysis(PointsandPredictorGrids).txt new file mode 100644 index 00000000000..c5b80b58ac1 --- /dev/null +++ b/python/plugins/processing/algs/saga/description/ZonalMultipleRegressionAnalysis(PointsandPredictorGrids).txt @@ -0,0 +1,14 @@ +Zonal Multiple Regression Analysis (Points and Predictor Grids) +statistics_regression +ParameterMultipleInput|PREDICTORS|Predictors|3|False +ParameterVector|ZONES|Zones|-1|False +ParameterVector|POINTS|Points|-1|False +ParameterTable|ATTRIBUTE|Dependent Variable|False +OutputVector|RESIDUALS|Residuals +OutputRaster|REGRESSION|Regression +ParameterSelection|RESAMPLING|Resampling|[0] Nearest Neighbour;[1] Bilinear Interpolation;[2] Bicubic Spline Interpolation;[3] B-Spline Interpolation| 3 +ParameterBoolean|COORD_X|Include X Coordinate|False +ParameterBoolean|COORD_Y|Include Y Coordinate|False +ParameterBoolean|INTERCEPT|Intercept|True +ParameterSelection|METHOD|Method|[0] include all;[1] forward;[2] backward;[3] stepwise| 3 +ParameterNumber|P_VALUE|Significance Level| 0.000000| 100.000000| 5.000000 \ No newline at end of file From 59d8b18ed8c53ab6fe21e7be306f66d8dc7d1a35 Mon Sep 17 00:00:00 2001 From: Andrea Aime Date: Sun, 20 Nov 2016 15:41:51 +0100 Subject: [PATCH 206/266] Porting Raymond Nijssen FOSS4G 2016 labeling work onto latest version of master, with basic support for testing. fixes #8925 --- src/core/qgsvectorlayer.cpp | 30 +- src/core/qgsvectorlayerlabeling.cpp | 49 +++ src/core/qgsvectorlayerlabeling.h | 16 +- .../python/test_qgssymbollayer_createsld.py | 54 ++++ tests/testdata/symbol_layer/simpleLabel.qml | 285 ++++++++++++++++++ 5 files changed, 424 insertions(+), 10 deletions(-) create mode 100644 tests/testdata/symbol_layer/simpleLabel.qml diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 09ac98c6172..2eb8e49e246 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -2205,20 +2205,34 @@ bool QgsVectorLayer::writeSld( QDomNode &node, QDomDocument &doc, QString &error { Q_UNUSED( errorMessage ); - // store the Name element - QDomElement nameNode = doc.createElement( QStringLiteral( "se:Name" ) ); - nameNode.appendChild( doc.createTextNode( name() ) ); - node.appendChild( nameNode ); - QgsStringMap localProps = QgsStringMap( props ); if ( hasScaleBasedVisibility() ) { QgsSymbolLayerUtils::mergeScaleDependencies( maximumScale(), minimumScale(), localProps ); } - if ( isSpatial() ) - { - node.appendChild( mRenderer->writeSld( doc, name(), localProps ) ); + if ( isSpatial() ) { + // store the Name element + QDomElement nameNode = doc.createElement( "se:Name" ); + nameNode.appendChild( doc.createTextNode( name() ) ); + node.appendChild( nameNode ); + + QDomElement userStyleElem = doc.createElement( "UserStyle" ); + node.appendChild( userStyleElem ); + + QDomElement nameElem = doc.createElement( "se:Name" ); + nameElem.appendChild( doc.createTextNode( name() ) ); + + userStyleElem.appendChild( nameElem ); + + QDomElement featureTypeStyleElem = doc.createElement( "se:FeatureTypeStyle" ); + userStyleElem.appendChild( featureTypeStyleElem ); + + mRenderer->toSld( doc, featureTypeStyleElem, localProps ); + if ( mLabeling != nullptr ) + { + mLabeling->toSld( featureTypeStyleElem, localProps ); + } } return true; } diff --git a/src/core/qgsvectorlayerlabeling.cpp b/src/core/qgsvectorlayerlabeling.cpp index 7a0cea27ec6..252fe1ca6cd 100644 --- a/src/core/qgsvectorlayerlabeling.cpp +++ b/src/core/qgsvectorlayerlabeling.cpp @@ -17,6 +17,8 @@ #include "qgspallabeling.h" #include "qgsrulebasedlabeling.h" #include "qgsvectorlayer.h" +#include "qgssymbollayerutils.h" +#include "qgis.h" QgsAbstractVectorLayerLabeling *QgsAbstractVectorLayerLabeling::create( const QDomElement &element, const QgsReadWriteContext &context ) @@ -88,3 +90,50 @@ QgsVectorLayerSimpleLabeling *QgsVectorLayerSimpleLabeling::create( const QDomEl return new QgsVectorLayerSimpleLabeling( QgsPalLayerSettings() ); } + +void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const +{ + + if ( mSettings->drawLabels ) + { + QDomDocument doc = parent.ownerDocument(); + + QDomElement ruleElement = doc.createElement( QStringLiteral( "se:Rule" ) ); + parent.appendChild( ruleElement ); + + QDomElement textSymbolizerElement = doc.createElement( QStringLiteral( "se:TextSymbolizer" ) ); + ruleElement.appendChild( textSymbolizerElement ); + + // label + QDomElement labelElement = doc.createElement( QStringLiteral( "se:Label" ) ); + textSymbolizerElement.appendChild( labelElement ); + + if ( mSettings->isExpression ) + { + labelElement.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( mSettings->getLabelExpression()->dump() ) ) ); + labelElement.appendChild( doc.createTextNode( "Placeholder" ) ); + } + else + { + QDomElement propertyNameElement = doc.createElement( QStringLiteral( "ogc:PropertyName" ) ); + propertyNameElement.appendChild( doc.createTextNode( mSettings->fieldName ) ); + labelElement.appendChild( propertyNameElement ); + } + + // font + QDomElement fontElement = doc.createElement( QStringLiteral( "se:Font" ) ); + textSymbolizerElement.appendChild( fontElement ); + QgsTextFormat format = mSettings->format(); + fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-family" ), format.font().family() ) ); + double fontSize = QgsSymbolLayerUtils::rescaleUom( format.size(), format.sizeUnit(), props ); + fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-size" ), QString::number( fontSize ) ) ); + + + // fill + QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) ); + textSymbolizerElement.appendChild( fillElement ); + fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), format.color().name() ) ); + } + + +} diff --git a/src/core/qgsvectorlayerlabeling.h b/src/core/qgsvectorlayerlabeling.h index 0324dd4783a..2b9669cf6ef 100644 --- a/src/core/qgsvectorlayerlabeling.h +++ b/src/core/qgsvectorlayerlabeling.h @@ -19,9 +19,9 @@ #include #include +#include -#include "qgis_core.h" -#include "qgis_sip.h" +#include "qgis.h" class QDomDocument; class QDomElement; @@ -76,6 +76,17 @@ class CORE_EXPORT QgsAbstractVectorLayerLabeling //! Try to create instance of an implementation based on the XML data static QgsAbstractVectorLayerLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ) SIP_FACTORY; + /** + * Writes the SE 1.1 TextSymbolizer element based on the current layer labelling settings + */ + virtual void toSld( QDomNode& parent, const QgsStringMap& props ) const + { + Q_UNUSED( parent ) + Q_UNUSED( props ) + QDomDocument doc = parent.ownerDocument(); + parent.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( type() ) ) ); + } + private: Q_DISABLE_COPY( QgsAbstractVectorLayerLabeling ) @@ -105,6 +116,7 @@ class CORE_EXPORT QgsVectorLayerSimpleLabeling : public QgsAbstractVectorLayerLa virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const override; virtual QgsPalLayerSettings settings( const QString &providerId = QString() ) const override; bool requiresAdvancedEffects() const override; + virtual void toSld( QDomNode& parent, const QgsStringMap& props ) const override; //! Create the instance from a DOM element with saved configuration static QgsVectorLayerSimpleLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ); diff --git a/tests/src/python/test_qgssymbollayer_createsld.py b/tests/src/python/test_qgssymbollayer_createsld.py index cd452cb4116..16e13149974 100644 --- a/tests/src/python/test_qgssymbollayer_createsld.py +++ b/tests/src/python/test_qgssymbollayer_createsld.py @@ -546,6 +546,60 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): ltValue = lt.childNodes().item(1) self.assertEquals(max, ltValue.toElement().text()) + def testSimpleLabelling(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + + dom, root = self.layerToSld(layer) + # print("Simple label text symbolizer" + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + self.assertPropertyName(ts, 'se:Label', 'NAME') + font = self.assertElement(ts, 'se:Font', 0) + self.assertEquals('Liberation Mono', self.assertSvgParameter(font, 'font-family').text()) + self.assertEquals('9', self.assertSvgParameter(font, 'font-size').text()) + + fill = self.assertElement(ts, 'se:Fill', 0) + self.assertEquals('#000000', self.assertSvgParameter(fill, "fill").text()) + + def loadStyleWithCustomProperties(self, layer, qmlFileName): + # load the style, only vector symbology + path = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), qmlFileName)) + + # labelling is in custom properties, they need to be loaded separately + status = layer.loadNamedStyle(path) + doc = QDomDocument() + file = QFile(path) + file.open(QIODevice.ReadOnly) + doc.setContent(file, True) + file.close() + flag = layer.readCustomProperties(doc.documentElement()) + + def assertElement(self, container, elementName, index): + list = container.elementsByTagName(elementName) + self.assertTrue(list.size() > index, 'Expected to find at least ' + str(index + 1) + ' ' + elementName + ' in ' + container.nodeName() + ' but found ' + str(list.size())) + node = list.item(index) + self.assertTrue(node.isElement(), 'Found node but it''s not an element') + return node.toElement() + + def getTextSymbolizer(self, root, ruleIndex, textSymbolizerIndex): + rule = self.assertElement(root, 'se:Rule', ruleIndex) + textSymbolizer = self.assertElement(rule, 'se:TextSymbolizer', textSymbolizerIndex) + return textSymbolizer + + def assertPropertyName(self, root, containerProperty, expectedAttributeName): + container = root.elementsByTagName(containerProperty).item(0).toElement() + property = container.elementsByTagName("ogc:PropertyName").item(0).toElement() + self.assertEqual(expectedAttributeName, property.text()) + + def assertSvgParameter(self, container, expectedName): + list = container.elementsByTagName("se:SvgParameter") + for i in range(0, list.size()): + item = list.item(i) + if item.isElement and item.isElement() and item.toElement().attribute('name') == expectedName: + return item.toElement() + self.fail('Could not find a se:SvgParameter named ' + expectedName + ' in ' + container.nodeName()) + def assertScaleDenominator(self, root, expectedMinScale, expectedMaxScale, index=0): rule = root.elementsByTagName('se:Rule').item(index).toElement() diff --git a/tests/testdata/symbol_layer/simpleLabel.qml b/tests/testdata/symbol_layer/simpleLabel.qml new file mode 100644 index 00000000000..bf4ee3c8fdd --- /dev/null +++ b/tests/testdata/symbol_layer/simpleLabel.qml @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 0 + NAME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + 0 + From 383422f069f9d067eef967abddf01e3da6a1167a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 26 Jul 2017 06:59:55 +1000 Subject: [PATCH 207/266] Fix "wrapped object has been deleted" errors in Processing Ownership of Python subclass algorithm instances was getting mangled due to passing through multiple functions with /Factory/ annotations. As per Phil Thomson's advice on https://www.riverbankcomputing.com/pipermail/pyqt/2017-July/039450.html: " /Factory/ is used when the instance returned is guaranteed to be new to Python. In this case it isn't because it has already been seen when being returned by createInstance(). (However for a different sub-class implemented in C++ then it would be the first time it was seen by Python so the /Factory/ on create() would be correct.) You might try using /TransferBack/ on create() instead - that might be the best compromise. " Changing to /TransferBack/ indeed fixes the error for me. --- .../processing/qgsprocessingalgorithm.sip | 11 +++----- .../core/processing/qgsprocessingregistry.sip | 3 ++- src/core/processing/qgsprocessingalgorithm.h | 27 ++++++++++++------- src/core/processing/qgsprocessingregistry.h | 17 +++++++++++- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index a0a72d869fa..be0c9b2d6ef 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -61,7 +61,8 @@ class QgsProcessingAlgorithm virtual ~QgsProcessingAlgorithm(); - QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const /Factory/; + + QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const /TransferBack/; %Docstring Creates a copy of the algorithm, ready for execution. @@ -360,19 +361,13 @@ class QgsProcessingAlgorithm protected: - virtual QgsProcessingAlgorithm *createInstance() const = 0; + virtual QgsProcessingAlgorithm *createInstance() const = 0 /Factory/; %Docstring Creates a new instance of the algorithm class. This method should return a 'pristine' instance of the algorithm class. :rtype: QgsProcessingAlgorithm %End -%VirtualCatcherCode - PyObject *resObj = sipCallMethod( 0, sipMethod, "" ); - sipIsErr = !resObj || sipParseResult( 0, sipMethod, resObj, "H2", sipType_QgsProcessingAlgorithm, &sipRes ) < 0; - if ( !sipIsErr ) - sipTransferTo( resObj, Py_None ); -%End virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) = 0; %Docstring diff --git a/python/core/processing/qgsprocessingregistry.sip b/python/core/processing/qgsprocessingregistry.sip index 124126858ea..a51c132abc2 100644 --- a/python/core/processing/qgsprocessingregistry.sip +++ b/python/core/processing/qgsprocessingregistry.sip @@ -89,7 +89,8 @@ class QgsProcessingRegistry : QObject :rtype: QgsProcessingAlgorithm %End - QgsProcessingAlgorithm *createAlgorithmById( const QString &id, const QVariantMap &configuration = QVariantMap() ) const /Factory/; + + QgsProcessingAlgorithm *createAlgorithmById( const QString &id, const QVariantMap &configuration = QVariantMap() ) const /TransferBack/; %Docstring Creates a new instance of an algorithm by its ID. If no matching algorithm is found, a None is returned. Callers take responsibility for deleting the returned object. diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 490b520a3c7..13f69a05156 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -91,6 +91,21 @@ class CORE_EXPORT QgsProcessingAlgorithm //! Algorithms cannot be copied- create() should be used instead QgsProcessingAlgorithm &operator=( const QgsProcessingAlgorithm &other ) = delete; + /* + * IMPORTANT: While it seems like /Factory/ would be the correct annotation here, that's not + * the case. + * As per Phil Thomson's advice on https://www.riverbankcomputing.com/pipermail/pyqt/2017-July/039450.html: + * + * " + * /Factory/ is used when the instance returned is guaranteed to be new to Python. + * In this case it isn't because it has already been seen when being returned by createInstance() + * (However for a different sub-class implemented in C++ then it would be the first time it was seen + * by Python so the /Factory/ on create() would be correct.) + * + * You might try using /TransferBack/ on create() instead - that might be the best compromise. + * " + */ + /** * Creates a copy of the algorithm, ready for execution. * @@ -105,7 +120,7 @@ class CORE_EXPORT QgsProcessingAlgorithm * * \see initAlgorithm() */ - QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const SIP_FACTORY; + QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const SIP_TRANSFERBACK; /** * Returns the algorithm name, used for identifying the algorithm. This string @@ -357,15 +372,7 @@ class CORE_EXPORT QgsProcessingAlgorithm * * This method should return a 'pristine' instance of the algorithm class. */ - virtual QgsProcessingAlgorithm *createInstance() const = 0; -#ifdef SIP_RUN - SIP_VIRTUAL_CATCHER_CODE - PyObject *resObj = sipCallMethod( 0, sipMethod, "" ); - sipIsErr = !resObj || sipParseResult( 0, sipMethod, resObj, "H2", sipType_QgsProcessingAlgorithm, &sipRes ) < 0; - if ( !sipIsErr ) - sipTransferTo( resObj, Py_None ); - SIP_END -#endif + virtual QgsProcessingAlgorithm *createInstance() const = 0 SIP_FACTORY; /** * Initializes the algorithm using the specified \a configuration. diff --git a/src/core/processing/qgsprocessingregistry.h b/src/core/processing/qgsprocessingregistry.h index 6ae13599a83..e4fc03de19d 100644 --- a/src/core/processing/qgsprocessingregistry.h +++ b/src/core/processing/qgsprocessingregistry.h @@ -100,6 +100,21 @@ class CORE_EXPORT QgsProcessingRegistry : public QObject */ const QgsProcessingAlgorithm *algorithmById( const QString &id ) const; + /* + * IMPORTANT: While it seems like /Factory/ would be the correct annotation here, that's not + * the case. + * As per Phil Thomson's advice on https://www.riverbankcomputing.com/pipermail/pyqt/2017-July/039450.html: + * + * " + * /Factory/ is used when the instance returned is guaranteed to be new to Python. + * In this case it isn't because it has already been seen when being returned by QgsProcessingAlgorithm::createInstance() + * (However for a different sub-class implemented in C++ then it would be the first time it was seen + * by Python so the /Factory/ on create() would be correct.) + * + * You might try using /TransferBack/ on create() instead - that might be the best compromise. + * " + */ + /** * Creates a new instance of an algorithm by its ID. If no matching algorithm is found, a nullptr * is returned. Callers take responsibility for deleting the returned object. @@ -113,7 +128,7 @@ class CORE_EXPORT QgsProcessingRegistry : public QObject * \see algorithms() * \see algorithmById() */ - QgsProcessingAlgorithm *createAlgorithmById( const QString &id, const QVariantMap &configuration = QVariantMap() ) const SIP_FACTORY; + QgsProcessingAlgorithm *createAlgorithmById( const QString &id, const QVariantMap &configuration = QVariantMap() ) const SIP_TRANSFERBACK; signals: From a95cbe9d2f8b39ab4edb24918d95a767d71cf191 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 26 Jul 2017 09:45:27 +1000 Subject: [PATCH 208/266] Move 'Run as batch' button to bottom of algorithm dialog Inserting it into the tab widget doesn't work well cross platform/between hidpi/lowdpi displays. So instead add it as a normal button in the button box. Also fix capitalization of button text Fixes #16767 --- .../processing/algs/gdal/GdalAlgorithmDialog.py | 15 +++++---------- python/plugins/processing/gui/AlgorithmDialog.py | 11 +++-------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py b/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py index 2251562de67..a3f56c8cf65 100644 --- a/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py +++ b/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py @@ -33,7 +33,8 @@ from qgis.PyQt.QtWidgets import (QWidget, QLineEdit, QComboBox, QCheckBox, - QSizePolicy) + QSizePolicy, + QDialogButtonBox) from qgis.gui import QgsMessageBar @@ -57,15 +58,9 @@ class GdalAlgorithmDialog(AlgorithmDialog): self.setMainWidget(GdalParametersPanel(self, alg)) - cornerWidget = QWidget() - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 5) - self.tabWidget.setStyleSheet("QTabBar::tab { height: 30px; }") - runAsBatchButton = QPushButton(self.tr("Run as batch process...")) - runAsBatchButton.clicked.connect(self.runAsBatch) - layout.addWidget(runAsBatchButton) - cornerWidget.setLayout(layout) - self.tabWidget.setCornerWidget(cornerWidget) + self.runAsBatchButton = QPushButton(self.tr("Run as Batch Process…")) + self.runAsBatchButton.clicked.connect(self.runAsBatch) + self.buttonBox.addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment self.mainWidget.parametersHaveChanged() diff --git a/python/plugins/processing/gui/AlgorithmDialog.py b/python/plugins/processing/gui/AlgorithmDialog.py index c8f758d22d6..4c7d96c14ba 100644 --- a/python/plugins/processing/gui/AlgorithmDialog.py +++ b/python/plugins/processing/gui/AlgorithmDialog.py @@ -30,7 +30,7 @@ from pprint import pformat import time from qgis.PyQt.QtCore import Qt -from qgis.PyQt.QtWidgets import QMessageBox, QApplication, QPushButton, QWidget, QVBoxLayout, QSizePolicy +from qgis.PyQt.QtWidgets import QMessageBox, QApplication, QPushButton, QWidget, QVBoxLayout, QSizePolicy, QDialogButtonBox from qgis.PyQt.QtGui import QCursor, QColor, QPalette from qgis.core import (QgsProject, @@ -83,14 +83,9 @@ class AlgorithmDialog(AlgorithmDialogBase): self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().insertWidget(0, self.bar) - self.cornerWidget = QWidget() - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 5) - self.runAsBatchButton = QPushButton(self.tr("Run as batch process...")) + self.runAsBatchButton = QPushButton(self.tr("Run as Batch Process…")) self.runAsBatchButton.clicked.connect(self.runAsBatch) - layout.addWidget(self.runAsBatchButton) - self.cornerWidget.setLayout(layout) - self.tabWidget.setCornerWidget(self.cornerWidget) + self.buttonBox.addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment def getParametersPanel(self, alg, parent): return ParametersPanel(parent, alg) From a48c1469165b487d0e9dfd9bf4dcac44158911c5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 26 Jul 2017 10:22:57 +1000 Subject: [PATCH 209/266] Fix vector file formats show in processing options for raster output extension (fix #16894) --- python/plugins/processing/algs/gdal/GdalUtils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) mode change 100644 => 100755 python/plugins/processing/algs/gdal/GdalUtils.py diff --git a/python/plugins/processing/algs/gdal/GdalUtils.py b/python/plugins/processing/algs/gdal/GdalUtils.py old mode 100644 new mode 100755 index 396e71f8ec7..3eabb719d0e --- a/python/plugins/processing/algs/gdal/GdalUtils.py +++ b/python/plugins/processing/algs/gdal/GdalUtils.py @@ -45,13 +45,13 @@ from processing.tools.system import isWindows, isMac try: from osgeo import gdal # NOQA + gdalAvailable = True except: gdalAvailable = False class GdalUtils(object): - GDAL_HELP_PATH = 'GDAL_HELP_PATH' supportedRasters = None @@ -106,7 +106,9 @@ class GdalUtils(object): if retry_count < 5: retry_count += 1 else: - raise IOError(e.message + u'\nTried 5 times without success. Last iteration stopped after reading {} line(s).\nLast line(s):\n{}'.format(len(loglines), u'\n'.join(loglines[-10:]))) + raise IOError( + e.message + u'\nTried 5 times without success. Last iteration stopped after reading {} line(s).\nLast line(s):\n{}'.format( + len(loglines), u'\n'.join(loglines[-10:]))) QgsMessageLog.logMessage('\n'.join(loglines), 'Processing', QgsMessageLog.INFO) GdalUtils.consoleOutput = loglines @@ -134,6 +136,10 @@ class GdalUtils(object): continue shortName = driver.ShortName metadata = driver.GetMetadata() + if gdal.DCAP_RASTER not in metadata \ + or metadata[gdal.DCAP_RASTER] != 'YES': + continue + # =================================================================== # if gdal.DCAP_CREATE not in metadata \ # or metadata[gdal.DCAP_CREATE] != 'YES': @@ -183,7 +189,7 @@ class GdalUtils(object): for s in strList: if s and s[0] != '-' and ' ' in s: escaped = '"' + s.replace('\\', '\\\\').replace('"', '\\"') \ - + '"' + + '"' else: escaped = s joined += escaped + ' ' From 11dd4170de9098021992771ec424efe005dbb705 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 26 Jul 2017 14:46:30 +1000 Subject: [PATCH 210/266] Try to balance UI element sizes on windows builds --- python/core/qgis.sip | 7 +++++++ src/core/qgis.cpp | 6 ++++++ src/core/qgis.h | 7 +++++++ src/gui/qgscolorswatchgrid.cpp | 10 +++++----- src/gui/qgscolorwidgets.cpp | 10 +++++----- src/gui/qgsmenuheader.cpp | 11 +++++------ 6 files changed, 35 insertions(+), 16 deletions(-) diff --git a/python/core/qgis.sip b/python/core/qgis.sip index 93a7ed36386..796fbb18338 100644 --- a/python/core/qgis.sip +++ b/python/core/qgis.sip @@ -108,6 +108,13 @@ Default threshold between map coordinates and device coordinates for map2pixel s Default Z coordinate value for 2.5d geometry This value have to be assigned to the Z coordinate for the new 2.5d geometry vertex. .. versionadded:: 3.0 +%End + + static const double UI_SCALE_FACTOR; +%Docstring + UI scaling factor. This should be applied to all widget sizes obtained from font metrics, + to account for differences in the default font sizes across different platforms. +.. versionadded:: 3.0 %End }; diff --git a/src/core/qgis.cpp b/src/core/qgis.cpp index 97674642349..460476ad70d 100644 --- a/src/core/qgis.cpp +++ b/src/core/qgis.cpp @@ -84,6 +84,12 @@ const double Qgis::SCALE_PRECISION = 0.9999999999; const double Qgis::DEFAULT_Z_COORDINATE = 0.0; +#ifdef Q_OS_WIN +const double Qgis::UI_SCALE_FACTOR = 1.5; +#else +const double Qgis::UI_SCALE_FACTOR = 1; +#endif + double qgsPermissiveToDouble( QString string, bool &ok ) { //remove any thousands separators diff --git a/src/core/qgis.h b/src/core/qgis.h index fe57185752d..2bfad1dd7db 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -120,6 +120,13 @@ class CORE_EXPORT Qgis * \since QGIS 3.0 */ static const double DEFAULT_Z_COORDINATE; + /** + * UI scaling factor. This should be applied to all widget sizes obtained from font metrics, + * to account for differences in the default font sizes across different platforms. + * \since QGIS 3.0 + */ + static const double UI_SCALE_FACTOR; + }; // hack to workaround warnings when casting void pointers diff --git a/src/gui/qgscolorswatchgrid.cpp b/src/gui/qgscolorswatchgrid.cpp index 4bc9da7366b..9122282ab48 100644 --- a/src/gui/qgscolorswatchgrid.cpp +++ b/src/gui/qgscolorswatchgrid.cpp @@ -38,14 +38,14 @@ QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme *scheme, const QString &c setFocusPolicy( Qt::StrongFocus ); setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); - mLabelHeight = fontMetrics().height(); - mLabelMargin = fontMetrics().width( QStringLiteral( "." ) ); + mLabelHeight = Qgis::UI_SCALE_FACTOR * fontMetrics().height(); + mLabelMargin = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "." ) ); - mSwatchSize = fontMetrics().width( QStringLiteral( "X" ) ) * 1.75; - mSwatchOutlineSize = qMax( mLabelMargin * 0.4, 1.0 ); + mSwatchSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "X" ) ) * 1.75; + mSwatchOutlineSize = qMax( fontMetrics().width( QStringLiteral( "." ) ) * 0.4, 1.0 ); mSwatchSpacing = mSwatchSize * 0.3; - mSwatchMargin = mLabelMargin * 1.5; + mSwatchMargin = mLabelMargin; //calculate widget width mWidth = NUMBER_COLORS_PER_ROW * mSwatchSize + ( NUMBER_COLORS_PER_ROW - 1 ) * mSwatchSpacing + mSwatchMargin + mSwatchMargin; diff --git a/src/gui/qgscolorwidgets.cpp b/src/gui/qgscolorwidgets.cpp index 1b852d0c52f..978406caedc 100644 --- a/src/gui/qgscolorwidgets.cpp +++ b/src/gui/qgscolorwidgets.cpp @@ -397,7 +397,7 @@ QgsColorWheel::~QgsColorWheel() QSize QgsColorWheel::sizeHint() const { - int size = fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ); + int size = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ); return QSize( size, size ); } @@ -758,7 +758,7 @@ QgsColorBox::~QgsColorBox() QSize QgsColorBox::sizeHint() const { - int size = fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ); + int size = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ); return QSize( size, size ); } @@ -991,12 +991,12 @@ QSize QgsColorRampWidget::sizeHint() const if ( mOrientation == QgsColorRampWidget::Horizontal ) { //horizontal - return QSize( fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), fontMetrics().height() * 1.3 ); + return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3 ); } else { //vertical - return QSize( fontMetrics().height() * 1.3, fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) ); + return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3, Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) ); } } @@ -1598,7 +1598,7 @@ void QgsColorPreviewWidget::paintEvent( QPaintEvent *event ) QSize QgsColorPreviewWidget::sizeHint() const { - return QSize( fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) * 0.75 ); + return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) * 0.75 ); } void QgsColorPreviewWidget::setColor2( const QColor &color ) diff --git a/src/gui/qgsmenuheader.cpp b/src/gui/qgsmenuheader.cpp index c68ba4edd91..ae432ad4ad7 100644 --- a/src/gui/qgsmenuheader.cpp +++ b/src/gui/qgsmenuheader.cpp @@ -25,7 +25,7 @@ QgsMenuHeader::QgsMenuHeader( const QString &text, QWidget *parent ) { int textMinWidth = fontMetrics().width( mText ); mTextHeight = fontMetrics().height(); - mLabelMargin = fontMetrics().width( QStringLiteral( "." ) ); + mLabelMargin = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "." ) ); mMinWidth = 2 * mLabelMargin + textMinWidth; setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ); updateGeometry(); @@ -33,12 +33,12 @@ QgsMenuHeader::QgsMenuHeader( const QString &text, QWidget *parent ) QSize QgsMenuHeader::minimumSizeHint() const { - return QSize( mMinWidth, mTextHeight + 0.5 * mLabelMargin ); + return QSize( mMinWidth, mTextHeight + mLabelMargin ); } QSize QgsMenuHeader::sizeHint() const { - return QSize( mMinWidth, mTextHeight + 0.5 * mLabelMargin ); + return QSize( mMinWidth, mTextHeight + mLabelMargin ); } void QgsMenuHeader::paintEvent( QPaintEvent * ) @@ -51,12 +51,11 @@ void QgsMenuHeader::paintEvent( QPaintEvent * ) //draw header background painter.setBrush( headerBgColor ); painter.setPen( Qt::NoPen ); - painter.drawRect( QRect( 0, 0, width(), mTextHeight + 0.5 * mLabelMargin ) ); + painter.drawRect( QRect( 0, 0, width(), mTextHeight + mLabelMargin ) ); //draw header text painter.setPen( headerTextColor ); - painter.drawText( QRect( mLabelMargin, 0.25 * mLabelMargin, width() - 2 * mLabelMargin, mTextHeight ), - Qt::AlignLeft | Qt::AlignVCenter, mText ); + painter.drawText( QPoint( mLabelMargin, 0.25 * mLabelMargin + mTextHeight ), mText ); painter.end(); } From 8642e88867c5be212f49d73fd3f3f4ce96b0dc61 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 21 Jul 2017 09:50:49 +0100 Subject: [PATCH 211/266] Fix relation reference widget when 'Chain Filters' is activated. Fixes #16903 --- .../qgsrelationreferencewidget.cpp | 81 +++++++++++++------ 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp index dc016f30df6..3796fa0dd32 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp +++ b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp @@ -814,6 +814,29 @@ void QgsRelationReferenceWidget::filterChanged() Q_ASSERT( scb ); + QgsFeature f; + QgsFeatureIds featureIds; + QString filterExpression; + + Q_FOREACH ( QComboBox *cb, mFilterComboBoxes ) + { + if ( cb->currentIndex() != 0 ) + { + const QString fieldName = cb->property( "Field" ).toString(); + + if ( cb->currentText() == nullValue.toString() ) + { + filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName ); + } + else + { + filters << QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() ); + } + attrs << mReferencedLayer->fields().lookupField( fieldName ); + } + } + + bool filtered = false; if ( mChainFilters ) { QComboBox *ccb = nullptr; @@ -834,6 +857,9 @@ void QgsRelationReferenceWidget::filterChanged() } else { + const QString fieldName = cb->property( "Field" ).toString(); + filtered = true; + cb->blockSignals( true ); cb->clear(); cb->addItem( cb->property( "FieldAlias" ).toString() ); @@ -843,8 +869,29 @@ void QgsRelationReferenceWidget::filterChanged() QStringList texts; Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] ) { - texts << txt; + QStringList filtersAttrs = filters; + filtersAttrs << QgsExpression::createFieldEqualityExpression( fieldName, txt ); + QString expression = filtersAttrs.join( QStringLiteral( " AND " ) ); + + QgsAttributeList subset = attrs; + subset << mReferencedLayer->fields().lookupField( fieldName ); + + QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) ); + + bool found = false; + while ( it.nextFeature( f ) ) + { + if ( !featureIds.contains( f.id() ) ) + featureIds << f.id(); + + found = true; + } + + // item is only provided if at least 1 feature exists + if ( found ) + texts << txt; } + texts.sort(); cb->addItems( texts ); @@ -856,36 +903,18 @@ void QgsRelationReferenceWidget::filterChanged() } } - Q_FOREACH ( QComboBox *cb, mFilterComboBoxes ) + if ( !mChainFilters || ( mChainFilters && !filtered ) ) { - if ( cb->currentIndex() != 0 ) + filterExpression = filters.join( QStringLiteral( " AND " ) ); + + QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) ); + + while ( it.nextFeature( f ) ) { - const QString fieldName = cb->property( "Field" ).toString(); - - if ( cb->currentText() == nullValue.toString() ) - { - filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName ); - } - else - { - filters << QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() ); - } - attrs << mReferencedLayer->fields().lookupField( fieldName ); + featureIds << f.id(); } } - QString filterExpression = filters.join( QStringLiteral( " AND " ) ); - - QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) ); - - QgsFeature f; - QgsFeatureIds featureIds; - - while ( it.nextFeature( f ) ) - { - featureIds << f.id(); - } - mFilterModel->setFilteredFeatures( featureIds ); } From 58ea88715df2811ce186a8f4b73934d0327a6f6f Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 21 Jul 2017 14:23:37 +0100 Subject: [PATCH 212/266] Add tests --- .../qgsrelationreferencewidget.cpp | 6 +- .../qgsrelationreferencewidget.h | 2 + tests/src/gui/CMakeLists.txt | 1 + .../gui/testqgsrelationreferencewidget.cpp | 172 ++++++++++++++++++ 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 tests/src/gui/testqgsrelationreferencewidget.cpp diff --git a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp index 3796fa0dd32..76aeab03cd8 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp +++ b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp @@ -198,10 +198,10 @@ void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first ); mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() ); - QgsAttributeEditorContext context( mEditorContext, relation, QgsAttributeEditorContext::Single, QgsAttributeEditorContext::Embed ); if ( mEmbedForm ) { + QgsAttributeEditorContext context( mEditorContext, relation, QgsAttributeEditorContext::Single, QgsAttributeEditorContext::Embed ); mAttributeEditorFrame->setTitle( mReferencedLayer->name() ); mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this ); mAttributeEditorLayout->addWidget( mReferencedAttributeForm ); @@ -467,6 +467,10 @@ void QgsRelationReferenceWidget::init() Q_FOREACH ( const QString &fieldName, mFilterFields ) { int idx = mReferencedLayer->fields().lookupField( fieldName ); + + if ( idx == -1 ) + continue; + QComboBox *cb = new QComboBox(); cb->setProperty( "Field", fieldName ); cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) ); diff --git a/src/gui/editorwidgets/qgsrelationreferencewidget.h b/src/gui/editorwidgets/qgsrelationreferencewidget.h index 17ee560c6d4..893406576d0 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidget.h +++ b/src/gui/editorwidgets/qgsrelationreferencewidget.h @@ -241,6 +241,8 @@ class GUI_EXPORT QgsRelationReferenceWidget : public QWidget QVBoxLayout *mAttributeEditorLayout = nullptr; QLineEdit *mLineEdit = nullptr; QLabel *mInvalidLabel = nullptr; + + friend class TestQgsRelationReferenceWidget; }; #endif // QGSRELATIONREFERENCEWIDGET_H diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt index 5be12347d04..4ab85dc32c8 100644 --- a/tests/src/gui/CMakeLists.txt +++ b/tests/src/gui/CMakeLists.txt @@ -136,3 +136,4 @@ ADD_QGIS_TEST(filedownloader testqgsfiledownloader.cpp) ADD_QGIS_TEST(composergui testqgscomposergui.cpp) ADD_QGIS_TEST(layoutview testqgslayoutview.cpp) ADD_QGIS_TEST(valuerelationwidgetwrapper testqgsvaluerelationwidgetwrapper.cpp) +ADD_QGIS_TEST(relationreferencewidget testqgsrelationreferencewidget.cpp) diff --git a/tests/src/gui/testqgsrelationreferencewidget.cpp b/tests/src/gui/testqgsrelationreferencewidget.cpp new file mode 100644 index 00000000000..9910c2c7813 --- /dev/null +++ b/tests/src/gui/testqgsrelationreferencewidget.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + testqgsrelationreferencewidget.cpp + -------------------------------------- + Date : 21 07 2017 + Copyright : (C) 2017 Paul Blottiere + Email : paul dot blottiere at oslandia 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 "qgstest.h" + +#include +#include +#include "qgseditorwidgetwrapper.h" +#include +#include +#include +#include +#include +#include "qgsgui.h" + +class TestQgsRelationReferenceWidget : public QObject +{ + Q_OBJECT + public: + TestQgsRelationReferenceWidget() {} + + private slots: + void initTestCase(); // will be called before the first testfunction is executed. + void cleanupTestCase(); // will be called after the last testfunction was executed. + void init(); // will be called before each testfunction is executed. + void cleanup(); // will be called after every testfunction. + + void testChainFilter(); +}; + +void TestQgsRelationReferenceWidget::initTestCase() +{ + QgsApplication::init(); + QgsApplication::initQgis(); + QgsGui::editorWidgetRegistry()->initEditors(); +} + +void TestQgsRelationReferenceWidget::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsRelationReferenceWidget::init() +{ +} + +void TestQgsRelationReferenceWidget::cleanup() +{ +} + +void TestQgsRelationReferenceWidget::testChainFilter() +{ + // create layers + QgsVectorLayer vl1( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=fk:int" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) ); + QgsVectorLayer vl2( QStringLiteral( "LineString?field=pk:int&field=material:string&field=diameter:int&field=raccord:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) ); + QgsProject::instance()->addMapLayer( &vl1, false, false ); + QgsProject::instance()->addMapLayer( &vl2, false, false ); + + // create a relation between them + QgsRelation relation; + relation.setId( QStringLiteral( "vl1.vl2" ) ); + relation.setName( QStringLiteral( "vl1.vl2" ) ); + relation.setReferencingLayer( vl1.id() ); + relation.setReferencedLayer( vl2.id() ); + relation.addFieldPair( "fk", "pk" ); + QVERIFY( relation.isValid() ); + QgsProject::instance()->relationManager()->addRelation( relation ); + + // add features + QgsFeature ft0( vl1.fields() ); + ft0.setAttribute( QStringLiteral( "pk" ), 0 ); + ft0.setAttribute( QStringLiteral( "fk" ), 0 ); + vl1.startEditing(); + vl1.addFeature( ft0 ); + vl1.commitChanges(); + + QgsFeature ft1( vl1.fields() ); + ft1.setAttribute( QStringLiteral( "pk" ), 1 ); + ft1.setAttribute( QStringLiteral( "fk" ), 1 ); + vl1.startEditing(); + vl1.addFeature( ft1 ); + vl1.commitChanges(); + + QgsFeature ft2( vl2.fields() ); + ft2.setAttribute( QStringLiteral( "pk" ), 10 ); + ft2.setAttribute( QStringLiteral( "material" ), "iron" ); + ft2.setAttribute( QStringLiteral( "diameter" ), 120 ); + ft2.setAttribute( QStringLiteral( "raccord" ), "brides" ); + vl2.startEditing(); + vl2.addFeature( ft2 ); + vl2.commitChanges(); + + QgsFeature ft3( vl2.fields() ); + ft3.setAttribute( QStringLiteral( "pk" ), 11 ); + ft3.setAttribute( QStringLiteral( "material" ), "iron" ); + ft3.setAttribute( QStringLiteral( "diameter" ), 120 ); + ft3.setAttribute( QStringLiteral( "raccord" ), "sleeve" ); + vl2.startEditing(); + vl2.addFeature( ft3 ); + vl2.commitChanges(); + + QgsFeature ft4( vl2.fields() ); + ft4.setAttribute( QStringLiteral( "pk" ), 12 ); + ft4.setAttribute( QStringLiteral( "material" ), "steel" ); + ft4.setAttribute( QStringLiteral( "diameter" ), 120 ); + ft4.setAttribute( QStringLiteral( "raccord" ), "collar" ); + vl2.startEditing(); + vl2.addFeature( ft4 ); + vl2.commitChanges(); + + // init a relation reference widget + QStringList filterFields = { "material", "diameter", "raccord" }; + + QgsRelationReferenceWidget w( new QWidget() ); + w.setChainFilters( true ); + w.setFilterFields( filterFields ); + w.setRelation( relation, true ); + w.init(); + + // check default status for comboboxes + QList cbs = w.mFilterComboBoxes; + QCOMPARE( cbs.count(), 3 ); + Q_FOREACH ( const QComboBox *cb, cbs ) + { + if ( cb->currentText() == "raccord" ) + QCOMPARE( cb->count(), 5 ); + else if ( cb->currentText() == "material" ) + QCOMPARE( cb->count(), 4 ); + else if ( cb->currentText() == "diameter" ) + QCOMPARE( cb->count(), 3 ); + } + + // set first filter + cbs[0]->setCurrentIndex( cbs[0]->findText( "iron" ) ); + cbs[1]->setCurrentIndex( cbs[1]->findText( "120" ) ); + + Q_FOREACH ( const QComboBox *cb, cbs ) + { + if ( cb->itemText( 0 ) == "material" ) + QCOMPARE( cb->count(), 4 ); + else if ( cb->itemText( 0 ) == "diameter" ) + QCOMPARE( cb->count(), 2 ); + else if ( cb->itemText( 0 ) == "raccord" ) + { + QStringList items; + for ( int i = 0; i < cb->count(); i++ ) + items << cb->itemText( i ); + + QCOMPARE( cb->count(), 3 ); + QCOMPARE( items.contains( "collar" ), false ); + // collar should not be available in combobox as there's no existing + // feature with the filter expression: + // "material" == 'iron' AND "diameter" == '120' AND "raccord" = 'collar' + } + } +} + +QGSTEST_MAIN( TestQgsRelationReferenceWidget ) +#include "testqgsrelationreferencewidget.moc" From 5cfed129b5a006e63af8757ad29e777e3151fd9e Mon Sep 17 00:00:00 2001 From: Andrea Aime Date: Fri, 16 Jun 2017 15:44:51 +0200 Subject: [PATCH 213/266] Ading all other labelling options supported by SLD. fixes #8925 --- python/core/qgsvectorlayerlabeling.sip | 6 + .../core/symbology-ng/qgssymbollayerutils.sip | 8 + src/core/qgsvectorlayer.cpp | 5 +- src/core/qgsvectorlayerlabeling.cpp | 421 +++++++- src/core/qgsvectorlayerlabeling.h | 6 +- src/core/symbology-ng/qgssymbollayerutils.cpp | 45 +- src/core/symbology-ng/qgssymbollayerutils.h | 8 + .../python/test_qgssymbollayer_createsld.py | 714 ++++++++++++-- tests/testdata/symbol_layer/lineLabel.qml | 242 +++++ tests/testdata/symbol_layer/polygonLabel.qml | 910 ++++++++++++++++++ tests/testdata/symbol_layer/simpleLabel.qml | 4 +- 11 files changed, 2295 insertions(+), 74 deletions(-) create mode 100644 tests/testdata/symbol_layer/lineLabel.qml create mode 100644 tests/testdata/symbol_layer/polygonLabel.qml diff --git a/python/core/qgsvectorlayerlabeling.sip b/python/core/qgsvectorlayerlabeling.sip index 32540309ea6..272be98c2fe 100644 --- a/python/core/qgsvectorlayerlabeling.sip +++ b/python/core/qgsvectorlayerlabeling.sip @@ -76,6 +76,11 @@ Try to create instance of an implementation based on the XML data :rtype: QgsAbstractVectorLayerLabeling %End + virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const; +%Docstring + Writes the SE 1.1 TextSymbolizer element based on the current layer labeling settings +%End + private: QgsAbstractVectorLayerLabeling( const QgsAbstractVectorLayerLabeling &rhs ); }; @@ -105,6 +110,7 @@ Constructs simple labeling configuration with given initial settings virtual QgsPalLayerSettings settings( const QString &providerId = QString() ) const; virtual bool requiresAdvancedEffects() const; + virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const; static QgsVectorLayerSimpleLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ); %Docstring diff --git a/python/core/symbology-ng/qgssymbollayerutils.sip b/python/core/symbology-ng/qgssymbollayerutils.sip index 0a0d68a29f8..78af8cd9619 100644 --- a/python/core/symbology-ng/qgssymbollayerutils.sip +++ b/python/core/symbology-ng/qgssymbollayerutils.sip @@ -481,6 +481,14 @@ Create ogr feature style string for pen :rtype: bool %End + static void createAnchorPointElement( QDomDocument &doc, QDomElement &element, QPointF anchor ); +%Docstring + Creates a SE 1.1 anchor point element as a child of the specified element + \param doc The document + \param element The parent element + \param anchor An anchor specification, with values between 0 and 1 +%End + static void createOnlineResourceElement( QDomDocument &doc, QDomElement &element, const QString &path, const QString &format ); static bool onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format ); %Docstring diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 2eb8e49e246..da6c9a95f13 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -2211,7 +2211,8 @@ bool QgsVectorLayer::writeSld( QDomNode &node, QDomDocument &doc, QString &error QgsSymbolLayerUtils::mergeScaleDependencies( maximumScale(), minimumScale(), localProps ); } - if ( isSpatial() ) { + if ( isSpatial() ) + { // store the Name element QDomElement nameNode = doc.createElement( "se:Name" ); nameNode.appendChild( doc.createTextNode( name() ) ); @@ -2229,7 +2230,7 @@ bool QgsVectorLayer::writeSld( QDomNode &node, QDomDocument &doc, QString &error userStyleElem.appendChild( featureTypeStyleElem ); mRenderer->toSld( doc, featureTypeStyleElem, localProps ); - if ( mLabeling != nullptr ) + if ( labelsEnabled() ) { mLabeling->toSld( featureTypeStyleElem, localProps ); } diff --git a/src/core/qgsvectorlayerlabeling.cpp b/src/core/qgsvectorlayerlabeling.cpp index 252fe1ca6cd..17e8e6486ac 100644 --- a/src/core/qgsvectorlayerlabeling.cpp +++ b/src/core/qgsvectorlayerlabeling.cpp @@ -18,6 +18,8 @@ #include "qgsrulebasedlabeling.h" #include "qgsvectorlayer.h" #include "qgssymbollayerutils.h" +#include "qgssymbollayer.h" +#include "qgsmarkersymbollayer.h" #include "qgis.h" @@ -91,6 +93,134 @@ QgsVectorLayerSimpleLabeling *QgsVectorLayerSimpleLabeling::create( const QDomEl return new QgsVectorLayerSimpleLabeling( QgsPalLayerSettings() ); } +QPointF quadOffsetToSldAnchor( QgsPalLayerSettings::QuadrantPosition quadrantPosition ) +{ + double quadOffsetX = 0.5, quadOffsetY = 0.5; + + // adjust quadrant offset of labels + switch ( quadrantPosition ) + { + case QgsPalLayerSettings::QuadrantAboveLeft: + quadOffsetX = 1; + quadOffsetY = 0; + break; + case QgsPalLayerSettings::QuadrantAbove: + quadOffsetX = 0.5; + quadOffsetY = 0; + break; + case QgsPalLayerSettings::QuadrantAboveRight: + quadOffsetX = 0; + quadOffsetY = 0; + break; + case QgsPalLayerSettings::QuadrantLeft: + quadOffsetX = 1; + quadOffsetY = 0.5; + break; + case QgsPalLayerSettings::QuadrantRight: + quadOffsetX = 0; + quadOffsetY = 0.5; + break; + case QgsPalLayerSettings::QuadrantBelowLeft: + quadOffsetX = 1; + quadOffsetY = 1; + break; + case QgsPalLayerSettings::QuadrantBelow: + quadOffsetX = 0.5; + quadOffsetY = 1; + break; + case QgsPalLayerSettings::QuadrantBelowRight: + quadOffsetX = 0; + quadOffsetY = 1.0; + break; + case QgsPalLayerSettings::QuadrantOver: + break; + } + + return QPointF( quadOffsetX, quadOffsetY ); +} + +/* + * This is not a generic function encoder, just enough to encode the label case control functions + */ +void appendSimpleFunction( QDomDocument &doc, QDomElement &parent, const QString &name, const QString &attribute ) +{ + QDomElement function = doc.createElement( QStringLiteral( "ogc:Function" ) ); + function.setAttribute( QStringLiteral( "name" ), name ); + parent.appendChild( function ); + QDomElement property = doc.createElement( QStringLiteral( "ogc:PropertyName" ) ); + property.appendChild( doc.createTextNode( attribute ) ); + function.appendChild( property ); +} + +std::unique_ptr backgroundToMarkerLayer( const QgsTextBackgroundSettings &settings ) +{ + std::unique_ptr layer; + switch ( settings.type() ) + { + case QgsTextBackgroundSettings::ShapeSVG: + { + QgsSvgMarkerSymbolLayer *svg = new QgsSvgMarkerSymbolLayer( settings.svgFile() ); + svg->setStrokeWidth( settings.strokeWidth() ); + svg->setStrokeWidthUnit( settings.strokeWidthUnit() ); + layer.reset( svg ); + break; + } + case QgsTextBackgroundSettings::ShapeCircle: + case QgsTextBackgroundSettings::ShapeEllipse: + case QgsTextBackgroundSettings::ShapeRectangle: + case QgsTextBackgroundSettings::ShapeSquare: + { + QgsSimpleMarkerSymbolLayer *marker = new QgsSimpleMarkerSymbolLayer(); + // default value + QgsSimpleMarkerSymbolLayerBase::Shape shape = shape = QgsSimpleMarkerSymbolLayerBase::Diamond; + switch ( settings.type() ) + { + case QgsTextBackgroundSettings::ShapeCircle: + case QgsTextBackgroundSettings::ShapeEllipse: + shape = QgsSimpleMarkerSymbolLayerBase::Circle; + break; + case QgsTextBackgroundSettings::ShapeRectangle: + case QgsTextBackgroundSettings::ShapeSquare: + shape = QgsSimpleMarkerSymbolLayerBase::Square; + break; + case QgsTextBackgroundSettings::ShapeSVG: + break; + } + + marker->setShape( shape ); + marker->setStrokeWidth( settings.strokeWidth() ); + marker->setStrokeWidthUnit( settings.strokeWidthUnit() ); + layer.reset( marker ); + } + } + layer->setEnabled( true ); + // a marker does not have a size x and y, just a size (and it should be at least one) + QSizeF size = settings.size(); + layer->setSize( qMax( 1., qMax( size.width(), size.height() ) ) ); + layer->setSizeUnit( settings.sizeUnit() ); + // fill and stroke + QColor fillColor = settings.fillColor(); + QColor strokeColor = settings.strokeColor(); + if ( settings.opacity() < 1 ) + { + int alpha = qRound( settings.opacity() * 255 ); + fillColor.setAlpha( alpha ); + strokeColor.setAlpha( alpha ); + } + layer->setFillColor( fillColor ); + layer->setStrokeColor( strokeColor ); + // rotation + if ( settings.rotationType() == QgsTextBackgroundSettings::RotationFixed ) + { + layer->setAngle( settings.rotation() ); + } + // offset + layer->setOffset( settings.offset() ); + layer->setOffsetUnit( settings.offsetUnit() ); + + return layer; +} + void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const { @@ -101,13 +231,24 @@ void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap & QDomElement ruleElement = doc.createElement( QStringLiteral( "se:Rule" ) ); parent.appendChild( ruleElement ); + // scale dependencies + if ( mSettings->scaleVisibility ) + { + QgsStringMap scaleProps = QgsStringMap(); + scaleProps.insert( "scaleMinDenom", qgsDoubleToString( mSettings->minimumScale ) ); + scaleProps.insert( "scaleMaxDenom", qgsDoubleToString( mSettings->maximumScale ) ); + QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, scaleProps ); + } + + // text symbolizer QDomElement textSymbolizerElement = doc.createElement( QStringLiteral( "se:TextSymbolizer" ) ); ruleElement.appendChild( textSymbolizerElement ); // label + QgsTextFormat format = mSettings->format(); + QFont font = format.font(); QDomElement labelElement = doc.createElement( QStringLiteral( "se:Label" ) ); textSymbolizerElement.appendChild( labelElement ); - if ( mSettings->isExpression ) { labelElement.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( mSettings->getLabelExpression()->dump() ) ) ); @@ -115,24 +256,292 @@ void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap & } else { - QDomElement propertyNameElement = doc.createElement( QStringLiteral( "ogc:PropertyName" ) ); - propertyNameElement.appendChild( doc.createTextNode( mSettings->fieldName ) ); - labelElement.appendChild( propertyNameElement ); + if ( font.capitalization() == QFont::AllUppercase ) + { + appendSimpleFunction( doc, labelElement, QStringLiteral( "strToUpperCase" ), mSettings->fieldName ); + } + else if ( font.capitalization() == QFont::AllLowercase ) + { + appendSimpleFunction( doc, labelElement, QStringLiteral( "strToLowerCase" ), mSettings->fieldName ); + } + else if ( font.capitalization() == QFont::Capitalize ) + { + appendSimpleFunction( doc, labelElement, QStringLiteral( "strCapitalize" ), mSettings->fieldName ); + } + else + { + QDomElement propertyNameElement = doc.createElement( QStringLiteral( "ogc:PropertyName" ) ); + propertyNameElement.appendChild( doc.createTextNode( mSettings->fieldName ) ); + labelElement.appendChild( propertyNameElement ); + } } // font QDomElement fontElement = doc.createElement( QStringLiteral( "se:Font" ) ); textSymbolizerElement.appendChild( fontElement ); - QgsTextFormat format = mSettings->format(); - fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-family" ), format.font().family() ) ); + fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-family" ), font.family() ) ); double fontSize = QgsSymbolLayerUtils::rescaleUom( format.size(), format.sizeUnit(), props ); fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-size" ), QString::number( fontSize ) ) ); + if ( format.font().italic() ) + { + fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-style" ), QStringLiteral( "italic" ) ) ); + } + if ( format.font().bold() ) + { + fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-weight" ), QStringLiteral( "bold" ) ) ); + } + // label placement + QDomElement labelPlacement = doc.createElement( QStringLiteral( "se:LabelPlacement" ) ); + textSymbolizerElement.appendChild( labelPlacement ); + double maxDisplacement = 0; + double repeatDistance = 0; + switch ( mSettings->placement ) + { + case QgsPalLayerSettings::OverPoint: + { + QDomElement pointPlacement = doc.createElement( "se:PointPlacement" ); + labelPlacement.appendChild( pointPlacement ); + // anchor point + QPointF anchor = quadOffsetToSldAnchor( mSettings->quadOffset ); + QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, anchor ); + // displacement + if ( mSettings->xOffset > 0 || mSettings->yOffset > 0 ) + { + QgsUnitTypes::RenderUnit offsetUnit = mSettings->offsetUnits; + double dx = QgsSymbolLayerUtils::rescaleUom( mSettings->xOffset, offsetUnit, props ); + double dy = QgsSymbolLayerUtils::rescaleUom( mSettings->yOffset, offsetUnit, props ); + QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( dx, dy ) ); + } + // rotation + if ( mSettings->angleOffset != 0 ) + { + QDomElement rotation = doc.createElement( "se:Rotation" ); + pointPlacement.appendChild( rotation ); + rotation.appendChild( doc.createTextNode( QString::number( mSettings->angleOffset ) ) ); + } + } + break; + case QgsPalLayerSettings::AroundPoint: + case QgsPalLayerSettings::OrderedPositionsAroundPoint: + { + QDomElement pointPlacement = doc.createElement( "se:PointPlacement" ); + labelPlacement.appendChild( pointPlacement ); + + // SLD cannot do either, but let's do a best effort setting the distance using + // anchor point and displacement + QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0, 0.5 ) ); + QgsUnitTypes::RenderUnit distUnit = mSettings->distUnits; + double radius = QgsSymbolLayerUtils::rescaleUom( mSettings->dist, distUnit, props ); + double offset = sqrt( radius * radius / 2 ); // make it start top/right + maxDisplacement = radius + 1; // lock the distance + QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( offset, offset ) ); + } + break; + case QgsPalLayerSettings::Horizontal: + case QgsPalLayerSettings::Free: + { + // still a point placement (for "free" it's a fallback, there is no SLD equivalent) + QDomElement pointPlacement = doc.createElement( "se:PointPlacement" ); + labelPlacement.appendChild( pointPlacement ); + QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0.5, 0.5 ) ); + QgsUnitTypes::RenderUnit distUnit = mSettings->distUnits; + double dist = QgsSymbolLayerUtils::rescaleUom( mSettings->dist, distUnit, props ); + QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( 0, dist ) ); + break; + } + case QgsPalLayerSettings::Line: + case QgsPalLayerSettings::Curved: + case QgsPalLayerSettings::PerimeterCurved: + { + QDomElement linePlacement = doc.createElement( "se:LinePlacement" ); + labelPlacement.appendChild( linePlacement ); + + // perpendicular distance if required + if ( mSettings->dist > 0 ) + { + QgsUnitTypes::RenderUnit distUnit = mSettings->distUnits; + double dist = QgsSymbolLayerUtils::rescaleUom( mSettings->dist, distUnit, props ); + QDomElement perpendicular = doc.createElement( "se:PerpendicularOffset" ); + linePlacement.appendChild( perpendicular ); + perpendicular.appendChild( doc.createTextNode( qgsDoubleToString( dist, 2 ) ) ); + } + + // repeat distance if required + if ( mSettings->repeatDistance > 0 ) + { + QDomElement repeat = doc.createElement( "se:Repeat" ); + linePlacement.appendChild( repeat ); + repeat.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) ); + QDomElement gap = doc.createElement( "se:Gap" ); + linePlacement.appendChild( gap ); + repeatDistance = QgsSymbolLayerUtils::rescaleUom( mSettings->repeatDistance, mSettings->repeatDistanceUnit, props ); + gap.appendChild( doc.createTextNode( qgsDoubleToString( repeatDistance, 2 ) ) ); + } + + // always generalized + QDomElement generalize = doc.createElement( "se:GeneralizeLine" ); + linePlacement.appendChild( generalize ); + generalize.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) ); + } + break; + } + + // halo + QgsTextBufferSettings buffer = format.buffer(); + if ( buffer.enabled() ) + { + QDomElement haloElement = doc.createElement( QStringLiteral( "se:Halo" ) ); + textSymbolizerElement.appendChild( haloElement ); + + QDomElement radiusElement = doc.createElement( QStringLiteral( "se:Radius" ) ); + haloElement.appendChild( radiusElement ); + // the SLD uses a radius, which is actually half of the link thickness the buffer size specifies + double radius = QgsSymbolLayerUtils::rescaleUom( buffer.size(), buffer.sizeUnit(), props ) / 2; + radiusElement.appendChild( doc.createTextNode( qgsDoubleToString( radius ) ) ); + + QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) ); + haloElement.appendChild( fillElement ); + fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), buffer.color().name() ) ); + if ( buffer.opacity() != 1 ) + { + fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( buffer.opacity() ) ) ); + } + } // fill QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) ); textSymbolizerElement.appendChild( fillElement ); fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), format.color().name() ) ); + if ( format.opacity() != 1 ) + { + fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( format.opacity() ) ) ); + } + + // background graphic (not supported by SE 1.1, but supported by the GeoTools ecosystem as an extension) + QgsTextBackgroundSettings background = format.background(); + if ( background.enabled() ) + { + std::unique_ptr layer = backgroundToMarkerLayer( background ); + layer->writeSldMarker( doc, textSymbolizerElement, props ); + } + + // priority and zIndex, the default values are 0 and 5 in qgis (and between 0 and 10), + // in the GeoTools ecosystem there is a single priority value set at 1000 by default + if ( mSettings->priority != 5 || mSettings->zIndex > 0 ) + { + QDomElement priorityElement = doc.createElement( QStringLiteral( "se:Priority" ) ); + textSymbolizerElement.appendChild( priorityElement ); + int priority = 500 + 1000 * mSettings->zIndex + ( mSettings->priority - 5 ) * 100; + if ( mSettings->priority == 0 && mSettings->zIndex > 0 ) + { + // small adjustment to make sure labels in z index n+1 are all above level n despite the priority value + priority += 1; + } + priorityElement.appendChild( doc.createTextNode( QString::number( priority ) ) ); + } + + // vendor options for text appearance + if ( font.underline() ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "underlineText" ), QStringLiteral( "true" ) ); + textSymbolizerElement.appendChild( vo ); + } + if ( font.strikeOut() ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "strikethroughText" ), QStringLiteral( "true" ) ); + textSymbolizerElement.appendChild( vo ); + } + // vendor options for text positioning + if ( maxDisplacement > 0 ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxDisplacement" ), qgsDoubleToString( maxDisplacement, 2 ) ); + textSymbolizerElement.appendChild( vo ); + } + if ( mSettings->placement == QgsPalLayerSettings::Curved || mSettings->placement == QgsPalLayerSettings::PerimeterCurved ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "followLine" ), QStringLiteral( "true" ) ); + textSymbolizerElement.appendChild( vo ); + if ( mSettings->maxCurvedCharAngleIn > 0 || mSettings->maxCurvedCharAngleOut > 0 ) + { + // SLD has no notion for this, the GeoTools ecosystem can only do a single angle + double angle = qMin( qAbs( mSettings->maxCurvedCharAngleIn ), qAbs( mSettings->maxCurvedCharAngleOut ) ); + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxAngleDelta" ), qgsDoubleToString( angle ) ); + textSymbolizerElement.appendChild( vo ); + } + } + if ( repeatDistance > 0 ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "repeat" ), qgsDoubleToString( repeatDistance, 2 ) ); + textSymbolizerElement.appendChild( vo ); + } + // miscellaneous options + if ( mSettings->displayAll ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "conflictResolution" ), QStringLiteral( "false" ) ); + textSymbolizerElement.appendChild( vo ); + } + if ( mSettings->upsidedownLabels == QgsPalLayerSettings::ShowAll ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "forceLeftToRight" ), QStringLiteral( "false" ) ); + textSymbolizerElement.appendChild( vo ); + } + if ( mSettings->mergeLines ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "group" ), QStringLiteral( "yes" ) ); + textSymbolizerElement.appendChild( vo ); + if ( mSettings->labelPerPart ) + { + QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "labelAllGroup" ), QStringLiteral( "true" ) ); + textSymbolizerElement.appendChild( vo ); + } + } + // background symbol resize handling + if ( background.enabled() ) + { + // enable resizing if needed + switch ( background.sizeType() ) + { + case QgsTextBackgroundSettings::SizeBuffer: + { + QString resizeType; + if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle || background.type() == QgsTextBackgroundSettings::ShapeEllipse ) + { + resizeType = QStringLiteral( "stretch" ); + } + else + { + resizeType = QStringLiteral( "proportional" ); + } + QDomElement voResize = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-resize" ), resizeType ); + textSymbolizerElement.appendChild( voResize ); + + // now hadle margin + QSizeF size = background.size(); + if ( size.width() > 0 || size.height() > 0 ) + { + double x = QgsSymbolLayerUtils::rescaleUom( size.width(), background.sizeUnit(), props ); + double y = QgsSymbolLayerUtils::rescaleUom( size.height(), background.sizeUnit(), props ); + // in case of ellipse qgis pads the size generously to make sure the text is inside the ellipse + // the following seems to do the trick and keep visual output similar + if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse ) + { + x += fontSize / 2; + y += fontSize; + } + QString resizeSpec = QString( "%1 %2" ).arg( qgsDoubleToString( x, 2 ) ).arg( qgsDoubleToString( y, 2 ) ); + QDomElement voMargin = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), resizeSpec ); + textSymbolizerElement.appendChild( voMargin ); + } + break; + } + case QgsTextBackgroundSettings::SizeFixed: + case QgsTextBackgroundSettings::SizePercent: + // nothing to do here + break; + } + } + } diff --git a/src/core/qgsvectorlayerlabeling.h b/src/core/qgsvectorlayerlabeling.h index 2b9669cf6ef..1ae0dd7a99c 100644 --- a/src/core/qgsvectorlayerlabeling.h +++ b/src/core/qgsvectorlayerlabeling.h @@ -77,9 +77,9 @@ class CORE_EXPORT QgsAbstractVectorLayerLabeling static QgsAbstractVectorLayerLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ) SIP_FACTORY; /** - * Writes the SE 1.1 TextSymbolizer element based on the current layer labelling settings + * Writes the SE 1.1 TextSymbolizer element based on the current layer labeling settings */ - virtual void toSld( QDomNode& parent, const QgsStringMap& props ) const + virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const { Q_UNUSED( parent ) Q_UNUSED( props ) @@ -116,7 +116,7 @@ class CORE_EXPORT QgsVectorLayerSimpleLabeling : public QgsAbstractVectorLayerLa virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const override; virtual QgsPalLayerSettings settings( const QString &providerId = QString() ) const override; bool requiresAdvancedEffects() const override; - virtual void toSld( QDomNode& parent, const QgsStringMap& props ) const override; + virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const override; //! Create the instance from a DOM element with saved configuration static QgsVectorLayerSimpleLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ); diff --git a/src/core/symbology-ng/qgssymbollayerutils.cpp b/src/core/symbology-ng/qgssymbollayerutils.cpp index eebd35a3b5c..7f3e62ceff9 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.cpp +++ b/src/core/symbology-ng/qgssymbollayerutils.cpp @@ -2313,15 +2313,32 @@ void QgsSymbolLayerUtils::createDisplacementElement( QDomDocument &doc, QDomElem element.appendChild( displacementElem ); QDomElement dispXElem = doc.createElement( QStringLiteral( "se:DisplacementX" ) ); - dispXElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.x() ) ) ); + dispXElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.x(), 2 ) ) ); QDomElement dispYElem = doc.createElement( QStringLiteral( "se:DisplacementY" ) ); - dispYElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.y() ) ) ); + dispYElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.y(), 2 ) ) ); displacementElem.appendChild( dispXElem ); displacementElem.appendChild( dispYElem ); } +void QgsSymbolLayerUtils::createAnchorPointElement( QDomDocument &doc, QDomElement &element, QPointF anchor ) +{ + // anchor is not tested for null, (0,0) is _not_ the default value (0.5, 0) is. + + QDomElement anchorElem = doc.createElement( QStringLiteral( "se:AnchorPoint" ) ); + element.appendChild( anchorElem ); + + QDomElement anchorXElem = doc.createElement( QStringLiteral( "se:AnchorPointX" ) ); + anchorXElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.x() ) ) ); + + QDomElement anchorYElem = doc.createElement( QStringLiteral( "se:AnchorPointY" ) ); + anchorYElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.y() ) ) ); + + anchorElem.appendChild( anchorXElem ); + anchorElem.appendChild( anchorYElem ); +} + bool QgsSymbolLayerUtils::displacementFromSldElement( QDomElement &element, QPointF &offset ) { offset = QPointF( 0, 0 ); @@ -2656,7 +2673,7 @@ QgsStringMap QgsSymbolLayerUtils::getSvgParameterList( QDomElement &element ) QDomElement QgsSymbolLayerUtils::createVendorOptionElement( QDomDocument &doc, const QString &name, const QString &value ) { - QDomElement nodeElem = doc.createElement( QStringLiteral( "VendorOption" ) ); + QDomElement nodeElem = doc.createElement( QStringLiteral( "se:VendorOption" ) ); nodeElem.setAttribute( QStringLiteral( "name" ), name ); nodeElem.appendChild( doc.createTextNode( value ) ); return nodeElem; @@ -4028,8 +4045,26 @@ double QgsSymbolLayerUtils::rescaleUom( double size, QgsUnitTypes::RenderUnit un scale = 1 / 0.28; roundToUnit = true; break; - // we don't have a good case for map units, as pixel values won't change based on zoom - default: + case QgsUnitTypes::RenderInches: + scale = 1 / 0.28 * 25.4; + roundToUnit = true; + break; + case QgsUnitTypes::RenderPoints: + scale = 90. /* dots per inch according to OGC SLD */ / 72. /* points per inch */; + roundToUnit = true; + break; + case QgsUnitTypes::RenderPixels: + // pixel is pixel + scale = 1; + break; + case QgsUnitTypes::RenderMapUnits: + case QgsUnitTypes::RenderMetersInMapUnits: + // already handed via uom + scale = 1; + break; + case QgsUnitTypes::RenderPercentage: + case QgsUnitTypes::RenderUnknownUnit: + // these do not make sense and should not really reach here scale = 1; } } diff --git a/src/core/symbology-ng/qgssymbollayerutils.h b/src/core/symbology-ng/qgssymbollayerutils.h index 45435e04887..afba020689e 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.h +++ b/src/core/symbology-ng/qgssymbollayerutils.h @@ -332,6 +332,14 @@ class CORE_EXPORT QgsSymbolLayerUtils static void createDisplacementElement( QDomDocument &doc, QDomElement &element, QPointF offset ); static bool displacementFromSldElement( QDomElement &element, QPointF &offset ); + /** + * \brief Creates a SE 1.1 anchor point element as a child of the specified element + * \param doc The document + * \param element The parent element + * \param anchor An anchor specification, with values between 0 and 1 + */ + static void createAnchorPointElement( QDomDocument &doc, QDomElement &element, QPointF anchor ); + static void createOnlineResourceElement( QDomDocument &doc, QDomElement &element, const QString &path, const QString &format ); static bool onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format ); diff --git a/tests/src/python/test_qgssymbollayer_createsld.py b/tests/src/python/test_qgssymbollayer_createsld.py index 16e13149974..bd1d98be60a 100644 --- a/tests/src/python/test_qgssymbollayer_createsld.py +++ b/tests/src/python/test_qgssymbollayer_createsld.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ *************************************************************************** test_qgssymbollayer_createsld.py @@ -25,15 +23,16 @@ __revision__ = '$Format:%H$' import qgis # NOQA -from qgis.PyQt.QtCore import Qt, QDir, QFile, QIODevice, QPointF +from qgis.PyQt.QtCore import Qt, QDir, QFile, QIODevice, QPointF, QSizeF from qgis.PyQt.QtXml import QDomDocument -from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtGui import QColor, QFont from qgis.core import ( QgsSimpleMarkerSymbolLayer, QgsSimpleMarkerSymbolLayerBase, QgsUnitTypes, QgsSvgMarkerSymbolLayer, QgsFontMarkerSymbolLayer, QgsEllipseSymbolLayer, QgsSimpleLineSymbolLayer, QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsSimpleFillSymbolLayer, QgsSVGFillSymbolLayer, - QgsLinePatternFillSymbolLayer, QgsPointPatternFillSymbolLayer, QgsVectorLayer) + QgsLinePatternFillSymbolLayer, QgsPointPatternFillSymbolLayer, QgsVectorLayer, QgsVectorLayerSimpleLabeling, + QgsTextBufferSettings, QgsPalLayerSettings, QgsTextBackgroundSettings) from qgis.testing import start_app, unittest from utilities import unitTestDataPath @@ -57,32 +56,6 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): self.assertStaticRotation(root, '50') - def assertStaticRotation(self, root, expectedValue, index=0): - # Check the rotation element is a literal, not a - rotation = root.elementsByTagName('se:Rotation').item(index) - literal = rotation.firstChild() - self.assertEqual("ogc:Literal", literal.nodeName()) - self.assertEqual(expectedValue, literal.firstChild().nodeValue()) - - def assertStaticDisplacement(self, root, expectedDispX, expectedDispY): - displacement = root.elementsByTagName('se:Displacement').item(0) - self.assertIsNotNone(displacement) - dx = displacement.firstChild() - self.assertIsNotNone(dx) - self.assertEqual("se:DisplacementX", dx.nodeName()) - self.assertSldNumber(expectedDispX, dx.firstChild().nodeValue()) - dy = displacement.lastChild() - self.assertIsNotNone(dy) - self.assertEqual("se:DisplacementY", dy.nodeName()) - self.assertSldNumber(expectedDispY, dy.firstChild().nodeValue()) - - def assertSldNumber(self, expected, stringValue): - value = float(stringValue) - self.assertFloatEquals(expected, value, 0.01) - - def assertFloatEquals(self, expected, actual, tol): - self.assertLess(abs(expected - actual), tol) - def testSimpleMarkerUnitDefault(self): symbol = QgsSimpleMarkerSymbolLayer( QgsSimpleMarkerSymbolLayerBase.Star, color=QColor(255, 0, 0), strokeColor=QColor(0, 255, 0), size=10) @@ -98,14 +71,6 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): self.assertStrokeWidth(root, 2, 11) self.assertStaticDisplacement(root, 18, 36) - def assertStrokeWidth(self, root, svgParameterIdx, expectedWidth): - strokeWidth = root.elementsByTagName( - 'se:SvgParameter').item(svgParameterIdx) - svgParameterName = strokeWidth.attributes().namedItem('name') - self.assertEqual("stroke-width", svgParameterName.nodeValue()) - self.assertSldNumber( - expectedWidth, strokeWidth.firstChild().nodeValue()) - def testSimpleMarkerUnitPixels(self): symbol = QgsSimpleMarkerSymbolLayer( QgsSimpleMarkerSymbolLayerBase.Star, color=QColor(255, 0, 0), strokeColor=QColor(0, 255, 0), size=10) @@ -530,25 +495,36 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): gt = filter.firstChild() expectedGtName = "ogc:PropertyIsGreaterThanOrEqualTo" if includeMin else "ogc:PropertyIsGreaterThan" - self.assertEquals(expectedGtName, gt.nodeName()) + self.assertEqual(expectedGtName, gt.nodeName()) gtProperty = gt.firstChild() - self.assertEquals("ogc:PropertyName", gtProperty.nodeName()) - self.assertEquals(attributeName, gtProperty.toElement().text()) + self.assertEqual("ogc:PropertyName", gtProperty.nodeName()) + self.assertEqual(attributeName, gtProperty.toElement().text()) gtValue = gt.childNodes().item(1) - self.assertEquals(min, gtValue.toElement().text()) + self.assertEqual(min, gtValue.toElement().text()) lt = filter.childNodes().item(1) expectedLtName = "ogc:PropertyIsLessThanOrEqualTo" if includeMax else "ogc:PropertyIsLessThan" - self.assertEquals(expectedLtName, lt.nodeName()) + self.assertEqual(expectedLtName, lt.nodeName()) ltProperty = lt.firstChild() - self.assertEquals("ogc:PropertyName", ltProperty.nodeName()) - self.assertEquals(attributeName, ltProperty.toElement().text()) + self.assertEqual("ogc:PropertyName", ltProperty.nodeName()) + self.assertEqual(attributeName, ltProperty.toElement().text()) ltValue = lt.childNodes().item(1) - self.assertEquals(max, ltValue.toElement().text()) + self.assertEqual(max, ltValue.toElement().text()) - def testSimpleLabelling(self): + def testSimpleLabeling(self): layer = QgsVectorLayer("Point", "addfeat", "memory") self.loadStyleWithCustomProperties(layer, "simpleLabel") + # Pick a local default font + fontFamily = QFont().family() + settings = layer.labeling().settings() + format = settings.format() + font = format.font() + font.setFamily(fontFamily) + font.setBold(False) + font.setItalic(False) + format.setFont(font) + settings.setFormat(format) + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) dom, root = self.layerToSld(layer) # print("Simple label text symbolizer" + dom.toString()) @@ -556,17 +532,566 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): ts = self.getTextSymbolizer(root, 1, 0) self.assertPropertyName(ts, 'se:Label', 'NAME') font = self.assertElement(ts, 'se:Font', 0) - self.assertEquals('Liberation Mono', self.assertSvgParameter(font, 'font-family').text()) - self.assertEquals('9', self.assertSvgParameter(font, 'font-size').text()) + self.assertEqual(fontFamily, self.assertSvgParameter(font, 'font-family').text()) + self.assertEqual('11', self.assertSvgParameter(font, 'font-size').text()) fill = self.assertElement(ts, 'se:Fill', 0) - self.assertEquals('#000000', self.assertSvgParameter(fill, "fill").text()) + self.assertEqual('#000000', self.assertSvgParameter(fill, "fill").text()) + self.assertIsNone(self.assertSvgParameter(fill, "fill-opacity", True)) + + def testLabelingUomMillimeter(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + self.updateLayerLabelingUnit(layer, QgsUnitTypes.RenderMillimeters) + + dom, root = self.layerToSld(layer) + #print("Label sized in mm " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertEqual('32', self.assertSvgParameter(font, 'font-size').text()) + + def testLabelingUomPixels(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + self.updateLayerLabelingUnit(layer, QgsUnitTypes.RenderPixels) + + dom, root = self.layerToSld(layer) + # print("Label sized in pixels " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertEqual('9', self.assertSvgParameter(font, 'font-size').text()) + + def testLabelingUomInches(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + self.updateLayerLabelingUnit(layer, QgsUnitTypes.RenderInches) + + dom, root = self.layerToSld(layer) + # print("Label sized in inches " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertEqual('816', self.assertSvgParameter(font, 'font-size').text()) + + def testTextStyle(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + + # testing regular + self.updateLayerLabelingFontStyle(layer, False, False) + dom, root = self.layerToSld(layer) + # print("Simple label italic text" + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertIsNone(self.assertSvgParameter(font, 'font-weight', True)) + self.assertIsNone(self.assertSvgParameter(font, 'font-style', True)) + + # testing bold + self.updateLayerLabelingFontStyle(layer, True, False) + dom, root = self.layerToSld(layer) + # print("Simple label bold text" + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertEqual('bold', self.assertSvgParameter(font, 'font-weight').text()) + self.assertIsNone(self.assertSvgParameter(font, 'font-style', True)) + + # testing italic + self.updateLayerLabelingFontStyle(layer, False, True) + dom, root = self.layerToSld(layer) + # print("Simple label italic text" + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertEqual('italic', self.assertSvgParameter(font, 'font-style').text()) + self.assertIsNone(self.assertSvgParameter(font, 'font-weight', True)) + + # testing bold italic + self.updateLayerLabelingFontStyle(layer, True, True) + dom, root = self.layerToSld(layer) + # print("Simple label bold and italic text" + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertEqual('italic', self.assertSvgParameter(font, 'font-style').text()) + self.assertEqual('bold', self.assertSvgParameter(font, 'font-weight').text()) + + # testing underline and strikethrough vendor options + self.updateLayerLabelingFontStyle(layer, False, False, True, True) + dom, root = self.layerToSld(layer) + # print("Simple label underline and strikethrough text" + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + font = self.assertElement(ts, 'se:Font', 0) + self.assertEqual('true', self.assertVendorOption(ts, 'underlineText').text()) + self.assertEqual('true', self.assertVendorOption(ts, 'strikethroughText').text()) + + def testTextMixedCase(self): + self.assertCapitalizationFunction(QFont.MixedCase, None) + + def testTextUppercase(self): + self.assertCapitalizationFunction(QFont.AllUppercase, "strToUpperCase") + + def testTextLowercase(self): + self.assertCapitalizationFunction(QFont.AllLowercase, "strToLowerCase") + + def testTextCapitalcase(self): + self.assertCapitalizationFunction(QFont.Capitalize, "strCapitalize") + + def assertCapitalizationFunction(self, capitalization, expectedFunction): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + + settings = layer.labeling().settings() + format = settings.format() + font = format.font() + font.setCapitalization(capitalization) + format.setFont(font) + settings.setFormat(format) + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Simple text with capitalization " + str(QFont.AllUppercase) + ": " + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + label = self.assertElement(ts, "se:Label", 0) + if expectedFunction is None: + property = self.assertElement(label, "ogc:PropertyName", 0) + self.assertEqual("NAME", property.text()) + else: + function = self.assertElement(label, "ogc:Function", 0) + self.assertEqual(expectedFunction, function.attribute("name")) + property = self.assertElement(function, "ogc:PropertyName", 0) + self.assertEqual("NAME", property.text()) + + def testLabelingTransparency(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + settings = layer.labeling().settings() + format = settings.format() + format.setOpacity(0.5) + settings.setFormat(format) + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Label with transparency " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + fill = self.assertElement(ts, 'se:Fill', 0) + self.assertEqual('#000000', self.assertSvgParameter(fill, "fill").text()) + self.assertEqual('0.5', self.assertSvgParameter(fill, "fill-opacity").text()) + + def testLabelingBuffer(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + buffer = QgsTextBufferSettings() + buffer.setEnabled(True) + buffer.setSize(10) + buffer.setSizeUnit(QgsUnitTypes.RenderPixels) + buffer.setColor(QColor("Black")) + self.setLabelBufferSettings(layer, buffer) + + dom, root = self.layerToSld(layer) + # print("Label with buffer 10 px " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + halo = self.assertElement(ts, 'se:Halo', 0) + # not full width, just radius here + self.assertEqual('5', self.assertElement(ts, 'se:Radius', 0).text()) + haloFill = self.assertElement(halo, 'se:Fill', 0) + self.assertEqual('#000000', self.assertSvgParameter(haloFill, "fill").text()) + + def testLabelingBufferPointTranslucent(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + buffer = QgsTextBufferSettings() + buffer.setEnabled(True) + buffer.setSize(10) + buffer.setSizeUnit(QgsUnitTypes.RenderPoints) + buffer.setColor(QColor("Red")) + buffer.setOpacity(0.5) + self.setLabelBufferSettings(layer, buffer) + + dom, root = self.layerToSld(layer) + # print("Label with buffer 10 points, red 50% transparent " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + halo = self.assertElement(ts, 'se:Halo', 0) + # not full width, just radius here + self.assertEqual('6.5', self.assertElement(ts, 'se:Radius', 0).text()) + haloFill = self.assertElement(halo, 'se:Fill', 0) + self.assertEqual('#ff0000', self.assertSvgParameter(haloFill, "fill").text()) + self.assertEqual('0.5', self.assertSvgParameter(haloFill, "fill-opacity").text()) + + def testLabelingLowPriority(self): + self.assertLabelingPriority(0, 0, '0') + + def testLabelingDefaultPriority(self): + self.assertLabelingPriority(0, 5, None) + + def testLabelingHighPriority(self): + self.assertLabelingPriority(0, 10, '1000') + + def testLabelingZIndexLowPriority(self): + self.assertLabelingPriority(1, 0, '1001') + + def testLabelingZIndexDefaultPriority(self): + self.assertLabelingPriority(1, 5, "1500") + + def testLabelingZIndexHighPriority(self): + self.assertLabelingPriority(1, 10, '2000') + + def assertLabelingPriority(self, zIndex, priority, expectedSldPriority): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + settings = layer.labeling().settings() + settings.zIndex = zIndex + settings.priority = priority + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Label with zIndex at " + str(zIndex) + " and priority at " + str(priority) + ": " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + priorityElement = self.assertElement(ts, "se:Priority", 0, True) + if expectedSldPriority is None: + self.assertIsNone(priorityElement) + else: + self.assertEqual(expectedSldPriority, priorityElement.text()) + + def testLabelingPlacementOverPointOffsetRotation(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + settings = layer.labeling().settings() + settings.placement = QgsPalLayerSettings.OverPoint + settings.xOffset = 5 + settings.yOffset = 10 + settings.offsetUnits = QgsUnitTypes.RenderMillimeters + settings.quadOffset = QgsPalLayerSettings.QuadrantOver + settings.angleOffset = 30 + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Label with 'over point' placement " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + pointPlacement = self.assertPointPlacement(ts) + self.assertStaticDisplacement(pointPlacement, 18, 36) + self.assertStaticAnchorPoint(pointPlacement, 0.5, 0.5) + + def testPointPlacementAboveLeft(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantAboveLeft, "AboveLeft", 1, 0) + + def testPointPlacementAbove(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantAbove, "Above", 0.5, 0) + + def testPointPlacementAboveRight(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantAboveRight, "AboveRight", 0, 0) + + def testPointPlacementLeft(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantLeft, "Left", 1, 0.5) + + def testPointPlacementRight(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantRight, "Right", 0, 0.5) + + def testPointPlacementBelowLeft(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantBelowLeft, "BelowLeft", 1, 1) + + def testPointPlacementBelow(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantBelow, "Below", 0.5, 1) + + def testPointPlacementAboveRight(self): + self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantBelowRight, "BelowRight", 0, 1) + + def testPointPlacementCartoraphic(self): + self.assertPointPlacementDistance(QgsPalLayerSettings.OrderedPositionsAroundPoint) + + def testPointPlacementCartoraphic(self): + self.assertPointPlacementDistance(QgsPalLayerSettings.AroundPoint) + + def testLineParallelPlacement(self): + layer = QgsVectorLayer("LineString", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "lineLabel") + + dom, root = self.layerToSld(layer) + # print("Label with parallel line placement " + dom.toString()) + linePlacement = self.assertLinePlacement(root) + generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0) + self.assertEqual("true", generalize.text()) + + def testLineParallelPlacementOffsetRepeat(self): + layer = QgsVectorLayer("LineString", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "lineLabel") + self.updateLinePlacementProperties(layer, QgsPalLayerSettings.Line, 2, 50) + + dom, root = self.layerToSld(layer) + # print("Label with parallel line placement, perp. offset and repeat " + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + linePlacement = self.assertLinePlacement(ts) + generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0) + self.assertEqual("true", generalize.text()) + offset = self.assertElement(linePlacement, 'se:PerpendicularOffset', 0) + self.assertEqual("7", offset.text()) + repeat = self.assertElement(linePlacement, 'se:Repeat', 0) + self.assertEqual("true", repeat.text()) + gap = self.assertElement(linePlacement, 'se:Gap', 0) + self.assertEqual("179", gap.text()) + self.assertEqual("179", self.assertVendorOption(ts, "repeat").text()) + + def testLineCurvePlacementOffsetRepeat(self): + layer = QgsVectorLayer("LineString", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "lineLabel") + self.updateLinePlacementProperties(layer, QgsPalLayerSettings.Curved, 2, 50, 30, 40) + + dom, root = self.layerToSld(layer) + # print("Label with curved line placement " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + linePlacement = self.assertLinePlacement(ts) + generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0) + self.assertEqual("true", generalize.text()) + offset = self.assertElement(linePlacement, 'se:PerpendicularOffset', 0) + self.assertEqual("7", offset.text()) + repeat = self.assertElement(linePlacement, 'se:Repeat', 0) + self.assertEqual("true", repeat.text()) + gap = self.assertElement(linePlacement, 'se:Gap', 0) + self.assertEqual("179", gap.text()) + self.assertEqual("179", self.assertVendorOption(ts, "repeat").text()) + self.assertEqual("true", self.assertVendorOption(ts, "followLine").text()) + self.assertEqual("30", self.assertVendorOption(ts, "maxAngleDelta").text()) + + def testLineCurveMergeLines(self): + layer = QgsVectorLayer("LineString", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "lineLabel") + settings = layer.labeling().settings() + settings.placement = QgsPalLayerSettings.Curved + settings.mergeLines = True + settings.labelPerPart = True + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Label with curved line and line grouping " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + self.assertEqual("yes", self.assertVendorOption(ts, "group").text()) + self.assertEqual("true", self.assertVendorOption(ts, "labelAllGroup").text()) + + def testLabelingPolygonFree(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "polygonLabel") + settings = layer.labeling().settings() + settings.placement = QgsPalLayerSettings.Free + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Polygon label with 'Free' placement " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + pointPlacement = self.assertPointPlacement(ts) + self.assertIsNone(self.assertElement(ts, "se:Displacement", 0, True)) + self.assertStaticAnchorPoint(pointPlacement, 0.5, 0.5) + + def testLabelingPolygonPerimeterCurved(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "polygonLabel") + self.updateLinePlacementProperties(layer, QgsPalLayerSettings.PerimeterCurved, 2, 50, 30, -40) + + dom, root = self.layerToSld(layer) + # print("Polygon Label with curved perimeter line placement " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + linePlacement = self.assertLinePlacement(ts) + generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0) + self.assertEqual("true", generalize.text()) + offset = self.assertElement(linePlacement, 'se:PerpendicularOffset', 0) + self.assertEqual("7", offset.text()) + repeat = self.assertElement(linePlacement, 'se:Repeat', 0) + self.assertEqual("true", repeat.text()) + gap = self.assertElement(linePlacement, 'se:Gap', 0) + self.assertEqual("179", gap.text()) + self.assertEqual("179", self.assertVendorOption(ts, "repeat").text()) + self.assertEqual("true", self.assertVendorOption(ts, "followLine").text()) + self.assertEqual("30", self.assertVendorOption(ts, "maxAngleDelta").text()) + + def testLabelScaleDependencies(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "polygonLabel") + settings = layer.labeling().settings() + settings.scaleVisibility = True + settings.minimumScale = 1000000 + settings.maximumScale = 10000000 + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Labeling with scale dependencies " + dom.toString()) + self.assertScaleDenominator(root, "1000000", "10000000", 1) + + def testLabelShowAll(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "polygonLabel") + settings = layer.labeling().settings() + settings.displayAll = True + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Labeling, showing all labels " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + self.assertVendorOption(ts, "conflictResolution", "false") + + def testLabelUpsideDown(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "polygonLabel") + settings = layer.labeling().settings() + settings.upsidedownLabels = QgsPalLayerSettings.ShowAll + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Labeling, showing upside down labels on lines " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + self.assertVendorOption(ts, "forceLeftToRight", "false") + + def testLabelBackgroundSquareResize(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeSquare, 'square', + QgsTextBackgroundSettings.SizeBuffer, 'proportional') + + def testLabelBackgroundRectangleResize(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeRectangle, 'square', + QgsTextBackgroundSettings.SizeBuffer, 'stretch') + + def testLabelBackgroundCircleResize(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeCircle, 'circle', + QgsTextBackgroundSettings.SizeBuffer, 'proportional') + + def testLabelBackgroundEllipseResize(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeEllipse, 'circle', + QgsTextBackgroundSettings.SizeBuffer, 'stretch') + + def testLabelBackgroundSquareAbsolute(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeSquare, 'square', + QgsTextBackgroundSettings.SizeFixed, None) + + def testLabelBackgroundRectangleAbsolute(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeRectangle, 'square', + QgsTextBackgroundSettings.SizeFixed, None) + + def testLabelBackgroundCircleAbsolute(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeCircle, 'circle', + QgsTextBackgroundSettings.SizeFixed, None) + + def testLabelBackgroundEllipseAbsolute(self): + self.assertLabelBackground(QgsTextBackgroundSettings.ShapeEllipse, 'circle', + QgsTextBackgroundSettings.SizeFixed, None) + + def assertLabelBackground(self, backgroundType, expectedMarkName, sizeType, expectedResize): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "polygonLabel") + settings = layer.labeling().settings() + background = QgsTextBackgroundSettings() + background.setEnabled(True) + background.setType(backgroundType) + background.setFillColor(QColor('yellow')) + background.setStrokeColor(QColor('black')) + background.setStrokeWidth(2) + background.setSize(QSizeF(10, 10)) + background.setSizeType(sizeType) + format = settings.format() + format.setBackground(background) + settings.setFormat(format) + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Labeling, with background type " + str(backgroundType) + " and size type " + str(sizeType) + ": " + dom.toString()) + + ts = self.getTextSymbolizer(root, 1, 0) + graphic = self.assertElement(ts, "se:Graphic", 0) + self.assertEqual("36", self.assertElement(graphic, 'se:Size', 0).text()) + self.assertWellKnownMark(graphic, 0, expectedMarkName, '#ffff00', '#000000', 7) + if expectedResize is None: + self.assertIsNone(expectedResize, self.assertVendorOption(ts, 'graphic-resize', True)) + else: + self.assertEqual(expectedResize, self.assertVendorOption(ts, 'graphic-resize').text()) + if sizeType == 0: + # check extra padding for proportional ellipse + if backgroundType == QgsTextBackgroundSettings.ShapeEllipse: + self.assertEqual("42.5 49", self.assertVendorOption(ts, 'graphic-margin').text()) + else: + self.assertEqual("36 36", self.assertVendorOption(ts, 'graphic-margin').text()) + else: + self.assertIsNone(self.assertVendorOption(ts, 'graphic-margin', True)) + + def updateLinePlacementProperties(self, layer, linePlacement, distance, repeat, maxAngleInternal=25, maxAngleExternal=-25): + settings = layer.labeling().settings() + settings.placement = linePlacement + settings.dist = distance + settings.repeatDistance = repeat + settings.maxCurvedCharAngleIn = maxAngleInternal + settings.maxCurvedCharAngleOut = maxAngleExternal + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + def assertPointPlacementDistance(self, placement): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + + settings = layer.labeling().settings() + settings.placement = placement + settings.xOffset = 0 + settings.yOffset = 0 + settings.dist = 2 + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Label with around point placement " + dom.toString()) + ts = self.getTextSymbolizer(root, 1, 0) + pointPlacement = self.assertPointPlacement(ts) + self.assertStaticAnchorPoint(pointPlacement, 0, 0.5) + self.assertStaticDisplacement(pointPlacement, 4.95, 4.95) + + def assertLabelQuadrant(self, quadrant, label, ax, ay): + layer = QgsVectorLayer("Point", "addfeat", "memory") + self.loadStyleWithCustomProperties(layer, "simpleLabel") + + settings = layer.labeling().settings() + settings.placement = QgsPalLayerSettings.OverPoint + settings.xOffset = 0 + settings.yOffset = 0 + settings.quadOffset = quadrant + settings.angleOffset = 0 + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + dom, root = self.layerToSld(layer) + # print("Label with " + label + " placement " + dom.toString()) + self.assertStaticAnchorPoint(root, ax, ay) + + def setLabelBufferSettings(self, layer, buffer): + settings = layer.labeling().settings() + format = settings.format() + format.setBuffer(buffer) + settings.setFormat(format) + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + def updateLayerLabelingFontStyle(self, layer, bold, italic, underline=False, strikeout=False): + settings = layer.labeling().settings() + format = settings.format() + font = format.font() + font.setBold(bold) + font.setItalic(italic) + font.setUnderline(underline) + font.setStrikeOut(strikeout) + format.setFont(font) + settings.setFormat(format) + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) + + def updateLayerLabelingUnit(self, layer, unit): + settings = layer.labeling().settings() + format = settings.format() + format.setSizeUnit(unit) + settings.setFormat(format) + layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) def loadStyleWithCustomProperties(self, layer, qmlFileName): # load the style, only vector symbology path = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), qmlFileName)) - # labelling is in custom properties, they need to be loaded separately + # labeling is in custom properties, they need to be loaded separately status = layer.loadNamedStyle(path) doc = QDomDocument() file = QFile(path) @@ -575,9 +1100,26 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): file.close() flag = layer.readCustomProperties(doc.documentElement()) - def assertElement(self, container, elementName, index): + def assertPointPlacement(self, textSymbolizer): + labelPlacement = self.assertElement(textSymbolizer, 'se:LabelPlacement', 0) + self.assertIsNone(self.assertElement(labelPlacement, 'se:LinePlacement', 0, True)) + pointPlacement = self.assertElement(labelPlacement, 'se:PointPlacement', 0) + return pointPlacement + + def assertLinePlacement(self, textSymbolizer): + labelPlacement = self.assertElement(textSymbolizer, 'se:LabelPlacement', 0) + self.assertIsNone(self.assertElement(labelPlacement, 'se:PointPlacement', 0, True)) + linePlacement = self.assertElement(labelPlacement, 'se:LinePlacement', 0) + return linePlacement + + def assertElement(self, container, elementName, index, allowMissing=False): list = container.elementsByTagName(elementName) - self.assertTrue(list.size() > index, 'Expected to find at least ' + str(index + 1) + ' ' + elementName + ' in ' + container.nodeName() + ' but found ' + str(list.size())) + if list.size() <= index: + if allowMissing: + return None + else: + self.fail('Expected to find at least ' + str(index + 1) + ' ' + elementName + ' in ' + container.nodeName() + ' but found ' + str(list.size())) + node = list.item(index) self.assertTrue(node.isElement(), 'Found node but it''s not an element') return node.toElement() @@ -592,13 +1134,27 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): property = container.elementsByTagName("ogc:PropertyName").item(0).toElement() self.assertEqual(expectedAttributeName, property.text()) - def assertSvgParameter(self, container, expectedName): + def assertSvgParameter(self, container, expectedName, allowMissing=False): list = container.elementsByTagName("se:SvgParameter") for i in range(0, list.size()): item = list.item(i) if item.isElement and item.isElement() and item.toElement().attribute('name') == expectedName: return item.toElement() - self.fail('Could not find a se:SvgParameter named ' + expectedName + ' in ' + container.nodeName()) + if allowMissing: + return None + else: + self.fail('Could not find a se:SvgParameter named ' + expectedName + ' in ' + container.nodeName()) + + def assertVendorOption(self, container, expectedName, allowMissing=False): + list = container.elementsByTagName("se:VendorOption") + for i in range(0, list.size()): + item = list.item(i) + if item.isElement and item.isElement() and item.toElement().attribute('name') == expectedName: + return item.toElement() + if allowMissing: + return None + else: + self.fail('Could not find a se:VendorOption named ' + expectedName + ' in ' + container.nodeName()) def assertScaleDenominator(self, root, expectedMinScale, expectedMaxScale, index=0): rule = root.elementsByTagName('se:Rule').item(index).toElement() @@ -669,6 +1225,52 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): self.assertEqual('stroke-width', parameter.attribute('name')) self.assertEqual(str(expectedStrokeWidth), parameter.text()) + def assertStaticRotation(self, root, expectedValue, index=0): + # Check the rotation element is a literal, not a + rotation = root.elementsByTagName('se:Rotation').item(index) + literal = rotation.firstChild() + self.assertEqual("ogc:Literal", literal.nodeName()) + self.assertEqual(expectedValue, literal.firstChild().nodeValue()) + + def assertStaticDisplacement(self, root, expectedAnchorX, expectedAnchorY): + displacement = root.elementsByTagName('se:Displacement').item(0) + self.assertIsNotNone(displacement) + dx = displacement.firstChild() + self.assertIsNotNone(dx) + self.assertEqual("se:DisplacementX", dx.nodeName()) + self.assertSldNumber(expectedAnchorX, dx.firstChild().nodeValue()) + dy = displacement.lastChild() + self.assertIsNotNone(dy) + self.assertEqual("se:DisplacementY", dy.nodeName()) + self.assertSldNumber(expectedAnchorY, dy.firstChild().nodeValue()) + + def assertStaticAnchorPoint(self, root, expectedDispX, expectedDispY): + anchor = root.elementsByTagName('se:AnchorPoint').item(0) + self.assertIsNotNone(anchor) + ax = anchor.firstChild() + self.assertIsNotNone(ax) + self.assertEqual("se:AnchorPointX", ax.nodeName()) + self.assertSldNumber(expectedDispX, ax.firstChild().nodeValue()) + ay = anchor.lastChild() + self.assertIsNotNone(ay) + self.assertEqual("se:AnchorPointY", ay.nodeName()) + self.assertSldNumber(expectedDispY, ay.firstChild().nodeValue()) + + def assertSldNumber(self, expected, stringValue): + value = float(stringValue) + self.assertFloatEquals(expected, value, 0.01) + + def assertFloatEquals(self, expected, actual, tol): + self.assertLess(abs(expected - actual), tol, 'Expected %d but was %d' % (expected, actual)) + + def assertStrokeWidth(self, root, svgParameterIdx, expectedWidth): + strokeWidth = root.elementsByTagName( + 'se:SvgParameter').item(svgParameterIdx) + svgParameterName = strokeWidth.attributes().namedItem('name') + self.assertEqual("stroke-width", svgParameterName.nodeValue()) + self.assertSldNumber( + expectedWidth, strokeWidth.firstChild().nodeValue()) + def symbolToSld(self, symbolLayer): dom = QDomDocument() root = dom.createElement("FakeRoot") diff --git a/tests/testdata/symbol_layer/lineLabel.qml b/tests/testdata/symbol_layer/lineLabel.qml new file mode 100644 index 00000000000..9d4fad2862c --- /dev/null +++ b/tests/testdata/symbol_layer/lineLabel.qml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + name + + 1 + diff --git a/tests/testdata/symbol_layer/polygonLabel.qml b/tests/testdata/symbol_layer/polygonLabel.qml new file mode 100644 index 00000000000..3b1cdf2d884 --- /dev/null +++ b/tests/testdata/symbol_layer/polygonLabel.qml @@ -0,0 +1,910 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + name + + 2 + diff --git a/tests/testdata/symbol_layer/simpleLabel.qml b/tests/testdata/symbol_layer/simpleLabel.qml index bf4ee3c8fdd..f725c52b0e0 100644 --- a/tests/testdata/symbol_layer/simpleLabel.qml +++ b/tests/testdata/symbol_layer/simpleLabel.qml @@ -51,7 +51,7 @@ - + @@ -97,7 +97,7 @@ - + From a7693d90769c6b9f2b73da0cefb72a340a44837c Mon Sep 17 00:00:00 2001 From: doublebyte1 Date: Wed, 26 Jul 2017 12:38:42 +0200 Subject: [PATCH 214/266] - closed unmatched xml tag. --- resources/qgis-resource-metadata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qgis-resource-metadata.xml b/resources/qgis-resource-metadata.xml index fee7182b864..e07675b62b8 100644 --- a/resources/qgis-resource-metadata.xml +++ b/resources/qgis-resource-metadata.xml @@ -11,7 +11,7 @@ kw2 None - None + None Copyright foo 2017 utf-8 EPSG:4326 From a3af69a44d7ee7e37c652302727a842aed42a9d7 Mon Sep 17 00:00:00 2001 From: rldhont Date: Wed, 26 Jul 2017 15:20:36 +0200 Subject: [PATCH 215/266] [BUGFIX][SERVER] GetFeatureInfo not evaluated for maptip context when using FILTER param fixed #16670 --- src/server/services/wms/qgswmsrenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index 41545eafd32..6dbe3e434c6 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -1350,6 +1350,8 @@ namespace QgsWms break; } + renderContext.expressionContext().setFeature( feature ); + if ( layer->wkbType() != QgsWkbTypes::NoGeometry && ! searchRect.isEmpty() ) { if ( !r2 ) @@ -1357,8 +1359,6 @@ namespace QgsWms continue; } - renderContext.expressionContext().setFeature( feature ); - //check if feature is rendered at all bool render = r2->willRenderFeature( feature, renderContext ); if ( !render ) From 8d4dec5c3f2ca6169a0f0b65413687a4625c2b44 Mon Sep 17 00:00:00 2001 From: Alexandre Neto Date: Wed, 26 Jul 2017 15:16:36 +0100 Subject: [PATCH 216/266] Making dialog titles use Book Style Capitalization [needs-docs] --- src/app/composer/qgsatlascompositionwidget.cpp | 4 ++-- src/app/composer/qgscomposer.cpp | 14 +++++++------- .../composer/qgscomposerattributetablewidget.cpp | 2 +- src/app/composer/qgscomposerhtmlwidget.cpp | 2 +- src/app/composer/qgscomposerlabelwidget.cpp | 2 +- src/app/composer/qgscomposermapgridwidget.cpp | 2 +- src/app/qgisapp.cpp | 8 ++++---- src/app/qgsaddtaborgroup.cpp | 2 +- src/app/qgsapplayertreeviewmenuprovider.cpp | 4 ++-- src/app/qgsattributeactiondialog.cpp | 4 ++-- src/app/qgsattributetabledialog.cpp | 4 ++-- src/app/qgsdiagramproperties.cpp | 2 +- src/app/qgsfieldsproperties.cpp | 6 +++--- src/app/qgsguivectorlayertools.cpp | 2 +- src/app/qgshtmlannotationdialog.cpp | 2 +- src/app/qgsmapsavedialog.cpp | 2 +- src/app/qgsprojectproperties.cpp | 2 +- src/app/qgssavestyletodbdialog.cpp | 2 +- src/app/qgsselectbyformdialog.cpp | 2 +- src/gui/attributetable/qgsdualview.cpp | 6 +++--- .../editorwidgets/qgsvaluerelationconfigdlg.cpp | 2 +- src/gui/ogr/qgsvectorlayersaveasdialog.cpp | 6 +++--- src/gui/qgsexpressionbuilderwidget.cpp | 2 +- src/gui/qgsexpressionselectiondialog.cpp | 2 +- src/gui/qgsnewgeopackagelayerdialog.cpp | 2 +- src/gui/qgsnewhttpconnection.cpp | 2 +- src/gui/qgsnewnamedialog.cpp | 2 +- src/gui/qgspropertyoverridebutton.cpp | 2 +- src/gui/qgssearchquerybuilder.cpp | 2 +- src/gui/qgssublayersdialog.cpp | 6 +++--- src/gui/qgstextformatwidget.cpp | 2 +- src/gui/symbology-ng/qgscptcitycolorrampdialog.cpp | 6 +++--- .../symbology-ng/qgsstyleexportimportdialog.cpp | 6 +++--- src/gui/symbology-ng/qgsstylesavedialog.cpp | 4 ++-- .../eventbrowser/evisgenericeventbrowsergui.cpp | 6 +++--- .../ui/qgsgeometrycheckerresulttab.cpp | 2 +- .../ui/qgsgeometrycheckfixdialog.cpp | 2 +- src/plugins/georeferencer/qgsgeorefplugingui.cpp | 2 +- src/plugins/georeferencer/qgsimagewarper.cpp | 2 +- src/plugins/grass/qgsgrassselect.cpp | 2 +- src/providers/arcgisrest/qgsafsdataitems.cpp | 4 ++-- src/providers/arcgisrest/qgsamsdataitems.cpp | 4 ++-- .../arcgisrest/qgsarcgisservicesourceselect.cpp | 4 ++-- src/providers/wfs/qgswfsdataitems.cpp | 4 ++-- src/providers/wfs/qgswfssourceselect.cpp | 4 ++-- src/ui/auth/qgsauthsslimporterrors.ui | 2 +- src/ui/composer/qgsattributeselectiondialogbase.ui | 2 +- src/ui/composer/qgscomposerimageexportoptions.ui | 2 +- .../composer/qgscomposerlegendlayersdialogbase.ui | 2 +- src/ui/composer/qgscomposermanagerbase.ui | 2 +- src/ui/qgsaddattrdialogbase.ui | 2 +- src/ui/qgsattributeloadfrommap.ui | 2 +- src/ui/qgscolorbrewercolorrampwidgetbase.ui | 2 +- src/ui/qgscolordialog.ui | 2 +- src/ui/qgsconfigureshortcutsdialog.ui | 2 +- src/ui/qgscptcitycolorrampv2dialogbase.ui | 2 +- src/ui/qgscustomizationdialogbase.ui | 2 +- src/ui/qgsdatadefinedsizelegendwidget.ui | 2 +- src/ui/qgsdatumtransformdialogbase.ui | 2 +- src/ui/qgsdb2newconnectionbase.ui | 2 +- src/ui/qgsdbsourceselectbase.ui | 2 +- src/ui/qgsdecorationgriddialog.ui | 2 +- src/ui/qgsdelattrdialogbase.ui | 2 +- src/ui/qgsdiscoverrelationsdlgbase.ui | 2 +- src/ui/qgsdxfexportdialogbase.ui | 2 +- src/ui/qgsexpressionbuilderdialogbase.ui | 2 +- src/ui/qgsfieldcalculatorbase.ui | 2 +- src/ui/qgsformannotationdialogbase.ui | 2 +- src/ui/qgsgradientcolorrampdialogbase.ui | 2 +- src/ui/qgsgroupwmsdatadialogbase.ui | 2 +- src/ui/qgshandlebadlayersbase.ui | 2 +- src/ui/qgsjoindialogbase.ui | 2 +- src/ui/qgslabelingrulepropswidget.ui | 2 +- src/ui/qgslabelpropertydialogbase.ui | 2 +- src/ui/qgslimitedrandomcolorrampwidgetbase.ui | 2 +- src/ui/qgsludialogbase.ui | 2 +- src/ui/qgsmanageconnectionsdialogbase.ui | 2 +- src/ui/qgsmapsavedialog.ui | 2 +- src/ui/qgsmapunitscalewidgetbase.ui | 2 +- src/ui/qgsmergeattributesdialogbase.ui | 2 +- src/ui/qgsmssqlnewconnectionbase.ui | 2 +- src/ui/qgsnewhttpconnectionbase.ui | 2 +- src/ui/qgsnewogrconnectionbase.ui | 2 +- src/ui/qgsopenvectorlayerdialogbase.ui | 2 +- src/ui/qgsoraclenewconnectionbase.ui | 2 +- src/ui/qgsorderbydialogbase.ui | 2 +- src/ui/qgsorganizetablecolumnsdialog.ui | 2 +- src/ui/qgsosmdownloaddialog.ui | 2 +- src/ui/qgsosmexportdialog.ui | 2 +- src/ui/qgspgnewconnectionbase.ui | 2 +- src/ui/qgspresetcolorrampwidgetbase.ui | 2 +- src/ui/qgsprojectlayergroupdialogbase.ui | 2 +- src/ui/qgsrastercalcdialogbase.ui | 2 +- src/ui/qgsrasterlayersaveasdialogbase.ui | 2 +- src/ui/qgsrelationadddlgbase.ui | 2 +- src/ui/qgsrendererv2propsdialogbase.ui | 2 +- src/ui/qgssavetodbdialog.ui | 2 +- src/ui/qgssqlcomposerdialogbase.ui | 2 +- src/ui/qgsstyleexportimportdialogbase.ui | 2 +- src/ui/qgsstylesavedialog.ui | 2 +- src/ui/qgssublayersdialogbase.ui | 2 +- src/ui/qgstextannotationdialogbase.ui | 2 +- src/ui/qgstextformatwidgetbase.ui | 2 +- src/ui/qgsvectorlayersaveasdialogbase.ui | 2 +- src/ui/qgswmtsdimensionsbase.ui | 2 +- src/ui/symbollayer/qgsdashspacedialogbase.ui | 2 +- 106 files changed, 139 insertions(+), 139 deletions(-) diff --git a/src/app/composer/qgsatlascompositionwidget.cpp b/src/app/composer/qgsatlascompositionwidget.cpp index 4af6c664fe3..cda2b9cd105 100644 --- a/src/app/composer/qgsatlascompositionwidget.cpp +++ b/src/app/composer/qgsatlascompositionwidget.cpp @@ -131,7 +131,7 @@ void QgsAtlasCompositionWidget::on_mAtlasFilenameExpressionButton_clicked() QgsExpressionContext context = mComposition->createExpressionContext(); QgsExpressionBuilderDialog exprDlg( atlasMap->coverageLayer(), mAtlasFilenamePatternEdit->text(), this, QStringLiteral( "generic" ), context ); - exprDlg.setWindowTitle( tr( "Expression based filename" ) ); + exprDlg.setWindowTitle( tr( "Expression Based Filename" ) ); if ( exprDlg.exec() == QDialog::Accepted ) { @@ -304,7 +304,7 @@ void QgsAtlasCompositionWidget::on_mAtlasFeatureFilterButton_clicked() QgsExpressionContext context = mComposition->createExpressionContext(); QgsExpressionBuilderDialog exprDlg( vl, mAtlasFeatureFilterEdit->text(), this, QStringLiteral( "generic" ), context ); - exprDlg.setWindowTitle( tr( "Expression based filter" ) ); + exprDlg.setWindowTitle( tr( "Expression Based Filter" ) ); if ( exprDlg.exec() == QDialog::Accepted ) { diff --git a/src/app/composer/qgscomposer.cpp b/src/app/composer/qgscomposer.cpp index d88afd1633d..acba3668172 100644 --- a/src/app/composer/qgscomposer.cpp +++ b/src/app/composer/qgscomposer.cpp @@ -1695,7 +1695,7 @@ void QgsComposer::exportCompositionAsPDF( QgsComposer::OutputMode mode ) } QProgressDialog progress( tr( "Rendering maps..." ), tr( "Abort" ), 0, atlasMap->numFeatures(), this ); - progress.setWindowTitle( tr( "Exporting atlas" ) ); + progress.setWindowTitle( tr( "Exporting Atlas" ) ); QApplication::setOverrideCursor( Qt::BusyCursor ); for ( int featureI = 0; featureI < atlasMap->numFeatures(); ++featureI ) @@ -1862,7 +1862,7 @@ void QgsComposer::printComposition( QgsComposer::OutputMode mode ) return; } QProgressDialog progress( tr( "Rendering maps..." ), tr( "Abort" ), 0, atlasMap->numFeatures(), this ); - progress.setWindowTitle( tr( "Exporting atlas" ) ); + progress.setWindowTitle( tr( "Exporting Atlas" ) ); for ( int i = 0; i < atlasMap->numFeatures(); ++i ) { @@ -2194,7 +2194,7 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode ) } QProgressDialog progress( tr( "Rendering maps..." ), tr( "Abort" ), 0, atlasMap->numFeatures(), this ); - progress.setWindowTitle( tr( "Exporting atlas" ) ); + progress.setWindowTitle( tr( "Exporting Atlas" ) ); for ( int feature = 0; feature < atlasMap->numFeatures(); ++feature ) { @@ -2383,7 +2383,7 @@ void QgsComposer::exportCompositionAsSVG( QgsComposer::OutputMode mode ) if ( displaySVGWarning ) { QgsMessageViewer *m = new QgsMessageViewer( this ); - m->setWindowTitle( tr( "SVG warning" ) ); + m->setWindowTitle( tr( "SVG Warning" ) ); m->setCheckBoxText( tr( "Don't show this message again" ) ); m->setCheckBoxState( Qt::Unchecked ); m->setCheckBoxVisible( true ); @@ -2541,7 +2541,7 @@ void QgsComposer::exportCompositionAsSVG( QgsComposer::OutputMode mode ) } } QProgressDialog progress( tr( "Rendering maps..." ), tr( "Abort" ), 0, atlasMap->numFeatures(), this ); - progress.setWindowTitle( tr( "Exporting atlas" ) ); + progress.setWindowTitle( tr( "Exporting Atlas" ) ); do { @@ -3429,7 +3429,7 @@ void QgsComposer::showWmsPrintingWarning() if ( displayWMSWarning ) { QgsMessageViewer *m = new QgsMessageViewer( this ); - m->setWindowTitle( tr( "Project contains WMS layers" ) ); + m->setWindowTitle( tr( "Project Contains WMS Layers" ) ); m->setMessage( tr( "Some WMS servers (e.g. UMN mapserver) have a limit for the WIDTH and HEIGHT parameter. Printing layers from such servers may exceed this limit. If this is the case, the WMS layer will not be printed" ), QgsMessageOutput::MessageText ); m->setCheckBoxText( tr( "Don't show this message again" ) ); m->setCheckBoxState( Qt::Unchecked ); @@ -3444,7 +3444,7 @@ void QgsComposer::showAdvancedEffectsWarning() if ( ! mComposition->printAsRaster() ) { QgsMessageViewer *m = new QgsMessageViewer( this, QgsGuiUtils::ModalDialogFlags, false ); - m->setWindowTitle( tr( "Project contains composition effects" ) ); + m->setWindowTitle( tr( "Project Contains Composition Effects" ) ); m->setMessage( tr( "Advanced composition effects such as blend modes or vector layer transparency are enabled in this project, which cannot be printed as vectors. Printing as a raster is recommended." ), QgsMessageOutput::MessageText ); m->setCheckBoxText( tr( "Print as raster" ) ); m->setCheckBoxState( Qt::Checked ); diff --git a/src/app/composer/qgscomposerattributetablewidget.cpp b/src/app/composer/qgscomposerattributetablewidget.cpp index bf083191dac..ecf1a58e3e7 100644 --- a/src/app/composer/qgscomposerattributetablewidget.cpp +++ b/src/app/composer/qgscomposerattributetablewidget.cpp @@ -785,7 +785,7 @@ void QgsComposerAttributeTableWidget::on_mFeatureFilterButton_clicked() QgsExpressionContext context = mComposerTable->createExpressionContext(); QgsExpressionBuilderDialog exprDlg( mComposerTable->sourceLayer(), mFeatureFilterEdit->text(), this, QStringLiteral( "generic" ), context ); - exprDlg.setWindowTitle( tr( "Expression based filter" ) ); + exprDlg.setWindowTitle( tr( "Expression Based Filter" ) ); if ( exprDlg.exec() == QDialog::Accepted ) { QString expression = exprDlg.expressionText(); diff --git a/src/app/composer/qgscomposerhtmlwidget.cpp b/src/app/composer/qgscomposerhtmlwidget.cpp index eb0fee73de9..0003eb0272f 100644 --- a/src/app/composer/qgscomposerhtmlwidget.cpp +++ b/src/app/composer/qgscomposerhtmlwidget.cpp @@ -362,7 +362,7 @@ void QgsComposerHtmlWidget::on_mInsertExpressionButton_clicked() QgsVectorLayer *coverageLayer = atlasCoverageLayer(); QgsExpressionContext context = mHtml->createExpressionContext(); QgsExpressionBuilderDialog exprDlg( coverageLayer, selText, this, QStringLiteral( "generic" ), context ); - exprDlg.setWindowTitle( tr( "Insert expression" ) ); + exprDlg.setWindowTitle( tr( "Insert Expression" ) ); if ( exprDlg.exec() == QDialog::Accepted ) { QString expression = exprDlg.expressionText(); diff --git a/src/app/composer/qgscomposerlabelwidget.cpp b/src/app/composer/qgscomposerlabelwidget.cpp index 2988f087ac3..3853db84d9b 100644 --- a/src/app/composer/qgscomposerlabelwidget.cpp +++ b/src/app/composer/qgscomposerlabelwidget.cpp @@ -149,7 +149,7 @@ void QgsComposerLabelWidget::on_mInsertExpressionButton_clicked() QgsExpressionContext context = mComposerLabel->createExpressionContext(); QgsExpressionBuilderDialog exprDlg( coverageLayer, selText, this, QStringLiteral( "generic" ), context ); - exprDlg.setWindowTitle( tr( "Insert expression" ) ); + exprDlg.setWindowTitle( tr( "Insert Expression" ) ); if ( exprDlg.exec() == QDialog::Accepted ) { QString expression = exprDlg.expressionText(); diff --git a/src/app/composer/qgscomposermapgridwidget.cpp b/src/app/composer/qgscomposermapgridwidget.cpp index 50c505a5128..e19e65e247d 100644 --- a/src/app/composer/qgscomposermapgridwidget.cpp +++ b/src/app/composer/qgscomposermapgridwidget.cpp @@ -1075,7 +1075,7 @@ void QgsComposerMapGridWidget::on_mAnnotationFormatButton_clicked() QgsExpressionContext expressionContext = mComposerMapGrid->createExpressionContext(); QgsExpressionBuilderDialog exprDlg( nullptr, mComposerMapGrid->annotationExpression(), this, QStringLiteral( "generic" ), expressionContext ); - exprDlg.setWindowTitle( tr( "Expression based annotation" ) ); + exprDlg.setWindowTitle( tr( "Expression Based Annotation" ) ); if ( exprDlg.exec() == QDialog::Accepted ) { diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 8dfae20a369..39e4bb81ef5 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -888,7 +888,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh else { QDialog *dialog = new QDialog( this ); - dialog->setWindowTitle( tr( "Project snapping settings" ) ); + dialog->setWindowTitle( tr( "Project Snapping Settings" ) ); QVBoxLayout *layout = new QVBoxLayout( dialog ); layout->addWidget( mSnappingDialog ); layout->setMargin( 0 ); @@ -6378,7 +6378,7 @@ void QgisApp::labelingFontNotFound( QgsVectorLayer *vlayer, const QString &fontf void QgisApp::commitError( QgsVectorLayer *vlayer ) { QgsMessageViewer *mv = new QgsMessageViewer(); - mv->setWindowTitle( tr( "Commit errors" ) ); + mv->setWindowTitle( tr( "Commit Errors" ) ); mv->setMessageAsPlainText( tr( "Could not commit changes to layer %1" ).arg( vlayer->name() ) + "\n\n" + tr( "Errors: %1\n" ).arg( vlayer->commitErrors().join( QStringLiteral( "\n " ) ) ) @@ -6480,7 +6480,7 @@ void QgisApp::diagramProperties() } QDialog dlg; - dlg.setWindowTitle( tr( "Layer diagram properties" ) ); + dlg.setWindowTitle( tr( "Layer Diagram Properties" ) ); QgsDiagramProperties *gui = new QgsDiagramProperties( vlayer, &dlg, mMapCanvas ); gui->layout()->setContentsMargins( 0, 0, 0, 0 ); QVBoxLayout *layout = new QVBoxLayout( &dlg ); @@ -6880,7 +6880,7 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer *vlayer, bool symbologyOpt if ( error != QgsVectorFileWriter::Canceled ) { QgsMessageViewer *m = new QgsMessageViewer( nullptr ); - m->setWindowTitle( tr( "Save error" ) ); + m->setWindowTitle( tr( "Save Error" ) ); m->setMessageAsPlainText( tr( "Export to vector file failed.\nError: %1" ).arg( errorMessage ) ); m->exec(); } diff --git a/src/app/qgsaddtaborgroup.cpp b/src/app/qgsaddtaborgroup.cpp index ebaae7c85bd..4fd74c7ff09 100644 --- a/src/app/qgsaddtaborgroup.cpp +++ b/src/app/qgsaddtaborgroup.cpp @@ -54,7 +54,7 @@ QgsAddTabOrGroup::QgsAddTabOrGroup( QgsVectorLayer *lyr, const QList < TabPair > mColumnCountSpinBox->setValue( QgsSettings().value( QStringLiteral( "/qgis/attributeForm/defaultTabColumnCount" ), 1 ).toInt() ); - setWindowTitle( tr( "Add tab or group for %1" ).arg( mLayer->name() ) ); + setWindowTitle( tr( "Add Tab or Group for %1" ).arg( mLayer->name() ) ); } // QgsVectorLayerProperties ctor QgsAddTabOrGroup::~QgsAddTabOrGroup() diff --git a/src/app/qgsapplayertreeviewmenuprovider.cpp b/src/app/qgsapplayertreeviewmenuprovider.cpp index 2349491cb0b..475e97e6180 100644 --- a/src/app/qgsapplayertreeviewmenuprovider.cpp +++ b/src/app/qgsapplayertreeviewmenuprovider.cpp @@ -504,7 +504,7 @@ void QgsAppLayerTreeViewMenuProvider::editVectorSymbol() std::unique_ptr< QgsSymbol > symbol( singleRenderer->symbol() ? singleRenderer->symbol()->clone() : nullptr ); QgsSymbolSelectorDialog dlg( symbol.get(), QgsStyle::defaultStyle(), layer, mView->window() ); - dlg.setWindowTitle( tr( "Symbol selector" ) ); + dlg.setWindowTitle( tr( "Symbol Selector" ) ); QgsSymbolWidgetContext context; context.setMapCanvas( mCanvas ); dlg.setContext( context ); @@ -581,7 +581,7 @@ void QgsAppLayerTreeViewMenuProvider::editSymbolLegendNodeSymbol() std::unique_ptr< QgsSymbol > symbol( originalSymbol->clone() ); QgsVectorLayer *vlayer = qobject_cast( node->layerNode()->layer() ); QgsSymbolSelectorDialog dlg( symbol.get(), QgsStyle::defaultStyle(), vlayer, mView->window() ); - dlg.setWindowTitle( tr( "Symbol selector" ) ); + dlg.setWindowTitle( tr( "Symbol Selector" ) ); QgsSymbolWidgetContext context; context.setMapCanvas( mCanvas ); dlg.setContext( context ); diff --git a/src/app/qgsattributeactiondialog.cpp b/src/app/qgsattributeactiondialog.cpp index b15dea77c5f..e9d84649688 100644 --- a/src/app/qgsattributeactiondialog.cpp +++ b/src/app/qgsattributeactiondialog.cpp @@ -267,7 +267,7 @@ void QgsAttributeActionDialog::insert() int pos = mAttributeActionTable->rowCount(); QgsAttributeActionPropertiesDialog dlg( mLayer, this ); - dlg.setWindowTitle( tr( "Add new action" ) ); + dlg.setWindowTitle( tr( "Add New Action" ) ); if ( dlg.exec() ) { @@ -325,7 +325,7 @@ void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item ) mLayer ); - actionProperties.setWindowTitle( tr( "Edit action" ) ); + actionProperties.setWindowTitle( tr( "Edit Action" ) ); if ( actionProperties.exec() ) { diff --git a/src/app/qgsattributetabledialog.cpp b/src/app/qgsattributetabledialog.cpp index 62e5ac698e7..5a810cce4ff 100644 --- a/src/app/qgsattributetabledialog.cpp +++ b/src/app/qgsattributetabledialog.cpp @@ -342,7 +342,7 @@ QgsAttributeTableDialog::~QgsAttributeTableDialog() void QgsAttributeTableDialog::updateTitle() { QWidget *w = mDock ? qobject_cast( mDock ) : qobject_cast( this ); - w->setWindowTitle( tr( " %1 :: Features total: %2, filtered: %3, selected: %4" ) + w->setWindowTitle( tr( " %1 :: Features Total: %2, Filtered: %3, Selected: %4" ) .arg( mLayer->name() ) .arg( qMax( static_cast< long >( mMainView->featureCount() ), mLayer->featureCount() ) ) // layer count may be estimated, so use larger of the two .arg( mMainView->filteredFeatureCount() ) @@ -584,7 +584,7 @@ void QgsAttributeTableDialog::filterExpressionBuilder() QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); QgsExpressionBuilderDialog dlg( mLayer, mFilterQuery->text(), this, QStringLiteral( "generic" ), context ); - dlg.setWindowTitle( tr( "Expression based filter" ) ); + dlg.setWindowTitle( tr( "Expression Based Filter" ) ); QgsDistanceArea myDa; myDa.setSourceCrs( mLayer->crs() ); diff --git a/src/app/qgsdiagramproperties.cpp b/src/app/qgsdiagramproperties.cpp index 5c4607316f3..668e061d798 100644 --- a/src/app/qgsdiagramproperties.cpp +++ b/src/app/qgsdiagramproperties.cpp @@ -851,7 +851,7 @@ QString QgsDiagramProperties::showExpressionBuilder( const QString &initialExpre << QgsExpressionContextUtils::layerScope( mLayer ); QgsExpressionBuilderDialog dlg( mLayer, initialExpression, this, QStringLiteral( "generic" ), context ); - dlg.setWindowTitle( tr( "Expression based attribute" ) ); + dlg.setWindowTitle( tr( "Expression Based Attribute" ) ); QgsDistanceArea myDa; myDa.setSourceCrs( mLayer->crs() ); diff --git a/src/app/qgsfieldsproperties.cpp b/src/app/qgsfieldsproperties.cpp index c5e2d82cd97..24ada09c6e4 100644 --- a/src/app/qgsfieldsproperties.cpp +++ b/src/app/qgsfieldsproperties.cpp @@ -1336,7 +1336,7 @@ void DesignerTree::onItemDoubleClicked( QTreeWidgetItem *item, int column ) if ( itemData.type() == QgsFieldsProperties::DesignerTreeItemData::Container ) { QDialog dlg; - dlg.setWindowTitle( tr( "Configure container" ) ); + dlg.setWindowTitle( tr( "Configure Container" ) ); QFormLayout *layout = new QFormLayout() ; dlg.setLayout( layout ); layout->addRow( baseWidget ); @@ -1395,7 +1395,7 @@ void DesignerTree::onItemDoubleClicked( QTreeWidgetItem *item, int column ) else if ( itemData.type() == QgsFieldsProperties::DesignerTreeItemData::Relation ) { QDialog dlg; - dlg.setWindowTitle( tr( "Configure relation editor" ) ); + dlg.setWindowTitle( tr( "Configure Relation Editor" ) ); QFormLayout *layout = new QFormLayout() ; dlg.setLayout( layout ); layout->addWidget( baseWidget ); @@ -1428,7 +1428,7 @@ void DesignerTree::onItemDoubleClicked( QTreeWidgetItem *item, int column ) else { QDialog dlg; - dlg.setWindowTitle( tr( "Configure field" ) ); + dlg.setWindowTitle( tr( "Configure Field" ) ); dlg.setLayout( new QGridLayout() ); dlg.layout()->addWidget( baseWidget ); diff --git a/src/app/qgsguivectorlayertools.cpp b/src/app/qgsguivectorlayertools.cpp index cfaf5dbeeb4..eb96d8a869f 100644 --- a/src/app/qgsguivectorlayertools.cpp +++ b/src/app/qgsguivectorlayertools.cpp @@ -162,7 +162,7 @@ bool QgsGuiVectorLayerTools::stopEditing( QgsVectorLayer *layer, bool allowCance void QgsGuiVectorLayerTools::commitError( QgsVectorLayer *vlayer ) const { QgsMessageViewer *mv = new QgsMessageViewer(); - mv->setWindowTitle( tr( "Commit errors" ) ); + mv->setWindowTitle( tr( "Commit Errors" ) ); mv->setMessageAsPlainText( tr( "Could not commit changes to layer %1" ).arg( vlayer->name() ) + "\n\n" + tr( "Errors: %1\n" ).arg( vlayer->commitErrors().join( QStringLiteral( "\n " ) ) ) diff --git a/src/app/qgshtmlannotationdialog.cpp b/src/app/qgshtmlannotationdialog.cpp index 7c1f4488600..3f13243bdfd 100644 --- a/src/app/qgshtmlannotationdialog.cpp +++ b/src/app/qgshtmlannotationdialog.cpp @@ -30,7 +30,7 @@ QgsHtmlAnnotationDialog::QgsHtmlAnnotationDialog( QgsMapCanvasAnnotationItem *it , mEmbeddedWidget( nullptr ) { setupUi( this ); - setWindowTitle( tr( "HTML annotation" ) ); + setWindowTitle( tr( "HTML Annotation" ) ); mEmbeddedWidget = new QgsAnnotationWidget( mItem ); mStackedWidget->addWidget( mEmbeddedWidget ); mStackedWidget->setCurrentWidget( mEmbeddedWidget ); diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index 982cee49421..21051748f6c 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -109,7 +109,7 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QL } mSaveAsRaster->setVisible( true ); - this->setWindowTitle( tr( "Save map as PDF" ) ); + this->setWindowTitle( tr( "Save Map as PDF" ) ); } else { diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp index 55f1eaaf0e1..b2f35aef06e 100644 --- a/src/app/qgsprojectproperties.cpp +++ b/src/app/qgsprojectproperties.cpp @@ -1399,7 +1399,7 @@ void QgsProjectProperties::on_mRemoveWMSComposerButton_clicked() void QgsProjectProperties::on_mAddLayerRestrictionButton_clicked() { QgsProjectLayerGroupDialog d( this, QgsProject::instance()->fileName() ); - d.setWindowTitle( tr( "Select restricted layers and groups" ) ); + d.setWindowTitle( tr( "Select Restricted Layers and Groups" ) ); if ( d.exec() == QDialog::Accepted ) { QStringList layerNames = d.selectedLayerNames(); diff --git a/src/app/qgssavestyletodbdialog.cpp b/src/app/qgssavestyletodbdialog.cpp index 611408edabe..2e4588ce06a 100644 --- a/src/app/qgssavestyletodbdialog.cpp +++ b/src/app/qgssavestyletodbdialog.cpp @@ -26,7 +26,7 @@ QgsSaveStyleToDbDialog::QgsSaveStyleToDbDialog( QWidget *parent ) : QDialog( parent ) { setupUi( this ); - setWindowTitle( QStringLiteral( "Save style in database" ) ); + setWindowTitle( QStringLiteral( "Save Style in Database" ) ); mDescriptionEdit->setTabChangesFocus( true ); setTabOrder( mNameEdit, mDescriptionEdit ); setTabOrder( mDescriptionEdit, mUseAsDefault ); diff --git a/src/app/qgsselectbyformdialog.cpp b/src/app/qgsselectbyformdialog.cpp index 46bcc9d2964..d4ba60bb176 100644 --- a/src/app/qgsselectbyformdialog.cpp +++ b/src/app/qgsselectbyformdialog.cpp @@ -44,7 +44,7 @@ QgsSelectByFormDialog::QgsSelectByFormDialog( QgsVectorLayer *layer, const QgsAt QgsSettings settings; restoreGeometry( settings.value( QStringLiteral( "Windows/SelectByForm/geometry" ) ).toByteArray() ); - setWindowTitle( tr( "Select features by value" ) ); + setWindowTitle( tr( "Select Features by Value" ) ); } QgsSelectByFormDialog::~QgsSelectByFormDialog() diff --git a/src/gui/attributetable/qgsdualview.cpp b/src/gui/attributetable/qgsdualview.cpp index 63e2d2c6e10..96242c4c583 100644 --- a/src/gui/attributetable/qgsdualview.cpp +++ b/src/gui/attributetable/qgsdualview.cpp @@ -377,7 +377,7 @@ void QgsDualView::previewExpressionBuilder() QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); QgsExpressionBuilderDialog dlg( mLayer, mFeatureList->displayExpression(), this, QStringLiteral( "generic" ), context ); - dlg.setWindowTitle( tr( "Expression based preview" ) ); + dlg.setWindowTitle( tr( "Expression Based Preview" ) ); dlg.setExpressionText( mFeatureList->displayExpression() ); if ( dlg.exec() == QDialog::Accepted ) @@ -606,7 +606,7 @@ void QgsDualView::modifySort() QgsAttributeTableConfig config = mConfig; QDialog orderByDlg; - orderByDlg.setWindowTitle( tr( "Configure attribute table sort order" ) ); + orderByDlg.setWindowTitle( tr( "Configure Attribute Table Sort Order" ) ); QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ); QGridLayout *layout = new QGridLayout(); connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept ); @@ -800,7 +800,7 @@ void QgsDualView::progress( int i, bool &cancel ) if ( !mProgressDlg ) { mProgressDlg = new QProgressDialog( tr( "Loading features..." ), tr( "Abort" ), 0, 0, this ); - mProgressDlg->setWindowTitle( tr( "Attribute table" ) ); + mProgressDlg->setWindowTitle( tr( "Attribute Table" ) ); mProgressDlg->setWindowModality( Qt::WindowModal ); mProgressDlg->show(); } diff --git a/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp b/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp index e8232d2db39..90c182f2be8 100644 --- a/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp @@ -75,7 +75,7 @@ void QgsValueRelationConfigDlg::editExpression() QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vl ) ); QgsExpressionBuilderDialog dlg( vl, mFilterExpression->toPlainText(), this, QStringLiteral( "generic" ), context ); - dlg.setWindowTitle( tr( "Edit filter expression" ) ); + dlg.setWindowTitle( tr( "Edit Filter Expression" ) ); if ( dlg.exec() == QDialog::Accepted ) { diff --git a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp index c4fc70bfdb8..e1584c59466 100644 --- a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp +++ b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp @@ -229,7 +229,7 @@ void QgsVectorLayerSaveAsDialog::accept() { QMessageBox msgBox; msgBox.setIcon( QMessageBox::Question ); - msgBox.setWindowTitle( tr( "The layer already exists" ) ); + msgBox.setWindowTitle( tr( "The Layer Already Exists" ) ); msgBox.setText( tr( "Do you want to overwrite the whole file or overwrite the layer?" ) ); QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole ); QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole ); @@ -258,7 +258,7 @@ void QgsVectorLayerSaveAsDialog::accept() { QMessageBox msgBox; msgBox.setIcon( QMessageBox::Question ); - msgBox.setWindowTitle( tr( "The layer already exists" ) ); + msgBox.setWindowTitle( tr( "The Layer Already Exists" ) ); msgBox.setText( tr( "Do you want to overwrite the whole file, overwrite the layer or append features to the layer?" ) ); QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole ); QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole ); @@ -279,7 +279,7 @@ void QgsVectorLayerSaveAsDialog::accept() { QMessageBox msgBox; msgBox.setIcon( QMessageBox::Question ); - msgBox.setWindowTitle( tr( "The layer already exists" ) ); + msgBox.setWindowTitle( tr( "The Layer Already Exists" ) ); msgBox.setText( tr( "Do you want to overwrite the whole file or append features to the layer?" ) ); QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole ); QPushButton *appendToLayerButton = msgBox.addButton( tr( "Append to layer" ), QMessageBox::ActionRole ); diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index 34d983617e7..7250f85f020 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -649,7 +649,7 @@ void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( const QString &lin { Q_UNUSED( link ); QgsMessageViewer *mv = new QgsMessageViewer( this ); - mv->setWindowTitle( tr( "More info on expression error" ) ); + mv->setWindowTitle( tr( "More Info on Expression Error" ) ); mv->setMessageAsHtml( txtExpressionString->toolTip() ); mv->exec(); } diff --git a/src/gui/qgsexpressionselectiondialog.cpp b/src/gui/qgsexpressionselectiondialog.cpp index 4d8fe47e0ce..8bdd94ea650 100644 --- a/src/gui/qgsexpressionselectiondialog.cpp +++ b/src/gui/qgsexpressionselectiondialog.cpp @@ -31,7 +31,7 @@ QgsExpressionSelectionDialog::QgsExpressionSelectionDialog( QgsVectorLayer *laye { setupUi( this ); - setWindowTitle( QStringLiteral( "Select by expression - %1" ).arg( layer->name() ) ); + setWindowTitle( QStringLiteral( "Select by Expression - %1" ).arg( layer->name() ) ); mActionSelect->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionSelect.svg" ) ) ); mActionAddToSelection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) ); diff --git a/src/gui/qgsnewgeopackagelayerdialog.cpp b/src/gui/qgsnewgeopackagelayerdialog.cpp index 38443804238..bfee2299ff4 100644 --- a/src/gui/qgsnewgeopackagelayerdialog.cpp +++ b/src/gui/qgsnewgeopackagelayerdialog.cpp @@ -241,7 +241,7 @@ bool QgsNewGeoPackageLayerDialog::apply() { QMessageBox msgBox; msgBox.setIcon( QMessageBox::Question ); - msgBox.setWindowTitle( tr( "The file already exists." ) ); + msgBox.setWindowTitle( tr( "The File Already Exists." ) ); msgBox.setText( tr( "Do you want to overwrite the existing file with a new database or add a new layer to it?" ) ); QPushButton *overwriteButton = msgBox.addButton( tr( "Overwrite" ), QMessageBox::ActionRole ); QPushButton *addNewLayerButton = msgBox.addButton( tr( "Add new layer" ), QMessageBox::ActionRole ); diff --git a/src/gui/qgsnewhttpconnection.cpp b/src/gui/qgsnewhttpconnection.cpp index 0c500e28e33..3795080a029 100644 --- a/src/gui/qgsnewhttpconnection.cpp +++ b/src/gui/qgsnewhttpconnection.cpp @@ -36,7 +36,7 @@ QgsNewHttpConnection::QgsNewHttpConnection( QRegExp rx( "/connections-([^/]+)/" ); rx.indexIn( baseKey ); - setWindowTitle( tr( "Create a new %1 connection" ).arg( rx.cap( 1 ).toUpper() ) ); + setWindowTitle( tr( "Create a New %1 Connection" ).arg( rx.cap( 1 ).toUpper() ) ); // It would be obviously much better to use mBaseKey also for credentials, // but for some strange reason a different hardcoded key was used instead. diff --git a/src/gui/qgsnewnamedialog.cpp b/src/gui/qgsnewnamedialog.cpp index ab5ef3bb87c..a44e35be01e 100644 --- a/src/gui/qgsnewnamedialog.cpp +++ b/src/gui/qgsnewnamedialog.cpp @@ -35,7 +35,7 @@ QgsNewNameDialog::QgsNewNameDialog( const QString &source, const QString &initia , mRegexp( regexp ) , mOverwriteEnabled( true ) { - setWindowTitle( tr( "New name" ) ); + setWindowTitle( tr( "New Name" ) ); QDialog::layout()->setSizeConstraint( QLayout::SetMinimumSize ); layout()->setSizeConstraint( QLayout::SetMinimumSize ); layout()->setSpacing( 6 ); diff --git a/src/gui/qgspropertyoverridebutton.cpp b/src/gui/qgspropertyoverridebutton.cpp index 6322f3161a8..c33c3080086 100644 --- a/src/gui/qgspropertyoverridebutton.cpp +++ b/src/gui/qgspropertyoverridebutton.cpp @@ -541,7 +541,7 @@ void QgsPropertyOverrideButton::menuActionTriggered( QAction *action ) void QgsPropertyOverrideButton::showDescriptionDialog() { QgsMessageViewer *mv = new QgsMessageViewer( this ); - mv->setWindowTitle( tr( "Data definition description" ) ); + mv->setWindowTitle( tr( "Data Definition Description" ) ); mv->setMessageAsHtml( mFullDescription ); mv->exec(); } diff --git a/src/gui/qgssearchquerybuilder.cpp b/src/gui/qgssearchquerybuilder.cpp index 66dcac5a9d6..3c3918e25f1 100644 --- a/src/gui/qgssearchquerybuilder.cpp +++ b/src/gui/qgssearchquerybuilder.cpp @@ -40,7 +40,7 @@ QgsSearchQueryBuilder::QgsSearchQueryBuilder( QgsVectorLayer *layer, setupUi( this ); setupListViews(); - setWindowTitle( tr( "Search query builder" ) ); + setWindowTitle( tr( "Search Query Builder" ) ); QPushButton *pbn = new QPushButton( tr( "&Test" ) ); buttonBox->addButton( pbn, QDialogButtonBox::ActionRole ); diff --git a/src/gui/qgssublayersdialog.cpp b/src/gui/qgssublayersdialog.cpp index aaccf9d1165..3ad57988a4b 100644 --- a/src/gui/qgssublayersdialog.cpp +++ b/src/gui/qgssublayersdialog.cpp @@ -50,7 +50,7 @@ QgsSublayersDialog::QgsSublayersDialog( ProviderType providerType, const QString if ( providerType == QgsSublayersDialog::Ogr ) { - setWindowTitle( tr( "Select vector layers to add..." ) ); + setWindowTitle( tr( "Select Vector Layers to Add..." ) ); layersTable->setHeaderLabels( QStringList() << tr( "Layer ID" ) << tr( "Layer name" ) << tr( "Number of features" ) << tr( "Geometry type" ) ); mShowCount = true; @@ -58,12 +58,12 @@ QgsSublayersDialog::QgsSublayersDialog( ProviderType providerType, const QString } else if ( providerType == QgsSublayersDialog::Gdal ) { - setWindowTitle( tr( "Select raster layers to add..." ) ); + setWindowTitle( tr( "Select Raster Layers to Add..." ) ); layersTable->setHeaderLabels( QStringList() << tr( "Layer ID" ) << tr( "Layer name" ) ); } else { - setWindowTitle( tr( "Select layers to add..." ) ); + setWindowTitle( tr( "Select Layers to Add..." ) ); layersTable->setHeaderLabels( QStringList() << tr( "Layer ID" ) << tr( "Layer name" ) << tr( "Type" ) ); mShowType = true; diff --git a/src/gui/qgstextformatwidget.cpp b/src/gui/qgstextformatwidget.cpp index 4819fb6528a..c7cc11e9c2e 100644 --- a/src/gui/qgstextformatwidget.cpp +++ b/src/gui/qgstextformatwidget.cpp @@ -1431,7 +1431,7 @@ void QgsTextFormatWidget::enableDataDefinedAlignment( bool enable ) QgsTextFormatDialog::QgsTextFormatDialog( const QgsTextFormat &format, QgsMapCanvas *mapCanvas, QWidget *parent, Qt::WindowFlags fl ) : QDialog( parent, fl ) { - setWindowTitle( tr( "Text settings" ) ); + setWindowTitle( tr( "Text Settings" ) ); mFormatWidget = new QgsTextFormatWidget( format, mapCanvas, this ); mFormatWidget->layout()->setContentsMargins( 0, 0, 0, 0 ); diff --git a/src/gui/symbology-ng/qgscptcitycolorrampdialog.cpp b/src/gui/symbology-ng/qgscptcitycolorrampdialog.cpp index e0f63968407..69e8901a5ce 100644 --- a/src/gui/symbology-ng/qgscptcitycolorrampdialog.cpp +++ b/src/gui/symbology-ng/qgscptcitycolorrampdialog.cpp @@ -60,7 +60,7 @@ QgsCptCityColorRampDialog::QgsCptCityColorRampDialog( const QgsCptCityColorRamp if ( ! mArchive || mArchive->isEmpty() ) { // QgsDialog dlg( this ); - // dlg.setWindowTitle( tr( "cpt-city gradient files not found" ) ); + // dlg.setWindowTitle( tr( "Cpt-city Gradient Files Not Found" ) ); QTextEdit *edit = new QTextEdit( nullptr ); edit->setReadOnly( true ); // not sure if we want this long string to be translated @@ -314,11 +314,11 @@ void QgsCptCityColorRampDialog::on_pbtnLicenseDetails_pressed() path = item->path(); if ( item->type() == QgsCptCityDataItem::Directory ) { - title = tr( "%1 directory details" ).arg( item->path() ); + title = tr( "%1 Directory Details" ).arg( item->path() ); } else if ( item->type() == QgsCptCityColorRampItem::Directory ) { - title = tr( "%1 gradient details" ).arg( path ); + title = tr( "%1 Gradient Details" ).arg( path ); } else { diff --git a/src/gui/symbology-ng/qgsstyleexportimportdialog.cpp b/src/gui/symbology-ng/qgsstyleexportimportdialog.cpp index d732ce712c8..71b715997fa 100644 --- a/src/gui/symbology-ng/qgsstyleexportimportdialog.cpp +++ b/src/gui/symbology-ng/qgsstyleexportimportdialog.cpp @@ -67,7 +67,7 @@ QgsStyleExportImportDialog::QgsStyleExportImportDialog( QgsStyle *style, QWidget if ( mDialogMode == Import ) { - setWindowTitle( tr( "Import symbol(s)" ) ); + setWindowTitle( tr( "Import Symbol(s)" ) ); // populate the import types importTypeCombo->addItem( tr( "file specified below" ), QVariant( "file" ) ); // importTypeCombo->addItem( "official QGIS repo online", QVariant( "official" ) ); @@ -84,7 +84,7 @@ QgsStyleExportImportDialog::QgsStyleExportImportDialog( QgsStyle *style, QWidget } else { - setWindowTitle( tr( "Export symbol(s)" ) ); + setWindowTitle( tr( "Export Symbol(s)" ) ); // hide import specific controls when exporting btnBrowse->setHidden( true ); fromLabel->setHidden( true ); @@ -439,7 +439,7 @@ void QgsStyleExportImportDialog::selectByGroup() if ( ! mGroupSelectionDlg ) { mGroupSelectionDlg = new QgsStyleGroupSelectionDialog( mStyle, this ); - mGroupSelectionDlg->setWindowTitle( tr( "Select symbols by group" ) ); + mGroupSelectionDlg->setWindowTitle( tr( "Select Symbols by Group" ) ); connect( mGroupSelectionDlg, &QgsStyleGroupSelectionDialog::tagSelected, this, &QgsStyleExportImportDialog::selectTag ); connect( mGroupSelectionDlg, &QgsStyleGroupSelectionDialog::tagDeselected, this, &QgsStyleExportImportDialog::deselectTag ); connect( mGroupSelectionDlg, &QgsStyleGroupSelectionDialog::allSelected, this, &QgsStyleExportImportDialog::selectAll ); diff --git a/src/gui/symbology-ng/qgsstylesavedialog.cpp b/src/gui/symbology-ng/qgsstylesavedialog.cpp index 2daf534a647..d4e93f755a2 100644 --- a/src/gui/symbology-ng/qgsstylesavedialog.cpp +++ b/src/gui/symbology-ng/qgsstylesavedialog.cpp @@ -30,11 +30,11 @@ QgsStyleSaveDialog::QgsStyleSaveDialog( QWidget *parent, QgsStyle::StyleEntity t if ( type == QgsStyle::SymbolEntity ) { - this->setWindowTitle( tr( "Save new symbol" ) ); + this->setWindowTitle( tr( "Save New Symbol" ) ); } else if ( type == QgsStyle::ColorrampEntity ) { - this->setWindowTitle( tr( "Save new color ramp" ) ); + this->setWindowTitle( tr( "Save New Color Ramp" ) ); } } diff --git a/src/plugins/evis/eventbrowser/evisgenericeventbrowsergui.cpp b/src/plugins/evis/eventbrowser/evisgenericeventbrowsergui.cpp index 53bec4a7845..842d2dd8ea1 100644 --- a/src/plugins/evis/eventbrowser/evisgenericeventbrowsergui.cpp +++ b/src/plugins/evis/eventbrowser/evisgenericeventbrowsergui.cpp @@ -303,7 +303,7 @@ bool eVisGenericEventBrowserGui::initBrowser() pbtnNext->setEnabled( true ); } - setWindowTitle( tr( "Event Browser - Displaying records 01 of %1" ).arg( mFeatureIds.size(), 2, 10, QChar( '0' ) ) ); + setWindowTitle( tr( "Event Browser - Displaying Records 01 of %1" ).arg( mFeatureIds.size(), 2, 10, QChar( '0' ) ) ); //Set Options tab gui items initOptionsTab(); @@ -997,7 +997,7 @@ void eVisGenericEventBrowserGui::on_pbtnNext_clicked() pbtnPrevious->setEnabled( true ); mCurrentFeatureIndex++; - setWindowTitle( tr( "Event Browser - Displaying records %1 of %2" ) + setWindowTitle( tr( "Event Browser - Displaying Records %1 of %2" ) .arg( mCurrentFeatureIndex + 1, 2, 10, QChar( '0' ) ).arg( mFeatureIds.size(), 2, 10, QChar( '0' ) ) ); loadRecord(); @@ -1019,7 +1019,7 @@ void eVisGenericEventBrowserGui::on_pbtnPrevious_clicked() pbtnNext->setEnabled( true ); mCurrentFeatureIndex--; - setWindowTitle( tr( "Event Browser - Displaying records %1 of %2" ) + setWindowTitle( tr( "Event Browser - Displaying Records %1 of %2" ) .arg( mCurrentFeatureIndex + 1, 2, 10, QChar( '0' ) ).arg( mFeatureIds.size(), 2, 10, QChar( '0' ) ) ); loadRecord(); diff --git a/src/plugins/geometry_checker/ui/qgsgeometrycheckerresulttab.cpp b/src/plugins/geometry_checker/ui/qgsgeometrycheckerresulttab.cpp index ce4b24a90d3..e1f929af3ff 100644 --- a/src/plugins/geometry_checker/ui/qgsgeometrycheckerresulttab.cpp +++ b/src/plugins/geometry_checker/ui/qgsgeometrycheckerresulttab.cpp @@ -107,7 +107,7 @@ void QgsGeometryCheckerResultTab::finalize() dialog.layout()->addWidget( bbox ); connect( bbox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept ); connect( bbox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject ); - dialog.setWindowTitle( tr( "Check errors occurred" ) ); + dialog.setWindowTitle( tr( "Check Errors Occurred" ) ); dialog.exec(); } } diff --git a/src/plugins/geometry_checker/ui/qgsgeometrycheckfixdialog.cpp b/src/plugins/geometry_checker/ui/qgsgeometrycheckfixdialog.cpp index 7bb4a19b40e..2ff5ca4d915 100644 --- a/src/plugins/geometry_checker/ui/qgsgeometrycheckfixdialog.cpp +++ b/src/plugins/geometry_checker/ui/qgsgeometrycheckfixdialog.cpp @@ -40,7 +40,7 @@ QgsGeometryCheckerFixDialog::QgsGeometryCheckerFixDialog( QgsGeometryChecker *ch , mErrors( errors ) , mIface( iface ) { - setWindowTitle( tr( "Fix errors" ) ); + setWindowTitle( tr( "Fix Errors" ) ); QGridLayout *layout = new QGridLayout(); layout->setContentsMargins( 4, 4, 4, 4 ); diff --git a/src/plugins/georeferencer/qgsgeorefplugingui.cpp b/src/plugins/georeferencer/qgsgeorefplugingui.cpp index 7dbdcfc415c..673d329103d 100644 --- a/src/plugins/georeferencer/qgsgeorefplugingui.cpp +++ b/src/plugins/georeferencer/qgsgeorefplugingui.cpp @@ -1841,7 +1841,7 @@ void QgsGeorefPluginGui::showGDALScript( const QStringList &commands ) layout->addWidget( bbxGdalScript ); QDialog *dlgShowGdalScrip = new QDialog( this ); - dlgShowGdalScrip->setWindowTitle( tr( "GDAL script" ) ); + dlgShowGdalScrip->setWindowTitle( tr( "GDAL Script" ) ); dlgShowGdalScrip->setLayout( layout ); connect( bbxGdalScript, &QDialogButtonBox::accepted, dlgShowGdalScrip, &QDialog::accept ); diff --git a/src/plugins/georeferencer/qgsimagewarper.cpp b/src/plugins/georeferencer/qgsimagewarper.cpp index 6a5fa5e88b3..7914932e1a9 100644 --- a/src/plugins/georeferencer/qgsimagewarper.cpp +++ b/src/plugins/georeferencer/qgsimagewarper.cpp @@ -215,7 +215,7 @@ int QgsImageWarper::warpFile( const QString &input, // Create a QT progress dialog QProgressDialog *progressDialog = new QProgressDialog( mParent ); - progressDialog->setWindowTitle( tr( "Progress indication" ) ); + progressDialog->setWindowTitle( tr( "Progress Indication" ) ); progressDialog->setRange( 0, 100 ); progressDialog->setAutoClose( true ); progressDialog->setModal( true ); diff --git a/src/plugins/grass/qgsgrassselect.cpp b/src/plugins/grass/qgsgrassselect.cpp index 4e4e5c6840c..24d798c3f39 100644 --- a/src/plugins/grass/qgsgrassselect.cpp +++ b/src/plugins/grass/qgsgrassselect.cpp @@ -81,7 +81,7 @@ QgsGrassSelect::QgsGrassSelect( QWidget *parent, int type ) /* Remove layer combo box */ Layer->hide(); elayer->hide(); - setWindowTitle( tr( "Select GRASS mapcalc schema" ) ); + setWindowTitle( tr( "Select GRASS Mapcalc Schema" ) ); break; case QgsGrassSelect::MapSet: diff --git a/src/providers/arcgisrest/qgsafsdataitems.cpp b/src/providers/arcgisrest/qgsafsdataitems.cpp index 5b44432bfb2..ba7f469fa98 100644 --- a/src/providers/arcgisrest/qgsafsdataitems.cpp +++ b/src/providers/arcgisrest/qgsafsdataitems.cpp @@ -72,7 +72,7 @@ void QgsAfsRootItem::connectionsChanged() void QgsAfsRootItem::newConnection() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-arcgisfeatureserver/" ) ); - nc.setWindowTitle( tr( "Create a new ArcGisFeatureServer connection" ) ); + nc.setWindowTitle( tr( "Create a New ArcGisFeatureServer Connection" ) ); if ( nc.exec() ) { @@ -138,7 +138,7 @@ QList QgsAfsConnectionItem::actions() void QgsAfsConnectionItem::editConnection() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-arcgisfeatureserver/" ), mName ); - nc.setWindowTitle( tr( "Modify ArcGisFeatureServer connection" ) ); + nc.setWindowTitle( tr( "Modify ArcGisFeatureServer Connection" ) ); if ( nc.exec() ) { diff --git a/src/providers/arcgisrest/qgsamsdataitems.cpp b/src/providers/arcgisrest/qgsamsdataitems.cpp index 9f2a7ce80ba..a8b9a3ce54a 100644 --- a/src/providers/arcgisrest/qgsamsdataitems.cpp +++ b/src/providers/arcgisrest/qgsamsdataitems.cpp @@ -71,7 +71,7 @@ void QgsAmsRootItem::connectionsChanged() void QgsAmsRootItem::newConnection() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-arcgismapserver/" ) ); - nc.setWindowTitle( tr( "Create a new ArcGisMapServer connection" ) ); + nc.setWindowTitle( tr( "Create a New ArcGisMapServer Connection" ) ); if ( nc.exec() ) { @@ -154,7 +154,7 @@ QList QgsAmsConnectionItem::actions() void QgsAmsConnectionItem::editConnection() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-arcgismapserver/" ), mName ); - nc.setWindowTitle( tr( "Modify ArcGisMapServer connection" ) ); + nc.setWindowTitle( tr( "Modify ArcGisMapServer Connection" ) ); if ( nc.exec() ) { diff --git a/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp b/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp index 2462062c2f5..72c5c9e2089 100644 --- a/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp +++ b/src/providers/arcgisrest/qgsarcgisservicesourceselect.cpp @@ -225,7 +225,7 @@ void QgsArcGisServiceSourceSelect::addEntryToServerList() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-%1/" ).arg( mServiceName.toLower() ) ); - nc.setWindowTitle( tr( "Create a new %1 connection" ).arg( mServiceName ) ); + nc.setWindowTitle( tr( "Create a New %1 Connection" ).arg( mServiceName ) ); if ( nc.exec() ) { @@ -237,7 +237,7 @@ void QgsArcGisServiceSourceSelect::addEntryToServerList() void QgsArcGisServiceSourceSelect::modifyEntryOfServerList() { QgsNewHttpConnection nc( 0, QStringLiteral( "qgis/connections-%1/" ).arg( mServiceName.toLower() ), cmbConnections->currentText() ); - nc.setWindowTitle( tr( "Modify %1 connection" ).arg( mServiceName ) ); + nc.setWindowTitle( tr( "Modify %1 Connection" ).arg( mServiceName ) ); if ( nc.exec() ) { diff --git a/src/providers/wfs/qgswfsdataitems.cpp b/src/providers/wfs/qgswfsdataitems.cpp index 7a8cb900bd6..e04fa463179 100644 --- a/src/providers/wfs/qgswfsdataitems.cpp +++ b/src/providers/wfs/qgswfsdataitems.cpp @@ -109,7 +109,7 @@ QList QgsWfsConnectionItem::actions() void QgsWfsConnectionItem::editConnection() { QgsNewHttpConnection nc( nullptr, QgsWFSConstants::CONNECTIONS_WFS, mName ); - nc.setWindowTitle( tr( "Modify WFS connection" ) ); + nc.setWindowTitle( tr( "Modify WFS Connection" ) ); if ( nc.exec() ) { @@ -183,7 +183,7 @@ void QgsWfsRootItem::connectionsChanged() void QgsWfsRootItem::newConnection() { QgsNewHttpConnection nc( nullptr, QgsWFSConstants::CONNECTIONS_WFS ); - nc.setWindowTitle( tr( "Create a new WFS connection" ) ); + nc.setWindowTitle( tr( "Create a New WFS Connection" ) ); if ( nc.exec() ) { diff --git a/src/providers/wfs/qgswfssourceselect.cpp b/src/providers/wfs/qgswfssourceselect.cpp index cad280e2736..8f4087a45dc 100644 --- a/src/providers/wfs/qgswfssourceselect.cpp +++ b/src/providers/wfs/qgswfssourceselect.cpp @@ -300,7 +300,7 @@ void QgsWFSSourceSelect::addEntryToServerList() { QgsNewHttpConnection *nc = new QgsNewHttpConnection( this, QgsWFSConstants::CONNECTIONS_WFS ); nc->setAttribute( Qt::WA_DeleteOnClose ); - nc->setWindowTitle( tr( "Create a new WFS connection" ) ); + nc->setWindowTitle( tr( "Create a New WFS Connection" ) ); // For testability, do not use exec() if ( !property( "hideDialogs" ).toBool() ) @@ -313,7 +313,7 @@ void QgsWFSSourceSelect::modifyEntryOfServerList() { QgsNewHttpConnection *nc = new QgsNewHttpConnection( this, QgsWFSConstants::CONNECTIONS_WFS, cmbConnections->currentText() ); nc->setAttribute( Qt::WA_DeleteOnClose ); - nc->setWindowTitle( tr( "Modify WFS connection" ) ); + nc->setWindowTitle( tr( "Modify WFS Connection" ) ); // For testability, do not use exec() if ( !property( "hideDialogs" ).toBool() ) diff --git a/src/ui/auth/qgsauthsslimporterrors.ui b/src/ui/auth/qgsauthsslimporterrors.ui index 4f3aec53169..de92911fff9 100644 --- a/src/ui/auth/qgsauthsslimporterrors.ui +++ b/src/ui/auth/qgsauthsslimporterrors.ui @@ -11,7 +11,7 @@ - Unable To Validate The Connection + Unable to Validate the Connection diff --git a/src/ui/composer/qgsattributeselectiondialogbase.ui b/src/ui/composer/qgsattributeselectiondialogbase.ui index 1a26de88ff6..2189873a63c 100644 --- a/src/ui/composer/qgsattributeselectiondialogbase.ui +++ b/src/ui/composer/qgsattributeselectiondialogbase.ui @@ -11,7 +11,7 @@ - Select attributes + Select Attributes diff --git a/src/ui/composer/qgscomposerimageexportoptions.ui b/src/ui/composer/qgscomposerimageexportoptions.ui index 6d92ab09c89..699240cea06 100644 --- a/src/ui/composer/qgscomposerimageexportoptions.ui +++ b/src/ui/composer/qgscomposerimageexportoptions.ui @@ -11,7 +11,7 @@ - Image export options + Image Export Options diff --git a/src/ui/composer/qgscomposerlegendlayersdialogbase.ui b/src/ui/composer/qgscomposerlegendlayersdialogbase.ui index fd832bf596d..59812f9162d 100644 --- a/src/ui/composer/qgscomposerlegendlayersdialogbase.ui +++ b/src/ui/composer/qgscomposerlegendlayersdialogbase.ui @@ -11,7 +11,7 @@ - Add layer to legend + Add Layer to Legend diff --git a/src/ui/composer/qgscomposermanagerbase.ui b/src/ui/composer/qgscomposermanagerbase.ui index 5429fa70b8d..0f6d76da55e 100644 --- a/src/ui/composer/qgscomposermanagerbase.ui +++ b/src/ui/composer/qgscomposermanagerbase.ui @@ -17,7 +17,7 @@ - Composer manager + Composer Manager diff --git a/src/ui/qgsaddattrdialogbase.ui b/src/ui/qgsaddattrdialogbase.ui index 3a98e9fc238..e45313007df 100644 --- a/src/ui/qgsaddattrdialogbase.ui +++ b/src/ui/qgsaddattrdialogbase.ui @@ -11,7 +11,7 @@ - Add field + Add Field true diff --git a/src/ui/qgsattributeloadfrommap.ui b/src/ui/qgsattributeloadfrommap.ui index 21050ea48ae..0a33bdd757d 100644 --- a/src/ui/qgsattributeloadfrommap.ui +++ b/src/ui/qgsattributeloadfrommap.ui @@ -11,7 +11,7 @@ - Load values from layer + Load Values from Layer diff --git a/src/ui/qgscolorbrewercolorrampwidgetbase.ui b/src/ui/qgscolorbrewercolorrampwidgetbase.ui index b6c69579b37..f79dbbfc72f 100644 --- a/src/ui/qgscolorbrewercolorrampwidgetbase.ui +++ b/src/ui/qgscolorbrewercolorrampwidgetbase.ui @@ -11,7 +11,7 @@ - ColorBrewer ramp + ColorBrewer Ramp diff --git a/src/ui/qgscolordialog.ui b/src/ui/qgscolordialog.ui index 893876bf902..7a62889209f 100644 --- a/src/ui/qgscolordialog.ui +++ b/src/ui/qgscolordialog.ui @@ -11,7 +11,7 @@ - Color picker + Color Picker true diff --git a/src/ui/qgsconfigureshortcutsdialog.ui b/src/ui/qgsconfigureshortcutsdialog.ui index 5741385b10c..95bf9ac8f19 100644 --- a/src/ui/qgsconfigureshortcutsdialog.ui +++ b/src/ui/qgsconfigureshortcutsdialog.ui @@ -11,7 +11,7 @@ - Keyboard shortcuts + Keyboard Shortcuts diff --git a/src/ui/qgscptcitycolorrampv2dialogbase.ui b/src/ui/qgscptcitycolorrampv2dialogbase.ui index 0cef2d282c1..72a055c2df1 100644 --- a/src/ui/qgscptcitycolorrampv2dialogbase.ui +++ b/src/ui/qgscptcitycolorrampv2dialogbase.ui @@ -17,7 +17,7 @@ - cpt-city color ramp + Cpt-city Color Ramp diff --git a/src/ui/qgscustomizationdialogbase.ui b/src/ui/qgscustomizationdialogbase.ui index 1826e85b6ca..820d372c833 100644 --- a/src/ui/qgscustomizationdialogbase.ui +++ b/src/ui/qgscustomizationdialogbase.ui @@ -11,7 +11,7 @@ - Interface customization + Interface Customization diff --git a/src/ui/qgsdatadefinedsizelegendwidget.ui b/src/ui/qgsdatadefinedsizelegendwidget.ui index f0545c4ddf8..6f7c661e5f2 100644 --- a/src/ui/qgsdatadefinedsizelegendwidget.ui +++ b/src/ui/qgsdatadefinedsizelegendwidget.ui @@ -11,7 +11,7 @@ - Data-defined size legend + Data-defined Size Legend diff --git a/src/ui/qgsdatumtransformdialogbase.ui b/src/ui/qgsdatumtransformdialogbase.ui index 462b255bcf3..8c1ea22f6f1 100644 --- a/src/ui/qgsdatumtransformdialogbase.ui +++ b/src/ui/qgsdatumtransformdialogbase.ui @@ -11,7 +11,7 @@ - Select datum transformations + Select Datum Transformations diff --git a/src/ui/qgsdb2newconnectionbase.ui b/src/ui/qgsdb2newconnectionbase.ui index 77ce1b3fb45..8ce99b1898d 100644 --- a/src/ui/qgsdb2newconnectionbase.ui +++ b/src/ui/qgsdb2newconnectionbase.ui @@ -11,7 +11,7 @@ - Create a New DB2 connection + Create a New DB2 Connection diff --git a/src/ui/qgsdbsourceselectbase.ui b/src/ui/qgsdbsourceselectbase.ui index e589ae1c887..0871c2ee9ca 100644 --- a/src/ui/qgsdbsourceselectbase.ui +++ b/src/ui/qgsdbsourceselectbase.ui @@ -11,7 +11,7 @@ - Add PostGIS layers + Add PostGIS Layers diff --git a/src/ui/qgsdecorationgriddialog.ui b/src/ui/qgsdecorationgriddialog.ui index 6e6e2a4ecaa..c6d6537bcc9 100755 --- a/src/ui/qgsdecorationgriddialog.ui +++ b/src/ui/qgsdecorationgriddialog.ui @@ -11,7 +11,7 @@ - Grid properties + Grid Properties diff --git a/src/ui/qgsdelattrdialogbase.ui b/src/ui/qgsdelattrdialogbase.ui index feec8dd93a9..08e5cc8ecea 100644 --- a/src/ui/qgsdelattrdialogbase.ui +++ b/src/ui/qgsdelattrdialogbase.ui @@ -11,7 +11,7 @@ - Delete fields + Delete Fields diff --git a/src/ui/qgsdiscoverrelationsdlgbase.ui b/src/ui/qgsdiscoverrelationsdlgbase.ui index 285a15f5a8a..f15e181ae6e 100644 --- a/src/ui/qgsdiscoverrelationsdlgbase.ui +++ b/src/ui/qgsdiscoverrelationsdlgbase.ui @@ -11,7 +11,7 @@ - Discover relations + Discover Relations diff --git a/src/ui/qgsdxfexportdialogbase.ui b/src/ui/qgsdxfexportdialogbase.ui index 55ed4ae1dd4..f10a6d58d72 100644 --- a/src/ui/qgsdxfexportdialogbase.ui +++ b/src/ui/qgsdxfexportdialogbase.ui @@ -11,7 +11,7 @@ - DXF export + DXF Export diff --git a/src/ui/qgsexpressionbuilderdialogbase.ui b/src/ui/qgsexpressionbuilderdialogbase.ui index 904b9166a07..e687901dabc 100644 --- a/src/ui/qgsexpressionbuilderdialogbase.ui +++ b/src/ui/qgsexpressionbuilderdialogbase.ui @@ -11,7 +11,7 @@ - Expression string builder + Expression String Builder diff --git a/src/ui/qgsfieldcalculatorbase.ui b/src/ui/qgsfieldcalculatorbase.ui index b1ab7ae1ade..1c83f8bd08e 100644 --- a/src/ui/qgsfieldcalculatorbase.ui +++ b/src/ui/qgsfieldcalculatorbase.ui @@ -11,7 +11,7 @@ - Field calculator + Field Calculator diff --git a/src/ui/qgsformannotationdialogbase.ui b/src/ui/qgsformannotationdialogbase.ui index fc2abaacbb9..0269a72a978 100644 --- a/src/ui/qgsformannotationdialogbase.ui +++ b/src/ui/qgsformannotationdialogbase.ui @@ -11,7 +11,7 @@ - Form annotation + Form Annotation diff --git a/src/ui/qgsgradientcolorrampdialogbase.ui b/src/ui/qgsgradientcolorrampdialogbase.ui index 04ab0059a3c..abca7c720e0 100644 --- a/src/ui/qgsgradientcolorrampdialogbase.ui +++ b/src/ui/qgsgradientcolorrampdialogbase.ui @@ -11,7 +11,7 @@ - Gradient color ramp + Gradient Color Ramp diff --git a/src/ui/qgsgroupwmsdatadialogbase.ui b/src/ui/qgsgroupwmsdatadialogbase.ui index 0b85474a2e6..67ed033236a 100644 --- a/src/ui/qgsgroupwmsdatadialogbase.ui +++ b/src/ui/qgsgroupwmsdatadialogbase.ui @@ -26,7 +26,7 @@ - Set group WMS data + Set Group WMS Data diff --git a/src/ui/qgshandlebadlayersbase.ui b/src/ui/qgshandlebadlayersbase.ui index 8dddc4ecc0e..feab6a16958 100644 --- a/src/ui/qgshandlebadlayersbase.ui +++ b/src/ui/qgshandlebadlayersbase.ui @@ -11,7 +11,7 @@ - Handle bad layers + Handle Bad Layers diff --git a/src/ui/qgsjoindialogbase.ui b/src/ui/qgsjoindialogbase.ui index 67651740a4c..679f6d20197 100644 --- a/src/ui/qgsjoindialogbase.ui +++ b/src/ui/qgsjoindialogbase.ui @@ -11,7 +11,7 @@ - Add vector join + Add Vector join diff --git a/src/ui/qgslabelingrulepropswidget.ui b/src/ui/qgslabelingrulepropswidget.ui index 4245b46703f..3f647331980 100644 --- a/src/ui/qgslabelingrulepropswidget.ui +++ b/src/ui/qgslabelingrulepropswidget.ui @@ -11,7 +11,7 @@ - Rule properties + Rule Properties diff --git a/src/ui/qgslabelpropertydialogbase.ui b/src/ui/qgslabelpropertydialogbase.ui index a65df32f591..57c484fa2b9 100644 --- a/src/ui/qgslabelpropertydialogbase.ui +++ b/src/ui/qgslabelpropertydialogbase.ui @@ -11,7 +11,7 @@ - Label properties + Label Properties diff --git a/src/ui/qgslimitedrandomcolorrampwidgetbase.ui b/src/ui/qgslimitedrandomcolorrampwidgetbase.ui index ebd5ccfb985..edaa776ffc1 100644 --- a/src/ui/qgslimitedrandomcolorrampwidgetbase.ui +++ b/src/ui/qgslimitedrandomcolorrampwidgetbase.ui @@ -11,7 +11,7 @@ - Random color ramp + Random Color Ramp diff --git a/src/ui/qgsludialogbase.ui b/src/ui/qgsludialogbase.ui index 5f9996fa9ee..d9128b3b6fe 100644 --- a/src/ui/qgsludialogbase.ui +++ b/src/ui/qgsludialogbase.ui @@ -11,7 +11,7 @@ - Enter class bounds + Enter Class Bounds true diff --git a/src/ui/qgsmanageconnectionsdialogbase.ui b/src/ui/qgsmanageconnectionsdialogbase.ui index 1a42528263a..3befa99c5a9 100644 --- a/src/ui/qgsmanageconnectionsdialogbase.ui +++ b/src/ui/qgsmanageconnectionsdialogbase.ui @@ -11,7 +11,7 @@ - Manage connections + Manage Connections diff --git a/src/ui/qgsmapsavedialog.ui b/src/ui/qgsmapsavedialog.ui index 2855832e87d..776640891df 100644 --- a/src/ui/qgsmapsavedialog.ui +++ b/src/ui/qgsmapsavedialog.ui @@ -11,7 +11,7 @@ - Save map as image + Save Map as Image diff --git a/src/ui/qgsmapunitscalewidgetbase.ui b/src/ui/qgsmapunitscalewidgetbase.ui index eb96d5e11db..e0b7bff7e25 100644 --- a/src/ui/qgsmapunitscalewidgetbase.ui +++ b/src/ui/qgsmapunitscalewidgetbase.ui @@ -11,7 +11,7 @@ - Adjust scaling range + Adjust Scaling Range diff --git a/src/ui/qgsmergeattributesdialogbase.ui b/src/ui/qgsmergeattributesdialogbase.ui index e302992762c..82a9d52ee6b 100644 --- a/src/ui/qgsmergeattributesdialogbase.ui +++ b/src/ui/qgsmergeattributesdialogbase.ui @@ -11,7 +11,7 @@ - Merge feature attributes + Merge Feature Attributes diff --git a/src/ui/qgsmssqlnewconnectionbase.ui b/src/ui/qgsmssqlnewconnectionbase.ui index cb937684f66..c85fe4e3dbb 100644 --- a/src/ui/qgsmssqlnewconnectionbase.ui +++ b/src/ui/qgsmssqlnewconnectionbase.ui @@ -17,7 +17,7 @@ - Create a New MSSQL connection + Create a New MSSQL Connection true diff --git a/src/ui/qgsnewhttpconnectionbase.ui b/src/ui/qgsnewhttpconnectionbase.ui index 3b8875c047b..1e1adad5106 100644 --- a/src/ui/qgsnewhttpconnectionbase.ui +++ b/src/ui/qgsnewhttpconnectionbase.ui @@ -11,7 +11,7 @@ - Create a new WMS connection + Create a New WMS Connection true diff --git a/src/ui/qgsnewogrconnectionbase.ui b/src/ui/qgsnewogrconnectionbase.ui index e8029da3813..3d610ba618e 100644 --- a/src/ui/qgsnewogrconnectionbase.ui +++ b/src/ui/qgsnewogrconnectionbase.ui @@ -17,7 +17,7 @@ - Create a New OGR Database connection + Create a New OGR Database Connection true diff --git a/src/ui/qgsopenvectorlayerdialogbase.ui b/src/ui/qgsopenvectorlayerdialogbase.ui index 3bf4c6a5ad5..8b3711088cc 100644 --- a/src/ui/qgsopenvectorlayerdialogbase.ui +++ b/src/ui/qgsopenvectorlayerdialogbase.ui @@ -20,7 +20,7 @@ - Add vector layer + Add Vector Layer diff --git a/src/ui/qgsoraclenewconnectionbase.ui b/src/ui/qgsoraclenewconnectionbase.ui index 6dcd1d5b35b..7fd7ae0c7eb 100644 --- a/src/ui/qgsoraclenewconnectionbase.ui +++ b/src/ui/qgsoraclenewconnectionbase.ui @@ -17,7 +17,7 @@ - Create a New Oracle connection + Create a New Oracle Connection true diff --git a/src/ui/qgsorderbydialogbase.ui b/src/ui/qgsorderbydialogbase.ui index 44ba05cf6f8..deacc61b888 100644 --- a/src/ui/qgsorderbydialogbase.ui +++ b/src/ui/qgsorderbydialogbase.ui @@ -11,7 +11,7 @@ - Define order + Define Order diff --git a/src/ui/qgsorganizetablecolumnsdialog.ui b/src/ui/qgsorganizetablecolumnsdialog.ui index 34887630d20..55c8d8c0fb1 100644 --- a/src/ui/qgsorganizetablecolumnsdialog.ui +++ b/src/ui/qgsorganizetablecolumnsdialog.ui @@ -11,7 +11,7 @@ - Organize table columns + Organize Table columns diff --git a/src/ui/qgsosmdownloaddialog.ui b/src/ui/qgsosmdownloaddialog.ui index 7418ba010ec..367264db16a 100644 --- a/src/ui/qgsosmdownloaddialog.ui +++ b/src/ui/qgsosmdownloaddialog.ui @@ -11,7 +11,7 @@ - Download OpenStreetMap data + Download OpenStreetMap Data diff --git a/src/ui/qgsosmexportdialog.ui b/src/ui/qgsosmexportdialog.ui index 0555612432b..ff01f96bbe6 100644 --- a/src/ui/qgsosmexportdialog.ui +++ b/src/ui/qgsosmexportdialog.ui @@ -11,7 +11,7 @@ - Export OpenStreetMap topology to SpatiaLite + Export OpenStreetMap Topology to SpatiaLite diff --git a/src/ui/qgspgnewconnectionbase.ui b/src/ui/qgspgnewconnectionbase.ui index 558ff5219d2..cd8073bd851 100644 --- a/src/ui/qgspgnewconnectionbase.ui +++ b/src/ui/qgspgnewconnectionbase.ui @@ -17,7 +17,7 @@ - Create a New PostGIS connection + Create a New PostGIS Connection true diff --git a/src/ui/qgspresetcolorrampwidgetbase.ui b/src/ui/qgspresetcolorrampwidgetbase.ui index d5d7bddddad..8d4205e0bcd 100644 --- a/src/ui/qgspresetcolorrampwidgetbase.ui +++ b/src/ui/qgspresetcolorrampwidgetbase.ui @@ -11,7 +11,7 @@ - ColorBrewer ramp + ColorBrewer Ramp diff --git a/src/ui/qgsprojectlayergroupdialogbase.ui b/src/ui/qgsprojectlayergroupdialogbase.ui index d43ce4316e8..01d4f3b31b4 100644 --- a/src/ui/qgsprojectlayergroupdialogbase.ui +++ b/src/ui/qgsprojectlayergroupdialogbase.ui @@ -11,7 +11,7 @@ - Select layers and groups to embed + Select Layers and Groups to Embed diff --git a/src/ui/qgsrastercalcdialogbase.ui b/src/ui/qgsrastercalcdialogbase.ui index 929eac28efc..9474f56409f 100644 --- a/src/ui/qgsrastercalcdialogbase.ui +++ b/src/ui/qgsrastercalcdialogbase.ui @@ -11,7 +11,7 @@ - Raster calculator + Raster Calculator diff --git a/src/ui/qgsrasterlayersaveasdialogbase.ui b/src/ui/qgsrasterlayersaveasdialogbase.ui index c956c596f13..732b6680b5b 100644 --- a/src/ui/qgsrasterlayersaveasdialogbase.ui +++ b/src/ui/qgsrasterlayersaveasdialogbase.ui @@ -11,7 +11,7 @@ - Save raster layer as... + Save Raster Layer as... diff --git a/src/ui/qgsrelationadddlgbase.ui b/src/ui/qgsrelationadddlgbase.ui index 6ad2e991fb3..0de12dc5f22 100644 --- a/src/ui/qgsrelationadddlgbase.ui +++ b/src/ui/qgsrelationadddlgbase.ui @@ -11,7 +11,7 @@ - Add relation + Add Relation diff --git a/src/ui/qgsrendererv2propsdialogbase.ui b/src/ui/qgsrendererv2propsdialogbase.ui index 13d09959999..20b3dfb3b74 100644 --- a/src/ui/qgsrendererv2propsdialogbase.ui +++ b/src/ui/qgsrendererv2propsdialogbase.ui @@ -11,7 +11,7 @@ - Renderer settings + Renderer Settings diff --git a/src/ui/qgssavetodbdialog.ui b/src/ui/qgssavetodbdialog.ui index 2e91447b8da..26e2cba283f 100644 --- a/src/ui/qgssavetodbdialog.ui +++ b/src/ui/qgssavetodbdialog.ui @@ -11,7 +11,7 @@ - Save style + Save Style true diff --git a/src/ui/qgssqlcomposerdialogbase.ui b/src/ui/qgssqlcomposerdialogbase.ui index 9e72bd01025..5df60f851b2 100644 --- a/src/ui/qgssqlcomposerdialogbase.ui +++ b/src/ui/qgssqlcomposerdialogbase.ui @@ -11,7 +11,7 @@ - SQL query composer + SQL Query Composer diff --git a/src/ui/qgsstyleexportimportdialogbase.ui b/src/ui/qgsstyleexportimportdialogbase.ui index 69509f39102..e74bd95451b 100644 --- a/src/ui/qgsstyleexportimportdialogbase.ui +++ b/src/ui/qgsstyleexportimportdialogbase.ui @@ -11,7 +11,7 @@ - Styles import/export + Styles Import/Export diff --git a/src/ui/qgsstylesavedialog.ui b/src/ui/qgsstylesavedialog.ui index 8eb148cc30f..d721277526b 100644 --- a/src/ui/qgsstylesavedialog.ui +++ b/src/ui/qgsstylesavedialog.ui @@ -11,7 +11,7 @@ - Save new style + Save New Style diff --git a/src/ui/qgssublayersdialogbase.ui b/src/ui/qgssublayersdialogbase.ui index 46296cb63c8..c3007e044c1 100644 --- a/src/ui/qgssublayersdialogbase.ui +++ b/src/ui/qgssublayersdialogbase.ui @@ -11,7 +11,7 @@ - Select layers to load + Select Layers to Load diff --git a/src/ui/qgstextannotationdialogbase.ui b/src/ui/qgstextannotationdialogbase.ui index 1d386abfd11..740cc211e00 100644 --- a/src/ui/qgstextannotationdialogbase.ui +++ b/src/ui/qgstextannotationdialogbase.ui @@ -11,7 +11,7 @@ - Annotation text + Annotation Text diff --git a/src/ui/qgstextformatwidgetbase.ui b/src/ui/qgstextformatwidgetbase.ui index 32768020c59..ed4b8092c62 100755 --- a/src/ui/qgstextformatwidgetbase.ui +++ b/src/ui/qgstextformatwidgetbase.ui @@ -11,7 +11,7 @@ - Layer labeling settings + Layer Labeling Settings diff --git a/src/ui/qgsvectorlayersaveasdialogbase.ui b/src/ui/qgsvectorlayersaveasdialogbase.ui index 6fb42b0915a..10aa968819e 100644 --- a/src/ui/qgsvectorlayersaveasdialogbase.ui +++ b/src/ui/qgsvectorlayersaveasdialogbase.ui @@ -17,7 +17,7 @@ - Save vector layer as... + Save Vector Layer as... diff --git a/src/ui/qgswmtsdimensionsbase.ui b/src/ui/qgswmtsdimensionsbase.ui index 8f6a324274b..38531982646 100644 --- a/src/ui/qgswmtsdimensionsbase.ui +++ b/src/ui/qgswmtsdimensionsbase.ui @@ -11,7 +11,7 @@ - Select dimensions + Select Dimensions diff --git a/src/ui/symbollayer/qgsdashspacedialogbase.ui b/src/ui/symbollayer/qgsdashspacedialogbase.ui index a3ea7630c3d..c749c5526c2 100644 --- a/src/ui/symbollayer/qgsdashspacedialogbase.ui +++ b/src/ui/symbollayer/qgsdashspacedialogbase.ui @@ -11,7 +11,7 @@ - Dash space pattern + Dash Space Pattern From db2968601f4f04c6f3f669b4693fc16d4ecb305e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 08:09:02 +1000 Subject: [PATCH 217/266] Fix typo causing undefined behavior warnings --- src/core/qgsvectorlayerlabeling.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/qgsvectorlayerlabeling.cpp b/src/core/qgsvectorlayerlabeling.cpp index 17e8e6486ac..54fa48e1da0 100644 --- a/src/core/qgsvectorlayerlabeling.cpp +++ b/src/core/qgsvectorlayerlabeling.cpp @@ -172,7 +172,7 @@ std::unique_ptr backgroundToMarkerLayer( const QgsTextBack { QgsSimpleMarkerSymbolLayer *marker = new QgsSimpleMarkerSymbolLayer(); // default value - QgsSimpleMarkerSymbolLayerBase::Shape shape = shape = QgsSimpleMarkerSymbolLayerBase::Diamond; + QgsSimpleMarkerSymbolLayerBase::Shape shape = QgsSimpleMarkerSymbolLayerBase::Diamond; switch ( settings.type() ) { case QgsTextBackgroundSettings::ShapeCircle: From 687adbf6696469c6dfc9700a9b10341200230770 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 09:00:44 +1000 Subject: [PATCH 218/266] Fix tiny svg preview size on hidpi displays --- python/gui/symbology-ng/qgssvgselectorwidget.sip | 6 ++++-- src/gui/symbology-ng/qgssvgselectorwidget.cpp | 13 +++++++++---- src/gui/symbology-ng/qgssvgselectorwidget.h | 11 +++++++++-- src/gui/symbology-ng/qgssymbollayerwidget.cpp | 5 ++++- src/gui/symbology-ng/qgssymbollayerwidget.h | 1 + 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/python/gui/symbology-ng/qgssvgselectorwidget.sip b/python/gui/symbology-ng/qgssvgselectorwidget.sip index 0a7a6866867..9ced1f262b4 100644 --- a/python/gui/symbology-ng/qgssvgselectorwidget.sip +++ b/python/gui/symbology-ng/qgssvgselectorwidget.sip @@ -24,18 +24,20 @@ class QgsSvgSelectorListModel : QAbstractListModel %End public: - QgsSvgSelectorListModel( QObject *parent /TransferThis/ ); + QgsSvgSelectorListModel( QObject *parent /TransferThis/, int iconSize = 30 ); %Docstring Constructor for QgsSvgSelectorListModel. All SVGs in folders from the application SVG search paths will be shown. \param parent parent object + \param iconSize desired size of SVG icons to create %End - QgsSvgSelectorListModel( QObject *parent /TransferThis/, const QString &path ); + QgsSvgSelectorListModel( QObject *parent /TransferThis/, const QString &path, int iconSize = 30 ); %Docstring Constructor for creating a model for SVG files in a specific path. \param parent parent object \param path initial path, which is recursively searched + \param iconSize desired size of SVG icons to create %End virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const; diff --git a/src/gui/symbology-ng/qgssvgselectorwidget.cpp b/src/gui/symbology-ng/qgssvgselectorwidget.cpp index 4e03faa5cfc..e0133db031e 100644 --- a/src/gui/symbology-ng/qgssvgselectorwidget.cpp +++ b/src/gui/symbology-ng/qgssvgselectorwidget.cpp @@ -214,18 +214,20 @@ void QgsSvgGroupLoader::loadGroup( const QString &parentPath ) // QgsSvgSelectorListModel // -QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject *parent ) +QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject *parent, int iconSize ) : QAbstractListModel( parent ) , mSvgLoader( new QgsSvgSelectorLoader( this ) ) + , mIconSize( iconSize ) { mSvgLoader->setPath( QString() ); connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs, this, &QgsSvgSelectorListModel::addSvgs ); mSvgLoader->start(); } -QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject *parent, const QString &path ) +QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject *parent, const QString &path, int iconSize ) : QAbstractListModel( parent ) , mSvgLoader( new QgsSvgSelectorLoader( this ) ) + , mIconSize( iconSize ) { mSvgLoader->setPath( path ); connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs, this, &QgsSvgSelectorListModel::addSvgs ); @@ -263,7 +265,7 @@ QPixmap QgsSvgSelectorListModel::createPreview( const QString &entry ) const strokeWidth = 0.2; bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size) - const QImage &img = QgsApplication::svgCache()->svgAsImage( entry, 30.0, fill, stroke, strokeWidth, 3.5 /*appr. 88 dpi*/, fitsInCache ); + const QImage &img = QgsApplication::svgCache()->svgAsImage( entry, mIconSize, fill, stroke, strokeWidth, 3.5 /*appr. 88 dpi*/, fitsInCache ); return QPixmap::fromImage( img ); } @@ -375,6 +377,9 @@ QgsSvgSelectorWidget::QgsSvgSelectorWidget( QWidget *parent ) // TODO: in-code gui setup with option to vertically or horizontally stack SVG groups/images widgets setupUi( this ); + mIconSize = qMax( 30, qRound( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXX" ) ) ) ); + mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) ); + mGroupsTreeView->setHeaderHidden( true ); populateList(); @@ -436,7 +441,7 @@ void QgsSvgSelectorWidget::populateIcons( const QModelIndex &idx ) QString path = idx.data( Qt::UserRole + 1 ).toString(); QAbstractItemModel *oldModel = mImagesListView->model(); - QgsSvgSelectorListModel *m = new QgsSvgSelectorListModel( mImagesListView, path ); + QgsSvgSelectorListModel *m = new QgsSvgSelectorListModel( mImagesListView, path, mIconSize ); mImagesListView->setModel( m ); delete oldModel; //explicitly delete old model to force any background threads to stop diff --git a/src/gui/symbology-ng/qgssvgselectorwidget.h b/src/gui/symbology-ng/qgssvgselectorwidget.h index e2915d6a145..065cb9c50c0 100644 --- a/src/gui/symbology-ng/qgssvgselectorwidget.h +++ b/src/gui/symbology-ng/qgssvgselectorwidget.h @@ -174,14 +174,16 @@ class GUI_EXPORT QgsSvgSelectorListModel : public QAbstractListModel /** Constructor for QgsSvgSelectorListModel. All SVGs in folders from the application SVG * search paths will be shown. * \param parent parent object + * \param iconSize desired size of SVG icons to create */ - QgsSvgSelectorListModel( QObject *parent SIP_TRANSFERTHIS ); + QgsSvgSelectorListModel( QObject *parent SIP_TRANSFERTHIS, int iconSize = 30 ); /** Constructor for creating a model for SVG files in a specific path. * \param parent parent object * \param path initial path, which is recursively searched + * \param iconSize desired size of SVG icons to create */ - QgsSvgSelectorListModel( QObject *parent SIP_TRANSFERTHIS, const QString &path ); + QgsSvgSelectorListModel( QObject *parent SIP_TRANSFERTHIS, const QString &path, int iconSize = 30 ); int rowCount( const QModelIndex &parent = QModelIndex() ) const override; QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; @@ -193,6 +195,8 @@ class GUI_EXPORT QgsSvgSelectorListModel : public QAbstractListModel QPixmap createPreview( const QString &entry ) const; QgsSvgSelectorLoader *mSvgLoader = nullptr; + int mIconSize = 30; + private slots: /** Called to add SVG files to the model. @@ -259,6 +263,9 @@ class GUI_EXPORT QgsSvgSelectorWidget : public QWidget, private Ui::WidgetSvgSel void on_mFileLineEdit_textChanged( const QString &text ); private: + + int mIconSize = 30; + QString mCurrentSvgPath; //!< Always stored as absolute path }; diff --git a/src/gui/symbology-ng/qgssymbollayerwidget.cpp b/src/gui/symbology-ng/qgssymbollayerwidget.cpp index aa5440e87e1..bf5003507f4 100644 --- a/src/gui/symbology-ng/qgssymbollayerwidget.cpp +++ b/src/gui/symbology-ng/qgssymbollayerwidget.cpp @@ -1740,6 +1740,9 @@ QgsSvgMarkerSymbolLayerWidget::QgsSvgMarkerSymbolLayerWidget( const QgsVectorLay spinOffsetY->setClearValue( 0.0 ); spinAngle->setClearValue( 0.0 ); + mIconSize = qMax( 30, qRound( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXX" ) ) ) ); + viewImages->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) ); + populateList(); connect( viewImages->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgMarkerSymbolLayerWidget::setName ); @@ -1779,7 +1782,7 @@ void QgsSvgMarkerSymbolLayerWidget::populateList() // Initially load the icons in the List view without any grouping oldModel = viewImages->model(); - QgsSvgSelectorListModel *m = new QgsSvgSelectorListModel( viewImages ); + QgsSvgSelectorListModel *m = new QgsSvgSelectorListModel( viewImages, mIconSize ); viewImages->setModel( m ); delete oldModel; } diff --git a/src/gui/symbology-ng/qgssymbollayerwidget.h b/src/gui/symbology-ng/qgssymbollayerwidget.h index dec741dfed0..a1df3aec3a5 100644 --- a/src/gui/symbology-ng/qgssymbollayerwidget.h +++ b/src/gui/symbology-ng/qgssymbollayerwidget.h @@ -467,6 +467,7 @@ class GUI_EXPORT QgsSvgMarkerSymbolLayerWidget : public QgsSymbolLayerWidget, pr private: std::shared_ptr< QgsMarkerSymbol > mAssistantPreviewSymbol; + int mIconSize = 30; }; From 3e3f1d42d9e8d113e05c5ad8970498e1bb52a152 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 09:46:08 +1000 Subject: [PATCH 219/266] Fix some unhandled db errors in user profiles (thanks to Coverity) --- src/core/qgsuserprofile.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/qgsuserprofile.cpp b/src/core/qgsuserprofile.cpp index 979e68bc57d..c42698fa688 100644 --- a/src/core/qgsuserprofile.cpp +++ b/src/core/qgsuserprofile.cpp @@ -21,6 +21,7 @@ #include #include #include +#include QgsUserProfile::QgsUserProfile( const QString &folder ) { @@ -76,10 +77,12 @@ const QString QgsUserProfile::alias() const QString profileAlias = name(); if ( query.exec() ) { - query.next(); - QString alias = query.value( 0 ).toString(); - if ( !alias.isEmpty() ) - profileAlias = alias; + if ( query.next() ) + { + QString alias = query.value( 0 ).toString(); + if ( !alias.isEmpty() ) + profileAlias = alias; + } } db.close(); return profileAlias; @@ -108,7 +111,10 @@ QgsError QgsUserProfile::setAlias( const QString &alias ) QString sql = "INSERT OR REPLACE INTO tbl_config_variables VALUES ('ALIAS', :alias);"; query.prepare( sql ); query.bindValue( ":alias", alias ); - query.exec(); + if ( !query.exec() ) + { + error.append( QObject::tr( "Could not save alias to database: %1" ).arg( query.lastError().text() ) ); + } db.close(); return error; } From 82936997bd572ebae2f8c594694154ca42e7e6a6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 09:54:32 +1000 Subject: [PATCH 220/266] Temporarily disable spelling and sip uptodate tests on Travis Tests are timing out for an unknown reason, causing valid commits to fail --- .ci/travis/code_layout/script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/travis/code_layout/script.sh b/.ci/travis/code_layout/script.sh index c04aae36d18..b36780d097b 100755 --- a/.ci/travis/code_layout/script.sh +++ b/.ci/travis/code_layout/script.sh @@ -16,5 +16,5 @@ set -e pushd build export CTEST_BUILD_COMMAND="/usr/bin/make -j3 -i -k" -python ${TRAVIS_BUILD_DIR}/.ci/travis/scripts/ctest2travis.py xvfb-run ctest -V --output-on-failure -S ${TRAVIS_BUILD_DIR}/.ci/travis/travis.ctest +python ${TRAVIS_BUILD_DIR}/.ci/travis/scripts/ctest2travis.py xvfb-run ctest -V -E "sip_uptodate|spelling" --output-on-failure -S ${TRAVIS_BUILD_DIR}/.ci/travis/travis.ctest popd From 6cb9fa80f636102464df23eb8eb36d63da14e0b1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 10:17:45 +1000 Subject: [PATCH 221/266] Astyle --- .../plugins/processing/algs/gdal/GdalUtils.py | 2 +- src/ui/qgsuserprofilemanagerwidget.ui | 214 +++++++++--------- 2 files changed, 108 insertions(+), 108 deletions(-) diff --git a/python/plugins/processing/algs/gdal/GdalUtils.py b/python/plugins/processing/algs/gdal/GdalUtils.py index 3eabb719d0e..21dac8d9286 100755 --- a/python/plugins/processing/algs/gdal/GdalUtils.py +++ b/python/plugins/processing/algs/gdal/GdalUtils.py @@ -137,7 +137,7 @@ class GdalUtils(object): shortName = driver.ShortName metadata = driver.GetMetadata() if gdal.DCAP_RASTER not in metadata \ - or metadata[gdal.DCAP_RASTER] != 'YES': + or metadata[gdal.DCAP_RASTER] != 'YES': continue # =================================================================== diff --git a/src/ui/qgsuserprofilemanagerwidget.ui b/src/ui/qgsuserprofilemanagerwidget.ui index 534e5d0b8ed..ef1cb48a8f9 100644 --- a/src/ui/qgsuserprofilemanagerwidget.ui +++ b/src/ui/qgsuserprofilemanagerwidget.ui @@ -1,107 +1,107 @@ - - - QgsUserProfileManagerWidget - - - - 0 - 0 - 314 - 380 - - - - Form - - - - - - - - Add - - - - :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg - - - - - - - Add - - - - :/images/themes/default/symbologyEdit.png:/images/themes/default/symbologyEdit.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Remove - - - - :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Profiles Folder - - - - - - - - - Profiles - - - - - - - - - - - - - + + + QgsUserProfileManagerWidget + + + + 0 + 0 + 314 + 380 + + + + Form + + + + + + + + Add + + + + :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg + + + + + + + Add + + + + :/images/themes/default/symbologyEdit.png:/images/themes/default/symbologyEdit.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Remove + + + + :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Profiles Folder + + + + + + + + + Profiles + + + + + + + + + + + + + From 18dd09762b01e793c365fbe66a638cbe901b51f6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 10:46:53 +1000 Subject: [PATCH 222/266] Remove vector.simpleMeasure It's quite a confusing function - instead use optimised versions of the measurement calculations in its place --- .../processing/algs/qgis/ConvexHull.py | 17 +++- .../algs/qgis/ExportGeometryInfo.py | 86 +++++++++++-------- python/plugins/processing/tools/vector.py | 32 ------- 3 files changed, 66 insertions(+), 69 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ConvexHull.py b/python/plugins/processing/algs/qgis/ConvexHull.py index 74e4e3e8bfc..755102811d1 100644 --- a/python/plugins/processing/algs/qgis/ConvexHull.py +++ b/python/plugins/processing/algs/qgis/ConvexHull.py @@ -37,7 +37,8 @@ from qgis.core import (QgsField, QgsGeometry, QgsWkbTypes, QgsProcessingUtils, - QgsFields) + QgsFields, + NULL) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException @@ -145,7 +146,12 @@ class ConvexHull(QgisAlgorithm): tmpGeom = QgsGeometry(outGeom.fromMultiPoint(hull)) try: outGeom = tmpGeom.convexHull() - (area, perim) = vector.simpleMeasure(outGeom) + if outGeom: + area = outGeom.geometry().area() + perim = outGeom.geometry().perimeter() + else: + area = NULL + perim = NULL outFeat.setGeometry(outGeom) outFeat.setAttributes([fid, val, area, perim]) writer.addFeature(outFeat, QgsFeatureSink.FastInsert) @@ -166,7 +172,12 @@ class ConvexHull(QgisAlgorithm): tmpGeom = QgsGeometry(outGeom.fromMultiPoint(hull)) try: outGeom = tmpGeom.convexHull() - (area, perim) = vector.simpleMeasure(outGeom) + if outGeom: + area = outGeom.geometry().area() + perim = outGeom.geometry().perimeter() + else: + area = NULL + perim = NULL outFeat.setGeometry(outGeom) outFeat.setAttributes([0, 'all', area, perim]) writer.addFeature(outFeat, QgsFeatureSink.FastInsert) diff --git a/python/plugins/processing/algs/qgis/ExportGeometryInfo.py b/python/plugins/processing/algs/qgis/ExportGeometryInfo.py index 9ba3b06ac99..0ccedf3ec29 100644 --- a/python/plugins/processing/algs/qgis/ExportGeometryInfo.py +++ b/python/plugins/processing/algs/qgis/ExportGeometryInfo.py @@ -30,8 +30,12 @@ import os from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtCore import QVariant -from qgis.core import QgsProject, QgsCoordinateTransform, QgsFeature, QgsField, QgsWkbTypes, QgsFeatureSink, QgsProcessingUtils -from qgis.utils import iface +from qgis.core import (QgsCoordinateTransform, + QgsField, + QgsWkbTypes, + QgsFeatureSink, + QgsProcessingUtils, + QgsDistanceArea) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.core.parameters import ParameterVector @@ -59,6 +63,9 @@ class ExportGeometryInfo(QgisAlgorithm): def __init__(self): super().__init__() + self.export_z = False + self.export_m = False + self.distance_area = None def initAlgorithm(self, config=None): self.calc_methods = [self.tr('Layer CRS'), @@ -85,8 +92,6 @@ class ExportGeometryInfo(QgisAlgorithm): geometryType = layer.geometryType() fields = layer.fields() - export_z = False - export_m = False if geometryType == QgsWkbTypes.PolygonGeometry: areaName = vector.createUniqueFieldName('area', fields) fields.append(QgsField(areaName, QVariant.Double)) @@ -101,19 +106,17 @@ class ExportGeometryInfo(QgisAlgorithm): yName = vector.createUniqueFieldName('ycoord', fields) fields.append(QgsField(yName, QVariant.Double)) if QgsWkbTypes.hasZ(layer.wkbType()): - export_z = True + self.export_z = True zName = vector.createUniqueFieldName('zcoord', fields) fields.append(QgsField(zName, QVariant.Double)) if QgsWkbTypes.hasM(layer.wkbType()): - export_m = True + self.export_m = True zName = vector.createUniqueFieldName('mvalue', fields) fields.append(QgsField(zName, QVariant.Double)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, layer.wkbType(), layer.crs(), context) - ellips = None - crs = None coordTransform = None # Calculate with: @@ -121,40 +124,29 @@ class ExportGeometryInfo(QgisAlgorithm): # 1 - project CRS # 2 - ellipsoidal + self.distance_area = QgsDistanceArea() if method == 2: - ellips = QgsProject.instance().ellipsoid() - crs = layer.crs().srsid() + self.distance_area.setSourceCrs(layer.crs()) + self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: - mapCRS = iface.mapCanvas().mapSettings().destinationCrs() - layCRS = layer.crs() - coordTransform = QgsCoordinateTransform(layCRS, mapCRS) - - outFeat = QgsFeature() - - outFeat.initAttributes(len(fields)) - outFeat.setFields(fields) + coordTransform = QgsCoordinateTransform(layer.crs(), context.project().crs()) features = QgsProcessingUtils.getFeatures(layer, context) total = 100.0 / layer.featureCount() if layer.featureCount() else 0 for current, f in enumerate(features): - inGeom = f.geometry() - - if method == 1: - inGeom.transform(coordTransform) - - (attr1, attr2) = vector.simpleMeasure(inGeom, method, ellips, crs) - - outFeat.setGeometry(inGeom) + outFeat = f attrs = f.attributes() - attrs.append(attr1) - if attr2 is not None: - attrs.append(attr2) + inGeom = f.geometry() + if inGeom: + if coordTransform is not None: + inGeom.transform(coordTransform) - # add point z/m - if export_z: - attrs.append(inGeom.geometry().z()) - if export_m: - attrs.append(inGeom.geometry().m()) + if inGeom.type() == QgsWkbTypes.PointGeometry: + attrs.extend(self.point_attributes(inGeom)) + elif inGeom.type() == QgsWkbTypes.PolygonGeometry: + attrs.extend(self.polygon_attributes(inGeom)) + else: + attrs.extend(self.line_attributes(inGeom)) outFeat.setAttributes(attrs) writer.addFeature(outFeat, QgsFeatureSink.FastInsert) @@ -162,3 +154,29 @@ class ExportGeometryInfo(QgisAlgorithm): feedback.setProgress(int(current * total)) del writer + + def point_attributes(self, geometry): + pt = None + if not geometry.isMultipart(): + pt = geometry.geometry() + else: + if geometry.numGeometries() > 0: + pt = geometry.geometryN(0) + attrs = [] + if pt: + attrs.append(pt.x()) + attrs.append(pt.y()) + # add point z/m + if self.export_z: + attrs.append(pt.z()) + if self.export_m: + attrs.append(pt.m()) + return attrs + + def line_attributes(self, geometry): + return [self.distance_area.measureLength(geometry)] + + def polygon_attributes(self, geometry): + area = self.distance_area.measureArea(geometry) + perimeter = self.distance_area.measurePerimeter(geometry) + return [area, perimeter] diff --git a/python/plugins/processing/tools/vector.py b/python/plugins/processing/tools/vector.py index ba750624f5f..41e3df48d34 100644 --- a/python/plugins/processing/tools/vector.py +++ b/python/plugins/processing/tools/vector.py @@ -196,38 +196,6 @@ def extractPoints(geom): return points -def simpleMeasure(geom, method=0, ellips=None, crs=None): - # Method defines calculation type: - # 0 - layer CRS - # 1 - project CRS - # 2 - ellipsoidal - - if geom.type() == QgsWkbTypes.PointGeometry: - if not geom.isMultipart(): - pt = geom.geometry() - attr1 = pt.x() - attr2 = pt.y() - else: - pt = geom.asMultiPoint() - attr1 = pt[0].x() - attr2 = pt[0].y() - else: - measure = QgsDistanceArea() - - if method == 2: - measure.setSourceCrs(crs) - measure.setEllipsoid(ellips) - - if geom.type() == QgsWkbTypes.PolygonGeometry: - attr1 = measure.measureArea(geom) - attr2 = measure.measurePerimeter(geom) - else: - attr1 = measure.measureLength(geom) - attr2 = None - - return (attr1, attr2) - - def combineFields(fieldsA, fieldsB): """Create single field map from two input field maps. """ From 2e8d940f2d3ba2a175d58774df76aa9db2a5e6e5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 10:49:52 +1000 Subject: [PATCH 223/266] Use context.project() instead of QgsProject.instance() --- python/plugins/processing/algs/qgis/FieldsCalculator.py | 6 +++--- python/plugins/processing/algs/qgis/FieldsMapper.py | 6 +++--- python/plugins/processing/algs/qgis/HubDistanceLines.py | 2 +- python/plugins/processing/algs/qgis/HubDistancePoints.py | 2 +- .../processing/algs/qgis/NearestNeighbourAnalysis.py | 2 +- python/plugins/processing/algs/qgis/PointDistance.py | 4 ++-- python/plugins/processing/algs/qgis/PointsToPaths.py | 2 +- .../plugins/processing/algs/qgis/RandomPointsAlongLines.py | 2 +- python/plugins/processing/algs/qgis/RandomPointsPolygons.py | 2 +- python/plugins/processing/algs/qgis/RasterCalculator.py | 2 +- python/plugins/processing/algs/qgis/SumLines.py | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/python/plugins/processing/algs/qgis/FieldsCalculator.py b/python/plugins/processing/algs/qgis/FieldsCalculator.py index 59f6d9a6a66..87e88abff1b 100644 --- a/python/plugins/processing/algs/qgis/FieldsCalculator.py +++ b/python/plugins/processing/algs/qgis/FieldsCalculator.py @@ -115,10 +115,10 @@ class FieldsCalculator(QgisAlgorithm): da = QgsDistanceArea() da.setSourceCrs(layer.crs()) - da.setEllipsoid(QgsProject.instance().ellipsoid()) + da.setEllipsoid(context.project().ellipsoid()) exp.setGeomCalculator(da) - exp.setDistanceUnits(QgsProject.instance().distanceUnits()) - exp.setAreaUnits(QgsProject.instance().areaUnits()) + exp.setDistanceUnits(context.project().distanceUnits()) + exp.setAreaUnits(context.project().areaUnits()) exp_context = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(layer)) diff --git a/python/plugins/processing/algs/qgis/FieldsMapper.py b/python/plugins/processing/algs/qgis/FieldsMapper.py index 0cf041a3b98..16a56e759e0 100644 --- a/python/plugins/processing/algs/qgis/FieldsMapper.py +++ b/python/plugins/processing/algs/qgis/FieldsMapper.py @@ -119,7 +119,7 @@ class FieldsMapper(QgisAlgorithm): da = QgsDistanceArea() da.setSourceCrs(layer.crs()) - da.setEllipsoid(QgsProject.instance().ellipsoid()) + da.setEllipsoid(context.project().ellipsoid()) exp_context = layer.createExpressionContext() @@ -131,8 +131,8 @@ class FieldsMapper(QgisAlgorithm): expression = QgsExpression(field_def['expression']) expression.setGeomCalculator(da) - expression.setDistanceUnits(QgsProject.instance().distanceUnits()) - expression.setAreaUnits(QgsProject.instance().areaUnits()) + expression.setDistanceUnits(context.project().distanceUnits()) + expression.setAreaUnits(context.project().areaUnits()) expression.prepare(exp_context) if expression.hasParserError(): raise GeoAlgorithmExecutionException( diff --git a/python/plugins/processing/algs/qgis/HubDistanceLines.py b/python/plugins/processing/algs/qgis/HubDistanceLines.py index 39e8f570c90..bb360b5a971 100644 --- a/python/plugins/processing/algs/qgis/HubDistanceLines.py +++ b/python/plugins/processing/algs/qgis/HubDistanceLines.py @@ -115,7 +115,7 @@ class HubDistanceLines(QgisAlgorithm): distance = QgsDistanceArea() distance.setSourceCrs(layerPoints.crs()) - distance.setEllipsoid(QgsProject.instance().ellipsoid()) + distance.setEllipsoid(context.project().ellipsoid()) # Scan source points, find nearest hub, and write to output file features = QgsProcessingUtils.getFeatures(layerPoints, context) diff --git a/python/plugins/processing/algs/qgis/HubDistancePoints.py b/python/plugins/processing/algs/qgis/HubDistancePoints.py index 7b715bedddc..ea670b39b4d 100644 --- a/python/plugins/processing/algs/qgis/HubDistancePoints.py +++ b/python/plugins/processing/algs/qgis/HubDistancePoints.py @@ -115,7 +115,7 @@ class HubDistancePoints(QgisAlgorithm): distance = QgsDistanceArea() distance.setSourceCrs(layerPoints.crs()) - distance.setEllipsoid(QgsProject.instance().ellipsoid()) + distance.setEllipsoid(context.project().ellipsoid()) # Scan source points, find nearest hub, and write to output file features = QgsProcessingUtils.getFeatures(layerPoints, context) diff --git a/python/plugins/processing/algs/qgis/NearestNeighbourAnalysis.py b/python/plugins/processing/algs/qgis/NearestNeighbourAnalysis.py index 15f01ad10ec..afcff2e2a15 100644 --- a/python/plugins/processing/algs/qgis/NearestNeighbourAnalysis.py +++ b/python/plugins/processing/algs/qgis/NearestNeighbourAnalysis.py @@ -98,7 +98,7 @@ class NearestNeighbourAnalysis(QgisAlgorithm): distance = QgsDistanceArea() distance.setSourceCrs(source.sourceCrs()) - distance.setEllipsoid(QgsProject.instance().ellipsoid()) + distance.setEllipsoid(context.project().ellipsoid()) sumDist = 0.00 A = source.sourceExtent() diff --git a/python/plugins/processing/algs/qgis/PointDistance.py b/python/plugins/processing/algs/qgis/PointDistance.py index dcd8a140ce2..3301fd9e792 100644 --- a/python/plugins/processing/algs/qgis/PointDistance.py +++ b/python/plugins/processing/algs/qgis/PointDistance.py @@ -158,7 +158,7 @@ class PointDistance(QgisAlgorithm): distArea = QgsDistanceArea() distArea.setSourceCrs(source.sourceCrs()) - distArea.setEllipsoid(QgsProject.instance().ellipsoid()) + distArea.setEllipsoid(context.project().ellipsoid()) features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([inIdx])) total = 100.0 / source.featureCount() if source.featureCount() else 0 @@ -213,7 +213,7 @@ class PointDistance(QgisAlgorithm): distArea = QgsDistanceArea() distArea.setSourceCrs(source.sourceCrs()) - distArea.setEllipsoid(QgsProject.instance().ellipsoid()) + distArea.setEllipsoid(context.project().ellipsoid()) first = True sink = None diff --git a/python/plugins/processing/algs/qgis/PointsToPaths.py b/python/plugins/processing/algs/qgis/PointsToPaths.py index 55351e2790b..e73c57a9fd5 100644 --- a/python/plugins/processing/algs/qgis/PointsToPaths.py +++ b/python/plugins/processing/algs/qgis/PointsToPaths.py @@ -122,7 +122,7 @@ class PointsToPaths(QgisAlgorithm): da = QgsDistanceArea() da.setSourceCrs(layer.sourceCrs()) - da.setEllipsoid(QgsProject.instance().ellipsoid()) + da.setEllipsoid(context.project().ellipsoid()) current = 0 total = 100.0 / len(points) if points else 1 diff --git a/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py b/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py index 4daca14296e..78fb7df883f 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py +++ b/python/plugins/processing/algs/qgis/RandomPointsAlongLines.py @@ -108,7 +108,7 @@ class RandomPointsAlongLines(QgisAlgorithm): da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs()) - da.setEllipsoid(QgsProject.instance().ellipsoid()) + da.setEllipsoid(context.project().ellipsoid()) request = QgsFeatureRequest() diff --git a/python/plugins/processing/algs/qgis/RandomPointsPolygons.py b/python/plugins/processing/algs/qgis/RandomPointsPolygons.py index 6e1e7f15e64..2cd6187a7f5 100644 --- a/python/plugins/processing/algs/qgis/RandomPointsPolygons.py +++ b/python/plugins/processing/algs/qgis/RandomPointsPolygons.py @@ -125,7 +125,7 @@ class RandomPointsPolygons(QgisAlgorithm): da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs()) - da.setEllipsoid(QgsProject.instance().ellipsoid()) + da.setEllipsoid(context.project().ellipsoid()) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(source.getFeatures()): diff --git a/python/plugins/processing/algs/qgis/RasterCalculator.py b/python/plugins/processing/algs/qgis/RasterCalculator.py index 08c69c4c647..87a0b31e3f3 100644 --- a/python/plugins/processing/algs/qgis/RasterCalculator.py +++ b/python/plugins/processing/algs/qgis/RasterCalculator.py @@ -107,7 +107,7 @@ class RasterCalculator(QgisAlgorithm): layers = [QgsProcessingUtils.mapLayerFromString(f, context) for f in layersValue.split(";")] layersDict = {os.path.basename(lyr.source().split(".")[0]): lyr for lyr in layers} - for lyr in QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance()): + for lyr in QgsProcessingUtils.compatibleRasterLayers(context.project()): name = lyr.name() if (name + "@") in expression: layersDict[name] = lyr diff --git a/python/plugins/processing/algs/qgis/SumLines.py b/python/plugins/processing/algs/qgis/SumLines.py index b3100120528..0d78aa9cada 100644 --- a/python/plugins/processing/algs/qgis/SumLines.py +++ b/python/plugins/processing/algs/qgis/SumLines.py @@ -105,7 +105,7 @@ class SumLines(QgisAlgorithm): distArea = QgsDistanceArea() distArea.setSourceCrs(poly_source.sourceCrs()) - distArea.setEllipsoid(QgsProject.instance().ellipsoid()) + distArea.setEllipsoid(context.project().ellipsoid()) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount() else 0 From 6ae2ddaa876ecd1e3e3956301043012446afe52a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 10:54:02 +1000 Subject: [PATCH 224/266] Remove some unrequired iface usage from processing algs Flip to context.project().crs() use instead --- python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py | 5 ++--- python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py | 5 ++--- python/plugins/processing/algs/qgis/SetRasterStyle.py | 1 - python/plugins/processing/algs/qgis/SetVectorStyle.py | 1 - .../plugins/processing/algs/qgis/ShortestPathLayerToPoint.py | 5 ++--- .../plugins/processing/algs/qgis/ShortestPathPointToLayer.py | 5 ++--- .../plugins/processing/algs/qgis/ShortestPathPointToPoint.py | 5 ++--- 7 files changed, 10 insertions(+), 17 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py b/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py index fff40f24acb..9e65011bdc6 100644 --- a/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py +++ b/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py @@ -53,7 +53,6 @@ from qgis.analysis import (QgsVectorLayerDirector, QgsGraphBuilder, QgsGraphAnalyzer ) -from qgis.utils import iface from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -199,7 +198,7 @@ class ServiceAreaFromLayer(QgisAlgorithm): bothValue, defaultDirection) - distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits() + distUnit = context.project().crs().mapUnits() multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters) if strategy == 0: strategy = QgsNetworkDistanceStrategy() @@ -209,7 +208,7 @@ class ServiceAreaFromLayer(QgisAlgorithm): multiplier * 1000.0 / 3600.0) director.addStrategy(strategy) - builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(), + builder = QgsGraphBuilder(context.project().crs(), True, tolerance) diff --git a/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py b/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py index edb559067e2..b5f43d77196 100644 --- a/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py +++ b/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py @@ -53,7 +53,6 @@ from qgis.analysis import (QgsVectorLayerDirector, QgsGraphBuilder, QgsGraphAnalyzer ) -from qgis.utils import iface from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -191,7 +190,7 @@ class ServiceAreaFromPoint(QgisAlgorithm): bothValue, defaultDirection) - distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits() + distUnit = context.project().crs().mapUnits() multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters) if strategy == 0: strategy = QgsNetworkDistanceStrategy() @@ -201,7 +200,7 @@ class ServiceAreaFromPoint(QgisAlgorithm): multiplier * 1000.0 / 3600.0) director.addStrategy(strategy) - builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(), + builder = QgsGraphBuilder(context.project().crs(), True, tolerance) feedback.pushInfo(self.tr('Building graph...')) diff --git a/python/plugins/processing/algs/qgis/SetRasterStyle.py b/python/plugins/processing/algs/qgis/SetRasterStyle.py index a4336078ba0..061e190d387 100644 --- a/python/plugins/processing/algs/qgis/SetRasterStyle.py +++ b/python/plugins/processing/algs/qgis/SetRasterStyle.py @@ -36,7 +36,6 @@ from processing.core.parameters import ParameterFile from processing.core.parameters import ParameterRaster from processing.core.outputs import OutputRaster from processing.tools import dataobjects -from qgis.utils import iface class SetRasterStyle(QgisAlgorithm): diff --git a/python/plugins/processing/algs/qgis/SetVectorStyle.py b/python/plugins/processing/algs/qgis/SetVectorStyle.py index b3f8770c07a..73f85a4a021 100644 --- a/python/plugins/processing/algs/qgis/SetVectorStyle.py +++ b/python/plugins/processing/algs/qgis/SetVectorStyle.py @@ -33,7 +33,6 @@ from processing.core.parameters import ParameterVector from processing.core.outputs import OutputVector from processing.core.parameters import ParameterFile from processing.tools import dataobjects -from qgis.utils import iface class SetVectorStyle(QgisAlgorithm): diff --git a/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py b/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py index 88881dedac4..ede6cd6621e 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py +++ b/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py @@ -54,7 +54,6 @@ from qgis.analysis import (QgsVectorLayerDirector, QgsGraphBuilder, QgsGraphAnalyzer ) -from qgis.utils import iface from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -196,7 +195,7 @@ class ShortestPathLayerToPoint(QgisAlgorithm): bothValue, defaultDirection) - distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits() + distUnit = context.project().crs().mapUnits() multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters) if strategy == 0: strategy = QgsNetworkDistanceStrategy() @@ -207,7 +206,7 @@ class ShortestPathLayerToPoint(QgisAlgorithm): multiplier = 3600 director.addStrategy(strategy) - builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(), + builder = QgsGraphBuilder(context.project().crs(), True, tolerance) diff --git a/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py b/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py index 9964ea40d03..030c51d32ca 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py +++ b/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py @@ -54,7 +54,6 @@ from qgis.analysis import (QgsVectorLayerDirector, QgsGraphBuilder, QgsGraphAnalyzer ) -from qgis.utils import iface from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -196,7 +195,7 @@ class ShortestPathPointToLayer(QgisAlgorithm): bothValue, defaultDirection) - distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits() + distUnit = context.project().crs().mapUnits() multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters) if strategy == 0: strategy = QgsNetworkDistanceStrategy() @@ -207,7 +206,7 @@ class ShortestPathPointToLayer(QgisAlgorithm): multiplier = 3600 director.addStrategy(strategy) - builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(), + builder = QgsGraphBuilder(context.project().crs(), True, tolerance) diff --git a/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py b/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py index 6a7862b8455..ab59b2baf1c 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py +++ b/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py @@ -55,7 +55,6 @@ from qgis.analysis import (QgsVectorLayerDirector, QgsGraphBuilder, QgsGraphAnalyzer ) -from qgis.utils import iface from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -196,7 +195,7 @@ class ShortestPathPointToPoint(QgisAlgorithm): bothValue, defaultDirection) - distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits() + distUnit = context.project().crs().mapUnits() multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters) if strategy == 0: strategy = QgsNetworkDistanceStrategy() @@ -207,7 +206,7 @@ class ShortestPathPointToPoint(QgisAlgorithm): multiplier = 3600 director.addStrategy(strategy) - builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(), + builder = QgsGraphBuilder(context.project().crs(), True, tolerance) feedback.pushInfo(self.tr('Building graph...')) From ab70e050a682751e3ea587e0d0b9a4bbe33f346c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 11:21:24 +1000 Subject: [PATCH 225/266] Port export geometry info to new API --- .../algs/qgis/ExportGeometryInfo.py | 58 +++++++++---------- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../tests/testdata/qgis_algorithm_tests.yaml | 22 +++---- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ExportGeometryInfo.py b/python/plugins/processing/algs/qgis/ExportGeometryInfo.py index 0ccedf3ec29..29ed2dbc25a 100644 --- a/python/plugins/processing/algs/qgis/ExportGeometryInfo.py +++ b/python/plugins/processing/algs/qgis/ExportGeometryInfo.py @@ -34,13 +34,12 @@ from qgis.core import (QgsCoordinateTransform, QgsField, QgsWkbTypes, QgsFeatureSink, - QgsProcessingUtils, - QgsDistanceArea) + QgsDistanceArea, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterSelection -from processing.core.outputs import OutputVector from processing.tools import vector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -66,18 +65,16 @@ class ExportGeometryInfo(QgisAlgorithm): self.export_z = False self.export_m = False self.distance_area = None - - def initAlgorithm(self, config=None): self.calc_methods = [self.tr('Layer CRS'), self.tr('Project CRS'), self.tr('Ellipsoidal')] - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'))) - self.addParameter(ParameterSelection(self.METHOD, - self.tr('Calculate using'), self.calc_methods, 0)) - - self.addOutput(OutputVector(self.OUTPUT, self.tr('Added geom info'))) + def initAlgorithm(self, config=None): + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterEnum(self.METHOD, + self.tr('Calculate using'), options=self.calc_methods, defaultValue=0)) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Added geom info'))) def name(self): return 'exportaddgeometrycolumns' @@ -86,18 +83,18 @@ class ExportGeometryInfo(QgisAlgorithm): return self.tr('Export/Add geometry columns') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - method = self.getParameterValue(self.METHOD) + source = self.parameterAsSource(parameters, self.INPUT, context) + method = self.parameterAsEnum(parameters, self.METHOD, context) - geometryType = layer.geometryType() - fields = layer.fields() + wkb_type = source.wkbType() + fields = source.fields() - if geometryType == QgsWkbTypes.PolygonGeometry: + if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: areaName = vector.createUniqueFieldName('area', fields) fields.append(QgsField(areaName, QVariant.Double)) perimeterName = vector.createUniqueFieldName('perimeter', fields) fields.append(QgsField(perimeterName, QVariant.Double)) - elif geometryType == QgsWkbTypes.LineGeometry: + elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: lengthName = vector.createUniqueFieldName('length', fields) fields.append(QgsField(lengthName, QVariant.Double)) else: @@ -105,17 +102,17 @@ class ExportGeometryInfo(QgisAlgorithm): fields.append(QgsField(xName, QVariant.Double)) yName = vector.createUniqueFieldName('ycoord', fields) fields.append(QgsField(yName, QVariant.Double)) - if QgsWkbTypes.hasZ(layer.wkbType()): + if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True zName = vector.createUniqueFieldName('zcoord', fields) fields.append(QgsField(zName, QVariant.Double)) - if QgsWkbTypes.hasM(layer.wkbType()): + if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True zName = vector.createUniqueFieldName('mvalue', fields) fields.append(QgsField(zName, QVariant.Double)) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, layer.wkbType(), layer.crs(), - context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, wkb_type, source.sourceCrs()) coordTransform = None @@ -126,14 +123,17 @@ class ExportGeometryInfo(QgisAlgorithm): self.distance_area = QgsDistanceArea() if method == 2: - self.distance_area.setSourceCrs(layer.crs()) + self.distance_area.setSourceCrs(source.sourceCrs()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: - coordTransform = QgsCoordinateTransform(layer.crs(), context.project().crs()) + coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs()) - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): + if feedback.isCanceled(): + break + outFeat = f attrs = f.attributes() inGeom = f.geometry() @@ -149,11 +149,11 @@ class ExportGeometryInfo(QgisAlgorithm): attrs.extend(self.line_attributes(inGeom)) outFeat.setAttributes(attrs) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} def point_attributes(self, geometry): pt = None diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index cf9f871a1bc..125b210157c 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -57,6 +57,7 @@ from .DensifyGeometriesInterval import DensifyGeometriesInterval from .Difference import Difference from .DropGeometry import DropGeometry from .DropMZValues import DropMZValues +from .ExportGeometryInfo import ExportGeometryInfo from .ExtendLines import ExtendLines from .ExtentFromLayer import ExtentFromLayer from .ExtractNodes import ExtractNodes @@ -117,7 +118,6 @@ from .VoronoiPolygons import VoronoiPolygons from .ZonalStatistics import ZonalStatistics # from .ExtractByLocation import ExtractByLocation -# from .ExportGeometryInfo import ExportGeometryInfo # from .SinglePartsToMultiparts import SinglePartsToMultiparts # from .ConvexHull import ConvexHull # from .FixedDistanceBuffer import FixedDistanceBuffer @@ -185,8 +185,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider): def getAlgs(self): # algs = [ - # - # ExportGeometryInfo(), # SinglePartsToMultiparts(), # ConvexHull(), FixedDistanceBuffer(), # VariableDistanceBuffer(), @@ -244,6 +242,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): Difference(), DropGeometry(), DropMZValues(), + ExportGeometryInfo(), ExtendLines(), ExtentFromLayer(), ExtractNodes(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 2931ff75d96..39f4ef79168 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1095,17 +1095,17 @@ tests: name: expected/smoothed_lines_max_angle.gml type: vector -# - algorithm: qgis:exportaddgeometrycolumns -# name: Add Geometry PointZ -# params: -# CALC_METHOD: '0' -# INPUT: -# name: pointsz.gml -# type: vector -# results: -# OUTPUT: -# name: expected/add_geometry_pointz.gml -# type: vector + - algorithm: qgis:exportaddgeometrycolumns + name: Add Geometry PointZ + params: + CALC_METHOD: '0' + INPUT: + name: pointsz.gml + type: vector + results: + OUTPUT: + name: expected/add_geometry_pointz.gml + type: vector - algorithm: qgis:countpointsinpolygon name: Count points in polygon From 95be6d17b593f3cf1bd530a76704c5d79ac161b3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 12:10:16 +1000 Subject: [PATCH 226/266] Restore text to float algorithm And add test --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../processing/algs/qgis/TextToFloat.py | 75 ++++++++----------- .../tests/testdata/custom/text_to_float.gml | 53 +++++++++++++ .../tests/testdata/custom/text_to_float.xsd | 44 +++++++++++ .../tests/testdata/expected/text_to_float.gfs | 31 ++++++++ .../tests/testdata/expected/text_to_float.gml | 52 +++++++++++++ .../tests/testdata/qgis_algorithm_tests.yaml | 12 +++ 7 files changed, 228 insertions(+), 44 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/custom/text_to_float.gml create mode 100644 python/plugins/processing/tests/testdata/custom/text_to_float.xsd create mode 100644 python/plugins/processing/tests/testdata/expected/text_to_float.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/text_to_float.gml diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 125b210157c..3c6794203d5 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -110,6 +110,7 @@ from .SnapGeometries import SnapGeometriesToLayer from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SumLines import SumLines from .SymmetricalDifference import SymmetricalDifference +from .TextToFloat import TextToFloat from .Translate import Translate from .Union import Union from .UniqueValues import UniqueValues @@ -127,7 +128,6 @@ from .ZonalStatistics import ZonalStatistics # from .SelectByLocation import SelectByLocation # from .SpatialJoin import SpatialJoin # from .DeleteDuplicateGeometries import DeleteDuplicateGeometries -# from .TextToFloat import TextToFloat # from .GridLine import GridLine # from .Gridify import Gridify # from .HubDistancePoints import HubDistancePoints @@ -192,7 +192,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # SelectByLocation(), # ExtractByLocation(), # SpatialJoin(), - # DeleteDuplicateGeometries(), TextToFloat(), + # DeleteDuplicateGeometries(), # GridLine(), Gridify(), HubDistancePoints(), # HubDistanceLines(), HubLines(), # GeometryConvert(), FieldsCalculator(), @@ -295,6 +295,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): SpatialiteExecuteSQL(), SumLines(), SymmetricalDifference(), + TextToFloat(), Translate(), Union(), UniqueValues(), diff --git a/python/plugins/processing/algs/qgis/TextToFloat.py b/python/plugins/processing/algs/qgis/TextToFloat.py index c65aeba01d1..266f663fcdb 100644 --- a/python/plugins/processing/algs/qgis/TextToFloat.py +++ b/python/plugins/processing/algs/qgis/TextToFloat.py @@ -26,34 +26,29 @@ __copyright__ = '(C) 2010, Michael Minn' __revision__ = '$Format:%H$' from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsApplication, - QgsField, - QgsFeatureSink, - QgsProcessingUtils) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterTableField -from processing.core.outputs import OutputVector +from qgis.core import (QgsField, + QgsProcessingParameterField) +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm -class TextToFloat(QgisAlgorithm): - INPUT = 'INPUT' +class TextToFloat(QgisFeatureBasedAlgorithm): + FIELD = 'FIELD' - OUTPUT = 'OUTPUT' def group(self): return self.tr('Vector table tools') def __init__(self): super().__init__() + self.field_name = None + self.field_idx = -1 - def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input Layer'))) - self.addParameter(ParameterTableField(self.FIELD, - self.tr('Text attribute to convert to float'), - self.INPUT, ParameterTableField.DATA_TYPE_STRING)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Float from text'))) + def initParameters(self, config=None): + self.addParameter(QgsProcessingParameterField(self.FIELD, + self.tr('Text attribute to convert to float'), + parentLayerParameterName='INPUT', + type=QgsProcessingParameterField.String + )) def name(self): return 'texttofloat' @@ -61,30 +56,26 @@ class TextToFloat(QgisAlgorithm): def displayName(self): return self.tr('Text to float') - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - fieldName = self.getParameterValue(self.FIELD) - idx = layer.fields().lookupField(fieldName) + def outputName(self): + return self.tr('Float from text') - fields = layer.fields() - fields[idx] = QgsField(fieldName, QVariant.Double, '', 24, 15) + def outputFields(self, inputFields): + self.field_idx = inputFields.lookupField(self.field_name) + if self.field_idx >= 0: + inputFields[self.field_idx] = QgsField(self.field_name, QVariant.Double, '', 24, 15) + return inputFields - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, layer.wkbType(), layer.crs(), context) + def prepareAlgorithm(self, parameters, context, feedback): + self.field_name = self.parameterAsString(parameters, self.FIELD, context) + return True - features = QgsProcessingUtils.getFeatures(layer, context) - - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 - for current, f in enumerate(features): - value = f[idx] - try: - if '%' in value: - f[idx] = float(value.replace('%', '')) / 100.0 - else: - f[idx] = float(value) - except: - f[idx] = None - - writer.addFeature(f, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - del writer + def processFeature(self, feature, feedback): + value = feature[self.field_idx] + try: + if '%' in value: + feature[self.field_idx] = float(value.replace('%', '')) / 100.0 + else: + feature[self.field_idx] = float(value) + except: + feature[self.field_idx] = None + return feature diff --git a/python/plugins/processing/tests/testdata/custom/text_to_float.gml b/python/plugins/processing/tests/testdata/custom/text_to_float.gml new file mode 100644 index 00000000000..d3bae42ad6e --- /dev/null +++ b/python/plugins/processing/tests/testdata/custom/text_to_float.gml @@ -0,0 +1,53 @@ + + + + + 11 + 53 + + + + + + 1,1 + 1 + 2 + 1 + + + + + 3,3 + 2 + 1 + 1.1 + + + + + 2,2 + 3 + 0 + 5% + + + + + 5,2 + 4 + 2 + notfloat + + + + + 4,1 + 5 + 1 + + + diff --git a/python/plugins/processing/tests/testdata/custom/text_to_float.xsd b/python/plugins/processing/tests/testdata/custom/text_to_float.xsd new file mode 100644 index 00000000000..7d58f2bc184 --- /dev/null +++ b/python/plugins/processing/tests/testdata/custom/text_to_float.xsd @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/text_to_float.gfs b/python/plugins/processing/tests/testdata/expected/text_to_float.gfs new file mode 100644 index 00000000000..1b9149a8ae6 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/text_to_float.gfs @@ -0,0 +1,31 @@ + + + text_to_float + text_to_float + + 1 + EPSG:4326 + + 5 + 1.00000 + 5.00000 + 1.00000 + 3.00000 + + + id + id + Integer + + + id2 + id2 + Integer + + + text_float + text_float + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/text_to_float.gml b/python/plugins/processing/tests/testdata/expected/text_to_float.gml new file mode 100644 index 00000000000..271072faef9 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/text_to_float.gml @@ -0,0 +1,52 @@ + + + + + 11 + 53 + + + + + + 1,1 + 1 + 2 + 1.000000000000000 + + + + + 3,3 + 2 + 1 + 1.100000000000000 + + + + + 2,2 + 3 + 0 + 0.050000000000000 + + + + + 5,2 + 4 + 2 + + + + + 4,1 + 5 + 1 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 39f4ef79168..828aaef28e0 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1107,6 +1107,18 @@ tests: name: expected/add_geometry_pointz.gml type: vector + - algorithm: qgis:texttofloat + name: Text to float + params: + FIELD: 'text_float' + INPUT: + name: custom/text_to_float.gml + type: vector + results: + OUTPUT: + name: expected/text_to_float.gml + type: vector + - algorithm: qgis:countpointsinpolygon name: Count points in polygon params: From 558580588c9dfefcff52801b97f72fc51e320d76 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 12:30:38 +1000 Subject: [PATCH 227/266] Port merge lines algorithm to new API --- .../processing/algs/qgis/MergeLines.py | 57 +++++++------------ .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../tests/testdata/qgis_algorithm_tests.yaml | 24 ++++---- 3 files changed, 34 insertions(+), 52 deletions(-) diff --git a/python/plugins/processing/algs/qgis/MergeLines.py b/python/plugins/processing/algs/qgis/MergeLines.py index 8941217e430..736b5fe5450 100644 --- a/python/plugins/processing/algs/qgis/MergeLines.py +++ b/python/plugins/processing/algs/qgis/MergeLines.py @@ -27,23 +27,18 @@ __revision__ = '$Format:%H$' import os -from qgis.core import QgsFeature, QgsFeatureSink, QgsProcessingUtils +from qgis.core import (QgsProcessing, + QgsWkbTypes) from qgis.PyQt.QtGui import QIcon -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector -from processing.tools import dataobjects +from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm + pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] -class MergeLines(QgisAlgorithm): - - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_LAYER = 'OUTPUT_LAYER' +class MergeLines(QgisFeatureBasedAlgorithm): def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'to_lines.png')) @@ -57,41 +52,27 @@ class MergeLines(QgisAlgorithm): def __init__(self): super().__init__() - def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Merged'), datatype=[dataobjects.TYPE_VECTOR_LINE])) - def name(self): return 'mergelines' def displayName(self): return self.tr('Merge lines') - def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + def outputName(self): + return self.tr('Merged') - writer = self.getOutputFromName( - self.OUTPUT_LAYER).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context) + def outputType(self): + return QgsProcessing.TypeVectorLine - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + def outputWkbType(self, input_wkb): + return QgsWkbTypes.MultiLineString - for current, inFeat in enumerate(features): - outFeat = QgsFeature() - attrs = inFeat.attributes() - outFeat.setAttributes(attrs) + def processFeature(self, feature, feedback): + input_geometry = feature.geometry() + if input_geometry: + output_geometry = input_geometry.mergeLines() + if not output_geometry: + feedback.reportError(self.tr('Error merging lines for feature {}').format(feature.id())) - inGeom = inFeat.geometry() - if inGeom: - outGeom = inGeom.mergeLines() - if outGeom is None: - raise GeoAlgorithmExecutionException( - self.tr('Error merging lines')) - - outFeat.setGeometry(outGeom) - - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) - - del writer + feature.setGeometry(output_geometry) + return feature diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 3c6794203d5..7e1b00d919e 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -73,6 +73,7 @@ from .LinesIntersection import LinesIntersection from .LinesToPolygons import LinesToPolygons from .MeanCoords import MeanCoords from .Merge import Merge +from .MergeLines import MergeLines from .NearestNeighbourAnalysis import NearestNeighbourAnalysis from .OffsetLine import OffsetLine from .Orthogonalize import Orthogonalize @@ -158,7 +159,6 @@ from .ZonalStatistics import ZonalStatistics # from .DefineProjection import DefineProjection # from .RectanglesOvalsDiamondsVariable import RectanglesOvalsDiamondsVariable # from .RectanglesOvalsDiamondsFixed import RectanglesOvalsDiamondsFixed -# from .MergeLines import MergeLines # from .PointsAlongGeometry import PointsAlongGeometry # from .Relief import Relief # from .IdwInterpolation import IdwInterpolation @@ -213,7 +213,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # OrientedMinimumBoundingBox(), # SpatialIndex(), DefineProjection(), # RectanglesOvalsDiamondsVariable(), - # RectanglesOvalsDiamondsFixed(), MergeLines(), + # RectanglesOvalsDiamondsFixed(), # PointsAlongGeometry(), # Relief(), # IdwInterpolation(), TinInterpolation(), @@ -258,6 +258,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): LinesToPolygons(), MeanCoords(), Merge(), + MergeLines(), NearestNeighbourAnalysis(), OffsetLine(), Orthogonalize(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 828aaef28e0..08636d90d51 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -389,18 +389,18 @@ tests: # compare: # geometry: # precision: 7 -# -# - algorithm: qgis:mergelines -# name: Merge lines algorithm -# params: -# INPUT_LAYER: -# name: multilines.gml -# type: vector -# results: -# OUTPUT_LAYER: -# name: expected/merge_lines.gml -# type: vector -# + + - algorithm: qgis:mergelines + name: Merge lines algorithm + params: + INPUT: + name: multilines.gml + type: vector + results: + OUTPUT: + name: expected/merge_lines.gml + type: vector + - algorithm: native:multiparttosingleparts name: Multiparts to singleparts params: From f7b25a17b16b67d6db2dde83134d3398adc9b8b2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 14:30:04 +1000 Subject: [PATCH 228/266] Allow setting layer type filter for QgsProcessingParameterVectorLayer Turns out this is required for some algorithms --- .../processing/qgsprocessingparameters.sip | 23 ++++++++++- .../algs/qgis/ImportIntoSpatialite.py | 2 +- .../algs/qgis/SpatialiteExecuteSQL.py | 2 +- python/plugins/processing/gui/wrappers.py | 12 +++++- .../processing/qgsprocessingparameters.cpp | 39 ++++++++++++++++++- src/core/processing/qgsprocessingparameters.h | 25 +++++++++++- tests/src/core/testqgsprocessing.cpp | 6 +-- 7 files changed, 99 insertions(+), 10 deletions(-) diff --git a/python/core/processing/qgsprocessingparameters.sip b/python/core/processing/qgsprocessingparameters.sip index f911026d2c6..9dd28b6919a 100644 --- a/python/core/processing/qgsprocessingparameters.sip +++ b/python/core/processing/qgsprocessingparameters.sip @@ -1395,7 +1395,10 @@ class QgsProcessingParameterVectorLayer : QgsProcessingParameterDefinition %End public: - QgsProcessingParameterVectorLayer( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), + QgsProcessingParameterVectorLayer( const QString &name, + const QString &description = QString(), + const QList< int > &types = QList< int >(), + const QVariant &defaultValue = QVariant(), bool optional = false ); %Docstring Constructor for QgsProcessingParameterVectorLayer. @@ -1412,6 +1415,24 @@ class QgsProcessingParameterVectorLayer : QgsProcessingParameterDefinition virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + QList< int > dataTypes() const; +%Docstring + Returns the geometry types for sources acceptable by the parameter. +.. seealso:: setDataTypes() + :rtype: list of int +%End + + void setDataTypes( const QList< int > &types ); +%Docstring + Sets the geometry ``types`` for sources acceptable by the parameter. +.. seealso:: dataTypes() +%End + + virtual QVariantMap toVariantMap() const; + + virtual bool fromVariantMap( const QVariantMap &map ); + + static QgsProcessingParameterVectorLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/; %Docstring Creates a new parameter using the definition from a script code. diff --git a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py index 24b0dfe3749..5cb5444db66 100644 --- a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py +++ b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py @@ -62,7 +62,7 @@ class ImportIntoSpatialite(QgisAlgorithm): def initAlgorithm(self, config=None): self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Layer to import'))) - self.addParameter(QgsProcessingParameterVectorLayer(self.DATABASE, self.tr('File database'), False, False)) + self.addParameter(QgsProcessingParameterVectorLayer(self.DATABASE, self.tr('File database'), [], False, False)) self.addParameter(QgsProcessingParameterString(self.TABLENAME, self.tr('Table to import to (leave blank to use layer name)'), optional=True)) self.addParameter(QgsProcessingParameterField(self.PRIMARY_KEY, self.tr('Primary key field'), self.INPUT, optional=True)) self.addParameter(QgsProcessingParameterString(self.GEOMETRY_COLUMN, self.tr('Geometry column'), 'geom')) diff --git a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py index 227abcf2cbe..f07492bf32d 100644 --- a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py +++ b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py @@ -47,7 +47,7 @@ class SpatialiteExecuteSQL(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterVectorLayer(self.DATABASE, self.tr('File Database'), False, False)) + self.addParameter(QgsProcessingParameterVectorLayer(self.DATABASE, self.tr('File Database'), [], False, False)) self.addParameter(QgsProcessingParameterString(self.SQL, self.tr('SQL query'), '', True)) def name(self): diff --git a/python/plugins/processing/gui/wrappers.py b/python/plugins/processing/gui/wrappers.py index 332d775b580..5ecb446f44f 100644 --- a/python/plugins/processing/gui/wrappers.py +++ b/python/plugins/processing/gui/wrappers.py @@ -1067,7 +1067,17 @@ class TableWidgetWrapper(WidgetWrapper): if self.param.flags() & QgsProcessingParameterDefinition.FlagOptional: self.combo.setAllowEmptyLayer(True) - self.combo.setFilters(QgsMapLayerProxyModel.VectorLayer) + filters = QgsMapLayerProxyModel.Filters() + if QgsProcessing.TypeVectorAny in self.param.dataTypes() or len(self.param.dataTypes()) == 0: + filters = QgsMapLayerProxyModel.VectorLayer + if QgsProcessing.TypeVectorPoint in self.param.dataTypes(): + filters |= QgsMapLayerProxyModel.PointLayer + if QgsProcessing.TypeVectorLine in self.param.dataTypes(): + filters |= QgsMapLayerProxyModel.LineLayer + if QgsProcessing.TypeVectorPolygon in self.param.dataTypes(): + filters |= QgsMapLayerProxyModel.PolygonLayer + self.combo.setFilters(filters) + self.combo.setExcludedProviders(['grass']) try: if iface.activeLayer().type() == QgsMapLayer.VectorLayer: diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index c9d49d7e574..24627855fde 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -2096,8 +2096,9 @@ QgsProcessingParameterExpression *QgsProcessingParameterExpression::fromScriptCo return new QgsProcessingParameterExpression( name, description, definition, QString(), isOptional ); } -QgsProcessingParameterVectorLayer::QgsProcessingParameterVectorLayer( const QString &name, const QString &description, const QVariant &defaultValue, bool optional ) +QgsProcessingParameterVectorLayer::QgsProcessingParameterVectorLayer( const QString &name, const QString &description, const QList &types, const QVariant &defaultValue, bool optional ) : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) + , mDataTypes( types ) { } @@ -2142,9 +2143,43 @@ QString QgsProcessingParameterVectorLayer::valueAsPythonString( const QVariant & return layer ? QgsProcessingUtils::normalizeLayerSource( layer->source() ).prepend( '\'' ).append( '\'' ) : QString(); } +QList QgsProcessingParameterVectorLayer::dataTypes() const +{ + return mDataTypes; +} + +void QgsProcessingParameterVectorLayer::setDataTypes( const QList &types ) +{ + mDataTypes = types; +} + +QVariantMap QgsProcessingParameterVectorLayer::toVariantMap() const +{ + QVariantMap map = QgsProcessingParameterDefinition::toVariantMap(); + QVariantList types; + Q_FOREACH ( int type, mDataTypes ) + { + types << type; + } + map.insert( QStringLiteral( "data_types" ), types ); + return map; +} + +bool QgsProcessingParameterVectorLayer::fromVariantMap( const QVariantMap &map ) +{ + QgsProcessingParameterDefinition::fromVariantMap( map ); + mDataTypes.clear(); + QVariantList values = map.value( QStringLiteral( "data_types" ) ).toList(); + Q_FOREACH ( const QVariant &val, values ) + { + mDataTypes << val.toInt(); + } + return true; +} + QgsProcessingParameterVectorLayer *QgsProcessingParameterVectorLayer::fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) { - return new QgsProcessingParameterVectorLayer( name, description, definition.isEmpty() ? QVariant() : definition, isOptional ); + return new QgsProcessingParameterVectorLayer( name, description, QList< int>(), definition.isEmpty() ? QVariant() : definition, isOptional ); } QgsProcessingParameterField::QgsProcessingParameterField( const QString &name, const QString &description, const QVariant &defaultValue, const QString &parentLayerParameterName, DataType type, bool allowMultiple, bool optional ) diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index 8ba27a0764f..6eac33d0455 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -1341,7 +1341,10 @@ class CORE_EXPORT QgsProcessingParameterVectorLayer : public QgsProcessingParame /** * Constructor for QgsProcessingParameterVectorLayer. */ - QgsProcessingParameterVectorLayer( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), + QgsProcessingParameterVectorLayer( const QString &name, + const QString &description = QString(), + const QList< int > &types = QList< int >(), + const QVariant &defaultValue = QVariant(), bool optional = false ); /** @@ -1352,11 +1355,31 @@ class CORE_EXPORT QgsProcessingParameterVectorLayer : public QgsProcessingParame bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; + /** + * Returns the geometry types for sources acceptable by the parameter. + * \see setDataTypes() + */ + QList< int > dataTypes() const; + + /** + * Sets the geometry \a types for sources acceptable by the parameter. + * \see dataTypes() + */ + void setDataTypes( const QList< int > &types ); + + QVariantMap toVariantMap() const override; + bool fromVariantMap( const QVariantMap &map ) override; + /** * Creates a new parameter using the definition from a script code. */ static QgsProcessingParameterVectorLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) SIP_FACTORY; + private: + + QList< int > mDataTypes = QList< int >() << QgsProcessing::TypeVectorAny; + + }; /** diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 9bbae9118be..6b91346d56e 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -3265,7 +3265,7 @@ void TestQgsProcessing::parameterVectorLayer() context.setProject( &p ); // not optional! - std::unique_ptr< QgsProcessingParameterVectorLayer > def( new QgsProcessingParameterVectorLayer( "non_optional", QString(), QString( "somelayer" ), false ) ); + std::unique_ptr< QgsProcessingParameterVectorLayer > def( new QgsProcessingParameterVectorLayer( "non_optional", QString(), QList< int >(), QString( "somelayer" ), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); @@ -3331,7 +3331,7 @@ void TestQgsProcessing::parameterVectorLayer() QVERIFY( dynamic_cast< QgsProcessingParameterVectorLayer *>( def.get() ) ); // optional - def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), v1->id(), true ) ); + def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QList< int >(), v1->id(), true ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); QVERIFY( def->checkValueIsAcceptable( false ) ); @@ -3353,7 +3353,7 @@ void TestQgsProcessing::parameterVectorLayer() QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); //optional with direct layer default - def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QVariant::fromValue( v1 ), true ) ); + def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QList< int >(), QVariant::fromValue( v1 ), true ) ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); } From 856125d366826400dd9e584eaaa11c89c30e33b7 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 14:30:40 +1000 Subject: [PATCH 229/266] Port create spatial index algorithm to new API --- .../algs/qgis/QGISAlgorithmProvider.py | 5 ++-- .../processing/algs/qgis/SpatialIndex.py | 22 +++++++--------- .../tests/testdata/qgis_algorithm_tests.yaml | 26 +++++++++---------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 7e1b00d919e..35a2488a7db 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -109,6 +109,7 @@ from .Slope import Slope from .Smooth import Smooth from .SnapGeometries import SnapGeometriesToLayer from .SpatialiteExecuteSQL import SpatialiteExecuteSQL +from .SpatialIndex import SpatialIndex from .SumLines import SumLines from .SymmetricalDifference import SymmetricalDifference from .TextToFloat import TextToFloat @@ -155,7 +156,6 @@ from .ZonalStatistics import ZonalStatistics # from .FieldsMapper import FieldsMapper # from .Datasources2Vrt import Datasources2Vrt # from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox -# from .SpatialIndex import SpatialIndex # from .DefineProjection import DefineProjection # from .RectanglesOvalsDiamondsVariable import RectanglesOvalsDiamondsVariable # from .RectanglesOvalsDiamondsFixed import RectanglesOvalsDiamondsFixed @@ -211,7 +211,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # SplitWithLines(), CreateConstantRaster(), # FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(), # OrientedMinimumBoundingBox(), - # SpatialIndex(), DefineProjection(), + # DefineProjection(), # RectanglesOvalsDiamondsVariable(), # RectanglesOvalsDiamondsFixed(), # PointsAlongGeometry(), @@ -294,6 +294,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): Smooth(), SnapGeometriesToLayer(), SpatialiteExecuteSQL(), + SpatialIndex(), SumLines(), SymmetricalDifference(), TextToFloat(), diff --git a/python/plugins/processing/algs/qgis/SpatialIndex.py b/python/plugins/processing/algs/qgis/SpatialIndex.py index 1597747857c..7662e28f440 100644 --- a/python/plugins/processing/algs/qgis/SpatialIndex.py +++ b/python/plugins/processing/algs/qgis/SpatialIndex.py @@ -27,13 +27,12 @@ __revision__ = '$Format:%H$' import os -from qgis.core import (QgsApplication, - QgsVectorDataProvider, - QgsProcessingUtils) +from qgis.core import (QgsVectorDataProvider, + QgsProcessingParameterVectorLayer, + QgsProcessingOutputVectorLayer, + QgsProcessing) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -50,10 +49,10 @@ class SpatialIndex(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input Layer'))) - self.addOutput(OutputVector(self.OUTPUT, - self.tr('Indexed layer'), True)) + self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, + self.tr('Input Layer'), + [QgsProcessing.TypeVectorPolygon, QgsProcessing.TypeVectorPoint, QgsProcessing.TypeVectorLine])) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Indexed layer'))) def name(self): return 'createspatialindex' @@ -62,8 +61,7 @@ class SpatialIndex(QgisAlgorithm): return self.tr('Create spatial index') def processAlgorithm(self, parameters, context, feedback): - fileName = self.getParameterValue(self.INPUT) - layer = QgsProcessingUtils.mapLayerFromString(fileName, context) + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) provider = layer.dataProvider() if provider.capabilities() & QgsVectorDataProvider.CreateSpatialIndex: @@ -73,4 +71,4 @@ class SpatialIndex(QgisAlgorithm): feedback.pushInfo(self.tr("Layer's data provider does not support " "spatial indexes")) - self.setOutputValue(self.OUTPUT, fileName) + return {self.OUTPUT: layer.id()} diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 08636d90d51..2ecc3327d65 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2248,19 +2248,19 @@ tests: type: vector in_place_result: true -# - algorithm: qgis:createspatialindex -# name: Create spatial index -# params: -# INPUT: -# name: custom/points.shp -# type: vector -# in_place: true -# results: -# INPUT: -# name: expected/create_attr_index_points.shp -# type: vector -# in_place_result: true -# + - algorithm: qgis:createspatialindex + name: Create spatial index + params: + INPUT: + name: custom/points.shp + type: vector + in_place: true + results: + INPUT: + name: expected/create_attr_index_points.shp + type: vector + in_place_result: true + # - algorithm: qgis:truncatetable # name: Truncate table # params: From 504cc1f390b1ae8b2365d14a658aa3ed17791d7e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 14:43:42 +1000 Subject: [PATCH 230/266] Port Truncate alg to new API --- .../algs/qgis/QGISAlgorithmProvider.py | 12 ++++------ .../processing/algs/qgis/TruncateTable.py | 23 +++++++----------- .../processing/tests/AlgorithmsTestBase.py | 2 +- .../tests/testdata/qgis_algorithm_tests.yaml | 24 +++++++++---------- 4 files changed, 26 insertions(+), 35 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 35a2488a7db..b8b504e42fc 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -114,6 +114,7 @@ from .SumLines import SumLines from .SymmetricalDifference import SymmetricalDifference from .TextToFloat import TextToFloat from .Translate import Translate +from .TruncateTable import TruncateTable from .Union import Union from .UniqueValues import UniqueValues from .VectorSplit import VectorSplit @@ -165,7 +166,6 @@ from .ZonalStatistics import ZonalStatistics # from .TinInterpolation import TinInterpolation # from .ExtractSpecificNodes import ExtractSpecificNodes # from .RasterCalculator import RasterCalculator -# from .TruncateTable import TruncateTable # from .Polygonize import Polygonize # from .ExecuteSQL import ExecuteSQL # from .FindProjection import FindProjection @@ -202,10 +202,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # StatisticsByCategories(), # RasterLayerStatistics(), PointsDisplacement(), # PointsFromPolygons(), - # PointsFromLines(), RandomPointsExtent(), - # RandomPointsLayer(), RandomPointsPolygonsFixed(), - # RandomPointsPolygonsVariable(), - # RandomPointsAlongLines(), PointsToPaths(), + # PointsFromLines(), PointsToPaths(), # SetVectorStyle(), SetRasterStyle(), # HypsometricCurves(), # SplitWithLines(), CreateConstantRaster(), @@ -219,9 +216,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # IdwInterpolation(), TinInterpolation(), # ExtractSpecificNodes(), # RasterCalculator(), - # ShortestPathPointToPoint(), ShortestPathPointToLayer(), - # ShortestPathLayerToPoint(), ServiceAreaFromPoint(), - # ServiceAreaFromLayer(), TruncateTable(), Polygonize(), + # Polygonize(), # ExecuteSQL(), FindProjection(), # TopoColor(), EliminateSelection() # ] @@ -299,6 +294,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): SymmetricalDifference(), TextToFloat(), Translate(), + TruncateTable(), Union(), UniqueValues(), VectorSplit(), diff --git a/python/plugins/processing/algs/qgis/TruncateTable.py b/python/plugins/processing/algs/qgis/TruncateTable.py index 13a967b63eb..9752340e9b5 100644 --- a/python/plugins/processing/algs/qgis/TruncateTable.py +++ b/python/plugins/processing/algs/qgis/TruncateTable.py @@ -25,13 +25,10 @@ __copyright__ = '(C) 2017, Nyall Dawson' __revision__ = '$Format:%H$' -from qgis.core import (QgsApplication, - QgsFeatureSink, - QgsProcessingUtils) +from qgis.core import (QgsProcessingParameterVectorLayer, + QgsProcessingOutputVectorLayer, + QgsProcessingException) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterTable -from processing.core.outputs import OutputVector -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException class TruncateTable(QgisAlgorithm): @@ -49,10 +46,9 @@ class TruncateTable(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterTable(self.INPUT, - self.tr('Input Layer'))) - self.addOutput(OutputVector(self.OUTPUT, - self.tr('Truncated layer'), True)) + self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, + self.tr('Input Layer'))) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Truncated layer'))) def name(self): return 'truncatetable' @@ -61,12 +57,11 @@ class TruncateTable(QgisAlgorithm): return self.tr('Truncate table') def processAlgorithm(self, parameters, context, feedback): - file_name = self.getParameterValue(self.INPUT) - layer = QgsProcessingUtils.mapLayerFromString(file_name, context) + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) provider = layer.dataProvider() if not provider.truncate(): - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Could not truncate table.')) - self.setOutputValue(self.OUTPUT, file_name) + return {self.OUTPUT: layer.id()} diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 95e8826efb0..65c4b119687 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -256,7 +256,7 @@ class AlgorithmsTest(object): expected_lyr = self.load_layer(id, expected_result) if 'in_place_result' in expected_result: result_lyr = QgsProcessingUtils.mapLayerFromString(self.in_place_layers[id], context) - self.assertTrue(result_lyr, self.in_place_layers[id]) + self.assertTrue(result_lyr.isValid(), self.in_place_layers[id]) else: try: results[id] diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 2ecc3327d65..d388dbe17e9 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2261,18 +2261,18 @@ tests: type: vector in_place_result: true -# - algorithm: qgis:truncatetable -# name: Truncate table -# params: -# INPUT: -# name: custom/points.shp -# type: vector -# in_place: true -# results: -# INPUT: -# name: expected/truncated.shp -# type: vector -# in_place_result: true + - algorithm: qgis:truncatetable + name: Truncate table + params: + INPUT: + name: custom/points.shp + type: vector + in_place: true + results: + INPUT: + name: expected/truncated.shp + type: vector + in_place_result: true - algorithm: qgis:distancematrix name: Distance matrix (only tests for run, does not check result as rows are in random order) From 9b3f8a8b09cb54dfde1a5b1190e270e7993d83b3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 14:56:39 +1000 Subject: [PATCH 231/266] Port Polygonize to new API --- .../processing/algs/qgis/Polygonize.py | 84 +++++++++---------- .../algs/qgis/QGISAlgorithmProvider.py | 4 +- .../tests/testdata/expected/polygonize.gfs | 10 --- .../tests/testdata/expected/polygonize.gml | 6 -- .../tests/testdata/qgis_algorithm_tests.yaml | 23 +++-- 5 files changed, 53 insertions(+), 74 deletions(-) diff --git a/python/plugins/processing/algs/qgis/Polygonize.py b/python/plugins/processing/algs/qgis/Polygonize.py index bbf811c22c6..96dd3f53df8 100644 --- a/python/plugins/processing/algs/qgis/Polygonize.py +++ b/python/plugins/processing/algs/qgis/Polygonize.py @@ -25,30 +25,24 @@ __copyright__ = '(C) 2013, Piotr Pociask' __revision__ = '$Format:%H$' -from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsApplication, - QgsFields, - QgsField, +from qgis.core import (QgsFields, QgsFeature, QgsFeatureSink, QgsGeometry, QgsWkbTypes, QgsFeatureRequest, - QgsProcessingUtils) + QgsProcessing, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterBoolean, + QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterBoolean -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class Polygonize(QgisAlgorithm): INPUT = 'INPUT' OUTPUT = 'OUTPUT' - FIELDS = 'FIELDS' - GEOMETRY = 'GEOMETRY' + KEEP_FIELDS = 'KEEP_FIELDS' def tags(self): return self.tr('create,lines,polygons,convert').split(',') @@ -60,13 +54,11 @@ class Polygonize(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterBoolean(self.FIELDS, - self.tr('Keep table structure of line layer'), False)) - self.addParameter(ParameterBoolean(self.GEOMETRY, - self.tr('Create geometry columns'), True)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Polygons from lines'), datatype=[dataobjects.TYPE_VECTOR_POLYGON])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), types=[QgsProcessing.TypeVectorLine])) + self.addParameter(QgsProcessingParameterBoolean(self.KEEP_FIELDS, + self.tr('Keep table structure of line layer'), defaultValue=False, optional=True)) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Polygons from lines'), QgsProcessing.TypeVectorPolygon)) def name(self): return 'polygonize' @@ -75,22 +67,23 @@ class Polygonize(QgisAlgorithm): return self.tr('Polygonize') def processAlgorithm(self, parameters, context, feedback): - vlayer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - output = self.getOutputFromName(self.OUTPUT) - if self.getParameterValue(self.FIELDS): - fields = vlayer.fields() + source = self.parameterAsSource(parameters, self.INPUT, context) + if self.parameterAsBool(parameters, self.KEEP_FIELDS, context): + fields = source.fields() else: fields = QgsFields() - if self.getParameterValue(self.GEOMETRY): - fieldsCount = fields.count() - fields.append(QgsField('area', QVariant.Double, 'double', 16, 2)) - fields.append(QgsField('perimeter', QVariant.Double, - 'double', 16, 2)) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, source.sourceCrs()) + allLinesList = [] - features = QgsProcessingUtils.getFeatures(vlayer, context, QgsFeatureRequest().setSubsetOfAttributes([])) + features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([])) feedback.pushInfo(self.tr('Processing lines...')) - total = 40.0 / QgsProcessingUtils.featureCount(vlayer, context) + total = (40.0 / source.featureCount()) if source.featureCount() else 1 for current, inFeat in enumerate(features): + if feedback.isCanceled(): + break + if inFeat.geometry(): allLinesList.append(inFeat.geometry()) feedback.setProgress(int(current * total)) @@ -99,24 +92,27 @@ class Polygonize(QgisAlgorithm): feedback.pushInfo(self.tr('Noding lines...')) allLines = QgsGeometry.unaryUnion(allLinesList) + if feedback.isCanceled(): + return {} feedback.setProgress(45) feedback.pushInfo(self.tr('Polygonizing...')) polygons = QgsGeometry.polygonize([allLines]) if polygons.isEmpty(): - raise GeoAlgorithmExecutionException(self.tr('No polygons were created!')) + feedback.reportError(self.tr('No polygons were created!')) feedback.setProgress(50) - feedback.pushInfo('Saving polygons...') - writer = output.getVectorWriter(fields, QgsWkbTypes.Polygon, vlayer.crs(), context) - total = 50.0 / polygons.geometry().numGeometries() - for i in range(polygons.geometry().numGeometries()): - outFeat = QgsFeature() - geom = QgsGeometry(polygons.geometry().geometryN(i).clone()) - outFeat.setGeometry(geom) - if self.getParameterValue(self.GEOMETRY): - outFeat.setAttributes([None] * fieldsCount + [geom.geometry().area(), - geom.geometry().perimeter()]) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) - feedback.setProgress(50 + int(current * total)) - del writer + if not polygons.isEmpty(): + feedback.pushInfo('Saving polygons...') + total = 50.0 / polygons.geometry().numGeometries() + for i in range(polygons.geometry().numGeometries()): + if feedback.isCanceled(): + break + + outFeat = QgsFeature() + geom = QgsGeometry(polygons.geometry().geometryN(i).clone()) + outFeat.setGeometry(geom) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + feedback.setProgress(50 + int(current * total)) + + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index b8b504e42fc..80834e8e5b4 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -82,6 +82,7 @@ from .PointOnSurface import PointOnSurface from .PointsInPolygon import PointsInPolygon from .PointsLayerFromTable import PointsLayerFromTable from .PoleOfInaccessibility import PoleOfInaccessibility +from .Polygonize import Polygonize from .PolygonsToLines import PolygonsToLines from .PostGISExecuteSQL import PostGISExecuteSQL from .RandomExtract import RandomExtract @@ -166,7 +167,6 @@ from .ZonalStatistics import ZonalStatistics # from .TinInterpolation import TinInterpolation # from .ExtractSpecificNodes import ExtractSpecificNodes # from .RasterCalculator import RasterCalculator -# from .Polygonize import Polygonize # from .ExecuteSQL import ExecuteSQL # from .FindProjection import FindProjection # from .TopoColors import TopoColor @@ -216,7 +216,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # IdwInterpolation(), TinInterpolation(), # ExtractSpecificNodes(), # RasterCalculator(), - # Polygonize(), # ExecuteSQL(), FindProjection(), # TopoColor(), EliminateSelection() # ] @@ -262,6 +261,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): PointsInPolygon(), PointsLayerFromTable(), PoleOfInaccessibility(), + Polygonize(), PolygonsToLines(), PostGISExecuteSQL(), RandomExtract(), diff --git a/python/plugins/processing/tests/testdata/expected/polygonize.gfs b/python/plugins/processing/tests/testdata/expected/polygonize.gfs index 5a1635cb4a4..52654691158 100644 --- a/python/plugins/processing/tests/testdata/expected/polygonize.gfs +++ b/python/plugins/processing/tests/testdata/expected/polygonize.gfs @@ -12,15 +12,5 @@ -0.31429 0.60000 - - area - area - Real - - - perimeter - perimeter - Real - diff --git a/python/plugins/processing/tests/testdata/expected/polygonize.gml b/python/plugins/processing/tests/testdata/expected/polygonize.gml index 09a0264fe53..a7d23a562c0 100644 --- a/python/plugins/processing/tests/testdata/expected/polygonize.gml +++ b/python/plugins/processing/tests/testdata/expected/polygonize.gml @@ -14,22 +14,16 @@ -0.6,0.2 0.0,0.2 0.6,0.2 -0.6,-0.314285714285714 -0.6,0.2 - 0.31 - 3.02 0.0,0.2 -0.6,0.2 -0.6,0.4 0.0,0.4 0.0,0.2 - 0.12 - 1.60 -0.6,0.4 -0.6,0.6 0.0,0.6 0.0,0.4 -0.6,0.4 - 0.12 - 1.60 diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index d388dbe17e9..dc75515ee4a 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2392,18 +2392,17 @@ tests: name: expected/valid.gml type: vector -# - algorithm: qgis:polygonize -# name: Polygonize -# params: -# FIELDS: false -# GEOMETRY: true -# INPUT: -# name: custom/polygonize_lines.gml -# type: vector -# results: -# OUTPUT: -# name: expected/polygonize.gml -# type: vector + - algorithm: qgis:polygonize + name: Polygonize + params: + KEEP_FIELDS: false + INPUT: + name: custom/polygonize_lines.gml + type: vector + results: + OUTPUT: + name: expected/polygonize.gml + type: vector - algorithm: qgis:voronoipolygons name: Standard voronoi From 4aaecb95ba25514530e5671fe38d63f02cb8b363 Mon Sep 17 00:00:00 2001 From: Ismail Sunni Date: Thu, 27 Jul 2017 09:18:28 +0700 Subject: [PATCH 232/266] Add bash version checking for sip_include.sh. --- scripts/sip_include.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/sip_include.sh b/scripts/sip_include.sh index 02678cb9724..c947eb9b298 100755 --- a/scripts/sip_include.sh +++ b/scripts/sip_include.sh @@ -15,6 +15,12 @@ ########################################################################### set -e +if [ $BASH_VERSINFO -lt 4 ]; then + echo "You need bash version 4+ to run this script." + echo "Your bash version is $BASH_VERSION" + exit 1 +fi + DIR=$(git rev-parse --show-toplevel) pushd ${DIR} > /dev/null From 516249cea761793f0574b1ec7f5380ec80344679 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 15:21:56 +1000 Subject: [PATCH 233/266] Port Explode algorithm to new API Improvements: - Keep Z/M values if present - Add unit tests --- .../plugins/processing/algs/qgis/Explode.py | 61 +++++++++-------- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../tests/testdata/expected/explode_lines.gfs | 16 +++++ .../tests/testdata/expected/explode_lines.gml | 68 +++++++++++++++++++ .../testdata/expected/explode_multilines.gfs | 16 +++++ .../testdata/expected/explode_multilines.gml | 58 ++++++++++++++++ .../tests/testdata/qgis_algorithm_tests.yaml | 28 ++++++++ 7 files changed, 222 insertions(+), 30 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/expected/explode_lines.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/explode_lines.gml create mode 100644 python/plugins/processing/tests/testdata/expected/explode_multilines.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/explode_multilines.gml diff --git a/python/plugins/processing/algs/qgis/Explode.py b/python/plugins/processing/algs/qgis/Explode.py index 5167d78ec82..1375f77f64d 100644 --- a/python/plugins/processing/algs/qgis/Explode.py +++ b/python/plugins/processing/algs/qgis/Explode.py @@ -30,12 +30,11 @@ from qgis.core import (QgsFeature, QgsGeometry, QgsFeatureSink, QgsWkbTypes, - QgsApplication, - QgsProcessingUtils) + QgsProcessing, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsLineString) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class Explode(QgisAlgorithm): @@ -50,10 +49,10 @@ class Explode(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'), - [dataobjects.TYPE_VECTOR_LINE])) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Exploded'), datatype=[dataobjects.TYPE_VECTOR_LINE])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), [QgsProcessing.TypeVectorLine])) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('Exploded'), QgsProcessing.TypeVectorLine)) def name(self): return 'explodelines' @@ -62,40 +61,46 @@ class Explode(QgisAlgorithm): return self.tr('Explode lines') def processAlgorithm(self, parameters, context, feedback): - vlayer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - output = self.getOutputFromName(self.OUTPUT) - fields = vlayer.fields() - writer = output.getVectorWriter(fields, QgsWkbTypes.LineString, vlayer.crs(), context) - outFeat = QgsFeature() - features = QgsProcessingUtils.getFeatures(vlayer, context) - total = 100.0 / vlayer.featureCount() if vlayer.featureCount() else 0 + source = self.parameterAsSource(parameters, self.INPUT, context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), QgsWkbTypes.singleType(source.wkbType()), source.sourceCrs()) + + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, feature in enumerate(features): + if feedback.isCanceled(): + break + feedback.setProgress(int(current * total)) + + if not feature.hasGeometry(): + sink.addFeature(feature, QgsFeatureSink.FastInsert) + continue + + outFeat = QgsFeature() inGeom = feature.geometry() - atMap = feature.attributes() segments = self.extractAsSingleSegments(inGeom) - outFeat.setAttributes(atMap) + outFeat.setAttributes(feature.attributes()) for segment in segments: outFeat.setGeometry(segment) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) - del writer + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + return {self.OUTPUT: dest_id} def extractAsSingleSegments(self, geom): segments = [] if geom.isMultipart(): - multi = geom.asMultiPolyline() - for polyline in multi: - segments.extend(self.getPolylineAsSingleSegments(polyline)) + for part in range(geom.geometry().numGeometries()): + segments.extend(self.getPolylineAsSingleSegments(geom.geometry().geometryN(part))) else: segments.extend(self.getPolylineAsSingleSegments( - geom.asPolyline())) + geom.geometry())) return segments def getPolylineAsSingleSegments(self, polyline): segments = [] - for i in range(len(polyline) - 1): - ptA = polyline[i] - ptB = polyline[i + 1] - segment = QgsGeometry.fromPolyline([ptA, ptB]) + for i in range(polyline.numPoints() - 1): + ptA = polyline.pointN(i) + ptB = polyline.pointN(i + 1) + segment = QgsGeometry(QgsLineString([ptA, ptB])) segments.append(segment) return segments diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 80834e8e5b4..7e6fd8014a0 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -57,6 +57,7 @@ from .DensifyGeometriesInterval import DensifyGeometriesInterval from .Difference import Difference from .DropGeometry import DropGeometry from .DropMZValues import DropMZValues +from .Explode import Explode from .ExportGeometryInfo import ExportGeometryInfo from .ExtendLines import ExtendLines from .ExtentFromLayer import ExtentFromLayer @@ -142,7 +143,6 @@ from .ZonalStatistics import ZonalStatistics # from .StatisticsByCategories import StatisticsByCategories # from .EquivalentNumField import EquivalentNumField # from .FieldsCalculator import FieldsCalculator -# from .Explode import Explode # from .FieldPyculator import FieldsPyculator # from .JoinAttributes import JoinAttributes # from .CreateConstantRaster import CreateConstantRaster @@ -197,7 +197,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # HubDistanceLines(), HubLines(), # GeometryConvert(), FieldsCalculator(), # JoinAttributes(), - # Explode(), FieldsPyculator(), + # FieldsPyculator(), # EquivalentNumField(), # StatisticsByCategories(), # RasterLayerStatistics(), PointsDisplacement(), @@ -236,6 +236,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): Difference(), DropGeometry(), DropMZValues(), + Explode(), ExportGeometryInfo(), ExtendLines(), ExtentFromLayer(), diff --git a/python/plugins/processing/tests/testdata/expected/explode_lines.gfs b/python/plugins/processing/tests/testdata/expected/explode_lines.gfs new file mode 100644 index 00000000000..736cd9c20a5 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/explode_lines.gfs @@ -0,0 +1,16 @@ + + + explode_lines + explode_lines + + 2 + EPSG:4326 + + 11 + -1.00000 + 11.00000 + -3.00000 + 5.00000 + + + diff --git a/python/plugins/processing/tests/testdata/expected/explode_lines.gml b/python/plugins/processing/tests/testdata/expected/explode_lines.gml new file mode 100644 index 00000000000..42737b458c6 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/explode_lines.gml @@ -0,0 +1,68 @@ + + + + + -1-3 + 115 + + + + + + 6,2 9,2 + + + + + 9,2 9,3 + + + + + 9,3 11,5 + + + + + -1,-1 1,-1 + + + + + 2,0 2,2 + + + + + 2,2 3,2 + + + + + 3,2 3,3 + + + + + 3,1 5,1 + + + + + 7,-3 10,-3 + + + + + 6,-3 10,1 + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/explode_multilines.gfs b/python/plugins/processing/tests/testdata/expected/explode_multilines.gfs new file mode 100644 index 00000000000..df252a6e746 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/explode_multilines.gfs @@ -0,0 +1,16 @@ + + + explode_multilines + explode_multilines + + 2 + EPSG:4326 + + 9 + -1.00000 + 5.58042 + -1.00000 + 4.11977 + + + diff --git a/python/plugins/processing/tests/testdata/expected/explode_multilines.gml b/python/plugins/processing/tests/testdata/expected/explode_multilines.gml new file mode 100644 index 00000000000..e02f192f33e --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/explode_multilines.gml @@ -0,0 +1,58 @@ + + + + + -1-1 + 5.580422264875244.119769673704415 + + + + + + -1,-1 1,-1 + + + + + 3,1 5,1 + + + + + 5.02418426103647,2.4147792706334 5,1 + + + + + + + + + 2,0 2,2 + + + + + 2,2 3,2 + + + + + 3,2 3,3 + + + + + 2.94433781190019,4.04721689059501 5.4595009596929,4.11976967370441 + + + + + 3,3 5.58042226487524,2.9468330134357 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index dc75515ee4a..0556cb9f3f0 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2428,6 +2428,34 @@ tests: name: expected/voronoi_buffer.gml type: vector + - algorithm: qgis:explodelines + name: Explode lines + params: + INPUT: + name: lines.gml + type: vector + results: + OUTPUT: + name: expected/explode_lines.gml + type: vector + compare: + fields: + fid: skip + + - algorithm: qgis:explodelines + name: Explode multilines + params: + INPUT: + name: multilines.gml + type: vector + results: + OUTPUT: + name: expected/explode_multilines.gml + type: vector + compare: + fields: + fid: skip + # - algorithm: qgis:findprojection # name: Find projection # params: From e23617a83d3ba3e80345745a9c5a581252dced29 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 15:38:55 +1000 Subject: [PATCH 234/266] Port split with lines to new API Improvements: - handle transparent reprojection if layer and lines are in different CRS --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../processing/algs/qgis/SplitWithLines.py | 67 ++++++------ .../tests/testdata/qgis_algorithm_tests.yaml | 100 +++++++++--------- 3 files changed, 90 insertions(+), 82 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 7e6fd8014a0..c72b4e57e14 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -112,6 +112,7 @@ from .Smooth import Smooth from .SnapGeometries import SnapGeometriesToLayer from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SpatialIndex import SpatialIndex +from .SplitWithLines import SplitWithLines from .SumLines import SumLines from .SymmetricalDifference import SymmetricalDifference from .TextToFloat import TextToFloat @@ -154,7 +155,6 @@ from .ZonalStatistics import ZonalStatistics # from .SetRasterStyle import SetRasterStyle # from .SelectByAttributeSum import SelectByAttributeSum # from .HypsometricCurves import HypsometricCurves -# from .SplitWithLines import SplitWithLines # from .FieldsMapper import FieldsMapper # from .Datasources2Vrt import Datasources2Vrt # from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox @@ -205,7 +205,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # PointsFromLines(), PointsToPaths(), # SetVectorStyle(), SetRasterStyle(), # HypsometricCurves(), - # SplitWithLines(), CreateConstantRaster(), + # CreateConstantRaster(), # FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(), # OrientedMinimumBoundingBox(), # DefineProjection(), @@ -291,6 +291,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): SnapGeometriesToLayer(), SpatialiteExecuteSQL(), SpatialIndex(), + SplitWithLines(), SumLines(), SymmetricalDifference(), TextToFloat(), diff --git a/python/plugins/processing/algs/qgis/SplitWithLines.py b/python/plugins/processing/algs/qgis/SplitWithLines.py index 9953cc83894..28160ea0641 100644 --- a/python/plugins/processing/algs/qgis/SplitWithLines.py +++ b/python/plugins/processing/algs/qgis/SplitWithLines.py @@ -26,26 +26,23 @@ __copyright__ = '(C) 2014, Bernhard Ströbl' __revision__ = '$Format:%H$' -from qgis.core import (QgsApplication, - QgsFeatureRequest, +from qgis.core import (QgsFeatureRequest, QgsFeature, QgsFeatureSink, QgsGeometry, QgsSpatialIndex, QgsWkbTypes, - QgsMessageLog, - QgsProcessingUtils) + QgsProcessingParameterFeatureSource, + QgsProcessing, + QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector -from processing.tools import dataobjects from processing.tools import vector class SplitWithLines(QgisAlgorithm): - INPUT_A = 'INPUT_A' - INPUT_B = 'INPUT_B' + INPUT = 'INPUT' + LINES = 'LINES' OUTPUT = 'OUTPUT' @@ -56,12 +53,13 @@ class SplitWithLines(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_A, - self.tr('Input layer, single geometries only'), [dataobjects.TYPE_VECTOR_POLYGON, - dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterVector(self.INPUT_B, - self.tr('Split layer'), [dataobjects.TYPE_VECTOR_LINE])) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Split'))) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer, single geometries only'), [QgsProcessing.TypeVectorLine, + QgsProcessing.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterFeatureSource(self.LINES, + self.tr('Split layer'), [QgsProcessing.TypeVectorLine])) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Split'))) def name(self): return 'splitwithlines' @@ -70,34 +68,37 @@ class SplitWithLines(QgisAlgorithm): return self.tr('Split with lines') def processAlgorithm(self, parameters, context, feedback): - layerA = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_A), context) - splitLayer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_B), context) + source = self.parameterAsSource(parameters, self.INPUT, context) + line_source = self.parameterAsSource(parameters, self.LINES, context) - sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B) - fieldList = layerA.fields() + sameLayer = parameters[self.INPUT] == parameters[self.LINES] - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList, QgsWkbTypes.multiType(layerA.wkbType()), - layerA.crs(), context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), QgsWkbTypes.multiType(source.wkbType()), source.sourceCrs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) + request.setDestinationCrs(source.sourceCrs()) + + for aSplitFeature in line_source.getFeatures(request): + if feedback.isCanceled(): + break - for aSplitFeature in QgsProcessingUtils.getFeatures(splitLayer, context, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() - features = QgsProcessingUtils.getFeatures(layerA, context) + features = source.getFeatures() - if QgsProcessingUtils.featureCount(layerA, context) == 0: - total = 100 - else: - total = 100.0 / layerA.featureCount() if layerA.featureCount() else 0 + total = 100.0 / source.featureCount() if source.featureCount() else 100 for current, inFeatA in enumerate(features): + if feedback.isCanceled(): + break + inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) @@ -141,6 +142,9 @@ class SplitWithLines(QgisAlgorithm): split_geom_engine.prepareGeometry() while len(inGeoms) > 0: + if feedback.isCanceled(): + break + inGeom = inGeoms.pop() if inGeom.isNull(): # this has been encountered and created a run-time error @@ -154,7 +158,7 @@ class SplitWithLines(QgisAlgorithm): try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: - QgsMessageLog.logMessage(self.tr('Geometry exception while splitting'), self.tr('Processing'), QgsMessageLog.WARNING) + feedback.reportError(self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections @@ -179,6 +183,9 @@ class SplitWithLines(QgisAlgorithm): parts = [] for aGeom in inGeoms: + if feedback.isCanceled(): + break + passed = True if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry: @@ -196,7 +203,7 @@ class SplitWithLines(QgisAlgorithm): if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 0556cb9f3f0..a17ff63fad1 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1716,56 +1716,56 @@ tests: name: expected/create_points.gml type: vector -# - algorithm: qgis:splitwithlines -# name: Split lines with lines (new alg) -# params: -# INPUT_A: -# name: lines.gml -# type: vector -# INPUT_B: -# name: custom/lines2.gml -# type: vector -# results: -# OUTPUT: -# name: expected/split_lines_with_lines.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:splitwithlines -# name: Split poly with lines -# params: -# INPUT_A: -# name: polys.gml -# type: vector -# INPUT_B: -# name: lines.gml -# type: vector -# results: -# OUTPUT: -# name: expected/split_polys_with_lines.gml -# type: vector -# compare: -# geometry: -# precision: 7 -# -# - algorithm: qgis:splitwithlines -# name: Split lines with same lines -# params: -# INPUT_A: -# name: lines.gml -# type: vector -# INPUT_B: -# name: lines.gml -# type: vector -# results: -# OUTPUT: -# name: expected/split_lines_with_lines_same.gml -# type: vector -# compare: -# geometry: -# precision: 7 + - algorithm: qgis:splitwithlines + name: Split lines with lines (new alg) + params: + INPUT: + name: lines.gml + type: vector + LINES: + name: custom/lines2.gml + type: vector + results: + OUTPUT: + name: expected/split_lines_with_lines.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:splitwithlines + name: Split poly with lines + params: + INPUT: + name: polys.gml + type: vector + LINES: + name: lines.gml + type: vector + results: + OUTPUT: + name: expected/split_polys_with_lines.gml + type: vector + compare: + geometry: + precision: 7 + + - algorithm: qgis:splitwithlines + name: Split lines with same lines + params: + INPUT: + name: lines.gml + type: vector + LINES: + name: lines.gml + type: vector + results: + OUTPUT: + name: expected/split_lines_with_lines_same.gml + type: vector + compare: + geometry: + precision: 7 - algorithm: qgis:dropgeometries name: Drop geometries From 82182040ab7a6756ab666cb4176b02fa35582ede Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 27 Jul 2017 16:02:52 +1000 Subject: [PATCH 235/266] Port Create Constant Raster to new API and add test --- .../algs/qgis/CreateConstantRaster.py | 32 ++++++++---------- .../algs/qgis/QGISAlgorithmProvider.py | 5 +-- .../testdata/expected/constant_raster.tif | Bin 0 -> 1232 bytes .../expected/constant_raster.tif.aux.xml | 10 ++++++ .../tests/testdata/qgis_algorithm_tests.yaml | 13 +++++++ 5 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/expected/constant_raster.tif create mode 100644 python/plugins/processing/tests/testdata/expected/constant_raster.tif.aux.xml diff --git a/python/plugins/processing/algs/qgis/CreateConstantRaster.py b/python/plugins/processing/algs/qgis/CreateConstantRaster.py index 72363b18f0f..e8a6dc9f9a8 100644 --- a/python/plugins/processing/algs/qgis/CreateConstantRaster.py +++ b/python/plugins/processing/algs/qgis/CreateConstantRaster.py @@ -27,12 +27,10 @@ __revision__ = '$Format:%H$' from osgeo import gdal -from qgis.core import (QgsApplication, - QgsProcessingUtils) +from qgis.core import (QgsProcessingParameterRasterLayer, + QgsProcessingParameterNumber, + QgsProcessingParameterRasterDestination) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterRaster -from processing.core.parameters import ParameterNumber -from processing.core.outputs import OutputRaster from processing.tools.raster import RasterWriter @@ -49,14 +47,12 @@ class CreateConstantRaster(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterRaster(self.INPUT, - self.tr('Reference layer'))) - self.addParameter(ParameterNumber(self.NUMBER, - self.tr('Constant value'), - default=1.0)) - - self.addOutput(OutputRaster(self.OUTPUT, - self.tr('Constant'))) + self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT, + self.tr('Reference layer'))) + self.addParameter(QgsProcessingParameterNumber(self.NUMBER, + self.tr('Constant value'), QgsProcessingParameterNumber.Double, + defaultValue=1)) + self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT, self.tr('Constant'))) def name(self): return 'createconstantrasterlayer' @@ -65,10 +61,10 @@ class CreateConstantRaster(QgisAlgorithm): return self.tr('Create constant raster layer') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - value = self.getParameterValue(self.NUMBER) + layer = self.parameterAsRasterLayer(parameters, self.INPUT, context) + value = self.parameterAsDouble(parameters, self.NUMBER, context) - output = self.getOutputFromName(self.OUTPUT) + outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) raster = gdal.Open(layer.source(), gdal.GA_ReadOnly) geoTransform = raster.GetGeoTransform() @@ -76,7 +72,7 @@ class CreateConstantRaster(QgisAlgorithm): cellsize = (layer.extent().xMaximum() - layer.extent().xMinimum()) \ / layer.width() - w = RasterWriter(output.getCompatibleFileName(self), + w = RasterWriter(outputFile, layer.extent().xMinimum(), layer.extent().yMinimum(), layer.extent().xMaximum(), @@ -88,3 +84,5 @@ class CreateConstantRaster(QgisAlgorithm): ) w.matrix.fill(value) w.close() + + return {self.OUTPUT: outputFile} diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index c72b4e57e14..a15b174383f 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -49,6 +49,7 @@ from .BoundingBox import BoundingBox from .CheckValidity import CheckValidity from .ConcaveHull import ConcaveHull from .CreateAttributeIndex import CreateAttributeIndex +from .CreateConstantRaster import CreateConstantRaster from .Delaunay import Delaunay from .DeleteColumn import DeleteColumn from .DeleteHoles import DeleteHoles @@ -146,7 +147,6 @@ from .ZonalStatistics import ZonalStatistics # from .FieldsCalculator import FieldsCalculator # from .FieldPyculator import FieldsPyculator # from .JoinAttributes import JoinAttributes -# from .CreateConstantRaster import CreateConstantRaster # from .PointsDisplacement import PointsDisplacement # from .PointsFromPolygons import PointsFromPolygons # from .PointsFromLines import PointsFromLines @@ -205,7 +205,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # PointsFromLines(), PointsToPaths(), # SetVectorStyle(), SetRasterStyle(), # HypsometricCurves(), - # CreateConstantRaster(), + # # FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(), # OrientedMinimumBoundingBox(), # DefineProjection(), @@ -228,6 +228,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): CheckValidity(), ConcaveHull(), CreateAttributeIndex(), + CreateConstantRaster(), Delaunay(), DeleteColumn(), DeleteHoles(), diff --git a/python/plugins/processing/tests/testdata/expected/constant_raster.tif b/python/plugins/processing/tests/testdata/expected/constant_raster.tif new file mode 100644 index 0000000000000000000000000000000000000000..cc388cedad49daf116ec735ff804fa0bc31a4e9c GIT binary patch literal 1232 zcmebD)MDUZU|Xqtfk6gIO)!)V6lUOS=3xNQ=YY7Wg@=I+NIwJO_3g|IDnMEcX#R$Fh?+Pc zdt*BjgA`D_4CEjXC`ffRs8n%K^>pHyEvDiC7Q;i_xbwlqp-sT?-nSzutxSQAFr|!O z-vGmf1sE(K2&I`=HnxiZ6)|#bY-eQa2Um-VKABo$V&qN(6Eu3 literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/constant_raster.tif.aux.xml b/python/plugins/processing/tests/testdata/expected/constant_raster.tif.aux.xml new file mode 100644 index 00000000000..5dea3552bd8 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/constant_raster.tif.aux.xml @@ -0,0 +1,10 @@ + + + + 3 + 3 + 3 + 0 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index a17ff63fad1..b5ca4f7e688 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1215,6 +1215,19 @@ tests: # hash: 7fe0e0174185fd743e23760f33615adf10f771b4275f320db6f7f4f8 # type: rasterhash + - algorithm: qgis:createconstantrasterlayer + name: Create constant raster + params: + INPUT: + name: raster.tif + type: raster + NUMBER: 3.0 + results: + OUTPUT: + hash: 4cb3e82e8512cdbb75d9c39a10adc818dd6842c5dc6361fbc43dd9aa + type: rasterhash + + # Case 1: Keep all fields - algorithm: qgis:lineintersections name: Line Intersection Keep All Fields from Both From 497662a40c942ac67d1508415dfdcb5021e82896 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 27 Jul 2017 13:09:40 +0100 Subject: [PATCH 236/266] Fixes relation reference widget when filters are reset --- .../qgsrelationreferencewidget.cpp | 57 ++++++++++++++----- .../qgsrelationreferencewidget.h | 1 + 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp index 76aeab03cd8..bbb98ffb825 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidget.cpp +++ b/src/gui/editorwidgets/qgsrelationreferencewidget.cpp @@ -811,7 +811,7 @@ void QgsRelationReferenceWidget::filterChanged() { QVariant nullValue = QgsApplication::nullRepresentation(); - QStringList filters; + QMap filters; QgsAttributeList attrs; QComboBox *scb = qobject_cast( sender() ); @@ -822,6 +822,11 @@ void QgsRelationReferenceWidget::filterChanged() QgsFeatureIds featureIds; QString filterExpression; + // comboboxes have to be disabled before building filters + if ( mChainFilters ) + disableChainedComboBoxes( scb ); + + // build filters Q_FOREACH ( QComboBox *cb, mFilterComboBoxes ) { if ( cb->currentIndex() != 0 ) @@ -830,11 +835,11 @@ void QgsRelationReferenceWidget::filterChanged() if ( cb->currentText() == nullValue.toString() ) { - filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName ); + filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName ); } else { - filters << QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() ); + filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() ); } attrs << mReferencedLayer->fields().lookupField( fieldName ); } @@ -854,12 +859,7 @@ void QgsRelationReferenceWidget::filterChanged() continue; } - if ( ccb->currentIndex() == 0 ) - { - cb->setCurrentIndex( 0 ); - cb->setEnabled( false ); - } - else + if ( ccb->currentIndex() != 0 ) { const QString fieldName = cb->property( "Field" ).toString(); filtered = true; @@ -873,9 +873,9 @@ void QgsRelationReferenceWidget::filterChanged() QStringList texts; Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] ) { - QStringList filtersAttrs = filters; - filtersAttrs << QgsExpression::createFieldEqualityExpression( fieldName, txt ); - QString expression = filtersAttrs.join( QStringLiteral( " AND " ) ); + QMap filtersAttrs = filters; + filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt ); + QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) ); QgsAttributeList subset = attrs; subset << mReferencedLayer->fields().lookupField( fieldName ); @@ -909,9 +909,13 @@ void QgsRelationReferenceWidget::filterChanged() if ( !mChainFilters || ( mChainFilters && !filtered ) ) { - filterExpression = filters.join( QStringLiteral( " AND " ) ); + filterExpression = filters.values().join( QStringLiteral( " AND " ) ); - QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) ); + QgsFeatureRequest req = QgsFeatureRequest().setSubsetOfAttributes( attrs ); + if ( !filterExpression.isEmpty() ) + req.setFilterExpression( filterExpression ); + + QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( req ) ); while ( it.nextFeature( f ) ) { @@ -951,3 +955,28 @@ void QgsRelationReferenceWidget::updateAddEntryButton() mAddEntryButton->setVisible( mAllowAddFeatures ); mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() ); } + +void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb ) +{ + QComboBox *ccb = nullptr; + Q_FOREACH ( QComboBox *cb, mFilterComboBoxes ) + { + if ( !ccb ) + { + if ( cb == scb ) + { + ccb = cb; + } + + continue; + } + + if ( ccb->currentIndex() == 0 ) + { + cb->setCurrentIndex( 0 ); + cb->setEnabled( false ); + } + else + ccb = cb; + } +} diff --git a/src/gui/editorwidgets/qgsrelationreferencewidget.h b/src/gui/editorwidgets/qgsrelationreferencewidget.h index 893406576d0..6a818a3d3ed 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidget.h +++ b/src/gui/editorwidgets/qgsrelationreferencewidget.h @@ -185,6 +185,7 @@ class GUI_EXPORT QgsRelationReferenceWidget : public QWidget private: void highlightFeature( QgsFeature f = QgsFeature(), CanvasExtent canvasExtent = Fixed ); void updateAttributeEditorFrame( const QgsFeature &feature ); + void disableChainedComboBoxes( const QComboBox *cb ); // initialized QgsAttributeEditorContext mEditorContext; From 5a620294783d823f976d3afab9fc6ba08247598c Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 27 Jul 2017 13:12:52 +0100 Subject: [PATCH 237/266] Add tests --- tests/src/gui/testqgsrelationreferencewidget.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/src/gui/testqgsrelationreferencewidget.cpp b/tests/src/gui/testqgsrelationreferencewidget.cpp index 9910c2c7813..ee7425c2246 100644 --- a/tests/src/gui/testqgsrelationreferencewidget.cpp +++ b/tests/src/gui/testqgsrelationreferencewidget.cpp @@ -166,6 +166,19 @@ void TestQgsRelationReferenceWidget::testChainFilter() // "material" == 'iron' AND "diameter" == '120' AND "raccord" = 'collar' } } + + // set the filter for "raccord" and then reset filter for "diameter". As + // chain filter is activated, the filter on "raccord" field should be reset + cbs[2]->setCurrentIndex( cbs[2]->findText( "brides" ) ); + cbs[1]->setCurrentIndex( cbs[1]->findText( "diameter" ) ); + + // combobox should propose NULL, 10 and 11 because the filter is now: + // "material" == 'iron' + QCOMPARE( w.mComboBox->count(), 3 ); + + // if there's no filter at all, all features' id should be proposed + cbs[0]->setCurrentIndex( cbs[0]->findText( "material" ) ); + QCOMPARE( w.mComboBox->count(), 4 ); } QGSTEST_MAIN( TestQgsRelationReferenceWidget ) From c0c18cd466d17959b38098f1f9050a4271716195 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 27 Jul 2017 19:22:01 +0200 Subject: [PATCH 238/266] Added missing \param to ctor documentation --- python/server/qgsbufferserverrequest.sip | 8 ++++++-- src/server/qgsbufferserverrequest.h | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/python/server/qgsbufferserverrequest.sip b/python/server/qgsbufferserverrequest.sip index 156fb1c37b3..adc09d356d2 100644 --- a/python/server/qgsbufferserverrequest.sip +++ b/python/server/qgsbufferserverrequest.sip @@ -26,7 +26,9 @@ class QgsBufferServerRequest : QgsServerRequest Constructor \param url the url string - \param method the request method + \param method the request method, default to GET + \param headers optional dictionary of header name-values + \param data optional POST data %End QgsBufferServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers(), QByteArray *data = 0 ); @@ -34,7 +36,9 @@ class QgsBufferServerRequest : QgsServerRequest Constructor \param url QUrl - \param method the request method + \param method the request method, default to GET + \param headers optional dictionary of header name-values + \param data optional POST data %End ~QgsBufferServerRequest(); diff --git a/src/server/qgsbufferserverrequest.h b/src/server/qgsbufferserverrequest.h index 67cd12854f8..8ebc64b7f60 100644 --- a/src/server/qgsbufferserverrequest.h +++ b/src/server/qgsbufferserverrequest.h @@ -39,7 +39,9 @@ class SERVER_EXPORT QgsBufferServerRequest : public QgsServerRequest * Constructor * * \param url the url string - * \param method the request method + * \param method the request method, default to GET + * \param headers optional dictionary of header name-values + * \param data optional POST data */ QgsBufferServerRequest( const QString &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers(), QByteArray *data = nullptr ); @@ -47,7 +49,9 @@ class SERVER_EXPORT QgsBufferServerRequest : public QgsServerRequest * Constructor * * \param url QUrl - * \param method the request method + * \param method the request method, default to GET + * \param headers optional dictionary of header name-values + * \param data optional POST data */ QgsBufferServerRequest( const QUrl &url, QgsServerRequest::Method method = QgsServerRequest::GetMethod, const QgsServerRequest::Headers &headers = QgsServerRequest::Headers(), QByteArray *data = nullptr ); From 0a4a76ec9bedccec0471dc4205d776f08100c906 Mon Sep 17 00:00:00 2001 From: Johan Van de Wauw Date: Thu, 27 Jul 2017 22:47:52 +0200 Subject: [PATCH 239/266] update INSTALL: only GRASS 7 is supported INSTALL has to be updated as only GRASS7 is now supported ( #4847 ) --- doc/overview.t2t | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/overview.t2t b/doc/overview.t2t index 60ab6419bbb..bed50d9a948 100644 --- a/doc/overview.t2t +++ b/doc/overview.t2t @@ -31,10 +31,7 @@ Required build dependencies: Optional dependencies: -- for GRASS providers and plugin - GRASS >= 6.0.0. QGIS may be compiled with GRASS 6 or GRASS 7. - It can also be compiled with both GRASS versions in a single build but only if QGIS - is not installed with rpath. The desired GRASS version is chosen on runtime by setting - LD_LIBRARY_PATH or PATH. +- for GRASS providers and plugin - GRASS >= 7.0.0. - for georeferencer - GSL >= 1.8 - for PostGIS support - PostgreSQL >= 8.0.x - for gps plugin - gpsbabel From fbff452e5cafda9b37511867bd349dda08668582 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Fri, 28 Jul 2017 01:09:11 +0200 Subject: [PATCH 240/266] run t2tdoc --- INSTALL | 268 +---------------------- doc/INSTALL.html | 554 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 404 insertions(+), 418 deletions(-) diff --git a/INSTALL b/INSTALL index 9fc7cc83b63..550423ceea5 100644 --- a/INSTALL +++ b/INSTALL @@ -1,9 +1,11 @@ QGIS Building QGIS from source - step by step -Saturday July 15, 2017 +Friday July 28, 2017 + + +Last Updated: Friday July 28, 2017 +Last Change : Friday July 28, 2017 -Last Updated: Saturday July 15, 2017 -Last Change : Saturday July 15, 2017 1. Introduction 2. Overview @@ -42,7 +44,6 @@ Last Change : Saturday July 15, 2017 9. Authors and Acknowledgments - 1. Introduction =============== @@ -93,7 +94,6 @@ Required build tools: - Flex >= 2.5.6 - Bison >= 2.4 - Required build dependencies: - Qt >= 5.3.0 @@ -109,13 +109,9 @@ Required build dependencies: - QCA - qtkeychain (>= 0.5) - Optional dependencies: -- for GRASS providers and plugin - GRASS >= 6.0.0. QGIS may be compiled with GRASS 6 or GRASS 7. - It can also be compiled with both GRASS versions in a single build but only if QGIS - is not installed with rpath. The desired GRASS version is chosen on runtime by setting - LD_LIBRARY_PATH or PATH. +- for GRASS providers and plugin - GRASS >= 7.0.0. - for georeferencer - GSL >= 1.8 - for PostGIS support - PostgreSQL >= 8.0.x - for gps plugin - gpsbabel @@ -124,7 +120,6 @@ Optional dependencies: - for qgis mapserver - FastCGI - for oracle provider - Oracle OCI library - Indirect dependencies: Some proprietary formats (e.g., ECW and MrSid) supported by GDAL require @@ -143,7 +138,7 @@ those formats in GDAL. Requires: Ubuntu / Debian derived distro -/!\ Note: Refer to the section ''Building Debian packages'' for building +/!\ Note: Refer to the section Building Debian packages for building debian packages. Unless you plan to develop on QGIS, that is probably the easiest option to compile and install QGIS. @@ -171,21 +166,17 @@ of Ubuntu. This is not activated by default, so you need to activate it: 1. Edit your /etc/apt/sources.list file. 2. Uncomment all the lines starting with "deb" - Also you will need to be running Ubuntu 'precise' or higher in order for all dependencies to be met. Now update your local sources database: - sudo apt-get update - 3.3. Install build dependencies =============================== - || Distribution | install command for packages | | stretch | ``apt-get install bison ca-certificates cmake dh-python doxygen flex gdal-bin git graphviz grass-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqt5opengl5-dev libqt5scintilla2-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite lighttpd locales ninja-build pkg-config poppler-utils pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-dev python3-future python3-gdal python3-mock python3-nose2 python3-psycopg2 python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-sip python3-sip-dev python3-termcolor python3-yaml qt5keychain-dev qtbase5-dev qtpositioning5-dev qtscript5-dev qttools5-dev qttools5-dev-tools spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb cmake-curses-gui expect`` | | xenial | ``apt-get install bison ca-certificates cmake dh-python doxygen flex gdal-bin git graphviz grass-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqt5opengl5-dev libqt5scintilla2-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite lighttpd locales ninja-build pkg-config poppler-utils pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-dev python3-future python3-gdal python3-mock python3-nose2 python3-psycopg2 python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-sip python3-sip-dev python3-termcolor python3-yaml qt5keychain-dev qtbase5-dev qtpositioning5-dev qtscript5-dev qttools5-dev qttools5-dev-tools spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb cmake-curses-gui expect`` | @@ -201,13 +192,11 @@ Now update your local sources database: You should also setup ccache to speed up compile times: - cd /usr/local/bin sudo ln -s /usr/bin/ccache gcc sudo ln -s /usr/bin/ccache g++ - 3.5. Prepare your development environment ========================================= @@ -215,11 +204,9 @@ As a convention I do all my development work in $HOME/dev/, so in this case we will create a work environment for C++ development work like this: - mkdir -p ${HOME}/dev/cpp cd ${HOME}/dev/cpp - This directory path will be assumed for all instructions that follow. @@ -233,19 +220,15 @@ if you do not have edit privileges for the QGIS source repository, or use 1. Anonymous Checkout - cd ${HOME}/dev/cpp git clone git://github.com/qgis/QGIS.git - 2. Developer Checkout - cd ${HOME}/dev/cpp git clone git@github.com:qgis/QGIS.git - 3.7. Starting the compile ========================= @@ -254,19 +237,15 @@ conflicts with Ubuntu packages that may be under /usr. This way for example you can use the binary packages of QGIS on your system along side with your development version. I suggest you do something similar: - mkdir -p ${HOME}/apps - Now we create a build directory and run ccmake: - cd QGIS mkdir build-master cd build-master ccmake .. - When you run ccmake (note the .. is required!), a menu will appear where you can configure various aspects of the build. If you want QGIS to have debugging capabilities then set CMAKE_BUILD_TYPE to Debug. If you do not have @@ -286,68 +265,50 @@ the empty build directory once before starting to use the interactive tools. Now on with the build: - make -jX - where X is the number of available cores. Depending on your platform, this can speed up the build time considerably. Then you can directly run from the build directory: - ./output/bin/qgis - Another option is to install to your system: - make install - After that you can try to run QGIS: - $HOME/apps/bin/qgis - If all has worked properly the QGIS application should start up and appear on your screen. If you get the error message "error while loading shared libraries", execute this command in your shell. - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${HOME}/apps/lib/ - Optionally, if you already know what aspects you want in your custom build then you can skip the interactive ccmake .. part by using the cmake -D option for each aspect, e.g.: - cmake -D CMAKE_BUILD_TYPE=Debug -D CMAKE_INSTALL_PREFIX=${HOME}/apps .. - Also, if you want to speed your build times, you can easily do it with ninja, an alternative to make with similar build options. For example, to configure your build you can do either one of: - ccmake -G Ninja .. - - cmake -G Ninja -D CMAKE_BUILD_TYPE=Debug -D CMAKE_INSTALL_PREFIX=${HOME}/apps .. - Build and install with ninja: - ninja (uses all cores by default; also supports the above described -jX option) ninja install - 3.8. Building Debian packages ============================= @@ -357,23 +318,17 @@ you'll find a debian directory. First you need to install the debian packaging tools once: - apt-get install build-essential - First you need to create an changelog entry for your distribution. For example for Ubuntu Precise: - dch -l ~precise --force-distribution --distribution precise "precise build" - The QGIS packages will be created with: - dpkg-buildpackage -us -uc -b - /!\ Note: Install devscripts to get dch. /!\ Note: If dpkg-buildpackage complains about unmet build dependencies @@ -391,11 +346,9 @@ build command. The upload of results can be avoided with DEB_TEST_TARGET=test. The packages are created in the parent directory (ie. one level up). Install them using dpkg. E.g.: - sudo debi - 3.9. On Fedora Linux ==================== @@ -406,47 +359,35 @@ new subdirectory called `build` or `build-qt5` in it. 3.9.1. Install build dependencies ================================= - dnf install qt5-qtwebkit-devel qt5-qtlocation-devel qt5-qttools-static qt5-qtscript-devel qca-qt5-devel python3-qt5-devel python3-qscintilla-qt5-devel qscintilla-qt5-devel python3-qscintilla-devel python3-qscintilla-qt5 clang flex bison geos-devel gdal-devel sqlite-devel libspatialite-devel qt5-qtsvg-devel qt5-qtxmlpatterns-devel spatialindex-devel expat-devel proj-devel qwt-qt5-devel gsl-devel postgresql-devel cmake python3-future gdal-python3 python3-psycopg2 python3-PyYAML python3-pygments python3-jinja2 python3-OWSLib qca-qt5-ossl qwt-qt5-devel qtkeychain-qt5-devel qwt-devel sip-devel - To build QGIS server additional dependencies are required: - dnf install fcgi-devel - Make sure that your build directory is completely empty when you enter the following command. Do never try to "re-use" an existing Qt4 build directory. If you want to use `ccmake` or other interactive tools, run the following command in the empty build directory once before starting to use the interactive tools. - cmake .. - If everything went OK you can finally start to compile. (As usual append a -jX where X is the number of available cores option to make to speed up your build process) - make - Run from the build directory - ./output/bin/qgis - Or install to your system - make install - 3.9.2. Suggested system tweaks ============================== @@ -455,14 +396,12 @@ the useful debug output which is normally printed when running the unit tests. To enable debug prints for the current user, execute: - cat > ~/.config/QtProject/qtlogging.ini << EOL [Rules] default.debug=true EOL - 4. Building on Windows ====================== @@ -491,7 +430,6 @@ The free (as in free beer) Community installer is available under: Download and install following packages: - || Tool | Website | | CMake | https://cmake.org/files/v3.7/cmake-3.7.2-win64-x64.msi | | GNU flex, GNU bison and GIT | http://cygwin.com/setup-x86.exe (32bit) or http://cygwin.com/setup-x86_64.exe (64bit) | @@ -508,7 +446,6 @@ For the QGIS build you need to install following packages from cygwin: - flex - git - and from OSGeo4W (select Advanced Installation): - expat @@ -536,7 +473,6 @@ and from OSGeo4W (select Advanced Installation): - sip-qt5 - spatialite - This will also select packages the above packages depend on. If you install other packages, this might cause issues. Particularly, make sure @@ -561,7 +497,6 @@ To start a command prompt with an environment that both has the VC++ and the OSG variables create the following batch file (assuming the above packages were installed in the default locations): - @echo off set VS90COMNTOOLS=%PROGRAMFILES%\Microsoft Visual Studio 9.0\Common7\Tools\ call "%PROGRAMFILES%\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86 @@ -579,14 +514,11 @@ installed in the default locations): @cmd - Start the batch file and on the command prompt checkout the QGIS source from git to the source directory QGIS: - git clone git://github.com/qgis/QGIS.git - Create a 'build' directory somewhere. This will be where all the build output will be generated. @@ -656,18 +588,14 @@ your own hand built QGIS executables, you need to copy them in from your windows installation into the ms-windows file tree created by the creatensis script. - cd ms-windows/ rm -rf osgeo4w/unpacked/apps/qgis/* cp -r /tmp/qgis1.7.0/* osgeo4w/unpacked/apps/qgis/ - Now create a package. - ./quickpackage.sh - After this you should now have a nsis installer containing your own build of QGIS and all dependencies needed to run it on a windows machine. @@ -727,11 +655,9 @@ When Qt installation is complete: Edit C:\Qt\4.8.0\bin\qtvars.bat and add the following lines: - set PATH=%PATH%;C:\msys\local\bin;c:\msys\local\lib set PATH=%PATH%;"C:\Program Files\Subversion\bin" - I suggest you also add C:\Qt\4.8.0\bin\ to your Environment Variables Path in the windows system preferences. @@ -743,11 +669,9 @@ this message "mingw32-make: *** No rule to make target `debug'. Stop.". To compile the debug version you have to go out of src directory and execute the following command: - c:\Qt\4.8.0 make - 4.2.3. Flex and Bison ===================== @@ -785,25 +709,21 @@ to get versions that match your current Qt installed version. 4.2.4.3. Compile SIP ==================== - c:\Qt\4.8.0\bin\qtvars.bat python configure.py -p win32-g++ make make install - 4.2.4.4. Compile PyQt ===================== - c:\Qt\4.8.0\bin\qtvars.bat python configure.py make make install - 4.2.4.5. Final python notes =========================== @@ -834,18 +754,14 @@ http://www.cmake.org/files/v2.8/cmake-2.8.2-win32-x86.exe Start a cmd.exe window ( Start -> Run -> cmd.exe ) Create development directory and move into it - md c:\dev\cpp cd c:\dev\cpp - Check out sources from GIT: - git clone git://github.com/qgis/QGIS.git - 4.2.8. Compiling ================ @@ -855,28 +771,22 @@ this document. Start a cmd.exe window ( Start -> Run -> cmd.exe ) if you don't have one already. Add paths to compiler and our MSYS environment: - c:\Qt\4.8.0\bin\qtvars.bat - For ease of use add c:\Qt\4.8.0\bin\ to your system path in system properties so you can just type qtvars.bat when you open the cmd console. Create build directory and set it as current directory: - cd c:\dev\cpp\qgis md build cd build - 4.2.9. Configuration ==================== - cmakesetup .. - Note: You must include the '..' above. Click 'Configure' button. When asked, you should choose 'MinGW Makefiles' as @@ -898,11 +808,9 @@ When configuration is done, click 'OK' to exit the setup utility. 4.2.10. Compilation and installation ==================================== - make make install - 4.2.11. Run qgis.exe from the directory where it's installed (CMAKE_INSTALL_PREFIX) =================================================================================== @@ -1017,7 +925,6 @@ In MSYS console go to the directory where you've unpacked or checked out sources Run these commands: - export PATH="/usr/local/bin:/usr/local/lib:$PATH" ./configure --prefix=/usr/local --bindir=/usr/local --with-includes=/usr/local/include --with-libs=/usr/local/lib --with-cxx --without-jpeg \ --without-tiff --with-postgres=yes --with-postgres-includes=/local/pgsql/include --with-pgsql-libs=/local/pgsql/lib --with-opengl=windows --with-fftw \ @@ -1026,7 +933,6 @@ Run these commands: make make install - It should get installed to c:\msys\local\grass-6.3.cvs By the way, these pages might be useful: @@ -1047,24 +953,19 @@ Unpack to e.g. c:\msys\local\src To compile, I had to patch the sources: in file source/headers/timeval.h line 13. Change it from: - #ifdef _WIN32 to: - #if defined(_WIN32) && defined(_MSC_VER) - Now, in MSYS console, go to the source directory and run: - ./configure --prefix=/usr/local make make install - 4.3.2.4. SQLITE =============== @@ -1094,13 +995,11 @@ Unpack to c:\msys\local\src Run from MSYS console in the source directory: - ./configure make make install - 4.3.2.6. EXPAT ============== @@ -1112,13 +1011,11 @@ Unpack to c:\msys\local\src Run from MSYS console in the source directory: - ./configure make make install - 4.3.2.7. POSTGRES ================= @@ -1161,10 +1058,8 @@ Parallel Compilation: On multiprocessor/multicore Macs, it's possible to speed up compilation, but it's not automatic. Whenever you type "make" (but NOT "make install"), instead type: - make -j [#cpus] - Replace [#cpus] with the number of cores and/or processors your Mac has. On recent models with hyperthreading processors this can be double the physical count of processors and cores. @@ -1175,16 +1070,12 @@ ie: Macbook Pro i5 (hyperthreading) = 2 cores X 2 = 4 To find out how many CPUs you have available, run the following in Terminal: - /usr/sbin/sysctl -n hw.ncpu - which can be used in build shell scripts like: - make -j $(/usr/sbin/sysctl -n hw.ncpu) - Note: if you get an error in parallel compilation, try removing the -j # flag, so it's just 'make', or using a smaller number. Sometimes make can hiccup on too many threads. @@ -1209,10 +1100,8 @@ do a custom install and install the Unix Development or Command Line Tools optio On Lion, if you have installed Xcode 4.0 - 4.2 and are upgrading to 4.3, it's a good idea to uninstall the old version first with: - sudo /Developer/Library/uninstall-devtools - On Lion and Mt. Lion, using Xcode 4.4+, the developer command line tools can be installed via the Xcode preferences. @@ -1227,11 +1116,9 @@ The supplied clang version 4 can compile QGIS, but presents many warnings compared to just using LLVM. You can specifically use LLVM by exporting paths to the compilers in Terminal, or shell scripts, prior to building QGIS: - export CC=/usr/bin/llvm-gcc export CXX=/usr/bin/llvm-g++ - If you have trouble building some of the dependencies listed below with clang (e.g. OSG & osgEarth), try using only the LLVM compilers. @@ -1275,11 +1162,9 @@ Leopard+ note: Qt includes a couple non-framework libraries in /usr/lib. When using a system SDK these libraries will not be found. To fix this problem, add symlinks to /usr/local: - sudo ln -s /usr/lib/libQtUiTools.a /usr/local/lib/ sudo ln -s /usr/lib/libQtCLucene.dylib /usr/local/lib/ - These should then be found automatically. Earlier systems may need some help by adding '-L/usr/local/lib' to CMAKE_SHARED_LINKER_FLAGS, CMAKE_MODULE_LINKER_FLAGS and CMAKE_EXE_LINKER_FLAGS in the cmake build. @@ -1298,13 +1183,11 @@ strange application). Instead, download the source. NOTE: 2.8.5 is broken for detecting part of Qt. Fixed in 2.8.6. Double-click the source tarball to unpack it, then cd to the source folder and: - ./bootstrap --docdir=/share/doc/CMake --mandir=/share/man make -j [#cpus] sudo make install - 5.3.1. Optional setup: ccache ============================= @@ -1323,33 +1206,27 @@ http://ccache.samba.org/ Double-click the source tarball to unpack, then, in Terminal.app, cd to the source folder and: - ./configure make sudo make install - After install, symbolically link compilers to /usr/local/bin/ccache. (Note: this differs from instructions at http://ccache.samba.org/manual.html Changing the /usr/bin:/usr/local/bin order in PATH is not recommended on OS X. - sudo mkdir /usr/local/bin/compilers && cd /usr/local/bin/compilers sudo ln -s ../ccache gcc sudo ln -s ../ccache g++ sudo ln -s ../ccache cc sudo ln -s ../ccache c++ - Add the following to the end of your ~/.bash_profile (and optionally ~/.bashrc) to allow your login shell to discover the symbolically linked compilers before /usr/bin compilers and to easily toggle using ccache off, by commenting out the line and starting a new login session in Terminal. - export PATH=/usr/local/bin/compilers:$PATH - If you have trouble building some of the dependencies listed below (e.g. OSG & osgEarth), try bypassing ccache. @@ -1408,13 +1285,11 @@ http://sourceforge.net/project/showfiles.php?group_id=10127 Double-click the source tarball to unpack, then, in Terminal.app, cd to the source folder and: - ./configure make sudo make install - 5.4.3. Additional dependencies: Spatialindex ============================================ @@ -1425,13 +1300,11 @@ http://download.osgeo.org/libspatialindex/ Double-click the source tarball to unpack, then, in Terminal.app, cd to the source folder and: - ./configure --disable-dependency-tracking CFLAGS=-Os make sudo make install - 5.4.4. Additional dependencies: Python ====================================== @@ -1464,22 +1337,18 @@ cd to the source folder. Then for your chosen Python: python.org Python - python configure.py make sudo make install - Leopard system Python SIP wants to install in the system path -- this is not a good idea. More configuration is needed to install outside the system path: - python configure.py -n -d /Library/Python/2.5/site-packages -b /usr/local/bin \ -e /usr/local/include -v /usr/local/share/sip -s MacOSX10.5.sdk - Snow Leopard system Python Similar to Leopard, you should install outside the system Python path. @@ -1490,18 +1359,14 @@ Lion. If you are using 32-bit Qt (Qt Carbon): - python2.6 configure.py -n -d /Library/Python/2.6/site-packages -b /usr/local/bin \ -e /usr/local/include -v /usr/local/share/sip --arch=i386 -s MacOSX10.6.sdk - For 64-bit Qt (Qt Cocoa), use this configure line: - python2.6 configure.py -n -d /Library/Python/2.6/site-packages -b /usr/local/bin \ -e /usr/local/include -v /usr/local/share/sip --arch=x86_64 -s MacOSX10.6.sdk - Lion+ system Python Similar to Snow Leopard, you should install outside the system Python path. @@ -1509,35 +1374,27 @@ The SDK option should match the system you are compiling on: for Lion: - python2.7 configure.py -d /Library/Python/2.7/site-packages -b /usr/local/bin \ -e /usr/local/include -v /usr/local/share/sip --arch=x86_64 -s MacOSX10.7.sdk - for Mt. Lion: - python2.7 configure.py -d /Library/Python/2.7/site-packages -b /usr/local/bin \ -e /usr/local/include -v /usr/local/share/sip --arch=x86_64 -s MacOSX10.8.sdk - for Mavericks: - python2.7 configure.py -d /Library/Python/2.7/site-packages -b /usr/local/bin \ -e /usr/local/include -v /usr/local/share/sip --arch=x86_64 -s MacOSX10.9.sdk - continue... Then continue with compilation and installation: - make sudo make install - 5.4.6. Additional dependencies: QScintilla2 =========================================== @@ -1553,13 +1410,10 @@ QScintilla2 wants to install in the system path -- with libraries going into idea, and it also basically breaks the QtDesigner plugin. More configuration is needed to install outside the system path, in /usr/local/: - cd Qt4Qt5 - Edit QScintilla-gpl-2.x.x/Qt4Qt5/qscintilla.pro in the following manner: - current line --> new line target.path = $$[QT_INSTALL_LIBS] --> target.path = /usr/local/lib @@ -1567,19 +1421,15 @@ Edit QScintilla-gpl-2.x.x/Qt4Qt5/qscintilla.pro in the following manner: Save the qscintilla.pro file and build the QScintilla2 C++ library: - qmake -spec macx-g++ qscintilla.pro make -j [#cpus] sudo make install - adjust the install_name_tool command for the version installed of QScintilla installed: - sudo install_name_tool -id /usr/local/lib/libqscintilla2.11.dylib \ /usr/local/lib/libqscintilla2.11.dylib - This installs QScintilla2's dylib in /usr/local/lib/ and the header files in /usr/local/include/Qsci/, both of which should be automatically found when building QGIS. @@ -1590,14 +1440,12 @@ building QGIS. The plugin allows QScintilla2 widgets to be used within QtDesigner. - cd cd designer-Qt4Qt5 qmake -spec macx-g++ designer.pro make sudo make install - Installs in /Developer/Applications/Qt/plugins/designer/ @@ -1613,20 +1461,16 @@ cd to the source folder. Then for your chosen Python: python.org Python - python configure.py -n /usr/local/Qt4.8/qsci yes - Leopard system Python PyQt wants to install in the system path -- this is not a good idea. More configuration is needed to install outside the system path: - python configure.py -d /Library/Python/2.5/site-packages -b /usr/local/bin -n /usr/local/Qt4.8/qsci -v /usr/local/share/sip/PyQt4 - Snow Leopard system Python Similar to Leopard, you should install outside the system Python path. @@ -1637,34 +1481,26 @@ Substitute '2.7' for python version and 10.7 for SDK version below for Lion. If you are using 32-bit Qt (Qt Carbon): - python2.6 configure.py -d /Library/Python/2.6/site-packages -b /usr/local/bin \ -n /usr/local/Qt4.8/qsci -v /usr/local/share/sip/PyQt4 --use-arch i386 - For 64-bit Qt (Qt Cocoa), use this configure line: - python2.6 configure.py -d /Library/Python/2.6/site-packages -b /usr/local/bin \ -n /usr/local/Qt4.8/qsci -v /usr/local/share/sip/PyQt4 --use-arch x86_64 - Lion, Mt. Lion, and Mavericks system Python Similar to Snow Leopard, you should install outside the system Python path. But you don't need the use-arch option: - python2.7 configure.py -d /Library/Python/2.7/site-packages -b /usr/local/bin -n /usr/local/Qt4.8/qsci -v /usr/local/share/sip/PyQt4 - continue... - make -j [#cpus] sudo make install - If there is a problem with undefined symbols in QtOpenGL on Leopard, edit QtOpenGL/makefile and add -undefined dynamic_lookup to LFLAGS. Then make again. @@ -1678,7 +1514,6 @@ Like PyQt, it needs help to not install in system locations. Snow Leopard: substitute '2.6' for Python version - cd cd Python python2.7 configure.py -o /usr/local/lib -n /usr/local/include \ @@ -1692,7 +1527,6 @@ Snow Leopard: substitute '2.6' for Python version make -j [#cpus] sudo make install - The -o and -n options should match the QScintilla2 C++ dylib install options. @@ -1713,7 +1547,6 @@ Terminal. Type these commands to build and install 6.0.x (assumes v6.0.2, adjust commands for other version as needed): - cat >> qwtconfig.pri </usr/local/bin/python32 < ServerName wcs.qgis.org ServerAdmin tim@linfiniti.com @@ -2215,28 +1988,22 @@ Then create /var/www/wcs/7-wcs.qgis.org.conf setting the contents to this: - 6.3. Create a home page ======================= - mkdir html vim html/index.html - Set the contents to: - This is the test platform for QGIS' wcs client. You can use these services from QGIS directly (to try out WCS for example) by pointing your QGIS to: http://wcs.qgis.org/1.9.0/wcs - 6.4. Now deploy it ================== - sudo mkdir /var/log/apache2/wcs_qgis.org sudo chown www-data /var/log/apache2/wcs_qgis.org cd /etc/apache2/sites-available/ @@ -2246,15 +2013,12 @@ Set the contents to: sudo /etc/init.d/apache2 reload - 6.5. Debugging ============== - sudo tail -f /var/log/apache2/wcs_qgis.org/error.log - 7. Setting up a Jenkins Build Server ==================================== @@ -2304,7 +2068,6 @@ procedure is: - Set repository url to git://github.com/qgis/QGIS.git - In advanced repository url settings set refspec to : - +refs/heads/master:refs/remotes/origin/master @@ -2313,7 +2076,6 @@ procedure is: - Build triggers: set to Poll SCM and set schedule to * * * * * (polls every minute) - Build - Execute shell and set shell script to: - cd build cmake .. xvfb-run --auto-servernum --server-num=1 \ @@ -2351,10 +2113,8 @@ http://alexott.blogspot.com/2012/03/jenkins-cmakectest.html If you are interested in seeing embedded debug output, change the following CMake option: - -D CMAKE_BUILD_TYPE=DEBUG (or RELWITHDEBINFO) - This will flood your terminal or system log with lots of useful output from QgsDebugMsg() calls in source code. @@ -2362,34 +2122,25 @@ If you would like to run the test suite, you will need to do so from the build directory, as it will not work with the installed/bundled app. First set the CMake option to enable tests: - -D ENABLE_TESTS=TRUE - Then run all tests from build directory: - cd build make test - To run all tests and report to http://dash.orfeo-toolbox.org/index.php?project=QGIS - cd build make Experimental - You can define the host name reported via 'make Experimental' by setting a CMake option: - -D SITE="my.domain.org" - To run specific test(s) (see 'man ctest'): - cd build # show listing of tests, without running them ctest --show-only @@ -2398,7 +2149,6 @@ To run specific test(s) (see 'man ctest'): ctest --verbose --tests-regex SomeTestName - 9. Authors and Acknowledgments ============================== @@ -2441,7 +2191,3 @@ The following people have contributed to this document: - Debug Output/Tests Section - Larry Shaffer 2012, by way of 'Test Friday' Tim Sutton - - - - diff --git a/doc/INSTALL.html b/doc/INSTALL.html index ba6dcda3911..844e0950ec4 100644 --- a/doc/INSTALL.html +++ b/doc/INSTALL.html @@ -1,11 +1,11 @@ - + QGIS - +