[qgis] 01/09: Imported Upstream version 2.14.4+dfsg
Bas Couwenberg
sebastic at debian.org
Fri Jul 8 15:47:35 UTC 2016
This is an automated email from the git hooks/post-receive script.
sebastic pushed a commit to branch master
in repository qgis.
commit 6d603d945f0ac41298b02623d3c743e4e3fcb7b0
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Fri Jul 8 14:22:04 2016 +0200
Imported Upstream version 2.14.4+dfsg
---
CMakeLists.txt | 25 +-
ChangeLog | 1190 ++++++++++++++++++++
cmake/PyQtMacros.cmake | 6 +-
cmake_templates/qgsconfig.h.in | 2 +
debian/changelog | 10 +-
debian/control | 1 +
debian/control.in | 56 +-
debian/rules | 11 +-
ms-windows/osgeo4w/creatensis.pl | 36 +-
ms-windows/osgeo4w/package-nightly.cmd | 2 +-
ms-windows/osgeo4w/package.cmd | 19 +-
python/core/__init__.py | 8 +-
python/core/composer/qgscomposition.sip | 2 +
.../core/composer/qgsgroupungroupitemscommand.sip | 43 +
python/core/core.sip | 1 +
python/core/dxf/qgsdxfexport.sip | 22 +-
python/core/geometry/qgscurvepolygonv2.sip | 2 +-
python/core/qgsdataprovider.sip | 41 +
python/core/qgsfeaturerequest.sip | 2 +-
python/core/qgsmaprenderer.sip | 14 +-
python/core/qgsmapsettings.sip | 4 +-
python/core/qgspallabeling.sip | 6 +-
python/core/qgsrendercontext.sip | 3 +-
.../core/symbology-ng/qgsellipsesymbollayerv2.sip | 2 +-
.../symbology-ng/qgsinvertedpolygonrenderer.sip | 15 +-
.../core/symbology-ng/qgsmarkersymbollayerv2.sip | 4 +-
.../symbology-ng/qgspointdisplacementrenderer.sip | 9 +-
python/core/symbology-ng/qgsrendererv2.sip | 15 +
python/core/symbology-ng/qgssymbollayerv2.sip | 8 +-
.../symbology-ng/qgsvectorfieldsymbollayer.sip | 3 +
python/gui/qgsannotationitem.sip | 26 +-
python/gui/qgscomposerview.sip | 7 +
python/gui/qgsrubberband.sip | 8 +
python/plugins/GdalTools/tools/GdalTools_utils.py | 28 +-
python/plugins/GdalTools/tools/doContour.py | 10 +
python/plugins/GdalTools/tools/doProjection.py | 4 +-
.../db_manager/db_plugins/oracle/connector.py | 2 +
.../plugins/db_manager/db_plugins/oracle/plugin.py | 2 +
.../db_manager/db_plugins/postgis/connector.py | 4 +-
.../db_manager/db_plugins/postgis/info_model.py | 13 +-
.../db_manager/db_plugins/postgis/plugin.py | 4 +
python/plugins/db_manager/dlg_sql_window.py | 1 +
python/plugins/fTools/tools/doGeometry.py | 12 +-
python/plugins/fTools/tools/doPointsInPolygon.py | 3 +
.../plugins/processing/algs/gdal/GdalAlgorithm.py | 8 +-
python/plugins/processing/algs/gdal/GdalUtils.py | 14 +-
python/plugins/processing/algs/gdal/contour.py | 7 +-
python/plugins/processing/algs/grass/GrassUtils.py | 11 +-
.../plugins/processing/algs/grass7/Grass7Utils.py | 7 +-
.../processing/algs/qgis/AutoincrementalField.py | 2 +-
python/plugins/processing/algs/qgis/Clip.py | 9 -
python/plugins/processing/algs/qgis/ConvexHull.py | 4 +-
.../processing/algs/qgis/CreateConstantRaster.py | 15 +-
python/plugins/processing/algs/qgis/Difference.py | 8 -
python/plugins/processing/algs/qgis/Dissolve.py | 1 +
.../processing/algs/qgis/ExtractByAttribute.py | 2 +-
python/plugins/processing/algs/qgis/HubDistance.py | 36 +-
.../plugins/processing/algs/qgis/Intersection.py | 8 -
python/plugins/processing/algs/qgis/Merge.py | 2 +-
.../processing/algs/qgis/SelectByAttribute.py | 2 +-
python/plugins/processing/algs/qgis/SpatialJoin.py | 2 +-
.../processing/algs/qgis/SymmetricalDifference.py | 8 -
python/plugins/processing/algs/qgis/Union.py | 8 -
.../processing/algs/qgis/ui/FieldsMappingPanel.py | 7 +-
python/plugins/processing/algs/r/RAlgorithm.py | 286 +++--
python/plugins/processing/core/Processing.py | 2 +-
.../plugins/processing/gui/AlgorithmDialogBase.py | 31 +-
.../plugins/processing/gui/GetScriptsAndModels.py | 8 +-
python/plugins/processing/gui/HelpEditionDialog.py | 2 +-
python/plugins/processing/gui/ResultsDialog.py | 13 +-
.../plugins/processing/modeler/ModelerAlgorithm.py | 13 +
.../processing/modeler/ModelerParametersDialog.py | 52 +-
.../plugins/processing/script/ScriptAlgorithm.py | 32 +
python/plugins/processing/tools/dataobjects.py | 5 +-
python/plugins/processing/tools/raster.py | 14 +-
python/plugins/processing/tools/vector.py | 5 +-
python/plugins/processing/ui/DlgAlgorithmBase.ui | 18 +-
.../processing/ui/DlgGetScriptsAndModels.ui | 2 +-
python/plugins/processing/ui/DlgHelpEdition.ui | 17 +-
python/plugins/processing/ui/DlgResults.ui | 21 +-
src/analysis/network/qgsgraph.cpp | 1 -
.../network/qgslinevectorlayerdirector.cpp | 9 +-
src/app/composer/qgscomposer.cpp | 4 +-
src/app/composer/qgscomposerlegendwidget.cpp | 122 +-
src/app/composer/qgscomposerlegendwidget.h | 2 +
src/app/gps/qgsgpsinformationwidget.cpp | 7 +-
src/app/nodetool/qgsmaptoolnodetool.cpp | 5 +
src/app/qgisapp.cpp | 187 +--
src/app/qgisapp.h | 2 +
src/app/qgsapplayertreeviewmenuprovider.cpp | 100 +-
src/app/qgsattributetabledialog.cpp | 4 +-
src/app/qgscustomization.cpp | 3 +-
src/app/qgsjoindialog.cpp | 17 +-
src/app/qgsjoindialog.h | 2 +
src/app/qgslabelingwidget.cpp | 19 +-
src/app/qgslabelingwidget.h | 3 +
src/app/qgsmaptoolmeasureangle.cpp | 3 +-
src/app/qgsmaptoolselectradius.cpp | 3 +-
src/app/qgsmaptoolselectutils.cpp | 47 +-
src/app/qgsmeasuretool.cpp | 3 +-
src/app/qgsnewspatialitelayerdialog.cpp | 2 +-
src/app/qgsoptions.cpp | 20 +-
src/core/CMakeLists.txt | 6 +-
src/core/composer/qgsaddremoveitemcommand.cpp | 4 +
src/core/composer/qgscomposerarrow.cpp | 23 +-
src/core/composer/qgscomposeritemgroup.cpp | 2 +-
src/core/composer/qgscomposerlegend.cpp | 9 +-
src/core/composer/qgscomposerlegend.h | 2 +-
src/core/composer/qgscomposition.cpp | 66 +-
src/core/composer/qgscomposition.h | 2 +
src/core/composer/qgsgroupungroupitemscommand.cpp | 96 ++
src/core/composer/qgsgroupungroupitemscommand.h | 75 ++
src/core/dxf/qgsdxfexport.cpp | 206 +++-
src/core/dxf/qgsdxfexport.h | 32 +-
src/core/dxf/qgsdxfpallabeling.cpp | 142 +--
src/core/dxf/qgsdxfpallabeling.h | 60 +-
src/core/gps/parse.c | 16 +-
src/core/gps/qgsnmeaconnection.cpp | 2 +-
src/core/qgis.cpp | 6 +
src/core/qgsapplication.cpp | 8 +-
src/core/qgscoordinatereferencesystem.cpp | 14 +
src/core/qgscoordinatetransform.cpp | 2 -
src/core/qgsdataitem.cpp | 2 +
src/core/qgsdataprovider.h | 41 +
src/core/qgsdistancearea.cpp | 8 +-
src/core/qgsexpression.cpp | 9 +-
src/core/qgsfeaturerequest.h | 2 +-
src/core/qgslabelfeature.cpp | 1 -
src/core/qgslegendrenderer.cpp | 8 +-
src/core/qgslogger.cpp | 2 +-
src/core/qgsmaplayer.cpp | 12 +
src/core/qgsmaplayerregistry.cpp | 21 +-
src/core/qgsmaprenderer.h | 19 +-
src/core/qgsmapsettings.h | 4 +-
src/core/qgspallabeling.cpp | 15 +-
src/core/qgspallabeling.h | 10 +-
src/core/qgsrendercontext.cpp | 1 +
src/core/qgsrendercontext.h | 1 +
src/core/qgsrulebasedlabeling.cpp | 16 +-
src/core/qgsrulebasedlabeling.h | 10 +-
src/core/qgsvectorlayer.cpp | 276 +++--
src/core/qgsvectorlayerdiagramprovider.cpp | 2 +-
src/core/qgsvectorlayerfeatureiterator.cpp | 74 +-
src/core/qgsvectorlayerlabelprovider.cpp | 2 +-
src/core/qgsvectorlayerlabelprovider.h | 1 -
src/core/raster/qgscontrastenhancement.cpp | 5 +-
src/core/raster/qgsrasterfilewriter.cpp | 2 +-
src/core/raster/qgsrasterlayer.cpp | 4 +-
src/core/raster/qgsrastershader.cpp | 3 +-
.../raster/qgssinglebandpseudocolorrenderer.cpp | 4 +-
.../qgscategorizedsymbolrendererv2.cpp | 2 +-
src/core/symbology-ng/qgsellipsesymbollayerv2.cpp | 36 +-
src/core/symbology-ng/qgsellipsesymbollayerv2.h | 2 +-
src/core/symbology-ng/qgsfillsymbollayerv2.cpp | 11 +
src/core/symbology-ng/qgsfillsymbollayerv2.h | 11 +-
.../symbology-ng/qgsinvertedpolygonrenderer.cpp | 41 +-
src/core/symbology-ng/qgsinvertedpolygonrenderer.h | 21 +-
src/core/symbology-ng/qgsmarkersymbollayerv2.cpp | 96 +-
src/core/symbology-ng/qgsmarkersymbollayerv2.h | 4 +-
.../symbology-ng/qgspointdisplacementrenderer.cpp | 59 +-
.../symbology-ng/qgspointdisplacementrenderer.h | 15 +-
src/core/symbology-ng/qgsrendererv2.h | 15 +
src/core/symbology-ng/qgssvgcache.cpp | 19 +-
src/core/symbology-ng/qgssymbollayerv2.cpp | 14 +-
src/core/symbology-ng/qgssymbollayerv2.h | 8 +-
src/core/symbology-ng/qgssymbollayerv2registry.cpp | 2 +-
src/core/symbology-ng/qgssymbolv2.cpp | 7 +-
.../symbology-ng/qgsvectorfieldsymbollayer.cpp | 19 +-
src/core/symbology-ng/qgsvectorfieldsymbollayer.h | 3 +
src/customwidgets/qgsextentgroupboxplugin.cpp | 4 +-
src/customwidgets/qgsextentgroupboxplugin.h | 2 +-
src/gui/attributetable/qgsattributetablemodel.cpp | 77 +-
.../editorwidgets/core/qgseditorwidgetregistry.cpp | 12 +
.../editorwidgets/core/qgseditorwidgetregistry.h | 7 +
src/gui/editorwidgets/qgscolorwidgetwrapper.cpp | 9 +-
src/gui/editorwidgets/qgsdatetimeedit.cpp | 2 +-
src/gui/editorwidgets/qgsphotowidgetwrapper.cpp | 37 +
src/gui/editorwidgets/qgsphotowidgetwrapper.h | 2 +
src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp | 2 +
src/gui/qgsattributeform.cpp | 7 +-
src/gui/qgscodeeditor.h | 4 +-
src/gui/qgscollapsiblegroupbox.cpp | 4 +-
src/gui/qgscolorbuttonv2.cpp | 4 +-
src/gui/qgscomposerview.cpp | 17 +-
src/gui/qgscomposerview.h | 7 +
src/gui/qgsexternalresourcewidget.cpp | 17 +-
src/gui/qgsmapcanvas.cpp | 15 +-
src/gui/qgsmessagelogviewer.cpp | 3 +-
src/gui/qgspixmaplabel.cpp | 16 +-
src/gui/qgsrasterlayersaveasdialog.cpp | 40 +-
src/gui/qgsrelationeditorwidget.cpp | 8 +
src/gui/qgsrubberband.cpp | 20 +
src/gui/qgsrubberband.h | 8 +
src/gui/symbology-ng/qgs25drendererwidget.cpp | 19 +-
src/plugins/georeferencer/qgsgeoreftransform.cpp | 7 +-
src/plugins/gps_importer/qgsgpsplugin.cpp | 2 +-
.../offline_editing/offline_editing_plugin_gui.cpp | 2 +-
src/plugins/roadgraph/shortestpathwidget.cpp | 2 +-
.../qgsdelimitedtextfeatureiterator.cpp | 2 +-
src/providers/memory/qgsmemoryprovider.cpp | 1 +
src/providers/mssql/qgsmssqlprovider.cpp | 4 +-
src/providers/ogr/qgsogrfeatureiterator.cpp | 23 +-
src/providers/ogr/qgsogrprovider.cpp | 310 ++++-
src/providers/ogr/qgsogrprovider.h | 33 +-
src/providers/oracle/qgsoracleconn.cpp | 5 +-
src/providers/oracle/qgsoracledataitems.cpp | 111 +-
src/providers/oracle/qgsoracledataitems.h | 1 +
.../oracle/qgsoracleexpressioncompiler.cpp | 30 +-
src/providers/oracle/qgsoraclefeatureiterator.cpp | 99 +-
src/providers/oracle/qgsoraclefeatureiterator.h | 3 +-
src/providers/oracle/qgsoraclenewconnection.cpp | 2 +
src/providers/oracle/qgsoracleprovider.cpp | 112 +-
src/providers/oracle/qgsoracleprovider.h | 3 +
src/providers/postgres/qgspostgresconn.cpp | 37 +-
.../postgres/qgspostgresfeatureiterator.cpp | 2 +-
src/providers/spatialite/qgsspatialiteconnection.h | 16 +-
src/providers/spatialite/qgsspatialiteconnpool.h | 8 +-
.../spatialite/qgsspatialitefeatureiterator.cpp | 2 +-
src/providers/spatialite/qgsspatialiteprovider.cpp | 34 +-
src/providers/spatialite/qgsspatialiteprovider.h | 3 +
src/providers/wms/qgswmsprovider.cpp | 43 +-
src/python/qgspythonutilsimpl.cpp | 2 +-
src/server/qgsmslayercache.cpp | 6 +
src/server/qgsserver.cpp | 7 +
.../effects/qgseffectstackpropertieswidgetbase.ui | 4 +-
src/ui/qgscustomizationdialogbase.ui | 2 +-
src/ui/qgsoraclenewconnectionbase.ui | 28 +-
src/ui/qgsrasterlayersaveasdialogbase.ui | 20 +-
tests/src/app/CMakeLists.txt | 1 +
tests/src/app/testqgisapppython.cpp | 97 ++
tests/src/app/testqgsmaptoolidentifyaction.cpp | 137 +++
tests/src/core/testqgscomposergroup.cpp | 273 ++++-
tests/src/core/testqgscomposerpicture.cpp | 18 +
.../src/core/testqgscoordinatereferencesystem.cpp | 7 +
tests/src/core/testqgsdistancearea.cpp | 24 +
tests/src/core/testqgsexpression.cpp | 18 +
tests/src/core/testqgsgeometry.cpp | 6 +-
tests/src/core/testqgslabelingenginev2.cpp | 5 +-
tests/src/gui/testqgsrubberband.cpp | 29 +
tests/src/python/CMakeLists.txt | 8 +
tests/src/python/providertestbase.py | 70 +-
tests/src/python/test_provider_ogr.py | 175 +++
tests/src/python/test_provider_oracle.py | 107 ++
tests/src/python/test_provider_shapefile.py | 198 +++-
tests/src/python/test_provider_spatialite.py | 84 ++
tests/src/python/test_provider_tabfile.py | 28 +-
tests/src/python/test_qgscolorbuttonv2.py | 43 +
tests/src/python/test_qgscomposerview.py | 65 ++
tests/src/python/test_qgsexpression.py | 20 +-
tests/src/python/test_qgsfeatureiterator.py | 50 +-
tests/src/python/test_qgssymbollayerv2.py | 14 +
tests/src/python/test_qgsvectorlayer.py | 43 +-
.../expected_composerpicture_issue_14644.png | Bin 0 -> 33704 bytes
.../expected_composerpicture_issue_14644_mask.png | Bin 0 -> 13448 bytes
.../expected_legend_basic_mask.png | Bin 19044 -> 19941 bytes
.../expected_legend_big_marker_mask.png | Bin 20496 -> 21900 bytes
.../expected_legend_filter_by_expression.png | Bin 9073 -> 6893 bytes
.../expected_legend_filter_by_expression_mask.png | Bin 12134 -> 5909 bytes
.../expected_legend_filter_by_map.png | Bin 10637 -> 7965 bytes
.../expected_legend_filter_by_map_mask.png | Bin 13766 -> 8402 bytes
.../expected_legend_filter_by_map_dupe_mask.png | Bin 2873 -> 2922 bytes
.../expected_legend_filter_by_polygon.png | Bin 9073 -> 6893 bytes
.../expected_legend_filter_by_polygon_mask.png | Bin 12134 -> 6538 bytes
.../expected_legend_long_symbol_text_mask.png | Bin 23507 -> 24351 bytes
.../expected_legend_mapunits_mask.png | Bin 2192 -> 2698 bytes
.../expected_legend_raster_border_mask.png | Bin 1014 -> 1049 bytes
.../expected_legend_three_columns_mask.png | Bin 19184 -> 20020 bytes
tests/testdata/elev.gpx | 15 +
tests/testdata/noelev.gpx | 12 +
tests/testdata/provider/testdata_oracle.sql | 32 +
tests/testdata/raster/test.asc | 6 +
tests/testdata/svg/issue_14644.svg | 194 ++++
272 files changed, 6209 insertions(+), 1525 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6e16eac..afccf48 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
SET(CPACK_PACKAGE_VERSION_MAJOR "2")
SET(CPACK_PACKAGE_VERSION_MINOR "14")
-SET(CPACK_PACKAGE_VERSION_PATCH "3")
+SET(CPACK_PACKAGE_VERSION_PATCH "4")
SET(COMPLETE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH})
SET(RELEASE_NAME "Essen")
IF (POLICY CMP0048) # in CMake 3.0.0+
@@ -150,6 +150,11 @@ IF (MSVC AND CMAKE_GENERATOR MATCHES "NMake")
SET (USING_NMAKE TRUE)
ENDIF (MSVC AND CMAKE_GENERATOR MATCHES "NMake")
+IF (CMAKE_GENERATOR MATCHES "Ninja")
+ # following variable is also used in qgsconfig.h
+ SET (USING_NINJA TRUE)
+ENDIF (CMAKE_GENERATOR MATCHES "Ninja")
+
#############################################################
# check if lexer and parser are not missing
# http://www.mail-archive.com/cmake@cmake.org/msg02861.html
@@ -393,9 +398,9 @@ IF (PEDANTIC)
MESSAGE (STATUS "Pedantic compiler settings enabled")
IF(MSVC)
SET(_warnings "")
- IF (NOT USING_NMAKE)
+ IF (NOT USING_NMAKE AND NOT USING_NINJA)
SET(_warnings "${_warnings} /W4" )
- ENDIF (NOT USING_NMAKE)
+ ENDIF (NOT USING_NMAKE AND NOT USING_NINJA)
# disable warnings
SET(_warnings "${_warnings} /wd4100 ") # unused formal parameters
@@ -502,10 +507,10 @@ IF (WIN32)
ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_WARNINGS)
IF (CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
- IF (NOT USING_NMAKE)
+ IF (NOT USING_NMAKE AND NOT USING_NINJA)
MESSAGE (STATUS "Generating browse files")
ADD_DEFINITIONS( /FR )
- ENDIF (NOT USING_NMAKE)
+ ENDIF (NOT USING_NMAKE AND NOT USING_NINJA)
ENDIF (CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
IF (INSTALL_DEPS)
@@ -740,9 +745,15 @@ IF (EXISTS ${CMAKE_SOURCE_DIR}/.git/index)
FIND_PROGRAM(GITCOMMAND git PATHS c:/cygwin/bin)
IF(GITCOMMAND)
IF(WIN32)
+ IF(USING_NINJA)
+ SET(ARG %a)
+ ELSE(USING_NINJA)
+ SET(ARG %%a)
+ ENDIF(USING_NINJA)
ADD_CUSTOM_COMMAND(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/qgsversion.h
- COMMAND for /f \"usebackq tokens=1\" %%a in "(`\"${GITCOMMAND}\" log -n1 --oneline`)" do echo \#define QGSVERSION \"%%a\" >${CMAKE_CURRENT_BINARY_DIR}/qgsversion.h.temp
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/qgsversion.h ${CMAKE_CURRENT_BINARY_DIR}/qgsversion.inc
+ COMMAND for /f \"usebackq tokens=1\" ${ARG} in "(`\"${GITCOMMAND}\" log -n1 --oneline`)" do echo \#define QGSVERSION \"${ARG}\" >${CMAKE_CURRENT_BINARY_DIR}/qgsversion.h.temp
+ COMMAND for /f \"usebackq tokens=1\" ${ARG} in "(`\"${GITCOMMAND}\" log -n1 --oneline`)" do echo PROJECT_NUMBER = \"${COMPLETE_VERSION}-${RELEASE_NAME} \(${ARG}\)\" >${CMAKE_CURRENT_BINARY_DIR}/qgsversion.inc
COMMAND ${CMAKE_COMMAND} -DSRC=${CMAKE_CURRENT_BINARY_DIR}/qgsversion.h.temp -DDST=${CMAKE_CURRENT_BINARY_DIR}/qgsversion.h -P ${CMAKE_SOURCE_DIR}/cmake/CopyIfChanged.cmake
MAIN_DEPENDENCY ${CMAKE_SOURCE_DIR}/.git/index
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
diff --git a/ChangeLog b/ChangeLog
index 9760ef2..9c2bfa2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,1189 @@
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-08
+
+ Fix build
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-08
+
+ Fix incorrect label/diagram distance when map is rotated
+
+ (Cherry-picked from 873eb7f90a271970a904b5d4a37740f91b24f941)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-07-08
+
+ [roadgraph] fix invalid characters in message (fix #15233)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-07
+
+ [oracle] Fix handling of date/time types
+
+ Also add test for Oracle default values
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from f9d839fac5569d5ceb743643873a08a124ddf101)
+
+nirvn <nirvn.asia at gmail.com> 2016-06-12
+
+ fix crash when right-clicking on geometryless layers
+
+ (cherry picked from commit 054604bc709129efbc05098f09cacd8f0204efe9)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ Fix build
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Implement provider test suite
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from 38e65c3f75bd57c16b37cff95137337db032014b)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Fix feature request when expression compilation fails,
+ fix incorrect provider side use of limit when expression compilation
+ could not be used
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from d089cbaaa6a51ca54414c7ea817539bb51862c72)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Always keep geometry when fetched, as it may have been
+ requested for filter expressions or sorting
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from 369e130d48ee116916ab0ef8be7fa04406f11a34)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Fixes for oracle expression compilation
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from 64bfbaaf5b44153f068a5f2d140e1abe4d4b83f5)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Fix broken iterator rewind
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from d785b904ac51ebeb2d004da78bb7840e27e426a2)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Fix detection of geometry type when table contains some
+ empty geometries
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from fe93e6217548acb4710a63f94c4c18070f86811f)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Fix missing items in oracle connections in browser
+
+ On behalf of Faunalia, sponsored by ENEL
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Don't report import failures when user has cancelled import
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from a31a1d3f13101a906de88bd71981c61526ccb1ae)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-06
+
+ [oracle] Synchronise wording and behaviour of browser actions
+ with postgres provider
+
+ On behalf of Faunalia, sponsored by ENEL
+
+ (cherry-picked from 82be399fcb2cfdcd64c41039f1a50f9c89c0380a)
+
+Marco Hugentobler <marco.hugentobler at sourcepole.ch> 2015-12-17
+
+ Legend: leave away empty groups (fix #12969)
+
+ (cherry-picked from c78347)
+
+Martin Dobias <wonder.sk at gmail.com> 2016-07-05
+
+ Fix recording of points for live GPS tracking (fixes #14996)
+
+ (cherry picked from commit 1cb4adc0845744f92214842299722345c7c99859)
+
+rldhont <rldhont at gmail.com> 2016-06-23
+
+ [BUGFIX] QgsMapLayerRegistry: Check layers before removed
+
+ Probably fixed #15088 Segmentation fault when using layersRemoved SIGNAL
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-02
+
+ Fix margins on photo and web view edit widgets
+
+ (cherry-picked from ece46d1c43df31d5688404e3c4ff5ee016c307c1)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-02
+
+ Various fixes for photo editor and external resource widgets
+
+ - Clear picture when changing from a feature with a picture to
+ a feature with no picture set
+ - Don't try to load "NULL" as a filename
+ - Fix calculation of widget size so that widget can collapse
+ its size to nothing when it doesn't have a picture set
+ - Avoid incorrect scaling and cropping of pictures
+
+ (cherry-picked from 0c161950a9974aa1cf165ed6151ff854c924665a)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-02
+
+ Fix Travis build, take 2
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-02
+
+ Fix travis build
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-02
+
+ Fix test
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [ogr] Read GPX elevation values as geometry Z values
+
+ (cherry-picked from 4080aad0eef5d8ca78fdab2e3c4e3ce0815e6610)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix cannot set line symbol data defined properties for vector
+ field marker (refs #15131)
+
+ (cherry-picked from 6d6aa8dd271f8990ceb713e3deb4cb57cca6db99)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix vector field symbol does not use subsymbol color (fix #15130)
+
+ (cherry-picked from e6034e9a1ed69f6e345a7a2dc7e42754107896a3)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Capitalisation
+
+ (cherry-picked from 302f8d418f8bc0e228d8897fbbd4f65895430047)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Make measure dialog only stay on top of QGIS window (fix #12261)
+
+ (cherry-picked from 340a6f654b5803f21a89ccbc22950cca80f4331b)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix displacement renderer when using map unit sizes for symbols
+
+ (cherry-picked from 8868303dbd4fad26eeb88bc5a18ba3ffe5a0e719)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix displacement symbols with data defined properties (fix #9601)
+
+ Previously only the attributes of the first feature were being
+ used to render the points inside a group
+
+ (cherry-picked from a6f96ba51b736cc97b3d8d6a848940737a46a6bc)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [composer] Instant feedback for legend when linked map changes
+
+ (cherry-picked from f9ff5e25d3eb1a2df52d5460e2ae8944aaa6418f)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [composer] Double click legend item to edit text (fix #13578)
+
+ (cherry-picked from dbf8d89459fb37921c1ce0ddb36ef5c1dd4a608d)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Followup 71dc33, fix projective transform in georeferencer (fix #14551)
+
+ (cherry-picked from 83160632ace41ef0306c903d977ef27283f83dcb)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix selecting features by radius (fix #14748)
+
+ (cherry-picked from 33a5ee7ab47c8c6db2985ef755ef6bdca23b36a0)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix incorrect area calculation for polygon (fix #14675)
+
+ (cherry-picked from bf4cf51e1a259252d5a762845f20aaac12d672bf)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Save raster symbology with full precision (fix #14950)
+
+ (cherry-picked from a6cb81bf609196677118a87c468489ba58583bae)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-06-14
+
+ Remove "attribute table" from dialog title (fix #14959)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix actions are not enabled when loading layer with default style (fix #13910)
+
+ (cherry-picked from 1563526f0dc59ae2b1fdc4a58c07488c5603de3d)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix cannot deactivate customization widget catcher (fix #9732)
+
+ (cherry-picked from e76571959431b5c9b96862578f8485dbfd203521)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [composer] Make parameterised SVG arrow heads respect colors (fix #14997)
+
+ (cherry-picked from 78c434a6c4fbdb6004c725abe0121dc4dd6dd8dc)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix debug noise when using 25D geometry layers
+
+ (cherry-picked from 92830a25090aae54d9b01b56bfe31f48a5fed71f)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Classifications on joined fields should only consider values which
+ are matched to layer's features (fix #9051)
+
+ (cherry-picked from 16eb1e14d0ba2b78f2d12a0813887a7bc493204c)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Prevent creating invalid join in add join dialog (fixes crash)
+
+ (cherry-picked from 693ead28bbb8f7837d8d211c1472eb32069084d9)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Update test import
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix virtual fields which depend on other virtual fields may not be
+ calculated in some circumstances (fix #14939)
+
+ (cherry-picked from df0d5969aa8e3bf0ec653bb6d6395afe1cf58950)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Default to requesting all attributes for python expression functions
+
+ Fix #14985
+
+ (cherry-picked from 1bc17e6c4f40ab64a7d3443886e13f926dab23b7)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Always use string comparison in expressions for string fields
+
+ Fixes #13204
+
+ (cherry-picked from 8a2e8715fb3ffa21ecbd3d38310b04257029f656)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix issues rendering SVG with text (fix #14644, #14794)
+
+ (cherry-picked from 2265115f8003857e538f07287c1337fed463a39c)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix home dir being added as default svg and template path
+
+ Fixes #14662, #14652, #14883
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Add missing proxies for legend check behaviour to subrenderers
+ for inverted polygon and displacement renderers
+
+ (cherry-picked from 7a8d9dd50654a27c4ecaaccfd66974dded6aaaa9)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix inverted polygons and displacement renderer don't allow right
+ click on legend items (fix #14966)
+
+ (cherry-picked from b2c43cb99715194c79dd011656f372edb8745a77)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix inverted polygon & displacement renderer don't show
+ color wheel in legend menu
+
+ Move embedded renderer handling to QgsFeatureRendererV2 and
+ add support for embedded renders to legend context menu
+
+ Fix #14967
+
+ (cherry-picked from b32afce79d473738716834216d25eea9783ce619)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [effect] fix issue with svg marker and antialiasing (fixes #14960)
+
+ Credit for original patch to @nirvn
+
+ (cherry-picked from 179a92cd65a70a411c8085875ab3e20bf5fa5d46)
+
+Sandro Mani <manisandro at gmail.com> 2015-12-15
+
+ Only emit QgsCollapsibleGroupBoxBasic::collapsedStateChanged when state really has changed
+
+Sandro Mani <manisandro at gmail.com> 2016-05-12
+
+ Fix avoid crash when measuring empty geometry
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Add QgsDistanceArea test for empty polygon
+
+ (cherry-picked from ebdccf3869e8ccf1e495b7e5ba6119d94fec4980)
+
+Marco Hugentobler <marco.hugentobler at sourcepole.ch> 2016-02-02
+
+ Fix crash when saving categorized symbology
+
+Sandro Mani <manisandro at gmail.com> 2016-03-14
+
+ Fix QgsCurvePolygon sip bindings
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [processing] fix missing quotes to field name in refactor fields
+
+ (cherry-picked from 14342ce65a84c1efa377862eeac085642736ed1b)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Set cursor to pan cursor when space-dragging canvas
+
+ (cherry-picked from 79d640715e5a5bf0d4e5e1f0d9d43becc3990502)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [composer] Prevent zooming out/in too far
+
+ Would cause issues when scale became 0 and it was impossible
+ to further interact with the composer.
+
+ (cherry-picked from aa53cfe3871a149371059b2c92ddf4233576e00f)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Fix incorrect tooltips in effects widget
+
+ (cherry-picked from e2abea671c052ffdce98474dacb9e3d3f287eb8e)
+
+ThomasG77 <thomas_gratier at yahoo.fr> 2016-05-26
+
+ Cast each child of QgsAnnotationItem in SIP bindings for Python
+
+ (cherry-picked from eeea18ecba2ecce8c57d4539d00768821dd1e9e7)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Add some tooltips to relation subform buttons
+
+ (cherry-picked from fff938c5d9d3b4e213ce37334dfbc180591f8266)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ [gui] improve no color logic by keeping RGB values
+
+ (cherry-picked from 64823d10c57e7769aa27cbfa273a5f9496e528f5)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Add test for color button that setting color to transparent
+ will retain non-alpha color components
+
+ (cherry-picked from 4089cc52266aab0e41b83546ee33c22040c1f417)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-07-01
+
+ Always mark features as valid when added to memory provider
+
+ For other providers features will automatically be made valid
+ through the proces of adding to the provider's storage itself
+ and then later retrieving via iterators. But the memory
+ provider uses a direct copy of the feature, so if we don't
+ explicitly mark features as valid the provider may be
+ returning features incorrectly marked as invalid.
+
+ ...and add tests to ensure all providers always return valid
+ features
+
+ (cherry-picked from f2b70cf5e27c7de920ea3b54893019fb3f450cf6)
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-30
+
+ Indentation fix
+
+Merge: c237ba7 a3dfd9d
+Even Rouault <even.rouault at spatialys.com> 2016-06-30
+
+ Merge branch 'release-2_14' of github.com:qgis/QGIS into release-2_14
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-30
+
+ [Spatialite provider] Make sure to release dangling connections on provider closing
+
+ Fixes #15137
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-30
+
+ [OGR provider] Make sure to release dangling connections on provider closing
+
+ Fixes #15137
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-30
+
+ mssql provider: make mapping of import fields case-insensitive
+
+ (cherry picked from commit 6f7a999ebf3bdf1561cfce49e4908a23adf958b3)
+
+Martin Dobias <wonder.sk at gmail.com> 2016-06-30
+
+ Return zero from "points in polygons" instead of both zero/null (fixes #15158)
+
+Sandro Santilli <strk at kbt.io> 2016-06-28
+
+ Only insert segment snap points in the layer they belong
+
+ Fixes #13952
+
+aharfoot <aharfoot at users.noreply.github.com> 2016-06-24
+
+ Fix bug in GDALTools Assign Projection
+
+ Assign Projection uses gdalwarp, and this works correctly when a raster has no CRS assigned, however, in the case of a raster with an incorrect CRS assignment, then gdalwarp will reproject the raster instead of simply changing the assigned CRS, propagating the error. Switching the Assign Projection tool to use gdal_translate provides the intended behaviour in all situations.
+ (cherry picked from commit bb8156836203b4dde2a6bb8c3cef69a3263871df)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-29
+
+ add objectName to button
+
+ (cherry picked from commit 00832918ffa110b77036f8581b56dea62e7434d6)
+
+Tudor Bărăscu <tudor.barascu at qtibia.ro> 2016-06-29
+
+ Show cannot pan WARNING for NULL geometry (#3255)
+
+ refs [#15122](https://hub.qgis.org/issues/15122)
+
+rldhont <rldhont at gmail.com> 2016-06-28
+
+ [BUGFIX] QgsMSLayerCache: remove layer from QgsMapLayerRegistry before delete it
+
+ In QGIS Server, layers can be added to QgsMapLayerRegistry and delete by QgsMSLayerCache. This means that QgsMapLayerRegistry can have reference to deleted pointers.
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-27
+
+ QgsCoordinateTransform::transformCoords(): do not convert elevations to radians
+
+ Fixes #14702
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-28
+
+ [processing] add missed variable initialization (fix #15154)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-28
+
+ Fix debug output (followup a7dcaad)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-27
+
+ [processing] support more field types
+
+ (cherry picked from commit 1b60b088a22ce2b417ec8a2c69266b98efa663f2)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-24
+
+ don't apply raster style to vectors (fix #15001)
+
+ (cherry picked from commit 3ece8aca11f242a71be7a8775ae1109028c8ad3f)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-27
+
+ dxf export: complete doxymentation updates (followup 07113b0)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-27
+
+ dxf export: merge doxymentation updates (followup 4c4ad05)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-27
+
+ dxf export: more adaptions to labeling changes (backported from b3bf4a1)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-27
+
+ [processing] drop WebView dependency (backported from cc7eb27)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-23
+
+ oracle provider: by default skip additional geometry columns (on 64bit
+ Windows/Linux OCI crashes when there are more than three geometry
+ columns)
+
+ (cherry picked from commit 4b00182482a2e897c8318fe4b46fa7d5dcc8ac6d)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-22
+
+ db manager: re-enable margins with line numbers in sql editor
+ (fixes #15110)
+
+ (cherry picked from commit d9f934f9508b6388f8245fe695bab0b7cc649c1e)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-22
+
+ selection by polygon: use 40 instead of 4 points for selection rectangle for more accurate transformation (fixes #13754)
+
+ (cherry picked from commit 0a83f182f3e8aea684163096f70efc9f38399ad2)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-22
+
+ save as raster: fix vrt creation (fixes #14171)
+
+ (cherry picked from commit d69ec2e8bb1c3a4b036f063454f27dff60c4e643)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-21
+
+ postgres provider: allow database without postgis (fixes #6891)
+
+ (cherry picked from commit 94413b35a08a154e5b40c2a9cab13fcb0d0ee4a4)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-20
+
+ db manager: show database name in postgis connection details (fixes #3489)
+
+ (cherry picked from commit ecf3b3719d4f24dc7f369d3253150c4990c9c681)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-21
+
+ gdal tools: use native file dialogs (fixes #5500)
+
+ (cherry picked from commit 2c112f96cab55eed2f3922dbf2dd14522eac5aee)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-14
+
+ open message log on QgisApp::openMessageLog instead of toggling it
+
+ (cherry picked from commit 5bb2c7d175c9113b844738f18c38c00ecc76c6bc)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-14
+
+ update 'Report an issue' link
+
+ (cherry picked from commit 0db9556b642ce855548d3be6501e4d9935a62e0a)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-07
+
+ avoid closing the last tab of the message log viewer
+
+ (cherry picked from commit ee8e340d6e84aa91b7b4813e2c0668cc2a6fd5f6)
+
+Juergen E. Fischer <jef at norbit.de> 2016-04-12
+
+ dxf export: add support for expression contexts and rotated symbols (fixes #14495)
+
+ (cherry picked from commit c30f71ac73796bb2a39ff0fd4f2349b9fde222ac)
+
+Juergen E. Fischer <jef at norbit.de> 2016-04-11
+
+ dxf export: support rule based labeling (fixes #13757)
+
+ (cherry picked from commit f19a35c34e758ed17704c77a7ee334bf866bd46f)
+
+Juergen E. Fischer <jef at norbit.de> 2016-04-03
+
+ extent group box: fix header spelling
+
+ (cherry picked from commit 59de73ac3ba2c0c56ba7bf7412bafad8e739ac4e)
+
+Juergen E. Fischer <jef at norbit.de> 2016-03-30
+
+ oracle provider: also try sdo_filter on queries
+
+ (cherry picked from commit 1cc82af899302107232832a4a31e1c782136c07c)
+
+Juergen E. Fischer <jef at norbit.de> 2016-03-24
+
+ don't strip utf8 in log
+
+Matthias Kuhn <matthias at opengis.ch> 2016-06-26
+
+ Don't lock canvas when trying to pan to null geometry
+
+ Fix #15122
+
+Matthias Kuhn <matthias at opengis.ch> 2016-06-26
+
+ QgsMapLayerRegistry::removeMapLayers don't emit signals when empty
+
+ Fix #15088
+
+Richard Duivenvoorde <richard at duif.net> 2016-06-21
+
+ Adding &TRANSPARENT=true makes too big legend images look good
+
+ See http://hub.qgis.org/issues/15089 for screenshots & test service url
+
+Richard Duivenvoorde <richard at duif.net> 2016-06-17
+
+ WMS GetLegendGraphic fix #15055
+
+ See http://hub.qgis.org/issues/15055
+
+ When creating the legend image url, this tests for available queryparams
+ in a case-insensitive way...
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-24
+
+ [processing] add support for longlong fields in spatial join alg (fix #15072)
+
+ (cherry picked from commit 87fea73647a2319aaa3c110cb26967f7f217d7f4)
+
+Merge: a419515 e94c24d
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-24
+
+ Merge pull request #3183 from DHI-GRAS/release-2_14
+
+ [processing] fixes to GrassUtils and Grass7Utils (mostly cherry-picked jef-n)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-23
+
+ use QgsWKBTypes to check layer wkb type (follow up 904dc21625)
+
+ (cherry picked from commit e6970ba597a778afe47b551a6999f5305450f52b)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-23
+
+ support 25D layers in network analysis library (fix #11952)
+
+ (cherry picked from commit 904dc216251d183305ba1c5dc6c846be10a879db)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-23
+
+ fix signal-slot connection in New SpatiaLite layer dialog (fix #14343)
+
+ (cherry picked from commit 70b9296f371a057ab90f64a6edc39f482a26f21d)
+
+Sandro Santilli <strk at kbt.io> 2016-06-22
+
+ Fix comment for precision loss
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-22
+
+ [processing] replace original layer name with exported in the final OGR command (fix #15099)
+
+ (cherry picked from commit c81b14d59ec578bf678cab2c71b821de574ac0c3)
+
+volaya <volayaf at gmail.com> 2016-05-28
+
+ [processing] added ‘supported’ parameter to exportVectorLayer
+
+ (cherry picked from commit 9c2721b08b02641ab4c61f97f710aa6347b94c15)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-22
+
+ [processing] fix gdal_contour algorithm
+
+ (cherry picked from commit e4c1d896e97952743ea1c0c2144e33983fa5706a)
+
+ Conflicts:
+python/plugins/processing/algs/gdal/GdalUtils.py
+
+Sandro Santilli <strk at kbt.io> 2016-06-22
+
+ Port new MapToolIdentify tests from master
+
+ Includes the test for identifying invalid polygons showing
+ (still passing as of 2.14) - see #13635
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-21
+
+ [GDALTools] pass output format to gdal_contour (fix #6695)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-21
+
+ [processing] speedup Hub distance algorithm (fix #15012)
+
+ (cherry picked from commit e0c9733f6482f184aeeff1339fafef210d1a0709)
+
+ Conflicts:
+python/plugins/processing/algs/qgis/HubDistance.py
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-21
+
+ [processing] use bulk features loading to speedup spatial index creation
+
+ (cherry picked from commit 2d9b2a354b01e29b8fe39aea02829d892880b438)
+
+Matteo <matteo.ghetta at gmail.com> 2016-06-20
+
+ Small fix in write.csv option (#3225)
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-20
+
+ [OGR provider] Make changeGeometryValues() accept null geometry
+
+ Fixes #15081
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-20
+
+ QgsCoordinateReferenceSystem::setProj4String(): harden validation
+
+ OSRImportFromProj4() may accept strings that are not valid proj.4 strings,
+ e.g if they lack a +ellps parameter, it will automatically add +ellps=WGS84, but as
+ we use the original mProj4 with QgsCoordinateTransform, it will fail to initialize
+ so better detect it now.
+
+ (cherry-picked and adapted from master 85128c54191cfedeaee04ca9c4ac0341ab8f5088)
+
+ Fixes #14844
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-18
+
+ [DXF export] Replace newline character in layer name by underscore
+
+ Fixes #15067
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-18
+
+ [OGR provider] Do not return wkbUnknown25D, wkbUnknownM/Z/ZM layer geometry types
+
+ Those are illegal QgsWKBTypes::Type / QGis::WkbType values, and can cause
+ undefined behaviour outside of the provider.
+
+ Fixes #15064
+
+Sandro Santilli <strk at kbt.io> 2016-06-09
+
+ Fix crash in composer on ungrouping after group move.
+
+ Closes #11371.
+
+ Adds support for undo/redo grouping/ungrouping operations.
+
+ Enable pending test for the crash (now passing) and add many more
+ undo/redo related ones (including signals testing).
+
+ Includes a new QgsGroupUngroupItemsCommand class
+ and its SIP bindings.
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-06-17
+
+ Fix Polygon Centroids tool hangs on null geometry (fix #15045)
+
+Larry Shaffer <lshaffer at boundlessgeo.com> 2016-06-15
+
+ Fix file write error when offline.sqlite is saved to root dir by default
+
+ - Default to user's home directory instead
+
+ (cherry-picked from 23a3273)
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-14
+
+ don't save extents to style (fixes #15026, followup 92aed6e and d690d72)
+
+Merge: 671850f 4fb3a24
+Nyall Dawson <nyall.dawson at gmail.com> 2016-06-14
+
+ Merge pull request #3197 from DelazJ/patch-9
+
+ fix button label
+
+Harrissou Sant-anna <delazj at gmail.com> 2016-06-13
+
+ fix button label
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-13
+
+ Fix sorting in attribute table
+
+ Regression introduced with 3ec3daeb14a047c3f4efdd8646a51b33661c28ce (2.14.3) where
+ QgsAttributeTableModel::data(index, SortRole) returned an empty variant.
+
+ Fixes #14927
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-13
+
+ [Spatialite provider] prefer rowid as primary key where available
+
+ Adapted from 1050174532627ae44c4467e9911c1fca41e138e3 without the cleanups.
+
+ fixes #14575, fixes #14626, fixes #14999
+
+Even Rouault <even.rouault at spatialys.com> 2016-06-13
+
+ [WMS provider] Avoid excessive number of decimals in BBOX parameter
+
+ Fix #14928
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-06-13
+
+ [processing] allow 2.5D geometries (fix #14929)
+
+ (cherry picked from commit 0553f7b33b4a3294f9a1cfb24e8c238f9211503d)
+
+ Conflicts:
+python/plugins/processing/algs/qgis/Clip.py
+python/plugins/processing/algs/qgis/Difference.py
+python/plugins/processing/algs/qgis/Intersection.py
+python/plugins/processing/algs/qgis/SymmetricalDifference.py
+python/plugins/processing/algs/qgis/Union.py
+
+rldhont <rldhont at gmail.com> 2016-06-10
+
+ [BUGFIX] Emit layerWillBeRemoved like layersWillBeRemoved (#3194)
+
+ The signal layerWillBeremoved is only emitted when the layer is owned by QgsMapLayerRegistry.
+
+ To fix it just move the emitted layerWilBeRemoved out of the scope of layers owned by QgsMapLayerRegistry.
+
+rldhont <rldhont at gmail.com> 2016-06-09
+
+ QgsEditorWidgetRegistry disconnect signals when mapLayer will be removed (#3186)
+
+ Add a method to disconnect signals added when layerIsAdded to QgsMapLayerRegistry. This method is connect to layerWillBeRemoved.
+
+ This method will probably fix an issue in QGIS Server about performance deterioration.
+ Each time a layer is added to QgsMapLayerRegistry by QGIS Server, QgsEditorWidgetRegistry connects to appropriate signals from map layers to load and save style but NEVER disconnects its.
+
+Jonas <josl at dhi-gras.com> 2016-06-08
+
+ fix to grassWinShell
+
+Juergen E. Fischer <jef at norbit.de> 2016-03-31
+
+ really fix ab5f06b (ouch again - machine mixup)
+
+Juergen E. Fischer <jef at norbit.de> 2016-03-31
+
+ fix ab5f06b (ouch)
+
+Juergen E. Fischer <jef at norbit.de> 2016-03-31
+
+ processing: base grass path on OSGEO4W_ROOT where available
+
+Larry Shaffer <lshaffer at boundlessgeo.com> 2016-06-08
+
+ Fix indentation test errors
+
+Ondřej Fibich <ondrej.fibich at gmail.com> 2016-03-04
+
+ Adds support for GNSS GNRMC messages
+
+ (cherry-picked from 88bddb8)
+
+Merge: 446daaa 50181ee
+rldhont <rldhont at gmail.com> 2016-06-07
+
+ Merge pull request #3181 from dmarteau/release-2_14
+
+ Clean up QgsExpressionContext in QgsServer::handleRequest
+
+David Marteau <david at innophi.com> 2016-06-06
+
+ Clean up QgsExpressionContext in QgsServer::handleRequest
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-07
+
+ fix windows build (followup 4648143c)
+
+Matthias Kuhn <matthias at opengis.ch> 2016-06-06
+
+ [gps] Fix default-misconfigured gpsbabel path
+
+ Fix #14866
+
+rldhont <rldhont at gmail.com> 2016-06-06
+
+ [BUGFIX][Processing] RScript: Insert None value as NULL
+
+rldhont <rldhont at gmail.com> 2016-06-06
+
+ [BUGFIX][Processing] RScript: Add name token
+
+Matthias Kuhn <matthias at opengis.ch> 2016-06-06
+
+ Fix crash when using 2.5D renderer with incompatible layer
+
+ Fixes #14814
+
+Nathan Woodrow <nathan_woodrow at technologyonecorp.com> 2016-05-24
+
+ More ninja changes
+
+Matthias Kuhn <matthias at opengis.ch> 2016-05-23
+
+ [build] Allow using ninja generator on non-win os'es
+
+Nathan Woodrow <nathan_woodrow at technologyonecorp.com> 2016-05-23
+
+ [build] Add better support for Ninja build system
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-01
+
+ fix recommends (followup ddcc2fb)
+
+Juergen E. Fischer <jef at norbit.de> 2016-05-31
+
+ debian packaging: disable globe plugin where osgearth >= 2.7
+
+Juergen E. Fischer <jef at norbit.de> 2016-06-01
+
+ osgeo4w: disable globe plugin (incompatible with OSGEarth 2.7)
+
+Juergen E. Fischer <jef at norbit.de> 2016-05-17
+
+ creatensis.pl: retrieve version earlier from CMakeLists.txt for
+ preremove
+
+Salvatore Larosa <lrssvtml at gmail.com> 2016-05-31
+
+ [processing] add again the algorithm name after being removed mistakenly in 507aeb0
+
+ (cherry-picked from 83502c5)
+
+rldhont <rldhont at gmail.com> 2016-05-31
+
+ [Processing] Add optional capabilities to R scripts
+
+rldhont <rldhont at gmail.com> 2016-05-31
+
+ [BUGFIX][Processing] R: Extent from raster package is "xmin, xmax, ymin, ymax"
+
+ Extent from raster package is like in Processing
+ http://www.inside-r.org/packages/cran/raster/docs/Extent
+
+rldhont <rldhont at gmail.com> 2016-05-31
+
+ [Processing] Fix getParameterDescriptions
+
+ Add import json for script and r
+ return descs and not None
+
+Merge: 7d7467f 8fafd3d
+Even Rouault <even.rouault at mines-paris.org> 2016-05-30
+
+ Merge pull request #3139 from rouault/ogr_concurrent_opening_branch_2_14
+
+ [Backport] [BUGFIX / FEATURE] [OGR] Allow concurrent edition of Shapefiles and Tabfiles in QGIS & MapInfo
+
+Even Rouault <even.rouault at spatialys.com> 2016-05-29
+
+ Doc: mark QgsDataProvider::enterUpdateMode() / leaveUpdateMode() as available in QGIS 2.14.4
+
+Even Rouault <even.rouault at spatialys.com> 2016-05-28
+
+ /test_provider_shapefile.py: do not test QgsVectorDataProvider.SimplifyGeometries since GDAL >= 1.11 is not available in Travis in this branch
+
+Even Rouault <even.rouault at spatialys.com> 2016-04-25
+
+ [BUGFIX] [OGR provider] Free OGR feature in changeAttributeValues() to avoid memory leak
+
+Even Rouault <even.rouault at spatialys.com> 2016-05-14
+
+ OGR provider: fix Coverity warning about mFetchGeometry member being not always initialized
+
+Even Rouault <even.rouault at spatialys.com> 2016-05-04
+
+ Use consistently dataSourceUri() with QgsOgrConnPool (follow up of https://github.com/qgis/QGIS/pull/3057)
+
+Even Rouault <even.rouault at spatialys.com> 2016-05-04
+
+ QgsOgrProvider::addAttributes(): call invalidateConnections() for MapInfo
+
+Even Rouault <even.rouault at spatialys.com> 2016-05-04
+
+ Move QgsOgrConnPool::instance()->unref() from QgsOgrProvider::close() to destructor, since we can open()/close() several times
+
+Even Rouault <even.rouault at spatialys.com> 2016-04-26
+
+ Fix style in previous commit regarding comparisons against nullptr
+
+ Cherry-picked from ed08ffb2aa945dcb0c103aa3fbe3120c0cd0337b
+
+Even Rouault <even.rouault at spatialys.com> 2016-04-11
+
+ [BUGFIX / FEATURE] [OGR] Allow concurrent edition of Shapefiles and Tabfiles in QGIS & MapInfo
+
+ - Closes https://hub.qgis.org/issues/14378
+ - Adds new virtual methods in QgsDataProvider(): enterUpdateMode() and leaveUpdateMode()
+ and implement them in the OGR provider. Limited to shapefiles and tabfiles
+ - Implements QgsOGRProvider:reloadData()
+ - Robustify OGR provider methods so they don't crash if dataset re-opening fails.
+
+ Cherry picked from dc18b5b36bfc10605d4c2905329e0ccd937f0828
+
+rldhont <rldhont at gmail.com> 2016-05-28
+
+ [Processing] Add shortHelp for Scripts, Models and R
+
+ And enhance getParameterDescriptions
+
+rldhont <rldhont at gmail.com> 2016-05-27
+
+ ending store and restore layer extents in projects
+
+Juergen E. Fischer <jef at norbit.de> 2016-03-30
+
+ store and restore layer extents in projects
+
+rldhont <rldhont at gmail.com> 2016-05-26
+
+ [BUGFIX][Processing] Add getParameterDescriptions to R, Model and Script algo
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-05-26
+
+ [processing] fix typo in Add autoincremental layer alg (fix #14892)
+
+rldhont <rldhont at gmail.com> 2016-05-24
+
+ [BUGFIX][Processing][Rscript] Write output Table
+
+ Add support forwriting output table to Rscript command.
+
+rldhont <rldhont at gmail.com> 2016-05-24
+
+ [BUGFIX][Processing][Rscript] Use CRS Parameter
+
+ Add support for CRS parameter to Rscript command.
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-25
+
+ Fix distorted date time calendar popup
+
+ (cherry-picked from 4ce16e1e31b7b7466a25e143ff2a9ebbdc7b0b1a)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-25
+
+ Fix invalid background/text colors showing in attribute table
+
+ (cherry-picked from 38e05026fb7a517cfef7b2b69a5a6e3cd7f0c355)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-25
+
+ Fix logic in detecting whether attribute form widgets have changed
+
+ Since two null QVariants can be reported as not equal if they have
+ different underlying types we need to ensure that we don't flag
+ this situation as a changed value.
+
+ (cherry-picked from 2fddc0079f153c6dd7cb312c8de96548d9812094)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-25
+
+ Correctly return null values from QgsColorWidgetWrapper
+
+ (cherry-picked from 94d88e65d847823dc8f94412ea25a5b91d472aa8)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-25
+
+ Always mark features as valid when added to memory provider
+
+ For other providers features will automatically be made valid
+ through the proces of adding to the provider's storage itself
+ and then later retrieving via iterators. But the memory
+ provider uses a direct copy of the feature, so if we don't
+ explicitly mark features as valid the provider may be
+ returning features incorrectly marked as invalid.
+
+ (cherry-picked from f2b70cf5e27c7de920ea3b54893019fb3f450cf6)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-25
+
+ Allow opacity change for 25d renderer colors (fix #14877)
+
+ (cherry-picked from 7af95b10a0e3e4946940dfb0cb85486ff992e608)
+
+rldhont <rldhont at gmail.com> 2016-05-24
+
+ [BUGFIX][Processing][Rscript] Use Extent Parameter
+
+ Add support for extent parameter to Rscript command.
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-24
+
+ Followup 73733a, fix crash when running invalid python strings
+
+ (cherry-picked from 7a8c3e0d2a2aa70c9c50e15d050a28da77763422)
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-24
+
+ Revert "Other check for null pointer before calling decref"
+
+ This reverts commit 9b2be7a2e16757620c08e93bc2ac2ccc54289de0.
+
+Nyall Dawson <nyall.dawson at gmail.com> 2016-05-24
+
+ Revert "Avoid a segfault when python code fails"
+
+ This reverts commit a1e826e4de08df240f81d2da4343e91ed3de66de.
+
+Alessandro Pasotti <apasotti at boundlessgeo.com> 2016-05-23
+
+ Other check for null pointer before calling decref
+
+Alessandro Pasotti <apasotti at boundlessgeo.com> 2016-05-23
+
+ Avoid a segfault when python code fails
+
+ PyRun_StringFlags: Returns the result of executing
+ the code as a Python object, or NULL if an exception was raised.
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-05-23
+
+ [processing] correctly set default value in modeler algorithms (fix #12767)
+
+ (cherry picked from commit d2b21891d44a1beb5be2112a53f354c7abec9221)
+
+Alexander Bruy <alexander.bruy at gmail.com> 2016-05-23
+
+ [processing] restore CreateConstantRaster algorithm (fix #14860)
+
+ (cherry picked from commit 6207412bf731d718a16246b37de7dfcc6eec9d6f)
+
+Merge: cf2ebb8 272b16a
+Matthias Kuhn <matthias at opengis.ch> 2016-05-21
+
+ Merge pull request #3054 from dgoedkoop/loadstylelabels214
+
+ [Bugfix] Update labeling settings after loading style from file (fixes #14224)
+
+Juergen E. Fischer <jef at norbit.de> 2016-05-20
+
+ Release of 2.14.3
+
Nyall Dawson <nyall.dawson at gmail.com> 2016-05-20
Precise that scale function returns the denominator and not the scale itself
@@ -139,6 +1325,10 @@ Juergen E. Fischer <jef at norbit.de> 2016-05-04
(cherry picked from commit 919c54ef5fabe7b7c2eef9c91094642c47b2eb7c)
+Daan Goedkoop <dgoedkoop at gmx.net> 2016-05-02
+
+ Update labeling settings in UI after loading style from file (fixes #14224)
+
Matthias Kuhn <matthias at opengis.ch> 2016-05-02
Fixup for AppStartup test which requires SIP API V1
diff --git a/cmake/PyQtMacros.cmake b/cmake/PyQtMacros.cmake
index 7a43f47..63c05f0 100644
--- a/cmake/PyQtMacros.cmake
+++ b/cmake/PyQtMacros.cmake
@@ -33,7 +33,11 @@ ENDIF(NOT PYUIC_PROGRAM)
MACRO(PYQT_WRAP_UI outfiles )
IF(WIN32)
SET(PYUIC_WRAPPER "${CMAKE_SOURCE_DIR}/scripts/${PYUIC_PROG_NAME}-wrapper.bat")
- SET(PYUIC_WRAPPER_PATH "${QGIS_OUTPUT_DIRECTORY}/bin/${CMAKE_BUILD_TYPE}")
+ IF(USING_NINJA OR USING_NMAKE)
+ SET(PYUIC_WRAPPER_PATH "${QGIS_OUTPUT_DIRECTORY}/bin")
+ ELSE(USING_NINJA OR USING_NMAKE)
+ SET(PYUIC_WRAPPER_PATH "${QGIS_OUTPUT_DIRECTORY}/bin/${CMAKE_BUILD_TYPE}")
+ ENDIF(USING_NINJA OR USING_NMAKE)
ELSE(WIN32)
# TODO osx
SET(PYUIC_WRAPPER "${CMAKE_SOURCE_DIR}/scripts/pyuic4-wrapper.sh")
diff --git a/cmake_templates/qgsconfig.h.in b/cmake_templates/qgsconfig.h.in
index 0766b13..3a4abe9 100644
--- a/cmake_templates/qgsconfig.h.in
+++ b/cmake_templates/qgsconfig.h.in
@@ -40,6 +40,8 @@
#cmakedefine USING_NMAKE
+#cmakedefine USING_NINJA
+
#cmakedefine HAVE_POSTGRESQL
#cmakedefine HAVE_SPATIALITE
diff --git a/debian/changelog b/debian/changelog
index 8d1b55c..da8fb0a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,14 @@
-qgis (2.14.3) UNRELEASED; urgency=medium
+qgis (2.14.4) UNRELEASED; urgency=medium
+
+ * Release of 2.14.4
+
+ -- Jürgen E. Fischer <jef at norbit.de> Fri, 08 Jul 2016 14:00:33 +0200
+
+qgis (2.14.3) unstable; urgency=medium
* Release of 2.14.3
- -- Jürgen E. Fischer <jef at norbit.de> Fri, 20 May 2016 14:05:10 +0200
+ -- Jürgen E. Fischer <jef at norbit.de> Fri, 08 Jul 2016 14:00:32 +0200
qgis (2.14.2) unstable; urgency=medium
diff --git a/debian/control b/debian/control
index ec88fe3..d9dd69d 100644
--- a/debian/control
+++ b/debian/control
@@ -31,6 +31,7 @@ Build-Depends:
python-dev,
python-qt4-dev (>= 4.1.0),
python-sip-dev (>= 4.5.0) | python-sip4-dev (>= 4.5.0) | sip4 (>= 4.5),
+ libosgearth-dev,
git,
txt2tags,
doxygen
diff --git a/debian/control.in b/debian/control.in
index 9964b4a..a5ca8b4 100644
--- a/debian/control.in
+++ b/debian/control.in
@@ -82,9 +82,9 @@ Depends:
qgis-providers (= ${binary:Version}),
qgis-common (= ${source:Version})
Recommends:
+#globe# qgis-plugin-globe,
qgis-plugin-grass,
- qgis-provider-grass,
- qgis-plugin-globe
+ qgis-provider-grass
Suggests: gpsbabel
Conflicts: uim-qt3
Description: Geographic Information System (GIS)
@@ -339,32 +339,32 @@ Description: GRASS plugin for QGIS - architecture-independent data
This package contains architecture-independent supporting data files for use
with the QGIS GRASS plugin.
-Package: qgis-plugin-globe
-Architecture: any
-Depends:
- qgis (= ${binary:Version}),
- qgis-plugin-globe-common (= ${source:Version}),
- openscenegraph-plugin-osgearth,
- ${shlibs:Depends},
- ${misc:Depends}
-Description: OSG globe plugin for QGIS
- QGIS is a Geographic Information System (GIS) which manages, analyzes and
- display databases of geographic information.
- .
- This plugin enables 3D viewing using OSG globe in the QGIS.
-
-Package: qgis-plugin-globe-common
-Architecture: all
-Depends:
- osgearth-data,
- ${misc:Depends}
-Description: OSG globe plugin for QGIS - architecture-independent data
- QGIS is a Geographic Information System (GIS) which manages, analyzes and
- display databases of geographic information.
- .
- This package contains architecture-independent supporting data files for use
- with the QGIS GLOBE plugin.
-
+#globe#Package: qgis-plugin-globe
+#globe#Architecture: any
+#globe#Depends:
+#globe# qgis (= ${binary:Version}),
+#globe# qgis-plugin-globe-common (= ${source:Version}),
+#globe# openscenegraph-plugin-osgearth,
+#globe# ${shlibs:Depends},
+#globe# ${misc:Depends}
+#globe#Description: OSG globe plugin for QGIS
+#globe# QGIS is a Geographic Information System (GIS) which manages, analyzes and
+#globe# display databases of geographic information.
+#globe# .
+#globe# This plugin enables 3D viewing using OSG globe in the QGIS.
+#globe#
+#globe#Package: qgis-plugin-globe-common
+#globe#Architecture: all
+#globe#Depends:
+#globe# osgearth-data,
+#globe# ${misc:Depends}
+#globe#Description: OSG globe plugin for QGIS - architecture-independent data
+#globe# QGIS is a Geographic Information System (GIS) which manages, analyzes and
+#globe# display databases of geographic information.
+#globe# .
+#globe# This package contains architecture-independent supporting data files for use
+#globe# with the QGIS GLOBE plugin.
+#globe#
Package: python-qgis
Architecture: any
Section: python
diff --git a/debian/rules b/debian/rules
index f44c81f..90e72c7 100755
--- a/debian/rules
+++ b/debian/rules
@@ -50,6 +50,8 @@ QGIS_ABI=$(QGIS_MAJOR).$(QGIS_MINOR).$(QGIS_PATCH)
GRASS=grass$(subst .,,$(shell pkg-config --modversion grass|cut -d. -f1,2))
GRASSVER=$(subst .,,$(shell pkg-config --modversion grass|cut -d. -f1))
+WITH_GLOBE=$(shell dpkg --compare-versions "$$(dpkg-query -W --showformat='$${Version}' libosgearth-dev)" lt 2.7 && echo 1)
+
CMAKE_OPTS := \
-DBUILDNAME=$(DEB_BUILD_NAME) \
-DCMAKE_VERBOSE_MAKEFILE=1 \
@@ -63,7 +65,6 @@ CMAKE_OPTS := \
-DQGIS_CGIBIN_SUBDIR=/usr/lib/cgi-bin \
-DWITH_APIDOC=TRUE \
-DWITH_CUSTOM_WIDGETS=TRUE \
- -DWITH_GLOBE=TRUE \
-DWITH_INTERNAL_HTTPLIB2=FALSE \
-DWITH_INTERNAL_JINJA2=FALSE \
-DWITH_INTERNAL_MARKUPSAFE=FALSE \
@@ -97,6 +98,10 @@ else
CMAKE_OPTS += -DWITH_INTERNAL_NOSE2=FALSE -DWITH_INTERNAL_SIX=FALSE
endif
+ifneq (,$(WITH_GLOBE))
+ CMAKE_OPTS += -DWITH_GLOBE=TRUE
+endif
+
ifneq (,$(findstring $(DISTRIBUTION),"wheezy precise"))
CMAKE_OPTS += -DWITH_PYSPATIALITE=TRUE
endif
@@ -181,6 +186,10 @@ endif
CONTROL_EXPRESSIONS = $(DISTRIBUTION) grass$(GRASSVER)
+ifneq (,$(WITH_GLOBE))
+ CONTROL_EXPRESSIONS += globe
+endif
+
ifneq (,$(WITH_ORACLE))
CONTROL_EXPRESSIONS += oracle
endif
diff --git a/ms-windows/osgeo4w/creatensis.pl b/ms-windows/osgeo4w/creatensis.pl
index 01bd33b..0b0663a 100755
--- a/ms-windows/osgeo4w/creatensis.pl
+++ b/ms-windows/osgeo4w/creatensis.pl
@@ -265,6 +265,24 @@ unless(-d $unpacked ) {
chdir "..";
}
+my($major, $minor, $patch);
+
+open F, "../../CMakeLists.txt";
+while(<F>) {
+ if(/SET\(CPACK_PACKAGE_VERSION_MAJOR "(\d+)"\)/) {
+ $major = $1;
+ } elsif(/SET\(CPACK_PACKAGE_VERSION_MINOR "(\d+)"\)/) {
+ $minor = $1;
+ } elsif(/SET\(CPACK_PACKAGE_VERSION_PATCH "(\d+)"\)/) {
+ $patch = $1;
+ } elsif(/SET\(RELEASE_NAME "(.+)"\)/) {
+ $releasename = $1 unless defined $releasename;
+ }
+}
+close F;
+
+$version = "$major.$minor.$patch" unless defined $version;
+
#
# Create postinstall.bat
#
@@ -337,24 +355,6 @@ print F "ren preremove.bat preremove.bat.done$r";
close F;
-my($major, $minor, $patch);
-
-open F, "../../CMakeLists.txt";
-while(<F>) {
- if(/SET\(CPACK_PACKAGE_VERSION_MAJOR "(\d+)"\)/) {
- $major = $1;
- } elsif(/SET\(CPACK_PACKAGE_VERSION_MINOR "(\d+)"\)/) {
- $minor = $1;
- } elsif(/SET\(CPACK_PACKAGE_VERSION_PATCH "(\d+)"\)/) {
- $patch = $1;
- } elsif(/SET\(RELEASE_NAME "(.+)"\)/) {
- $releasename = $1 unless defined $releasename;
- }
-}
-close F;
-
-$version = "$major.$minor.$patch" unless defined $version;
-
unless( defined $binary ) {
if( -f "binary$archpostfix-$version" ) {
open P, "binary$archpostfix-$version";
diff --git a/ms-windows/osgeo4w/package-nightly.cmd b/ms-windows/osgeo4w/package-nightly.cmd
index 18ffe6c..a0e9bfb 100644
--- a/ms-windows/osgeo4w/package-nightly.cmd
+++ b/ms-windows/osgeo4w/package-nightly.cmd
@@ -165,7 +165,7 @@ cmake %CMAKE_OPT% ^
-D WITH_GRASS7=TRUE ^
-D GRASS_PREFIX=%O4W_ROOT%/apps/grass/grass-%GRASS6_VERSION% ^
-D GRASS_PREFIX7=%GRASS70_PATH:\=/% ^
- -D WITH_GLOBE=TRUE ^
+ -D WITH_GLOBE=FALSE ^
-D WITH_TOUCH=TRUE ^
-D WITH_ORACLE=TRUE ^
-D WITH_CUSTOM_WIDGETS=TRUE ^
diff --git a/ms-windows/osgeo4w/package.cmd b/ms-windows/osgeo4w/package.cmd
index b508376..694de15 100644
--- a/ms-windows/osgeo4w/package.cmd
+++ b/ms-windows/osgeo4w/package.cmd
@@ -161,7 +161,7 @@ cmake %CMAKE_OPT% ^
-D WITH_GRASS7=TRUE ^
-D GRASS_PREFIX=%O4W_ROOT%/apps/grass/grass-%GRASS6_VERSION% ^
-D GRASS_PREFIX7=%GRASS70_PATH:\=/% ^
- -D WITH_GLOBE=TRUE ^
+ -D WITH_GLOBE=FALSE ^
-D WITH_TOUCH=TRUE ^
-D WITH_ORACLE=TRUE ^
-D WITH_CUSTOM_WIDGETS=TRUE ^
@@ -267,7 +267,8 @@ if not exist %OSGEO4W_ROOT%\httpd.d mkdir %OSGEO4W_ROOT%\httpd.d
sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' httpd.conf.tmpl >%OSGEO4W_ROOT%\httpd.d\httpd_%PACKAGENAME%.conf.tmpl
if errorlevel 1 (echo creation of httpd.conf template failed & goto error)
-set packages="" "-common" "-server" "-devel" "-globe-plugin" "-oracle-provider" "-grass-plugin-common"
+set packages="" "-common" "-server" "-devel" "-oracle-provider" "-grass-plugin-common"
+REM set packages=%packages% "-globe-plugin"
for %%g IN (%GRASS_VERSIONS%) do (
for /F "delims=." %%i in ("%%g") do set v=%%i
@@ -445,12 +446,12 @@ for %%g IN (%GRASS_VERSIONS%) do (
if errorlevel 1 (echo tar grass-plugin!w! failed & goto error)
)
-tar -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-globe-plugin/%PACKAGENAME%-globe-plugin-%VERSION%-%PACKAGE%.tar.bz2 ^
- --exclude-from exclude ^
- --exclude "*.pyc" ^
- "apps/%PACKAGENAME%/globe" ^
- "apps/%PACKAGENAME%/plugins/globeplugin.dll"
-if errorlevel 1 (echo tar globe-plugin failed & goto error)
+REM tar -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-globe-plugin/%PACKAGENAME%-globe-plugin-%VERSION%-%PACKAGE%.tar.bz2 ^
+REM --exclude-from exclude ^
+REM --exclude "*.pyc" ^
+REM "apps/%PACKAGENAME%/globe" ^
+REM "apps/%PACKAGENAME%/plugins/globeplugin.dll"
+REM if errorlevel 1 (echo tar globe-plugin failed & goto error)
tar -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-oracle-provider/%PACKAGENAME%-oracle-provider-%VERSION%-%PACKAGE%.tar.bz2 ^
"apps/%PACKAGENAME%/plugins/oracleprovider.dll" ^
@@ -474,7 +475,7 @@ exit
:error
echo BUILD ERROR %ERRORLEVEL%: %DATE% %TIME%
-for %%i in ("" "-common" "-server" "-devel" "-grass-plugin" "-globe-plugin" "-oracle-provider") do (
+for %%i in (%packages%) do (
if exist %ARCH%\release\qgis\%PACKAGENAME%%%i\%PACKAGENAME%%%i-%VERSION%-%PACKAGE%.tar.bz2 del %ARCH%\release\qgis\%PACKAGENAME%%%i\%PACKAGENAME%%%i-%VERSION%-%PACKAGE%.tar.bz2
)
diff --git a/python/core/__init__.py b/python/core/__init__.py
index e8fe1b7..a150bb4 100644
--- a/python/core/__init__.py
+++ b/python/core/__init__.py
@@ -35,7 +35,7 @@ from qgis._core import *
from PyQt4.QtCore import QCoreApplication
-def register_function(function, arg_count, group, usesgeometry=False, **kwargs):
+def register_function(function, arg_count, group, usesgeometry=False, referenced_columns=[QgsFeatureRequest.AllAttributes], **kwargs):
"""
Register a Python function to be used as a expression function.
@@ -64,8 +64,8 @@ def register_function(function, arg_count, group, usesgeometry=False, **kwargs):
"""
class QgsExpressionFunction(QgsExpression.Function):
- def __init__(self, func, name, args, group, helptext='', usesgeometry=True, expandargs=False):
- QgsExpression.Function.__init__(self, name, args, group, helptext, usesgeometry)
+ def __init__(self, func, name, args, group, helptext='', usesgeometry=True, referencedColumns=QgsFeatureRequest.AllAttributes, expandargs=False):
+ QgsExpression.Function.__init__(self, name, args, group, helptext, usesgeometry, referencedColumns)
self.function = func
self.expandargs = expandargs
@@ -105,7 +105,7 @@ def register_function(function, arg_count, group, usesgeometry=False, **kwargs):
function.__name__ = name
helptext = helptemplate.safe_substitute(name=name, doc=helptext)
- f = QgsExpressionFunction(function, name, arg_count, group, helptext, usesgeometry, expandargs)
+ f = QgsExpressionFunction(function, name, arg_count, group, helptext, usesgeometry, referenced_columns, expandargs)
# This doesn't really make any sense here but does when used from a decorator context
# so it can stay.
diff --git a/python/core/composer/qgscomposition.sip b/python/core/composer/qgscomposition.sip
index 4a206cf..d3b0771 100644
--- a/python/core/composer/qgscomposition.sip
+++ b/python/core/composer/qgscomposition.sip
@@ -830,6 +830,8 @@ class QgsComposition : QGraphicsScene
void composerArrowAdded( QgsComposerArrow* arrow );
/** Is emitted when a new composer html has been added to the view*/
void composerHtmlFrameAdded( QgsComposerHtml* html, QgsComposerFrame* frame );
+ /** Is emitted when a new item group has been added to the view*/
+ void composerItemGroupAdded( QgsComposerItemGroup* group );
/** Is emitted when new composer label has been added to the view*/
void composerLabelAdded( QgsComposerLabel* label );
/** Is emitted when new composer map has been added to the view*/
diff --git a/python/core/composer/qgsgroupungroupitemscommand.sip b/python/core/composer/qgsgroupungroupitemscommand.sip
new file mode 100644
index 0000000..756635c
--- /dev/null
+++ b/python/core/composer/qgsgroupungroupitemscommand.sip
@@ -0,0 +1,43 @@
+/** A composer command class for grouping / ungrouping composer items.
+ *
+ * If mState == Ungrouped, the command owns the group item
+ */
+class QgsGroupUngroupItemsCommand: QObject, QUndoCommand
+{
+%TypeHeaderCode
+#include "qgsgroupungroupitemscommand.h"
+%End
+
+ public:
+
+ /** Command kind, and state */
+ enum State
+ {
+ Grouped = 0,
+ Ungrouped
+ };
+
+ /** Create a group or ungroup command
+ *
+ * @param s command kind (@see State)
+ * @param item the group item being created or ungrouped
+ * @param c the composition including this group
+ * @param text command label
+ * @param parent parent command, if any
+ *
+ */
+ QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent = nullptr );
+ ~QgsGroupUngroupItemsCommand();
+
+ void redo();
+ void undo();
+
+ signals:
+ /** Signals addition of an item (the group) */
+ void itemAdded( QgsComposerItem* item );
+ /** Signals removal of an item (the group) */
+ void itemRemoved( QgsComposerItem* item );
+
+};
+
+
diff --git a/python/core/core.sip b/python/core/core.sip
index 657f745..f54fddd 100644
--- a/python/core/core.sip
+++ b/python/core/core.sip
@@ -158,6 +158,7 @@
%Include auth/qgsauthmethod.sip
%Include composer/qgsaddremoveitemcommand.sip
+%Include composer/qgsgroupungroupitemscommand.sip
%Include composer/qgsaddremovemultiframecommand.sip
%Include composer/qgsatlascomposition.sip
%Include composer/qgscomposerarrow.sip
diff --git a/python/core/dxf/qgsdxfexport.sip b/python/core/dxf/qgsdxfexport.sip
index 7436fa5..00b1743 100644
--- a/python/core/dxf/qgsdxfexport.sip
+++ b/python/core/dxf/qgsdxfexport.sip
@@ -117,7 +117,7 @@ class QgsDxfExport
* Get DXF palette index of nearest entry for given color
* @param color
*/
- static int closestColorMatch( QRgb pixel );
+ static int closestColorMatch( QRgb color );
/**
* Get layer name for feature
@@ -215,7 +215,7 @@ class QgsDxfExport
* @param line polyline
* @param layer layer name to use
* @param lineStyleName line type to use
- * @param color coolor to use
+ * @param color color to use
* @param width line width to use
*/
void writePolyline( const QgsPolyline &line, const QString &layer, const QString &lineStyleName, const QColor& color, double width = -1 );
@@ -225,7 +225,7 @@ class QgsDxfExport
* @param polygon polygon
* @param layer layer name to use
* @param hatchPattern hatchPattern to use
- * @param color coolor to use
+ * @param color color to use
*/
void writePolygon( const QgsPolygon &polygon, const QString &layer, const QString &hatchPattern, const QColor& color );
@@ -270,4 +270,20 @@ class QgsDxfExport
//! return list of available DXF encodings
static QStringList encodings();
+ /** Output the label
+ * @param layerId id of the layer
+ * @param context render context
+ * @param label position of label
+ * @param settings label settings
+ * @note not available in Python bindings
+ */
+ // void drawLabel( QString layerId, QgsRenderContext& context, pal::LabelPosition* label, const QgsPalLayerSettings &settings );
+
+ /** Register name of layer for feature
+ * @param layerId id of layer
+ * @param fid id of feature
+ * @param layer dxf layer of feature
+ */
+ void registerDxfLayer( QString layerId, QgsFeatureId fid, QString layer );
+
};
diff --git a/python/core/geometry/qgscurvepolygonv2.sip b/python/core/geometry/qgscurvepolygonv2.sip
index 861b84a..4843e16 100644
--- a/python/core/geometry/qgscurvepolygonv2.sip
+++ b/python/core/geometry/qgscurvepolygonv2.sip
@@ -80,7 +80,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2
virtual int vertexCount( int /*part*/ = 0, int ring = 0 ) const;
virtual int ringCount( int /*part*/ = 0 ) const;
virtual int partCount() const;
- virtual QgsPointV2 vertexAt( QgsVertexId id ) const;
+ virtual QgsPointV2 vertexAt( const QgsVertexId& id ) const;
virtual bool addZValue( double zValue = 0 );
virtual bool addMValue( double mValue = 0 );
diff --git a/python/core/qgsdataprovider.sip b/python/core/qgsdataprovider.sip
index 7ea28ec..aab6e1a 100644
--- a/python/core/qgsdataprovider.sip
+++ b/python/core/qgsdataprovider.sip
@@ -223,6 +223,47 @@ class QgsDataProvider : QObject
*/
virtual void invalidateConnections( const QString& connection );
+ /** Enter update mode.
+ *
+ * This is aimed at providers that can open differently the connection to
+ * the datasource, according it to be in update mode or in read-only mode.
+ * A call to this method shall be balanced with a call to leaveUpdateMode(),
+ * if this method returns true.
+ *
+ * Most providers will have an empty implementation for that method.
+ *
+ * For backward compatibility, providers that implement enterUpdateMode() should
+ * still make sure to allow editing operations to work even if enterUpdateMode()
+ * is not explicitly called.
+ *
+ * Several successive calls to enterUpdateMode() can be done. So there is
+ * a concept of stack of calls that must be handled by the provider. Only the first
+ * call to enterUpdateMode() will really turn update mode on.
+ *
+ * @return true in case of success (or no-op implementation), false in case of failure
+ *
+ * @note added in QGIS 2.14.4
+ */
+ virtual bool enterUpdateMode();
+
+ /** Leave update mode.
+ *
+ * This is aimed at providers that can open differently the connection to
+ * the datasource, according it to be in update mode or in read-only mode.
+ * This method shall be balanced with a succesful call to enterUpdateMode().
+ *
+ * Most providers will have an empty implementation for that method.
+ *
+ * Several successive calls to enterUpdateMode() can be done. So there is
+ * a concept of stack of calls that must be handled by the provider. Only the last
+ * call to leaveUpdateMode() will really turn update mode off.
+ *
+ * @return true in case of success (or no-op implementation), false in case of failure
+ *
+ * @note added in QGIS 2.14.4
+ */
+ virtual bool leaveUpdateMode();
+
signals:
/**
diff --git a/python/core/qgsfeaturerequest.sip b/python/core/qgsfeaturerequest.sip
index ff191cd..40d7f50 100644
--- a/python/core/qgsfeaturerequest.sip
+++ b/python/core/qgsfeaturerequest.sip
@@ -302,7 +302,7 @@ class QgsFeatureRequest
* Return the subset of attributes which at least need to be fetched
* @return A list of attributes to be fetched
*/
- const QgsAttributeList& subsetOfAttributes() const;
+ QgsAttributeList subsetOfAttributes() const;
//! Set a subset of attributes by names that will be fetched
QgsFeatureRequest& setSubsetOfAttributes( const QStringList& attrNames, const QgsFields& fields );
diff --git a/python/core/qgsmaprenderer.sip b/python/core/qgsmaprenderer.sip
index 0f62aeb..130069d 100644
--- a/python/core/qgsmaprenderer.sip
+++ b/python/core/qgsmaprenderer.sip
@@ -35,7 +35,7 @@ class QgsLabelingEngineInterface
//! called when we're going to start with rendering
//! @deprecated since 2.4 - use override with QgsMapSettings
- virtual void init( QgsMapRenderer* mp ) = 0 /Deprecated/;
+ virtual void init( QgsMapRenderer *mp ) = 0 /Deprecated/;
//! called when we're going to start with rendering
virtual void init( const QgsMapSettings& mapSettings ) = 0;
//! called to find out whether the layer is used for labeling
@@ -48,17 +48,17 @@ class QgsLabelingEngineInterface
virtual int prepareLayer( QgsVectorLayer* layer, QStringList& attrNames, QgsRenderContext& ctx ) = 0;
//! returns PAL layer settings for a registered layer
//! @deprecated since 2.12 - if direct access to QgsPalLayerSettings is necessary, use QgsPalLayerSettings::fromLayer()
- virtual QgsPalLayerSettings& layer( const QString& layerName ) = 0 /Deprecated/;
+ virtual QgsPalLayerSettings &layer( const QString &layerName ) = 0 /Deprecated/;
//! adds a diagram layer to the labeling engine
//! @note added in QGIS 2.12
- virtual int prepareDiagramLayer( QgsVectorLayer* layer, QStringList& attrNames, QgsRenderContext& ctx );
+ virtual int prepareDiagramLayer( QgsVectorLayer *layer, QStringList &attrNames, QgsRenderContext &ctx );
//! adds a diagram layer to the labeling engine
//! @deprecated since 2.12 - use prepareDiagramLayer()
- virtual int addDiagramLayer( QgsVectorLayer* layer, const QgsDiagramLayerSettings* s ) /Deprecated/;
+ virtual int addDiagramLayer( QgsVectorLayer *layer, const QgsDiagramLayerSettings *s ) /Deprecated/;
//! called for every feature
- virtual void registerFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context, const QString& dxfLayer = QString::null ) = 0;
+ virtual void registerFeature( const QString &layerID, QgsFeature &feat, QgsRenderContext &context ) = 0;
//! called for every diagram feature
- virtual void registerDiagramFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context );
+ virtual void registerDiagramFeature( const QString &layerID, QgsFeature &feat, QgsRenderContext &context );
//! called when the map is drawn and labels should be placed
virtual void drawLabeling( QgsRenderContext& context ) = 0;
//! called when we're done with rendering
@@ -268,7 +268,7 @@ class QgsMapRenderer : QObject
//! Accessor for render context
QgsRenderContext* rendererContext();
- //! Labeling engine (NULL if there's no custom engine)
+ //! Labeling engine (nullptr if there's no custom engine)
QgsLabelingEngineInterface* labelingEngine();
//! Set labeling engine. Previous engine (if any) is deleted.
diff --git a/python/core/qgsmapsettings.sip b/python/core/qgsmapsettings.sip
index e139aaa..fa581a4 100644
--- a/python/core/qgsmapsettings.sip
+++ b/python/core/qgsmapsettings.sip
@@ -78,10 +78,10 @@ class QgsMapSettings
//! Get color that is used for drawing of selected vector features
QColor selectionColor() const;
- //! Enumeration of flags that adjust the way how map is rendered
+ //! Enumeration of flags that adjust the way the map is rendered
enum Flag
{
- Antialiasing, //!< Enable anti-aliasin for map rendering
+ Antialiasing, //!< Enable anti-aliasing for map rendering
DrawEditingInfo, //!< Enable drawing of vertex markers for layers in editing mode
ForceVectorOutput, //!< Vector graphics should not be cached and drawn as raster images
UseAdvancedEffects, //!< Enable layer transparency and blending effects
diff --git a/python/core/qgspallabeling.sip b/python/core/qgspallabeling.sip
index 0877730..0b3f813 100644
--- a/python/core/qgspallabeling.sip
+++ b/python/core/qgspallabeling.sip
@@ -551,7 +551,6 @@ class QgsPalLayerSettings
* @param f feature to label
* @param context render context. The QgsExpressionContext contained within the render context
* must have already had the feature and fields sets prior to calling this method.
- * @param dxfLayer dxfLayer name
* @param labelFeature if using QgsLabelingEngineV2, this will receive the label feature. Not available
* in Python bindings.
* @param obstacleGeometry optional obstacle geometry, if a different geometry to the feature's geometry
@@ -560,7 +559,7 @@ class QgsPalLayerSettings
* the feature's original geometry will be used as an obstacle for labels. Not available
* in Python bindings.
*/
- void registerFeature( QgsFeature& f, QgsRenderContext& context, const QString& dxfLayer );
+ void registerFeature( QgsFeature& f, QgsRenderContext& context );
void readFromLayer( QgsVectorLayer* layer );
void writeToLayer( QgsVectorLayer* layer );
@@ -867,9 +866,8 @@ class QgsPalLabeling : QgsLabelingEngineInterface
* @param feat feature to label
* @param context render context. The QgsExpressionContext contained within the render context
* must have already had the feature and fields sets prior to calling this method.
- * @param dxfLayer dxfLayer name
*/
- virtual void registerFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context, const QString& dxfLayer = QString::null );
+ virtual void registerFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context );
virtual void registerDiagramFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context );
//! called when the map is drawn and labels should be placed
diff --git a/python/core/qgsrendercontext.sip b/python/core/qgsrendercontext.sip
index e7f5be4..3764790 100644
--- a/python/core/qgsrendercontext.sip
+++ b/python/core/qgsrendercontext.sip
@@ -21,7 +21,8 @@ class QgsRenderContext
UseRenderingOptimization, //!< Enable vector simplification and other rendering optimizations
DrawSelection, //!< Whether vector selections should be shown in the rendered map
DrawSymbolBounds, //!< Draw bounds of symbols (for debugging/testing)
- RenderMapTile
+ RenderMapTile, //!< Draw map such that there are no problems between adjacent tiles
+ Antialiasing, //!< Use antialiasing while drawing
};
typedef QFlags<QgsRenderContext::Flag> Flags;
diff --git a/python/core/symbology-ng/qgsellipsesymbollayerv2.sip b/python/core/symbology-ng/qgsellipsesymbollayerv2.sip
index c8e6101..a94facd 100644
--- a/python/core/symbology-ng/qgsellipsesymbollayerv2.sip
+++ b/python/core/symbology-ng/qgsellipsesymbollayerv2.sip
@@ -21,7 +21,7 @@ class QgsEllipseSymbolLayerV2 : QgsMarkerSymbolLayerV2
void toSld( QDomDocument& doc, QDomElement &element, const QgsStringMap& props ) const;
void writeSldMarker( QDomDocument& doc, QDomElement &element, const QgsStringMap& props ) const;
- bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
+ bool writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
void setSymbolName( const QString& name );
QString symbolName() const;
diff --git a/python/core/symbology-ng/qgsinvertedpolygonrenderer.sip b/python/core/symbology-ng/qgsinvertedpolygonrenderer.sip
index e29b5b6..0e535c7 100644
--- a/python/core/symbology-ng/qgsinvertedpolygonrenderer.sip
+++ b/python/core/symbology-ng/qgsinvertedpolygonrenderer.sip
@@ -6,9 +6,11 @@ class QgsInvertedPolygonRenderer : QgsFeatureRendererV2
public:
/** Constructor
- * @param embeddedRenderer optional embeddedRenderer. If null, a default one will be assigned
+ * @param embeddedRenderer optional embeddedRenderer. If null, a default one will be assigned.
+ * Ownership will be transferred.
*/
- QgsInvertedPolygonRenderer( const QgsFeatureRendererV2* embeddedRenderer /Transfer/ = 0 );
+ QgsInvertedPolygonRenderer( QgsFeatureRendererV2* embeddedRenderer /Transfer/ = 0 );
+
virtual ~QgsInvertedPolygonRenderer();
/** Used to clone this feature renderer.*/
@@ -80,14 +82,11 @@ class QgsInvertedPolygonRenderer : QgsFeatureRendererV2
*/
virtual QDomElement save( QDomDocument& doc );
- /** Sets the embedded renderer
- * @param subRenderer the embedded renderer (will be cloned)
- */
- void setEmbeddedRenderer( const QgsFeatureRendererV2* subRenderer );
- /** @returns the current embedded renderer
- */
+ void setEmbeddedRenderer( QgsFeatureRendererV2* subRenderer /Transfer/ );
const QgsFeatureRendererV2* embeddedRenderer() const;
+ virtual void setLegendSymbolItem( const QString& key, QgsSymbolV2* symbol );
+
/** @returns true if the geometries are to be preprocessed (merged with an union) before rendering.*/
bool preprocessingEnabled() const;
/**
diff --git a/python/core/symbology-ng/qgsmarkersymbollayerv2.sip b/python/core/symbology-ng/qgsmarkersymbollayerv2.sip
index 1aa110f..90c039d 100644
--- a/python/core/symbology-ng/qgsmarkersymbollayerv2.sip
+++ b/python/core/symbology-ng/qgsmarkersymbollayerv2.sip
@@ -67,7 +67,7 @@ class QgsSimpleMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
void setOutlineWidthMapUnitScale( const QgsMapUnitScale& scale);
const QgsMapUnitScale& outlineWidthMapUnitScale() const;
- bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
+ bool writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
void setOutputUnit( QgsSymbolV2::OutputUnit unit );
QgsSymbolV2::OutputUnit outputUnit() const;
@@ -146,7 +146,7 @@ class QgsSvgMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
void setMapUnitScale( const QgsMapUnitScale& scale );
QgsMapUnitScale mapUnitScale() const;
- bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
+ bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
QRectF bounds( QPointF point, QgsSymbolV2RenderContext& context );
};
diff --git a/python/core/symbology-ng/qgspointdisplacementrenderer.sip b/python/core/symbology-ng/qgspointdisplacementrenderer.sip
index 177194d..6542a02 100644
--- a/python/core/symbology-ng/qgspointdisplacementrenderer.sip
+++ b/python/core/symbology-ng/qgspointdisplacementrenderer.sip
@@ -68,9 +68,14 @@ class QgsPointDisplacementRenderer : QgsFeatureRendererV2
void setLabelAttributeName( const QString& name );
QString labelAttributeName() const;
- /** Sets embedded renderer (takes ownership)*/
void setEmbeddedRenderer( QgsFeatureRendererV2* r /Transfer/ );
- QgsFeatureRendererV2* embeddedRenderer();
+ const QgsFeatureRendererV2* embeddedRenderer() const;
+
+ virtual void setLegendSymbolItem( const QString& key, QgsSymbolV2* symbol );
+
+ virtual bool legendSymbolItemsCheckable() const;
+ virtual bool legendSymbolItemChecked( const QString& key );
+ virtual void checkLegendSymbolItem( const QString& key, bool state = true );
//! not available in python bindings
//! @deprecated since 2.4
diff --git a/python/core/symbology-ng/qgsrendererv2.sip b/python/core/symbology-ng/qgsrendererv2.sip
index 58916a9..0812774 100644
--- a/python/core/symbology-ng/qgsrendererv2.sip
+++ b/python/core/symbology-ng/qgsrendererv2.sip
@@ -384,6 +384,21 @@ class QgsFeatureRendererV2
*/
void setOrderByEnabled( bool enabled );
+ /** Sets an embedded renderer (subrenderer) for this feature renderer. The base class implementation
+ * does nothing with subrenderers, but individual derived classes can use these to modify their behaviour.
+ * @param subRenderer the embedded renderer. Ownership will be transferred.
+ * @see embeddedRenderer()
+ * @note added in QGIS 2.16
+ */
+ virtual void setEmbeddedRenderer( QgsFeatureRendererV2* subRenderer /Transfer/ );
+
+ /** Returns the current embedded renderer (subrenderer) for this feature renderer. The base class
+ * implementation does not use subrenderers and will always return null.
+ * @see setEmbeddedRenderer()
+ * @note added in QGIS 2.16
+ */
+ virtual const QgsFeatureRendererV2* embeddedRenderer() const;
+
protected:
QgsFeatureRendererV2( const QString& type );
diff --git a/python/core/symbology-ng/qgssymbollayerv2.sip b/python/core/symbology-ng/qgssymbollayerv2.sip
index 102c6bd..fe9dd49 100644
--- a/python/core/symbology-ng/qgssymbollayerv2.sip
+++ b/python/core/symbology-ng/qgssymbollayerv2.sip
@@ -252,17 +252,13 @@ class QgsSymbolLayerV2
*/
virtual QVariant evaluateDataDefinedProperty( const QString& property, const QgsSymbolV2RenderContext& context, const QVariant& defaultVal = QVariant(), bool *ok = 0 ) const;
- virtual bool writeDxf( QgsDxfExport& e,
- double mmMapUnitScaleFactor,
- const QString& layerName,
- QgsSymbolV2RenderContext* context,
- const QgsFeature* f,
- QPointF shift = QPointF( 0.0, 0.0 ) ) const;
+ virtual bool writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
virtual double dxfWidth( const QgsDxfExport& e, QgsSymbolV2RenderContext& context ) const;
virtual double dxfOffset( const QgsDxfExport& e, QgsSymbolV2RenderContext& context ) const;
virtual QColor dxfColor( QgsSymbolV2RenderContext& context ) const;
+ virtual double dxfAngle( QgsSymbolV2RenderContext& context ) const;
virtual QVector<qreal> dxfCustomDashPattern( QgsSymbolV2::OutputUnit& unit ) const;
virtual Qt::PenStyle dxfPenStyle() const;
diff --git a/python/core/symbology-ng/qgsvectorfieldsymbollayer.sip b/python/core/symbology-ng/qgsvectorfieldsymbollayer.sip
index b7ee68f..b524991 100644
--- a/python/core/symbology-ng/qgsvectorfieldsymbollayer.sip
+++ b/python/core/symbology-ng/qgsvectorfieldsymbollayer.sip
@@ -34,6 +34,9 @@ class QgsVectorFieldSymbolLayer : QgsMarkerSymbolLayerV2
bool setSubSymbol( QgsSymbolV2* symbol /Transfer/ );
QgsSymbolV2* subSymbol();
+ void setColor( const QColor& color );
+ virtual QColor color() const;
+
void renderPoint( QPointF point, QgsSymbolV2RenderContext& context );
void startRender( QgsSymbolV2RenderContext& context );
void stopRender( QgsSymbolV2RenderContext& context );
diff --git a/python/gui/qgsannotationitem.sip b/python/gui/qgsannotationitem.sip
index 70cbf67..2526e36 100644
--- a/python/gui/qgsannotationitem.sip
+++ b/python/gui/qgsannotationitem.sip
@@ -1,11 +1,33 @@
-/** An annotation item can be either placed either on screen corrdinates or on map coordinates.
- It may reference a feature and displays that associatiation with a balloon like appearance*/
+/** An annotation item can be either placed either on screen coordinates or on map coordinates.
+ It may reference a feature and displays that association with a balloon like appearance*/
+
+%ModuleCode
+#include "qgsformannotationitem.h"
+#include "qgshtmlannotationitem.h"
+#include "qgssvgannotationitem.h"
+#include "qgstextannotationitem.h"
+%End
+
class QgsAnnotationItem: QgsMapCanvasItem
{
%TypeHeaderCode
#include <qgsannotationitem.h>
%End
+%ConvertToSubClassCode
+ if (dynamic_cast<QgsFormAnnotationItem*>(sipCpp) )
+ sipType = sipType_QgsFormAnnotationItem;
+ else if (dynamic_cast<QgsHtmlAnnotationItem*>(sipCpp) )
+ sipType = sipType_QgsHtmlAnnotationItem;
+ else if (dynamic_cast<QgsSvgAnnotationItem*>(sipCpp) )
+ sipType = sipType_QgsSvgAnnotationItem;
+ else if (dynamic_cast<QgsTextAnnotationItem*>(sipCpp) )
+ sipType = sipType_QgsTextAnnotationItem;
+ else
+ sipType = 0;
+%End
+
+
public:
enum MouseMoveAction
{
diff --git a/python/gui/qgscomposerview.sip b/python/gui/qgscomposerview.sip
index 057e20d..da7c342 100644
--- a/python/gui/qgscomposerview.sip
+++ b/python/gui/qgscomposerview.sip
@@ -112,6 +112,13 @@ class QgsComposerView : QGraphicsView
/** Set zoom level, where a zoom level of 1.0 corresponds to 100%*/
void setZoomLevel( double zoomLevel );
+ /** Scales the view in a safe way, by limiting the acceptable range
+ * of the scale applied.
+ * @param scale factor to scale view by
+ * @note added in QGIS 2.16
+ */
+ void scaleSafe( double scale );
+
/** Sets whether a preview effect should be used to alter the view's appearance
* @param enabled Set to true to enable the preview effect on the view
* @note added in 2.3
diff --git a/python/gui/qgsrubberband.sip b/python/gui/qgsrubberband.sip
index 1a56469..beefe33 100644
--- a/python/gui/qgsrubberband.sip
+++ b/python/gui/qgsrubberband.sip
@@ -122,6 +122,14 @@ class QgsRubberBand: QgsMapCanvasItem
*/
void addPoint( const QgsPoint & p, bool doUpdate = true, int geometryIndex = 0 );
+ /** Ensures that a polygon geometry is closed and that the last vertex equals the
+ * first vertex.
+ * @param doUpdate set to true to update the map canvas immediately
+ * @param geometryIndex index of the feature part (in case of multipart geometries)
+ * @note added in QGIS 2.16
+ */
+ void closePoints( bool doUpdate = true, int geometryIndex = 0 );
+
/**
* Remove a vertex from the rubberband and (optionally) update canvas.
* @param index The index of the vertex/point to remove, negative indexes start at end
diff --git a/python/plugins/GdalTools/tools/GdalTools_utils.py b/python/plugins/GdalTools/tools/GdalTools_utils.py
index 9eded70..149a4d8 100644
--- a/python/plugins/GdalTools/tools/GdalTools_utils.py
+++ b/python/plugins/GdalTools/tools/GdalTools_utils.py
@@ -444,19 +444,39 @@ class FileDialog:
@classmethod
def getOpenFileNames(self, parent=None, caption='', filter='', selectedFilter=None, useEncoding=False):
- return self.getDialog(parent, caption, QFileDialog.AcceptOpen, QFileDialog.ExistingFiles, filter, selectedFilter, useEncoding)
+ if useEncoding:
+ return self.getDialog(parent, caption, QFileDialog.AcceptOpen, QFileDialog.ExistingFiles, filter, selectedFilter, useEncoding)
+ res = QFileDialog.getOpenFileNames(parent, caption, getLastUsedDir(), filter)
+ if len(res) > 0:
+ setLastUsedDir(res[-1])
+ return res
@classmethod
def getOpenFileName(self, parent=None, caption='', filter='', selectedFilter=None, useEncoding=False):
- return self.getDialog(parent, caption, QFileDialog.AcceptOpen, QFileDialog.ExistingFile, filter, selectedFilter, useEncoding)
+ if useEncoding:
+ return self.getDialog(parent, caption, QFileDialog.AcceptOpen, QFileDialog.ExistingFile, filter, selectedFilter, useEncoding)
+ res = QFileDialog.getOpenFileName(parent, caption, getLastUsedDir(), filter)
+ if res:
+ setLastUsedDir(res)
+ return res
@classmethod
def getSaveFileName(self, parent=None, caption='', filter='', selectedFilter=None, useEncoding=False):
- return self.getDialog(parent, caption, QFileDialog.AcceptSave, QFileDialog.AnyFile, filter, selectedFilter, useEncoding)
+ if useEncoding:
+ return self.getDialog(parent, caption, QFileDialog.AcceptSave, QFileDialog.AnyFile, filter, selectedFilter, useEncoding)
+ res = QFileDialog.getSaveFileName(parent, caption, getLastUsedDir(), filter)
+ if res:
+ setLastUsedDir(res)
+ return res
@classmethod
def getExistingDirectory(self, parent=None, caption='', useEncoding=False):
- return self.getDialog(parent, caption, QFileDialog.AcceptOpen, QFileDialog.DirectoryOnly, '', None, useEncoding)
+ if useEncoding:
+ return self.getDialog(parent, caption, QFileDialog.AcceptOpen, QFileDialog.DirectoryOnly, '', None, useEncoding)
+ res = QFileDialog.getExistingDirectory(parent, caption, getLastUsedDir(), QFileDialog.ShowDirsOnly)
+ if res:
+ setLastUsedDir(res)
+ return res
class FileFilter:
diff --git a/python/plugins/GdalTools/tools/doContour.py b/python/plugins/GdalTools/tools/doContour.py
index 8d179cd..f645ff2 100644
--- a/python/plugins/GdalTools/tools/doContour.py
+++ b/python/plugins/GdalTools/tools/doContour.py
@@ -47,6 +47,8 @@ class GdalToolsDialog(QWidget, Ui_Widget, BasePluginWidget):
self.outSelector.setType(self.outSelector.FILE)
+ self.outputFormat = Utils.fillVectorOutputFormat()
+
# set the default QSpinBoxes value
self.intervalDSpinBox.setValue(10.0)
@@ -85,6 +87,7 @@ class GdalToolsDialog(QWidget, Ui_Widget, BasePluginWidget):
if not self.useDirAsOutput:
Utils.FileFilter.setLastUsedVectorFilter(lastUsedFilter)
+ self.outputFormat = Utils.fillVectorOutputFormat(lastUsedFilter, outputFile)
self.outSelector.setFilename(outputFile)
self.lastEncoding = encoding
@@ -96,8 +99,15 @@ class GdalToolsDialog(QWidget, Ui_Widget, BasePluginWidget):
if True: # XXX in this moment the -i argument is not optional
arguments.append("-i")
arguments.append(unicode(self.intervalDSpinBox.value()))
+
+ outputFn = self.getOutputFileName()
+ if outputFn:
+ arguments.append("-f")
+ arguments.append(self.outputFormat)
+
arguments.append(self.getInputFileName())
arguments.append(self.outSelector.filename())
+
return arguments
def getInputFileName(self):
diff --git a/python/plugins/GdalTools/tools/doProjection.py b/python/plugins/GdalTools/tools/doProjection.py
index e01f3ec..8869cae 100644
--- a/python/plugins/GdalTools/tools/doProjection.py
+++ b/python/plugins/GdalTools/tools/doProjection.py
@@ -42,7 +42,7 @@ class GdalToolsDialog(QWidget, Ui_Widget, BaseBatchWidget):
self.iface = iface
self.setupUi(self)
- BaseBatchWidget.__init__(self, self.iface, "gdalwarp")
+ BaseBatchWidget.__init__(self, self.iface, "gdal_translate")
self.inSelector.setType(self.inSelector.FILE)
@@ -112,7 +112,7 @@ class GdalToolsDialog(QWidget, Ui_Widget, BaseBatchWidget):
def getArguments(self):
arguments = []
if self.desiredSRSEdit.text():
- arguments.append("-t_srs")
+ arguments.append("-a_srs")
arguments.append(self.desiredSRSEdit.text())
if self.batchCheck.isChecked():
return arguments
diff --git a/python/plugins/db_manager/db_plugins/oracle/connector.py b/python/plugins/db_manager/db_plugins/oracle/connector.py
index e1dfb30..6fb46ac 100644
--- a/python/plugins/db_manager/db_plugins/oracle/connector.py
+++ b/python/plugins/db_manager/db_plugins/oracle/connector.py
@@ -86,6 +86,8 @@ class OracleDBConnector(DBConnector):
'allowGeometrylessTables').lower() == "true"
self.onlyExistingTypes = uri.param(
'onlyExistingTypes').lower() == "true"
+ self.includeGeoAttributes = uri.param(
+ 'includeGeoAttributes').lower() == "true"
# For refreshing
self.populated = False
diff --git a/python/plugins/db_manager/db_plugins/oracle/plugin.py b/python/plugins/db_manager/db_plugins/oracle/plugin.py
index 0a10703..ce5d0f1 100644
--- a/python/plugins/db_manager/db_plugins/oracle/plugin.py
+++ b/python/plugins/db_manager/db_plugins/oracle/plugin.py
@@ -112,6 +112,8 @@ class OracleDBPlugin(DBPlugin):
settings.value("allowGeometrylessTables", False, type=bool)))
uri.setParam('onlyExistingTypes', unicode(
settings.value("onlyExistingTypes", False, type=bool)))
+ uri.setParam('includeGeoAttributes', unicode(
+ settings.value("includeGeoAttributes", False, type=bool)))
settings.endGroup()
diff --git a/python/plugins/db_manager/db_plugins/postgis/connector.py b/python/plugins/db_manager/db_plugins/postgis/connector.py
index ec23ade..4e8c856 100644
--- a/python/plugins/db_manager/db_plugins/postgis/connector.py
+++ b/python/plugins/db_manager/db_plugins/postgis/connector.py
@@ -125,8 +125,8 @@ class PostGisDBConnector(DBConnector):
self.connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
- c = self._execute(None, u"SELECT current_user")
- self.user = self._fetchone(c)
+ c = self._execute(None, u"SELECT current_user,current_database()")
+ self.user, self.dbname = self._fetchone(c)
self._close_cursor(c)
self._checkSpatial()
diff --git a/python/plugins/db_manager/db_plugins/postgis/info_model.py b/python/plugins/db_manager/db_plugins/postgis/info_model.py
index c4b0923..83299eb 100644
--- a/python/plugins/db_manager/db_plugins/postgis/info_model.py
+++ b/python/plugins/db_manager/db_plugins/postgis/info_model.py
@@ -22,10 +22,21 @@ email : brush.tyler at gmail.com
from PyQt4.QtGui import QApplication
-from ..info_model import TableInfo, VectorTableInfo, RasterTableInfo
+from ..info_model import TableInfo, VectorTableInfo, RasterTableInfo, DatabaseInfo
from ..html_elems import HtmlSection, HtmlParagraph, HtmlTable, HtmlTableHeader, HtmlTableCol
+class PGDatabaseInfo(DatabaseInfo):
+
+ def connectionDetails(self):
+ tbl = [
+ (QApplication.translate("DBManagerPlugin", "Host:"), self.db.connector.host),
+ (QApplication.translate("DBManagerPlugin", "User:"), self.db.connector.user),
+ (QApplication.translate("DBManagerPlugin", "Database:"), self.db.connector.dbname)
+ ]
+ return HtmlTable(tbl)
+
+
class PGTableInfo(TableInfo):
def __init__(self, table):
diff --git a/python/plugins/db_manager/db_plugins/postgis/plugin.py b/python/plugins/db_manager/db_plugins/postgis/plugin.py
index 3f42a4c..1be0d17 100644
--- a/python/plugins/db_manager/db_plugins/postgis/plugin.py
+++ b/python/plugins/db_manager/db_plugins/postgis/plugin.py
@@ -111,6 +111,10 @@ class PGDatabase(Database):
def dataTablesFactory(self, row, db, schema=None):
return PGTable(row, db, schema)
+ def info(self):
+ from .info_model import PGDatabaseInfo
+ return PGDatabaseInfo(self)
+
def vectorTablesFactory(self, row, db, schema=None):
return PGVectorTable(row, db, schema)
diff --git a/python/plugins/db_manager/dlg_sql_window.py b/python/plugins/db_manager/dlg_sql_window.py
index e62719e..4efbf95 100644
--- a/python/plugins/db_manager/dlg_sql_window.py
+++ b/python/plugins/db_manager/dlg_sql_window.py
@@ -70,6 +70,7 @@ class DlgSqlWindow(QWidget, Ui_Dialog):
self.editSql.setFocus()
self.editSql.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self.editSql.setMarginVisible(True)
self.initCompleter()
# allow copying results
diff --git a/python/plugins/fTools/tools/doGeometry.py b/python/plugins/fTools/tools/doGeometry.py
index 4f997a7..7ecdc49 100644
--- a/python/plugins/fTools/tools/doGeometry.py
+++ b/python/plugins/fTools/tools/doGeometry.py
@@ -598,22 +598,22 @@ class geometryThread(QThread):
writer = QgsVectorFileWriter(self.myName, self.myEncoding, vprovider.fields(),
QGis.WKBPoint, vprovider.crs())
inFeat = QgsFeature()
- outFeat = QgsFeature()
nFeat = vprovider.featureCount()
nElement = 0
self.emit(SIGNAL("runStatus( PyQt_PyObject )"), 0)
self.emit(SIGNAL("runRange( PyQt_PyObject )"), (0, nFeat))
fit = vprovider.getFeatures()
while fit.nextFeature(inFeat):
+ outFeat = QgsFeature()
nElement += 1
self.emit(SIGNAL("runStatus( PyQt_PyObject )"), nElement)
- inGeom = inFeat.geometry()
+ if inFeat.constGeometry():
+ inGeom = inFeat.geometry()
+ outGeom = inGeom.centroid()
+ outFeat.setGeometry(QgsGeometry(outGeom))
+
atMap = inFeat.attributes()
- outGeom = inGeom.centroid()
- if outGeom is None:
- return "math_error"
outFeat.setAttributes(atMap)
- outFeat.setGeometry(QgsGeometry(outGeom))
writer.addFeature(outFeat)
del writer
return True
diff --git a/python/plugins/fTools/tools/doPointsInPolygon.py b/python/plugins/fTools/tools/doPointsInPolygon.py
index 1b0d28d..84bf373 100644
--- a/python/plugins/fTools/tools/doPointsInPolygon.py
+++ b/python/plugins/fTools/tools/doPointsInPolygon.py
@@ -299,6 +299,9 @@ class PointsInPolygonThread(QThread):
value = math.sqrt(value)
atMap.append(value)
+ else: # no intersection - store at least the zero count
+ atMap.append(0)
+
outFeat.setAttributes(atMap)
writer.addFeature(outFeat)
diff --git a/python/plugins/processing/algs/gdal/GdalAlgorithm.py b/python/plugins/processing/algs/gdal/GdalAlgorithm.py
index e50a348..9b65d14 100644
--- a/python/plugins/processing/algs/gdal/GdalAlgorithm.py
+++ b/python/plugins/processing/algs/gdal/GdalAlgorithm.py
@@ -50,10 +50,16 @@ class GdalAlgorithm(GeoAlgorithm):
def processAlgorithm(self, progress):
commands = self.getConsoleCommands()
layers = dataobjects.getVectorLayers()
+ supported = dataobjects.getSupportedOutputVectorLayerExtensions()
for i, c in enumerate(commands):
for layer in layers:
if layer.source() in c:
- c = c.replace(layer.source(), dataobjects.exportVectorLayer(layer))
+ exported = dataobjects.exportVectorLayer(layer, supported)
+ exportedFileName = os.path.splitext(os.path.split(exported)[1])[0]
+ c = c.replace(layer.source(), exported)
+ if os.path.isfile(layer.source()):
+ fileName = os.path.splitext(os.path.split(layer.source())[1])[0]
+ c = c.replace(fileName, exportedFileName)
commands[i] = c
GdalUtils.runGdal(commands, progress)
diff --git a/python/plugins/processing/algs/gdal/GdalUtils.py b/python/plugins/processing/algs/gdal/GdalUtils.py
index 2cb7781..d9e71aa 100644
--- a/python/plugins/processing/algs/gdal/GdalUtils.py
+++ b/python/plugins/processing/algs/gdal/GdalUtils.py
@@ -29,7 +29,7 @@ import os
import subprocess
import platform
from PyQt4.QtCore import QSettings
-from qgis.core import QgsApplication
+from qgis.core import QgsApplication, QgsVectorFileWriter
from processing.core.ProcessingLog import ProcessingLog
try:
@@ -131,6 +131,18 @@ class GdalUtils:
return allexts
@staticmethod
+ def getVectorDriverFromFileName(filename):
+ ext = os.path.splitext(filename)[1]
+ if ext == '':
+ return 'ESRI Shapefile'
+
+ formats = QgsVectorFileWriter.supportedFiltersAndFormats()
+ for k, v in formats.iteritems():
+ if ext in k:
+ return v
+ return 'ESRI Shapefile'
+
+ @staticmethod
def getFormatShortNameFromFilename(filename):
ext = filename[filename.rfind('.') + 1:]
supported = GdalUtils.getSupportedRasters()
diff --git a/python/plugins/processing/algs/gdal/contour.py b/python/plugins/processing/algs/gdal/contour.py
index eeefa6c..cd2ba3d 100644
--- a/python/plugins/processing/algs/gdal/contour.py
+++ b/python/plugins/processing/algs/gdal/contour.py
@@ -62,6 +62,7 @@ class contour(GdalAlgorithm):
self.tr('Contours')))
def getConsoleCommands(self):
+ output = self.getOutputValue(self.OUTPUT_VECTOR)
interval = unicode(self.getParameterValue(self.INTERVAL))
fieldName = unicode(self.getParameterValue(self.FIELD_NAME))
extra = self.getParameterValue(self.EXTRA)
@@ -75,10 +76,14 @@ class contour(GdalAlgorithm):
arguments.append('-i')
arguments.append(interval)
+ driver = GdalUtils.getVectorDriverFromFileName(output)
+ arguments.append('-f')
+ arguments.append(driver)
+
if extra and len(extra) > 0:
arguments.append(extra)
arguments.append(self.getParameterValue(self.INPUT_RASTER))
- arguments.append(self.getOutputValue(self.OUTPUT_VECTOR))
+ arguments.append(output)
return ['gdal_contour', GdalUtils.escapeAndJoin(arguments)]
diff --git a/python/plugins/processing/algs/grass/GrassUtils.py b/python/plugins/processing/algs/grass/GrassUtils.py
index 2d6c99e..1adcf29 100644
--- a/python/plugins/processing/algs/grass/GrassUtils.py
+++ b/python/plugins/processing/algs/grass/GrassUtils.py
@@ -91,11 +91,14 @@ class GrassUtils:
folder = None
if folder is None:
if isWindows():
- testfolder = os.path.dirname(QgsApplication.prefixPath())
+ if "OSGEO4W_ROOT" in os.environ:
+ testfolder = os.path.join(unicode(os.environ['OSGEO4W_ROOT']), "apps")
+ else:
+ testfolder = unicode(QgsApplication.prefixPath())
testfolder = os.path.join(testfolder, 'grass')
if os.path.isdir(testfolder):
for subfolder in os.listdir(testfolder):
- if subfolder.startswith('grass'):
+ if subfolder.startswith('grass-6'):
folder = os.path.join(testfolder, subfolder)
break
else:
@@ -110,10 +113,10 @@ class GrassUtils:
folder = ProcessingConfig.getSetting(GrassUtils.GRASS_WIN_SHELL) or ''
if not os.path.exists(folder):
folder = None
- if folder is None:
+ if folder is None and GrassUtils.grassPath():
folder = os.path.dirname(unicode(QgsApplication.prefixPath()))
folder = os.path.join(folder, 'msys')
- return folder
+ return folder or ''
@staticmethod
def grassDescriptionPath():
diff --git a/python/plugins/processing/algs/grass7/Grass7Utils.py b/python/plugins/processing/algs/grass7/Grass7Utils.py
index 79ac28d..34b660f 100644
--- a/python/plugins/processing/algs/grass7/Grass7Utils.py
+++ b/python/plugins/processing/algs/grass7/Grass7Utils.py
@@ -88,7 +88,10 @@ class Grass7Utils:
folder = None
if folder is None:
if isWindows():
- testfolder = os.path.dirname(unicode(QgsApplication.prefixPath()))
+ if "OSGEO4W_ROOT" in os.environ:
+ testfolder = os.path.join(unicode(os.environ['OSGEO4W_ROOT']), "apps")
+ else:
+ testfolder = unicode(QgsApplication.prefixPath())
testfolder = os.path.join(testfolder, 'grass')
if os.path.isdir(testfolder):
for subfolder in os.listdir(testfolder):
@@ -100,7 +103,7 @@ class Grass7Utils:
if not os.path.isdir(folder):
folder = '/Applications/GRASS-7.0.app/Contents/MacOS'
- return folder
+ return folder or ''
@staticmethod
def grassDescriptionPath():
diff --git a/python/plugins/processing/algs/qgis/AutoincrementalField.py b/python/plugins/processing/algs/qgis/AutoincrementalField.py
index 998c728..df7db61 100644
--- a/python/plugins/processing/algs/qgis/AutoincrementalField.py
+++ b/python/plugins/processing/algs/qgis/AutoincrementalField.py
@@ -62,7 +62,7 @@ class AutoincrementalField(GeoAlgorithm):
geom = feat.geometry()
outFeat.setGeometry(geom)
attrs = feat.attributes()
- attrs.append(count)
+ attrs.append(current)
outFeat.setAttributes(attrs)
writer.addFeature(outFeat)
del writer
diff --git a/python/plugins/processing/algs/qgis/Clip.py b/python/plugins/processing/algs/qgis/Clip.py
index d8d1d5d..0df9c36 100644
--- a/python/plugins/processing/algs/qgis/Clip.py
+++ b/python/plugins/processing/algs/qgis/Clip.py
@@ -34,10 +34,6 @@ from processing.core.parameters import ParameterVector
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector
-GEOM_25D = [QGis.WKBPoint25D, QGis.WKBLineString25D, QGis.WKBPolygon25D,
- QGis.WKBMultiPoint25D, QGis.WKBMultiLineString25D,
- QGis.WKBMultiPolygon25D]
-
class Clip(GeoAlgorithm):
@@ -60,11 +56,6 @@ class Clip(GeoAlgorithm):
layerB = dataobjects.getObjectFromUri(
self.getParameterValue(Clip.OVERLAY))
- geomType = layerA.dataProvider().geometryType()
- if geomType in GEOM_25D:
- raise GeoAlgorithmExecutionException(
- self.tr('Input layer has unsupported geometry type {}').format(geomType))
-
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
layerA.pendingFields(),
layerA.dataProvider().geometryType(),
diff --git a/python/plugins/processing/algs/qgis/ConvexHull.py b/python/plugins/processing/algs/qgis/ConvexHull.py
index 4b56831..5ade572 100644
--- a/python/plugins/processing/algs/qgis/ConvexHull.py
+++ b/python/plugins/processing/algs/qgis/ConvexHull.py
@@ -70,8 +70,8 @@ class ConvexHull(GeoAlgorithm):
if useField:
index = layer.fieldNameIndex(fieldName)
fType = layer.pendingFields()[index].type()
- if fType == QVariant.Int:
- f.setType(QVariant.Int)
+ if fType in [QVariant.Int, QVariant.UInt, QVariant.LongLong, QVariant.ULongLong]:
+ f.setType(fType)
f.setLength(20)
elif fType == QVariant.Double:
f.setType(QVariant.Double)
diff --git a/python/plugins/processing/algs/qgis/CreateConstantRaster.py b/python/plugins/processing/algs/qgis/CreateConstantRaster.py
index f877105..cb494ed 100644
--- a/python/plugins/processing/algs/qgis/CreateConstantRaster.py
+++ b/python/plugins/processing/algs/qgis/CreateConstantRaster.py
@@ -25,6 +25,8 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
+from osgeo import gdal
+
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.parameters import ParameterRaster
from processing.core.parameters import ParameterNumber
@@ -46,7 +48,8 @@ class CreateConstantRaster(GeoAlgorithm):
self.addParameter(ParameterRaster(self.INPUT,
self.tr('Reference layer')))
self.addParameter(ParameterNumber(self.NUMBER,
- self.tr('Constant value'), default=1.0))
+ self.tr('Constant value'),
+ default=1.0))
self.addOutput(OutputRaster(self.OUTPUT,
self.tr('Constant')))
@@ -54,10 +57,13 @@ class CreateConstantRaster(GeoAlgorithm):
def processAlgorithm(self, progress):
layer = dataobjects.getObjectFromUri(
self.getParameterValue(self.INPUT))
- value = self.getOutputValue(self.NUMBER)
+ value = self.getParameterValue(self.NUMBER)
output = self.getOutputFromName(self.OUTPUT)
+ raster = gdal.Open(layer.source(), gdal.GA_ReadOnly)
+ geoTransform = raster.GetGeoTransform()
+
cellsize = (layer.extent().xMaximum() - layer.extent().xMinimum()) \
/ layer.width()
@@ -68,7 +74,8 @@ class CreateConstantRaster(GeoAlgorithm):
layer.extent().yMaximum(),
cellsize,
1,
- self.crs,
+ layer.crs(),
+ geoTransform
)
- w.matrix[:] = value
+ w.matrix.fill(value)
w.close()
diff --git a/python/plugins/processing/algs/qgis/Difference.py b/python/plugins/processing/algs/qgis/Difference.py
index 3032631..d375c30 100644
--- a/python/plugins/processing/algs/qgis/Difference.py
+++ b/python/plugins/processing/algs/qgis/Difference.py
@@ -33,10 +33,6 @@ from processing.core.parameters import ParameterVector
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector
-GEOM_25D = [QGis.WKBPoint25D, QGis.WKBLineString25D, QGis.WKBPolygon25D,
- QGis.WKBMultiPoint25D, QGis.WKBMultiLineString25D,
- QGis.WKBMultiPolygon25D]
-
class Difference(GeoAlgorithm):
@@ -65,10 +61,6 @@ class Difference(GeoAlgorithm):
self.getParameterValue(Difference.OVERLAY))
geomType = layerA.dataProvider().geometryType()
- if geomType in GEOM_25D:
- raise GeoAlgorithmExecutionException(
- self.tr('Input layer has unsupported geometry type {}').format(geomType))
-
writer = self.getOutputFromName(
Difference.OUTPUT).getVectorWriter(layerA.pendingFields(),
geomType,
diff --git a/python/plugins/processing/algs/qgis/Dissolve.py b/python/plugins/processing/algs/qgis/Dissolve.py
index b063416..80af1fc 100644
--- a/python/plugins/processing/algs/qgis/Dissolve.py
+++ b/python/plugins/processing/algs/qgis/Dissolve.py
@@ -125,6 +125,7 @@ class Dissolve(GeoAlgorithm):
features = None
+ nElement = 0
for key, value in myDict.items():
nElement += 1
progress.setPercentage(int(nElement * 100 / nFeat))
diff --git a/python/plugins/processing/algs/qgis/ExtractByAttribute.py b/python/plugins/processing/algs/qgis/ExtractByAttribute.py
index 2d6fa06..481244c 100644
--- a/python/plugins/processing/algs/qgis/ExtractByAttribute.py
+++ b/python/plugins/processing/algs/qgis/ExtractByAttribute.py
@@ -95,7 +95,7 @@ class ExtractByAttribute(GeoAlgorithm):
raise GeoAlgorithmExecutionException(
self.tr('Operators %s can be used only with string fields.' % op))
- if fieldType in [QVariant.Int, QVariant.Double]:
+ if fieldType in [QVariant.Int, QVariant.Double, QVariant.UInt, QVariant.LongLong, QVariant.ULongLong]:
expr = '"%s" %s %s' % (fieldName, operator, value)
elif fieldType == QVariant.String:
if operator not in self.OPERATORS[-2:]:
diff --git a/python/plugins/processing/algs/qgis/HubDistance.py b/python/plugins/processing/algs/qgis/HubDistance.py
index de625b6..617318d 100644
--- a/python/plugins/processing/algs/qgis/HubDistance.py
+++ b/python/plugins/processing/algs/qgis/HubDistance.py
@@ -26,7 +26,7 @@ __copyright__ = '(C) 2010, Michael Minn'
__revision__ = '$Format:%H$'
from PyQt4.QtCore import QVariant
-from qgis.core import QGis, QgsField, QgsGeometry, QgsDistanceArea, QgsFeature
+from qgis.core import QGis, QgsField, QgsGeometry, QgsDistanceArea, QgsFeature, QgsFeatureRequest
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
@@ -106,12 +106,7 @@ class HubDistance(GeoAlgorithm):
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
fields, geomType, layerPoints.crs())
- # Create array of hubs in memory
- hubs = []
- features = vector.features(layerHubs)
- for f in features:
- hubs.append(Hub(f.geometry().boundingBox().center(),
- unicode(f[fieldName])))
+ index = vector.spatialindex(layerHubs)
distance = QgsDistanceArea()
distance.setSourceCrs(layerPoints.crs().srsid())
@@ -123,17 +118,13 @@ class HubDistance(GeoAlgorithm):
for current, f in enumerate(features):
src = f.geometry().boundingBox().center()
- closest = hubs[0]
- hubDist = distance.measureLine(src, closest.point)
-
- for hub in hubs:
- dist = distance.measureLine(src, hub.point)
- if dist < hubDist:
- closest = hub
- hubDist = dist
+ neighbors = index.nearestNeighbor(src, 1)
+ ft = layerHubs.getFeatures(QgsFeatureRequest().setFilterFid(neighbors[0])).next()
+ closest = ft.geometry().boundingBox().center()
+ hubDist = distance.measureLine(src, closest)
attributes = f.attributes()
- attributes.append(closest.name)
+ attributes.append(ft[fieldName])
if units == 'Feet':
attributes.append(hubDist * 3.2808399)
elif units == 'Miles':
@@ -142,8 +133,8 @@ class HubDistance(GeoAlgorithm):
attributes.append(hubDist / 1000.0)
elif units != 'Meters':
attributes.append(sqrt(
- pow(src.x() - closest.point.x(), 2.0) +
- pow(src.y() - closest.point.y(), 2.0)))
+ pow(src.x() - closest.x(), 2.0) +
+ pow(src.y() - closest.y(), 2.0)))
else:
attributes.append(hubDist)
@@ -153,16 +144,9 @@ class HubDistance(GeoAlgorithm):
if geomType == QGis.WKBPoint:
feat.setGeometry(QgsGeometry.fromPoint(src))
else:
- feat.setGeometry(QgsGeometry.fromPolyline([src, closest.point]))
+ feat.setGeometry(QgsGeometry.fromPolyline([src, closest]))
writer.addFeature(feat)
progress.setPercentage(int(current * total))
del writer
-
-
-class Hub:
-
- def __init__(self, point, name):
- self.point = point
- self.name = name
diff --git a/python/plugins/processing/algs/qgis/Intersection.py b/python/plugins/processing/algs/qgis/Intersection.py
index 832d97c..b71da9a 100644
--- a/python/plugins/processing/algs/qgis/Intersection.py
+++ b/python/plugins/processing/algs/qgis/Intersection.py
@@ -43,10 +43,6 @@ for key, value in wkbTypeGroups.items():
for const in value:
wkbTypeGroups[const] = key
-GEOM_25D = [QGis.WKBPoint25D, QGis.WKBLineString25D, QGis.WKBPolygon25D,
- QGis.WKBMultiPoint25D, QGis.WKBMultiLineString25D,
- QGis.WKBMultiPolygon25D]
-
class Intersection(GeoAlgorithm):
@@ -71,10 +67,6 @@ class Intersection(GeoAlgorithm):
vproviderA = vlayerA.dataProvider()
geomType = vproviderA.geometryType()
- if geomType in GEOM_25D:
- raise GeoAlgorithmExecutionException(
- self.tr('Input layer has unsupported geometry type {}').format(geomType))
-
fields = vector.combineVectorFields(vlayerA, vlayerB)
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields,
geomType, vproviderA.crs())
diff --git a/python/plugins/processing/algs/qgis/Merge.py b/python/plugins/processing/algs/qgis/Merge.py
index 626653f..0c9f750 100644
--- a/python/plugins/processing/algs/qgis/Merge.py
+++ b/python/plugins/processing/algs/qgis/Merge.py
@@ -92,7 +92,7 @@ class Merge(GeoAlgorithm):
sattributes = feature.attributes()
dattributes = []
for dindex, dfield in enumerate(fields):
- if (dfield.type() == QVariant.Int):
+ if (dfield.type() == QVariant.Int, QVariant.UInt, QVariant.LongLong, QVariant.ULongLong):
dattribute = 0
elif (dfield.type() == QVariant.Double):
dattribute = 0.0
diff --git a/python/plugins/processing/algs/qgis/SelectByAttribute.py b/python/plugins/processing/algs/qgis/SelectByAttribute.py
index cb8c368..49786f4 100644
--- a/python/plugins/processing/algs/qgis/SelectByAttribute.py
+++ b/python/plugins/processing/algs/qgis/SelectByAttribute.py
@@ -94,7 +94,7 @@ class SelectByAttribute(GeoAlgorithm):
raise GeoAlgorithmExecutionException(
self.tr('Operators %s can be used only with string fields.' % op))
- if fieldType in [QVariant.Int, QVariant.Double]:
+ if fieldType in [QVariant.Int, QVariant.Double, QVariant.UInt, QVariant.LongLong, QVariant.ULongLong]:
expr = '"%s" %s %s' % (fieldName, operator, value)
elif fieldType == QVariant.String:
if operator not in self.OPERATORS[-2:]:
diff --git a/python/plugins/processing/algs/qgis/SpatialJoin.py b/python/plugins/processing/algs/qgis/SpatialJoin.py
index 2474719..6fb5adc 100644
--- a/python/plugins/processing/algs/qgis/SpatialJoin.py
+++ b/python/plugins/processing/algs/qgis/SpatialJoin.py
@@ -113,7 +113,7 @@ class SpatialJoin(GeoAlgorithm):
else:
numFields = {}
for j in xrange(len(joinFields)):
- if joinFields[j].type() in [QVariant.Int, QVariant.Double]:
+ if joinFields[j].type() in [QVariant.Int, QVariant.Double, QVariant.LongLong, QVariant.UInt, QVariant.ULongLong]:
numFields[j] = []
for i in sumList:
field = QgsField(i + unicode(joinFields[j].name()), QVariant.Double, '', 24, 16)
diff --git a/python/plugins/processing/algs/qgis/SymmetricalDifference.py b/python/plugins/processing/algs/qgis/SymmetricalDifference.py
index 05f13c4..5392da2 100644
--- a/python/plugins/processing/algs/qgis/SymmetricalDifference.py
+++ b/python/plugins/processing/algs/qgis/SymmetricalDifference.py
@@ -33,10 +33,6 @@ from processing.core.parameters import ParameterVector
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector
-GEOM_25D = [QGis.WKBPoint25D, QGis.WKBLineString25D, QGis.WKBPolygon25D,
- QGis.WKBMultiPoint25D, QGis.WKBMultiLineString25D,
- QGis.WKBMultiPolygon25D]
-
class SymmetricalDifference(GeoAlgorithm):
@@ -64,10 +60,6 @@ class SymmetricalDifference(GeoAlgorithm):
providerB = layerB.dataProvider()
geomType = providerA.geometryType()
- if geomType in GEOM_25D:
- raise GeoAlgorithmExecutionException(
- self.tr('Input layer has unsupported geometry type {}').format(geomType))
-
fields = vector.combineVectorFields(layerA, layerB)
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
fields, geomType, providerA.crs())
diff --git a/python/plugins/processing/algs/qgis/Union.py b/python/plugins/processing/algs/qgis/Union.py
index 9d5e07c..f64c1ca 100644
--- a/python/plugins/processing/algs/qgis/Union.py
+++ b/python/plugins/processing/algs/qgis/Union.py
@@ -42,10 +42,6 @@ for key, value in wkbTypeGroups.items():
for const in value:
wkbTypeGroups[const] = key
-GEOM_25D = [QGis.WKBPoint25D, QGis.WKBLineString25D, QGis.WKBPolygon25D,
- QGis.WKBMultiPoint25D, QGis.WKBMultiLineString25D,
- QGis.WKBMultiPolygon25D]
-
class Union(GeoAlgorithm):
@@ -69,10 +65,6 @@ class Union(GeoAlgorithm):
vproviderA = vlayerA.dataProvider()
geomType = vproviderA.geometryType()
- if geomType in GEOM_25D:
- raise GeoAlgorithmExecutionException(
- self.tr('Input layer has unsupported geometry type {}').format(geomType))
-
fields = vector.combineVectorFields(vlayerA, vlayerB)
writer = self.getOutputFromName(Union.OUTPUT).getVectorWriter(fields,
geomType, vproviderA.crs())
diff --git a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
index 8436969..872d347 100644
--- a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
+++ b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
@@ -217,7 +217,7 @@ class FieldsMappingModel(QtCore.QAbstractTableModel):
'type': field.type(),
'length': field.length(),
'precision': field.precision(),
- 'expression': field.name()}
+ 'expression': QgsExpression.quotedColumnRef(field.name())}
def loadLayerFields(self, layer):
self.beginResetModel()
@@ -289,7 +289,10 @@ class FieldDelegate(QtGui.QStyledItemDelegate):
elif fieldType == QgsExpression:
(value, isExpression, isValid) = editor.currentField()
- model.setData(index, value)
+ if isExpression is True:
+ model.setData(index, value)
+ else:
+ model.setData(index, QgsExpression.quotedColumnRef(value))
else:
QtGui.QStyledItemDelegate.setModelData(self, editor, model, index)
diff --git a/python/plugins/processing/algs/r/RAlgorithm.py b/python/plugins/processing/algs/r/RAlgorithm.py
index 051db38..1fe7b30 100644
--- a/python/plugins/processing/algs/r/RAlgorithm.py
+++ b/python/plugins/processing/algs/r/RAlgorithm.py
@@ -26,6 +26,7 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
import os
+import json
from PyQt4.QtGui import QIcon
@@ -43,6 +44,7 @@ from processing.core.parameters import ParameterBoolean
from processing.core.parameters import ParameterSelection
from processing.core.parameters import ParameterTableField
from processing.core.parameters import ParameterExtent
+from processing.core.parameters import ParameterCrs
from processing.core.parameters import ParameterFile
from processing.core.outputs import OutputTable
from processing.core.outputs import OutputVector
@@ -149,84 +151,128 @@ class RAlgorithm(GeoAlgorithm):
tokens = line.split('=')
desc = self.createDescriptiveName(tokens[0])
if tokens[1].lower().strip() == 'group':
- self.group = tokens[0]
+ self.group = self.i18n_group = tokens[0]
return
- if tokens[1].lower().strip().startswith('raster'):
- param = ParameterRaster(tokens[0], desc, False)
- elif tokens[1].lower().strip() == 'vector':
- param = ParameterVector(tokens[0], desc,
+ if tokens[1].lower().strip() == 'name':
+ self.name = self.i18n_name = tokens[0]
+ return
+
+ if tokens[1].lower().strip().startswith('output'):
+ outToken = tokens[1].strip()[len('output') + 1:]
+ out = self.processOutputParameterToken(outToken)
+
+ elif tokens[1].lower().strip().startswith('optional'):
+ optToken = tokens[1].strip()[len('optional') + 1:]
+ param = self.processInputParameterToken(optToken, tokens[0])
+ if param:
+ param.optional = True
+
+ else:
+ param = self.processInputParameterToken(tokens[1], tokens[0])
+
+ if param is not None:
+ self.addParameter(param)
+ elif out is not None:
+ out.name = tokens[0]
+ out.description = tokens[0]
+ self.addOutput(out)
+ else:
+ raise WrongScriptException(
+ self.tr('Could not load R script: %s.\n Problem with line %s' % (self.descriptionFile, line)))
+
+ def processInputParameterToken(self, token, name):
+ param = None
+
+ desc = self.createDescriptiveName(name)
+
+ if token.lower().strip().startswith('raster'):
+ param = ParameterRaster(name, desc, False)
+ elif token.lower().strip() == 'vector':
+ param = ParameterVector(name, desc,
[ParameterVector.VECTOR_TYPE_ANY])
- elif tokens[1].lower().strip() == 'vector point':
- param = ParameterVector(tokens[0], desc,
+ elif token.lower().strip() == 'vector point':
+ param = ParameterVector(name, desc,
[ParameterVector.VECTOR_TYPE_POINT])
- elif tokens[1].lower().strip() == 'vector line':
- param = ParameterVector(tokens[0], desc,
+ elif token.lower().strip() == 'vector line':
+ param = ParameterVector(name, desc,
[ParameterVector.VECTOR_TYPE_LINE])
- elif tokens[1].lower().strip() == 'vector polygon':
- param = ParameterVector(tokens[0], desc,
+ elif token.lower().strip() == 'vector polygon':
+ param = ParameterVector(name, desc,
[ParameterVector.VECTOR_TYPE_POLYGON])
- elif tokens[1].lower().strip() == 'table':
- param = ParameterTable(tokens[0], desc, False)
- elif tokens[1].lower().strip().startswith('multiple raster'):
- param = ParameterMultipleInput(tokens[0], desc,
+ elif token.lower().strip() == 'table':
+ param = ParameterTable(name, desc, False)
+ elif token.lower().strip().startswith('multiple raster'):
+ param = ParameterMultipleInput(name, desc,
ParameterMultipleInput.TYPE_RASTER)
param.optional = False
- elif tokens[1].lower().strip() == 'multiple vector':
- param = ParameterMultipleInput(tokens[0], desc,
+ elif token.lower().strip() == 'multiple vector':
+ param = ParameterMultipleInput(name, desc,
ParameterMultipleInput.TYPE_VECTOR_ANY)
param.optional = False
- elif tokens[1].lower().strip().startswith('selection'):
- options = tokens[1].strip()[len('selection'):].split(';')
- param = ParameterSelection(tokens[0], desc, options)
- elif tokens[1].lower().strip().startswith('boolean'):
- default = tokens[1].strip()[len('boolean') + 1:]
- param = ParameterBoolean(tokens[0], desc, default)
- elif tokens[1].lower().strip().startswith('number'):
- try:
- default = float(tokens[1].strip()[len('number') + 1:])
- param = ParameterNumber(tokens[0], desc, default=default)
- except:
- raise WrongScriptException(
- self.tr('Could not load R script: %s.\n Problem with line %s' % (self.descriptionFile, line)))
- elif tokens[1].lower().strip().startswith('field'):
- field = tokens[1].strip()[len('field') + 1:]
+ elif token.lower().strip().startswith('selection'):
+ options = token.strip()[len('selection'):].split(';')
+ param = ParameterSelection(name, desc, options)
+ elif token.lower().strip().startswith('boolean'):
+ default = token.strip()[len('boolean') + 1:]
+ if default:
+ param = ParameterBoolean(name, desc, default)
+ else:
+ param = ParameterBoolean(name, desc)
+ elif token.lower().strip().startswith('number'):
+ default = token.strip()[len('number') + 1:]
+ if default:
+ param = ParameterNumber(name, desc, default=default)
+ else:
+ param = ParameterNumber(name, desc)
+ elif token.lower().strip().startswith('field'):
+ field = token.strip()[len('field') + 1:]
found = False
for p in self.parameters:
if p.name == field:
found = True
break
if found:
- param = ParameterTableField(tokens[0], tokens[0], field)
- elif tokens[1].lower().strip() == 'extent':
- param = ParameterExtent(tokens[0], desc)
- elif tokens[1].lower().strip() == 'file':
- param = ParameterFile(tokens[0], desc, False)
- elif tokens[1].lower().strip() == 'folder':
- param = ParameterFile(tokens[0], desc, True)
- elif tokens[1].lower().strip().startswith('string'):
- default = tokens[1].strip()[len('string') + 1:]
- param = ParameterString(tokens[0], desc, default)
- elif tokens[1].lower().strip().startswith('longstring'):
- default = tokens[1].strip()[len('longstring') + 1:]
- param = ParameterString(tokens[0], desc, default, multiline=True)
- elif tokens[1].lower().strip().startswith('output raster'):
+ param = ParameterTableField(name, desc, field)
+ elif token.lower().strip() == 'extent':
+ param = ParameterExtent(name, desc)
+ elif token.lower().strip() == 'file':
+ param = ParameterFile(name, desc, False)
+ elif token.lower().strip() == 'folder':
+ param = ParameterFile(name, desc, True)
+ elif token.lower().strip().startswith('string'):
+ default = token.strip()[len('string') + 1:]
+ if default:
+ param = ParameterString(name, desc, default)
+ else:
+ param = ParameterString(name, desc)
+ elif token.lower().strip().startswith('longstring'):
+ default = token.strip()[len('longstring') + 1:]
+ if default:
+ param = ParameterString(name, desc, default, multiline=True)
+ else:
+ param = ParameterString(name, desc, multiline=True)
+ elif token.lower().strip() == 'crs':
+ default = token.strip()[len('crs') + 1:]
+ if default:
+ param = ParameterCrs(name, desc, default)
+ else:
+ param = ParameterCrs(name, desc)
+
+ return param
+
+ def processOutputParameterToken(self, token):
+ out = None
+
+ if token.lower().strip().startswith('raster'):
out = OutputRaster()
- elif tokens[1].lower().strip().startswith('output vector'):
+ elif token.lower().strip().startswith('vector'):
out = OutputVector()
- elif tokens[1].lower().strip().startswith('output table'):
+ elif token.lower().strip().startswith('table'):
out = OutputTable()
- elif tokens[1].lower().strip().startswith('output file'):
+ elif token.lower().strip().startswith('file'):
out = OutputFile()
- if param is not None:
- self.addParameter(param)
- elif out is not None:
- out.name = tokens[0]
- out.description = tokens[0]
- self.addOutput(out)
- else:
- raise WrongScriptException(
- self.tr('Could not load R script: %s.\n Problem with line %s' % (self.descriptionFile, line)))
+ return out
def processAlgorithm(self, progress):
if isWindows():
@@ -275,7 +321,7 @@ class RAlgorithm(GeoAlgorithm):
value = value + '.tif'
commands.append('writeGDAL(' + out.name + ',"' + value
+ '")')
- if isinstance(out, OutputVector):
+ elif isinstance(out, OutputVector):
value = out.value
if not value.endswith('shp'):
value = value + '.shp'
@@ -284,6 +330,10 @@ class RAlgorithm(GeoAlgorithm):
filename = filename[:-4]
commands.append('writeOGR(' + out.name + ',"' + value + '","'
+ filename + '", driver="ESRI Shapefile")')
+ elif isinstance(out, OutputTable):
+ value = out.value
+ value = value.replace('\\', '/')
+ commands.append('write.csv(' + out.name + ',"' + value + '")')
if self.showPlots:
commands.append('dev.off()')
@@ -310,42 +360,70 @@ class RAlgorithm(GeoAlgorithm):
for param in self.parameters:
if isinstance(param, ParameterRaster):
- value = param.value
- value = value.replace('\\', '/')
- if self.passFileNames:
- commands.append(param.name + ' = "' + value + '"')
- elif self.useRasterPackage:
- commands.append(param.name + ' = ' + 'brick("' + value
- + '")')
+ if param.value is None:
+ commands.append(param.name + '= NULL')
else:
- commands.append(param.name + ' = ' + 'readGDAL("' + value
- + '")')
- if isinstance(param, ParameterVector):
- value = param.getSafeExportedLayer()
- value = value.replace('\\', '/')
- filename = os.path.basename(value)
- filename = filename[:-4]
- folder = os.path.dirname(value)
- if self.passFileNames:
- commands.append(param.name + ' = "' + value + '"')
+ value = param.value
+ value = value.replace('\\', '/')
+ if self.passFileNames:
+ commands.append(param.name + ' = "' + value + '"')
+ elif self.useRasterPackage:
+ commands.append(param.name + ' = ' + 'brick("' + value
+ + '")')
+ else:
+ commands.append(param.name + ' = ' + 'readGDAL("' + value
+ + '")')
+ elif isinstance(param, ParameterVector):
+ if param.value is None:
+ commands.append(param.name + '= NULL')
+ else:
+ value = param.getSafeExportedLayer()
+ value = value.replace('\\', '/')
+ filename = os.path.basename(value)
+ filename = filename[:-4]
+ folder = os.path.dirname(value)
+ if self.passFileNames:
+ commands.append(param.name + ' = "' + value + '"')
+ else:
+ commands.append(param.name + ' = readOGR("' + folder
+ + '",layer="' + filename + '")')
+ elif isinstance(param, ParameterTable):
+ if param.value is None:
+ commands.append(param.name + '= NULL')
else:
- commands.append(param.name + ' = readOGR("' + folder
- + '",layer="' + filename + '")')
- if isinstance(param, ParameterTable):
- value = param.value
- if not value.lower().endswith('csv'):
- raise GeoAlgorithmExecutionException(
- 'Unsupported input file format.\n' + value)
- if self.passFileNames:
- commands.append(param.name + ' = "' + value + '"')
+ value = param.value
+ if not value.lower().endswith('csv'):
+ raise GeoAlgorithmExecutionException(
+ 'Unsupported input file format.\n' + value)
+ if self.passFileNames:
+ commands.append(param.name + ' = "' + value + '"')
+ else:
+ commands.append(param.name + ' <- read.csv("' + value
+ + '", head=TRUE, sep=",")')
+ elif isinstance(param, ParameterExtent):
+ if param.value:
+ tokens = unicode(param.value).split(',')
+ # Extent from raster package is "xmin, xmax, ymin, ymax" like in Processing
+ # http://www.inside-r.org/packages/cran/raster/docs/Extent
+ commands.append(param.name + ' = extent(' + tokens[0] + ',' + tokens[1] + ',' + tokens[2] + ',' + tokens[3] + ')')
else:
- commands.append(param.name + ' <- read.csv("' + value
- + '", head=TRUE, sep=",")')
+ commands.append(param.name + ' = NULL')
+ elif isinstance(param, ParameterCrs):
+ if param.value is None:
+ commands.append(param.name + '= NULL')
+ else:
+ commands.append(param.name + ' = "' + param.value + '"')
elif isinstance(param, (ParameterTableField, ParameterString,
ParameterFile)):
- commands.append(param.name + '="' + param.value + '"')
+ if param.value is None:
+ commands.append(param.name + '= NULL')
+ else:
+ commands.append(param.name + '="' + param.value + '"')
elif isinstance(param, (ParameterNumber, ParameterSelection)):
- commands.append(param.name + '=' + unicode(param.value))
+ if param.value is None:
+ commands.append(param.name + '= NULL')
+ else:
+ commands.append(param.name + '=' + unicode(param.value))
elif isinstance(param, ParameterBoolean):
if param.value:
commands.append(param.name + '=TRUE')
@@ -416,6 +494,36 @@ class RAlgorithm(GeoAlgorithm):
else:
return False, None
+ def shortHelp(self):
+ if self.descriptionFile is None:
+ return None
+ helpFile = unicode(self.descriptionFile) + '.help'
+ if os.path.exists(helpFile):
+ with open(helpFile) as f:
+ try:
+ descriptions = json.load(f)
+ if 'ALG_DESC' in descriptions:
+ return self._formatHelp(unicode(descriptions['ALG_DESC']))
+ except:
+ return None
+ return None
+
+ def getParameterDescriptions(self):
+ descs = {}
+ if self.descriptionFile is None:
+ return descs
+ helpFile = unicode(self.descriptionFile) + '.help'
+ if os.path.exists(helpFile):
+ with open(helpFile) as f:
+ try:
+ descriptions = json.load(f)
+ for param in self.parameters:
+ if param.name in descriptions:
+ descs[param.name] = unicode(descriptions[param.name])
+ except:
+ return descs
+ return descs
+
def checkBeforeOpeningParametersDialog(self):
msg = RUtils.checkRIsInstalled()
if msg is not None:
diff --git a/python/plugins/processing/core/Processing.py b/python/plugins/processing/core/Processing.py
index 426f37f..d889ff2 100644
--- a/python/plugins/processing/core/Processing.py
+++ b/python/plugins/processing/core/Processing.py
@@ -358,7 +358,7 @@ class Processing:
if kwargs is not None and "progress" in kwargs.keys():
progress = kwargs["progress"]
elif iface is not None:
- progress = MessageBarProgress()
+ progress = MessageBarProgress(alg.name)
ret = runalg(alg, progress)
if ret:
diff --git a/python/plugins/processing/gui/AlgorithmDialogBase.py b/python/plugins/processing/gui/AlgorithmDialogBase.py
index 3c93881..4dd1ad5 100644
--- a/python/plugins/processing/gui/AlgorithmDialogBase.py
+++ b/python/plugins/processing/gui/AlgorithmDialogBase.py
@@ -31,6 +31,7 @@ import webbrowser
from PyQt4 import uic
from PyQt4.QtCore import QCoreApplication, QSettings, QByteArray, SIGNAL, QUrl
from PyQt4.QtGui import QApplication, QDialogButtonBox, QDesktopWidget
+from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply
from qgis.utils import iface
from qgis.core import *
@@ -64,9 +65,9 @@ class AlgorithmDialogBase(BASE, WIDGET):
self.setWindowTitle(AlgorithmClassification.getDisplayName(self.alg))
- desktop = QDesktopWidget()
- if desktop.physicalDpiX() > 96:
- self.textHelp.setZoomFactor(desktop.physicalDpiX() / 96)
+ #~ desktop = QDesktopWidget()
+ #~ if desktop.physicalDpiX() > 96:
+ #~ self.textHelp.setZoomFactor(desktop.physicalDpiX() / 96)
algHelp = self.alg.shortHelp()
if algHelp is None:
@@ -84,20 +85,22 @@ class AlgorithmDialogBase(BASE, WIDGET):
def linkClicked(url):
webbrowser.open(url.toString())
- self.textShortHelp.connect(self.textShortHelp, SIGNAL("anchorClicked(const QUrl&)"), linkClicked)
- self.textHelp.page().setNetworkAccessManager(QgsNetworkAccessManager.instance())
+ self.textShortHelp.anchorClicked.connect(linkClicked)
isText, algHelp = self.alg.help()
if algHelp is not None:
algHelp = algHelp if isText else QUrl(algHelp)
try:
if isText:
- self.textHelp.setHtml(algHelp)
+ self.txtHelp.setHtml(algHelp)
else:
- self.textHelp.settings().clearMemoryCaches()
- self.textHelp.load(algHelp)
- except:
+ html = self.tr('<p>Downloading algorithm help... Please wait.</p>')
+ self.txtHelp.setHtml(html)
+ rq = QNetworkRequest(algHelp)
+ self.reply = QgsNetworkAccessManager.instance().get(rq)
+ self.reply.finished.connect(self.requestFinished)
+ except Exception, e:
self.tabWidget.removeTab(2)
else:
self.tabWidget.removeTab(2)
@@ -105,6 +108,16 @@ class AlgorithmDialogBase(BASE, WIDGET):
self.showDebug = ProcessingConfig.getSetting(
ProcessingConfig.SHOW_DEBUG_IN_DIALOG)
+ def requestFinished(self):
+ """Change the webview HTML content"""
+ reply = self.sender()
+ if reply.error() != QNetworkReply.NoError:
+ html = self.tr('<h2>No help available for this algorithm</h2><p>{}</p>'.format(reply.errorString()))
+ else:
+ html = unicode(reply.readAll())
+ reply.deleteLater()
+ self.txtHelp.setHtml(html)
+
def closeEvent(self, evt):
self.settings.setValue("/Processing/dialogBase", self.saveGeometry())
super(AlgorithmDialogBase, self).closeEvent(evt)
diff --git a/python/plugins/processing/gui/GetScriptsAndModels.py b/python/plugins/processing/gui/GetScriptsAndModels.py
index d928f9d..76487e6 100644
--- a/python/plugins/processing/gui/GetScriptsAndModels.py
+++ b/python/plugins/processing/gui/GetScriptsAndModels.py
@@ -207,10 +207,10 @@ class GetScriptsAndModelsDialog(BASE, WIDGET):
self.tree.addTopLevelItem(self.notinstalledItem)
self.tree.addTopLevelItem(self.uptodateItem)
- self.webView.setHtml(self.HELP_TEXT)
+ self.txtHelp.setHtml(self.HELP_TEXT)
def setHelp(self, reply, item):
- """Change the webview HTML content"""
+ """Change the HTML content"""
QApplication.restoreOverrideCursor()
if reply.error() != QNetworkReply.NoError:
html = self.tr('<h2>No detailed description available for this script</h2>')
@@ -222,14 +222,14 @@ class GetScriptsAndModelsDialog(BASE, WIDGET):
html += self.tr('<p><b>Created by:</b> %s') % getDescription(ALG_CREATOR, descriptions)
html += self.tr('<p><b>Version:</b> %s') % getDescription(ALG_VERSION, descriptions)
reply.deleteLater()
- self.webView.setHtml(html)
+ self.txtHelp.setHtml(html)
def currentItemChanged(self, item, prev):
if isinstance(item, TreeItem):
url = self.urlBase + item.filename.replace(' ', '%20') + '.help'
self.grabHTTP(url, self.setHelp, item)
else:
- self.webView.setHtml(self.HELP_TEXT)
+ self.txtHelp.setHtml(self.HELP_TEXT)
def getTreeBranchForState(self, filename, version):
if not os.path.exists(os.path.join(self.folder, filename)):
diff --git a/python/plugins/processing/gui/HelpEditionDialog.py b/python/plugins/processing/gui/HelpEditionDialog.py
index cfed621..61937fa 100644
--- a/python/plugins/processing/gui/HelpEditionDialog.py
+++ b/python/plugins/processing/gui/HelpEditionDialog.py
@@ -138,7 +138,7 @@ class HelpEditionDialog(BASE, WIDGET):
self.updateHtmlView()
def updateHtmlView(self):
- self.webView.setHtml(self.getHtml())
+ self.txtPreview.setHtml(self.getHtml())
def getDescription(self, name):
if name in self.descriptions:
diff --git a/python/plugins/processing/gui/ResultsDialog.py b/python/plugins/processing/gui/ResultsDialog.py
index 8793fd9..051eb43 100644
--- a/python/plugins/processing/gui/ResultsDialog.py
+++ b/python/plugins/processing/gui/ResultsDialog.py
@@ -26,6 +26,7 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
import os
+import codecs
from PyQt4 import uic
from PyQt4.QtCore import QUrl
@@ -52,7 +53,7 @@ class ResultsDialog(BASE, WIDGET):
self.fillTree()
if self.lastUrl:
- self.webView.load(self.lastUrl)
+ self.txtResults.setHtml(self.loadResults(self.lastUrl))
def fillTree(self):
elements = ProcessingResults.getResults()
@@ -63,13 +64,17 @@ class ResultsDialog(BASE, WIDGET):
item = TreeResultItem(element)
item.setIcon(0, self.keyIcon)
self.tree.addTopLevelItem(item)
- self.lastUrl = QUrl(elements[-1].filename)
+ self.lastUrl = elements[-1].filename
def changeResult(self):
item = self.tree.currentItem()
if isinstance(item, TreeResultItem):
- url = QUrl(item.filename)
- self.webView.load(url)
+ self.txtResults.setHtml(self.loadResults(item.filename))
+
+ def loadResults(self, fileName):
+ with codecs.open(fileName, encoding='utf-8') as f:
+ content = f.read()
+ return content
class TreeResultItem(QTreeWidgetItem):
diff --git a/python/plugins/processing/modeler/ModelerAlgorithm.py b/python/plugins/processing/modeler/ModelerAlgorithm.py
index 10a43dc..0872485 100644
--- a/python/plugins/processing/modeler/ModelerAlgorithm.py
+++ b/python/plugins/processing/modeler/ModelerAlgorithm.py
@@ -533,6 +533,19 @@ class ModelerAlgorithm(GeoAlgorithm):
except:
return False, None
+ def shortHelp(self):
+ if 'ALG_DESC' in self.helpContent:
+ return self._formatHelp(unicode(self.helpContent['ALG_DESC']))
+ return None
+
+ def getParameterDescriptions(self):
+ descs = {}
+ descriptions = self.helpContent
+ for param in self.parameters:
+ if param.name in descriptions:
+ descs[param.name] = unicode(descriptions[param.name])
+ return descs
+
def todict(self):
keys = ["inputs", "group", "name", "algs", "helpContent"]
return {k: v for k, v in self.__dict__.iteritems() if k in keys}
diff --git a/python/plugins/processing/modeler/ModelerParametersDialog.py b/python/plugins/processing/modeler/ModelerParametersDialog.py
index 499ed58..72f8336 100644
--- a/python/plugins/processing/modeler/ModelerParametersDialog.py
+++ b/python/plugins/processing/modeler/ModelerParametersDialog.py
@@ -27,7 +27,9 @@ __revision__ = '$Format:%H$'
from PyQt4.QtCore import Qt, QUrl, QMetaObject
from PyQt4.QtGui import QDialog, QDialogButtonBox, QLabel, QLineEdit, QFrame, QPushButton, QSizePolicy, QVBoxLayout, QHBoxLayout, QTabWidget, QWidget, QScrollArea, QComboBox, QTableWidgetItem, QMessageBox
-from PyQt4.QtWebKit import QWebView
+from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply
+
+from qgis.core import QgsNetworkAccessManager
from processing.modeler.ModelerAlgorithm import ValueFromInput, \
ValueFromOutput, Algorithm, ModelerOutput
@@ -166,27 +168,27 @@ class ModelerParametersDialog(QDialog):
self.scrollArea.setWidget(self.paramPanel)
self.scrollArea.setWidgetResizable(True)
self.tabWidget.addTab(self.scrollArea, self.tr('Parameters'))
- self.webView = QWebView()
+
+ self.txtHelp = QTextBrowser()
html = None
url = None
- isText, help = self._alg.help()
- if help is not None:
- if isText:
- html = help
- else:
- url = QUrl(help)
- else:
- html = self.tr('<h2>Sorry, no help is available for this '
- 'algorithm.</h2>')
- try:
- if html:
- self.webView.setHtml(html)
- elif url:
- self.webView.load(url)
- except:
- self.webView.setHtml(self.tr('<h2>Could not open help file :-( </h2>'))
- self.tabWidget.addTab(self.webView, 'Help')
+ isText, algHelp = self._alg.help()
+ if algHelp is not None:
+ algHelp = algHelp if isText else QUrl(algHelp)
+ try:
+ if isText:
+ self.txtHelp.setHtml(algHelp)
+ else:
+ html = self.tr('<p>Downloading algorithm help... Please wait.</p>')
+ self.txtHelp.setHtml(html)
+ self.reply = QgsNetworkAccessManager.instance().get(QNetworkRequest(algHelp))
+ self.reply.finished.connect(self.requestFinished)
+ except:
+ self.txtHelp.setHtml(self.tr('<h2>No help available for this algorithm</h2>'))
+
+ self.tabWidget.addTab(self.txtHelp, 'Help')
+
self.verticalLayout2.addWidget(self.tabWidget)
self.verticalLayout2.addWidget(self.buttonBox)
self.setLayout(self.verticalLayout2)
@@ -205,6 +207,16 @@ class ModelerParametersDialog(QDialog):
opts.append(alg)
return opts
+ def requestFinished(self):
+ """Change the webview HTML content"""
+ reply = self.sender()
+ if reply.error() != QNetworkReply.NoError:
+ html = self.tr('<h2>No help available for this algorithm</h2><p>{}</p>'.format(reply.errorString()))
+ else:
+ html = unicode(reply.readAll())
+ reply.deleteLater()
+ self.txtHelp.setHtml(html)
+
def getDependenciesPanel(self):
return MultipleInputPanel([alg.algorithm.name for alg in self.getAvailableDependencies()])
@@ -286,7 +298,7 @@ class ModelerParametersDialog(QDialog):
elif isinstance(param, ParameterSelection):
item = QComboBox()
item.addItems(param.options)
- item.setCurrentIndex(param.default or 1)
+ item.setCurrentIndex(param.default or 0)
elif isinstance(param, ParameterFixedTable):
item = FixedTablePanel(param)
elif isinstance(param, ParameterRange):
diff --git a/python/plugins/processing/script/ScriptAlgorithm.py b/python/plugins/processing/script/ScriptAlgorithm.py
index 834f4a5..00922ef 100644
--- a/python/plugins/processing/script/ScriptAlgorithm.py
+++ b/python/plugins/processing/script/ScriptAlgorithm.py
@@ -27,6 +27,8 @@ __revision__ = '$Format:%H$'
import os
from PyQt4 import QtGui
+import re
+import json
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.gui.Help2Html import getHtmlFromHelpFile
from processing.core.parameters import ParameterRaster
@@ -330,3 +332,33 @@ class ScriptAlgorithm(GeoAlgorithm):
return True, getHtmlFromHelpFile(self, helpfile)
else:
return False, None
+
+ def shortHelp(self):
+ if self.descriptionFile is None:
+ return None
+ helpFile = unicode(self.descriptionFile) + '.help'
+ if os.path.exists(helpFile):
+ with open(helpFile) as f:
+ try:
+ descriptions = json.load(f)
+ if 'ALG_DESC' in descriptions:
+ return self._formatHelp(unicode(descriptions['ALG_DESC']))
+ except:
+ return None
+ return None
+
+ def getParameterDescriptions(self):
+ descs = {}
+ if self.descriptionFile is None:
+ return descs
+ helpFile = unicode(self.descriptionFile) + '.help'
+ if os.path.exists(helpFile):
+ with open(helpFile) as f:
+ try:
+ descriptions = json.load(f)
+ for param in self.parameters:
+ if param.name in descriptions:
+ descs[param.name] = unicode(descriptions[param.name])
+ except:
+ return descs
+ return descs
diff --git a/python/plugins/processing/tools/dataobjects.py b/python/plugins/processing/tools/dataobjects.py
index 3e98422..b00c57b 100644
--- a/python/plugins/processing/tools/dataobjects.py
+++ b/python/plugins/processing/tools/dataobjects.py
@@ -271,7 +271,7 @@ def getObjectFromUri(uri, forceLoad=True):
return None
-def exportVectorLayer(layer):
+def exportVectorLayer(layer, supported=None):
"""Takes a QgsVectorLayer and returns the filename to refer to it,
which allows external apps which support only file-based layers to
use it. It performs the necessary export in case the input layer
@@ -285,6 +285,7 @@ def exportVectorLayer(layer):
a new file if the original one contains non-ascii characters.
"""
+ supported = supported or ["shp"]
settings = QSettings()
systemEncoding = settings.value('/UI/encoding', 'System')
@@ -317,7 +318,7 @@ def exportVectorLayer(layer):
unicode(layer.source()).decode('ascii')
except UnicodeEncodeError:
isASCII = False
- if not unicode(layer.source()).endswith('shp') or not isASCII:
+ if not os.path.splitext()[1] in supported or not isASCII:
writer = QgsVectorFileWriter(
output, systemEncoding,
layer.pendingFields(), provider.geometryType(),
diff --git a/python/plugins/processing/tools/raster.py b/python/plugins/processing/tools/raster.py
index 4cfd3e9..6c71351 100644
--- a/python/plugins/processing/tools/raster.py
+++ b/python/plugins/processing/tools/raster.py
@@ -84,17 +84,18 @@ class RasterWriter:
NODATA = -99999.0
def __init__(self, fileName, minx, miny, maxx, maxy, cellsize,
- nbands, crs):
+ nbands, crs, geotransform=None):
self.fileName = fileName
self.nx = int((maxx - minx) / float(cellsize))
self.ny = int((maxy - miny) / float(cellsize))
self.nbands = nbands
- self.matrix = numpy.ones(shape=(self.ny, self.nx), dtype=numpy.float32)
- self.matrix[:] = self.NODATA
+ self.matrix = numpy.empty(shape=(self.ny, self.nx), dtype=numpy.float32)
+ self.matrix.fill(self.NODATA)
self.cellsize = cellsize
self.crs = crs
self.minx = minx
self.maxy = maxy
+ self.geotransform = geotransform
def setValue(self, value, x, y, band=0):
try:
@@ -114,7 +115,10 @@ class RasterWriter:
dst_ds = driver.Create(self.fileName, self.nx, self.ny, 1,
gdal.GDT_Float32)
dst_ds.SetProjection(str(self.crs.toWkt()))
- dst_ds.SetGeoTransform([self.minx, self.cellsize, 0,
- self.maxy, self.cellsize, 0])
+ if self.geotransform is None:
+ dst_ds.SetGeoTransform([self.minx, self.cellsize, 0,
+ self.maxy, self.cellsize, 0])
+ else:
+ dst_ds.SetGeoTransform(self.geotransform)
dst_ds.GetRasterBand(1).WriteArray(self.matrix)
dst_ds = None
diff --git a/python/plugins/processing/tools/vector.py b/python/plugins/processing/tools/vector.py
index a84e284..84e47d1 100644
--- a/python/plugins/processing/tools/vector.py
+++ b/python/plugins/processing/tools/vector.py
@@ -201,10 +201,7 @@ def testForUniqueness(fieldList1, fieldList2):
def spatialindex(layer):
"""Creates a spatial index for the passed vector layer.
"""
- idx = QgsSpatialIndex()
- feats = features(layer)
- for ft in feats:
- idx.insertFeature(ft)
+ idx = QgsSpatialIndex(layer.getFeatures())
return idx
diff --git a/python/plugins/processing/ui/DlgAlgorithmBase.ui b/python/plugins/processing/ui/DlgAlgorithmBase.ui
index e76058e..0e0a9bb 100644
--- a/python/plugins/processing/ui/DlgAlgorithmBase.ui
+++ b/python/plugins/processing/ui/DlgAlgorithmBase.ui
@@ -47,9 +47,6 @@
</property>
<item>
<widget class="QTextEdit" name="txtLog">
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
<property name="readOnly">
<bool>true</bool>
</property>
@@ -69,13 +66,7 @@
<number>0</number>
</property>
<item>
- <widget class="QWebView" name="textHelp">
- <property name="url">
- <url>
- <string>about:blank</string>
- </url>
- </property>
- </widget>
+ <widget class="QTextBrowser" name="txtHelp"/>
</item>
</layout>
</widget>
@@ -131,13 +122,6 @@
</item>
</layout>
</widget>
- <customwidgets>
- <customwidget>
- <class>QWebView</class>
- <extends>QWidget</extends>
- <header>QtWebKit/QWebView</header>
- </customwidget>
- </customwidgets>
<resources/>
<connections>
<connection>
diff --git a/python/plugins/processing/ui/DlgGetScriptsAndModels.ui b/python/plugins/processing/ui/DlgGetScriptsAndModels.ui
index 735a0ce..62a0372 100644
--- a/python/plugins/processing/ui/DlgGetScriptsAndModels.ui
+++ b/python/plugins/processing/ui/DlgGetScriptsAndModels.ui
@@ -74,7 +74,7 @@
<number>0</number>
</property>
<item>
- <widget class="QWebView" name="webView">
+ <widget class="QTextEdit" name="txtHelp">
<property name="maximumSize">
<size>
<width>10000</width>
diff --git a/python/plugins/processing/ui/DlgHelpEdition.ui b/python/plugins/processing/ui/DlgHelpEdition.ui
index 3f06653..c00fc13 100644
--- a/python/plugins/processing/ui/DlgHelpEdition.ui
+++ b/python/plugins/processing/ui/DlgHelpEdition.ui
@@ -21,17 +21,13 @@
<number>9</number>
</property>
<item>
+ <widget class="QTextEdit" name="txtPreview"/>
+ </item>
+ <item>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
- <widget class="QWebView" name="webView">
- <property name="url">
- <url>
- <string>about:blank</string>
- </url>
- </property>
- </widget>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -110,13 +106,6 @@
</item>
</layout>
</widget>
- <customwidgets>
- <customwidget>
- <class>QWebView</class>
- <extends>QWidget</extends>
- <header>QtWebKit/QWebView</header>
- </customwidget>
- </customwidgets>
<resources/>
<connections>
<connection>
diff --git a/python/plugins/processing/ui/DlgResults.ui b/python/plugins/processing/ui/DlgResults.ui
index aef1ad2..7772bc2 100644
--- a/python/plugins/processing/ui/DlgResults.ui
+++ b/python/plugins/processing/ui/DlgResults.ui
@@ -41,19 +41,7 @@
</property>
</column>
</widget>
- <widget class="QWebView" name="webView">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="url">
- <url>
- <string>about:blank</string>
- </url>
- </property>
- </widget>
+ <widget class="QTextEdit" name="txtResults"/>
</widget>
</item>
<item>
@@ -68,13 +56,6 @@
</item>
</layout>
</widget>
- <customwidgets>
- <customwidget>
- <class>QWebView</class>
- <extends>QWidget</extends>
- <header>QtWebKit/QWebView</header>
- </customwidget>
- </customwidgets>
<resources/>
<connections>
<connection>
diff --git a/src/analysis/network/qgsgraph.cpp b/src/analysis/network/qgsgraph.cpp
index c1784cf..007ea5e 100644
--- a/src/analysis/network/qgsgraph.cpp
+++ b/src/analysis/network/qgsgraph.cpp
@@ -52,7 +52,6 @@ const QgsGraphArc& QgsGraph::arc( int idx ) const
return mGraphArc[ idx ];
}
-
int QgsGraph::vertexCount() const
{
return mGraphVertexes.size();
diff --git a/src/analysis/network/qgslinevectorlayerdirector.cpp b/src/analysis/network/qgslinevectorlayerdirector.cpp
index d4a0aa8..ed4c6f0 100644
--- a/src/analysis/network/qgslinevectorlayerdirector.cpp
+++ b/src/analysis/network/qgslinevectorlayerdirector.cpp
@@ -23,6 +23,7 @@
#include <qgspoint.h>
#include <qgsgeometry.h>
#include <qgsdistancearea.h>
+#include <qgswkbtypes.h>
// QT includes
#include <QString>
@@ -162,9 +163,9 @@ void QgsLineVectorLayerDirector::makeGraph( QgsGraphBuilderInterface *builder, c
while ( fit.nextFeature( feature ) )
{
QgsMultiPolyline mpl;
- if ( feature.constGeometry()->wkbType() == QGis::WKBMultiLineString )
+ if ( QgsWKBTypes::flatType( feature.constGeometry()->geometry()->wkbType() ) == QgsWKBTypes::MultiLineString )
mpl = feature.constGeometry()->asMultiPolyline();
- else if ( feature.constGeometry()->wkbType() == QGis::WKBLineString )
+ else if ( QgsWKBTypes::flatType( feature.constGeometry()->geometry()->wkbType() ) == QgsWKBTypes::LineString )
mpl.push_back( feature.constGeometry()->asPolyline() );
QgsMultiPolyline::iterator mplIt;
@@ -296,9 +297,9 @@ void QgsLineVectorLayerDirector::makeGraph( QgsGraphBuilderInterface *builder, c
// begin features segments and add arc to the Graph;
QgsMultiPolyline mpl;
- if ( feature.constGeometry()->wkbType() == QGis::WKBMultiLineString )
+ if ( QgsWKBTypes::flatType( feature.constGeometry()->geometry()->wkbType() ) == QgsWKBTypes::MultiLineString )
mpl = feature.constGeometry()->asMultiPolyline();
- else if ( feature.constGeometry()->wkbType() == QGis::WKBLineString )
+ else if ( QgsWKBTypes::flatType( feature.constGeometry()->geometry()->wkbType() ) == QgsWKBTypes::LineString )
mpl.push_back( feature.constGeometry()->asPolyline() );
QgsMultiPolyline::iterator mplIt;
diff --git a/src/app/composer/qgscomposer.cpp b/src/app/composer/qgscomposer.cpp
index 53a8386..33c76be 100644
--- a/src/app/composer/qgscomposer.cpp
+++ b/src/app/composer/qgscomposer.cpp
@@ -1227,7 +1227,7 @@ void QgsComposer::on_mActionZoomAll_triggered()
void QgsComposer::on_mActionZoomIn_triggered()
{
- mView->scale( 2, 2 );
+ mView->scaleSafe( 2 );
mView->updateRulers();
mView->update();
emit zoomLevelChanged();
@@ -1235,7 +1235,7 @@ void QgsComposer::on_mActionZoomIn_triggered()
void QgsComposer::on_mActionZoomOut_triggered()
{
- mView->scale( .5, .5 );
+ mView->scaleSafe( 0.5 );
mView->updateRulers();
mView->update();
emit zoomLevelChanged();
diff --git a/src/app/composer/qgscomposerlegendwidget.cpp b/src/app/composer/qgscomposerlegendwidget.cpp
index 4aa9889..f3de5bb 100644
--- a/src/app/composer/qgscomposerlegendwidget.cpp
+++ b/src/app/composer/qgscomposerlegendwidget.cpp
@@ -728,62 +728,7 @@ void QgsComposerLegendWidget::on_mEditPushButton_clicked()
}
QModelIndex idx = mItemTreeView->selectionModel()->currentIndex();
- QgsLayerTreeModel* model = mItemTreeView->layerTreeModel();
- QgsLayerTreeNode* currentNode = model->index2node( idx );
- QgsLayerTreeModelLegendNode* legendNode = model->index2legendNode( idx );
- QString currentText;
-
- if ( QgsLayerTree::isGroup( currentNode ) )
- {
- currentText = QgsLayerTree::toGroup( currentNode )->name();
- }
- else if ( QgsLayerTree::isLayer( currentNode ) )
- {
- currentText = QgsLayerTree::toLayer( currentNode )->layerName();
- QVariant v = currentNode->customProperty( "legend/title-label" );
- if ( !v.isNull() )
- currentText = v.toString();
- }
- else if ( legendNode )
- {
- currentText = legendNode->data( Qt::EditRole ).toString();
- }
-
- bool ok;
- QString newText = QInputDialog::getText( this, tr( "Legend item properties" ), tr( "Item text" ),
- QLineEdit::Normal, currentText, &ok );
- if ( !ok || newText == currentText )
- return;
-
- mLegend->beginCommand( tr( "Legend item edited" ) );
-
- if ( QgsLayerTree::isGroup( currentNode ) )
- {
- QgsLayerTree::toGroup( currentNode )->setName( newText );
- }
- else if ( QgsLayerTree::isLayer( currentNode ) )
- {
- currentNode->setCustomProperty( "legend/title-label", newText );
-
- // force update of label of the legend node with embedded icon (a bit clumsy i know)
- QList<QgsLayerTreeModelLegendNode*> nodes = model->layerLegendNodes( QgsLayerTree::toLayer( currentNode ) );
- if ( nodes.count() == 1 && nodes[0]->isEmbeddedInParent() )
- nodes[0]->setUserLabel( QString() );
- }
- else if ( legendNode )
- {
- QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( legendNode->layerNode() );
- //find unfiltered row number
- QList<QgsLayerTreeModelLegendNode*> layerLegendNodes = model->layerOriginalLegendNodes( legendNode->layerNode() );
- int unfilteredRowIndex = layerLegendNodes.indexOf( legendNode );
- int originalIndex = ( unfilteredRowIndex >= 0 && unfilteredRowIndex < order.count() ? order[unfilteredRowIndex] : -1 );
- QgsMapLayerLegendUtils::setLegendNodeUserLabel( legendNode->layerNode(), originalIndex, newText );
- model->refreshLayerLegend( legendNode->layerNode() );
- }
-
- mLegend->adjustBoxSize();
- mLegend->updateItem();
- mLegend->endCommand();
+ on_mItemTreeView_doubleClicked( idx );
}
void QgsComposerLegendWidget::resetLayerNodeToDefaults()
@@ -1056,6 +1001,71 @@ void QgsComposerLegendWidget::updateFilterLegendByAtlasButton()
mFilterLegendByAtlasCheckBox->setEnabled( atlas.enabled() && atlas.coverageLayer() && atlas.coverageLayer()->geometryType() == QGis::Polygon );
}
+void QgsComposerLegendWidget::on_mItemTreeView_doubleClicked( const QModelIndex &idx )
+{
+ if ( !mLegend || !idx.isValid() )
+ {
+ return;
+ }
+
+ QgsLayerTreeModel* model = mItemTreeView->layerTreeModel();
+ QgsLayerTreeNode* currentNode = model->index2node( idx );
+ QgsLayerTreeModelLegendNode* legendNode = model->index2legendNode( idx );
+ QString currentText;
+
+ if ( QgsLayerTree::isGroup( currentNode ) )
+ {
+ currentText = QgsLayerTree::toGroup( currentNode )->name();
+ }
+ else if ( QgsLayerTree::isLayer( currentNode ) )
+ {
+ currentText = QgsLayerTree::toLayer( currentNode )->layerName();
+ QVariant v = currentNode->customProperty( "legend/title-label" );
+ if ( !v.isNull() )
+ currentText = v.toString();
+ }
+ else if ( legendNode )
+ {
+ currentText = legendNode->data( Qt::EditRole ).toString();
+ }
+
+ bool ok;
+ QString newText = QInputDialog::getText( this, tr( "Legend item properties" ), tr( "Item text" ),
+ QLineEdit::Normal, currentText, &ok );
+ if ( !ok || newText == currentText )
+ return;
+
+ mLegend->beginCommand( tr( "Legend item edited" ) );
+
+ if ( QgsLayerTree::isGroup( currentNode ) )
+ {
+ QgsLayerTree::toGroup( currentNode )->setName( newText );
+ }
+ else if ( QgsLayerTree::isLayer( currentNode ) )
+ {
+ currentNode->setCustomProperty( "legend/title-label", newText );
+
+ // force update of label of the legend node with embedded icon (a bit clumsy i know)
+ QList<QgsLayerTreeModelLegendNode*> nodes = model->layerLegendNodes( QgsLayerTree::toLayer( currentNode ) );
+ if ( nodes.count() == 1 && nodes[0]->isEmbeddedInParent() )
+ nodes[0]->setUserLabel( QString() );
+ }
+ else if ( legendNode )
+ {
+ QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( legendNode->layerNode() );
+ //find unfiltered row number
+ QList<QgsLayerTreeModelLegendNode*> layerLegendNodes = model->layerOriginalLegendNodes( legendNode->layerNode() );
+ int unfilteredRowIndex = layerLegendNodes.indexOf( legendNode );
+ int originalIndex = ( unfilteredRowIndex >= 0 && unfilteredRowIndex < order.count() ? order[unfilteredRowIndex] : -1 );
+ QgsMapLayerLegendUtils::setLegendNodeUserLabel( legendNode->layerNode(), originalIndex, newText );
+ model->refreshLayerLegend( legendNode->layerNode() );
+ }
+
+ mLegend->adjustBoxSize();
+ mLegend->updateItem();
+ mLegend->endCommand();
+}
+
//
// QgsComposerLegendMenuProvider
diff --git a/src/app/composer/qgscomposerlegendwidget.h b/src/app/composer/qgscomposerlegendwidget.h
index b1ecd46..738951e 100644
--- a/src/app/composer/qgscomposerlegendwidget.h
+++ b/src/app/composer/qgscomposerlegendwidget.h
@@ -102,6 +102,8 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
/** Update the enabling state of the filter by atlas button */
void updateFilterLegendByAtlasButton();
+ void on_mItemTreeView_doubleClicked( const QModelIndex &index );
+
private:
QgsComposerLegendWidget();
void blockAllSignals( bool b );
diff --git a/src/app/gps/qgsgpsinformationwidget.cpp b/src/app/gps/qgsgpsinformationwidget.cpp
index 9669151..f81d8d3 100644
--- a/src/app/gps/qgsgpsinformationwidget.cpp
+++ b/src/app/gps/qgsgpsinformationwidget.cpp
@@ -817,19 +817,16 @@ void QgsGPSInformationWidget::on_mBtnCloseFeature_clicked()
{
QgsFeature* f = new QgsFeature( 0 );
- int size = 0;
- int wkbtype = 0;
-
QgsCoordinateTransform t( mWgs84CRS, vlayer->crs() );
QgsPoint myPoint = t.transform( mLastGpsPosition );
double x = myPoint.x();
double y = myPoint.y();
- size = 1 + sizeof( int ) + 2 * sizeof( double );
+ int size = 1 + sizeof( int ) + 2 * sizeof( double );
unsigned char *buf = new unsigned char[size];
QgsWkbPtr wkbPtr( buf, size );
- wkbPtr << ( char ) QgsApplication::endian() << wkbtype << x << y;
+ wkbPtr << ( char ) QgsApplication::endian() << QGis::WKBPoint << x << y;
QgsGeometry *g = new QgsGeometry();
g->fromWkb( buf, size );
diff --git a/src/app/nodetool/qgsmaptoolnodetool.cpp b/src/app/nodetool/qgsmaptoolnodetool.cpp
index 6c4fdac..b92df9d 100644
--- a/src/app/nodetool/qgsmaptoolnodetool.cpp
+++ b/src/app/nodetool/qgsmaptoolnodetool.cpp
@@ -762,6 +762,11 @@ int QgsMapToolNodeTool::insertSegmentVerticesForSnap( const QList<QgsSnappingRes
QList<QgsSnappingResult>::const_iterator it = snapResults.constBegin();
for ( ; it != snapResults.constEnd(); ++it )
{
+ //skip if snappingResult is in a different layer
+ //See http://hub.qgis.org/issues/13952#note-29
+ if ( it->layer != editedLayer )
+ continue;
+
//skip if id is in skip list or we have already added a vertex to a feature
if ( skipFids.contains( it->snappedAtGeometry ) || addedFeatures.contains( it->snappedAtGeometry ) )
continue;
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index cb05993..8b71c47 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -1853,6 +1853,7 @@ void QgisApp::createToolBars()
bt->addActions( selectActions );
bt->setDefaultAction( mActionSelectByExpression );
QAction* selectionAction = mAttributesToolBar->insertWidget( mActionDeselectAll, bt );
+ selectionAction->setObjectName( "ActionSelection" );
// select tool button
@@ -2689,7 +2690,7 @@ void QgisApp::toggleLogMessageIcon( bool hasLogMessage )
void QgisApp::openMessageLog()
{
- mMessageButton->toggle();
+ mMessageButton->setChecked( true );
}
void QgisApp::addUserInputWidget( QWidget *widget )
@@ -3329,6 +3330,7 @@ bool QgisApp::addVectorLayers( const QStringList &theLayerQStringList, const QSt
bool ok;
l->loadDefaultStyle( ok );
}
+ activateDeactivateLayerRelatedActions( activeLayer() );
// Only update the map if we frozen in this method
// Let the caller do it otherwise
@@ -5538,110 +5540,118 @@ void QgisApp::saveAsRasterFile()
mMapCanvas->extent(), rasterLayer->crs(),
mMapCanvas->mapSettings().destinationCrs(),
this );
- if ( d.exec() == QDialog::Accepted )
- {
- QSettings settings;
- settings.setValue( "/UI/lastRasterFileDir", QFileInfo( d.outputFileName() ).absolutePath() );
+ if ( d.exec() == QDialog::Rejected )
+ return;
- QgsRasterFileWriter fileWriter( d.outputFileName() );
- if ( d.tileMode() )
- {
- fileWriter.setTiledMode( true );
- fileWriter.setMaxTileWidth( d.maximumTileSizeX() );
- fileWriter.setMaxTileHeight( d.maximumTileSizeY() );
- }
+ QSettings settings;
+ settings.setValue( "/UI/lastRasterFileDir", QFileInfo( d.outputFileName() ).absolutePath() );
- QProgressDialog pd( nullptr, tr( "Abort..." ), 0, 0 );
- // Show the dialo immediately because cloning pipe can take some time (WCS)
- pd.setLabelText( tr( "Reading raster" ) );
- pd.setWindowTitle( tr( "Saving raster" ) );
- pd.show();
- pd.setWindowModality( Qt::WindowModal );
+ QgsRasterFileWriter fileWriter( d.outputFileName() );
+ if ( d.tileMode() )
+ {
+ fileWriter.setTiledMode( true );
+ fileWriter.setMaxTileWidth( d.maximumTileSizeX() );
+ fileWriter.setMaxTileHeight( d.maximumTileSizeY() );
+ }
- // TODO: show error dialogs
- // TODO: this code should go somewhere else, but probably not into QgsRasterFileWriter
- // clone pipe/provider is not really necessary, ready for threads
- QScopedPointer<QgsRasterPipe> pipe( nullptr );
+ QProgressDialog pd( nullptr, tr( "Abort..." ), 0, 0 );
+ // Show the dialo immediately because cloning pipe can take some time (WCS)
+ pd.setLabelText( tr( "Reading raster" ) );
+ pd.setWindowTitle( tr( "Saving raster" ) );
+ pd.show();
+ pd.setWindowModality( Qt::WindowModal );
- if ( d.mode() == QgsRasterLayerSaveAsDialog::RawDataMode )
- {
- QgsDebugMsg( "Writing raw data" );
- //QgsDebugMsg( QString( "Writing raw data" ).arg( pos ) );
- pipe.reset( new QgsRasterPipe() );
- if ( !pipe->set( rasterLayer->dataProvider()->clone() ) )
- {
- QgsDebugMsg( "Cannot set pipe provider" );
- return;
- }
+ // TODO: show error dialogs
+ // TODO: this code should go somewhere else, but probably not into QgsRasterFileWriter
+ // clone pipe/provider is not really necessary, ready for threads
+ QScopedPointer<QgsRasterPipe> pipe( nullptr );
- QgsRasterNuller *nuller = new QgsRasterNuller();
- for ( int band = 1; band <= rasterLayer->dataProvider()->bandCount(); band ++ )
- {
- nuller->setNoData( band, d.noData() );
- }
- if ( !pipe->insert( 1, nuller ) )
- {
- QgsDebugMsg( "Cannot set pipe nuller" );
- return;
- }
+ if ( d.mode() == QgsRasterLayerSaveAsDialog::RawDataMode )
+ {
+ QgsDebugMsg( "Writing raw data" );
+ //QgsDebugMsg( QString( "Writing raw data" ).arg( pos ) );
+ pipe.reset( new QgsRasterPipe() );
+ if ( !pipe->set( rasterLayer->dataProvider()->clone() ) )
+ {
+ QgsDebugMsg( "Cannot set pipe provider" );
+ return;
+ }
- // add projector if necessary
- if ( d.outputCrs() != rasterLayer->crs() )
- {
- QgsRasterProjector * projector = new QgsRasterProjector;
- projector->setCRS( rasterLayer->crs(), d.outputCrs() );
- if ( !pipe->insert( 2, projector ) )
- {
- QgsDebugMsg( "Cannot set pipe projector" );
- return;
- }
- }
+ QgsRasterNuller *nuller = new QgsRasterNuller();
+ for ( int band = 1; band <= rasterLayer->dataProvider()->bandCount(); band ++ )
+ {
+ nuller->setNoData( band, d.noData() );
+ }
+ if ( !pipe->insert( 1, nuller ) )
+ {
+ QgsDebugMsg( "Cannot set pipe nuller" );
+ return;
}
- else // RenderedImageMode
+
+ // add projector if necessary
+ if ( d.outputCrs() != rasterLayer->crs() )
{
- // clone the whole pipe
- QgsDebugMsg( "Writing rendered image" );
- pipe.reset( new QgsRasterPipe( *rasterLayer->pipe() ) );
- QgsRasterProjector *projector = pipe->projector();
- if ( !projector )
+ QgsRasterProjector * projector = new QgsRasterProjector;
+ projector->setCRS( rasterLayer->crs(), d.outputCrs() );
+ if ( !pipe->insert( 2, projector ) )
{
- QgsDebugMsg( "Cannot get pipe projector" );
+ QgsDebugMsg( "Cannot set pipe projector" );
return;
}
- projector->setCRS( rasterLayer->crs(), d.outputCrs() );
}
-
- if ( !pipe->last() )
+ }
+ else // RenderedImageMode
+ {
+ // clone the whole pipe
+ QgsDebugMsg( "Writing rendered image" );
+ pipe.reset( new QgsRasterPipe( *rasterLayer->pipe() ) );
+ QgsRasterProjector *projector = pipe->projector();
+ if ( !projector )
{
+ QgsDebugMsg( "Cannot get pipe projector" );
return;
}
- fileWriter.setCreateOptions( d.createOptions() );
+ projector->setCRS( rasterLayer->crs(), d.outputCrs() );
+ }
+
+ if ( !pipe->last() )
+ {
+ return;
+ }
+ fileWriter.setCreateOptions( d.createOptions() );
- fileWriter.setBuildPyramidsFlag( d.buildPyramidsFlag() );
- fileWriter.setPyramidsList( d.pyramidsList() );
- fileWriter.setPyramidsResampling( d.pyramidsResamplingMethod() );
- fileWriter.setPyramidsFormat( d.pyramidsFormat() );
- fileWriter.setPyramidsConfigOptions( d.pyramidsConfigOptions() );
+ fileWriter.setBuildPyramidsFlag( d.buildPyramidsFlag() );
+ fileWriter.setPyramidsList( d.pyramidsList() );
+ fileWriter.setPyramidsResampling( d.pyramidsResamplingMethod() );
+ fileWriter.setPyramidsFormat( d.pyramidsFormat() );
+ fileWriter.setPyramidsConfigOptions( d.pyramidsConfigOptions() );
- QgsRasterFileWriter::WriterError err = fileWriter.writeRaster( pipe.data(), d.nColumns(), d.nRows(), d.outputRectangle(), d.outputCrs(), &pd );
- if ( err != QgsRasterFileWriter::NoError )
- {
- QMessageBox::warning( this, tr( "Error" ),
- tr( "Cannot write raster error code: %1" ).arg( err ),
- QMessageBox::Ok );
+ QgsRasterFileWriter::WriterError err = fileWriter.writeRaster( pipe.data(), d.nColumns(), d.nRows(), d.outputRectangle(), d.outputCrs(), &pd );
+ if ( err != QgsRasterFileWriter::NoError )
+ {
+ QMessageBox::warning( this, tr( "Error" ),
+ tr( "Cannot write raster error code: %1" ).arg( err ),
+ QMessageBox::Ok );
+ }
+ else
+ {
+ QString fileName( d.outputFileName() );
+ if ( d.tileMode() )
+ {
+ QFileInfo outputInfo( fileName );
+ fileName = QString( "%1/%2.vrt" ).arg( fileName, outputInfo.fileName() );
}
- else
+
+ if ( d.addToCanvas() )
{
- if ( d.addToCanvas() )
- {
- addRasterLayers( QStringList( d.outputFileName() ) );
- }
- emit layerSavedAs( rasterLayer, d.outputFileName() );
- messageBar()->pushMessage( tr( "Saving done" ),
- tr( "Export to raster file has been completed" ),
- QgsMessageBar::INFO, messageTimeout() );
+ addRasterLayers( QStringList( fileName ) );
}
+
+ emit layerSavedAs( rasterLayer, fileName );
+ messageBar()->pushMessage( tr( "Saving done" ),
+ tr( "Export to raster file has been completed" ),
+ QgsMessageBar::INFO, messageTimeout() );
}
}
@@ -7172,6 +7182,13 @@ void QgisApp::pasteStyle( QgsMapLayer * destinationLayer )
return;
}
+ bool isVectorStyle = doc.elementsByTagName( "pipe" ).isEmpty();
+ if (( selectionLayer->type() == QgsMapLayer::RasterLayer && isVectorStyle ) ||
+ ( selectionLayer->type() == QgsMapLayer::VectorLayer && !isVectorStyle ) )
+ {
+ return;
+ }
+
if ( !selectionLayer->importNamedStyle( doc, errorMsg ) )
{
messageBar()->pushMessage( tr( "Cannot paste style" ),
@@ -8545,7 +8562,7 @@ void QgisApp::apiDocumentation()
void QgisApp::reportaBug()
{
- openURL( tr( "https://qgis.org/en/site/getinvolved/development/index.html#bugs-features-and-issues" ), false );
+ openURL( tr( "https://qgis.org/en/site/getinvolved/development/bugreporting.html" ), false );
}
void QgisApp::supportProviders()
{
diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h
index f45e412..26666bb 100644
--- a/src/app/qgisapp.h
+++ b/src/app/qgisapp.h
@@ -1735,6 +1735,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
bool gestureEvent( QGestureEvent *event );
void tapAndHoldTriggered( QTapAndHoldGesture *gesture );
#endif
+
+ friend class TestQgisAppPython;
};
#ifdef ANDROID
diff --git a/src/app/qgsapplayertreeviewmenuprovider.cpp b/src/app/qgsapplayertreeviewmenuprovider.cpp
index f556b9b..4faa702 100644
--- a/src/app/qgsapplayertreeviewmenuprovider.cpp
+++ b/src/app/qgsapplayertreeviewmenuprovider.cpp
@@ -129,7 +129,11 @@ QMenu* QgsAppLayerTreeViewMenuProvider::createContextMenu()
if ( vlayer )
{
- QgsSingleSymbolRendererV2* singleRenderer = dynamic_cast< QgsSingleSymbolRendererV2* >( vlayer->rendererV2() );
+ const QgsSingleSymbolRendererV2* singleRenderer = dynamic_cast< const QgsSingleSymbolRendererV2* >( vlayer->rendererV2() );
+ if ( !singleRenderer && vlayer->rendererV2() && vlayer->rendererV2()->embeddedRenderer() )
+ {
+ singleRenderer = dynamic_cast< const QgsSingleSymbolRendererV2* >( vlayer->rendererV2()->embeddedRenderer() );
+ }
if ( singleRenderer && singleRenderer->symbol() )
{
//single symbol renderer, so add set color/edit symbol actions
@@ -260,44 +264,44 @@ QMenu* QgsAppLayerTreeViewMenuProvider::createContextMenu()
menu->addAction( QgsApplication::getThemeIcon( "/mActionHideAllLayers.png" ), tr( "&Hide All Items" ),
symbolNode, SLOT( uncheckAllItems() ) );
menu->addSeparator();
+ }
- if ( symbolNode->symbol() )
+ if ( symbolNode->symbol() )
+ {
+ QgsColorWheel* colorWheel = new QgsColorWheel( menu );
+ colorWheel->setColor( symbolNode->symbol()->color() );
+ QgsColorWidgetAction* colorAction = new QgsColorWidgetAction( colorWheel, menu, menu );
+ colorAction->setDismissOnColorSelection( false );
+ connect( colorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( setSymbolLegendNodeColor( const QColor& ) ) );
+ //store the layer id and rule key in action, so we can later retrieve the corresponding
+ //legend node, if it still exists
+ colorAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
+ colorAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
+ menu->addAction( colorAction );
+
+ //add recent colors action
+ QList<QgsRecentColorScheme *> recentSchemes;
+ QgsColorSchemeRegistry::instance()->schemes( recentSchemes );
+ if ( !recentSchemes.isEmpty() )
{
- QgsColorWheel* colorWheel = new QgsColorWheel( menu );
- colorWheel->setColor( symbolNode->symbol()->color() );
- QgsColorWidgetAction* colorAction = new QgsColorWidgetAction( colorWheel, menu, menu );
- colorAction->setDismissOnColorSelection( false );
- connect( colorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( setSymbolLegendNodeColor( const QColor& ) ) );
- //store the layer id and rule key in action, so we can later retrieve the corresponding
- //legend node, if it still exists
- colorAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
- colorAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
- menu->addAction( colorAction );
-
- //add recent colors action
- QList<QgsRecentColorScheme *> recentSchemes;
- QgsColorSchemeRegistry::instance()->schemes( recentSchemes );
- if ( !recentSchemes.isEmpty() )
- {
- QgsColorSwatchGridAction* recentColorAction = new QgsColorSwatchGridAction( recentSchemes.at( 0 ), menu, "symbology", menu );
- recentColorAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
- recentColorAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
- recentColorAction->setDismissOnColorSelection( false );
- menu->addAction( recentColorAction );
- connect( recentColorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( setSymbolLegendNodeColor( const QColor& ) ) );
- }
-
- menu->addSeparator();
+ QgsColorSwatchGridAction* recentColorAction = new QgsColorSwatchGridAction( recentSchemes.at( 0 ), menu, "symbology", menu );
+ recentColorAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
+ recentColorAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
+ recentColorAction->setDismissOnColorSelection( false );
+ menu->addAction( recentColorAction );
+ connect( recentColorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( setSymbolLegendNodeColor( const QColor& ) ) );
}
- QAction* editSymbolAction = new QAction( tr( "Edit Symbol..." ), menu );
- //store the layer id and rule key in action, so we can later retrieve the corresponding
- //legend node, if it still exists
- editSymbolAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
- editSymbolAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
- connect( editSymbolAction, SIGNAL( triggered() ), this, SLOT( editSymbolLegendNodeSymbol() ) );
- menu->addAction( editSymbolAction );
+ menu->addSeparator();
}
+
+ QAction* editSymbolAction = new QAction( tr( "Edit Symbol..." ), menu );
+ //store the layer id and rule key in action, so we can later retrieve the corresponding
+ //legend node, if it still exists
+ editSymbolAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
+ editSymbolAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
+ connect( editSymbolAction, SIGNAL( triggered() ), this, SLOT( editSymbolLegendNodeSymbol() ) );
+ menu->addAction( editSymbolAction );
}
}
@@ -488,12 +492,34 @@ void QgsAppLayerTreeViewMenuProvider::setVectorSymbolColor( const QColor& color
return;
QgsSingleSymbolRendererV2* singleRenderer = dynamic_cast< QgsSingleSymbolRendererV2* >( layer->rendererV2() );
- if ( !singleRenderer || !singleRenderer->symbol() )
+ QgsSymbolV2* newSymbol = nullptr;
+
+ if ( singleRenderer && singleRenderer->symbol() )
+ newSymbol = singleRenderer->symbol()->clone();
+
+ const QgsSingleSymbolRendererV2* embeddedRenderer = nullptr;
+ if ( !newSymbol && layer->rendererV2()->embeddedRenderer() )
+ {
+ embeddedRenderer = dynamic_cast< const QgsSingleSymbolRendererV2* >( layer->rendererV2()->embeddedRenderer() );
+ if ( embeddedRenderer && embeddedRenderer->symbol() )
+ newSymbol = embeddedRenderer->symbol()->clone();
+ }
+
+ if ( !newSymbol )
return;
- QgsSymbolV2* newSymbol = singleRenderer->symbol()->clone();
newSymbol->setColor( color );
- singleRenderer->setSymbol( newSymbol );
+ if ( singleRenderer )
+ {
+ singleRenderer->setSymbol( newSymbol );
+ }
+ else if ( embeddedRenderer )
+ {
+ QgsSingleSymbolRendererV2* newRenderer = embeddedRenderer->clone();
+ newRenderer->setSymbol( newSymbol );
+ layer->rendererV2()->setEmbeddedRenderer( newRenderer );
+ }
+
layer->triggerRepaint();
mView->refreshLayerSymbology( layer->id() );
}
diff --git a/src/app/qgsattributetabledialog.cpp b/src/app/qgsattributetabledialog.cpp
index 63bad96..fd69f58 100644
--- a/src/app/qgsattributetabledialog.cpp
+++ b/src/app/qgsattributetabledialog.cpp
@@ -155,7 +155,7 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *theLayer, QWid
bool myDockFlag = settings.value( "/qgis/dockAttributeTable", false ).toBool();
if ( myDockFlag )
{
- mDock = new QgsAttributeTableDock( tr( "Attribute table - %1 (%n Feature(s))", "feature count", mMainView->featureCount() ).arg( mLayer->name() ), QgisApp::instance() );
+ mDock = new QgsAttributeTableDock( tr( "%1 (%n Feature(s))", "feature count", mMainView->featureCount() ).arg( mLayer->name() ), QgisApp::instance() );
mDock->setAllowedAreas( Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea );
mDock->setWidget( this );
connect( this, SIGNAL( destroyed() ), mDock, SLOT( close() ) );
@@ -252,7 +252,7 @@ QgsAttributeTableDialog::~QgsAttributeTableDialog()
void QgsAttributeTableDialog::updateTitle()
{
QWidget *w = mDock ? qobject_cast<QWidget*>( mDock ) : qobject_cast<QWidget*>( this );
- w->setWindowTitle( tr( "Attribute table - %1 :: Features total: %2, filtered: %3, selected: %4%5" )
+ w->setWindowTitle( tr( " %1 :: Features total: %2, filtered: %3, selected: %4%5" )
.arg( mLayer->name() )
.arg( mMainView->featureCount() )
.arg( mMainView->filteredFeatureCount() )
diff --git a/src/app/qgscustomization.cpp b/src/app/qgscustomization.cpp
index f51dc14..0f9c5c7 100644
--- a/src/app/qgscustomization.cpp
+++ b/src/app/qgscustomization.cpp
@@ -388,10 +388,11 @@ bool QgsCustomizationDialog::switchWidget( QWidget *widget, QMouseEvent *e )
QgsDebugMsg( "Entered" );
if ( !actionCatch->isChecked() )
return false;
+
QString path = widgetPath( widget );
QgsDebugMsg( "path = " + path );
- if ( path.startsWith( "/QgsCustomizationDialogBase" ) )
+ if ( path.contains( "/QgsCustomizationDialogBase" ) )
{
// do not allow modification of this dialog
return false;
diff --git a/src/app/qgsjoindialog.cpp b/src/app/qgsjoindialog.cpp
index 4059f46..80e7984 100644
--- a/src/app/qgsjoindialog.cpp
+++ b/src/app/qgsjoindialog.cpp
@@ -24,6 +24,7 @@
#include "qgsfieldcombobox.h"
#include <QStandardItemModel>
+#include <QPushButton>
QgsJoinDialog::QgsJoinDialog( QgsVectorLayer* layer, QList<QgsMapLayer*> alreadyJoinedLayers, QWidget * parent, Qt::WindowFlags f )
: QDialog( parent, f )
@@ -53,6 +54,12 @@ QgsJoinDialog::QgsJoinDialog( QgsVectorLayer* layer, QList<QgsMapLayer*> already
mJoinFieldComboBox->setLayer( joinLayer );
joinedLayerChanged( joinLayer );
}
+
+ connect( mJoinLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), this, SLOT( checkDefinitionValid() ) );
+ connect( mJoinFieldComboBox, SIGNAL( fieldChanged( QString ) ), this, SLOT( checkDefinitionValid() ) );
+ connect( mTargetFieldComboBox, SIGNAL( fieldChanged( QString ) ), this, SLOT( checkDefinitionValid() ) );
+
+ checkDefinitionValid();
}
QgsJoinDialog::~QgsJoinDialog()
@@ -98,7 +105,8 @@ void QgsJoinDialog::setJoinInfo( const QgsVectorJoinInfo& joinInfo )
QgsVectorJoinInfo QgsJoinDialog::joinInfo() const
{
QgsVectorJoinInfo info;
- info.joinLayerId = mJoinLayerComboBox->currentLayer()->id();
+ if ( mJoinLayerComboBox->currentLayer() )
+ info.joinLayerId = mJoinLayerComboBox->currentLayer()->id();
info.joinFieldName = mJoinFieldComboBox->currentField();
info.targetFieldName = mTargetFieldComboBox->currentField();
info.memoryCache = mCacheInMemoryCheckBox->isChecked();
@@ -173,3 +181,10 @@ void QgsJoinDialog::joinedLayerChanged( QgsMapLayer* layer )
mCustomPrefix->setText( layer->name() + '_' );
}
}
+
+void QgsJoinDialog::checkDefinitionValid()
+{
+ buttonBox->button( QDialogButtonBox::Ok )->setEnabled( mJoinLayerComboBox->currentIndex() != -1
+ && mJoinFieldComboBox->currentIndex() != -1
+ && mTargetFieldComboBox->currentIndex() != -1 );
+}
diff --git a/src/app/qgsjoindialog.h b/src/app/qgsjoindialog.h
index 71f800d..3a48bb0 100644
--- a/src/app/qgsjoindialog.h
+++ b/src/app/qgsjoindialog.h
@@ -42,6 +42,8 @@ class APP_EXPORT QgsJoinDialog: public QDialog, private Ui::QgsJoinDialogBase
private slots:
void joinedLayerChanged( QgsMapLayer* layer );
+ void checkDefinitionValid();
+
private:
/** Target layer*/
QgsVectorLayer* mLayer;
diff --git a/src/app/qgslabelingwidget.cpp b/src/app/qgslabelingwidget.cpp
index 15f2067..7d879d9 100644
--- a/src/app/qgslabelingwidget.cpp
+++ b/src/app/qgslabelingwidget.cpp
@@ -41,6 +41,10 @@ void QgsLabelingWidget::adaptToLayer()
{
mLabelModeComboBox->setCurrentIndex( -1 );
+ // Delete the widget, so that labelModeChanged() recreates it with
+ // settings loaded from the layer
+ deleteWidget();
+
// pick the right mode of the layer
if ( mLayer->labeling() && mLayer->labeling()->type() == "rule-based" )
{
@@ -107,11 +111,7 @@ void QgsLabelingWidget::labelModeChanged( int index )
// in general case we need to recreate the widget
- if ( mWidget )
- mStackedWidget->removeWidget( mWidget );
-
- delete mWidget;
- mWidget = nullptr;
+ deleteWidget();
if ( index == 2 )
{
@@ -139,3 +139,12 @@ void QgsLabelingWidget::showEngineConfigDialog()
QgsLabelEngineConfigDialog dlg( this );
dlg.exec();
}
+
+void QgsLabelingWidget::deleteWidget()
+{
+ if ( mWidget )
+ mStackedWidget->removeWidget( mWidget );
+
+ delete mWidget;
+ mWidget = nullptr;
+}
diff --git a/src/app/qgslabelingwidget.h b/src/app/qgslabelingwidget.h
index c72f475..a432370 100644
--- a/src/app/qgslabelingwidget.h
+++ b/src/app/qgslabelingwidget.h
@@ -38,6 +38,9 @@ class QgsLabelingWidget : public QWidget, private Ui::QgsLabelingWidget
QgsMapCanvas* mCanvas;
QWidget* mWidget;
+
+ //! Delete the child widget
+ void deleteWidget();
};
#endif // QGSLABELINGWIDGET_H
diff --git a/src/app/qgsmaptoolmeasureangle.cpp b/src/app/qgsmaptoolmeasureangle.cpp
index 4815ecf..50243e9 100644
--- a/src/app/qgsmaptoolmeasureangle.cpp
+++ b/src/app/qgsmaptoolmeasureangle.cpp
@@ -93,7 +93,8 @@ void QgsMapToolMeasureAngle::canvasReleaseEvent( QgsMapMouseEvent* e )
{
if ( !mResultDisplay )
{
- mResultDisplay = new QgsDisplayAngle( this, Qt::WindowStaysOnTopHint );
+ mResultDisplay = new QgsDisplayAngle( this );
+ mResultDisplay->setWindowFlags( mResultDisplay->windowFlags() | Qt::Tool );
QObject::connect( mResultDisplay, SIGNAL( rejected() ), this, SLOT( stopMeasuring() ) );
}
configureDistanceArea();
diff --git a/src/app/qgsmaptoolselectradius.cpp b/src/app/qgsmaptoolselectradius.cpp
index b0b0a29..e0431ce 100644
--- a/src/app/qgsmaptoolselectradius.cpp
+++ b/src/app/qgsmaptoolselectradius.cpp
@@ -109,6 +109,7 @@ void QgsMapToolSelectRadius::setRadiusRubberBand( QgsPoint & radiusEdge )
double theta = i * ( 2.0 * M_PI / RADIUS_SEGMENTS );
QgsPoint radiusPoint( mRadiusCenter.x() + r * cos( theta ),
mRadiusCenter.y() + r * sin( theta ) );
- mRubberBand->addPoint( radiusPoint );
+ mRubberBand->addPoint( radiusPoint, false );
}
+ mRubberBand->closePoints( true );
}
diff --git a/src/app/qgsmaptoolselectutils.cpp b/src/app/qgsmaptoolselectutils.cpp
index 25924ec..0325b52 100644
--- a/src/app/qgsmaptoolselectutils.cpp
+++ b/src/app/qgsmaptoolselectutils.cpp
@@ -102,20 +102,51 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
// the rubber band.
// For example, if you project a world map onto a globe using EPSG 2163
// and then click somewhere off the globe, an exception will be thrown.
- QgsGeometry selectGeomTrans( *selectGeometry );
+ QScopedPointer<QgsGeometry> selectGeomTrans( new QgsGeometry( *selectGeometry ) );
if ( canvas->mapSettings().hasCrsTransformEnabled() )
{
try
{
QgsCoordinateTransform ct( canvas->mapSettings().destinationCrs(), vlayer->crs() );
- selectGeomTrans.transform( ct );
+
+ if ( !ct.isShortCircuited() && selectGeomTrans->type() == QGis::Polygon )
+ {
+ // convert add more points to the edges of the rectangle
+ // improve transformation result
+ QgsPolygon poly( selectGeomTrans->asPolygon() );
+ if ( poly.size() == 1 && poly.at( 0 ).size() == 5 )
+ {
+ const QgsPolyline &ringIn = poly.at( 0 );
+
+ QgsPolygon newpoly( 1 );
+ newpoly[0].resize( 41 );
+ QgsPolyline &ringOut = newpoly[0];
+
+ ringOut[ 0 ] = ringIn.at( 0 );
+
+ int i = 1;
+ for ( int j = 1; j < 5; j++ )
+ {
+ QgsVector v(( ringIn.at( j ) - ringIn.at( j - 1 ) ) / 10.0 );
+ for ( int k = 0; k < 9; k++ )
+ {
+ ringOut[ i ] = ringOut[ i - 1 ] + v;
+ i++;
+ }
+ ringOut[ i++ ] = ringIn.at( j );
+ }
+ selectGeomTrans.reset( QgsGeometry::fromPolygon( newpoly ) );
+ }
+ }
+
+ selectGeomTrans->transform( ct );
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
// catch exception for 'invalid' point and leave existing selection unchanged
- QgsLogger::warning( "Caught CRS exception " + QString( __FILE__ ) + ": " + QString::number( __LINE__ ) );
+ QgsDebugMsg( "Caught CRS exception " );
QgisApp::instance()->messageBar()->pushMessage(
QObject::tr( "CRS Exception" ),
QObject::tr( "Selection extends beyond layer's coordinate system" ),
@@ -128,7 +159,7 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
QApplication::setOverrideCursor( Qt::WaitCursor );
QgsDebugMsg( "Selection layer: " + vlayer->name() );
- QgsDebugMsg( "Selection polygon: " + selectGeomTrans.exportToWkt() );
+ QgsDebugMsg( "Selection polygon: " + selectGeomTrans->exportToWkt() );
QgsDebugMsg( "doContains: " + QString( doContains ? "T" : "F" ) );
QgsDebugMsg( "doDifference: " + QString( doDifference ? "T" : "F" ) );
@@ -139,7 +170,7 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
r->startRender( context, vlayer->fields() );
QgsFeatureRequest request;
- request.setFilterRect( selectGeomTrans.boundingBox() );
+ request.setFilterRect( selectGeomTrans->boundingBox() );
request.setFlags( QgsFeatureRequest::ExactIntersect );
if ( r )
request.setSubsetOfAttributes( r->usedAttributes(), vlayer->fields() );
@@ -163,18 +194,18 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
const QgsGeometry* g = f.constGeometry();
if ( doContains )
{
- if ( !selectGeomTrans.contains( g ) )
+ if ( !selectGeomTrans->contains( g ) )
continue;
}
else
{
- if ( !selectGeomTrans.intersects( g ) )
+ if ( !selectGeomTrans->intersects( g ) )
continue;
}
if ( singleSelect )
{
foundSingleFeature = true;
- double distance = g->distance( selectGeomTrans );
+ double distance = g->distance( *selectGeomTrans );
if ( distance <= closestFeatureDist )
{
closestFeatureDist = distance;
diff --git a/src/app/qgsmeasuretool.cpp b/src/app/qgsmeasuretool.cpp
index f47e6ca..a3bd9b8 100644
--- a/src/app/qgsmeasuretool.cpp
+++ b/src/app/qgsmeasuretool.cpp
@@ -47,7 +47,8 @@ QgsMeasureTool::QgsMeasureTool( QgsMapCanvas* canvas, bool measureArea )
// Append point we will move
mPoints.append( QgsPoint( 0, 0 ) );
- mDialog = new QgsMeasureDialog( this, Qt::WindowStaysOnTopHint );
+ mDialog = new QgsMeasureDialog( this );
+ mDialog->setWindowFlags( mDialog->windowFlags() | Qt::Tool );
mDialog->restorePosition();
connect( canvas, SIGNAL( destinationCrsChanged() ),
diff --git a/src/app/qgsnewspatialitelayerdialog.cpp b/src/app/qgsnewspatialitelayerdialog.cpp
index 24e65e1..24f8fdf 100644
--- a/src/app/qgsnewspatialitelayerdialog.cpp
+++ b/src/app/qgsnewspatialitelayerdialog.cpp
@@ -84,7 +84,7 @@ QgsNewSpatialiteLayerDialog::QgsNewSpatialiteLayerDialog( QWidget *parent, Qt::W
connect( mNameEdit, SIGNAL( textChanged( QString ) ), this, SLOT( nameChanged( QString ) ) );
connect( mAttributeView, SIGNAL( itemSelectionChanged() ), this, SLOT( selectionChanged() ) );
- connect( leLayerName, SIGNAL( textChanged( const QString& text ) ), this, SLOT( checkOk() ) );
+ connect( leLayerName, SIGNAL( textChanged( QString ) ), this, SLOT( checkOk() ) );
connect( checkBoxPrimaryKey, SIGNAL( clicked() ), this, SLOT( checkOk() ) );
mAddAttributeButton->setEnabled( false );
diff --git a/src/app/qgsoptions.cpp b/src/app/qgsoptions.cpp
index 678a32e..4afba0b 100644
--- a/src/app/qgsoptions.cpp
+++ b/src/app/qgsoptions.cpp
@@ -232,29 +232,25 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) :
}
//local directories to search when looking for an SVG with a given basename
- myPaths = mSettings->value( "svg/searchPathsForSVG", QDir::homePath() ).toString();
- if ( !myPaths.isEmpty() )
+ QStringList svgPaths = QgsApplication::svgPaths();
+ if ( !svgPaths.isEmpty() )
{
- QStringList myPathList = myPaths.split( '|' );
- QStringList::const_iterator pathIt = myPathList.constBegin();
- for ( ; pathIt != myPathList.constEnd(); ++pathIt )
+ Q_FOREACH ( const QString& path, svgPaths )
{
QListWidgetItem* newItem = new QListWidgetItem( mListSVGPaths );
- newItem->setText( *pathIt );
+ newItem->setText( path );
newItem->setFlags( Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable );
mListSVGPaths->addItem( newItem );
}
}
- myPaths = mSettings->value( "composer/searchPathsForTemplates", "" ).toString();
- if ( !myPaths.isEmpty() )
+ QStringList templatePaths = QgsApplication::composerTemplatePaths();
+ if ( !templatePaths.isEmpty() )
{
- QStringList myPathList = myPaths.split( '|' );
- QStringList::const_iterator pathIt = myPathList.constBegin();
- for ( ; pathIt != myPathList.constEnd(); ++pathIt )
+ Q_FOREACH ( const QString& path, templatePaths )
{
QListWidgetItem* newItem = new QListWidgetItem( mListComposerTemplatePaths );
- newItem->setText( *pathIt );
+ newItem->setText( path );
newItem->setFlags( Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable );
mListComposerTemplatePaths->addItem( newItem );
}
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ac5b91c..bbbdf8d 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -253,6 +253,7 @@ SET(QGIS_CORE_SRCS
composer/qgscomposerutils.cpp
composer/qgscomposition.cpp
composer/qgsdoubleboxscalebarstyle.cpp
+ composer/qgsgroupungroupitemscommand.cpp
composer/qgslegendmodel.cpp
composer/qgsnumericscalebarstyle.cpp
composer/qgspaperitem.cpp
@@ -342,7 +343,9 @@ SET(QGIS_CORE_SRCS
)
FILE(GLOB JSON_HELP_FILES "${CMAKE_SOURCE_DIR}/resources/function_help/json/*")
-STRING(REPLACE "$" "$$" JSON_HELP_FILES "${JSON_HELP_FILES}")
+IF(NOT USING_NINJA)
+ STRING(REPLACE "$" "$$" JSON_HELP_FILES "${JSON_HELP_FILES}")
+ENDIF(NOT USING_NINJA)
STRING(REPLACE "\(" "\\(" JSON_HELP_FILES "${JSON_HELP_FILES}")
STRING(REPLACE "\)" "\\)" JSON_HELP_FILES "${JSON_HELP_FILES}")
ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/qgsexpression_texts.cpp
@@ -508,6 +511,7 @@ SET(QGIS_CORE_MOC_HDRS
composer/qgscomposertablev2.h
composer/qgscomposertexttable.h
composer/qgscomposition.h
+ composer/qgsgroupungroupitemscommand.h
composer/qgslegendmodel.h
composer/qgspaperitem.h
diff --git a/src/core/composer/qgsaddremoveitemcommand.cpp b/src/core/composer/qgsaddremoveitemcommand.cpp
index 10f60f9..a8714d0 100644
--- a/src/core/composer/qgsaddremoveitemcommand.cpp
+++ b/src/core/composer/qgsaddremoveitemcommand.cpp
@@ -36,6 +36,7 @@ QgsAddRemoveItemCommand::~QgsAddRemoveItemCommand()
void QgsAddRemoveItemCommand::redo()
{
+ QUndoCommand::redo(); // call redo() on all childs
if ( mFirstRun )
{
mFirstRun = false;
@@ -46,6 +47,7 @@ void QgsAddRemoveItemCommand::redo()
void QgsAddRemoveItemCommand::undo()
{
+ QUndoCommand::undo(); // call undo() on all childs, in reverse order
if ( mFirstRun )
{
mFirstRun = false;
@@ -58,6 +60,7 @@ void QgsAddRemoveItemCommand::switchState()
{
if ( mState == Added )
{
+ // Remove
if ( mComposition )
{
mComposition->itemsModel()->setItemRemoved( mItem );
@@ -68,6 +71,7 @@ void QgsAddRemoveItemCommand::switchState()
}
else //Removed
{
+ // Add
if ( mComposition )
{
mComposition->itemsModel()->setItemRestored( mItem );
diff --git a/src/core/composer/qgscomposerarrow.cpp b/src/core/composer/qgscomposerarrow.cpp
index 6e07059..cf50f3a 100644
--- a/src/core/composer/qgscomposerarrow.cpp
+++ b/src/core/composer/qgscomposerarrow.cpp
@@ -19,6 +19,7 @@
#include "qgscomposition.h"
#include "qgscomposerutils.h"
#include "qgssymbollayerv2utils.h"
+#include "qgssvgcache.h"
#include <QPainter>
#include <QSvgRenderer>
#include <QVector2D>
@@ -252,22 +253,14 @@ void QgsComposerArrow::drawSVGMarker( QPainter* p, MarkerType type, const QStrin
imageFixPoint.setY( 0 );
}
- //rasterize svg
+ QString svgFileName = ( type == StartMarker ? mStartMarkerFile : mEndMarkerFile );
+ if ( svgFileName.isEmpty() )
+ return;
+
QSvgRenderer r;
- if ( type == StartMarker )
- {
- if ( mStartMarkerFile.isEmpty() || !r.load( mStartMarkerFile ) )
- {
- return;
- }
- }
- else //end marker
- {
- if ( mEndMarkerFile.isEmpty() || !r.load( mEndMarkerFile ) )
- {
- return;
- }
- }
+ const QByteArray &svgContent = QgsSvgCache::instance()->svgContent( svgFileName, mArrowHeadWidth, mArrowHeadFillColor, mArrowHeadOutlineColor, mArrowHeadOutlineWidth,
+ 1.0, 1.0 );
+ r.load( svgContent );
p->save();
p->setRenderHint( QPainter::Antialiasing );
diff --git a/src/core/composer/qgscomposeritemgroup.cpp b/src/core/composer/qgscomposeritemgroup.cpp
index b419ecf..cadf1f3 100644
--- a/src/core/composer/qgscomposeritemgroup.cpp
+++ b/src/core/composer/qgscomposeritemgroup.cpp
@@ -36,7 +36,7 @@ QgsComposerItemGroup::~QgsComposerItemGroup()
//loop through group members and remove them from the scene
Q_FOREACH ( QgsComposerItem* item, mItems )
{
- if ( !item )
+ if ( !item || item->isRemoved() )
continue;
//inform model that we are about to remove an item from the scene
diff --git a/src/core/composer/qgscomposerlegend.cpp b/src/core/composer/qgscomposerlegend.cpp
index b5e559e..d51b5aa 100644
--- a/src/core/composer/qgscomposerlegend.cpp
+++ b/src/core/composer/qgscomposerlegend.cpp
@@ -306,7 +306,7 @@ void QgsComposerLegend::updateLegend()
void QgsComposerLegend::updateItem()
{
- updateFilterByMap();
+ updateFilterByMap( false );
QgsComposerItem::updateItem();
}
@@ -605,7 +605,7 @@ void QgsComposerLegend::mapLayerStyleOverridesChanged()
{
// legend is being filtered by map, so we need to re run the hit test too
// as the style overrides may also have affected the visible symbols
- updateFilterByMap();
+ updateFilterByMap( false );
}
else
{
@@ -619,7 +619,7 @@ void QgsComposerLegend::mapLayerStyleOverridesChanged()
updateItem();
}
-void QgsComposerLegend::updateFilterByMap()
+void QgsComposerLegend::updateFilterByMap( bool redraw )
{
if ( isRemoved() )
return;
@@ -627,6 +627,9 @@ void QgsComposerLegend::updateFilterByMap()
// the actual update will take place before the redraw.
// This is to avoid multiple calls to the filter
mFilterAskedForUpdate = true;
+
+ if ( redraw )
+ QgsComposerItem::updateItem();
}
void QgsComposerLegend::doUpdateFilterByMap()
diff --git a/src/core/composer/qgscomposerlegend.h b/src/core/composer/qgscomposerlegend.h
index b7332da..9a4090c 100644
--- a/src/core/composer/qgscomposerlegend.h
+++ b/src/core/composer/qgscomposerlegend.h
@@ -255,7 +255,7 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
void invalidateCurrentMap();
private slots:
- void updateFilterByMap();
+ void updateFilterByMap( bool redraw = true );
//! update legend in case style of associated map has changed
void mapLayerStyleOverridesChanged();
diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp
index d3b3d91..1f7f83a 100644
--- a/src/core/composer/qgscomposition.cpp
+++ b/src/core/composer/qgscomposition.cpp
@@ -33,6 +33,7 @@
#include "qgscomposerattributetablev2.h"
#include "qgsaddremovemultiframecommand.h"
#include "qgscomposermultiframecommand.h"
+#include "qgsgroupungroupitemscommand.h"
#include "qgspaintenginehack.h"
#include "qgspaperitem.h"
#include "qgsproject.h"
@@ -1515,6 +1516,10 @@ void QgsComposition::addItemsFromXML( const QDomElement& elem, const QDomDocumen
QgsComposerItemGroup *newGroup = new QgsComposerItemGroup( this );
newGroup->readXML( groupElem, doc );
addItem( newGroup );
+ if ( addUndoCommands )
+ {
+ pushAddRemoveCommand( newGroup, tr( "Group added" ) );
+ }
}
//Since this function adds items grouped by type, and each item is added to end of
@@ -1940,14 +1945,28 @@ QgsComposerItemGroup *QgsComposition::groupItems( QList<QgsComposerItem *> items
}
QgsComposerItemGroup* itemGroup = new QgsComposerItemGroup( this );
+ QgsDebugMsg( QString( "itemgroup created with %1 items (%2 to be added)" ) .arg( itemGroup->items().size() ).arg( items.size() ) );
QList<QgsComposerItem*>::iterator itemIter = items.begin();
for ( ; itemIter != items.end(); ++itemIter )
{
itemGroup->addItem( *itemIter );
+ QgsDebugMsg( QString( "itemgroup now has %1" )
+ .arg( itemGroup->items().size() ) );
}
addItem( itemGroup );
+
+ QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Grouped, itemGroup, this, tr( "Items grouped" ) );
+ QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
+ QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
+
+ undoStack()->push( c );
+ QgsProject::instance()->setDirty( true );
+ //QgsDebugMsg( QString( "itemgroup after pushAddRemove has %1" ) .arg( itemGroup->items().size() ) );
+
+ emit composerItemGroupAdded( itemGroup );
+
return itemGroup;
}
@@ -1959,6 +1978,17 @@ QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup* gro
return ungroupedItems;
}
+ // group ownership transferred to QgsGroupUngroupItemsCommand
+ // Call this before removing group items so it can keep note
+ // of contents
+ QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Ungrouped, group, this, tr( "Items ungrouped" ) );
+ QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
+ QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
+
+ undoStack()->push( c );
+ QgsProject::instance()->setDirty( true );
+
+
QSet<QgsComposerItem*> groupedItems = group->items();
QSet<QgsComposerItem*>::iterator itemIt = groupedItems.begin();
for ( ; itemIt != groupedItems.end(); ++itemIt )
@@ -1967,10 +1997,9 @@ QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup* gro
}
group->removeItems();
- removeComposerItem( group, false, false );
- emit itemRemoved( group );
- delete( group );
+ // note: emits itemRemoved
+ removeComposerItem( group, false, false );
return ungroupedItems;
}
@@ -2542,6 +2571,7 @@ void QgsComposition::addComposerTableFrame( QgsComposerAttributeTableV2 *table,
emit composerTableFrameAdded( table, frame );
}
+/* public */
void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool createCommand, const bool removeGroupItems )
{
QgsComposerMap* map = dynamic_cast<QgsComposerMap *>( item );
@@ -2550,25 +2580,36 @@ void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool creat
{
mItemsModel->setItemRemoved( item );
removeItem( item );
+ emit itemRemoved( item );
+
+ QgsDebugMsg( QString( "removeComposerItem called, createCommand:%1 removeGroupItems:%2" )
+ .arg( createCommand ).arg( removeGroupItems ) );
QgsComposerItemGroup* itemGroup = dynamic_cast<QgsComposerItemGroup*>( item );
if ( itemGroup && removeGroupItems )
{
- //add add/remove item command for every item in the group
- QUndoCommand* parentCommand = new QUndoCommand( tr( "Remove item group" ) );
+ QgsDebugMsg( QString( "itemGroup && removeGroupItems" ) );
+
+ // Takes ownership of itemGroup
+ QgsAddRemoveItemCommand* parentCommand = new QgsAddRemoveItemCommand(
+ QgsAddRemoveItemCommand::Removed, itemGroup, this,
+ tr( "Remove item group" ) );
+ connectAddRemoveCommandSignals( parentCommand );
+ //add add/remove item command for every item in the group
QSet<QgsComposerItem*> groupedItems = itemGroup->items();
+ QgsDebugMsg( QString( "itemGroup contains %1 items" ) .arg( groupedItems.size() ) );
QSet<QgsComposerItem*>::iterator it = groupedItems.begin();
for ( ; it != groupedItems.end(); ++it )
{
+ mItemsModel->setItemRemoved( *it );
+ removeItem( *it );
QgsAddRemoveItemCommand* subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, "", parentCommand );
connectAddRemoveCommandSignals( subcommand );
emit itemRemoved( *it );
}
undoStack()->push( parentCommand );
- emit itemRemoved( itemGroup );
- delete itemGroup;
}
else
{
@@ -2580,19 +2621,13 @@ void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool creat
{
multiFrame = static_cast<QgsComposerFrame*>( item )->multiFrame();
item->beginItemCommand( tr( "Frame deleted" ) );
- emit itemRemoved( item );
item->endItemCommand();
}
else
{
- emit itemRemoved( item );
pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
}
}
- else
- {
- emit itemRemoved( item );
- }
//check if there are frames left. If not, remove the multi frame
if ( frameItem && multiFrame )
@@ -2715,6 +2750,11 @@ void QgsComposition::sendItemAddedSignal( QgsComposerItem* item )
emit selectedItemChanged( frame );
return;
}
+ QgsComposerItemGroup* group = dynamic_cast<QgsComposerItemGroup*>( item );
+ if ( group )
+ {
+ emit composerItemGroupAdded( group );
+ }
}
void QgsComposition::updatePaperItems()
diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h
index 8ad5918..9f30742 100644
--- a/src/core/composer/qgscomposition.h
+++ b/src/core/composer/qgscomposition.h
@@ -1061,6 +1061,8 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
void composerArrowAdded( QgsComposerArrow* arrow );
/** Is emitted when a new composer html has been added to the view*/
void composerHtmlFrameAdded( QgsComposerHtml* html, QgsComposerFrame* frame );
+ /** Is emitted when a new item group has been added to the view*/
+ void composerItemGroupAdded( QgsComposerItemGroup* group );
/** Is emitted when new composer label has been added to the view*/
void composerLabelAdded( QgsComposerLabel* label );
/** Is emitted when new composer map has been added to the view*/
diff --git a/src/core/composer/qgsgroupungroupitemscommand.cpp b/src/core/composer/qgsgroupungroupitemscommand.cpp
new file mode 100644
index 0000000..0ee18d8
--- /dev/null
+++ b/src/core/composer/qgsgroupungroupitemscommand.cpp
@@ -0,0 +1,96 @@
+/***************************************************************************
+ qgsgroupungroupitemscommand.cpp
+ ---------------------------
+ begin : 2016-06-09
+ copyright : (C) 2016 by Sandro Santilli
+ email : strk at kbt dot io
+***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "qgsgroupungroupitemscommand.h"
+#include "qgscomposeritem.h"
+#include "qgscomposeritemgroup.h"
+#include "qgscomposition.h"
+#include "qgsproject.h"
+#include "qgscomposermodel.h"
+#include "qgslogger.h"
+
+QgsGroupUngroupItemsCommand::QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent ):
+ QUndoCommand( text, parent ), mGroup( item ), mComposition( c ), mState( s ), mFirstRun( true )
+{
+ mItems = mGroup->items();
+}
+
+QgsGroupUngroupItemsCommand::~QgsGroupUngroupItemsCommand()
+{
+ if ( mState == Ungrouped )
+ {
+ //command class stores the item if ungrouped from the composition
+ delete mGroup;
+ }
+}
+
+void QgsGroupUngroupItemsCommand::redo()
+{
+ if ( mFirstRun )
+ {
+ mFirstRun = false;
+ return;
+ }
+ switchState();
+}
+
+void QgsGroupUngroupItemsCommand::undo()
+{
+ if ( mFirstRun )
+ {
+ mFirstRun = false;
+ return;
+ }
+ switchState();
+}
+
+void QgsGroupUngroupItemsCommand::switchState()
+{
+ if ( mState == Grouped )
+ {
+ // ungroup
+ if ( mComposition )
+ {
+ // This is probably redundant
+ mComposition->itemsModel()->setItemRemoved( mGroup );
+ mComposition->removeItem( mGroup );
+ }
+ mGroup->removeItems();
+ emit itemRemoved( mGroup );
+ mState = Ungrouped;
+ }
+ else //Ungrouped
+ {
+ // group
+ if ( mComposition )
+ {
+ //delete mGroup; mGroup = new QgsComposerItemGroup( mCompoiser );
+ QSet<QgsComposerItem*>::iterator itemIter = mItems.begin();
+ for ( ; itemIter != mItems.end(); ++itemIter )
+ {
+ mGroup->addItem( *itemIter );
+ QgsDebugMsg( QString( "itemgroup now has %1" ) .arg( mGroup->items().size() ) );
+ }
+ // Add the group
+ mComposition->itemsModel()->setItemRestored( mGroup );
+ mComposition->addItem( mGroup );
+ }
+ mState = Grouped;
+ emit itemAdded( mGroup );
+ }
+ QgsProject::instance()->setDirty( true );
+}
diff --git a/src/core/composer/qgsgroupungroupitemscommand.h b/src/core/composer/qgsgroupungroupitemscommand.h
new file mode 100644
index 0000000..0c2b548
--- /dev/null
+++ b/src/core/composer/qgsgroupungroupitemscommand.h
@@ -0,0 +1,75 @@
+/***************************************************************************
+ qgsgroupungroupitemscommand.h
+ ------------------------
+ begin : 2016-06-09
+ copyright : (C) 2016 by Sandro Santilli
+ email : strk at kbt dot io
+***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 QGSGROUPUNGROUPITEMSCOMMAND_H
+#define QGSGROUPUNGROUPITEMSCOMMAND_H
+
+#include <QUndoCommand>
+#include "qgscomposeritemgroup.h"
+class QgsComposerItem;
+class QgsComposition;
+
+/** A composer command class for grouping / ungrouping composer items.
+ *
+ * If mState == Ungrouped, the command owns the group item
+ */
+class CORE_EXPORT QgsGroupUngroupItemsCommand: public QObject, public QUndoCommand
+{
+ Q_OBJECT
+
+ public:
+
+ /** Command kind, and state */
+ enum State
+ {
+ Grouped = 0,
+ Ungrouped
+ };
+
+ /** Create a group or ungroup command
+ *
+ * @param s command kind (@see State)
+ * @param item the group item being created or ungrouped
+ * @param c the composition including this group
+ * @param text command label
+ * @param parent parent command, if any
+ *
+ */
+ QgsGroupUngroupItemsCommand( State s, QgsComposerItemGroup* item, QgsComposition* c, const QString& text, QUndoCommand* parent = nullptr );
+ ~QgsGroupUngroupItemsCommand();
+
+ void redo() override;
+ void undo() override;
+
+ signals:
+ /** Signals addition of an item (the group) */
+ void itemAdded( QgsComposerItem* item );
+ /** Signals removal of an item (the group) */
+ void itemRemoved( QgsComposerItem* item );
+
+ private:
+ QgsComposerItemGroup* mGroup;
+ QSet<QgsComposerItem*> mItems;
+ QgsComposition* mComposition;
+ State mState;
+ bool mFirstRun; //flag to prevent execution when the command is pushed to the QUndoStack
+
+ //changes between added / removed state
+ void switchState();
+};
+
+#endif // QGSGROUPUNGROUPITEMSCOMMAND_H
diff --git a/src/core/dxf/qgsdxfexport.cpp b/src/core/dxf/qgsdxfexport.cpp
index b3a7142..3950a53 100644
--- a/src/core/dxf/qgsdxfexport.cpp
+++ b/src/core/dxf/qgsdxfexport.cpp
@@ -37,6 +37,11 @@
#include "qgsvectorlayer.h"
#include "qgsmaplayerregistry.h"
#include "qgsunittypes.h"
+#include "qgstextlabelfeature.h"
+
+#include "pal/feature.h"
+#include "pal/pointset.h"
+#include "pal/labelposition.h"
#include <QIODevice>
@@ -865,7 +870,7 @@ void QgsDxfExport::writeBlocks()
writeGroup( 1, "" );
// maplayer 0 -> block receives layer from INSERT statement
- ml->writeDxf( *this, mapUnitScaleFactor( mSymbologyScaleDenominator, ml->sizeUnit(), mMapUnits ), "0", &ctx, nullptr );
+ ml->writeDxf( *this, mapUnitScaleFactor( mSymbologyScaleDenominator, ml->sizeUnit(), mMapUnits ), "0", ctx );
writeGroup( 0, "ENDBLK" );
writeHandle();
@@ -914,6 +919,7 @@ void QgsDxfExport::writeEntities()
// label engine
QgsLabelingEngineV2 engine;
+ engine.readSettingsFromProject();
engine.setMapSettings( mapSettings );
// iterate through the maplayers
@@ -942,12 +948,32 @@ void QgsDxfExport::writeEntities()
attributes << layerAttr;
}
- QgsDxfLabelProvider* lp = new QgsDxfLabelProvider( vl, this );
- engine.addProvider( lp );
- if ( !lp->prepare( ctx, attributes ) )
+ const QgsAbstractVectorLayerLabeling *labeling = vl->labeling();
+ QgsDxfLabelProvider *lp = nullptr;
+ QgsDxfRuleBasedLabelProvider *rblp = nullptr;
+ if ( dynamic_cast<const QgsRuleBasedLabeling*>( labeling ) )
+ {
+ const QgsRuleBasedLabeling *rbl = dynamic_cast<const QgsRuleBasedLabeling*>( labeling );
+ rblp = new QgsDxfRuleBasedLabelProvider( *rbl, vl, this );
+ rblp->reinit( vl );
+ engine.addProvider( rblp );
+
+ if ( !rblp->prepare( ctx, attributes ) )
+ {
+ engine.removeProvider( rblp );
+ rblp = nullptr;
+ }
+ }
+ else
{
- engine.removeProvider( lp );
- lp = nullptr;
+ lp = new QgsDxfLabelProvider( vl, this, nullptr );
+ engine.addProvider( lp );
+
+ if ( !lp->prepare( ctx, attributes ) )
+ {
+ engine.removeProvider( lp );
+ lp = nullptr;
+ }
}
if ( mSymbologyExport == QgsDxfExport::SymbolLayerSymbology &&
@@ -959,7 +985,7 @@ void QgsDxfExport::writeEntities()
continue;
}
- QgsFeatureRequest freq = QgsFeatureRequest().setSubsetOfAttributes( attributes, vl->fields() );
+ QgsFeatureRequest freq = QgsFeatureRequest().setSubsetOfAttributes( attributes, vl->fields() ).setExpressionContext( ctx.expressionContext() );
if ( !mExtent.isEmpty() )
{
freq.setFilterRect( mExtent );
@@ -1012,6 +1038,10 @@ void QgsDxfExport::writeEntities()
{
lp->registerDxfFeature( fet, ctx, lName );
}
+ else if ( rblp )
+ {
+ rblp->registerDxfFeature( fet, ctx, lName );
+ }
}
}
@@ -3300,7 +3330,7 @@ void QgsDxfExport::endSection()
writeGroup( 0, "ENDSEC" );
}
-void QgsDxfExport::writePoint( const QgsPoint& pt, const QString& layer, const QColor& color, const QgsFeature* f, const QgsSymbolLayerV2* symbolLayer, const QgsSymbolV2* symbol )
+void QgsDxfExport::writePoint( const QgsPoint& pt, const QString& layer, const QColor& color, QgsSymbolV2RenderContext &ctx, const QgsSymbolLayerV2* symbolLayer, const QgsSymbolV2* symbol, double angle )
{
#if 0
// debug: draw rectangle for debugging
@@ -3327,9 +3357,7 @@ void QgsDxfExport::writePoint( const QgsPoint& pt, const QString& layer, const Q
const QgsMarkerSymbolLayerV2* msl = dynamic_cast< const QgsMarkerSymbolLayerV2* >( symbolLayer );
if ( msl && symbol )
{
- QgsRenderContext ct;
- QgsSymbolV2RenderContext ctx( ct, QgsSymbolV2::MapUnit, symbol->alpha(), false, symbol->renderHints(), f );
- if ( symbolLayer->writeDxf( *this, mapUnitScaleFactor( mSymbologyScaleDenominator, msl->sizeUnit(), mMapUnits ), layer, &ctx, f, QPointF( pt.x(), pt.y() ) ) )
+ if ( symbolLayer->writeDxf( *this, mapUnitScaleFactor( mSymbologyScaleDenominator, msl->sizeUnit(), mMapUnits ), layer, ctx, QPointF( pt.x(), pt.y() ) ) )
{
return;
}
@@ -3345,6 +3373,7 @@ void QgsDxfExport::writePoint( const QgsPoint& pt, const QString& layer, const Q
writeGroup( 100, "AcDbBlockReference" );
writeGroup( 8, layer );
writeGroup( 2, blockIt.value() ); // Block name
+ writeGroup( 50, angle ); // angle
writeGroup( 0, pt ); // Insertion point (in OCS)
}
}
@@ -3630,10 +3659,12 @@ void QgsDxfExport::addFeature( QgsSymbolV2RenderContext& ctx, const QString& lay
Qt::BrushStyle brushStyle( Qt::NoBrush );
double width = -1;
double offset = 0.0;
+ double angle = 0.0;
if ( mSymbologyExport != NoSymbology && symbolLayer )
{
width = symbolLayer->dxfWidth( *this, ctx );
offset = symbolLayer->dxfOffset( *this, ctx );
+ angle = symbolLayer->dxfAngle( ctx );
penStyle = symbolLayer->dxfPenStyle();
brushStyle = symbolLayer->dxfBrushStyle();
@@ -3650,7 +3681,7 @@ void QgsDxfExport::addFeature( QgsSymbolV2RenderContext& ctx, const QString& lay
// single point
if ( geometryType == QGis::WKBPoint || geometryType == QGis::WKBPoint25D )
{
- writePoint( geom->asPoint(), layer, penColor, fet, symbolLayer, symbol );
+ writePoint( geom->asPoint(), layer, penColor, ctx, symbolLayer, symbol, angle );
return;
}
@@ -3661,7 +3692,7 @@ void QgsDxfExport::addFeature( QgsSymbolV2RenderContext& ctx, const QString& lay
QgsMultiPoint::const_iterator it = multiPoint.constBegin();
for ( ; it != multiPoint.constEnd(); ++it )
{
- writePoint( *it, layer, penColor, fet, symbolLayer, symbol );
+ writePoint( *it, layer, penColor, ctx, symbolLayer, symbol, angle );
}
return;
@@ -4101,6 +4132,11 @@ QString QgsDxfExport::dxfLayerName( const QString& name )
layerName.replace( '=', '_' );
layerName.replace( '\'', '_' );
+ // also remove newline characters (#15067)
+ layerName.replace( "\r\n", "_" );
+ layerName.replace( '\r', '_' );
+ layerName.replace( '\n', '_' );
+
return layerName.trimmed();
}
@@ -4170,3 +4206,147 @@ QString QgsDxfExport::layerName( QgsVectorLayer *vl ) const
Q_ASSERT( vl );
return mLayerTitleAsName && !vl->title().isEmpty() ? vl->title() : vl->name();
}
+
+void QgsDxfExport::drawLabel( QString layerId, QgsRenderContext& context, pal::LabelPosition* label, const QgsPalLayerSettings &settings )
+{
+ Q_UNUSED( context );
+
+ if ( !settings.drawLabels )
+ return;
+
+ QgsTextLabelFeature* lf = dynamic_cast<QgsTextLabelFeature*>( label->getFeaturePart()->feature() );
+
+ // Copy to temp, editable layer settings
+ // these settings will be changed by any data defined values, then used for rendering label components
+ // settings may be adjusted during rendering of components
+ QgsPalLayerSettings tmpLyr( settings );
+
+ // apply any previously applied data defined settings for the label
+ const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues = lf->dataDefinedValues();
+
+ //font
+ QFont dFont = lf->definedFont();
+ QgsDebugMsgLevel( QString( "PAL font tmpLyr: %1, Style: %2" ).arg( tmpLyr.textFont.toString(), tmpLyr.textFont.styleName() ), 4 );
+ QgsDebugMsgLevel( QString( "PAL font definedFont: %1, Style: %2" ).arg( dFont.toString(), dFont.styleName() ), 4 );
+ tmpLyr.textFont = dFont;
+
+ if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiFollowPlacement )
+ {
+ //calculate font alignment based on label quadrant
+ switch ( label->getQuadrant() )
+ {
+ case pal::LabelPosition::QuadrantAboveLeft:
+ case pal::LabelPosition::QuadrantLeft:
+ case pal::LabelPosition::QuadrantBelowLeft:
+ tmpLyr.multilineAlign = QgsPalLayerSettings::MultiRight;
+ break;
+ case pal::LabelPosition::QuadrantAbove:
+ case pal::LabelPosition::QuadrantOver:
+ case pal::LabelPosition::QuadrantBelow:
+ tmpLyr.multilineAlign = QgsPalLayerSettings::MultiCenter;
+ break;
+ case pal::LabelPosition::QuadrantAboveRight:
+ case pal::LabelPosition::QuadrantRight:
+ case pal::LabelPosition::QuadrantBelowRight:
+ tmpLyr.multilineAlign = QgsPalLayerSettings::MultiLeft;
+ break;
+ }
+ }
+
+ // update tmpLyr with any data defined text style values
+ QgsPalLabeling::dataDefinedTextStyle( tmpLyr, ddValues );
+
+ // update tmpLyr with any data defined text buffer values
+ QgsPalLabeling::dataDefinedTextBuffer( tmpLyr, ddValues );
+
+ // update tmpLyr with any data defined text formatting values
+ QgsPalLabeling::dataDefinedTextFormatting( tmpLyr, ddValues );
+
+ // add to the results
+ QString txt = label->getFeaturePart()->feature()->labelText();
+
+ QgsFeatureId fid = label->getFeaturePart()->featureId();
+ QString dxfLayer = mDxfLayerNames[layerId][fid];
+
+ QString wrapchr = tmpLyr.wrapChar.isEmpty() ? "\n" : tmpLyr.wrapChar;
+
+ //add the direction symbol if needed
+ if ( !txt.isEmpty() && tmpLyr.placement == QgsPalLayerSettings::Line && tmpLyr.addDirectionSymbol )
+ {
+ bool prependSymb = false;
+ QString symb = tmpLyr.rightDirectionSymbol;
+
+ if ( label->getReversed() )
+ {
+ prependSymb = true;
+ symb = tmpLyr.leftDirectionSymbol;
+ }
+
+ if ( tmpLyr.reverseDirectionSymbol )
+ {
+ if ( symb == tmpLyr.rightDirectionSymbol )
+ {
+ prependSymb = true;
+ symb = tmpLyr.leftDirectionSymbol;
+ }
+ else
+ {
+ prependSymb = false;
+ symb = tmpLyr.rightDirectionSymbol;
+ }
+ }
+
+ if ( tmpLyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolAbove )
+ {
+ prependSymb = true;
+ symb = symb + wrapchr;
+ }
+ else if ( tmpLyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolBelow )
+ {
+ prependSymb = false;
+ symb = wrapchr + symb;
+ }
+
+ if ( prependSymb )
+ {
+ txt.prepend( symb );
+ }
+ else
+ {
+ txt.append( symb );
+ }
+ }
+
+ txt = txt.replace( wrapchr, "\\P" );
+
+ if ( tmpLyr.textFont.underline() )
+ {
+ txt.prepend( "\\L" ).append( "\\l" );
+ }
+
+ if ( tmpLyr.textFont.overline() )
+ {
+ txt.prepend( "\\O" ).append( "\\o" );
+ }
+
+ if ( tmpLyr.textFont.strikeOut() )
+ {
+ txt.prepend( "\\K" ).append( "\\k" );
+ }
+
+ txt.prepend( QString( "\\f%1|i%2|b%3;\\H%4;\\W0.75;" )
+ .arg( tmpLyr.textFont.family() )
+ .arg( tmpLyr.textFont.italic() ? 1 : 0 )
+ .arg( tmpLyr.textFont.bold() ? 1 : 0 )
+ .arg( label->getHeight() / ( 1 + txt.count( "\\P" ) ) * 0.75 ) );
+
+ writeMText( dxfLayer, txt, QgsPoint( label->getX(), label->getY() ), label->getWidth(), label->getAlpha() * 180.0 / M_PI, tmpLyr.textColor );
+}
+
+void QgsDxfExport::registerDxfLayer( QString layerId, QgsFeatureId fid, QString layerName )
+{
+ if ( !mDxfLayerNames.contains( layerId ) )
+ mDxfLayerNames[ layerId ] = QMap<QgsFeatureId, QString>();
+
+ mDxfLayerNames[layerId][fid] = layerName;
+}
diff --git a/src/core/dxf/qgsdxfexport.h b/src/core/dxf/qgsdxfexport.h
index cc5c232..0e92a3e 100644
--- a/src/core/dxf/qgsdxfexport.h
+++ b/src/core/dxf/qgsdxfexport.h
@@ -20,6 +20,7 @@
#include "qgsgeometry.h"
#include "qgssymbolv2.h"
+
#include <QColor>
#include <QList>
#include <QTextStream>
@@ -28,6 +29,12 @@ class QgsMapLayer;
class QgsPoint;
class QgsSymbolLayerV2;
class QIODevice;
+class QgsPalLayerSettings;
+
+namespace pal
+{
+ class LabelPosition;
+};
class CORE_EXPORT QgsDxfExport
{
@@ -228,7 +235,7 @@ class CORE_EXPORT QgsDxfExport
* @param line polyline
* @param layer layer name to use
* @param lineStyleName line type to use
- * @param color coolor to use
+ * @param color color to use
* @param width line width to use
*/
void writePolyline( const QgsPolyline &line, const QString &layer, const QString &lineStyleName, const QColor& color, double width = -1 );
@@ -238,7 +245,7 @@ class CORE_EXPORT QgsDxfExport
* @param polygon polygon
* @param layer layer name to use
* @param hatchPattern hatchPattern to use
- * @param color coolor to use
+ * @param color color to use
*/
void writePolygon( const QgsPolygon &polygon, const QString &layer, const QString &hatchPattern, const QColor& color );
@@ -283,6 +290,22 @@ class CORE_EXPORT QgsDxfExport
//! return list of available DXF encodings
static QStringList encodings();
+ /** Output the label
+ * @param layerId id of the layer
+ * @param context render context
+ * @param label position of label
+ * @param settings label settings
+ * @note not available in Python bindings
+ */
+ void drawLabel( QString layerId, QgsRenderContext& context, pal::LabelPosition* label, const QgsPalLayerSettings &settings );
+
+ /** Register name of layer for feature
+ * @param layerId id of layer
+ * @param fid id of feature
+ * @param layer dxf layer of feature
+ */
+ void registerDxfLayer( QString layerId, QgsFeatureId fid, QString layer );
+
private:
QList< QPair<QgsVectorLayer*, int> > mLayers;
@@ -317,7 +340,7 @@ class CORE_EXPORT QgsDxfExport
void startSection();
void endSection();
- void writePoint( const QgsPoint &pt, const QString &layer, const QColor& color, const QgsFeature *f, const QgsSymbolLayerV2 *symbolLayer, const QgsSymbolV2 *symbol );
+ void writePoint( const QgsPoint &pt, const QString &layer, const QColor& color, QgsSymbolV2RenderContext &ctx, const QgsSymbolLayerV2 *symbolLayer, const QgsSymbolV2 *symbol, double angle );
void writeVertex( const QgsPoint &pt, const QString &layer );
void writeDefaultLinetypes();
void writeSymbolLayerLinetype( const QgsSymbolLayerV2 *symbolLayer );
@@ -350,6 +373,9 @@ class CORE_EXPORT QgsDxfExport
QHash<QString, int> mBlockHandles;
QString mBlockHandle;
+
+ //! DXF layer name for each label feature
+ QMap< QString, QMap<QgsFeatureId, QString> > mDxfLayerNames;
};
#endif // QGSDXFEXPORT_H
diff --git a/src/core/dxf/qgsdxfpallabeling.cpp b/src/core/dxf/qgsdxfpallabeling.cpp
index 9785f47..928c14b 100644
--- a/src/core/dxf/qgsdxfpallabeling.cpp
+++ b/src/core/dxf/qgsdxfpallabeling.cpp
@@ -17,131 +17,57 @@
#include "qgsdxfpallabeling.h"
#include "qgsdxfexport.h"
-#include "qgstextlabelfeature.h"
#include "qgspallabeling.h"
#include "qgsmapsettings.h"
+#include "qgslogger.h"
-#include "pal/feature.h"
-#include "pal/pointset.h"
-#include "pal/labelposition.h"
-
-QgsDxfLabelProvider::QgsDxfLabelProvider( QgsVectorLayer* layer , QgsDxfExport* dxf )
- : QgsVectorLayerLabelProvider( layer, false )
+QgsDxfLabelProvider::QgsDxfLabelProvider( QgsVectorLayer* layer, QgsDxfExport* dxf, const QgsPalLayerSettings *settings )
+ : QgsVectorLayerLabelProvider( layer, false, settings )
, mDxfExport( dxf )
{
}
void QgsDxfLabelProvider::drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const
{
- Q_UNUSED( context );
-
- //debug: print label infos
- if ( mDxfExport )
- {
- QgsTextLabelFeature* lf = dynamic_cast<QgsTextLabelFeature*>( label->getFeaturePart()->feature() );
- if ( !lf )
- return;
-
- const QgsPalLayerSettings& tmpLyr = mSettings;
-
- //label text
- QString txt = lf->text( label->getPartId() );
-
- //angle
- double angle = label->getAlpha() * 180 / M_PI;
-
- QgsFeatureId fid = label->getFeaturePart()->featureId();
- QString dxfLayer = mDxfLayerNames[fid];
-
- //debug: show label rectangle
-#if 0
- QgsPolyline line;
- for ( int i = 0; i < 4; ++i )
- {
- line.append( QgsPoint( label->getX( i ), label->getY( i ) ) );
- }
- mDxfExport->writePolyline( line, dxfLayer, "CONTINUOUS", 1, 0.01, true );
-#endif
-
- QString wrapchr = tmpLyr.wrapChar.isEmpty() ? "\n" : tmpLyr.wrapChar;
-
- //add the direction symbol if needed
- if ( !txt.isEmpty() && tmpLyr.placement == QgsPalLayerSettings::Line && tmpLyr.addDirectionSymbol )
- {
- bool prependSymb = false;
- QString symb = tmpLyr.rightDirectionSymbol;
-
- if ( label->getReversed() )
- {
- prependSymb = true;
- symb = tmpLyr.leftDirectionSymbol;
- }
-
- if ( tmpLyr.reverseDirectionSymbol )
- {
- if ( symb == tmpLyr.rightDirectionSymbol )
- {
- prependSymb = true;
- symb = tmpLyr.leftDirectionSymbol;
- }
- else
- {
- prependSymb = false;
- symb = tmpLyr.rightDirectionSymbol;
- }
- }
-
- if ( tmpLyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolAbove )
- {
- prependSymb = true;
- symb = symb + wrapchr;
- }
- else if ( tmpLyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolBelow )
- {
- prependSymb = false;
- symb = wrapchr + symb;
- }
-
- if ( prependSymb )
- {
- txt.prepend( symb );
- }
- else
- {
- txt.append( symb );
- }
- }
-
- txt = txt.replace( wrapchr, "\\P" );
+ Q_ASSERT( mDxfExport );
+ mDxfExport->drawLabel( layerId(), context, label, mSettings );
+}
- if ( tmpLyr.textFont.underline() )
- {
- txt.prepend( "\\L" ).append( "\\l" );
- }
+void QgsDxfLabelProvider::registerDxfFeature( QgsFeature& feature, QgsRenderContext& context, const QString& dxfLayerName )
+{
+ registerFeature( feature, context );
+ mDxfExport->registerDxfLayer( layerId(), feature.id(), dxfLayerName );
+}
- if ( tmpLyr.textFont.overline() )
- {
- txt.prepend( "\\O" ).append( "\\o" );
- }
+QgsDxfRuleBasedLabelProvider::QgsDxfRuleBasedLabelProvider( const QgsRuleBasedLabeling &rules, QgsVectorLayer* layer, QgsDxfExport* dxf )
+ : QgsRuleBasedLabelProvider( rules, layer, false )
+ , mDxfExport( dxf )
+{
+}
- if ( tmpLyr.textFont.strikeOut() )
- {
- txt.prepend( "\\K" ).append( "\\k" );
- }
+void QgsDxfRuleBasedLabelProvider::reinit( QgsVectorLayer* layer )
+{
+ QgsDebugMsg( "Entering." );
+ mRules.rootRule()->createSubProviders( layer, mSubProviders, this );
+}
- txt.prepend( QString( "\\f%1|i%2|b%3;\\H%4;\\W0.75;" )
- .arg( tmpLyr.textFont.family() )
- .arg( tmpLyr.textFont.italic() ? 1 : 0 )
- .arg( tmpLyr.textFont.bold() ? 1 : 0 )
- .arg( label->getHeight() / ( 1 + txt.count( "\\P" ) ) * 0.75 ) );
+QgsVectorLayerLabelProvider *QgsDxfRuleBasedLabelProvider::createProvider( QgsVectorLayer *layer, bool withFeatureLoop, const QgsPalLayerSettings *settings )
+{
+ QgsDebugMsg( "Entering." );
+ Q_UNUSED( withFeatureLoop );
+ return new QgsDxfLabelProvider( layer, mDxfExport, settings );
+}
- mDxfExport->writeMText( dxfLayer, txt, QgsPoint( label->getX(), label->getY() ), label->getWidth() * 1.1, angle, tmpLyr.textColor );
- }
+void QgsDxfRuleBasedLabelProvider::drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const
+{
+ QgsDebugMsg( "Entering." );
+ Q_ASSERT( mDxfExport );
+ mDxfExport->drawLabel( layerId(), context, label, mSettings );
}
-void QgsDxfLabelProvider::registerDxfFeature( QgsFeature& feature, QgsRenderContext& context, const QString& dxfLayerName )
+void QgsDxfRuleBasedLabelProvider::registerDxfFeature( QgsFeature &feature, QgsRenderContext &context, const QString &dxfLayerName )
{
registerFeature( feature, context );
- mDxfLayerNames[feature.id()] = dxfLayerName;
+ mDxfExport->registerDxfLayer( layerId(), feature.id(), dxfLayerName );
}
diff --git a/src/core/dxf/qgsdxfpallabeling.h b/src/core/dxf/qgsdxfpallabeling.h
index f051b50..f30b235 100644
--- a/src/core/dxf/qgsdxfpallabeling.h
+++ b/src/core/dxf/qgsdxfpallabeling.h
@@ -21,8 +21,11 @@
#include "qgsmaprenderer.h"
#include "qgsrendercontext.h"
#include "qgsvectorlayerlabelprovider.h"
+#include "qgsrulebasedlabeling.h"
class QgsDxfExport;
+class QgsPalLayerSettings;
+class QgsRuleBasedLabeling;
/** Implements a derived label provider internally used for DXF export
@@ -34,19 +37,64 @@ class QgsDxfLabelProvider : public QgsVectorLayerLabelProvider
{
public:
//! construct the provider
- explicit QgsDxfLabelProvider( QgsVectorLayer* layer, QgsDxfExport* dxf );
+ explicit QgsDxfLabelProvider( QgsVectorLayer* layer, QgsDxfExport* dxf, const QgsPalLayerSettings *settings );
- //! re-implementation that writes to DXF file instead of drawing with QPainter
- virtual void drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const override;
+ /** Re-implementation that writes to DXF file instead of drawing with QPainter
+ * @param context render context
+ * @param label label
+ */
+ void drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const override;
- //! registration method that keeps track of DXF layer names of individual features
+ /** Registration method that keeps track of DXF layer names of individual features
+ * @param feature feature
+ * @param context render context
+ * @param dxfLayerName name of dxf layer
+ */
void registerDxfFeature( QgsFeature& feature, QgsRenderContext &context, const QString& dxfLayerName );
protected:
//! pointer to parent DXF export where this instance is used
QgsDxfExport* mDxfExport;
- //! DXF layer name for each label feature
- QMap<QgsFeatureId, QString> mDxfLayerNames;
};
+/** Implements a derived label provider for rule based labels internally used
+ * for DXF export
+ *
+ * Internal class, not in public API. Backported from QGIS 2.15
+ * @note not available in Python bindings
+ */
+class QgsDxfRuleBasedLabelProvider : public QgsRuleBasedLabelProvider
+{
+ public:
+ //! construct the provider
+ explicit QgsDxfRuleBasedLabelProvider( const QgsRuleBasedLabeling &rules, QgsVectorLayer* layer, QgsDxfExport* dxf );
+
+ /** Reinitialize the subproviders with QgsDxfLabelProviders
+ * @param layer layer
+ */
+ void reinit( QgsVectorLayer* layer );
+
+ /** Re-implementation that writes to DXF file instead of drawing with QPainter
+ * @param context render context
+ * @param label label
+ */
+ void drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const override;
+
+ /** Registration method that keeps track of DXF layer names of individual features
+ * @param feature feature
+ * @param context render context
+ * @param dxfLayerName name of dxf layer
+ */
+ void registerDxfFeature( QgsFeature& feature, QgsRenderContext &context, const QString& dxfLayerName );
+
+ //! create QgsDxfLabelProvider
+ virtual QgsVectorLayerLabelProvider *createProvider( QgsVectorLayer *layer, bool withFeatureLoop, const QgsPalLayerSettings *settings ) override;
+
+ protected:
+ //! pointer to parent DXF export where this instance is used
+ QgsDxfExport* mDxfExport;
+};
+
+
+
#endif // QGSDXFPALLABELING_H
diff --git a/src/core/gps/parse.c b/src/core/gps/parse.c
index 526b208..a2d2dbf 100644
--- a/src/core/gps/parse.c
+++ b/src/core/gps/parse.c
@@ -133,6 +133,7 @@ int nmea_pack_type( const char *buff, int buff_sz )
"GPGSV",
"GPRMC",
"GPVTG",
+ "GNRMC",
};
NMEA_ASSERT( buff );
@@ -149,6 +150,8 @@ int nmea_pack_type( const char *buff, int buff_sz )
return GPRMC;
else if ( 0 == memcmp( buff, pheads[4], 5 ) )
return GPVTG;
+ else if ( 0 == memcmp( buff, pheads[5], 5 ) )
+ return GPRMC;
return GPNON;
}
@@ -322,6 +325,7 @@ int nmea_parse_GPGSV( const char *buff, int buff_sz, nmeaGPGSV *pack )
int nmea_parse_GPRMC( const char *buff, int buff_sz, nmeaGPRMC *pack )
{
int nsen;
+ char type;
char time_buff[NMEA_TIMEPARSE_BUF];
NMEA_ASSERT( buff && pack );
@@ -331,19 +335,25 @@ int nmea_parse_GPRMC( const char *buff, int buff_sz, nmeaGPRMC *pack )
nmea_trace_buff( buff, buff_sz );
nsen = nmea_scanf( buff, buff_sz,
- "$GPRMC,%s,%C,%f,%C,%f,%C,%f,%f,%2d%2d%2d,%f,%C,%C*",
- &( time_buff[0] ),
+ "$G%CRMC,%s,%C,%f,%C,%f,%C,%f,%f,%2d%2d%2d,%f,%C,%C*",
+ &( type ), &( time_buff[0] ),
&( pack->status ), &( pack->lat ), &( pack->ns ), &( pack->lon ), &( pack->ew ),
&( pack->speed ), &( pack->direction ),
&( pack->utc.day ), &( pack->utc.mon ), &( pack->utc.year ),
&( pack->declination ), &( pack->declin_ew ), &( pack->mode ) );
- if ( nsen != 13 && nsen != 14 )
+ if ( nsen != 14 && nsen != 15 )
{
nmea_error( "GPRMC parse error!" );
return 0;
}
+ if ( type != 'P' && type != 'N' )
+ {
+ nmea_error( "G?RMC invalid type " );
+ return 0;
+ }
+
if ( 0 != _nmea_parse_time( &time_buff[0], ( int )strlen( &time_buff[0] ), &( pack->utc ) ) )
{
nmea_error( "GPRMC time parse error!" );
diff --git a/src/core/gps/qgsnmeaconnection.cpp b/src/core/gps/qgsnmeaconnection.cpp
index 44e9efb..f116010 100644
--- a/src/core/gps/qgsnmeaconnection.cpp
+++ b/src/core/gps/qgsnmeaconnection.cpp
@@ -103,7 +103,7 @@ void QgsNMEAConnection::processStringBuffer()
mStatus = GPSDataReceived;
QgsDebugMsg( "*******************GPS data received****************" );
}
- else if ( substring.startsWith( "$GPRMC" ) )
+ else if ( substring.startsWith( "$GPRMC" ) || substring.startsWith( "$GNRMC" ) )
{
QgsDebugMsg( substring );
processRMCSentence( ba.data(), ba.length() );
diff --git a/src/core/qgis.cpp b/src/core/qgis.cpp
index 786837f..d6a7ba1 100644
--- a/src/core/qgis.cpp
+++ b/src/core/qgis.cpp
@@ -160,16 +160,22 @@ QGis::WkbType QGis::fromNewWkbType( QgsWKBTypes::Type type )
case QgsWKBTypes::NoGeometry:
return QGis::WKBNoGeometry;
case QgsWKBTypes::PointZ:
+ case QgsWKBTypes::Point25D:
return QGis::WKBPoint25D;
case QgsWKBTypes::LineStringZ:
+ case QgsWKBTypes::LineString25D:
return QGis::WKBLineString25D;
case QgsWKBTypes::PolygonZ:
+ case QgsWKBTypes::Polygon25D:
return QGis::WKBPolygon25D;
case QgsWKBTypes::MultiPointZ:
+ case QgsWKBTypes::MultiPoint25D:
return QGis::WKBMultiPoint25D;
case QgsWKBTypes::MultiLineStringZ:
+ case QgsWKBTypes::MultiLineString25D:
return QGis::WKBMultiLineString25D;
case QgsWKBTypes::MultiPolygonZ:
+ case QgsWKBTypes::MultiPolygon25D:
return QGis::WKBMultiPolygon25D;
default:
break;
diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp
index dc6f26b..a1ca088 100644
--- a/src/core/qgsapplication.cpp
+++ b/src/core/qgsapplication.cpp
@@ -153,14 +153,14 @@ void QgsApplication::init( QString customConfigPath )
{
// we run from source directory - not installed to destination (specified prefix)
ABISYM( mPrefixPath ) = QString(); // set invalid path
-#if defined(_MSC_VER) && !defined(USING_NMAKE)
+#if defined(_MSC_VER) && !defined(USING_NMAKE) && !defined(USING_NINJA)
setPluginPath( ABISYM( mBuildOutputPath ) + '/' + QString( QGIS_PLUGIN_SUBDIR ) + '/' + ABISYM( mCfgIntDir ) );
#else
setPluginPath( ABISYM( mBuildOutputPath ) + '/' + QString( QGIS_PLUGIN_SUBDIR ) );
#endif
setPkgDataPath( ABISYM( mBuildSourcePath ) ); // directly source path - used for: doc, resources, svg
ABISYM( mLibraryPath ) = ABISYM( mBuildOutputPath ) + '/' + QGIS_LIB_SUBDIR + '/';
-#if defined(_MSC_VER) && !defined(USING_NMAKE)
+#if defined(_MSC_VER) && !defined(USING_NMAKE) && !defined(USING_NINJA)
ABISYM( mLibexecPath ) = ABISYM( mBuildOutputPath ) + '/' + QGIS_LIBEXEC_SUBDIR + '/' + ABISYM( mCfgIntDir ) + '/';
#else
ABISYM( mLibexecPath ) = ABISYM( mBuildOutputPath ) + '/' + QGIS_LIBEXEC_SUBDIR + '/';
@@ -696,7 +696,7 @@ QStringList QgsApplication::svgPaths()
//defined by user in options dialog
QSettings settings;
QStringList myPathList;
- QString myPaths = settings.value( "svg/searchPathsForSVG", QDir::homePath() ).toString();
+ QString myPaths = settings.value( "svg/searchPathsForSVG", QString() ).toString();
if ( !myPaths.isEmpty() )
{
myPathList = myPaths.split( '|' );
@@ -715,7 +715,7 @@ QStringList QgsApplication::composerTemplatePaths()
//defined by user in options dialog
QSettings settings;
QStringList myPathList;
- QString myPaths = settings.value( "composer/searchPathsForTemplates", QDir::homePath() ).toString();
+ QString myPaths = settings.value( "composer/searchPathsForTemplates", QString() ).toString();
if ( !myPaths.isEmpty() )
{
myPathList = myPaths.split( '|' );
diff --git a/src/core/qgscoordinatereferencesystem.cpp b/src/core/qgscoordinatereferencesystem.cpp
index 6a50078..7c00a63 100644
--- a/src/core/qgscoordinatereferencesystem.cpp
+++ b/src/core/qgscoordinatereferencesystem.cpp
@@ -927,6 +927,20 @@ void QgsCoordinateReferenceSystem::setProj4String( const QString& theProj4String
OSRDestroySpatialReference( mCRS );
mCRS = OSRNewSpatialReference( nullptr );
mIsValidFlag = OSRImportFromProj4( mCRS, theProj4String.trimmed().toLatin1().constData() ) == OGRERR_NONE;
+ // OSRImportFromProj4() may accept strings that are not valid proj.4 strings,
+ // e.g if they lack a +ellps parameter, it will automatically add +ellps=WGS84, but as
+ // we use the original mProj4 with QgsCoordinateTransform, it will fail to initialize
+ // so better detect it now.
+ projPJ theProj = pj_init_plus( theProj4String.trimmed().toLatin1().constData() );
+ if ( !theProj )
+ {
+ QgsDebugMsg( "proj.4 string rejected by pj_init_plus()" );
+ mIsValidFlag = false;
+ }
+ else
+ {
+ pj_free( theProj );
+ }
mWkt.clear();
setMapUnits();
diff --git a/src/core/qgscoordinatetransform.cpp b/src/core/qgscoordinatetransform.cpp
index 0518000..cdce4c5 100644
--- a/src/core/qgscoordinatetransform.cpp
+++ b/src/core/qgscoordinatetransform.cpp
@@ -668,7 +668,6 @@ void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *
{
x[i] *= DEG_TO_RAD;
y[i] *= DEG_TO_RAD;
- z[i] *= DEG_TO_RAD;
}
}
@@ -736,7 +735,6 @@ void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *
{
x[i] *= RAD_TO_DEG;
y[i] *= RAD_TO_DEG;
- z[i] *= RAD_TO_DEG;
}
}
#ifdef COORDINATE_TRANSFORM_VERBOSE
diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp
index 33bc1b0..df1c5ba 100644
--- a/src/core/qgsdataitem.cpp
+++ b/src/core/qgsdataitem.cpp
@@ -670,6 +670,8 @@ void QgsDataItem::setState( State state )
mPopulated = state == Populated;
emit stateChanged( this, oldState );
+ if ( state == Populated )
+ emitDataChanged();
}
// ---------------------------------------------------------------------
diff --git a/src/core/qgsdataprovider.h b/src/core/qgsdataprovider.h
index b4b3f6e..0c64ec4 100644
--- a/src/core/qgsdataprovider.h
+++ b/src/core/qgsdataprovider.h
@@ -311,6 +311,47 @@ class CORE_EXPORT QgsDataProvider : public QObject
*/
virtual void invalidateConnections( const QString& connection ) { Q_UNUSED( connection ); }
+ /** Enter update mode.
+ *
+ * This is aimed at providers that can open differently the connection to
+ * the datasource, according it to be in update mode or in read-only mode.
+ * A call to this method shall be balanced with a call to leaveUpdateMode(),
+ * if this method returns true.
+ *
+ * Most providers will have an empty implementation for that method.
+ *
+ * For backward compatibility, providers that implement enterUpdateMode() should
+ * still make sure to allow editing operations to work even if enterUpdateMode()
+ * is not explicitly called.
+ *
+ * Several successive calls to enterUpdateMode() can be done. So there is
+ * a concept of stack of calls that must be handled by the provider. Only the first
+ * call to enterUpdateMode() will really turn update mode on.
+ *
+ * @return true in case of success (or no-op implementation), false in case of failure.
+ *
+ * @note added in QGIS 2.14.4
+ */
+ virtual bool enterUpdateMode() { return true; }
+
+ /** Leave update mode.
+ *
+ * This is aimed at providers that can open differently the connection to
+ * the datasource, according it to be in update mode or in read-only mode.
+ * This method shall be balanced with a succesful call to enterUpdateMode().
+ *
+ * Most providers will have an empty implementation for that method.
+ *
+ * Several successive calls to enterUpdateMode() can be done. So there is
+ * a concept of stack of calls that must be handled by the provider. Only the last
+ * call to leaveUpdateMode() will really turn update mode off.
+ *
+ * @return true in case of success (or no-op implementation), false in case of failure.
+ *
+ * @note added in QGIS 2.14.4
+ */
+ virtual bool leaveUpdateMode() { return true; }
+
signals:
/**
diff --git a/src/core/qgsdistancearea.cpp b/src/core/qgsdistancearea.cpp
index b46ec67..01b8b48 100644
--- a/src/core/qgsdistancearea.cpp
+++ b/src/core/qgsdistancearea.cpp
@@ -890,6 +890,11 @@ void QgsDistanceArea::computeAreaInit()
double QgsDistanceArea::computePolygonArea( const QList<QgsPoint>& points ) const
{
+ if ( points.isEmpty() )
+ {
+ return 0;
+ }
+
double x1, y1, x2, y2, dx, dy;
double Qbar1, Qbar2;
double area;
@@ -926,7 +931,8 @@ double QgsDistanceArea::computePolygonArea( const QList<QgsPoint>& points ) cons
dx = x2 - x1;
area += dx * ( m_Qp - getQ( y2 ) );
- if (( dy = y2 - y1 ) != 0.0 )
+ dy = y2 - y1;
+ if ( !qgsDoubleNear( dy, 0.0 ) )
area += dx * getQ( y2 ) - ( dx / dy ) * ( Qbar2 - Qbar1 );
}
if (( area *= m_AE ) < 0.0 )
diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp
index a478da7..26853f1 100644
--- a/src/core/qgsexpression.cpp
+++ b/src/core/qgsexpression.cpp
@@ -3751,9 +3751,11 @@ QVariant QgsExpression::NodeBinaryOperator::eval( QgsExpression *parent, const Q
{
return TVL_Unknown;
}
- else if ( isDoubleSafe( vL ) && isDoubleSafe( vR ) )
+ else if ( isDoubleSafe( vL ) && isDoubleSafe( vR ) &&
+ ( vL.type() != QVariant::String || vR.type() != QVariant::String ) )
{
- // do numeric comparison if both operators can be converted to numbers
+ // do numeric comparison if both operators can be converted to numbers,
+ // and they aren't both string
double fL = getDoubleValue( vL, parent );
ENSURE_NO_EVAL_ERROR;
double fR = getDoubleValue( vR, parent );
@@ -3780,7 +3782,8 @@ QVariant QgsExpression::NodeBinaryOperator::eval( QgsExpression *parent, const Q
else // both operators non-null
{
bool equal = false;
- if ( isDoubleSafe( vL ) && isDoubleSafe( vR ) )
+ if ( isDoubleSafe( vL ) && isDoubleSafe( vR ) &&
+ ( vL.type() != QVariant::String || vR.type() != QVariant::String ) )
{
double fL = getDoubleValue( vL, parent );
ENSURE_NO_EVAL_ERROR;
diff --git a/src/core/qgsfeaturerequest.h b/src/core/qgsfeaturerequest.h
index 0fc468a..8eaa77a 100644
--- a/src/core/qgsfeaturerequest.h
+++ b/src/core/qgsfeaturerequest.h
@@ -370,7 +370,7 @@ class CORE_EXPORT QgsFeatureRequest
* Return the subset of attributes which at least need to be fetched
* @return A list of attributes to be fetched
*/
- const QgsAttributeList& subsetOfAttributes() const { return mAttrs; }
+ QgsAttributeList subsetOfAttributes() const { return mAttrs; }
//! Set a subset of attributes by names that will be fetched
QgsFeatureRequest& setSubsetOfAttributes( const QStringList& attrNames, const QgsFields& fields );
diff --git a/src/core/qgslabelfeature.cpp b/src/core/qgslabelfeature.cpp
index 4f0cd62..4ed75b7 100644
--- a/src/core/qgslabelfeature.cpp
+++ b/src/core/qgslabelfeature.cpp
@@ -13,7 +13,6 @@
* *
***************************************************************************/
#include "qgslabelfeature.h"
-
#include "feature.h"
diff --git a/src/core/qgslegendrenderer.cpp b/src/core/qgslegendrenderer.cpp
index 0306007..74cbd35 100644
--- a/src/core/qgslegendrenderer.cpp
+++ b/src/core/qgslegendrenderer.cpp
@@ -161,6 +161,7 @@ QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGr
// Group subitems
QList<Atom> groupAtoms = createAtomList( nodeGroup, splitLayer );
+ bool hasSubItems = groupAtoms.size() > 0;
if ( nodeLegendStyle( nodeGroup ) != QgsComposerLegendStyle::Hidden )
{
@@ -188,7 +189,12 @@ QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGr
groupAtoms.append( atom );
}
}
- atoms.append( groupAtoms );
+
+ if ( hasSubItems ) //leave away groups without content
+ {
+ atoms.append( groupAtoms );
+ }
+
}
else if ( QgsLayerTree::isLayer( node ) )
{
diff --git a/src/core/qgslogger.cpp b/src/core/qgslogger.cpp
index 250e050..c45066c 100644
--- a/src/core/qgslogger.cpp
+++ b/src/core/qgslogger.cpp
@@ -103,7 +103,7 @@ void QgsLogger::debug( const QString& msg, int debuglevel, const char* file, con
if ( sLogFile.isEmpty() )
{
- qDebug( "%s", m.toLocal8Bit().constData() );
+ qDebug( "%s", m.toUtf8().constData() );
}
else
{
diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp
index d710c18..08f9338 100644
--- a/src/core/qgsmaplayer.cpp
+++ b/src/core/qgsmaplayer.cpp
@@ -48,6 +48,7 @@
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "qgsmaplayerregistry.h"
+#include "qgsxmlutils.h"
QgsMapLayer::QgsMapLayer( QgsMapLayer::LayerType type,
@@ -425,6 +426,12 @@ bool QgsMapLayer::readLayerXML( const QDomElement& layerElement )
setMinimumScale( layerElement.attribute( "minimumScale" ).toFloat() );
setMaximumScale( layerElement.attribute( "maximumScale" ).toFloat() );
+ QDomNode extentNode = layerElement.namedItem( "extent" );
+ if ( !extentNode.isNull() )
+ {
+ setExtent( QgsXmlUtils::readRectangle( extentNode.toElement() ) );
+ }
+
// set name
mnl = layerElement.namedItem( "layername" );
mne = mnl.toElement();
@@ -531,6 +538,11 @@ bool QgsMapLayer::writeLayerXML( QDomElement& layerElement, QDomDocument& docume
layerElement.setAttribute( "minimumScale", QString::number( minimumScale() ) );
layerElement.setAttribute( "maximumScale", QString::number( maximumScale() ) );
+ if ( !mExtent.isNull() )
+ {
+ layerElement.appendChild( QgsXmlUtils::writeRectangle( mExtent, document ) );
+ }
+
// ID
QDomElement layerId = document.createElement( "id" );
QDomText layerIdText = document.createTextNode( id() );
diff --git a/src/core/qgsmaplayerregistry.cpp b/src/core/qgsmaplayerregistry.cpp
index 6a9cbe0..f13a913 100644
--- a/src/core/qgsmaplayerregistry.cpp
+++ b/src/core/qgsmaplayerregistry.cpp
@@ -127,27 +127,32 @@ void QgsMapLayerRegistry::removeMapLayers( const QStringList& theLayerIds )
void QgsMapLayerRegistry::removeMapLayers( const QList<QgsMapLayer*>& layers )
{
+ if ( layers.isEmpty() )
+ return;
+
QStringList layerIds;
+ QList<QgsMapLayer*> layerList;
Q_FOREACH ( QgsMapLayer* layer, layers )
{
- if ( layer )
+ // check layer and the registry contains it
+ if ( layer && mMapLayers.contains( layer->id() ) )
+ {
layerIds << layer->id();
+ layerList << layer;
+ }
}
emit layersWillBeRemoved( layerIds );
- emit layersWillBeRemoved( layers );
+ emit layersWillBeRemoved( layerList );
- Q_FOREACH ( QgsMapLayer* lyr, layers )
+ Q_FOREACH ( QgsMapLayer* lyr, layerList )
{
- if ( !lyr )
- continue;
-
QString myId( lyr->id() );
+ emit layerWillBeRemoved( myId );
+ emit layerWillBeRemoved( lyr );
if ( mOwnedLayers.contains( lyr ) )
{
- emit layerWillBeRemoved( myId );
- emit layerWillBeRemoved( lyr );
delete lyr;
mOwnedLayers.remove( lyr );
}
diff --git a/src/core/qgsmaprenderer.h b/src/core/qgsmaprenderer.h
index 15ebb87..648a38a 100644
--- a/src/core/qgsmaprenderer.h
+++ b/src/core/qgsmaprenderer.h
@@ -46,9 +46,10 @@ class QgsDiagramLayerSettings;
class CORE_EXPORT QgsLabelPosition
{
public:
- QgsLabelPosition( int id, double r, const QVector< QgsPoint >& corners, const QgsRectangle& rect, double w, double h, const QString& layer, const QString& labeltext, const QFont& labelfont, bool upside_down, bool diagram = false, bool pinned = false ):
- featureId( id ), rotation( r ), cornerPoints( corners ), labelRect( rect ), width( w ), height( h ), layerID( layer ), labelText( labeltext ), labelFont( labelfont ), upsideDown( upside_down ), isDiagram( diagram ), isPinned( pinned ) {}
- QgsLabelPosition(): featureId( -1 ), rotation( 0 ), labelRect( QgsRectangle() ), width( 0 ), height( 0 ), layerID( "" ), labelText( "" ), labelFont( QFont() ), upsideDown( false ), isDiagram( false ), isPinned( false ) {}
+ QgsLabelPosition( int id, double r, const QVector< QgsPoint >& corners, const QgsRectangle& rect, double w, double h, const QString& layer, const QString& labeltext, const QFont& labelfont, bool upside_down, bool diagram = false, bool pinned = false )
+ : featureId( id ), rotation( r ), cornerPoints( corners ), labelRect( rect ), width( w ), height( h ), layerID( layer ), labelText( labeltext ), labelFont( labelfont ), upsideDown( upside_down ), isDiagram( diagram ), isPinned( pinned ) {}
+ QgsLabelPosition()
+ : featureId( -1 ), rotation( 0 ), labelRect( QgsRectangle() ), width( 0 ), height( 0 ), layerID( "" ), labelText( "" ), labelFont( QFont() ), upsideDown( false ), isDiagram( false ), isPinned( false ) {}
int featureId;
double rotation;
QVector< QgsPoint > cornerPoints;
@@ -72,7 +73,7 @@ class CORE_EXPORT QgsLabelingEngineInterface
//! called when we're going to start with rendering
//! @deprecated since 2.4 - use override with QgsMapSettings
- Q_DECL_DEPRECATED virtual void init( QgsMapRenderer* mp ) = 0;
+ Q_DECL_DEPRECATED virtual void init( QgsMapRenderer *mp ) = 0;
//! called when we're going to start with rendering
virtual void init( const QgsMapSettings& mapSettings ) = 0;
//! called to find out whether the layer is used for labeling
@@ -85,19 +86,19 @@ class CORE_EXPORT QgsLabelingEngineInterface
virtual int prepareLayer( QgsVectorLayer* layer, QStringList& attrNames, QgsRenderContext& ctx ) = 0;
//! returns PAL layer settings for a registered layer
//! @deprecated since 2.12 - if direct access to QgsPalLayerSettings is necessary, use QgsPalLayerSettings::fromLayer()
- Q_DECL_DEPRECATED virtual QgsPalLayerSettings& layer( const QString& layerName ) = 0;
+ Q_DECL_DEPRECATED virtual QgsPalLayerSettings &layer( const QString &layerName ) = 0;
//! adds a diagram layer to the labeling engine
//! @note added in QGIS 2.12
- virtual int prepareDiagramLayer( QgsVectorLayer* layer, QStringList& attrNames, QgsRenderContext& ctx )
+ virtual int prepareDiagramLayer( QgsVectorLayer *layer, QStringList &attrNames, QgsRenderContext &ctx )
{ Q_UNUSED( layer ); Q_UNUSED( attrNames ); Q_UNUSED( ctx ); return 0; }
//! adds a diagram layer to the labeling engine
//! @deprecated since 2.12 - use prepareDiagramLayer()
- Q_DECL_DEPRECATED virtual int addDiagramLayer( QgsVectorLayer* layer, const QgsDiagramLayerSettings* s )
+ Q_DECL_DEPRECATED virtual int addDiagramLayer( QgsVectorLayer *layer, const QgsDiagramLayerSettings *s )
{ Q_UNUSED( layer ); Q_UNUSED( s ); return 0; }
//! called for every feature
- virtual void registerFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context, const QString& dxfLayer = QString::null ) = 0;
+ virtual void registerFeature( const QString &layerID, QgsFeature &feat, QgsRenderContext &context ) = 0;
//! called for every diagram feature
- virtual void registerDiagramFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context )
+ virtual void registerDiagramFeature( const QString &layerID, QgsFeature &feat, QgsRenderContext &context )
{ Q_UNUSED( layerID ); Q_UNUSED( feat ); Q_UNUSED( context ); }
//! called when the map is drawn and labels should be placed
virtual void drawLabeling( QgsRenderContext& context ) = 0;
diff --git a/src/core/qgsmapsettings.h b/src/core/qgsmapsettings.h
index 1a80b5e..24aa1c8 100644
--- a/src/core/qgsmapsettings.h
+++ b/src/core/qgsmapsettings.h
@@ -125,10 +125,10 @@ class CORE_EXPORT QgsMapSettings
//! Get color that is used for drawing of selected vector features
QColor selectionColor() const { return mSelectionColor; }
- //! Enumeration of flags that adjust the way how map is rendered
+ //! Enumeration of flags that adjust the way the map is rendered
enum Flag
{
- Antialiasing = 0x01, //!< Enable anti-aliasin for map rendering
+ Antialiasing = 0x01, //!< Enable anti-aliasing for map rendering
DrawEditingInfo = 0x02, //!< Enable drawing of vertex markers for layers in editing mode
ForceVectorOutput = 0x04, //!< Vector graphics should not be cached and drawn as raster images
UseAdvancedEffects = 0x08, //!< Enable layer transparency and blending effects
diff --git a/src/core/qgspallabeling.cpp b/src/core/qgspallabeling.cpp
index 7fc6cd3..d88b771 100644
--- a/src/core/qgspallabeling.cpp
+++ b/src/core/qgspallabeling.cpp
@@ -2093,13 +2093,11 @@ void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF* fm, QString t
#endif
}
-void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &context, const QString& dxfLayer, QgsLabelFeature** labelFeature , QgsGeometry* obstacleGeometry )
+void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &context, QgsLabelFeature** labelFeature , QgsGeometry* obstacleGeometry )
{
// either used in QgsPalLabeling (palLayer is set) or in QgsLabelingEngineV2 (labelFeature is set)
Q_ASSERT( labelFeature );
- Q_UNUSED( dxfLayer ); // now handled in QgsDxfLabelProvider
-
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
mCurFeat = &f;
@@ -2114,7 +2112,7 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
{
if ( isObstacle )
{
- registerObstacleFeature( f, context, QString(), labelFeature, obstacleGeometry );
+ registerObstacleFeature( f, context, labelFeature, obstacleGeometry );
}
return;
}
@@ -2900,7 +2898,7 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
{
distance *= vectorScaleFactor;
}
- double d = qAbs( ptOne.x() - ptZero.x() ) * distance;
+ double d = sqrt( ptOne.sqrDist( ptZero ) ) * distance;
( *labelFeature )->setDistLabel( d );
}
@@ -2966,10 +2964,8 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
lf->setDataDefinedValues( dataDefinedValues );
}
-void QgsPalLayerSettings::registerObstacleFeature( QgsFeature& f, QgsRenderContext &context, const QString& dxfLayer, QgsLabelFeature** obstacleFeature, QgsGeometry* obstacleGeometry )
+void QgsPalLayerSettings::registerObstacleFeature( QgsFeature& f, QgsRenderContext &context, QgsLabelFeature** obstacleFeature, QgsGeometry* obstacleGeometry )
{
- Q_UNUSED( dxfLayer ); // now handled in QgsDxfLabelProvider
-
mCurFeat = &f;
const QgsGeometry* geom = nullptr;
@@ -3963,9 +3959,8 @@ int QgsPalLabeling::addDiagramLayer( QgsVectorLayer* layer, const QgsDiagramLaye
return 0;
}
-void QgsPalLabeling::registerFeature( const QString& layerID, QgsFeature& f, QgsRenderContext &context, const QString& dxfLayer )
+void QgsPalLabeling::registerFeature( const QString& layerID, QgsFeature& f, QgsRenderContext &context )
{
- Q_UNUSED( dxfLayer ); // now handled by QgsDxfLabelProvider
if ( QgsVectorLayerLabelProvider* provider = mLabelProviders.value( layerID, nullptr ) )
provider->registerFeature( f, context );
}
diff --git a/src/core/qgspallabeling.h b/src/core/qgspallabeling.h
index da9e259..c966c09 100644
--- a/src/core/qgspallabeling.h
+++ b/src/core/qgspallabeling.h
@@ -58,6 +58,7 @@ class QgsMapSettings;
class QgsLabelFeature;
class QgsLabelingEngineV2;
class QgsVectorLayerLabelProvider;
+class QgsDxfExport;
class QgsVectorLayerDiagramProvider;
class CORE_EXPORT QgsPalLayerSettings
@@ -533,7 +534,6 @@ class CORE_EXPORT QgsPalLayerSettings
* @param f feature to label
* @param context render context. The QgsExpressionContext contained within the render context
* must have already had the feature and fields sets prior to calling this method.
- * @param dxfLayer dxfLayer name
* @param labelFeature if using QgsLabelingEngineV2, this will receive the label feature. Not available
* in Python bindings.
* @param obstacleGeometry optional obstacle geometry, if a different geometry to the feature's geometry
@@ -542,7 +542,7 @@ class CORE_EXPORT QgsPalLayerSettings
* the feature's original geometry will be used as an obstacle for labels. Not available
* in Python bindings.
*/
- void registerFeature( QgsFeature& f, QgsRenderContext& context, const QString& dxfLayer, QgsLabelFeature** labelFeature = nullptr, QgsGeometry* obstacleGeometry = nullptr );
+ void registerFeature( QgsFeature& f, QgsRenderContext& context, QgsLabelFeature** labelFeature = nullptr, QgsGeometry* obstacleGeometry = nullptr );
void readFromLayer( QgsVectorLayer* layer );
void writeToLayer( QgsVectorLayer* layer );
@@ -712,7 +712,7 @@ class CORE_EXPORT QgsPalLayerSettings
/** Registers a feature as an obstacle only (no label rendered)
*/
- void registerObstacleFeature( QgsFeature &f, QgsRenderContext &context, const QString& dxfLayer, QgsLabelFeature** obstacleFeature, QgsGeometry* obstacleGeometry = nullptr );
+ void registerObstacleFeature( QgsFeature &f, QgsRenderContext &context, QgsLabelFeature** obstacleFeature, QgsGeometry* obstacleGeometry = nullptr );
QMap<DataDefinedProperties, QVariant> dataDefinedValues;
QgsExpression* expression;
@@ -949,9 +949,8 @@ class CORE_EXPORT QgsPalLabeling : public QgsLabelingEngineInterface
* @param feat feature to label
* @param context render context. The QgsExpressionContext contained within the render context
* must have already had the feature and fields sets prior to calling this method.
- * @param dxfLayer dxfLayer name
*/
- virtual void registerFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context, const QString& dxfLayer = QString::null ) override;
+ virtual void registerFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context ) override;
virtual void registerDiagramFeature( const QString& layerID, QgsFeature& feat, QgsRenderContext& context ) override;
//! called when the map is drawn and labels should be placed
@@ -1057,6 +1056,7 @@ class CORE_EXPORT QgsPalLabeling : public QgsLabelingEngineInterface
const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues );
friend class QgsVectorLayerLabelProvider; // to allow calling the static methods above
+ friend class QgsDxfExport; // to allow calling the static methods above
void deleteTemporaryData();
diff --git a/src/core/qgsrendercontext.cpp b/src/core/qgsrendercontext.cpp
index d6e8fe6..0b9ef79 100644
--- a/src/core/qgsrendercontext.cpp
+++ b/src/core/qgsrendercontext.cpp
@@ -123,6 +123,7 @@ QgsRenderContext QgsRenderContext::fromMapSettings( const QgsMapSettings& mapSet
ctx.setFlag( DrawSelection, mapSettings.testFlag( QgsMapSettings::DrawSelection ) );
ctx.setFlag( DrawSymbolBounds, mapSettings.testFlag( QgsMapSettings::DrawSymbolBounds ) );
ctx.setFlag( RenderMapTile, mapSettings.testFlag( QgsMapSettings::RenderMapTile ) );
+ ctx.setFlag( Antialiasing, mapSettings.testFlag( QgsMapSettings::Antialiasing ) );
ctx.setRasterScaleFactor( 1.0 );
ctx.setScaleFactor( mapSettings.outputDpi() / 25.4 ); // = pixels per mm
ctx.setRendererScale( mapSettings.scale() );
diff --git a/src/core/qgsrendercontext.h b/src/core/qgsrendercontext.h
index 58df97a..be7d132 100644
--- a/src/core/qgsrendercontext.h
+++ b/src/core/qgsrendercontext.h
@@ -63,6 +63,7 @@ class CORE_EXPORT QgsRenderContext
DrawSelection = 0x10, //!< Whether vector selections should be shown in the rendered map
DrawSymbolBounds = 0x20, //!< Draw bounds of symbols (for debugging/testing)
RenderMapTile = 0x40, //!< Draw map such that there are no problems between adjacent tiles
+ Antialiasing = 0x80, //!< Use antialiasing while drawing
};
Q_DECLARE_FLAGS( Flags, Flag )
diff --git a/src/core/qgsrulebasedlabeling.cpp b/src/core/qgsrulebasedlabeling.cpp
index 54fbf21..4f89475 100644
--- a/src/core/qgsrulebasedlabeling.cpp
+++ b/src/core/qgsrulebasedlabeling.cpp
@@ -15,12 +15,11 @@
#include "qgsrulebasedlabeling.h"
-
QgsRuleBasedLabelProvider::QgsRuleBasedLabelProvider( const QgsRuleBasedLabeling& rules, QgsVectorLayer* layer, bool withFeatureLoop )
: QgsVectorLayerLabelProvider( layer, withFeatureLoop )
, mRules( rules )
{
- mRules.rootRule()->createSubProviders( layer, mSubProviders );
+ mRules.rootRule()->createSubProviders( layer, mSubProviders, this );
}
QgsRuleBasedLabelProvider::~QgsRuleBasedLabelProvider()
@@ -28,6 +27,10 @@ QgsRuleBasedLabelProvider::~QgsRuleBasedLabelProvider()
// sub-providers owned by labeling engine
}
+QgsVectorLayerLabelProvider *QgsRuleBasedLabelProvider::createProvider( QgsVectorLayer *layer, bool withFeatureLoop, const QgsPalLayerSettings *settings )
+{
+ return new QgsVectorLayerLabelProvider( layer, withFeatureLoop, settings );
+}
bool QgsRuleBasedLabelProvider::prepare( const QgsRenderContext& context, QStringList& attributeNames )
{
@@ -213,19 +216,20 @@ QDomElement QgsRuleBasedLabeling::Rule::save( QDomDocument& doc ) const
return ruleElem;
}
-void QgsRuleBasedLabeling::Rule::createSubProviders( QgsVectorLayer* layer, QgsRuleBasedLabeling::RuleToProviderMap& subProviders )
+void QgsRuleBasedLabeling::Rule::createSubProviders( QgsVectorLayer* layer, QgsRuleBasedLabeling::RuleToProviderMap& subProviders, QgsRuleBasedLabelProvider *provider )
{
if ( mSettings )
{
// add provider!
- QgsVectorLayerLabelProvider* p = new QgsVectorLayerLabelProvider( layer, false, mSettings );
+ QgsVectorLayerLabelProvider *p = provider->createProvider( layer, false, mSettings );
+ delete subProviders.value( this, nullptr );
subProviders[this] = p;
}
// call recursively
Q_FOREACH ( Rule* rule, mChildren )
{
- rule->createSubProviders( layer, subProviders );
+ rule->createSubProviders( layer, subProviders, provider );
}
}
@@ -262,6 +266,8 @@ QgsRuleBasedLabeling::Rule::RegisterResult QgsRuleBasedLabeling::Rule::registerF
bool registered = false;
+ Q_ASSERT( !mSettings == subProviders.contains( this ) );
+
// do we have active subprovider for the rule?
if ( subProviders.contains( this ) && mIsActive )
{
diff --git a/src/core/qgsrulebasedlabeling.h b/src/core/qgsrulebasedlabeling.h
index ac4d0fb..8d39b8e 100644
--- a/src/core/qgsrulebasedlabeling.h
+++ b/src/core/qgsrulebasedlabeling.h
@@ -19,6 +19,7 @@
#include <QMap>
#include "qgsvectorlayerlabeling.h"
+#include "qgsvectorlayerlabelprovider.h"
class QDomDocument;
class QDomElement;
@@ -28,6 +29,7 @@ class QgsFeature;
class QgsPalLayerSettings;
class QgsRenderContext;
class QgsGeometry;
+class QgsRuleBasedLabelProvider;
/**
* @class QgsRuleBasedLabeling
@@ -42,7 +44,6 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
typedef QList<Rule*> RuleList;
typedef QMap<Rule*, QgsVectorLayerLabelProvider*> RuleToProviderMap;
-
/**
* @class QgsRuleBasedLabeling::Rule
* @note not available in Python bindings
@@ -214,7 +215,7 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
// evaluation
//! add providers
- void createSubProviders( QgsVectorLayer* layer, RuleToProviderMap& subProviders );
+ void createSubProviders( QgsVectorLayer* layer, RuleToProviderMap& subProviders, QgsRuleBasedLabelProvider *provider );
//! call prepare() on sub-providers and populate attributeNames
void prepare( const QgsRenderContext& context, QStringList& attributeNames, RuleToProviderMap& subProviders );
@@ -285,15 +286,13 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
virtual QString type() const override;
virtual QDomElement save( QDomDocument& doc ) const override;
- virtual QgsVectorLayerLabelProvider* provider( QgsVectorLayer* layer ) const override;
+ virtual QgsVectorLayerLabelProvider *provider( QgsVectorLayer* layer ) const override;
protected:
Rule* mRootRule;
};
-#include "qgsvectorlayerlabelprovider.h"
-
/**
* @class QgsRuleBasedLabelProvider
* @note not available in Python bindings
@@ -312,6 +311,7 @@ class CORE_EXPORT QgsRuleBasedLabelProvider : public QgsVectorLayerLabelProvider
virtual void registerFeature( QgsFeature& feature, QgsRenderContext& context, QgsGeometry* obstacleGeometry = nullptr ) override;
// new methods
+ virtual QgsVectorLayerLabelProvider *createProvider( QgsVectorLayer *layer, bool withFeatureLoop, const QgsPalLayerSettings *settings );
virtual QList<QgsAbstractLabelProvider*> subProviders() override;
diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp
index 851954d..c8d4d71 100644
--- a/src/core/qgsvectorlayer.cpp
+++ b/src/core/qgsvectorlayer.cpp
@@ -379,6 +379,7 @@ void QgsVectorLayer::reload()
if ( mDataProvider )
{
mDataProvider->reloadData();
+ updateFields();
}
}
@@ -1380,6 +1381,8 @@ bool QgsVectorLayer::startEditing()
emit beforeEditingStarted();
+ mDataProvider->enterUpdateMode();
+
if ( mDataProvider->transaction() )
{
mEditBuffer = new QgsVectorLayerEditPassthrough( this );
@@ -2334,6 +2337,8 @@ bool QgsVectorLayer::commitChanges()
updateFields();
mDataProvider->updateExtents();
+ mDataProvider->leaveUpdateMode();
+
emit repaintRequested();
return success;
@@ -2384,6 +2389,8 @@ bool QgsVectorLayer::rollBack( bool deleteBuffer )
if ( rollbackExtent )
updateExtents();
+ mDataProvider->leaveUpdateMode();
+
emit repaintRequested();
return true;
}
@@ -2931,87 +2938,81 @@ void QgsVectorLayer::uniqueValues( int index, QList<QVariant> &uniqueValues, int
}
QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin( index );
- if ( origin == QgsFields::OriginUnknown )
- {
- return;
- }
-
- if ( origin == QgsFields::OriginProvider ) //a provider field
+ switch ( origin )
{
- mDataProvider->uniqueValues( index, uniqueValues, limit );
+ case QgsFields::OriginUnknown:
+ return;
- if ( mEditBuffer )
+ case QgsFields::OriginProvider: //a provider field
{
- QSet<QString> vals;
- Q_FOREACH ( const QVariant& v, uniqueValues )
- {
- vals << v.toString();
- }
+ mDataProvider->uniqueValues( index, uniqueValues, limit );
- QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
- while ( it.hasNext() && ( limit < 0 || uniqueValues.count() < limit ) )
+ if ( mEditBuffer )
{
- it.next();
- QVariant v = it.value().value( index );
- if ( v.isValid() )
+ QSet<QString> vals;
+ Q_FOREACH ( const QVariant& v, uniqueValues )
+ {
+ vals << v.toString();
+ }
+
+ QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
+ while ( it.hasNext() && ( limit < 0 || uniqueValues.count() < limit ) )
{
- QString vs = v.toString();
- if ( !vals.contains( vs ) )
+ it.next();
+ QVariant v = it.value().value( index );
+ if ( v.isValid() )
{
- vals << vs;
- uniqueValues << v;
+ QString vs = v.toString();
+ if ( !vals.contains( vs ) )
+ {
+ vals << vs;
+ uniqueValues << v;
+ }
}
}
}
- }
-
- return;
- }
- else if ( origin == QgsFields::OriginJoin )
- {
- int sourceLayerIndex;
- const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex );
- Q_ASSERT( join );
-
- QgsVectorLayer *vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) );
-
- if ( vl )
- vl->dataProvider()->uniqueValues( sourceLayerIndex, uniqueValues, limit );
- return;
- }
- else if ( origin == QgsFields::OriginEdit || origin == QgsFields::OriginExpression )
- {
- // the layer is editable, but in certain cases it can still be avoided going through all features
- if ( origin == QgsFields::OriginEdit && mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mAddedFeatures.isEmpty() && !mEditBuffer->mDeletedAttributeIds.contains( index ) && mEditBuffer->mChangedAttributeValues.isEmpty() )
- {
- mDataProvider->uniqueValues( index, uniqueValues, limit );
return;
}
- // we need to go through each feature
- QgsAttributeList attList;
- attList << index;
+ case QgsFields::OriginEdit:
+ // the layer is editable, but in certain cases it can still be avoided going through all features
+ if ( mEditBuffer->mDeletedFeatureIds.isEmpty() &&
+ mEditBuffer->mAddedFeatures.isEmpty() &&
+ !mEditBuffer->mDeletedAttributeIds.contains( index ) &&
+ mEditBuffer->mChangedAttributeValues.isEmpty() )
+ {
+ mDataProvider->uniqueValues( index, uniqueValues, limit );
+ return;
+ }
+ FALLTHROUGH;
+ //we need to go through each feature
+ case QgsFields::OriginJoin:
+ case QgsFields::OriginExpression:
+ {
+ QgsAttributeList attList;
+ attList << index;
- QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
- .setFlags( QgsFeatureRequest::NoGeometry )
- .setSubsetOfAttributes( attList ) );
+ QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
+ .setFlags( QgsFeatureRequest::NoGeometry )
+ .setSubsetOfAttributes( attList ) );
- QgsFeature f;
- QVariant currentValue;
- QHash<QString, QVariant> val;
- while ( fit.nextFeature( f ) )
- {
- currentValue = f.attribute( index );
- val.insert( currentValue.toString(), currentValue );
- if ( limit >= 0 && val.size() >= limit )
+ QgsFeature f;
+ QVariant currentValue;
+ QHash<QString, QVariant> val;
+ while ( fit.nextFeature( f ) )
{
- break;
+ currentValue = f.attribute( index );
+ val.insert( currentValue.toString(), currentValue );
+ if ( limit >= 0 && val.size() >= limit )
+ {
+ break;
+ }
}
- }
- uniqueValues = val.values();
- return;
+ uniqueValues = val.values();
+ return;
+ }
}
Q_ASSERT_X( false, "QgsVectorLayer::uniqueValues()", "Unknown source of the field!" );
@@ -3025,58 +3026,52 @@ QVariant QgsVectorLayer::minimumValue( int index )
}
QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin( index );
- if ( origin == QgsFields::OriginUnknown )
- {
- return QVariant();
- }
- if ( origin == QgsFields::OriginProvider ) //a provider field
- {
- return mDataProvider->minimumValue( index );
- }
- else if ( origin == QgsFields::OriginJoin )
+ switch ( origin )
{
- int sourceLayerIndex;
- const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex );
- Q_ASSERT( join );
+ case QgsFields::OriginUnknown:
+ return QVariant();
- QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) );
- Q_ASSERT( vl );
+ case QgsFields::OriginProvider: //a provider field
+ return mDataProvider->minimumValue( index );
- return vl->minimumValue( sourceLayerIndex );
- }
- else if ( origin == QgsFields::OriginEdit || origin == QgsFields::OriginExpression )
- {
- // the layer is editable, but in certain cases it can still be avoided going through all features
- if ( origin == QgsFields::OriginEdit &&
- mEditBuffer->mDeletedFeatureIds.isEmpty() &&
- mEditBuffer->mAddedFeatures.isEmpty() && !
- mEditBuffer->mDeletedAttributeIds.contains( index ) &&
- mEditBuffer->mChangedAttributeValues.isEmpty() )
+ case QgsFields::OriginEdit:
{
- return mDataProvider->minimumValue( index );
+ // the layer is editable, but in certain cases it can still be avoided going through all features
+ if ( mEditBuffer->mDeletedFeatureIds.isEmpty() &&
+ mEditBuffer->mAddedFeatures.isEmpty() && !
+ mEditBuffer->mDeletedAttributeIds.contains( index ) &&
+ mEditBuffer->mChangedAttributeValues.isEmpty() )
+ {
+ return mDataProvider->minimumValue( index );
+ }
}
+ FALLTHROUGH;
+ // no choice but to go through all features
+ case QgsFields::OriginExpression:
+ case QgsFields::OriginJoin:
+ {
+ // we need to go through each feature
+ QgsAttributeList attList;
+ attList << index;
- // we need to go through each feature
- QgsAttributeList attList;
- attList << index;
-
- QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
- .setFlags( QgsFeatureRequest::NoGeometry )
- .setSubsetOfAttributes( attList ) );
+ QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
+ .setFlags( QgsFeatureRequest::NoGeometry )
+ .setSubsetOfAttributes( attList ) );
- QgsFeature f;
- double minimumValue = std::numeric_limits<double>::max();
- double currentValue = 0;
- while ( fit.nextFeature( f ) )
- {
- currentValue = f.attribute( index ).toDouble();
- if ( currentValue < minimumValue )
+ QgsFeature f;
+ double minimumValue = std::numeric_limits<double>::max();
+ double currentValue = 0;
+ while ( fit.nextFeature( f ) )
{
- minimumValue = currentValue;
+ currentValue = f.attribute( index ).toDouble();
+ if ( currentValue < minimumValue )
+ {
+ minimumValue = currentValue;
+ }
}
+ return QVariant( minimumValue );
}
- return QVariant( minimumValue );
}
Q_ASSERT_X( false, "QgsVectorLayer::minimumValue()", "Unknown source of the field!" );
@@ -3091,58 +3086,49 @@ QVariant QgsVectorLayer::maximumValue( int index )
}
QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin( index );
- if ( origin == QgsFields::OriginUnknown )
+ switch ( origin )
{
- return QVariant();
- }
+ case QgsFields::OriginUnknown:
+ return QVariant();
- if ( origin == QgsFields::OriginProvider ) //a provider field
- {
- return mDataProvider->maximumValue( index );
- }
- else if ( origin == QgsFields::OriginJoin )
- {
- int sourceLayerIndex;
- const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex );
- Q_ASSERT( join );
+ case QgsFields::OriginProvider: //a provider field
+ return mDataProvider->maximumValue( index );
- QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) );
- Q_ASSERT( vl );
+ case QgsFields::OriginEdit:
+ // the layer is editable, but in certain cases it can still be avoided going through all features
+ if ( mEditBuffer->mDeletedFeatureIds.isEmpty() &&
+ mEditBuffer->mAddedFeatures.isEmpty() &&
+ !mEditBuffer->mDeletedAttributeIds.contains( index ) &&
+ mEditBuffer->mChangedAttributeValues.isEmpty() )
+ {
+ return mDataProvider->maximumValue( index );
+ }
- return vl->maximumValue( sourceLayerIndex );
- }
- else if ( origin == QgsFields::OriginEdit || origin == QgsFields::OriginExpression )
- {
- // the layer is editable, but in certain cases it can still be avoided going through all features
- if ( origin == QgsFields::OriginEdit &&
- mEditBuffer->mDeletedFeatureIds.isEmpty() &&
- mEditBuffer->mAddedFeatures.isEmpty() &&
- !mEditBuffer->mDeletedAttributeIds.contains( index ) &&
- mEditBuffer->mChangedAttributeValues.isEmpty() )
+ FALLTHROUGH;
+ //no choice but to go through each feature
+ case QgsFields::OriginJoin:
+ case QgsFields::OriginExpression:
{
- return mDataProvider->maximumValue( index );
- }
-
- // we need to go through each feature
- QgsAttributeList attList;
- attList << index;
+ QgsAttributeList attList;
+ attList << index;
- QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
- .setFlags( QgsFeatureRequest::NoGeometry )
- .setSubsetOfAttributes( attList ) );
+ QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
+ .setFlags( QgsFeatureRequest::NoGeometry )
+ .setSubsetOfAttributes( attList ) );
- QgsFeature f;
- double maximumValue = -std::numeric_limits<double>::max();
- double currentValue = 0;
- while ( fit.nextFeature( f ) )
- {
- currentValue = f.attribute( index ).toDouble();
- if ( currentValue > maximumValue )
+ QgsFeature f;
+ double maximumValue = -std::numeric_limits<double>::max();
+ double currentValue = 0;
+ while ( fit.nextFeature( f ) )
{
- maximumValue = currentValue;
+ currentValue = f.attribute( index ).toDouble();
+ if ( currentValue > maximumValue )
+ {
+ maximumValue = currentValue;
+ }
}
+ return QVariant( maximumValue );
}
- return QVariant( maximumValue );
}
Q_ASSERT_X( false, "QgsVectorLayer::maximumValue()", "Unknown source of the field!" );
diff --git a/src/core/qgsvectorlayerdiagramprovider.cpp b/src/core/qgsvectorlayerdiagramprovider.cpp
index 52467ca..13daa59 100644
--- a/src/core/qgsvectorlayerdiagramprovider.cpp
+++ b/src/core/qgsvectorlayerdiagramprovider.cpp
@@ -368,6 +368,6 @@ QgsLabelFeature* QgsVectorLayerDiagramProvider::registerDiagram( QgsFeature& fea
QgsPoint ptZero = mSettings.xform->toMapCoordinates( 0, 0 );
QgsPoint ptOne = mSettings.xform->toMapCoordinates( 1, 0 );
- lf->setDistLabel( qAbs( ptOne.x() - ptZero.x() ) * mSettings.dist );
+ lf->setDistLabel( sqrt( ptOne.sqrDist( ptZero ) ) * mSettings.dist );
return lf;
}
diff --git a/src/core/qgsvectorlayerfeatureiterator.cpp b/src/core/qgsvectorlayerfeatureiterator.cpp
index 184e5ca..7448ba9 100644
--- a/src/core/qgsvectorlayerfeatureiterator.cpp
+++ b/src/core/qgsvectorlayerfeatureiterator.cpp
@@ -538,6 +538,7 @@ void QgsVectorLayerFeatureIterator::prepareExpressions()
mExpressionContext->appendScope( QgsExpressionContextUtils::projectScope() );
mExpressionContext->setFields( mSource->mFields );
+ QList< int > virtualFieldsToFetch;
for ( int i = 0; i < mSource->mFields.count(); i++ )
{
if ( mSource->mFields.fieldOrigin( i ) == QgsFields::OriginExpression )
@@ -546,38 +547,53 @@ void QgsVectorLayerFeatureIterator::prepareExpressions()
if ( !( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
|| mRequest.subsetOfAttributes().contains( i ) )
{
- int oi = mSource->mFields.fieldOriginIndex( i );
- QgsExpression* exp = new QgsExpression( exps[oi].cachedExpression );
-
- QgsDistanceArea da;
- da.setSourceCrs( mSource->mCrsId );
- da.setEllipsoidalMode( true );
- da.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
- exp->setGeomCalculator( da );
- exp->setDistanceUnits( QgsProject::instance()->distanceUnits() );
- exp->setAreaUnits( QgsProject::instance()->areaUnits() );
-
- exp->prepare( mExpressionContext.data() );
- mExpressionFieldInfo.insert( i, exp );
-
- if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
- {
- QgsAttributeList attrs;
- Q_FOREACH ( const QString& col, exp->referencedColumns() )
- {
- attrs.append( mSource->mFields.fieldNameIndex( col ) );
- }
-
- mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() + attrs );
- }
-
- if ( exp->needsGeometry() )
- {
- mRequest.setFlags( mRequest.flags() & ~QgsFeatureRequest::NoGeometry );
- }
+ virtualFieldsToFetch << i;
}
}
}
+
+ QList< int > virtualFieldsProcessed;
+ while ( !virtualFieldsToFetch.isEmpty() )
+ {
+ int fieldIdx = virtualFieldsToFetch.takeFirst();
+ if ( virtualFieldsProcessed.contains( fieldIdx ) )
+ continue;
+
+ virtualFieldsProcessed << fieldIdx;
+
+
+ int oi = mSource->mFields.fieldOriginIndex( fieldIdx );
+ QgsExpression* exp = new QgsExpression( exps[oi].cachedExpression );
+
+ QgsDistanceArea da;
+ da.setSourceCrs( mSource->mCrsId );
+ da.setEllipsoidalMode( true );
+ da.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
+ exp->setGeomCalculator( da );
+ exp->setDistanceUnits( QgsProject::instance()->distanceUnits() );
+ exp->setAreaUnits( QgsProject::instance()->areaUnits() );
+
+ exp->prepare( mExpressionContext.data() );
+ mExpressionFieldInfo.insert( fieldIdx, exp );
+
+ Q_FOREACH ( const QString& col, exp->referencedColumns() )
+ {
+ int dependantFieldIdx = mSource->mFields.fieldNameIndex( col );
+ if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
+ {
+ mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() << dependantFieldIdx );
+ }
+ // also need to fetch this dependant field
+ if ( mSource->mFields.fieldOrigin( dependantFieldIdx ) == QgsFields::OriginExpression )
+ virtualFieldsToFetch << dependantFieldIdx;
+ }
+
+ if ( exp->needsGeometry() )
+ {
+ mRequest.setFlags( mRequest.flags() & ~QgsFeatureRequest::NoGeometry );
+ }
+ }
+
}
void QgsVectorLayerFeatureIterator::addJoinedAttributes( QgsFeature &f )
diff --git a/src/core/qgsvectorlayerlabelprovider.cpp b/src/core/qgsvectorlayerlabelprovider.cpp
index 4807264..9780a60 100644
--- a/src/core/qgsvectorlayerlabelprovider.cpp
+++ b/src/core/qgsvectorlayerlabelprovider.cpp
@@ -316,7 +316,7 @@ QList<QgsLabelFeature*> QgsVectorLayerLabelProvider::labelFeatures( QgsRenderCon
void QgsVectorLayerLabelProvider::registerFeature( QgsFeature& feature, QgsRenderContext& context, QgsGeometry* obstacleGeometry )
{
QgsLabelFeature* label = nullptr;
- mSettings.registerFeature( feature, context, QString(), &label, obstacleGeometry );
+ mSettings.registerFeature( feature, context, &label, obstacleGeometry );
if ( label )
mLabels << label;
}
diff --git a/src/core/qgsvectorlayerlabelprovider.h b/src/core/qgsvectorlayerlabelprovider.h
index 3ad6049..fb3efaf 100644
--- a/src/core/qgsvectorlayerlabelprovider.h
+++ b/src/core/qgsvectorlayerlabelprovider.h
@@ -116,5 +116,4 @@ class CORE_EXPORT QgsVectorLayerLabelProvider : public QgsAbstractLabelProvider
QList<QgsLabelFeature*> mLabels;
};
-
#endif // QGSVECTORLAYERLABELPROVIDER_H
diff --git a/src/core/raster/qgscontrastenhancement.cpp b/src/core/raster/qgscontrastenhancement.cpp
index 1fa98b2..c1d323a 100644
--- a/src/core/raster/qgscontrastenhancement.cpp
+++ b/src/core/raster/qgscontrastenhancement.cpp
@@ -25,6 +25,7 @@ class originally created circa 2004 by T.Sutton, Gary E.Sherman, Steve Halasz
#include "qgslinearminmaxenhancement.h"
#include "qgslinearminmaxenhancementwithclip.h"
#include "qgscliptominmaxenhancement.h"
+#include "qgsrasterblock.h"
#include <QDomDocument>
#include <QDomElement>
@@ -361,13 +362,13 @@ void QgsContrastEnhancement::writeXML( QDomDocument& doc, QDomElement& parentEle
{
//minimum value
QDomElement minElem = doc.createElement( "minValue" );
- QDomText minText = doc.createTextNode( QString::number( mMinimumValue ) );
+ QDomText minText = doc.createTextNode( QgsRasterBlock::printValue( mMinimumValue ) );
minElem.appendChild( minText );
parentElem.appendChild( minElem );
//maximum value
QDomElement maxElem = doc.createElement( "maxValue" );
- QDomText maxText = doc.createTextNode( QString::number( mMaximumValue ) );
+ QDomText maxText = doc.createTextNode( QgsRasterBlock::printValue( mMaximumValue ) );
maxElem.appendChild( maxText );
parentElem.appendChild( maxElem );
diff --git a/src/core/raster/qgsrasterfilewriter.cpp b/src/core/raster/qgsrasterfilewriter.cpp
index af388d5..aa8c474 100644
--- a/src/core/raster/qgsrasterfilewriter.cpp
+++ b/src/core/raster/qgsrasterfilewriter.cpp
@@ -355,7 +355,7 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster(
// hmm why is there a for(;;) here ..
// not good coding practice IMHO, it might be better to use [ for() and break ] or [ while (test) ]
- for ( ;; )
+ Q_FOREVER
{
for ( int i = 1; i <= nBands; ++i )
{
diff --git a/src/core/raster/qgsrasterlayer.cpp b/src/core/raster/qgsrasterlayer.cpp
index 2e41268..0fb031e 100644
--- a/src/core/raster/qgsrasterlayer.cpp
+++ b/src/core/raster/qgsrasterlayer.cpp
@@ -1528,8 +1528,8 @@ bool QgsRasterLayer::writeXml( QDomNode & layer_node,
{
QDomElement noDataRange = document.createElement( "noDataRange" );
- noDataRange.setAttribute( "min", range.min() );
- noDataRange.setAttribute( "max", range.max() );
+ noDataRange.setAttribute( "min", QgsRasterBlock::printValue( range.min() ) );
+ noDataRange.setAttribute( "max", QgsRasterBlock::printValue( range.max() ) );
noDataRangeList.appendChild( noDataRange );
}
diff --git a/src/core/raster/qgsrastershader.cpp b/src/core/raster/qgsrastershader.cpp
index f601840..e1f5e95 100644
--- a/src/core/raster/qgsrastershader.cpp
+++ b/src/core/raster/qgsrastershader.cpp
@@ -19,6 +19,7 @@ email : ersts at amnh.org
#include "qgslogger.h"
#include "qgscolorrampshader.h"
#include "qgsrastershader.h"
+#include "qgsrasterblock.h"
#include <QDomDocument>
#include <QDomElement>
@@ -151,7 +152,7 @@ void QgsRasterShader::writeXML( QDomDocument& doc, QDomElement& parent ) const
{
QDomElement itemElem = doc.createElement( "item" );
itemElem.setAttribute( "label", itemIt->label );
- itemElem.setAttribute( "value", QString::number( itemIt->value ) );
+ itemElem.setAttribute( "value", QgsRasterBlock::printValue( itemIt->value ) );
itemElem.setAttribute( "color", itemIt->color.name() );
itemElem.setAttribute( "alpha", itemIt->color.alpha() );
colorRampShaderElem.appendChild( itemElem );
diff --git a/src/core/raster/qgssinglebandpseudocolorrenderer.cpp b/src/core/raster/qgssinglebandpseudocolorrenderer.cpp
index 9dc8f59..ae67c5d 100644
--- a/src/core/raster/qgssinglebandpseudocolorrenderer.cpp
+++ b/src/core/raster/qgssinglebandpseudocolorrenderer.cpp
@@ -224,8 +224,8 @@ void QgsSingleBandPseudoColorRenderer::writeXML( QDomDocument& doc, QDomElement&
{
mShader->writeXML( doc, rasterRendererElem ); //todo: include color ramp items directly in this renderer
}
- rasterRendererElem.setAttribute( "classificationMin", QString::number( mClassificationMin ) );
- rasterRendererElem.setAttribute( "classificationMax", QString::number( mClassificationMax ) );
+ rasterRendererElem.setAttribute( "classificationMin", QgsRasterBlock::printValue( mClassificationMin ) );
+ rasterRendererElem.setAttribute( "classificationMax", QgsRasterBlock::printValue( mClassificationMax ) );
rasterRendererElem.setAttribute( "classificationMinMaxOrigin", QgsRasterRenderer::minMaxOriginName( mClassificationMinMaxOrigin ) );
parentElem.appendChild( rasterRendererElem );
diff --git a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp
index d39409a..11ca33f 100644
--- a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp
+++ b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp
@@ -724,7 +724,7 @@ QDomElement QgsCategorizedSymbolRendererV2::save( QDomDocument& doc )
QgsSymbolV2Map symbols;
QDomElement catsElem = doc.createElement( "categories" );
QgsCategoryList::const_iterator it = mCategories.constBegin();
- for ( ; it != mCategories.end(); ++it )
+ for ( ; it != mCategories.constEnd(); ++it )
{
const QgsRendererCategoryV2& cat = *it;
QString symbolName = QString::number( i );
diff --git a/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp b/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp
index 16b14db..ef7ba6a 100644
--- a/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp
+++ b/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp
@@ -686,17 +686,17 @@ QRectF QgsEllipseSymbolLayerV2::bounds( QPointF point, QgsSymbolV2RenderContext&
return symbolBounds;
}
-bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext *context, const QgsFeature*, QPointF shift ) const
+bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext &context, QPointF shift ) const
{
//width
double symbolWidth = mSymbolWidth;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_WIDTH ) ) //1. priority: data defined setting on symbol layer le
{
- context->setOriginalValueVariable( mSymbolWidth );
- symbolWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_WIDTH, *context, mSymbolWidth ).toDouble();
+ context.setOriginalValueVariable( mSymbolWidth );
+ symbolWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_WIDTH, context, mSymbolWidth ).toDouble();
}
- else if ( context->renderHints() & QgsSymbolV2::DataDefinedSizeScale ) //2. priority: is data defined size on symbol level
+ else if ( context.renderHints() & QgsSymbolV2::DataDefinedSizeScale ) //2. priority: is data defined size on symbol level
{
symbolWidth = mSize;
}
@@ -709,10 +709,10 @@ bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFa
double symbolHeight = mSymbolHeight;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_HEIGHT ) ) //1. priority: data defined setting on symbol layer level
{
- context->setOriginalValueVariable( mSymbolHeight );
- symbolHeight = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_HEIGHT, *context, mSymbolHeight ).toDouble();
+ context.setOriginalValueVariable( mSymbolHeight );
+ symbolHeight = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_HEIGHT, context, mSymbolHeight ).toDouble();
}
- else if ( context->renderHints() & QgsSymbolV2::DataDefinedSizeScale ) //2. priority: is data defined size on symbol level
+ else if ( context.renderHints() & QgsSymbolV2::DataDefinedSizeScale ) //2. priority: is data defined size on symbol level
{
symbolHeight = mSize;
}
@@ -726,8 +726,8 @@ bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFa
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH ) )
{
- context->setOriginalValueVariable( mOutlineWidth );
- outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, *context, mOutlineWidth ).toDouble();
+ context.setOriginalValueVariable( mOutlineWidth );
+ outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, context, mOutlineWidth ).toDouble();
}
if ( mOutlineWidthUnit == QgsSymbolV2::MM )
{
@@ -739,8 +739,8 @@ bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFa
QColor fc = mColor;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_FILL_COLOR ) )
{
- context->setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mColor ) );
- QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_FILL_COLOR, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mColor ) );
+ QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_FILL_COLOR, context, QVariant(), &ok ).toString();
if ( ok )
fc = QgsSymbolLayerV2Utils::decodeColor( colorString );
}
@@ -749,8 +749,8 @@ bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFa
QColor oc = mOutlineColor;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_COLOR ) )
{
- context->setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mOutlineColor ) );
- QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_COLOR, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mOutlineColor ) );
+ QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_COLOR, context, QVariant(), &ok ).toString();
if ( ok )
oc = QgsSymbolLayerV2Utils::decodeColor( colorString );
}
@@ -759,22 +759,22 @@ bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFa
QString symbolName = mSymbolName;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_SYMBOL_NAME ) )
{
- context->setOriginalValueVariable( mSymbolName );
- symbolName = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_SYMBOL_NAME, *context, mSymbolName ).toString();
+ context.setOriginalValueVariable( mSymbolName );
+ symbolName = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_SYMBOL_NAME, context, mSymbolName ).toString();
}
//offset
double offsetX = 0;
double offsetY = 0;
- markerOffset( *context, offsetX, offsetY );
+ markerOffset( context, offsetX, offsetY );
QPointF off( offsetX, offsetY );
//priority for rotation: 1. data defined symbol level, 2. symbol layer rotation (mAngle)
double rotation = 0.0;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION ) )
{
- context->setOriginalValueVariable( mAngle );
- rotation = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION, *context, mAngle ).toDouble() + mLineAngle;
+ context.setOriginalValueVariable( mAngle );
+ rotation = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION, context, mAngle ).toDouble() + mLineAngle;
}
else if ( !qgsDoubleNear( mAngle + mLineAngle, 0.0 ) )
{
diff --git a/src/core/symbology-ng/qgsellipsesymbollayerv2.h b/src/core/symbology-ng/qgsellipsesymbollayerv2.h
index 090db06..811600a 100644
--- a/src/core/symbology-ng/qgsellipsesymbollayerv2.h
+++ b/src/core/symbology-ng/qgsellipsesymbollayerv2.h
@@ -40,7 +40,7 @@ class CORE_EXPORT QgsEllipseSymbolLayerV2: public QgsMarkerSymbolLayerV2
void toSld( QDomDocument& doc, QDomElement &element, const QgsStringMap& props ) const override;
void writeSldMarker( QDomDocument& doc, QDomElement &element, const QgsStringMap& props ) const override;
- bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, QPointF shift = QPointF( 0.0, 0.0 ) ) const override;
+ bool writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const override;
void setSymbolName( const QString& name ) { mSymbolName = name; }
QString symbolName() const { return mSymbolName; }
diff --git a/src/core/symbology-ng/qgsfillsymbollayerv2.cpp b/src/core/symbology-ng/qgsfillsymbollayerv2.cpp
index ba5229e..445e68a 100644
--- a/src/core/symbology-ng/qgsfillsymbollayerv2.cpp
+++ b/src/core/symbology-ng/qgsfillsymbollayerv2.cpp
@@ -422,6 +422,17 @@ QColor QgsSimpleFillSymbolLayerV2::dxfColor( QgsSymbolV2RenderContext &context )
return mBorderColor;
}
+double QgsSimpleFillSymbolLayerV2::dxfAngle( QgsSymbolV2RenderContext &context ) const
+{
+ double angle = mAngle;
+ if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE ) )
+ {
+ context.setOriginalValueVariable( mAngle );
+ angle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE, context, mAngle ).toDouble();
+ }
+ return angle;
+}
+
Qt::PenStyle QgsSimpleFillSymbolLayerV2::dxfPenStyle() const
{
return mBorderStyle;
diff --git a/src/core/symbology-ng/qgsfillsymbollayerv2.h b/src/core/symbology-ng/qgsfillsymbollayerv2.h
index d14dd30..d5409e4 100644
--- a/src/core/symbology-ng/qgsfillsymbollayerv2.h
+++ b/src/core/symbology-ng/qgsfillsymbollayerv2.h
@@ -118,6 +118,8 @@ class CORE_EXPORT QgsSimpleFillSymbolLayerV2 : public QgsFillSymbolLayerV2
double dxfWidth( const QgsDxfExport& e, QgsSymbolV2RenderContext& context ) const override;
QColor dxfColor( QgsSymbolV2RenderContext& context ) const override;
+ double dxfAngle( QgsSymbolV2RenderContext& context ) const override;
+
Qt::PenStyle dxfPenStyle() const override;
QColor dxfBrushColor( QgsSymbolV2RenderContext &context ) const override;
Qt::BrushStyle dxfBrushStyle() const override;
@@ -562,14 +564,15 @@ class CORE_EXPORT QgsImageFillSymbolLayer: public QgsFillSymbolLayerV2
void setOutputUnit( QgsSymbolV2::OutputUnit unit ) override;
QgsSymbolV2::OutputUnit outputUnit() const override;
- void setMapUnitScale( const QgsMapUnitScale& scale ) override;
+ void setMapUnitScale( const QgsMapUnitScale &scale ) override;
QgsMapUnitScale mapUnitScale() const override;
virtual double estimateMaxBleed() const override;
- virtual double dxfWidth( const QgsDxfExport& e, QgsSymbolV2RenderContext& context ) const override;
- virtual QColor dxfColor( QgsSymbolV2RenderContext& context ) const override;
- virtual Qt::PenStyle dxfPenStyle() const override;
+ double dxfWidth( const QgsDxfExport& e, QgsSymbolV2RenderContext& context ) const override;
+ QColor dxfColor( QgsSymbolV2RenderContext& context ) const override;
+
+ Qt::PenStyle dxfPenStyle() const override;
QSet<QString> usedAttributes() const override;
diff --git a/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp b/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp
index 82202b1..e401c15 100644
--- a/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp
+++ b/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp
@@ -29,7 +29,7 @@
#include <QDomDocument>
#include <QDomElement>
-QgsInvertedPolygonRenderer::QgsInvertedPolygonRenderer( const QgsFeatureRendererV2* subRenderer )
+QgsInvertedPolygonRenderer::QgsInvertedPolygonRenderer( QgsFeatureRendererV2* subRenderer )
: QgsFeatureRendererV2( "invertedPolygonRenderer" )
, mPreprocessingEnabled( false )
{
@@ -47,11 +47,11 @@ QgsInvertedPolygonRenderer::~QgsInvertedPolygonRenderer()
{
}
-void QgsInvertedPolygonRenderer::setEmbeddedRenderer( const QgsFeatureRendererV2* subRenderer )
+void QgsInvertedPolygonRenderer::setEmbeddedRenderer( QgsFeatureRendererV2* subRenderer )
{
if ( subRenderer )
{
- mSubRenderer.reset( const_cast<QgsFeatureRendererV2*>( subRenderer )->clone() );
+ mSubRenderer.reset( subRenderer );
}
else
{
@@ -64,6 +64,38 @@ const QgsFeatureRendererV2* QgsInvertedPolygonRenderer::embeddedRenderer() const
return mSubRenderer.data();
}
+void QgsInvertedPolygonRenderer::setLegendSymbolItem( const QString& key, QgsSymbolV2* symbol )
+{
+ if ( !mSubRenderer )
+ return;
+
+ mSubRenderer->setLegendSymbolItem( key, symbol );
+}
+
+bool QgsInvertedPolygonRenderer::legendSymbolItemsCheckable() const
+{
+ if ( !mSubRenderer )
+ return false;
+
+ return mSubRenderer->legendSymbolItemsCheckable();
+}
+
+bool QgsInvertedPolygonRenderer::legendSymbolItemChecked( const QString& key )
+{
+ if ( !mSubRenderer )
+ return false;
+
+ return mSubRenderer->legendSymbolItemChecked( key );
+}
+
+void QgsInvertedPolygonRenderer::checkLegendSymbolItem( const QString& key, bool state )
+{
+ if ( !mSubRenderer )
+ return;
+
+ return mSubRenderer->checkLegendSymbolItem( key, state );
+}
+
void QgsInvertedPolygonRenderer::startRender( QgsRenderContext& context, const QgsFields& fields )
{
if ( !mSubRenderer )
@@ -341,7 +373,7 @@ QgsInvertedPolygonRenderer* QgsInvertedPolygonRenderer::clone() const
}
else
{
- newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data() );
+ newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data()->clone() );
}
newRenderer->setPreprocessingEnabled( preprocessingEnabled() );
copyRendererData( newRenderer );
@@ -357,7 +389,6 @@ QgsFeatureRendererV2* QgsInvertedPolygonRenderer::create( QDomElement& element )
{
QgsFeatureRendererV2* renderer = QgsFeatureRendererV2::load( embeddedRendererElem );
r->setEmbeddedRenderer( renderer );
- delete renderer;
}
r->setPreprocessingEnabled( element.attribute( "preprocessing", "0" ).toInt() == 1 );
return r;
diff --git a/src/core/symbology-ng/qgsinvertedpolygonrenderer.h b/src/core/symbology-ng/qgsinvertedpolygonrenderer.h
index 8a64326..6f3f08b 100644
--- a/src/core/symbology-ng/qgsinvertedpolygonrenderer.h
+++ b/src/core/symbology-ng/qgsinvertedpolygonrenderer.h
@@ -42,9 +42,11 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
public:
/** Constructor
- * @param embeddedRenderer optional embeddedRenderer. If null, a default one will be assigned
+ * @param embeddedRenderer optional embeddedRenderer. If null, a default one will be assigned.
+ * Ownership will be transferred.
*/
- QgsInvertedPolygonRenderer( const QgsFeatureRendererV2* embeddedRenderer = nullptr );
+ QgsInvertedPolygonRenderer( QgsFeatureRendererV2* embeddedRenderer = nullptr );
+
virtual ~QgsInvertedPolygonRenderer();
/** Used to clone this feature renderer.*/
@@ -116,13 +118,14 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
*/
virtual QDomElement save( QDomDocument& doc ) override;
- /** Sets the embedded renderer
- * @param subRenderer the embedded renderer (will be cloned)
- */
- void setEmbeddedRenderer( const QgsFeatureRendererV2* subRenderer );
- /** @returns the current embedded renderer
- */
- const QgsFeatureRendererV2* embeddedRenderer() const;
+ void setEmbeddedRenderer( QgsFeatureRendererV2* subRenderer ) override;
+ const QgsFeatureRendererV2* embeddedRenderer() const override;
+
+ virtual void setLegendSymbolItem( const QString& key, QgsSymbolV2* symbol ) override;
+
+ virtual bool legendSymbolItemsCheckable() const override;
+ virtual bool legendSymbolItemChecked( const QString& key ) override;
+ virtual void checkLegendSymbolItem( const QString& key, bool state = true ) override;
/** @returns true if the geometries are to be preprocessed (merged with an union) before rendering.*/
bool preprocessingEnabled() const { return mPreprocessingEnabled; }
diff --git a/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp b/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp
index 4f849c4..ebf4183 100644
--- a/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp
+++ b/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp
@@ -830,16 +830,12 @@ void QgsSimpleMarkerSymbolLayerV2::drawMarker( QPainter* p, QgsSymbolV2RenderCon
}
}
-bool QgsSimpleMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext *context, const QgsFeature*, QPointF shift ) const
+bool QgsSimpleMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext &context, QPointF shift ) const
{
//data defined size?
double size = mSize;
- bool hasDataDefinedSize = false;
- if ( context )
- {
- hasDataDefinedSize = context->renderHints() & QgsSymbolV2::DataDefinedSizeScale || hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE );
- }
+ bool hasDataDefinedSize = context.renderHints() & QgsSymbolV2::DataDefinedSizeScale || hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE );
//data defined size
bool ok = true;
@@ -847,8 +843,8 @@ bool QgsSimpleMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitSc
{
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE ) )
{
- context->setOriginalValueVariable( mSize );
- size = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE, *context, mSize, &ok ).toDouble();
+ context.setOriginalValueVariable( mSize );
+ size = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE, context, mSize, &ok ).toDouble();
}
if ( ok )
@@ -863,7 +859,7 @@ bool QgsSimpleMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitSc
}
}
- size = QgsSymbolLayerV2Utils::convertToPainterUnits( context->renderContext(), size, mSizeUnit, mSizeMapUnitScale );
+ size = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), size, mSizeUnit, mSizeMapUnitScale );
}
if ( mSizeUnit == QgsSymbolV2::MM )
{
@@ -874,10 +870,10 @@ bool QgsSimpleMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitSc
//outlineWidth
double outlineWidth = mOutlineWidth;
- if ( context && hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH ) )
+ if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH ) )
{
- context->setOriginalValueVariable( mOutlineWidth );
- outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, *context, mOutlineWidth ).toDouble();
+ context.setOriginalValueVariable( mOutlineWidth );
+ outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, context, mOutlineWidth ).toDouble();
}
if ( mSizeUnit == QgsSymbolV2::MM )
{
@@ -887,17 +883,17 @@ bool QgsSimpleMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitSc
//color
QColor pc = mPen.color();
QColor bc = mBrush.color();
- if ( context && hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR ) )
+ if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR ) )
{
- context->setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mColor ) );
- QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mColor ) );
+ QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR, context, QVariant(), &ok ).toString();
if ( ok )
bc = QgsSymbolLayerV2Utils::decodeColor( colorString );
}
- if ( context && hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR_BORDER ) )
+ if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR_BORDER ) )
{
- context->setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mBorderColor ) );
- QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR_BORDER, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mBorderColor ) );
+ QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_COLOR_BORDER, context, QVariant(), &ok ).toString();
if ( ok )
pc = QgsSymbolLayerV2Utils::decodeColor( colorString );
}
@@ -905,25 +901,23 @@ bool QgsSimpleMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitSc
//offset
double offsetX = 0;
double offsetY = 0;
- if ( context )
- {
- markerOffset( *context, offsetX, offsetY );
- }
+ markerOffset( context, offsetX, offsetY );
+
QPointF off( offsetX, offsetY );
//angle
double angle = mAngle + mLineAngle;
- if ( context && hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE ) )
+ if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE ) )
{
- context->setOriginalValueVariable( mAngle );
- angle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE, *context, mAngle ).toDouble() + mLineAngle;
+ context.setOriginalValueVariable( mAngle );
+ angle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE, context, mAngle ).toDouble() + mLineAngle;
}
QString name( mName );
- if ( context && hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_NAME ) )
+ if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_NAME ) )
{
- context->setOriginalValueVariable( mName );
- name = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_NAME, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( mName );
+ name = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_NAME, context, QVariant(), &ok ).toString();
}
angle = -angle; //rotation in Qt is counterclockwise
@@ -1443,6 +1437,13 @@ void QgsSvgMarkerSymbolLayerV2::renderPoint( QPointF point, QgsSymbolV2RenderCon
}
p->restore();
+
+ if ( context.renderContext().flags() & QgsRenderContext::Antialiasing )
+ {
+ // workaround issue with nested QPictures forgetting antialiasing flag - see http://hub.qgis.org/issues/14960
+ p->setRenderHint( QPainter::Antialiasing );
+ }
+
}
double QgsSvgMarkerSymbolLayerV2::calculateSize( QgsSymbolV2RenderContext& context, bool& hasDataDefinedSize ) const
@@ -1654,8 +1655,7 @@ QgsSymbolLayerV2* QgsSvgMarkerSymbolLayerV2::createFromSld( QDomElement &element
return m;
}
-bool QgsSvgMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext *context, const QgsFeature*,
- QPointF shift ) const
+bool QgsSvgMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext &context, QPointF shift ) const
{
Q_UNUSED( layerName );
Q_UNUSED( shift ); //todo...
@@ -1663,13 +1663,13 @@ bool QgsSvgMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScale
//size
double size = mSize;
- bool hasDataDefinedSize = context->renderHints() & QgsSymbolV2::DataDefinedSizeScale || hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE );
+ bool hasDataDefinedSize = context.renderHints() & QgsSymbolV2::DataDefinedSizeScale || hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE );
bool ok = true;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE ) )
{
- context->setOriginalValueVariable( mSize );
- size = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE, *context, mSize, &ok ).toDouble();
+ context.setOriginalValueVariable( mSize );
+ size = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_SIZE, context, mSize, &ok ).toDouble();
}
if ( hasDataDefinedSize && ok )
@@ -1696,8 +1696,8 @@ bool QgsSvgMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScale
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OFFSET ) )
{
- context->setOriginalValueVariable( QgsSymbolLayerV2Utils::encodePoint( mOffset ) );
- QString offsetString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OFFSET, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodePoint( mOffset ) );
+ QString offsetString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OFFSET, context, QVariant(), &ok ).toString();
if ( ok )
offset = QgsSymbolLayerV2Utils::decodePoint( offsetString );
}
@@ -1714,8 +1714,8 @@ bool QgsSvgMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScale
double angle = mAngle + mLineAngle;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE ) )
{
- context->setOriginalValueVariable( mAngle );
- angle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE, *context, mAngle ).toDouble() + mLineAngle;
+ context.setOriginalValueVariable( mAngle );
+ angle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ANGLE, context, mAngle ).toDouble() + mLineAngle;
}
//angle = -angle; //rotation in Qt is counterclockwise
if ( angle )
@@ -1724,23 +1724,23 @@ bool QgsSvgMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScale
QString path = mPath;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_NAME ) )
{
- context->setOriginalValueVariable( mPath );
- path = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_NAME, *context, mPath ).toString();
+ context.setOriginalValueVariable( mPath );
+ path = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_NAME, context, mPath ).toString();
}
double outlineWidth = mOutlineWidth;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH ) )
{
- context->setOriginalValueVariable( mOutlineWidth );
- outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, *context, mOutlineWidth ).toDouble();
+ context.setOriginalValueVariable( mOutlineWidth );
+ outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, context, mOutlineWidth ).toDouble();
}
- outlineWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context->renderContext(), outlineWidth, mOutlineWidthUnit, mOutlineWidthMapUnitScale );
+ outlineWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), outlineWidth, mOutlineWidthUnit, mOutlineWidthMapUnitScale );
QColor fillColor = mColor;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_FILL ) )
{
- context->setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mColor ) );
- QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_FILL, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mColor ) );
+ QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_FILL, context, QVariant(), &ok ).toString();
if ( ok )
fillColor = QgsSymbolLayerV2Utils::decodeColor( colorString );
}
@@ -1748,15 +1748,15 @@ bool QgsSvgMarkerSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScale
QColor outlineColor = mOutlineColor;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE ) )
{
- context->setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mOutlineColor ) );
- QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE, *context, QVariant(), &ok ).toString();
+ context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodeColor( mOutlineColor ) );
+ QString colorString = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE, context, QVariant(), &ok ).toString();
if ( ok )
outlineColor = QgsSymbolLayerV2Utils::decodeColor( colorString );
}
const QByteArray &svgContent = QgsSvgCache::instance()->svgContent( path, size, fillColor, outlineColor, outlineWidth,
- context->renderContext().scaleFactor(),
- context->renderContext().rasterScaleFactor() );
+ context.renderContext().scaleFactor(),
+ context.renderContext().rasterScaleFactor() );
//if current entry image is 0: cache image for entry
// checks to see if image will fit into cache
diff --git a/src/core/symbology-ng/qgsmarkersymbollayerv2.h b/src/core/symbology-ng/qgsmarkersymbollayerv2.h
index 76780a8..d82bd79 100644
--- a/src/core/symbology-ng/qgsmarkersymbollayerv2.h
+++ b/src/core/symbology-ng/qgsmarkersymbollayerv2.h
@@ -96,7 +96,7 @@ class CORE_EXPORT QgsSimpleMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2
void setOutlineWidthMapUnitScale( const QgsMapUnitScale& scale ) { mOutlineWidthMapUnitScale = scale; }
const QgsMapUnitScale& outlineWidthMapUnitScale() const { return mOutlineWidthMapUnitScale; }
- bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, QPointF shift = QPointF( 0.0, 0.0 ) ) const override;
+ bool writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const override;
void setOutputUnit( QgsSymbolV2::OutputUnit unit ) override;
QgsSymbolV2::OutputUnit outputUnit() const override;
@@ -201,7 +201,7 @@ class CORE_EXPORT QgsSvgMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2
void setMapUnitScale( const QgsMapUnitScale& scale ) override;
QgsMapUnitScale mapUnitScale() const override;
- bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, QPointF shift = QPointF( 0.0, 0.0 ) ) const override;
+ bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const override;
QRectF bounds( QPointF point, QgsSymbolV2RenderContext& context ) override;
diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp
index df6c519..093f7de 100644
--- a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp
+++ b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp
@@ -156,13 +156,15 @@ void QgsPointDisplacementRenderer::drawGroup( const DisplacementGroup& group, Qg
//get list of labels and symbols
QStringList labelAttributeList;
- QList<QgsMarkerSymbolV2*> symbolList;
+ QList< QgsMarkerSymbolV2* > symbolList;
+ QgsFeatureList featureList;
QgsMultiPointV2* groupMultiPoint = new QgsMultiPointV2();
for ( DisplacementGroup::const_iterator attIt = group.constBegin(); attIt != group.constEnd(); ++attIt )
{
labelAttributeList << ( mDrawLabels ? getLabel( attIt.value().first ) : QString() );
symbolList << dynamic_cast<QgsMarkerSymbolV2*>( attIt.value().second );
+ featureList << attIt.value().first;
groupMultiPoint->addGeometry( attIt.value().first.constGeometry()->geometry()->clone() );
}
@@ -181,7 +183,7 @@ void QgsPointDisplacementRenderer::drawGroup( const DisplacementGroup& group, Qg
{
diagonal = qMax( diagonal, QgsSymbolLayerV2Utils::convertToPainterUnits( context,
M_SQRT2 * symbol->size(),
- symbol->outputUnit(), symbol->mapUnitScale() ) );
+ symbol->sizeUnit(), symbol->sizeMapUnitScale() ) );
}
}
@@ -210,7 +212,7 @@ void QgsPointDisplacementRenderer::drawGroup( const DisplacementGroup& group, Qg
}
//draw symbols on the circle
- drawSymbols( feature, context, symbolList, symbolPositions, selected );
+ drawSymbols( featureList, context, symbolList, symbolPositions, selected );
//and also the labels
drawLabels( pt, symbolContext, labelPositions, labelAttributeList );
}
@@ -221,6 +223,43 @@ void QgsPointDisplacementRenderer::setEmbeddedRenderer( QgsFeatureRendererV2* r
mRenderer = r;
}
+const QgsFeatureRendererV2* QgsPointDisplacementRenderer::embeddedRenderer() const
+{
+ return mRenderer;
+}
+
+void QgsPointDisplacementRenderer::setLegendSymbolItem( const QString& key, QgsSymbolV2* symbol )
+{
+ if ( !mRenderer )
+ return;
+
+ mRenderer->setLegendSymbolItem( key, symbol );
+}
+
+bool QgsPointDisplacementRenderer::legendSymbolItemsCheckable() const
+{
+ if ( !mRenderer )
+ return false;
+
+ return mRenderer->legendSymbolItemsCheckable();
+}
+
+bool QgsPointDisplacementRenderer::legendSymbolItemChecked( const QString& key )
+{
+ if ( !mRenderer )
+ return false;
+
+ return mRenderer->legendSymbolItemChecked( key );
+}
+
+void QgsPointDisplacementRenderer::checkLegendSymbolItem( const QString& key, bool state )
+{
+ if ( !mRenderer )
+ return;
+
+ return mRenderer->checkLegendSymbolItem( key, state );
+}
+
QList<QString> QgsPointDisplacementRenderer::usedAttributes()
{
QList<QString> attributeList;
@@ -534,7 +573,7 @@ void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( QgsSymbolV2
{
double centerDiagonal = QgsSymbolLayerV2Utils::convertToPainterUnits( symbolContext.renderContext(),
M_SQRT2 * mCenterSymbol->size(),
- mCenterSymbol->outputUnit(), mCenterSymbol->mapUnitScale() );
+ mCenterSymbol->sizeUnit(), mCenterSymbol->sizeMapUnitScale() );
int pointsRemaining = nPosition;
int ringNumber = 1;
@@ -582,15 +621,21 @@ void QgsPointDisplacementRenderer::drawCircle( double radiusPainterUnits, QgsSym
p->drawArc( QRectF( centerPoint.x() - radiusPainterUnits, centerPoint.y() - radiusPainterUnits, 2 * radiusPainterUnits, 2 * radiusPainterUnits ), 0, 5760 );
}
-void QgsPointDisplacementRenderer::drawSymbols( const QgsFeature& f, QgsRenderContext& context, const QList<QgsMarkerSymbolV2*>& symbolList, const QList<QPointF>& symbolPositions, bool selected )
+void QgsPointDisplacementRenderer::drawSymbols( const QgsFeatureList& features, QgsRenderContext& context,
+ const QList< QgsMarkerSymbolV2* >& symbolList, const QList<QPointF>& symbolPositions, bool selected )
{
QList<QPointF>::const_iterator symbolPosIt = symbolPositions.constBegin();
QList<QgsMarkerSymbolV2*>::const_iterator symbolIt = symbolList.constBegin();
- for ( ; symbolPosIt != symbolPositions.constEnd() && symbolIt != symbolList.constEnd(); ++symbolPosIt, ++symbolIt )
+ QgsFeatureList::const_iterator featIt = features.constBegin();
+ for ( ; symbolPosIt != symbolPositions.constEnd() && symbolIt != symbolList.constEnd() && featIt != features.constEnd();
+ ++symbolPosIt, ++symbolIt, ++featIt )
{
if ( *symbolIt )
{
- ( *symbolIt )->renderPoint( *symbolPosIt, &f, context, -1, selected );
+ context.expressionContext().setFeature( *featIt );
+ ( *symbolIt )->startRender( context );
+ ( *symbolIt )->renderPoint( *symbolPosIt, &( *featIt ), context, -1, selected );
+ ( *symbolIt )->stopRender( context );
}
}
}
diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.h b/src/core/symbology-ng/qgspointdisplacementrenderer.h
index 9d00362..e079ede 100644
--- a/src/core/symbology-ng/qgspointdisplacementrenderer.h
+++ b/src/core/symbology-ng/qgspointdisplacementrenderer.h
@@ -95,9 +95,14 @@ class CORE_EXPORT QgsPointDisplacementRenderer: public QgsFeatureRendererV2
void setLabelAttributeName( const QString& name ) { mLabelAttributeName = name; }
QString labelAttributeName() const { return mLabelAttributeName; }
- /** Sets embedded renderer (takes ownership)*/
- void setEmbeddedRenderer( QgsFeatureRendererV2* r );
- QgsFeatureRendererV2* embeddedRenderer() const { return mRenderer;}
+ void setEmbeddedRenderer( QgsFeatureRendererV2* r ) override;
+ const QgsFeatureRendererV2* embeddedRenderer() const override;
+
+ virtual void setLegendSymbolItem( const QString& key, QgsSymbolV2* symbol ) override;
+
+ virtual bool legendSymbolItemsCheckable() const override;
+ virtual bool legendSymbolItemChecked( const QString& key ) override;
+ virtual void checkLegendSymbolItem( const QString& key, bool state = true ) override;
//! not available in python bindings
//! @deprecated since 2.4
@@ -230,7 +235,7 @@ class CORE_EXPORT QgsPointDisplacementRenderer: public QgsFeatureRendererV2
QMap<QgsFeatureId, int> mGroupIndex;
/** Spatial index for fast lookup of close points*/
QgsSpatialIndex* mSpatialIndex;
- /** Keeps trask which features are selected */
+ /** Keeps track which features are selected */
QSet<QgsFeatureId> mSelectedFeatures;
/** Creates a search rectangle with specified distance tolerance */
@@ -249,7 +254,7 @@ class CORE_EXPORT QgsPointDisplacementRenderer: public QgsFeatureRendererV2
void calculateSymbolAndLabelPositions( QgsSymbolV2RenderContext &symbolContext, QPointF centerPoint, int nPosition, double symbolDiagonal, QList<QPointF>& symbolPositions, QList<QPointF>& labelShifts , double &circleRadius ) const;
void drawGroup( const DisplacementGroup& group, QgsRenderContext& context );
void drawCircle( double radiusPainterUnits, QgsSymbolV2RenderContext& context, QPointF centerPoint, int nSymbols );
- void drawSymbols( const QgsFeature& f, QgsRenderContext& context, const QList<QgsMarkerSymbolV2*>& symbolList, const QList<QPointF>& symbolPositions, bool selected = false );
+ void drawSymbols( const QgsFeatureList& features, QgsRenderContext& context, const QList< QgsMarkerSymbolV2* >& symbolList, const QList<QPointF>& symbolPositions, bool selected = false );
void drawLabels( QPointF centerPoint, QgsSymbolV2RenderContext& context, const QList<QPointF>& labelShifts, const QStringList& labelList );
/** Returns first symbol for feature or 0 if none*/
QgsSymbolV2* firstSymbolForFeature( QgsFeatureRendererV2* r, QgsFeature& f, QgsRenderContext& context );
diff --git a/src/core/symbology-ng/qgsrendererv2.h b/src/core/symbology-ng/qgsrendererv2.h
index 312cc5d..58716d3 100644
--- a/src/core/symbology-ng/qgsrendererv2.h
+++ b/src/core/symbology-ng/qgsrendererv2.h
@@ -405,6 +405,21 @@ class CORE_EXPORT QgsFeatureRendererV2
*/
void setOrderByEnabled( bool enabled );
+ /** Sets an embedded renderer (subrenderer) for this feature renderer. The base class implementation
+ * does nothing with subrenderers, but individual derived classes can use these to modify their behaviour.
+ * @param subRenderer the embedded renderer. Ownership will be transferred.
+ * @see embeddedRenderer()
+ * @note added in QGIS 2.16
+ */
+ virtual void setEmbeddedRenderer( QgsFeatureRendererV2* subRenderer ) { delete subRenderer; }
+
+ /** Returns the current embedded renderer (subrenderer) for this feature renderer. The base class
+ * implementation does not use subrenderers and will always return null.
+ * @see setEmbeddedRenderer()
+ * @note added in QGIS 2.16
+ */
+ virtual const QgsFeatureRendererV2* embeddedRenderer() const { return nullptr; }
+
protected:
QgsFeatureRendererV2( const QString& type );
diff --git a/src/core/symbology-ng/qgssvgcache.cpp b/src/core/symbology-ng/qgssvgcache.cpp
index 56fb4ef..ebab07b 100644
--- a/src/core/symbology-ng/qgssvgcache.cpp
+++ b/src/core/symbology-ng/qgssvgcache.cpp
@@ -328,7 +328,13 @@ void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry* entry )
entry->viewboxSize = viewboxSize;
replaceElemParams( docElem, entry->fill, entry->outline, entry->outlineWidth * sizeScaleFactor );
- entry->svgContent = svgDoc.toByteArray();
+ entry->svgContent = svgDoc.toByteArray( 0 );
+
+ // toByteArray screws up tspans inside text by adding new lines before and after each span... this should help, at the
+ // risk of potentially breaking some svgs where the newline is desired
+ entry->svgContent.replace( "\n<tspan", "<tspan" );
+ entry->svgContent.replace( "</tspan>\n", "</tspan>" );
+
mTotalSize += entry->svgContent.size();
}
@@ -342,16 +348,23 @@ double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry* entry, const QDomElem
//find svg viewbox attribute
//first check if docElem is svg element
- if ( docElem.tagName() == "svg" )
+ if ( docElem.tagName() == "svg" && docElem.hasAttribute( "viewBox" ) )
{
viewBox = docElem.attribute( "viewBox", QString() );
}
+ else if ( docElem.tagName() == "svg" && docElem.hasAttribute( "viewbox" ) )
+ {
+ viewBox = docElem.attribute( "viewbox", QString() );
+ }
else
{
QDomElement svgElem = docElem.firstChildElement( "svg" ) ;
if ( !svgElem.isNull() )
{
- viewBox = svgElem.attribute( "viewBox", QString() );
+ if ( svgElem.hasAttribute( "viewBox" ) )
+ viewBox = svgElem.attribute( "viewBox", QString() );
+ else if ( svgElem.hasAttribute( "viewbox" ) )
+ viewBox = svgElem.attribute( "viewbox", QString() );
}
}
diff --git a/src/core/symbology-ng/qgssymbollayerv2.cpp b/src/core/symbology-ng/qgssymbollayerv2.cpp
index 44b114a..fa18e74 100644
--- a/src/core/symbology-ng/qgssymbollayerv2.cpp
+++ b/src/core/symbology-ng/qgssymbollayerv2.cpp
@@ -256,18 +256,12 @@ QVariant QgsSymbolLayerV2::evaluateDataDefinedProperty( const QString& property,
return defaultVal;
}
-bool QgsSymbolLayerV2::writeDxf( QgsDxfExport& e,
- double mmMapUnitScaleFactor,
- const QString& layerName,
- QgsSymbolV2RenderContext *context,
- const QgsFeature* f,
- QPointF shift ) const
+bool QgsSymbolLayerV2::writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolV2RenderContext &context, QPointF shift ) const
{
Q_UNUSED( e );
Q_UNUSED( mmMapUnitScaleFactor );
Q_UNUSED( layerName );
Q_UNUSED( context );
- Q_UNUSED( f );
Q_UNUSED( shift );
return false;
}
@@ -292,6 +286,12 @@ QColor QgsSymbolLayerV2::dxfColor( QgsSymbolV2RenderContext &context ) const
return color();
}
+double QgsSymbolLayerV2::dxfAngle( QgsSymbolV2RenderContext &context ) const
+{
+ Q_UNUSED( context );
+ return 0.0;
+}
+
QVector<qreal> QgsSymbolLayerV2::dxfCustomDashPattern( QgsSymbolV2::OutputUnit& unit ) const
{
Q_UNUSED( unit );
diff --git a/src/core/symbology-ng/qgssymbollayerv2.h b/src/core/symbology-ng/qgssymbollayerv2.h
index e9166a9..be260d3 100644
--- a/src/core/symbology-ng/qgssymbollayerv2.h
+++ b/src/core/symbology-ng/qgssymbollayerv2.h
@@ -235,17 +235,13 @@ class CORE_EXPORT QgsSymbolLayerV2
*/
virtual QVariant evaluateDataDefinedProperty( const QString& property, const QgsSymbolV2RenderContext& context, const QVariant& defaultVal = QVariant(), bool *ok = nullptr ) const;
- virtual bool writeDxf( QgsDxfExport& e,
- double mmMapUnitScaleFactor,
- const QString& layerName,
- QgsSymbolV2RenderContext* context,
- const QgsFeature* f,
- QPointF shift = QPointF( 0.0, 0.0 ) ) const;
+ virtual bool writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolV2RenderContext &context, QPointF shift = QPointF( 0.0, 0.0 ) ) const;
virtual double dxfWidth( const QgsDxfExport& e, QgsSymbolV2RenderContext& context ) const;
virtual double dxfOffset( const QgsDxfExport& e, QgsSymbolV2RenderContext& context ) const;
virtual QColor dxfColor( QgsSymbolV2RenderContext& context ) const;
+ virtual double dxfAngle( QgsSymbolV2RenderContext& context ) const;
virtual QVector<qreal> dxfCustomDashPattern( QgsSymbolV2::OutputUnit& unit ) const;
virtual Qt::PenStyle dxfPenStyle() const;
diff --git a/src/core/symbology-ng/qgssymbollayerv2registry.cpp b/src/core/symbology-ng/qgssymbollayerv2registry.cpp
index 197b89b..9329ff6 100644
--- a/src/core/symbology-ng/qgssymbollayerv2registry.cpp
+++ b/src/core/symbology-ng/qgssymbollayerv2registry.cpp
@@ -38,7 +38,7 @@ QgsSymbolLayerV2Registry::QgsSymbolLayerV2Registry()
QgsFontMarkerSymbolLayerV2::create, QgsFontMarkerSymbolLayerV2::createFromSld ) );
addSymbolLayerType( new QgsSymbolLayerV2Metadata( "EllipseMarker", QObject::tr( "Ellipse marker" ), QgsSymbolV2::Marker,
QgsEllipseSymbolLayerV2::create, QgsEllipseSymbolLayerV2::createFromSld ) );
- addSymbolLayerType( new QgsSymbolLayerV2Metadata( "VectorField", QObject::tr( "Vector Field marker" ), QgsSymbolV2::Marker,
+ addSymbolLayerType( new QgsSymbolLayerV2Metadata( "VectorField", QObject::tr( "Vector field marker" ), QgsSymbolV2::Marker,
QgsVectorFieldSymbolLayer::create ) );
addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SimpleFill", QObject::tr( "Simple fill" ), QgsSymbolV2::Fill,
diff --git a/src/core/symbology-ng/qgssymbolv2.cpp b/src/core/symbology-ng/qgssymbolv2.cpp
index 1154f12..534fa43 100644
--- a/src/core/symbology-ng/qgssymbolv2.cpp
+++ b/src/core/symbology-ng/qgssymbolv2.cpp
@@ -469,8 +469,11 @@ void QgsSymbolV2::startRender( QgsRenderContext& context, const QgsFields* field
void QgsSymbolV2::stopRender( QgsRenderContext& context )
{
Q_UNUSED( context )
- Q_FOREACH ( QgsSymbolLayerV2* layer, mLayers )
- layer->stopRender( *mSymbolRenderContext );
+ if ( mSymbolRenderContext )
+ {
+ Q_FOREACH ( QgsSymbolLayerV2* layer, mLayers )
+ layer->stopRender( *mSymbolRenderContext );
+ }
delete mSymbolRenderContext;
mSymbolRenderContext = nullptr;
diff --git a/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp b/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp
index c39a0d7..b2d7f16 100644
--- a/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp
+++ b/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp
@@ -283,7 +283,7 @@ void QgsVectorFieldSymbolLayer::drawPreviewIcon( QgsSymbolV2RenderContext& conte
QSet<QString> QgsVectorFieldSymbolLayer::usedAttributes() const
{
- QSet<QString> attributes;
+ QSet<QString> attributes = QgsMarkerSymbolLayerV2::usedAttributes();
if ( !mXAttribute.isEmpty() )
{
attributes.insert( mXAttribute );
@@ -292,6 +292,10 @@ QSet<QString> QgsVectorFieldSymbolLayer::usedAttributes() const
{
attributes.insert( mYAttribute );
}
+ if ( mLineSymbol )
+ {
+ attributes.unite( mLineSymbol->usedAttributes() );
+ }
return attributes;
}
@@ -319,4 +323,17 @@ void QgsVectorFieldSymbolLayer::convertPolarToCartesian( double length, double a
y = length * cos( angle );
}
+void QgsVectorFieldSymbolLayer::setColor( const QColor& color )
+{
+ if ( mLineSymbol )
+ mLineSymbol->setColor( color );
+
+ mColor = color;
+}
+
+QColor QgsVectorFieldSymbolLayer::color() const
+{
+ return mLineSymbol ? mLineSymbol->color() : mColor;
+}
+
diff --git a/src/core/symbology-ng/qgsvectorfieldsymbollayer.h b/src/core/symbology-ng/qgsvectorfieldsymbollayer.h
index cac4284..fe92f72 100644
--- a/src/core/symbology-ng/qgsvectorfieldsymbollayer.h
+++ b/src/core/symbology-ng/qgsvectorfieldsymbollayer.h
@@ -54,6 +54,9 @@ class CORE_EXPORT QgsVectorFieldSymbolLayer: public QgsMarkerSymbolLayerV2
bool setSubSymbol( QgsSymbolV2* symbol ) override;
QgsSymbolV2* subSymbol() override { return mLineSymbol; }
+ void setColor( const QColor& color ) override;
+ virtual QColor color() const override;
+
void renderPoint( QPointF point, QgsSymbolV2RenderContext& context ) override;
void startRender( QgsSymbolV2RenderContext& context ) override;
void stopRender( QgsSymbolV2RenderContext& context ) override;
diff --git a/src/customwidgets/qgsextentgroupboxplugin.cpp b/src/customwidgets/qgsextentgroupboxplugin.cpp
index e421bfa..639b83c 100644
--- a/src/customwidgets/qgsextentgroupboxplugin.cpp
+++ b/src/customwidgets/qgsextentgroupboxplugin.cpp
@@ -1,5 +1,5 @@
/***************************************************************************
- qgsextentroupboxplugin.cpp
+ qgsextentgroupboxplugin.cpp
--------------------------------------
Date : 28.07.2015
Copyright : (C) 2015 Denis Rouzaud
@@ -37,7 +37,7 @@ QString QgsExtentGroupBoxPlugin::group() const
QString QgsExtentGroupBoxPlugin::includeFile() const
{
- return "qgsextentroupbox.h";
+ return "qgsextentgroupbox.h";
}
QIcon QgsExtentGroupBoxPlugin::icon() const
diff --git a/src/customwidgets/qgsextentgroupboxplugin.h b/src/customwidgets/qgsextentgroupboxplugin.h
index 00cab41..f3167d9 100644
--- a/src/customwidgets/qgsextentgroupboxplugin.h
+++ b/src/customwidgets/qgsextentgroupboxplugin.h
@@ -1,5 +1,5 @@
/***************************************************************************
- qgsextentroupboxplugin.h
+ qgsextentgroupboxplugin.h
--------------------------------------
Date : 28.07.2015
Copyright : (C) 2015 Denis Rouzaud
diff --git a/src/gui/attributetable/qgsattributetablemodel.cpp b/src/gui/attributetable/qgsattributetablemodel.cpp
index 93c57b6..0fa131b 100644
--- a/src/gui/attributetable/qgsattributetablemodel.cpp
+++ b/src/gui/attributetable/qgsattributetablemodel.cpp
@@ -553,7 +553,7 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
if ( index.column() >= mFieldCount )
return role == Qt::DisplayRole ? rowId : QVariant();
- int fieldId = mAttributes[index.column()];
+ int fieldId = mAttributes.at( index.column() );
if ( role == FieldIndexRole )
return fieldId;
@@ -593,46 +593,61 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
val = mFeat.attribute( fieldId );
}
- if ( role == Qt::DisplayRole )
+ if ( role == SortRole )
{
- return mWidgetFactories[index.column()]->representValue( layer(), fieldId, mWidgetConfigs[index.column()], mAttributeWidgetCaches[index.column()], val );
+ return val;
}
- if ( role == Qt::BackgroundColorRole || role == Qt::TextColorRole || role == Qt::DecorationRole || role == Qt::FontRole )
+ switch ( role )
{
- mExpressionContext.setFeature( mFeat );
- QList<QgsConditionalStyle> styles;
- if ( mRowStylesMap.contains( index.row() ) )
- {
- styles = mRowStylesMap[index.row()];
- }
- else
+ case Qt::DisplayRole:
+ return mWidgetFactories.at( index.column() )->representValue( layer(), fieldId, mWidgetConfigs.at( index.column() ),
+ mAttributeWidgetCaches.at( index.column() ), val );
+
+ case Qt::EditRole:
+ return val;
+
+ case Qt::BackgroundColorRole:
+ case Qt::TextColorRole:
+ case Qt::DecorationRole:
+ case Qt::FontRole:
{
- styles = QgsConditionalStyle::matchingConditionalStyles( layer()->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
- mRowStylesMap.insert( index.row(), styles );
+ mExpressionContext.setFeature( mFeat );
+ QList<QgsConditionalStyle> styles;
+ if ( mRowStylesMap.contains( index.row() ) )
+ {
+ styles = mRowStylesMap[index.row()];
+ }
+ else
+ {
+ styles = QgsConditionalStyle::matchingConditionalStyles( layer()->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
+ mRowStylesMap.insert( index.row(), styles );
- }
+ }
- QgsConditionalStyle rowstyle = QgsConditionalStyle::compressStyles( styles );
- styles = layer()->conditionalStyles()->fieldStyles( field.name() );
- styles = QgsConditionalStyle::matchingConditionalStyles( styles , val, mExpressionContext );
- styles.insert( 0, rowstyle );
- QgsConditionalStyle style = QgsConditionalStyle::compressStyles( styles );
+ QgsConditionalStyle rowstyle = QgsConditionalStyle::compressStyles( styles );
+ styles = layer()->conditionalStyles()->fieldStyles( field.name() );
+ styles = QgsConditionalStyle::matchingConditionalStyles( styles , val, mExpressionContext );
+ styles.insert( 0, rowstyle );
+ QgsConditionalStyle style = QgsConditionalStyle::compressStyles( styles );
- if ( style.isValid() )
- {
- if ( role == Qt::BackgroundColorRole && style.validBackgroundColor() )
- return style.backgroundColor();
- if ( role == Qt::TextColorRole && style.validTextColor() )
- return style.textColor();
- if ( role == Qt::DecorationRole )
- return style.icon();
- if ( role == Qt::FontRole )
- return style.font();
- }
+ if ( style.isValid() )
+ {
+ if ( role == Qt::BackgroundColorRole && style.validBackgroundColor() )
+ return style.backgroundColor();
+ if ( role == Qt::TextColorRole && style.validTextColor() )
+ return style.textColor();
+ if ( role == Qt::DecorationRole )
+ return style.icon();
+ if ( role == Qt::FontRole )
+ return style.font();
+ }
+ return QVariant();
+ }
}
- return val;
+
+ return QVariant();
}
bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp
index af34dac..3ec347f 100644
--- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp
+++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp
@@ -79,6 +79,7 @@ QgsEditorWidgetRegistry::QgsEditorWidgetRegistry()
connect( QgsProject::instance(), SIGNAL( readMapLayer( QgsMapLayer*, const QDomElement& ) ), this, SLOT( readMapLayer( QgsMapLayer*, const QDomElement& ) ) );
// connect( QgsProject::instance(), SIGNAL( writeMapLayer( QgsMapLayer*, QDomElement&, QDomDocument& ) ), this, SLOT( writeMapLayer( QgsMapLayer*, QDomElement&, QDomDocument& ) ) );
+ connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QgsMapLayer* ) ), this, SLOT( mapLayerWillBeRemoved( QgsMapLayer* ) ) );
connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( mapLayerAdded( QgsMapLayer* ) ) );
}
@@ -318,6 +319,17 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement&
layerElem.appendChild( editTypesNode );
}
+void QgsEditorWidgetRegistry::mapLayerWillBeRemoved( QgsMapLayer* mapLayer )
+{
+ QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( mapLayer );
+
+ if ( vl )
+ {
+ disconnect( vl, SIGNAL( readCustomSymbology( const QDomElement&, QString& ) ), this, SLOT( readSymbology( const QDomElement&, QString& ) ) );
+ disconnect( vl, SIGNAL( writeCustomSymbology( QDomElement&, QDomDocument&, QString& ) ), this, SLOT( writeSymbology( QDomElement&, QDomDocument&, QString& ) ) );
+ }
+}
+
void QgsEditorWidgetRegistry::mapLayerAdded( QgsMapLayer* mapLayer )
{
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( mapLayer );
diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.h b/src/gui/editorwidgets/core/qgseditorwidgetregistry.h
index 687d01b..10cc6d3 100644
--- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.h
+++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.h
@@ -174,6 +174,13 @@ class GUI_EXPORT QgsEditorWidgetRegistry : public QObject
void mapLayerAdded( QgsMapLayer* mapLayer );
/**
+ * Will disconnect to appropriate signals from map layers to load and save style
+ *
+ * @param mapLayer The layer to disconnect
+ */
+ void mapLayerWillBeRemoved( QgsMapLayer* mapLayer );
+
+ /**
* Loads layer symbology for the layer that emitted the signal
*
* @param element The XML element containing the style information
diff --git a/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp b/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp
index 2aff246..a0f1af3 100644
--- a/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp
+++ b/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp
@@ -24,12 +24,11 @@ QgsColorWidgetWrapper::QgsColorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx,
QVariant QgsColorWidgetWrapper::value() const
{
- QVariant v;
-
+ QColor c;
if ( mColorButton )
- v = mColorButton->color();
+ c = mColorButton->color();
- return v;
+ return c.isValid() ? QVariant( c ) : QVariant( QVariant::Color );
}
QWidget* QgsColorWidgetWrapper::createWidget( QWidget* parent )
@@ -54,5 +53,5 @@ bool QgsColorWidgetWrapper::valid() const
void QgsColorWidgetWrapper::setValue( const QVariant& value )
{
if ( mColorButton )
- mColorButton->setColor( QColor( value.toString() ) );
+ mColorButton->setColor( !value.isNull() ? QColor( value.toString() ) : QColor() );
}
diff --git a/src/gui/editorwidgets/qgsdatetimeedit.cpp b/src/gui/editorwidgets/qgsdatetimeedit.cpp
index 5594051..beb1862 100644
--- a/src/gui/editorwidgets/qgsdatetimeedit.cpp
+++ b/src/gui/editorwidgets/qgsdatetimeedit.cpp
@@ -41,7 +41,7 @@ QgsDateTimeEdit::QgsDateTimeEdit( QWidget *parent )
mNullLabel->setStyleSheet( "position: absolute; border: none; font-style: italic; color: grey;" );
mNullLabel->hide();
- setStyleSheet( QString( "padding-right: %1px;" ).arg( mClearButton->sizeHint().width() + spinButtonWidth() + frameWidth() + 1 ) );
+ setStyleSheet( QString( ".QWidget, QLineEdit, QToolButton { padding-right: %1px; }" ).arg( mClearButton->sizeHint().width() + spinButtonWidth() + frameWidth() + 1 ) );
QSize msz = minimumSizeHint();
setMinimumSize( qMax( msz.width(), mClearButton->sizeHint().height() + frameWidth() * 2 + 2 ),
diff --git a/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp b/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp
index 9d74e6e..0ddc897 100644
--- a/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp
+++ b/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp
@@ -56,6 +56,18 @@ void QgsPhotoWidgetWrapper::selectFileName()
void QgsPhotoWidgetWrapper::loadPixmap( const QString& fileName )
{
+ if ( fileName.isEmpty() )
+ {
+#ifdef WITH_QTWEBKIT
+ if ( mWebView )
+ {
+ mWebView->setUrl( QString() );
+ }
+#endif
+ clearPicture();
+ return;
+ }
+
QString filePath = fileName;
if ( QUrl( fileName ).isRelative() )
@@ -98,6 +110,24 @@ void QgsPhotoWidgetWrapper::loadPixmap( const QString& fileName )
mPhotoLabel->setPixmap( pm );
}
}
+ else
+ {
+ clearPicture();
+ }
+}
+
+void QgsPhotoWidgetWrapper::clearPicture()
+{
+ if ( mPhotoLabel )
+ {
+ mPhotoLabel->clear();
+ mPhotoLabel->setMinimumSize( QSize( 0, 0 ) );
+
+ if ( mPhotoPixmapLabel )
+ mPhotoPixmapLabel->setPixmap( QPixmap() );
+ else
+ mPhotoLabel->setPixmap( QPixmap() );
+ }
}
QVariant QgsPhotoWidgetWrapper::value() const
@@ -128,6 +158,8 @@ QWidget* QgsPhotoWidgetWrapper::createWidget( QWidget* parent )
layout->addWidget( label, 0, 0, 1, 2 );
layout->addWidget( le, 1, 0 );
layout->addWidget( pb, 1, 1 );
+ layout->setMargin( 0 );
+ layout->setContentsMargins( 0, 0, 0, 0 );
container->setLayout( layout );
@@ -200,7 +232,12 @@ void QgsPhotoWidgetWrapper::setValue( const QVariant& value )
if ( mLineEdit )
{
if ( value.isNull() )
+ {
+ mLineEdit->blockSignals( true );
mLineEdit->setText( QSettings().value( "qgis/nullValue", "NULL" ).toString() );
+ mLineEdit->blockSignals( false );
+ clearPicture();
+ }
else
mLineEdit->setText( value.toString() );
}
diff --git a/src/gui/editorwidgets/qgsphotowidgetwrapper.h b/src/gui/editorwidgets/qgsphotowidgetwrapper.h
index cc4a1e8..6776a0f 100644
--- a/src/gui/editorwidgets/qgsphotowidgetwrapper.h
+++ b/src/gui/editorwidgets/qgsphotowidgetwrapper.h
@@ -77,6 +77,8 @@ class GUI_EXPORT QgsPhotoWidgetWrapper : public QgsEditorWidgetWrapper
QLineEdit* mLineEdit;
//! The button to open the file chooser dialog
QPushButton* mButton;
+
+ void clearPicture();
};
#endif // QGSPHOTOWIDGETWRAPPER_H
diff --git a/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp b/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp
index 89dc5ba..039cd85 100644
--- a/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp
+++ b/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp
@@ -70,6 +70,8 @@ QWidget* QgsWebViewWidgetWrapper::createWidget( QWidget* parent )
layout->addWidget( webView, 0, 0, 1, 2 );
layout->addWidget( le, 1, 0 );
layout->addWidget( pb, 1, 1 );
+ layout->setMargin( 0 );
+ layout->setContentsMargins( 0, 0, 0, 0 );
container->setLayout( layout );
diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp
index e51c9c9..afbd079 100644
--- a/src/gui/qgsattributeform.cpp
+++ b/src/gui/qgsattributeform.cpp
@@ -176,9 +176,14 @@ bool QgsAttributeForm::save()
{
QVariant dstVar = dst.at( eww->fieldIdx() );
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() ) )
+ // be careful- sometimes two null qvariants will be reported as not equal!! (eg different types)
+ bool changed = ( dstVar != srcVar && !dstVar.isNull() && !srcVar.isNull() )
+ || ( dstVar.isNull() != srcVar.isNull() );
+ if ( changed && srcVar.isValid()
+ && !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) )
{
dst[eww->fieldIdx()] = srcVar;
diff --git a/src/gui/qgscodeeditor.h b/src/gui/qgscodeeditor.h
index 9117e15..f957c53 100644
--- a/src/gui/qgscodeeditor.h
+++ b/src/gui/qgscodeeditor.h
@@ -39,8 +39,8 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla
*
* @param parent The parent QWidget
* @param title The title to show in the code editor dialog
- * @param folding False: Enable margin for code editor
- * @param margin False: Enable folding for code editor
+ * @param folding false: Enable folding for code editor
+ * @param margin false: Enable margin for code editor
* @note added in 2.6
*/
QgsCodeEditor( QWidget *parent = nullptr, const QString& title = "", bool folding = false, bool margin = false );
diff --git a/src/gui/qgscollapsiblegroupbox.cpp b/src/gui/qgscollapsiblegroupbox.cpp
index 90ea75c..28bce83 100644
--- a/src/gui/qgscollapsiblegroupbox.cpp
+++ b/src/gui/qgscollapsiblegroupbox.cpp
@@ -408,6 +408,7 @@ void QgsCollapsibleGroupBoxBasic::updateStyle()
void QgsCollapsibleGroupBoxBasic::setCollapsed( bool collapse )
{
+ bool changed = collapse != mCollapsed;
mCollapsed = collapse;
if ( !isVisible() )
@@ -444,7 +445,8 @@ void QgsCollapsibleGroupBoxBasic::setCollapsed( bool collapse )
mParentScrollArea->setUpdatesEnabled( true );
}
// emit signal for connections using collapsed state
- emit collapsedStateChanged( isCollapsed() );
+ if ( changed )
+ emit collapsedStateChanged( isCollapsed() );
}
void QgsCollapsibleGroupBoxBasic::collapseExpandFixes()
diff --git a/src/gui/qgscolorbuttonv2.cpp b/src/gui/qgscolorbuttonv2.cpp
index 63d3568..c08fd24 100644
--- a/src/gui/qgscolorbuttonv2.cpp
+++ b/src/gui/qgscolorbuttonv2.cpp
@@ -159,7 +159,9 @@ void QgsColorButtonV2::setToNoColor()
{
if ( mAllowAlpha )
{
- setColor( QColor( 0, 0, 0, 0 ) );
+ QColor noColor = QColor( mColor );
+ noColor.setAlpha( 0 );
+ setColor( noColor );
}
}
diff --git a/src/gui/qgscomposerview.cpp b/src/gui/qgscomposerview.cpp
index 679919d..8787aef 100644
--- a/src/gui/qgscomposerview.cpp
+++ b/src/gui/qgscomposerview.cpp
@@ -47,6 +47,9 @@
#include "qgscursors.h"
#include "qgscomposerutils.h"
+#define MIN_VIEW_SCALE 0.05
+#define MAX_VIEW_SCALE 1000.0
+
QgsComposerView::QgsComposerView( QWidget* parent, const char* name, const Qt::WindowFlags& f )
: QGraphicsView( parent )
, mCurrentTool( Select )
@@ -1651,11 +1654,11 @@ void QgsComposerView::wheelZoom( QWheelEvent * event )
//zoom composition
if ( zoomIn )
{
- scale( zoomFactor, zoomFactor );
+ scaleSafe( zoomFactor );
}
else
{
- scale( 1 / zoomFactor, 1 / zoomFactor );
+ scaleSafe( 1 / zoomFactor );
}
//update composition for new zoom
@@ -1683,7 +1686,7 @@ void QgsComposerView::setZoomLevel( double zoomLevel )
dpi = 72;
//desired pixel width for 1mm on screen
- double scale = zoomLevel * dpi / 25.4;
+ double scale = qBound( MIN_VIEW_SCALE, zoomLevel * dpi / 25.4, MAX_VIEW_SCALE );
setTransform( QTransform::fromScale( scale, scale ) );
updateRulers();
@@ -1691,6 +1694,14 @@ void QgsComposerView::setZoomLevel( double zoomLevel )
emit zoomLevelChanged();
}
+void QgsComposerView::scaleSafe( double scale )
+{
+ double currentScale = transform().m11();
+ scale *= currentScale;
+ scale = qBound( MIN_VIEW_SCALE, scale, MAX_VIEW_SCALE );
+ setTransform( QTransform::fromScale( scale, scale ) );
+}
+
void QgsComposerView::setPreviewModeEnabled( bool enabled )
{
if ( !mPreviewEffect )
diff --git a/src/gui/qgscomposerview.h b/src/gui/qgscomposerview.h
index eafa1c9..d33a3be 100644
--- a/src/gui/qgscomposerview.h
+++ b/src/gui/qgscomposerview.h
@@ -143,6 +143,13 @@ class GUI_EXPORT QgsComposerView: public QGraphicsView
/** Set zoom level, where a zoom level of 1.0 corresponds to 100%*/
void setZoomLevel( double zoomLevel );
+ /** Scales the view in a safe way, by limiting the acceptable range
+ * of the scale applied.
+ * @param scale factor to scale view by
+ * @note added in QGIS 2.16
+ */
+ void scaleSafe( double scale );
+
/** Sets whether a preview effect should be used to alter the view's appearance
* @param enabled Set to true to enable the preview effect on the view
* @note added in 2.3
diff --git a/src/gui/qgsexternalresourcewidget.cpp b/src/gui/qgsexternalresourcewidget.cpp
index d061108..5605410 100644
--- a/src/gui/qgsexternalresourcewidget.cpp
+++ b/src/gui/qgsexternalresourcewidget.cpp
@@ -142,7 +142,11 @@ void QgsExternalResourceWidget::updateDocumentViewer()
{
const QPixmap* pm = mPixmapLabel->pixmap();
- if ( pm )
+ if ( !pm || pm->isNull() )
+ {
+ mPixmapLabel->setMinimumSize( QSize( 0, 0 ) );
+ }
+ else
{
QSize size( mDocumentViewerWidth, mDocumentViewerHeight );
if ( size.width() == 0 && size.height() > 0 )
@@ -165,7 +169,7 @@ void QgsExternalResourceWidget::updateDocumentViewer()
void QgsExternalResourceWidget::loadDocument( const QString& path )
{
- if ( path.isNull() )
+ if ( path.isEmpty() )
{
#ifdef WITH_QTWEBKIT
if ( mDocumentViewerContent == Web )
@@ -176,6 +180,7 @@ void QgsExternalResourceWidget::loadDocument( const QString& path )
if ( mDocumentViewerContent == Image )
{
mPixmapLabel->clear();
+ updateDocumentViewer();
}
}
@@ -190,12 +195,8 @@ void QgsExternalResourceWidget::loadDocument( const QString& path )
if ( mDocumentViewerContent == Image )
{
QPixmap pm( path );
- if ( !pm.isNull() )
- {
- mPixmapLabel->setPixmap( pm );
-
- updateDocumentViewer();
- }
+ mPixmapLabel->setPixmap( pm );
+ updateDocumentViewer();
}
}
diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp
index babf0c4..856d16b 100644
--- a/src/gui/qgsmapcanvas.cpp
+++ b/src/gui/qgsmapcanvas.cpp
@@ -862,7 +862,7 @@ QgsRectangle QgsMapCanvas::fullExtent() const
} // extent
-void QgsMapCanvas::setExtent( QgsRectangle const & r )
+void QgsMapCanvas::setExtent( const QgsRectangle& r )
{
QgsRectangle current = extent();
@@ -1134,8 +1134,14 @@ void QgsMapCanvas::panToSelected( QgsVectorLayer* layer )
return;
QgsRectangle rect = mapSettings().layerExtentToOutputExtent( layer, layer->boundingBoxOfSelected() );
- setExtent( QgsRectangle( rect.center(), rect.center() ) );
- refresh();
+ if ( !rect.isNull() )
+ {
+ setCenter( rect.center() );
+ }
+ else
+ {
+ emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "Geometry is NULL" ), QgsMessageBar::WARNING );
+ }
} // panToSelected
void QgsMapCanvas::keyPressEvent( QKeyEvent * e )
@@ -1192,6 +1198,7 @@ void QgsMapCanvas::keyPressEvent( QKeyEvent * e )
//mCanvasProperties->dragging = true;
if ( ! e->isAutoRepeat() )
{
+ QApplication::setOverrideCursor( Qt::ClosedHandCursor );
mCanvasProperties->panSelectorDown = true;
mCanvasProperties->rubberStartPoint = mCanvasProperties->mouseLastXY;
}
@@ -1245,7 +1252,7 @@ void QgsMapCanvas::keyReleaseEvent( QKeyEvent * e )
if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
{
QgsDebugMsg( "Releasing pan selector" );
-
+ QApplication::restoreOverrideCursor();
mCanvasProperties->panSelectorDown = false;
panActionEnd( mCanvasProperties->mouseLastXY );
}
diff --git a/src/gui/qgsmessagelogviewer.cpp b/src/gui/qgsmessagelogviewer.cpp
index 92a379d..bbd4c93 100644
--- a/src/gui/qgsmessagelogviewer.cpp
+++ b/src/gui/qgsmessagelogviewer.cpp
@@ -78,5 +78,6 @@ void QgsMessageLogViewer::logMessage( QString message, QString tag, QgsMessageLo
void QgsMessageLogViewer::closeTab( int index )
{
- tabWidget->removeTab( index );
+ if ( tabWidget->count() > 1 )
+ tabWidget->removeTab( index );
}
diff --git a/src/gui/qgspixmaplabel.cpp b/src/gui/qgspixmaplabel.cpp
index 7411a9e..c853e70 100644
--- a/src/gui/qgspixmaplabel.cpp
+++ b/src/gui/qgspixmaplabel.cpp
@@ -24,17 +24,31 @@ QgsPixmapLabel::QgsPixmapLabel( QWidget *parent ) :
void QgsPixmapLabel::setPixmap( const QPixmap & p )
{
+ bool sizeChanged = ( p.size() != mPixmap.size() );
mPixmap = p;
- QLabel::setPixmap( p );
+
+ if ( sizeChanged )
+ {
+ updateGeometry();
+ }
+
+ QLabel::setPixmap( mPixmap.scaled( this->size(),
+ Qt::KeepAspectRatio, Qt::SmoothTransformation ) );
}
int QgsPixmapLabel::heightForWidth( int width ) const
{
+ if ( mPixmap.isNull() )
+ return 0;
+
return (( qreal )mPixmap.height()*width ) / mPixmap.width();
}
QSize QgsPixmapLabel::sizeHint() const
{
+ if ( mPixmap.isNull() )
+ return QSize( 0, 0 );
+
int w = this->width();
return QSize( w, heightForWidth( w ) );
}
diff --git a/src/gui/qgsrasterlayersaveasdialog.cpp b/src/gui/qgsrasterlayersaveasdialog.cpp
index 779a232..e09c84c 100644
--- a/src/gui/qgsrasterlayersaveasdialog.cpp
+++ b/src/gui/qgsrasterlayersaveasdialog.cpp
@@ -163,13 +163,14 @@ void QgsRasterLayerSaveAsDialog::on_mBrowseButton_clicked()
if ( mTileModeCheckBox->isChecked() )
{
- while ( true )
+ Q_FOREVER
{
// TODO: would not it be better to select .vrt file instead of directory?
fileName = QFileDialog::getExistingDirectory( this, tr( "Select output directory" ), dirName );
//fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), QString(), tr( "VRT" ) + " (*.vrt *.VRT)" );
- if ( fileName.isEmpty() ) break; // canceled
+ if ( fileName.isEmpty() )
+ break; // canceled
// Check if directory is empty
QDir dir( fileName );
@@ -177,39 +178,30 @@ void QgsRasterLayerSaveAsDialog::on_mBrowseButton_clicked()
QStringList filters;
filters << QString( "%1.*" ).arg( baseName );
QStringList files = dir.entryList( filters );
- if ( !files.isEmpty() )
- {
- QMessageBox::StandardButton button = QMessageBox::warning( this, tr( "Warning" ),
- tr( "The directory %1 contains files which will be overwritten: %2" ).arg( dir.absolutePath(), files.join( ", " ) ),
- QMessageBox::Ok | QMessageBox::Cancel );
+ if ( files.isEmpty() )
+ break;
- if ( button == QMessageBox::Ok )
- {
- break;
- }
- else
- {
- fileName = "";
- }
- }
- else
- {
+ if ( QMessageBox::warning( this, tr( "Warning" ),
+ tr( "The directory %1 contains files which will be overwritten: %2" ).arg( dir.absolutePath(), files.join( ", " ) ),
+ QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Ok )
break;
- }
+
+ fileName = "";
}
}
else
{
fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), dirName, tr( "GeoTIFF" ) + " (*.tif *.tiff *.TIF *.TIFF)" );
- }
- if ( !fileName.isEmpty() )
- {
- // ensure the user never ommited the extension from the file name
- if ( !fileName.endsWith( ".tif", Qt::CaseInsensitive ) && !fileName.endsWith( ".tiff", Qt::CaseInsensitive ) )
+ // ensure the user never omits the extension from the file name
+ if ( !fileName.isEmpty() && !fileName.endsWith( ".tif", Qt::CaseInsensitive ) && !fileName.endsWith( ".tiff", Qt::CaseInsensitive ) )
{
fileName += ".tif";
}
+ }
+
+ if ( !fileName.isEmpty() )
+ {
mSaveAsLineEdit->setText( fileName );
}
}
diff --git a/src/gui/qgsrelationeditorwidget.cpp b/src/gui/qgsrelationeditorwidget.cpp
index 9472399..06adc31 100644
--- a/src/gui/qgsrelationeditorwidget.cpp
+++ b/src/gui/qgsrelationeditorwidget.cpp
@@ -47,35 +47,41 @@ QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget* parent )
mToggleEditingButton->setText( tr( "Toggle editing" ) );
mToggleEditingButton->setEnabled( false );
mToggleEditingButton->setCheckable( true );
+ mToggleEditingButton->setToolTip( tr( "Toggle editing mode for child layer" ) );
buttonLayout->addWidget( mToggleEditingButton );
// save Edits
mSaveEditsButton = new QToolButton( this );
mSaveEditsButton->setIcon( QgsApplication::getThemeIcon( "/mActionSaveEdits.svg" ) );
mSaveEditsButton->setText( tr( "Save layer edits" ) );
+ mSaveEditsButton->setToolTip( tr( "Save child layer edits" ) );
mSaveEditsButton->setEnabled( true );
buttonLayout->addWidget( mSaveEditsButton );
// add feature
mAddFeatureButton = new QToolButton( this );
mAddFeatureButton->setIcon( QgsApplication::getThemeIcon( "/mActionAdd.svg" ) );
mAddFeatureButton->setText( tr( "Add feature" ) );
+ mAddFeatureButton->setToolTip( tr( "Add child feature" ) );
mAddFeatureButton->setObjectName( "mAddFeatureButton" );
buttonLayout->addWidget( mAddFeatureButton );
// delete feature
mDeleteFeatureButton = new QToolButton( this );
mDeleteFeatureButton->setIcon( QgsApplication::getThemeIcon( "/mActionRemove.svg" ) );
mDeleteFeatureButton->setText( tr( "Delete feature" ) );
+ mDeleteFeatureButton->setToolTip( tr( "Delete child feature" ) );
mDeleteFeatureButton->setObjectName( "mDeleteFeatureButton" );
buttonLayout->addWidget( mDeleteFeatureButton );
// link feature
mLinkFeatureButton = new QToolButton( this );
mLinkFeatureButton->setIcon( QgsApplication::getThemeIcon( "/mActionLink.svg" ) );
mLinkFeatureButton->setText( tr( "Link feature" ) );
+ mLinkFeatureButton->setToolTip( tr( "Link existing child features" ) );
mLinkFeatureButton->setObjectName( "mLinkFeatureButton" );
buttonLayout->addWidget( mLinkFeatureButton );
// unlink feature
mUnlinkFeatureButton = new QToolButton( this );
mUnlinkFeatureButton->setIcon( QgsApplication::getThemeIcon( "/mActionUnlink.svg" ) );
mUnlinkFeatureButton->setText( tr( "Unlink feature" ) );
+ mUnlinkFeatureButton->setToolTip( tr( "Unlink child feature" ) );
mUnlinkFeatureButton->setObjectName( "mUnlinkFeatureButton" );
buttonLayout->addWidget( mUnlinkFeatureButton );
// spacer
@@ -83,6 +89,7 @@ QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget* parent )
// form view
mFormViewButton = new QToolButton( this );
mFormViewButton->setText( tr( "Form view" ) );
+ mFormViewButton->setToolTip( tr( "Switch to form view" ) );
mFormViewButton->setIcon( QgsApplication::getThemeIcon( "/mActionPropertyItem.png" ) );
mFormViewButton->setCheckable( true );
mFormViewButton->setChecked( mViewMode == QgsDualView::AttributeEditor );
@@ -90,6 +97,7 @@ QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget* parent )
// table view
mTableViewButton = new QToolButton( this );
mTableViewButton->setText( tr( "Table view" ) );
+ mTableViewButton->setToolTip( tr( "Switch to table view" ) );
mTableViewButton->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTable.svg" ) );
mTableViewButton->setCheckable( true );
mTableViewButton->setChecked( mViewMode == QgsDualView::AttributeTable );
diff --git a/src/gui/qgsrubberband.cpp b/src/gui/qgsrubberband.cpp
index c480af1..5d7b1c7 100644
--- a/src/gui/qgsrubberband.cpp
+++ b/src/gui/qgsrubberband.cpp
@@ -188,6 +188,26 @@ void QgsRubberBand::addPoint( const QgsPoint & p, bool doUpdate /* = true */, in
}
}
+void QgsRubberBand::closePoints( bool doUpdate, int geometryIndex )
+{
+ if ( geometryIndex < 0 || geometryIndex >= mPoints.size() )
+ {
+ return;
+ }
+
+ if ( mPoints.at( geometryIndex ).at( 0 ) != mPoints.at( geometryIndex ).at( mPoints.at( geometryIndex ).size() - 1 ) )
+ {
+ mPoints[geometryIndex] << mPoints.at( geometryIndex ).at( 0 );
+ }
+
+ if ( doUpdate )
+ {
+ setVisible( true );
+ updateRect();
+ update();
+ }
+}
+
void QgsRubberBand::removePoint( int index, bool doUpdate/* = true*/, int geometryIndex/* = 0*/ )
{
diff --git a/src/gui/qgsrubberband.h b/src/gui/qgsrubberband.h
index 5a2cc80..65f64a5 100644
--- a/src/gui/qgsrubberband.h
+++ b/src/gui/qgsrubberband.h
@@ -149,6 +149,14 @@ class GUI_EXPORT QgsRubberBand: public QgsMapCanvasItem
*/
void addPoint( const QgsPoint & p, bool doUpdate = true, int geometryIndex = 0 );
+ /** Ensures that a polygon geometry is closed and that the last vertex equals the
+ * first vertex.
+ * @param doUpdate set to true to update the map canvas immediately
+ * @param geometryIndex index of the feature part (in case of multipart geometries)
+ * @note added in QGIS 2.16
+ */
+ void closePoints( bool doUpdate = true, int geometryIndex = 0 );
+
/**
* Remove a vertex from the rubberband and (optionally) update canvas.
* @param index The index of the vertex/point to remove, negative indexes start at end
diff --git a/src/gui/symbology-ng/qgs25drendererwidget.cpp b/src/gui/symbology-ng/qgs25drendererwidget.cpp
index ce1b168..2aaf868 100644
--- a/src/gui/symbology-ng/qgs25drendererwidget.cpp
+++ b/src/gui/symbology-ng/qgs25drendererwidget.cpp
@@ -38,6 +38,16 @@ Qgs25DRendererWidget::Qgs25DRendererWidget( QgsVectorLayer* layer, QgsStyleV2* s
setupUi( this );
+ mWallColorButton->setColorDialogTitle( tr( "Select wall color" ) );
+ mWallColorButton->setAllowAlpha( true );
+ mWallColorButton->setContext( "symbology" );
+ mRoofColorButton->setColorDialogTitle( tr( "Select roof color" ) );
+ mRoofColorButton->setAllowAlpha( true );
+ mRoofColorButton->setContext( "symbology" );
+ mShadowColorButton->setColorDialogTitle( tr( "Select shadow color" ) );
+ mShadowColorButton->setAllowAlpha( true );
+ mShadowColorButton->setContext( "symbology" );
+
if ( renderer )
{
mRenderer = Qgs25DRenderer::convertFromRenderer( renderer );
@@ -86,10 +96,13 @@ void Qgs25DRendererWidget::updateRenderer()
void Qgs25DRendererWidget::apply()
{
- QgsExpressionContextUtils::setLayerVariable( mLayer, "qgis_25d_height", mHeightWidget->currentText() );
- QgsExpressionContextUtils::setLayerVariable( mLayer, "qgis_25d_angle", mAngleWidget->value() );
+ if ( mHeightWidget )
+ {
+ QgsExpressionContextUtils::setLayerVariable( mLayer, "qgis_25d_height", mHeightWidget->currentText() );
+ QgsExpressionContextUtils::setLayerVariable( mLayer, "qgis_25d_angle", mAngleWidget->value() );
- emit layerVariablesChanged();
+ emit layerVariablesChanged();
+ }
}
QgsRendererV2Widget* Qgs25DRendererWidget::create( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer )
diff --git a/src/plugins/georeferencer/qgsgeoreftransform.cpp b/src/plugins/georeferencer/qgsgeoreftransform.cpp
index 8af66a6..e36b4d9 100644
--- a/src/plugins/georeferencer/qgsgeoreftransform.cpp
+++ b/src/plugins/georeferencer/qgsgeoreftransform.cpp
@@ -575,10 +575,11 @@ bool QgsProjectiveGeorefTransform::updateParametersFromGCPs( const QVector<QgsPo
return false;
// HACK: flip y coordinates, because georeferencer and gdal use different conventions
- QVector<QgsPoint> flippedPixelCoords( pixelCoords.size() );
- for ( int i = 0; i < pixelCoords.size(); i++ )
+ QVector<QgsPoint> flippedPixelCoords;
+ flippedPixelCoords.reserve( pixelCoords.size() );
+ Q_FOREACH ( const QgsPoint& coord, pixelCoords )
{
- flippedPixelCoords[i] = pixelCoords[i];
+ flippedPixelCoords << QgsPoint( coord.x(), -coord.y() );
}
QgsLeastSquares::projective( mapCoords, flippedPixelCoords, mParameters.H );
diff --git a/src/plugins/gps_importer/qgsgpsplugin.cpp b/src/plugins/gps_importer/qgsgpsplugin.cpp
index f82eedb..9bab68f 100644
--- a/src/plugins/gps_importer/qgsgpsplugin.cpp
+++ b/src/plugins/gps_importer/qgsgpsplugin.cpp
@@ -570,7 +570,7 @@ void QgsGPSPlugin::setupBabel()
{
// where is gpsbabel?
QSettings settings;
- mBabelPath = settings.value( "/Plugin-GPS/gpsbabelpath", QDir::homePath() ).toString();
+ mBabelPath = settings.value( "/Plugin-GPS/gpsbabelpath", QString() ).toString();
if ( mBabelPath.isEmpty() )
mBabelPath = "gpsbabel";
// the importable formats
diff --git a/src/plugins/offline_editing/offline_editing_plugin_gui.cpp b/src/plugins/offline_editing/offline_editing_plugin_gui.cpp
index 7dd54af..0dc47d0 100644
--- a/src/plugins/offline_editing/offline_editing_plugin_gui.cpp
+++ b/src/plugins/offline_editing/offline_editing_plugin_gui.cpp
@@ -170,7 +170,7 @@ void QgsOfflineEditingPluginGui::on_buttonBox_helpRequested()
void QgsOfflineEditingPluginGui::restoreState()
{
QSettings settings;
- mOfflineDataPath = settings.value( "Plugin-OfflineEditing/offline_data_path", QDir().absolutePath() ).toString();
+ mOfflineDataPath = settings.value( "Plugin-OfflineEditing/offline_data_path", QDir::homePath() ).toString();
restoreGeometry( settings.value( "Plugin-OfflineEditing/geometry" ).toByteArray() );
}
diff --git a/src/plugins/roadgraph/shortestpathwidget.cpp b/src/plugins/roadgraph/shortestpathwidget.cpp
index 3b6ac10..7b84102 100644
--- a/src/plugins/roadgraph/shortestpathwidget.cpp
+++ b/src/plugins/roadgraph/shortestpathwidget.cpp
@@ -248,7 +248,7 @@ QgsGraph* RgShortestPathWidget::getPath( QgsPoint& p1, QgsPoint& p2 )
const QgsGraphDirector *director = mPlugin->director();
if ( !director )
{
- QMessageBox::critical( this, tr( "Plugin isn't configured" ), tr( "Plugin isn't configured! Please go to the Vector → Road graph → Settings to configure it." ) );
+ QMessageBox::critical( this, tr( "Plugin isn't configured" ), tr( "Plugin isn't configured! Please go to the Vector, Road graph, Settings to configure it." ) );
return nullptr;
}
connect( director, SIGNAL( buildProgress( int, int ) ), mPlugin->iface()->mainWindow(), SLOT( showProgress( int, int ) ) );
diff --git a/src/providers/delimitedtext/qgsdelimitedtextfeatureiterator.cpp b/src/providers/delimitedtext/qgsdelimitedtextfeatureiterator.cpp
index 43eacf8..0638034 100644
--- a/src/providers/delimitedtext/qgsdelimitedtextfeatureiterator.cpp
+++ b/src/providers/delimitedtext/qgsdelimitedtextfeatureiterator.cpp
@@ -341,7 +341,7 @@ bool QgsDelimitedTextFeatureIterator::nextFeatureInternal( QgsFeature& feature )
if ( ! mTestSubset && ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) )
{
- const QgsAttributeList& attrs = mRequest.subsetOfAttributes();
+ QgsAttributeList attrs = mRequest.subsetOfAttributes();
for ( QgsAttributeList::const_iterator i = attrs.begin(); i != attrs.end(); ++i )
{
int fieldIdx = *i;
diff --git a/src/providers/memory/qgsmemoryprovider.cpp b/src/providers/memory/qgsmemoryprovider.cpp
index 1f74b11..c029533 100644
--- a/src/providers/memory/qgsmemoryprovider.cpp
+++ b/src/providers/memory/qgsmemoryprovider.cpp
@@ -334,6 +334,7 @@ bool QgsMemoryProvider::addFeatures( QgsFeatureList & flist )
mFeatures[mNextFeatureId] = *it;
QgsFeature& newfeat = mFeatures[mNextFeatureId];
newfeat.setFeatureId( mNextFeatureId );
+ newfeat.setValid( true );
it->setFeatureId( mNextFeatureId );
// update spatial index
diff --git a/src/providers/mssql/qgsmssqlprovider.cpp b/src/providers/mssql/qgsmssqlprovider.cpp
index 4bfdc57..df1ea3a 100644
--- a/src/providers/mssql/qgsmssqlprovider.cpp
+++ b/src/providers/mssql/qgsmssqlprovider.cpp
@@ -1851,7 +1851,7 @@ QgsVectorLayerImport::ImportError QgsMssqlProvider::createEmptyLayer(
QgsField fld = fields[i];
if ( oldToNewAttrIdxMap && fld.name() == primaryKey )
{
- oldToNewAttrIdxMap->insert( fields.indexFromName( fld.name() ), 0 );
+ oldToNewAttrIdxMap->insert( fields.fieldNameIndex( fld.name() ), 0 );
continue;
}
@@ -1872,7 +1872,7 @@ QgsVectorLayerImport::ImportError QgsMssqlProvider::createEmptyLayer(
flist.append( fld );
if ( oldToNewAttrIdxMap )
- oldToNewAttrIdxMap->insert( fields.indexFromName( fld.name() ), offset++ );
+ oldToNewAttrIdxMap->insert( fields.fieldNameIndex( fld.name() ), offset++ );
}
if ( !provider->addAttributes( flist ) )
diff --git a/src/providers/ogr/qgsogrfeatureiterator.cpp b/src/providers/ogr/qgsogrfeatureiterator.cpp
index 8850109..426c117 100644
--- a/src/providers/ogr/qgsogrfeatureiterator.cpp
+++ b/src/providers/ogr/qgsogrfeatureiterator.cpp
@@ -36,14 +36,19 @@
QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource* source, bool ownSource, const QgsFeatureRequest& request )
: QgsAbstractFeatureIteratorFromSource<QgsOgrFeatureSource>( source, ownSource, request )
+ , mFeatureFetched( false )
+ , mConn( nullptr )
, ogrLayer( nullptr )
, mSubsetStringSet( false )
+ , mFetchGeometry( false )
, mGeometrySimplifier( nullptr )
, mExpressionCompiled( false )
{
- mFeatureFetched = false;
-
mConn = QgsOgrConnPool::instance()->acquireConnection( mSource->mProvider->dataSourceUri() );
+ if ( !mConn->ds )
+ {
+ return;
+ }
if ( mSource->mLayerName.isNull() )
{
@@ -53,10 +58,18 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource* source, bool
{
ogrLayer = OGR_DS_GetLayerByName( mConn->ds, TO8( mSource->mLayerName ) );
}
+ if ( !ogrLayer )
+ {
+ return;
+ }
if ( !mSource->mSubsetString.isEmpty() )
{
ogrLayer = QgsOgrUtils::setSubsetString( ogrLayer, mConn->ds, mSource->mEncoding, mSource->mSubsetString );
+ if ( !ogrLayer )
+ {
+ return;
+ }
mSubsetStringSet = true;
}
@@ -204,7 +217,7 @@ bool QgsOgrFeatureIterator::fetchFeature( QgsFeature& feature )
{
feature.setValid( false );
- if ( mClosed )
+ if ( mClosed || !ogrLayer )
return false;
if ( mRequest.filterType() == QgsFeatureRequest::FilterFid )
@@ -249,7 +262,7 @@ bool QgsOgrFeatureIterator::fetchFeature( QgsFeature& feature )
bool QgsOgrFeatureIterator::rewind()
{
- if ( mClosed )
+ if ( mClosed || !ogrLayer )
return false;
OGR_L_ResetReading( ogrLayer );
@@ -391,7 +404,7 @@ bool QgsOgrFeatureIterator::readFeature( OGRFeatureH fet, QgsFeature& feature )
// fetch attributes
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
{
- const QgsAttributeList& attrs = mRequest.subsetOfAttributes();
+ QgsAttributeList attrs = mRequest.subsetOfAttributes();
for ( QgsAttributeList::const_iterator it = attrs.begin(); it != attrs.end(); ++it )
{
getFeatureAttribute( fet, feature, *it );
diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp
index a170890..3249e4a 100644
--- a/src/providers/ogr/qgsogrprovider.cpp
+++ b/src/providers/ogr/qgsogrprovider.cpp
@@ -139,7 +139,7 @@ bool QgsOgrProvider::convertField( QgsField &field, const QTextCodec &encoding )
void QgsOgrProvider::repack()
{
- if ( ogrDriverName != "ESRI Shapefile" || !ogrOrigLayer )
+ if ( !mValid || ogrDriverName != "ESRI Shapefile" || !ogrOrigLayer )
return;
QByteArray layerName = OGR_FD_GetName( OGR_L_GetLayerDefn( ogrOrigLayer ) );
@@ -283,7 +283,11 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri )
, geomType( wkbUnknown )
, mFeaturesCounted( -1 )
, mWriteAccess( false )
+ , mWriteAccessPossible( false )
+ , mDynamicWriteAccess( false )
, mShapefileMayBeCorrupted( false )
+ , mUpdateModeStackDepth( 0 )
+ , mCapabilities( 0 )
{
QgsApplication::registerOgrDrivers();
@@ -354,7 +358,7 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri )
}
}
- open();
+ open( OpenModeInitial );
mNativeTypes
<< QgsVectorDataProvider::NativeType( tr( "Whole number (integer)" ), "integer", QVariant::Int, 1, 10 )
@@ -380,6 +384,10 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri )
QgsOgrProvider::~QgsOgrProvider()
{
close();
+ QgsOgrConnPool::instance()->unref( dataSourceUri() );
+ // We must also make sure to flush unusef cached connections so that
+ // the file can be removed (#15137)
+ QgsOgrConnPool::instance()->invalidateConnections( dataSourceUri() );
}
QgsAbstractFeatureSource* QgsOgrProvider::featureSource() const
@@ -391,6 +399,9 @@ bool QgsOgrProvider::setSubsetString( const QString& theSQL, bool updateFeatureC
{
QgsCPLErrorHandler handler;
+ if ( !ogrDataSource )
+ return false;
+
if ( theSQL == mSubsetString && mFeaturesCounted >= 0 )
return true;
@@ -657,6 +668,11 @@ OGRwkbGeometryType QgsOgrProvider::getOgrGeomType( OGRLayerH ogrLayer )
{
geomType = OGR_FD_GetGeomType( fdef );
+ // Handle wkbUnknown and its Z/M variants. QGIS has no unknown Z/M variants,
+ // so just use flat wkbUnknown
+ if ( wkbFlatten( geomType ) == wkbUnknown )
+ geomType = wkbUnknown;
+
// Some ogr drivers (e.g. GML) are not able to determine the geometry type of a layer like this.
// In such cases, we use virtual sublayers for each geometry if the layer contains
// multiple geometries (see subLayers) otherwise we guess geometry type from first feature
@@ -688,6 +704,8 @@ void QgsOgrProvider::loadFields()
QgsOgrConnPool::instance()->invalidateConnections( dataSourceUri() );
//the attribute fields need to be read again when the encoding changes
mAttributeFields.clear();
+ if ( !ogrLayer )
+ return;
if ( mOgrGeometryTypeFilter != wkbUnknown )
{
@@ -898,6 +916,8 @@ void QgsOgrProvider::updateExtents()
size_t QgsOgrProvider::layerCount() const
{
+ if ( !mValid )
+ return 0;
return OGR_DS_GetLayerCount( ogrDataSource );
} // QgsOgrProvider::layerCount()
@@ -1059,6 +1079,9 @@ bool QgsOgrProvider::addFeature( QgsFeature& f )
bool QgsOgrProvider::addFeatures( QgsFeatureList & flist )
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
setRelevantFields( ogrLayer, true, attributeIndexes() );
bool returnvalue = true;
@@ -1085,6 +1108,16 @@ bool QgsOgrProvider::addFeatures( QgsFeatureList & flist )
bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
+ if ( ogrDriverName == "MapInfo File" )
+ {
+ // adding attributes in mapinfo requires to be able to delete the .dat file
+ // so drop any cached connections.
+ QgsOgrConnPool::instance()->invalidateConnections( dataSourceUri() );
+ }
+
bool returnvalue = true;
for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
@@ -1142,6 +1175,9 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
bool QgsOgrProvider::deleteAttributes( const QgsAttributeIds &attributes )
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
#if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1900
bool res = true;
QList<int> attrsLst = attributes.toList();
@@ -1167,6 +1203,9 @@ bool QgsOgrProvider::deleteAttributes( const QgsAttributeIds &attributes )
bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map )
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
if ( attr_map.isEmpty() )
return true;
@@ -1270,6 +1309,8 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
{
pushError( tr( "OGR error setting feature %1: %2" ).arg( fid ).arg( CPLGetLastErrorMsg() ) );
}
+
+ OGR_F_Destroy( of );
}
if ( OGR_L_SyncToDisk( ogrLayer ) != OGRERR_NONE )
@@ -1282,8 +1323,8 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
{
- OGRFeatureH theOGRFeature = nullptr;
- OGRGeometryH theNewGeometry = nullptr;
+ if ( !doInitialActionsForEdition() )
+ return false;
setRelevantFields( ogrLayer, true, attributeIndexes() );
@@ -1295,37 +1336,47 @@ bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
continue;
}
- theOGRFeature = OGR_L_GetFeature( ogrLayer, static_cast<long>( FID_TO_NUMBER( it.key() ) ) );
+ OGRFeatureH theOGRFeature = OGR_L_GetFeature( ogrLayer, static_cast<long>( FID_TO_NUMBER( it.key() ) ) );
if ( !theOGRFeature )
{
pushError( tr( "OGR error changing geometry: feature %1 not found" ).arg( it.key() ) );
continue;
}
- //create an OGRGeometry
- if ( OGR_G_CreateFromWkb( const_cast<unsigned char*>( it->asWkb() ),
- OGR_L_GetSpatialRef( ogrLayer ),
- &theNewGeometry,
- it->wkbSize() ) != OGRERR_NONE )
+ OGRGeometryH theNewGeometry = nullptr;
+ // We might receive null geometries. It is ok, but don't go through the
+ // OGR_G_CreateFromWkb() route then
+ if ( it->wkbSize() != 0 )
{
- pushError( tr( "OGR error creating geometry for feature %1: %2" ).arg( it.key() ).arg( CPLGetLastErrorMsg() ) );
- OGR_G_DestroyGeometry( theNewGeometry );
- theNewGeometry = nullptr;
- continue;
- }
+ //create an OGRGeometry
+ if ( OGR_G_CreateFromWkb( const_cast<unsigned char*>( it->asWkb() ),
+ OGR_L_GetSpatialRef( ogrLayer ),
+ &theNewGeometry,
+ it->wkbSize() ) != OGRERR_NONE )
+ {
+ pushError( tr( "OGR error creating geometry for feature %1: %2" ).arg( it.key() ).arg( CPLGetLastErrorMsg() ) );
+ OGR_G_DestroyGeometry( theNewGeometry );
+ theNewGeometry = nullptr;
+ OGR_F_Destroy( theOGRFeature );
+ continue;
+ }
- if ( !theNewGeometry )
- {
- pushError( tr( "OGR error in feature %1: geometry is null" ).arg( it.key() ) );
- continue;
+ if ( !theNewGeometry )
+ {
+ pushError( tr( "OGR error in feature %1: geometry is null" ).arg( it.key() ) );
+ OGR_F_Destroy( theOGRFeature );
+ continue;
+ }
}
//set the new geometry
if ( OGR_F_SetGeometryDirectly( theOGRFeature, theNewGeometry ) != OGRERR_NONE )
{
pushError( tr( "OGR error setting geometry of feature %1: %2" ).arg( it.key() ).arg( CPLGetLastErrorMsg() ) );
- OGR_G_DestroyGeometry( theNewGeometry );
- theNewGeometry = nullptr;
+ // Shouldn't happen normally. If it happens, ownership of the geometry
+ // may be not really well defined, so better not destroy it, but just
+ // the feature.
+ OGR_F_Destroy( theOGRFeature );
continue;
}
@@ -1333,8 +1384,7 @@ bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
if ( OGR_L_SetFeature( ogrLayer, theOGRFeature ) != OGRERR_NONE )
{
pushError( tr( "OGR error setting feature %1: %2" ).arg( it.key() ).arg( CPLGetLastErrorMsg() ) );
- OGR_G_DestroyGeometry( theNewGeometry );
- theNewGeometry = nullptr;
+ OGR_F_Destroy( theOGRFeature );
continue;
}
mShapefileMayBeCorrupted = true;
@@ -1347,6 +1397,9 @@ bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
bool QgsOgrProvider::createSpatialIndex()
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
if ( ogrDriverName != "ESRI Shapefile" )
return false;
@@ -1367,6 +1420,9 @@ bool QgsOgrProvider::createSpatialIndex()
bool QgsOgrProvider::createAttributeIndex( int field )
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
QByteArray quotedLayerName = quotedIdentifier( OGR_FD_GetName( OGR_L_GetLayerDefn( ogrOrigLayer ) ) );
QByteArray dropSql = "DROP INDEX ON " + quotedLayerName;
OGR_DS_ExecuteSQL( ogrDataSource, dropSql.constData(), OGR_L_GetSpatialFilter( ogrOrigLayer ), nullptr );
@@ -1381,6 +1437,9 @@ bool QgsOgrProvider::createAttributeIndex( int field )
bool QgsOgrProvider::deleteFeatures( const QgsFeatureIds & id )
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
bool returnvalue = true;
for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it )
{
@@ -1406,6 +1465,9 @@ bool QgsOgrProvider::deleteFeatures( const QgsFeatureIds & id )
bool QgsOgrProvider::deleteFeature( QgsFeatureId id )
{
+ if ( !doInitialActionsForEdition() )
+ return false;
+
if ( FID_TO_NUMBER( id ) > std::numeric_limits<long>::max() )
{
pushError( tr( "OGR error on feature %1: id too large" ).arg( id ) );
@@ -1423,8 +1485,28 @@ bool QgsOgrProvider::deleteFeature( QgsFeatureId id )
return true;
}
+bool QgsOgrProvider::doInitialActionsForEdition()
+{
+ if ( !mValid )
+ return false;
+
+ if ( !mWriteAccess && mWriteAccessPossible && mDynamicWriteAccess )
+ {
+ QgsDebugMsg( "Enter update mode implictly" );
+ if ( !enterUpdateMode() )
+ return false;
+ }
+
+ return true;
+}
+
int QgsOgrProvider::capabilities() const
{
+ return mCapabilities;
+}
+
+void QgsOgrProvider::computeCapabilities()
+{
int ability = 0;
// collect abilities reported by OGR
@@ -1446,19 +1528,19 @@ int QgsOgrProvider::capabilities() const
ability |= QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId;
}
- if ( mWriteAccess && OGR_L_TestCapability( ogrLayer, "SequentialWrite" ) )
+ if ( mWriteAccessPossible && OGR_L_TestCapability( ogrLayer, "SequentialWrite" ) )
// true if the CreateFeature() method works for this layer.
{
ability |= QgsVectorDataProvider::AddFeatures;
}
- if ( mWriteAccess && OGR_L_TestCapability( ogrLayer, "DeleteFeature" ) )
+ if ( mWriteAccessPossible && OGR_L_TestCapability( ogrLayer, "DeleteFeature" ) )
// true if this layer can delete its features
{
ability |= DeleteFeatures;
}
- if ( mWriteAccess && OGR_L_TestCapability( ogrLayer, "RandomWrite" ) )
+ if ( mWriteAccessPossible && OGR_L_TestCapability( ogrLayer, "RandomWrite" ) )
// true if the SetFeature() method is operational on this layer.
{
// TODO According to http://shapelib.maptools.org/ (Shapefile C Library V1.2)
@@ -1506,12 +1588,12 @@ int QgsOgrProvider::capabilities() const
}
#endif
- if ( mWriteAccess && OGR_L_TestCapability( ogrLayer, "CreateField" ) )
+ if ( mWriteAccessPossible && OGR_L_TestCapability( ogrLayer, "CreateField" ) )
{
ability |= AddAttributes;
}
- if ( mWriteAccess && OGR_L_TestCapability( ogrLayer, "DeleteField" ) )
+ if ( mWriteAccessPossible && OGR_L_TestCapability( ogrLayer, "DeleteField" ) )
{
ability |= DeleteAttributes;
}
@@ -1561,7 +1643,7 @@ int QgsOgrProvider::capabilities() const
#endif
}
- return ability;
+ mCapabilities = ability;
}
@@ -2359,6 +2441,8 @@ QgsCoordinateReferenceSystem QgsOgrProvider::crs()
QgsDebugMsg( "Entering." );
QgsCoordinateReferenceSystem srs;
+ if ( !mValid )
+ return srs;
if ( ogrDriver )
{
@@ -2410,7 +2494,7 @@ void QgsOgrProvider::uniqueValues( int index, QList<QVariant> &uniqueValues, int
{
uniqueValues.clear();
- if ( index < 0 || index >= mAttributeFields.count() )
+ if ( !mValid || index < 0 || index >= mAttributeFields.count() )
return;
const QgsField& fld = mAttributeFields.at( index );
@@ -2457,7 +2541,7 @@ void QgsOgrProvider::uniqueValues( int index, QList<QVariant> &uniqueValues, int
QVariant QgsOgrProvider::minimumValue( int index )
{
- if ( index < 0 || index >= mAttributeFields.count() )
+ if ( !mValid || index < 0 || index >= mAttributeFields.count() )
{
return QVariant();
}
@@ -2496,7 +2580,7 @@ QVariant QgsOgrProvider::minimumValue( int index )
QVariant QgsOgrProvider::maximumValue( int index )
{
- if ( index < 0 || index >= mAttributeFields.count() )
+ if ( !mValid || index < 0 || index >= mAttributeFields.count() )
{
return QVariant();
}
@@ -2590,7 +2674,7 @@ QString QgsOgrUtils::quotedValue( const QVariant& value )
bool QgsOgrProvider::syncToDisc()
{
//for shapefiles, remove spatial index files and create a new index
- QgsOgrConnPool::instance()->unref( mFilePath );
+ QgsOgrConnPool::instance()->unref( dataSourceUri() );
bool shapeIndex = false;
if ( ogrDriverName == "ESRI Shapefile" )
{
@@ -2607,7 +2691,9 @@ bool QgsOgrProvider::syncToDisc()
close();
QgsOgrConnPool::instance()->invalidateConnections( dataSourceUri() );
QFile::remove( sbnIndexFile );
- open();
+ open( OpenModeSameAsCurrent );
+ if ( !mValid )
+ return false;
}
}
@@ -2632,6 +2718,12 @@ bool QgsOgrProvider::syncToDisc()
void QgsOgrProvider::recalculateFeatureCount()
{
+ if ( !ogrLayer )
+ {
+ mFeaturesCounted = 0;
+ return;
+ }
+
OGRGeometryH filter = OGR_L_GetSpatialFilter( ogrLayer );
if ( filter )
{
@@ -2724,7 +2816,7 @@ OGRLayerH QgsOgrUtils::setSubsetString( OGRLayerH layer, OGRDataSourceH ds, QTex
return OGR_DS_ExecuteSQL( ds, sql.constData(), nullptr, nullptr );
}
-void QgsOgrProvider::open()
+void QgsOgrProvider::open( OpenMode mode )
{
bool openReadOnly = false;
@@ -2749,30 +2841,38 @@ void QgsOgrProvider::open()
QgsDebugMsg( "mLayerName: " + mLayerName );
QgsDebugMsg( "mSubsetString: " + mSubsetString );
CPLSetConfigOption( "OGR_ORGANIZE_POLYGONS", "ONLY_CCW" ); // "SKIP" returns MULTIPOLYGONs for multiringed POLYGONs
+ CPLSetConfigOption( "GPX_ELE_AS_25D", "YES" ); // use GPX elevation as z values
if ( mFilePath.startsWith( "MySQL:" ) && !mLayerName.isEmpty() && !mFilePath.endsWith( ",tables=" + mLayerName ) )
{
mFilePath += ",tables=" + mLayerName;
}
+ if ( mode == OpenModeForceReadOnly )
+ openReadOnly = true;
+ else if ( mode == OpenModeSameAsCurrent && !mWriteAccess )
+ openReadOnly = true;
+
// first try to open in update mode (unless specified otherwise)
if ( !openReadOnly )
ogrDataSource = OGROpen( TO8F( mFilePath ), true, &ogrDriver );
+ mValid = false;
if ( ogrDataSource )
{
mWriteAccess = true;
+ mWriteAccessPossible = true;
}
else
{
- QgsDebugMsg( "OGR failed to opened in update mode, trying in read-only mode" );
+ mWriteAccess = false;
+ if ( !openReadOnly )
+ {
+ QgsDebugMsg( "OGR failed to opened in update mode, trying in read-only mode" );
+ }
// try to open read-only
ogrDataSource = OGROpen( TO8F( mFilePath ), false, &ogrDriver );
-
- //TODO Need to set a flag or something to indicate that the layer
- //TODO is in read-only mode, otherwise edit ops will fail
- //TODO: capabilities() should now reflect this; need to test.
}
if ( ogrDataSource )
@@ -2801,6 +2901,10 @@ void QgsOgrProvider::open()
mValid = setSubsetString( mSubsetString );
if ( mValid )
{
+ if ( mode == OpenModeInitial )
+ {
+ computeCapabilities();
+ }
QgsDebugMsg( "Data source is valid" );
}
else
@@ -2817,6 +2921,59 @@ void QgsOgrProvider::open()
{
QgsMessageLog::logMessage( tr( "Data source is invalid (%1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ), tr( "OGR" ) );
}
+
+ // For shapefiles or MapInfo .tab, so as to allow concurrent opening between
+ // QGIS and MapInfo, we go back to read-only mode for now.
+ // We limit to those drivers as re-opening is relatively cheap (other drivers
+ // like GeoJSON might do full content ingestion for example)
+ if ( mValid && mode == OpenModeInitial && mWriteAccess &&
+ ( ogrDriverName == "ESRI Shapefile" || ogrDriverName == "MapInfo File" ) )
+ {
+ OGR_DS_Destroy( ogrDataSource );
+ ogrLayer = ogrOrigLayer = nullptr;
+ mValid = false;
+
+ ogrDataSource = OGROpen( TO8F( mFilePath ), false, &ogrDriver );
+
+ mWriteAccess = false;
+
+ if ( ogrDataSource )
+ {
+ // We get the layer which was requested by the uri. The layername
+ // has precedence over the layerid if both are given.
+ if ( mLayerName.isNull() )
+ {
+ ogrOrigLayer = OGR_DS_GetLayer( ogrDataSource, mLayerIndex );
+ }
+ else
+ {
+ ogrOrigLayer = OGR_DS_GetLayerByName( ogrDataSource, TO8( mLayerName ) );
+ }
+
+ ogrLayer = ogrOrigLayer;
+ }
+ if ( ogrLayer )
+ {
+ mValid = true;
+ mDynamicWriteAccess = true;
+
+ if ( !mSubsetString.isEmpty() )
+ {
+ int featuresCountedBackup = mFeaturesCounted;
+ mFeaturesCounted = -1;
+ mValid = setSubsetString( mSubsetString, false );
+ mFeaturesCounted = featuresCountedBackup;
+ }
+ }
+ }
+
+ // For debug/testing purposes
+ if ( !mValid )
+ setProperty( "_debug_open_mode", "invalid" );
+ else if ( mWriteAccess )
+ setProperty( "_debug_open_mode", "read-write" );
+ else
+ setProperty( "_debug_open_mode", "read-only" );
}
void QgsOgrProvider::close()
@@ -2831,10 +2988,81 @@ void QgsOgrProvider::close()
OGR_DS_Destroy( ogrDataSource );
}
ogrDataSource = nullptr;
+ ogrLayer = nullptr;
+ ogrOrigLayer = nullptr;
+ mValid = false;
+ setProperty( "_debug_open_mode", "invalid" );
updateExtents();
+}
+
+void QgsOgrProvider::reloadData()
+{
+ forceReload();
+ close();
+ open( OpenModeSameAsCurrent );
+ if ( !mValid )
+ pushError( tr( "Cannot reopen datasource %1" ).arg( dataSourceUri() ) );
+}
+
+bool QgsOgrProvider::enterUpdateMode()
+{
+ if ( !mWriteAccessPossible )
+ {
+ return false;
+ }
+ if ( mWriteAccess )
+ {
+ ++mUpdateModeStackDepth;
+ return true;
+ }
+ if ( mUpdateModeStackDepth == 0 )
+ {
+ Q_ASSERT( mDynamicWriteAccess );
+ QgsDebugMsg( QString( "Reopening %1 in update mode" ).arg( dataSourceUri() ) );
+ close();
+ open( OpenModeForceUpdate );
+ if ( !ogrDataSource || !mWriteAccess )
+ {
+ QgsMessageLog::logMessage( tr( "Cannot reopen datasource %1 in update mode" ).arg( dataSourceUri() ), tr( "OGR" ) );
+ pushError( tr( "Cannot reopen datasource %1 in update mode" ).arg( dataSourceUri() ) );
+ return false;
+ }
+ }
+ ++mUpdateModeStackDepth;
+ return true;
+}
- QgsOgrConnPool::instance()->unref( mFilePath );
+bool QgsOgrProvider::leaveUpdateMode()
+{
+ if ( !mWriteAccessPossible )
+ {
+ return false;
+ }
+ --mUpdateModeStackDepth;
+ if ( mUpdateModeStackDepth < 0 )
+ {
+ QgsMessageLog::logMessage( tr( "Unbalanced call to leaveUpdateMode() w.r.t. enterUpdateMode()" ), tr( "OGR" ) );
+ mUpdateModeStackDepth = 0;
+ return false;
+ }
+ if ( !mDynamicWriteAccess )
+ {
+ return true;
+ }
+ if ( mUpdateModeStackDepth == 0 )
+ {
+ QgsDebugMsg( QString( "Reopening %1 in read-only mode" ).arg( dataSourceUri() ) );
+ close();
+ open( OpenModeForceReadOnly );
+ if ( !ogrDataSource )
+ {
+ QgsMessageLog::logMessage( tr( "Cannot reopen datasource %1 in read-only mode" ).arg( dataSourceUri() ), tr( "OGR" ) );
+ pushError( tr( "Cannot reopen datasource %1 in read-only mode" ).arg( dataSourceUri() ) );
+ return false;
+ }
+ }
+ return true;
}
// ---------------------------------------------------------------------------
diff --git a/src/providers/ogr/qgsogrprovider.h b/src/providers/ogr/qgsogrprovider.h
index 101c859..c1cf3aa 100644
--- a/src/providers/ogr/qgsogrprovider.h
+++ b/src/providers/ogr/qgsogrprovider.h
@@ -174,6 +174,9 @@ class QgsOgrProvider : public QgsVectorDataProvider
virtual void setEncoding( const QString& e ) override;
+ virtual bool enterUpdateMode() override;
+
+ virtual bool leaveUpdateMode() override;
/** Return vector file filter string
*
@@ -271,6 +274,9 @@ class QgsOgrProvider : public QgsVectorDataProvider
*/
void forceReload() override;
+ /** Closes and re-open the datasource */
+ void reloadData() override;
+
protected:
/** Loads fields from input file to member attributeFields */
void loadFields();
@@ -287,7 +293,15 @@ class QgsOgrProvider : public QgsVectorDataProvider
/** Clean shapefile from features which are marked as deleted */
void repack();
- void open();
+ enum OpenMode
+ {
+ OpenModeInitial,
+ OpenModeSameAsCurrent,
+ OpenModeForceReadOnly,
+ OpenModeForceUpdate,
+ };
+
+ void open( OpenMode mode );
void close();
private:
@@ -355,7 +369,24 @@ class QgsOgrProvider : public QgsVectorDataProvider
/** Whether the file is opened in write mode*/
bool mWriteAccess;
+ /** Whether the file can potentially be opened in write mode (but not necessarily currently) */
+ bool mWriteAccessPossible;
+
+ /** Whether the open mode of the datasource changes w.r.t calls to enterUpdateMode() / leaveUpdateMode() */
+ bool mDynamicWriteAccess;
+
bool mShapefileMayBeCorrupted;
+
+ /** Converts the geometry to the layer type if necessary. Takes ownership of the passed geometry */
+ OGRGeometryH ConvertGeometryIfNecessary( OGRGeometryH );
+
+ int mUpdateModeStackDepth;
+
+ void computeCapabilities();
+
+ int mCapabilities;
+
+ bool doInitialActionsForEdition();
};
diff --git a/src/providers/oracle/qgsoracleconn.cpp b/src/providers/oracle/qgsoracleconn.cpp
index d081bd4..2fa7d40 100644
--- a/src/providers/oracle/qgsoracleconn.cpp
+++ b/src/providers/oracle/qgsoracleconn.cpp
@@ -361,7 +361,7 @@ QString QgsOracleConn::fieldExpression( const QgsField &fld )
void QgsOracleConn::retrieveLayerTypes( QgsOracleLayerProperty &layerProperty, bool useEstimatedMetadata, bool onlyExistingTypes )
{
- QgsDebugMsg( "entering: " + layerProperty.toString() );
+ QgsDebugMsgLevel( "entering: " + layerProperty.toString(), 3 );
if ( layerProperty.isView )
{
@@ -657,6 +657,7 @@ void QgsOracleConn::deleteConnection( QString theConnName )
settings.remove( key + "/allowGeometrylessTables" );
settings.remove( key + "/estimatedMetadata" );
settings.remove( key + "/onlyExistingTypes" );
+ settings.remove( key + "/includeGeoAttributes" );
settings.remove( key + "/saveUsername" );
settings.remove( key + "/savePassword" );
settings.remove( key + "/save" );
@@ -677,7 +678,7 @@ void QgsOracleConn::setSelectedConnection( QString name )
QgsDataSourceURI QgsOracleConn::connUri( QString theConnName )
{
- QgsDebugMsg( "theConnName = " + theConnName );
+ QgsDebugMsgLevel( "theConnName = " + theConnName, 3 );
QSettings settings;
diff --git a/src/providers/oracle/qgsoracledataitems.cpp b/src/providers/oracle/qgsoracledataitems.cpp
index 8bfe203..bb4c813 100644
--- a/src/providers/oracle/qgsoracledataitems.cpp
+++ b/src/providers/oracle/qgsoracledataitems.cpp
@@ -30,7 +30,7 @@ QGISEXTERN bool deleteLayer( const QString& uri, QString& errCause );
// ---------------------------------------------------------------------------
QgsOracleConnectionItem::QgsOracleConnectionItem( QgsDataItem* parent, QString name, QString path )
: QgsDataCollectionItem( parent, name, path )
- , mColumnTypeThread( 0 )
+ , mColumnTypeThread( nullptr )
{
mIconName = "mIconConnect.png";
}
@@ -47,14 +47,12 @@ void QgsOracleConnectionItem::stop()
mColumnTypeThread->stop();
mColumnTypeThread->wait();
delete mColumnTypeThread;
- mColumnTypeThread = 0;
+ mColumnTypeThread = nullptr;
}
}
void QgsOracleConnectionItem::refresh()
{
- QApplication::setOverrideCursor( Qt::WaitCursor );
-
stop();
Q_FOREACH ( QgsDataItem *child, mChildren )
@@ -66,18 +64,28 @@ void QgsOracleConnectionItem::refresh()
{
addChildItem( item, true );
}
+}
- QApplication::restoreOverrideCursor();
+void QgsOracleConnectionItem::setAllAsPopulated()
+{
+ Q_FOREACH ( QgsDataItem *child, mChildren )
+ {
+ child->setState( Populated );
+ }
+ setState( Populated );
}
QVector<QgsDataItem*> QgsOracleConnectionItem::createChildren()
{
- QgsDebugMsg( "Entered" );
+ setState( Populating );
mOwnerMap.clear();
stop();
+ if ( deferredDelete() )
+ return QVector<QgsDataItem*>();
+
if ( !mColumnTypeThread )
{
mColumnTypeThread = new QgsOracleColumnTypeThread( mName,
@@ -99,26 +107,31 @@ QVector<QgsDataItem*> QgsOracleConnectionItem::createChildren()
}
if ( mColumnTypeThread )
+ {
mColumnTypeThread->start();
+ }
+ else
+ {
+ setAllAsPopulated();
+ }
return QVector<QgsDataItem*>();
}
void QgsOracleConnectionItem::threadStarted()
{
- QgsDebugMsg( "Entering." );
- qApp->setOverrideCursor( Qt::BusyCursor );
+ QgsDebugMsgLevel( "Entering.", 3 );
}
void QgsOracleConnectionItem::threadFinished()
{
- QgsDebugMsg( "Entering." );
- qApp->restoreOverrideCursor();
+ QgsDebugMsgLevel( "Entering.", 3 );
+ setAllAsPopulated();
}
void QgsOracleConnectionItem::setLayerType( QgsOracleLayerProperty layerProperty )
{
- QgsDebugMsg( layerProperty.toString() );
+ QgsDebugMsgLevel( layerProperty.toString(), 3 );
QgsOracleOwnerItem *ownerItem = mOwnerMap.value( layerProperty.ownerName, 0 );
for ( int i = 0 ; i < layerProperty.size(); i++ )
@@ -126,19 +139,20 @@ void QgsOracleConnectionItem::setLayerType( QgsOracleLayerProperty layerProperty
QGis::WkbType wkbType = layerProperty.types.at( i );
if ( wkbType == QGis::WKBUnknown )
{
- QgsDebugMsg( "skip unknown geometry type" );
+ QgsDebugMsgLevel( "skip unknown geometry type", 3 );
continue;
}
if ( !ownerItem )
{
ownerItem = new QgsOracleOwnerItem( this, layerProperty.ownerName, mPath + "/" + layerProperty.ownerName );
- QgsDebugMsg( "add owner item: " + layerProperty.ownerName );
+ ownerItem->setState( Populating );
+ QgsDebugMsgLevel( "add owner item: " + layerProperty.ownerName, 3 );
addChildItem( ownerItem, true );
mOwnerMap[ layerProperty.ownerName ] = ownerItem;
}
- QgsDebugMsg( "ADD LAYER" );
+ QgsDebugMsgLevel( "ADD LAYER", 3 );
ownerItem->addLayer( layerProperty.at( i ) );
}
}
@@ -158,18 +172,22 @@ QList<QAction*> QgsOracleConnectionItem::actions()
{
QList<QAction*> lst;
- QAction* actionEdit = new QAction( tr( "Edit..." ), this );
+ QAction* actionRefresh = new QAction( tr( "Refresh" ), this );
+ connect( actionRefresh, SIGNAL( triggered() ), this, SLOT( refreshConnection() ) );
+ lst.append( actionRefresh );
+
+ QAction* separator = new QAction( this );
+ separator->setSeparator( true );
+ lst.append( separator );
+
+ QAction* actionEdit = new QAction( tr( "Edit Connection..." ), this );
connect( actionEdit, SIGNAL( triggered() ), this, SLOT( editConnection() ) );
lst.append( actionEdit );
- QAction* actionDelete = new QAction( tr( "Delete" ), this );
+ QAction* actionDelete = new QAction( tr( "Delete Connection" ), this );
connect( actionDelete, SIGNAL( triggered() ), this, SLOT( deleteConnection() ) );
lst.append( actionDelete );
- QAction* actionRefresh = new QAction( tr( "Refresh" ), this );
- connect( actionRefresh, SIGNAL( triggered() ), this, SLOT( refreshConnection() ) );
- lst.append( actionRefresh );
-
return lst;
}
@@ -185,10 +203,16 @@ void QgsOracleConnectionItem::editConnection()
void QgsOracleConnectionItem::deleteConnection()
{
+ if ( QMessageBox::question( nullptr, QObject::tr( "Delete Connection" ),
+ QObject::tr( "Are you sure you want to delete the connection to %1?" ).arg( mName ),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
+ return;
+
QgsOracleConn::deleteConnection( mName );
// the parent should be updated
- mParent->refresh();
+ if ( mParent )
+ mParent->refresh();
}
void QgsOracleConnectionItem::refreshConnection()
@@ -207,13 +231,15 @@ bool QgsOracleConnectionItem::handleDrop( const QMimeData * data, Qt::DropAction
qApp->setOverrideCursor( Qt::WaitCursor );
- QProgressDialog *progress = new QProgressDialog( tr( "Copying features..." ), tr( "Abort" ), 0, 0, 0 );
+ QProgressDialog *progress = new QProgressDialog( tr( "Copying features..." ), tr( "Abort" ), 0, 0, nullptr );
progress->setWindowTitle( tr( "Import layer" ) );
progress->setWindowModality( Qt::WindowModal );
progress->show();
QStringList importResults;
bool hasError = false;
+ bool cancelled = false;
+
QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( data );
Q_FOREACH ( const QgsMimeDataUtils::Uri& u, lst )
{
@@ -230,18 +256,20 @@ bool QgsOracleConnectionItem::handleDrop( const QMimeData * data, Qt::DropAction
if ( srcLayer->isValid() )
{
uri.setDataSource( QString(), u.name.left( 30 ).toUpper(), "GEOM" );
- uri.setWkbType( srcLayer->wkbType() );
+ uri.setWkbType( QGis::fromOldWkbType( srcLayer->wkbType() ) );
QString authid = srcLayer->crs().authid();
if ( authid.startsWith( "EPSG:", Qt::CaseInsensitive ) )
{
uri.setSrid( authid.mid( 5 ) );
}
- QgsDebugMsg( "URI " + uri.uri() );
+ QgsDebugMsgLevel( "URI " + uri.uri(), 3 );
QgsVectorLayerImport::ImportError err;
QString importError;
- err = QgsVectorLayerImport::importLayer( srcLayer, uri.uri(), "oracle", &srcLayer->crs(), false, &importError, false, 0, progress );
+ err = QgsVectorLayerImport::importLayer( srcLayer, uri.uri(), "oracle", &srcLayer->crs(), false, &importError, false, nullptr, progress );
if ( err == QgsVectorLayerImport::NoError )
importResults.append( tr( "%1: OK!" ).arg( u.name ) );
+ else if ( err == QgsVectorLayerImport::ErrUserCancelled )
+ cancelled = true;
else
{
importResults.append( QString( "%1: %2" ).arg( u.name ).arg( importError ) );
@@ -261,7 +289,12 @@ bool QgsOracleConnectionItem::handleDrop( const QMimeData * data, Qt::DropAction
qApp->restoreOverrideCursor();
- if ( hasError )
+ if ( cancelled )
+ {
+ QMessageBox::information( nullptr, tr( "Import to Oracle database" ), tr( "Import cancelled." ) );
+ refresh();
+ }
+ else if ( hasError )
{
QgsMessageOutput *output = QgsMessageOutput::createMessageOutput();
output->setTitle( tr( "Import to Oracle database" ) );
@@ -274,6 +307,11 @@ bool QgsOracleConnectionItem::handleDrop( const QMimeData * data, Qt::DropAction
refresh();
}
+ if ( state() == Populated )
+ refresh();
+ else
+ populate();
+
return true;
}
@@ -294,7 +332,7 @@ QList<QAction*> QgsOracleLayerItem::actions()
{
QList<QAction*> lst;
- QAction* actionDeleteLayer = new QAction( tr( "Delete layer" ), this );
+ QAction* actionDeleteLayer = new QAction( tr( "Delete Table" ), this );
connect( actionDeleteLayer, SIGNAL( triggered() ), this, SLOT( deleteLayer() ) );
lst.append( actionDeleteLayer );
@@ -303,15 +341,20 @@ QList<QAction*> QgsOracleLayerItem::actions()
void QgsOracleLayerItem::deleteLayer()
{
+ if ( QMessageBox::question( nullptr, QObject::tr( "Delete Table" ),
+ QObject::tr( "Are you sure you want to delete %1.%2?" ).arg( mLayerProperty.ownerName, mLayerProperty.tableName ),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
+ return;
+
QString errCause;
bool res = ::deleteLayer( mUri, errCause );
if ( !res )
{
- QMessageBox::warning( 0, tr( "Delete layer" ), errCause );
+ QMessageBox::warning( 0, tr( "Delete Table" ), errCause );
}
else
{
- QMessageBox::information( 0, tr( "Delete layer" ), tr( "Layer deleted successfully." ) );
+ QMessageBox::information( 0, tr( "Delete Table" ), tr( "Table deleted successfully." ) );
deleteLater();
}
}
@@ -330,10 +373,10 @@ QString QgsOracleLayerItem::createUri()
QgsDataSourceURI uri = QgsOracleConn::connUri( connItem->name() );
uri.setDataSource( mLayerProperty.ownerName, mLayerProperty.tableName, mLayerProperty.geometryColName, mLayerProperty.sql, QString::null );
uri.setSrid( QString::number( mLayerProperty.srids.at( 0 ) ) );
- uri.setWkbType( mLayerProperty.types.at( 0 ) );
+ uri.setWkbType( QGis::fromOldWkbType( mLayerProperty.types.at( 0 ) ) );
if ( mLayerProperty.isView && mLayerProperty.pkCols.size() > 0 )
uri.setKeyColumn( mLayerProperty.pkCols[0] );
- QgsDebugMsg( QString( "layer uri: %1" ).arg( uri.uri() ) );
+ QgsDebugMsgLevel( QString( "layer uri: %1" ).arg( uri.uri() ), 3 );
return uri.uri();
}
@@ -342,11 +385,13 @@ QgsOracleOwnerItem::QgsOracleOwnerItem( QgsDataItem* parent, QString name, QStri
: QgsDataCollectionItem( parent, name, path )
{
mIconName = "mIconDbOwner.png";
+ //not fertile, since children are created by QgsOracleConnectionItem
+ mCapabilities &= ~( Fertile );
}
QVector<QgsDataItem*> QgsOracleOwnerItem::createChildren()
{
- QgsDebugMsg( "Entering." );
+ QgsDebugMsgLevel( "Entering.", 3 );
return QVector<QgsDataItem*>();
}
@@ -356,7 +401,7 @@ QgsOracleOwnerItem::~QgsOracleOwnerItem()
void QgsOracleOwnerItem::addLayer( QgsOracleLayerProperty layerProperty )
{
- QgsDebugMsg( layerProperty.toString() );
+ QgsDebugMsgLevel( layerProperty.toString(), 3 );
Q_ASSERT( layerProperty.size() == 1 );
QGis::WkbType wkbType = layerProperty.types.at( 0 );
diff --git a/src/providers/oracle/qgsoracledataitems.h b/src/providers/oracle/qgsoracledataitems.h
index 00e5d7f..999039d 100644
--- a/src/providers/oracle/qgsoracledataitems.h
+++ b/src/providers/oracle/qgsoracledataitems.h
@@ -85,6 +85,7 @@ class QgsOracleConnectionItem : public QgsDataCollectionItem
void stop();
QMap<QString, QgsOracleOwnerItem * > mOwnerMap;
QgsOracleColumnTypeThread *mColumnTypeThread;
+ void setAllAsPopulated();
};
class QgsOracleOwnerItem : public QgsDataCollectionItem
diff --git a/src/providers/oracle/qgsoracleexpressioncompiler.cpp b/src/providers/oracle/qgsoracleexpressioncompiler.cpp
index 88453bf..3b7ffa9 100644
--- a/src/providers/oracle/qgsoracleexpressioncompiler.cpp
+++ b/src/providers/oracle/qgsoracleexpressioncompiler.cpp
@@ -29,8 +29,15 @@ QgsSqlExpressionCompiler::Result QgsOracleExpressionCompiler::compileNode( const
switch ( bin->op() )
{
+ case QgsExpression::boConcat:
+ // oracle's handling of || WRT null is not standards compliant
+ return Fail;
+
case QgsExpression::boPow:
case QgsExpression::boRegexp:
+ case QgsExpression::boILike:
+ case QgsExpression::boNotILike:
+ case QgsExpression::boMod:
{
QString op1, op2;
@@ -48,6 +55,18 @@ QgsSqlExpressionCompiler::Result QgsOracleExpressionCompiler::compileNode( const
result = QString( "regexp_like(%1,%2)" ).arg( op1, op2 );
return Complete;
+ case QgsExpression::boILike:
+ result = QString( "lower(%1) LIKE lower(%2)" ).arg( op1, op2 );
+ return Complete;
+
+ case QgsExpression::boNotILike:
+ result = QString( "NOT lower(%1) LIKE lower(%2)" ).arg( op1, op2 );
+ return Complete;
+
+ case QgsExpression::boMod :
+ result = QString( "MOD(%1,%2)" ).arg( op1, op2 );
+ return Complete;
+
default:
break;
}
@@ -70,5 +89,14 @@ QString QgsOracleExpressionCompiler::quotedIdentifier( const QString& identifier
QString QgsOracleExpressionCompiler::quotedValue( const QVariant& value, bool& ok )
{
ok = true;
- return QgsOracleConn::quotedValue( value );
+
+ switch ( value.type() )
+ {
+ case QVariant::Bool:
+ //no boolean literal support in Oracle, so fake it
+ return value.toBool() ? "(1=1)" : "(1=0)";
+
+ default:
+ return QgsOracleConn::quotedValue( value );
+ }
}
diff --git a/src/providers/oracle/qgsoraclefeatureiterator.cpp b/src/providers/oracle/qgsoraclefeatureiterator.cpp
index f3ae92e..e2c79df 100644
--- a/src/providers/oracle/qgsoraclefeatureiterator.cpp
+++ b/src/providers/oracle/qgsoraclefeatureiterator.cpp
@@ -61,7 +61,7 @@ QgsOracleFeatureIterator::QgsOracleFeatureIterator( QgsOracleFeatureSource* sour
else
mAttributeList = mSource->mFields.allAttributesList();
-
+ bool limitAtProvider = ( mRequest.limit() >= 0 );
QString whereClause;
if ( !mSource->mGeometryColumn.isNull() )
@@ -139,15 +139,7 @@ QgsOracleFeatureIterator::QgsOracleFeatureIterator( QgsOracleFeatureSource* sour
break;
case QgsFeatureRequest::FilterExpression:
- if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
- {
- QgsOracleExpressionCompiler compiler( mSource );
- if ( compiler.compile( mRequest.filterExpression() ) == QgsSqlExpressionCompiler::Complete )
- {
- whereClause = QgsOracleUtils::andWhereClauses( whereClause, compiler.result() );
- mExpressionCompiled = true;
- }
- }
+ //handled below
break;
case QgsFeatureRequest::FilterRect:
@@ -163,22 +155,67 @@ QgsOracleFeatureIterator::QgsOracleFeatureIterator( QgsOracleFeatureSource* sour
whereClause += QgsOracleConn::databaseTypeFilter( "FEATUREREQUEST", mSource->mGeometryColumn, mSource->mRequestedGeomType );
}
- if ( mRequest.limit() >= 0 )
+ if ( !mSource->mSqlWhereClause.isEmpty() )
{
if ( !whereClause.isEmpty() )
whereClause += " AND ";
+ whereClause += "(" + mSource->mSqlWhereClause + ")";
+ }
- whereClause += QString( "rownum<=%1" ).arg( mRequest.limit() );
+ //NOTE - must be last added!
+ mExpressionCompiled = false;
+ QString fallbackStatement;
+ bool useFallback = false;
+ if ( request.filterType() == QgsFeatureRequest::FilterExpression )
+ {
+ if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
+ {
+ QgsOracleExpressionCompiler compiler( mSource );
+ QgsSqlExpressionCompiler::Result result = compiler.compile( mRequest.filterExpression() );
+ if ( result == QgsSqlExpressionCompiler::Complete || result == QgsSqlExpressionCompiler::Partial )
+ {
+ fallbackStatement = whereClause;
+ useFallback = true;
+ whereClause = QgsOracleUtils::andWhereClauses( whereClause, compiler.result() );
+
+ //if only partial success when compiling expression, we need to double-check results using QGIS' expressions
+ mExpressionCompiled = ( result == QgsSqlExpressionCompiler::Complete );
+ limitAtProvider = mExpressionCompiled;
+ }
+ else
+ {
+ limitAtProvider = false;
+ }
+ }
+ else
+ {
+ limitAtProvider = false;
+ }
}
- if ( !mSource->mSqlWhereClause.isEmpty() )
+ if ( !mRequest.orderBy().isEmpty() )
+ {
+ limitAtProvider = false;
+ }
+
+ if ( mRequest.limit() >= 0 && limitAtProvider )
{
if ( !whereClause.isEmpty() )
whereClause += " AND ";
- whereClause += "(" + mSource->mSqlWhereClause + ")";
+
+ whereClause += QString( "rownum<=%1" ).arg( mRequest.limit() );
+ fallbackStatement += QString( "rownum<=%1" ).arg( mRequest.limit() );
}
- openQuery( whereClause );
+ bool result = openQuery( whereClause, !useFallback );
+ if ( !result && useFallback )
+ {
+ result = openQuery( fallbackStatement );
+ if ( result )
+ {
+ mExpressionCompiled = false;
+ }
+ }
}
QgsOracleFeatureIterator::~QgsOracleFeatureIterator()
@@ -209,10 +246,16 @@ bool QgsOracleFeatureIterator::fetchFeature( QgsFeature& feature )
if ( mRewind )
{
mRewind = false;
- if ( !mQry.first() )
- return true;
+ if ( !QgsOracleProvider::exec( mQry, mSql ) )
+ {
+ QgsMessageLog::logMessage( QObject::tr( "Fetching features failed.\nSQL:%1\nError: %2" )
+ .arg( mQry.lastQuery() )
+ .arg( mQry.lastError().text() ),
+ QObject::tr( "Oracle" ) );
+ return false;
+ }
}
- else if ( !mQry.next() )
+ if ( !mQry.next() )
{
return false;
}
@@ -269,12 +312,6 @@ bool QgsOracleFeatureIterator::fetchFeature( QgsFeature& feature )
}
}
}
-
- if (( mRequest.flags() & QgsFeatureRequest::NoGeometry ) != 0 )
- {
- // clear not requested geometry
- feature.setGeometry( 0 );
- }
}
QgsFeatureId fid = 0;
@@ -392,7 +429,7 @@ bool QgsOracleFeatureIterator::close()
return true;
}
-bool QgsOracleFeatureIterator::openQuery( QString whereClause )
+bool QgsOracleFeatureIterator::openQuery( QString whereClause, bool showLog )
{
try
{
@@ -444,12 +481,16 @@ bool QgsOracleFeatureIterator::openQuery( QString whereClause )
query += QString( " WHERE %1" ).arg( whereClause );
QgsDebugMsg( QString( "Fetch features: %1" ).arg( query ) );
+ mSql = query;
if ( !QgsOracleProvider::exec( mQry, query ) )
{
- QgsMessageLog::logMessage( QObject::tr( "Fetching features failed.\nSQL:%1\nError: %2" )
- .arg( mQry.lastQuery() )
- .arg( mQry.lastError().text() ),
- QObject::tr( "Oracle" ) );
+ if ( showLog )
+ {
+ QgsMessageLog::logMessage( QObject::tr( "Fetching features failed.\nSQL:%1\nError: %2" )
+ .arg( mQry.lastQuery() )
+ .arg( mQry.lastError().text() ),
+ QObject::tr( "Oracle" ) );
+ }
return false;
}
}
diff --git a/src/providers/oracle/qgsoraclefeatureiterator.h b/src/providers/oracle/qgsoraclefeatureiterator.h
index 8c3430a..ce8901f 100644
--- a/src/providers/oracle/qgsoraclefeatureiterator.h
+++ b/src/providers/oracle/qgsoraclefeatureiterator.h
@@ -76,7 +76,7 @@ class QgsOracleFeatureIterator : public QgsAbstractFeatureIteratorFromSource<Qgs
//! fetch next feature filter expression
bool nextFeatureFilterExpression( QgsFeature& f ) override;
- bool openQuery( QString whereClause );
+ bool openQuery( QString whereClause, bool showLog = true );
QgsOracleConn *mConnection;
QSqlQuery mQry;
@@ -84,6 +84,7 @@ class QgsOracleFeatureIterator : public QgsAbstractFeatureIteratorFromSource<Qgs
bool mExpressionCompiled;
bool mFetchGeometry;
QgsAttributeList mAttributeList;
+ QString mSql;
};
#endif // QGSORACLEFEATUREITERATOR_H
diff --git a/src/providers/oracle/qgsoraclenewconnection.cpp b/src/providers/oracle/qgsoraclenewconnection.cpp
index 0443cf5..7d53331 100644
--- a/src/providers/oracle/qgsoraclenewconnection.cpp
+++ b/src/providers/oracle/qgsoraclenewconnection.cpp
@@ -51,6 +51,7 @@ QgsOracleNewConnection::QgsOracleNewConnection( QWidget *parent, const QString&
cb_allowGeometrylessTables->setChecked( settings.value( key + "/allowGeometrylessTables", false ).toBool() );
cb_useEstimatedMetadata->setChecked( settings.value( key + "/estimatedMetadata", false ).toBool() );
cb_onlyExistingTypes->setChecked( settings.value( key + "/onlyExistingTypes", true ).toBool() );
+ cb_includeGeoAttributes->setChecked( settings.value( key + "/includeGeoAttributes", false ).toBool() );
if ( settings.value( key + "/saveUsername" ).toString() == "true" )
{
@@ -125,6 +126,7 @@ void QgsOracleNewConnection::accept()
settings.setValue( baseKey + "/allowGeometrylessTables", cb_allowGeometrylessTables->isChecked() );
settings.setValue( baseKey + "/estimatedMetadata", cb_useEstimatedMetadata->isChecked() ? "true" : "false" );
settings.setValue( baseKey + "/onlyExistingTypes", cb_onlyExistingTypes->isChecked() ? "true" : "false" );
+ settings.setValue( baseKey + "/includeGeoAttributes", cb_includeGeoAttributes->isChecked() ? "true" : "false" );
settings.setValue( baseKey + "/saveUsername", chkStoreUsername->isChecked() ? "true" : "false" );
settings.setValue( baseKey + "/savePassword", chkStorePassword->isChecked() ? "true" : "false" );
settings.setValue( baseKey + "/dboptions", txtOptions->text() );
diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp
index fbdb0eb..61e8b5e 100644
--- a/src/providers/oracle/qgsoracleprovider.cpp
+++ b/src/providers/oracle/qgsoracleprovider.cpp
@@ -69,6 +69,7 @@ QgsOracleProvider::QgsOracleProvider( QString const & uri )
mSrid = mUri.srid().toInt();
mRequestedGeomType = mUri.wkbType();
mUseEstimatedMetadata = mUri.useEstimatedMetadata();
+ mIncludeGeoAttributes = mUri.hasParam( "includegeoattributes" ) ? mUri.param( "includegeoattributes" ) == "true" : false;
mConnection = QgsOracleConn::connectDb( mUri.connectionInfo() );
if ( !mConnection )
@@ -157,6 +158,10 @@ QgsOracleProvider::QgsOracleProvider( QString const & uri )
<< QgsVectorDataProvider::NativeType( tr( "Text, fixed length (char)" ), "CHAR", QVariant::String, 1, 255 )
<< QgsVectorDataProvider::NativeType( tr( "Text, limited variable length (varchar2)" ), "VARCHAR2", QVariant::String, 1, 255 )
<< QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (long)" ), "LONG", QVariant::String )
+
+ // date type
+ << QgsVectorDataProvider::NativeType( tr( "Date" ), "DATE", QVariant::Date, 38, 38, 0, 0 )
+ << QgsVectorDataProvider::NativeType( tr( "Date & Time" ), "TIMESTAMP(6)", QVariant::DateTime, 38, 38, 6, 6 )
;
QString key;
@@ -602,11 +607,12 @@ bool QgsOracleProvider::loadFields()
",t.char_used"
",t.data_default"
" FROM all_tab_columns t"
- " WHERE t.owner=%1 AND t.table_name=%2%3"
+ " WHERE t.owner=%1 AND t.table_name=%2%3%4"
" ORDER BY t.column_id" )
.arg( quotedValue( mOwnerName ) )
.arg( quotedValue( mTableName ) )
.arg( mGeometryColumn.isEmpty() ? "" : QString( " AND t.column_name<>%1 " ).arg( quotedValue( mGeometryColumn ) ) )
+ .arg( mIncludeGeoAttributes ? "" : " AND (t.data_type_owner<>'MDSYS' OR t.data_type<>'SDO_GEOMETRY')" )
) )
{
while ( qry.next() )
@@ -693,29 +699,31 @@ bool QgsOracleProvider::loadFields()
.arg( qry.lastError().text() ),
tr( "Oracle" ) );
}
-
- if ( !mHasSpatialIndex )
- {
- mHasSpatialIndex = qry.exec( QString( "SELECT %2 FROM %1 WHERE sdo_filter(%2,mdsys.sdo_geometry(2003,%3,NULL,mdsys.sdo_elem_info_array(1,1003,3),mdsys.sdo_ordinate_array(-1,-1,1,1)))='TRUE'" )
- .arg( mQuery )
- .arg( quotedIdentifier( mGeometryColumn ) )
- .arg( mSrid < 1 ? "NULL" : QString::number( mSrid ) ) );
- if ( !mHasSpatialIndex )
- {
- QgsMessageLog::logMessage( tr( "No spatial index on column %1.%2.%3 found - expect poor performance." )
- .arg( mOwnerName )
- .arg( mTableName )
- .arg( mGeometryColumn ),
- tr( "Oracle" ) );
- }
- }
}
- qry.finish();
-
mEnabledCapabilities |= QgsVectorDataProvider::CreateSpatialIndex;
}
+ if ( !mGeometryColumn.isEmpty() )
+ {
+ if ( !mHasSpatialIndex )
+ {
+ mHasSpatialIndex = qry.exec( QString( "SELECT %2 FROM %1 WHERE sdo_filter(%2,mdsys.sdo_geometry(2003,%3,NULL,mdsys.sdo_elem_info_array(1,1003,3),mdsys.sdo_ordinate_array(-1,-1,1,1)))='TRUE'" )
+ .arg( mQuery )
+ .arg( quotedIdentifier( mGeometryColumn ) )
+ .arg( mSrid < 1 ? "NULL" : QString::number( mSrid ) ) );
+ }
+
+ if ( !mHasSpatialIndex )
+ {
+ QgsMessageLog::logMessage( tr( "No spatial index on column %1 found - expect poor performance." )
+ .arg( mGeometryColumn ),
+ tr( "Oracle" ) );
+ }
+ }
+
+ qry.finish();
+
if ( !exec( qry, QString( "SELECT * FROM %1 WHERE 1=0" ).arg( mQuery ) ) )
{
QgsMessageLog::logMessage( tr( "Retrieving fields from '%1' failed [%2]" ).arg( mQuery ).arg( qry.lastError().text() ), tr( "Oracle" ) );
@@ -734,7 +742,15 @@ bool QgsOracleProvider::loadFields()
if ( !mIsQuery && !types.contains( field.name() ) )
continue;
- mAttributeFields.append( QgsField( field.name(), field.type(), types.value( field.name() ), field.length(), field.precision(), comments.value( field.name() ) ) );
+ QVariant::Type type = field.type();
+
+ if ( types.value( field.name() ) == "DATE" )
+ {
+ // date types are incorrectly detected as datetime
+ type = QVariant::Date;
+ }
+
+ mAttributeFields.append( QgsField( field.name(), type, types.value( field.name() ), field.length(), field.precision(), comments.value( field.name() ) ) );
mDefaultValues.append( defvalues.value( field.name(), QVariant() ) );
}
@@ -816,7 +832,6 @@ bool QgsOracleProvider::hasSufficientPermsAndCapabilities()
.arg( qry.lastQuery() ),
tr( "Oracle" ) );
}
-
}
else
{
@@ -2056,38 +2071,40 @@ QgsRectangle QgsOracleProvider::extent()
{
QString sql;
QSqlQuery qry( *mConnection );
+ bool ok = false;
- if ( mUseEstimatedMetadata )
+ if ( !mIsQuery )
{
- if ( exec( qry, QString( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=%1 AND table_name=%2 AND column_name=%3 AND sdo_dimname='X'" )
- .arg( quotedValue( mOwnerName ) )
- .arg( quotedValue( mTableName ) )
- .arg( quotedValue( mGeometryColumn ) ) ) && qry.next() )
+ if ( mUseEstimatedMetadata )
{
- mLayerExtent.setXMinimum( qry.value( 0 ).toDouble() );
- mLayerExtent.setXMaximum( qry.value( 1 ).toDouble() );
-
- if ( exec( qry, QString( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=%1 AND table_name=%2 AND column_name=%3 AND sdo_dimname='Y'" )
+ if ( exec( qry, QString( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=%1 AND table_name=%2 AND column_name=%3 AND sdo_dimname='X'" )
.arg( quotedValue( mOwnerName ) )
.arg( quotedValue( mTableName ) )
- .arg( quotedValue( mGeometryColumn ) ) ) && qry.next() )
+ .arg( quotedValue( mGeometryColumn ) ) ) && qry.next() )
{
- mLayerExtent.setYMinimum( qry.value( 0 ).toDouble() );
- mLayerExtent.setYMaximum( qry.value( 1 ).toDouble() );
- return mLayerExtent;
+ mLayerExtent.setXMinimum( qry.value( 0 ).toDouble() );
+ mLayerExtent.setXMaximum( qry.value( 1 ).toDouble() );
+
+ if ( exec( qry, QString( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=%1 AND table_name=%2 AND column_name=%3 AND sdo_dimname='Y'" )
+ .arg( quotedValue( mOwnerName ) )
+ .arg( quotedValue( mTableName ) )
+ .arg( quotedValue( mGeometryColumn ) ) ) && qry.next() )
+ {
+ mLayerExtent.setYMinimum( qry.value( 0 ).toDouble() );
+ mLayerExtent.setYMaximum( qry.value( 1 ).toDouble() );
+ return mLayerExtent;
+ }
}
}
- }
-
- bool ok = false;
- if ( mHasSpatialIndex && ( mUseEstimatedMetadata || mSqlWhereClause.isEmpty() ) )
- {
- sql = QString( "SELECT SDO_TUNE.EXTENT_OF(%1,%2) FROM dual" )
- .arg( quotedValue( QString( "%1.%2" ).arg( mOwnerName ).arg( mTableName ) ) )
- .arg( quotedValue( mGeometryColumn ) );
+ if ( mHasSpatialIndex && ( mUseEstimatedMetadata || mSqlWhereClause.isEmpty() ) )
+ {
+ sql = QString( "SELECT SDO_TUNE.EXTENT_OF(%1,%2) FROM dual" )
+ .arg( quotedValue( QString( "%1.%2" ).arg( mOwnerName ).arg( mTableName ) ) )
+ .arg( quotedValue( mGeometryColumn ) );
- ok = exec( qry, sql );
+ ok = exec( qry, sql );
+ }
}
if ( !ok )
@@ -2191,8 +2208,8 @@ bool QgsOracleProvider::getGeometryDetails()
}
if ( exec( qry, QString( mUseEstimatedMetadata
- ? "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE rownum<1000) WHERE rownum<=2"
- : "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE rownum<=2" ).arg( quotedIdentifier( geomCol ) ).arg( mQuery ) ) )
+ ? "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<1000) WHERE rownum<=2"
+ : "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<=2" ).arg( quotedIdentifier( geomCol ) ).arg( mQuery ) ) )
{
if ( qry.next() )
{
@@ -2421,6 +2438,11 @@ bool QgsOracleProvider::convertField( QgsField &field )
break;
case QVariant::DateTime:
+ fieldType = "TIMESTAMP";
+ fieldPrec = -1;
+ break;
+
+
case QVariant::Time:
case QVariant::String:
fieldType = "VARCHAR2(2047)";
diff --git a/src/providers/oracle/qgsoracleprovider.h b/src/providers/oracle/qgsoracleprovider.h
index cfc6fd7..89015c0 100644
--- a/src/providers/oracle/qgsoracleprovider.h
+++ b/src/providers/oracle/qgsoracleprovider.h
@@ -348,6 +348,9 @@ class QgsOracleProvider : public QgsVectorDataProvider
/* Use estimated metadata. Uses fast table counts, geometry type and extent determination */
bool mUseEstimatedMetadata;
+ /* Include additional geo attributes */
+ bool mIncludeGeoAttributes;
+
struct OracleFieldNotFound {}; //! Exception to throw
struct OracleException
diff --git a/src/providers/postgres/qgspostgresconn.cpp b/src/providers/postgres/qgspostgresconn.cpp
index 6e5ff4e..65bc445 100644
--- a/src/providers/postgres/qgspostgresconn.cpp
+++ b/src/providers/postgres/qgspostgresconn.cpp
@@ -290,12 +290,21 @@ QgsPostgresConn::QgsPostgresConn( const QString& conninfo, bool readOnly, bool s
deduceEndian();
/* Check to see if we have working PostGIS support */
- if ( postgisVersion().isNull() )
+ if ( !postgisVersion().isNull() )
{
- QgsMessageLog::logMessage( tr( "Your database has no working PostGIS support." ), tr( "PostGIS" ) );
- PQfinish();
- mRef = 0;
- return;
+ /* Check to see if we have GEOS support and if not, warn the user about
+ the problems they will see :) */
+ QgsDebugMsg( "Checking for GEOS support" );
+
+ if ( !hasGEOS() )
+ {
+ QgsMessageLog::logMessage( tr( "Your PostGIS installation has no GEOS support. Feature selection and identification will not work properly. Please install PostGIS with GEOS support (http://geos.refractions.net)" ), tr( "PostGIS" ) );
+ }
+
+ if ( hasTopology() )
+ {
+ QgsDebugMsg( "Topology support available!" );
+ }
}
if ( mPostgresqlVersion >= 90000 )
@@ -303,19 +312,6 @@ QgsPostgresConn::QgsPostgresConn( const QString& conninfo, bool readOnly, bool s
PQexecNR( "SET application_name='QGIS'" );
}
- /* Check to see if we have GEOS support and if not, warn the user about
- the problems they will see :) */
- QgsDebugMsg( "Checking for GEOS support" );
-
- if ( !hasGEOS() )
- {
- QgsMessageLog::logMessage( tr( "Your PostGIS installation has no GEOS support. Feature selection and identification will not work properly. Please install PostGIS with GEOS support (http://geos.refractions.net)" ), tr( "PostGIS" ) );
- }
-
- if ( hasTopology() )
- {
- QgsDebugMsg( "Topology support available!" );
- }
PQsetNoticeProcessor( mConn, noticeProcessor, nullptr );
}
@@ -836,10 +832,11 @@ QString QgsPostgresConn::postgisVersion()
mPostgresqlVersion = PQserverVersion( mConn );
- QgsPostgresResult result( PQexec( "SELECT postgis_version()" ) );
+ QgsPostgresResult result( PQexec( "SELECT postgis_version()", false ) );
if ( result.PQntuples() != 1 )
{
- QgsMessageLog::logMessage( tr( "Retrieval of postgis version failed" ), tr( "PostGIS" ) );
+ QgsMessageLog::logMessage( tr( "No PostGIS support in the database." ), tr( "PostGIS" ) );
+ mGotPostgisVersion = true;
return QString::null;
}
diff --git a/src/providers/postgres/qgspostgresfeatureiterator.cpp b/src/providers/postgres/qgspostgresfeatureiterator.cpp
index c66eaf3..ddf2060 100644
--- a/src/providers/postgres/qgspostgresfeatureiterator.cpp
+++ b/src/providers/postgres/qgspostgresfeatureiterator.cpp
@@ -685,7 +685,7 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
QgsFeatureId fid = 0;
bool subsetOfAttributes = mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes;
- const QgsAttributeList& fetchAttributes = mRequest.subsetOfAttributes();
+ QgsAttributeList fetchAttributes = mRequest.subsetOfAttributes();
switch ( mSource->mPrimaryKeyType )
{
diff --git a/src/providers/spatialite/qgsspatialiteconnection.h b/src/providers/spatialite/qgsspatialiteconnection.h
index e951ef6..d6023a8 100644
--- a/src/providers/spatialite/qgsspatialiteconnection.h
+++ b/src/providers/spatialite/qgsspatialiteconnection.h
@@ -131,7 +131,10 @@ class QgsSqliteHandle
//
public:
QgsSqliteHandle( sqlite3 * handle, const QString& dbPath, bool shared )
- : ref( shared ? 1 : -1 ), sqlite_handle( handle ), mDbPath( dbPath )
+ : ref( shared ? 1 : -1 )
+ , sqlite_handle( handle )
+ , mDbPath( dbPath )
+ , mIsValid( true )
{
}
@@ -145,6 +148,16 @@ class QgsSqliteHandle
return mDbPath;
}
+ bool isValid() const
+ {
+ return mIsValid;
+ }
+
+ void invalidate()
+ {
+ mIsValid = false;
+ }
+
//
// libsqlite3 wrapper
//
@@ -165,6 +178,7 @@ class QgsSqliteHandle
int ref;
sqlite3 *sqlite_handle;
QString mDbPath;
+ bool mIsValid;
static QMap < QString, QgsSqliteHandle * > handles;
};
diff --git a/src/providers/spatialite/qgsspatialiteconnpool.h b/src/providers/spatialite/qgsspatialiteconnpool.h
index d66eea1..6f37fec 100644
--- a/src/providers/spatialite/qgsspatialiteconnpool.h
+++ b/src/providers/spatialite/qgsspatialiteconnpool.h
@@ -36,13 +36,15 @@ inline void qgsConnectionPool_ConnectionDestroy( QgsSqliteHandle* c )
inline void qgsConnectionPool_InvalidateConnection( QgsSqliteHandle* c )
{
- Q_UNUSED( c );
+ /* Invalidation is used in particular by the WFS provider that uses a */
+ /* temporary spatialite DB and want to delete it at some point. For that */
+ /* it must invalidate all handles pointing to it */
+ c->invalidate();
}
inline bool qgsConnectionPool_ConnectionIsValid( QgsSqliteHandle* c )
{
- Q_UNUSED( c );
- return true;
+ return c->isValid();
}
diff --git a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp
index d2b5b79..bfa28f1 100644
--- a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp
+++ b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp
@@ -291,7 +291,7 @@ bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString& whereClause,
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
{
- const QgsAttributeList& fetchAttributes = mRequest.subsetOfAttributes();
+ QgsAttributeList fetchAttributes = mRequest.subsetOfAttributes();
for ( QgsAttributeList::const_iterator it = fetchAttributes.constBegin(); it != fetchAttributes.constEnd(); ++it )
{
sql += ',' + fieldName( mSource->mFields.field( *it ) );
diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp
index 149a18f..5156225 100644
--- a/src/providers/spatialite/qgsspatialiteprovider.cpp
+++ b/src/providers/spatialite/qgsspatialiteprovider.cpp
@@ -568,6 +568,11 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri )
return;
}
+ if ( mTableBased && hasRowid() )
+ {
+ mPrimaryKey = "ROWID";
+ }
+
// retrieve version information
spatialiteVersion();
@@ -584,6 +589,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri )
QgsSpatiaLiteProvider::~QgsSpatiaLiteProvider()
{
closeDb();
+ invalidateConnections( mSqlitePath );
}
QgsAbstractFeatureSource* QgsSpatiaLiteProvider::featureSource() const
@@ -649,9 +655,6 @@ void QgsSpatiaLiteProvider::loadFieldsAbstractInterface( gaiaVectorLayerPtr lyr
fld = fld->Next;
}
- mPrimaryKey.clear();
- mPrimaryKeyAttrs.clear();
-
QString sql = QString( "PRAGMA table_info(%1)" ).arg( quotedIdentifier( mTableName ) );
char **results;
@@ -668,8 +671,10 @@ void QgsSpatiaLiteProvider::loadFieldsAbstractInterface( gaiaVectorLayerPtr lyr
if ( pk.toInt() == 0 )
continue;
- if ( mPrimaryKey.isEmpty() )
+ if ( mPrimaryKeyAttrs.isEmpty() )
mPrimaryKey = name;
+ else
+ mPrimaryKey.clear();
mPrimaryKeyAttrs << i - 1;
}
}
@@ -766,7 +771,10 @@ void QgsSpatiaLiteProvider::loadFields()
{
// found a Primary Key column
pkCount++;
- pkName = name;
+ if ( mPrimaryKeyAttrs.isEmpty() )
+ pkName = name;
+ else
+ pkName.clear();
mPrimaryKeyAttrs << i - 1;
QgsDebugMsg( "found primaryKey " + name );
}
@@ -841,7 +849,10 @@ void QgsSpatiaLiteProvider::loadFields()
if ( name == mPrimaryKey )
{
pkCount++;
- pkName = name;
+ if ( mPrimaryKeyAttrs.isEmpty() )
+ pkName = name;
+ else
+ pkName.clear();
mPrimaryKeyAttrs << i - 1;
QgsDebugMsg( "found primaryKey " + name );
}
@@ -939,6 +950,17 @@ bool QgsSpatiaLiteProvider::hasTriggers()
return ( ret == SQLITE_OK && rows > 0 );
}
+bool QgsSpatiaLiteProvider::hasRowid()
+{
+ if ( attributeFields.fieldNameIndex( "ROWID" ) >= 0 )
+ return false;
+
+ // table without rowid column
+ QString sql = QString( "SELECT rowid FROM %1 WHERE 0" ).arg( quotedIdentifier( mTableName ) );
+ char *errMsg = nullptr;
+ return sqlite3_exec( sqliteHandle, sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK;
+}
+
QString QgsSpatiaLiteProvider::storageType() const
{
diff --git a/src/providers/spatialite/qgsspatialiteprovider.h b/src/providers/spatialite/qgsspatialiteprovider.h
index 1b9929f..6973120 100644
--- a/src/providers/spatialite/qgsspatialiteprovider.h
+++ b/src/providers/spatialite/qgsspatialiteprovider.h
@@ -266,6 +266,9 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider
/** Check if a table/view has any triggers. Triggers can be used on views to make them editable.*/
bool hasTriggers();
+ //! Check if a table has a row id (internal primary key)
+ bool hasRowid();
+
/** Convert a QgsField to work with SL */
static bool convertField( QgsField &field );
diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp
index 9df9a3f..a39354c 100644
--- a/src/providers/wms/qgswmsprovider.cpp
+++ b/src/providers/wms/qgswmsprovider.cpp
@@ -2952,20 +2952,29 @@ QUrl QgsWmsProvider::getLegendGraphicFullURL( double scale, const QgsRectangle&
QUrl url( lurl );
- if ( !url.hasQueryItem( "SERVICE" ) )
+ // query names are NOT case-sensitive, so make an uppercase list for proper comparison
+ QStringList qnames = QStringList();
+ for ( int i = 0; i < url.queryItems().size(); i++ )
+ {
+ qnames << url.queryItems().at( i ).first.toUpper();
+ }
+ if ( !qnames.contains( "SERVICE" ) )
setQueryItem( url, "SERVICE", "WMS" );
- if ( !url.hasQueryItem( "VERSION" ) )
+ if ( !qnames.contains( "VERSION" ) )
setQueryItem( url, "VERSION", mCaps.mCapabilities.version );
- if ( !url.hasQueryItem( "SLD_VERSION" ) )
+ if ( !qnames.contains( "SLD_VERSION" ) )
setQueryItem( url, "SLD_VERSION", "1.1.0" ); // can not determine SLD_VERSION
- if ( !url.hasQueryItem( "REQUEST" ) )
+ if ( !qnames.contains( "REQUEST" ) )
setQueryItem( url, "REQUEST", "GetLegendGraphic" );
- if ( !url.hasQueryItem( "FORMAT" ) )
+ if ( !qnames.contains( "FORMAT" ) )
setFormatQueryItem( url );
- if ( !url.hasQueryItem( "LAYER" ) )
+ if ( !qnames.contains( "LAYER" ) )
setQueryItem( url, "LAYER", mSettings.mActiveSubLayers[0] );
- if ( !url.hasQueryItem( "STYLE" ) )
+ if ( !qnames.contains( "STYLE" ) )
setQueryItem( url, "STYLE", mSettings.mActiveSubStyles[0] );
+ // by setting TRANSPARENT=true, even too big legend images will look good
+ if ( !qnames.contains( "TRANSPARENT" ) )
+ setQueryItem( url, "TRANSPARENT", "true" );
// add config parameter related to resolution
QSettings s;
@@ -3605,14 +3614,24 @@ void QgsWmsTiledImageDownloadHandler::repeatTileRequest( QNetworkRequest const &
connect( reply, SIGNAL( finished() ), this, SLOT( tileReplyFinished() ) );
}
+// Some servers like http://glogow.geoportal2.pl/map/wms/wms.php? do not BBOX
+// to be formatted with excessive precision. As a double is exactly represented
+// with 19 decimal figures, do not attempt to output more
+static QString formatDouble( double x )
+{
+ if ( x == 0.0 )
+ return "0";
+ const int numberOfDecimals = qMax( 0, 19 - static_cast<int>( ceil( log10( fabs( x ) ) ) ) );
+ return qgsDoubleToString( x, numberOfDecimals );
+}
+
QString QgsWmsProvider::toParamValue( const QgsRectangle& rect, bool changeXY )
{
- // Warning: does not work with scientific notation
return QString( changeXY ? "%2,%1,%4,%3" : "%1,%2,%3,%4" )
- .arg( qgsDoubleToString( rect.xMinimum() ),
- qgsDoubleToString( rect.yMinimum() ),
- qgsDoubleToString( rect.xMaximum() ),
- qgsDoubleToString( rect.yMaximum() ) );
+ .arg( formatDouble( rect.xMinimum() ),
+ formatDouble( rect.yMinimum() ),
+ formatDouble( rect.xMaximum() ),
+ formatDouble( rect.yMaximum() ) );
}
void QgsWmsProvider::setSRSQueryItem( QUrl& url )
diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp
index 1c8f5ad..5ac0352 100644
--- a/src/python/qgspythonutilsimpl.cpp
+++ b/src/python/qgspythonutilsimpl.cpp
@@ -300,7 +300,7 @@ bool QgsPythonUtilsImpl::runStringUnsafe( const QString& command, bool single )
// (non-unicode strings can be mangled)
PyObject* obj = PyRun_String( command.toUtf8().data(), single ? Py_single_input : Py_file_input, mMainDict, mMainDict );
bool res = nullptr == PyErr_Occurred();
- Py_DECREF( obj );
+ Py_XDECREF( obj );
// we are done calling python API, release global interpreter lock
PyGILState_Release( gstate );
diff --git a/src/server/qgsmslayercache.cpp b/src/server/qgsmslayercache.cpp
index 363f747..fe01c26 100644
--- a/src/server/qgsmslayercache.cpp
+++ b/src/server/qgsmslayercache.cpp
@@ -17,6 +17,8 @@
#include "qgsmslayercache.h"
#include "qgsmessagelog.h"
+#include "qgsmaplayerregistry.h"
+#include "qgsmaplayer.h"
#include "qgsvectorlayer.h"
#include "qgslogger.h"
#include <QFile>
@@ -187,6 +189,10 @@ void QgsMSLayerCache::removeLeastUsedEntry()
void QgsMSLayerCache::freeEntryRessources( QgsMSLayerCacheEntry& entry )
{
+ // remove layer from QgsMapLayerRegistry before delete it
+ if ( QgsMapLayerRegistry::instance()->mapLayer( entry.layerPointer->id() ) )
+ QgsMapLayerRegistry::instance()->removeMapLayer( entry.layerPointer->id() );
+
delete entry.layerPointer;
//remove the temporary files of a layer
diff --git a/src/server/qgsserver.cpp b/src/server/qgsserver.cpp
index 0392b42..22600f8 100644
--- a/src/server/qgsserver.cpp
+++ b/src/server/qgsserver.cpp
@@ -465,6 +465,13 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
int logLevel = QgsServerLogger::instance()->logLevel();
QTime time; //used for measuring request time if loglevel < 1
QgsMapLayerRegistry::instance()->removeAllMapLayers();
+
+ // Clean up Expression Context
+ // because each call to QgsMapLayer::draw add items to QgsExpressionContext scope
+ // list. This prevent the scope list to grow indefinitely and seriously deteriorate
+ // performances and memory in the long run
+ sMapRenderer->rendererContext()->setExpressionContext( QgsExpressionContext() );
+
sQgsApplication->processEvents();
if ( logLevel < 1 )
{
diff --git a/src/ui/effects/qgseffectstackpropertieswidgetbase.ui b/src/ui/effects/qgseffectstackpropertieswidgetbase.ui
index 5e4abb7..5fdf574 100644
--- a/src/ui/effects/qgseffectstackpropertieswidgetbase.ui
+++ b/src/ui/effects/qgseffectstackpropertieswidgetbase.ui
@@ -83,7 +83,7 @@
</size>
</property>
<property name="toolTip">
- <string>Add symbol layer</string>
+ <string>Add new effect</string>
</property>
</widget>
</item>
@@ -96,7 +96,7 @@
</size>
</property>
<property name="toolTip">
- <string>Remove symbol layer</string>
+ <string>Remove effect</string>
</property>
</widget>
</item>
diff --git a/src/ui/qgscustomizationdialogbase.ui b/src/ui/qgscustomizationdialogbase.ui
index 3c5d3e8..a0cd1de 100644
--- a/src/ui/qgscustomizationdialogbase.ui
+++ b/src/ui/qgscustomizationdialogbase.ui
@@ -151,7 +151,7 @@
<normaloff>:/images/themes/default/mActionSelectAllTree.svg</normaloff>:/images/themes/default/mActionSelectAllTree.svg</iconset>
</property>
<property name="text">
- <string>Select All</string>
+ <string>Check All</string>
</property>
</action>
</widget>
diff --git a/src/ui/qgsoraclenewconnectionbase.ui b/src/ui/qgsoraclenewconnectionbase.ui
index 674c582..347e24d 100644
--- a/src/ui/qgsoraclenewconnectionbase.ui
+++ b/src/ui/qgsoraclenewconnectionbase.ui
@@ -45,6 +45,13 @@
<string>Connection Information</string>
</property>
<layout class="QGridLayout" name="gridLayout_1">
+ <item row="9" column="0" colspan="2">
+ <widget class="QCheckBox" name="chkStorePassword">
+ <property name="text">
+ <string>Save Password</string>
+ </property>
+ </widget>
+ </item>
<item row="13" column="0" colspan="3">
<widget class="QCheckBox" name="cb_useEstimatedMetadata">
<property name="toolTip">
@@ -187,13 +194,6 @@
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="txtDatabase"/>
</item>
- <item row="9" column="0" colspan="2">
- <widget class="QCheckBox" name="chkStorePassword">
- <property name="text">
- <string>Save Password</string>
- </property>
- </widget>
- </item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="txtName">
<property name="toolTip">
@@ -235,6 +235,19 @@
</property>
</widget>
</item>
+ <item row="15" column="0" colspan="3">
+ <widget class="QCheckBox" name="cb_includeGeoAttributes">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Include additional geometry attributes</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -257,6 +270,7 @@
<tabstop>cb_allowGeometrylessTables</tabstop>
<tabstop>cb_useEstimatedMetadata</tabstop>
<tabstop>cb_onlyExistingTypes</tabstop>
+ <tabstop>cb_includeGeoAttributes</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
diff --git a/src/ui/qgsrasterlayersaveasdialogbase.ui b/src/ui/qgsrasterlayersaveasdialogbase.ui
index 507a36b..70296be 100644
--- a/src/ui/qgsrasterlayersaveasdialogbase.ui
+++ b/src/ui/qgsrasterlayersaveasdialogbase.ui
@@ -564,8 +564,8 @@ datasets with maximum width and height specified below.</string>
<string>...</string>
</property>
<property name="icon">
- <iconset>
- <normaloff>../../images/themes/default/mActionNewAttribute.png</normaloff>../../images/themes/default/mActionNewAttribute.png</iconset>
+ <iconset resource="../../images/images.qrc">
+ <normaloff>:/images/themes/default/mActionNewAttribute.svg</normaloff>:/images/themes/default/mActionNewAttribute.svg</iconset>
</property>
</widget>
</item>
@@ -578,8 +578,8 @@ datasets with maximum width and height specified below.</string>
<string>...</string>
</property>
<property name="icon">
- <iconset>
- <normaloff>../../images/themes/default/mActionCopySelected.png</normaloff>../../images/themes/default/mActionCopySelected.png</iconset>
+ <iconset resource="../../images/images.qrc">
+ <normaloff>:/images/themes/default/mActionCopySelected.png</normaloff>:/images/themes/default/mActionCopySelected.png</iconset>
</property>
</widget>
</item>
@@ -595,8 +595,8 @@ datasets with maximum width and height specified below.</string>
<string>...</string>
</property>
<property name="icon">
- <iconset>
- <normaloff>../../images/themes/default/mActionDeleteAttribute.png</normaloff>../../images/themes/default/mActionDeleteAttribute.png</iconset>
+ <iconset resource="../../images/images.qrc">
+ <normaloff>:/images/themes/default/mActionDeleteAttribute.png</normaloff>:/images/themes/default/mActionDeleteAttribute.png</iconset>
</property>
</widget>
</item>
@@ -609,8 +609,8 @@ datasets with maximum width and height specified below.</string>
<string>...</string>
</property>
<property name="icon">
- <iconset>
- <normaloff>../../images/themes/default/mActionRemove.png</normaloff>../../images/themes/default/mActionRemove.png</iconset>
+ <iconset resource="../../images/images.qrc">
+ <normaloff>:/images/themes/default/mActionRemove.png</normaloff>:/images/themes/default/mActionRemove.png</iconset>
</property>
</widget>
</item>
@@ -714,7 +714,9 @@ datasets with maximum width and height specified below.</string>
<tabstop>mLoadTransparentNoDataToolButton</tabstop>
<tabstop>mRemoveAllNoDataToolButton</tabstop>
</tabstops>
- <resources/>
+ <resources>
+ <include location="../../images/images.qrc"/>
+ </resources>
<connections>
<connection>
<sender>mButtonBox</sender>
diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt
index 2829e95..9f098ce 100644
--- a/tests/src/app/CMakeLists.txt
+++ b/tests/src/app/CMakeLists.txt
@@ -101,6 +101,7 @@ ENDMACRO (ADD_QGIS_TEST)
#############################################################
# Tests:
+ADD_QGIS_TEST(apppythontest testqgisapppython.cpp)
ADD_QGIS_TEST(qgisappclipboard testqgisappclipboard.cpp)
ADD_QGIS_TEST(attributetabletest testqgsattributetable.cpp)
ADD_QGIS_TEST(fieldcalculatortest testqgsfieldcalculator.cpp)
diff --git a/tests/src/app/testqgisapppython.cpp b/tests/src/app/testqgisapppython.cpp
new file mode 100644
index 0000000..b75c6d9
--- /dev/null
+++ b/tests/src/app/testqgisapppython.cpp
@@ -0,0 +1,97 @@
+/***************************************************************************
+ testqgsapppython.cpp
+ --------------------
+ Date : May 2016
+ Copyright : (C) 2016 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 <QApplication>
+#include <QObject>
+#include <QSplashScreen>
+#include <QString>
+#include <QStringList>
+#include <QtTest/QtTest>
+
+#include <qgisapp.h>
+#include <qgsapplication.h>
+
+/** \ingroup UnitTests
+ * This is a unit test for the QgisApp python support.
+ */
+class TestQgisAppPython : public QObject
+{
+ Q_OBJECT
+
+ public:
+ TestQgisAppPython();
+
+ 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 runString();
+ void evalString();
+
+ private:
+ QgisApp * mQgisApp;
+ QString mTestDataDir;
+};
+
+TestQgisAppPython::TestQgisAppPython()
+ : mQgisApp( nullptr )
+{
+
+}
+
+//runs before all tests
+void TestQgisAppPython::initTestCase()
+{
+ // Set up the QSettings environment
+ QCoreApplication::setOrganizationName( "QGIS" );
+ QCoreApplication::setOrganizationDomain( "qgis.org" );
+ QCoreApplication::setApplicationName( "QGIS-TEST" );
+
+ qDebug() << "TestQgisAppClipboard::initTestCase()";
+ // init QGIS's paths - true means that all path will be inited from prefix
+ QgsApplication::init();
+ QgsApplication::initQgis();
+ mTestDataDir = QString( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
+ mQgisApp = new QgisApp();
+ mQgisApp->loadPythonSupport();
+}
+
+//runs after all tests
+void TestQgisAppPython::cleanupTestCase()
+{
+ QgsApplication::exitQgis();
+}
+
+void TestQgisAppPython::runString()
+{
+ QVERIFY( mQgisApp->mPythonUtils->runString( "a=1+1" ) );
+ QVERIFY( !mQgisApp->mPythonUtils->runString( "x" ) );
+ QVERIFY( !mQgisApp->mPythonUtils->runString( "" ) );
+}
+
+void TestQgisAppPython::evalString()
+{
+ QString result;
+ //good string
+ QVERIFY( mQgisApp->mPythonUtils->evalString( "1+1", result ) );
+ QCOMPARE( result, QString( "2" ) );
+
+ //bad string
+ QVERIFY( !mQgisApp->mPythonUtils->evalString( "1+", result ) );
+}
+
+QTEST_MAIN( TestQgisAppPython )
+#include "testqgisapppython.moc"
diff --git a/tests/src/app/testqgsmaptoolidentifyaction.cpp b/tests/src/app/testqgsmaptoolidentifyaction.cpp
index 1a932eb..d98997b 100644
--- a/tests/src/app/testqgsmaptoolidentifyaction.cpp
+++ b/tests/src/app/testqgsmaptoolidentifyaction.cpp
@@ -16,6 +16,7 @@
#include <QtTest/QtTest>
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
+#include "qgsrasterlayer.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
#include "qgsvectordataprovider.h"
@@ -24,6 +25,8 @@
#include "qgsunittypes.h"
#include "qgsmaptoolidentifyaction.h"
+#include "cpl_conv.h"
+
class TestQgsMapToolIdentifyAction : public QObject
{
Q_OBJECT
@@ -40,9 +43,38 @@ class TestQgsMapToolIdentifyAction : public QObject
void lengthCalculation(); //test calculation of derived length attributes
void perimeterCalculation(); //test calculation of derived perimeter attribute
void areaCalculation(); //test calculation of derived area attribute
+ void identifyRasterFloat32(); // test pixel identification and decimal precision
+ void identifyRasterFloat64(); // test pixel identification and decimal precision
+ void identifyInvalidPolygons(); // test selecting invalid polygons
private:
QgsMapCanvas* canvas;
+
+ QString testIdentifyRaster( QgsRasterLayer* layer, double xGeoref, double yGeoref );
+ QList<QgsMapToolIdentify::IdentifyResult> testIdentifyVector( QgsVectorLayer* layer, double xGeoref, double yGeoref );
+
+ // Release return with delete []
+ unsigned char *
+ hex2bytes( const char *hex, int *size )
+ {
+ QByteArray ba = QByteArray::fromHex( hex );
+ unsigned char *out = new unsigned char[ba.size()];
+ memcpy( out, ba.data(), ba.size() );
+ *size = ba.size();
+ return out;
+ }
+
+ // TODO: make this a QgsGeometry member...
+ QgsGeometry geomFromHexWKB( const char *hexwkb )
+ {
+ int wkbsize;
+ unsigned char *wkb = hex2bytes( hexwkb, &wkbsize );
+ QgsGeometry geom;
+ // NOTE: QgsGeometry takes ownership of wkb
+ geom.fromWkb( wkb, wkbsize );
+ return geom;
+ }
+
};
void TestQgsMapToolIdentifyAction::initTestCase()
@@ -241,6 +273,111 @@ void TestQgsMapToolIdentifyAction::areaCalculation()
QVERIFY( qgsDoubleNear( area, 389.6117, 0.001 ) );
}
+// private
+QString TestQgsMapToolIdentifyAction::testIdentifyRaster( QgsRasterLayer* layer, double xGeoref, double yGeoref )
+{
+ QScopedPointer< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
+ QgsPoint mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref );
+ QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << layer );
+ if ( result.length() != 1 )
+ return "";
+ return result[0].mAttributes["Band 1"];
+}
+
+// private
+QList<QgsMapToolIdentify::IdentifyResult>
+TestQgsMapToolIdentifyAction::testIdentifyVector( QgsVectorLayer* layer, double xGeoref, double yGeoref )
+{
+ QScopedPointer< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
+ QgsPoint mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref );
+ QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << layer );
+ return result;
+}
+
+void TestQgsMapToolIdentifyAction::identifyRasterFloat32()
+{
+ //create a temporary layer
+ QString raster = QString( TEST_DATA_DIR ) + "/raster/test.asc";
+
+ // By default the QgsRasterLayer forces AAIGRID_DATATYPE=Float64
+ CPLSetConfigOption( "AAIGRID_DATATYPE", "Float32" );
+ QScopedPointer< QgsRasterLayer> tempLayer( new QgsRasterLayer( raster ) );
+ CPLSetConfigOption( "AAIGRID_DATATYPE", nullptr );
+
+ QVERIFY( tempLayer->isValid() );
+
+ canvas->setExtent( QgsRectangle( 0, 0, 7, 1 ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 0.5, 0.5 ), QString( "-999.9" ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 1.5, 0.5 ), QString( "-999.987" ) );
+
+ // More than 6 significant digits for corresponding value in .asc:
+ // precision loss in Float32
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 2.5, 0.5 ), QString( "1.2345678" ) ); // in .asc file : 1.2345678
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 3.5, 0.5 ), QString( "123456" ) );
+
+ // More than 6 significant digits: no precision loss here for that particular value
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 4.5, 0.5 ), QString( "1234567" ) );
+
+ // More than 6 significant digits: no precision loss here for that particular value
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 5.5, 0.5 ), QString( "-999.9876" ) );
+
+ // More than 6 significant digits for corresponding value in .asc:
+ // precision loss in Float32
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 6.5, 0.5 ), QString( "1.2345678901234" ) ); // in .asc file : 1.2345678901234
+}
+
+void TestQgsMapToolIdentifyAction::identifyRasterFloat64()
+{
+ //create a temporary layer
+ QString raster = QString( TEST_DATA_DIR ) + "/raster/test.asc";
+ QScopedPointer< QgsRasterLayer> tempLayer( new QgsRasterLayer( raster ) );
+ QVERIFY( tempLayer->isValid() );
+
+ canvas->setExtent( QgsRectangle( 0, 0, 7, 1 ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 0.5, 0.5 ), QString( "-999.9" ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 1.5, 0.5 ), QString( "-999.987" ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 2.5, 0.5 ), QString( "1.2345678" ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 3.5, 0.5 ), QString( "123456" ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 4.5, 0.5 ), QString( "1234567" ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 5.5, 0.5 ), QString( "-999.9876" ) );
+
+ QCOMPARE( testIdentifyRaster( tempLayer.data(), 6.5, 0.5 ), QString( "1.2345678901234" ) );
+}
+
+void TestQgsMapToolIdentifyAction::identifyInvalidPolygons()
+{
+ //create a temporary layer
+ QScopedPointer< QgsVectorLayer > memoryLayer( new QgsVectorLayer( "Polygon?field=pk:int", "vl", "memory" ) );
+ QVERIFY( memoryLayer->isValid() );
+ QgsFeature f1( memoryLayer->dataProvider()->fields(), 1 );
+ f1.setAttribute( "pk", 1 );
+ f1.setGeometry( geomFromHexWKB(
+ "010300000001000000030000000000000000000000000000000000000000000000000024400000000000000000000000000000244000000000000024400000000000000000"
+ ) );
+ // TODO: check why we need the ->dataProvider() part, since
+ // there's a QgsVectorLayer::addFeatures method too
+ //memoryLayer->addFeatures( QgsFeatureList() << f1 );
+ memoryLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
+
+ canvas->setExtent( QgsRectangle( 0, 0, 10, 10 ) );
+ QList<QgsMapToolIdentify::IdentifyResult> identified;
+ identified = testIdentifyVector( memoryLayer.data(), 4, 6 );
+ QCOMPARE( identified.length(), 0 );
+ identified = testIdentifyVector( memoryLayer.data(), 6, 4 );
+ QCOMPARE( identified.length(), 1 );
+ QCOMPARE( identified[0].mFeature.attribute( "pk" ), QVariant( 1 ) );
+
+}
+
QTEST_MAIN( TestQgsMapToolIdentifyAction )
#include "testqgsmaptoolidentifyaction.moc"
diff --git a/tests/src/core/testqgscomposergroup.cpp b/tests/src/core/testqgscomposergroup.cpp
index 4886375..d7213a4 100644
--- a/tests/src/core/testqgscomposergroup.cpp
+++ b/tests/src/core/testqgscomposergroup.cpp
@@ -17,11 +17,14 @@
#include "qgscomposeritemgroup.h"
#include "qgscomposerlabel.h"
+#include "qgscomposerarrow.h"
#include "qgscomposition.h"
#include "qgscompositionchecker.h"
#include "qgsapplication.h"
+#include "qgslogger.h"
#include <QObject>
+#include <QtTest/QSignalSpy>
#include <QtTest/QtTest>
class TestQgsComposerGroup : public QObject
@@ -48,6 +51,9 @@ class TestQgsComposerGroup : public QObject
void undoRedo(); //test that group/ungroup undo/redo commands don't crash
private:
+
+ void dumpUndoStack( const QUndoStack&, QString prefix = "" ) const;
+
QgsComposition *mComposition;
QgsMapSettings *mMapSettings;
QgsComposerLabel* mItem1;
@@ -56,6 +62,18 @@ class TestQgsComposerGroup : public QObject
QString mReport;
};
+// private
+void TestQgsComposerGroup::dumpUndoStack( const QUndoStack& us, QString prefix ) const
+{
+ if ( ! prefix.isEmpty() ) prefix += ": ";
+ for ( int i = 0; i < us.count(); ++i )
+ {
+ QgsDebugMsg( QString( "%4US %1: %2%3" )
+ .arg( i ). arg( i >= us.index() ? "-" : "" )
+ .arg( us.text( i ) ) .arg( prefix ) );
+ }
+}
+
void TestQgsComposerGroup::initTestCase()
{
QgsApplication::init();
@@ -161,36 +179,261 @@ void TestQgsComposerGroup::deleteGroup()
QCOMPARE( items.size(), 1 );
QVERIFY( mItem1->isRemoved() );
QVERIFY( mItem2->isRemoved() );
+ QVERIFY( mGroup->isRemoved() );
}
+Q_DECLARE_METATYPE( QgsComposerItemGroup * );
+Q_DECLARE_METATYPE( QgsComposerArrow * );
+Q_DECLARE_METATYPE( QgsComposerItem * );
+
void TestQgsComposerGroup::undoRedo()
{
-#if 0 //expected fail - see #11371
+ QgsComposerArrow *item1, *item2;
+ int arrowsAdded = 0;
+ int groupsAdded = 0;
+ int itemsRemoved = 0;
+
+ qRegisterMetaType<QgsComposerArrow *>();
+ QSignalSpy spyArrowAdded( mComposition, SIGNAL( composerArrowAdded( QgsComposerArrow* ) ) );
+ QCOMPARE( spyArrowAdded.count(), 0 );
+
+ qRegisterMetaType<QgsComposerItemGroup *>();
+ QSignalSpy spyGroupAdded( mComposition, SIGNAL( composerItemGroupAdded( QgsComposerItemGroup* ) ) );
+ QCOMPARE( spyGroupAdded.count(), 0 );
+
+ qRegisterMetaType<QgsComposerItem *>();
+ QSignalSpy spyItemRemoved( mComposition, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
+ QCOMPARE( spyItemRemoved.count(), 0 );
+
//test for crash when undo/redoing with groups
+ // Set initial condition
+ QUndoStack *us = mComposition->undoStack();
+ QgsDebugMsg( QString( "clearing" ) );
+ us->clear();
+ QgsDebugMsg( QString( "clearing completed" ) );
+ QList<QgsComposerItem*> items;
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 1 ); // paper only
+ QgsDebugMsg( QString( "clear stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
//create some items
- mItem1 = new QgsComposerLabel( mComposition );
- mComposition->addItem( mItem1 );
- mItem2 = new QgsComposerLabel( mComposition );
- mComposition->addItem( mItem2 );
+ item1 = new QgsComposerArrow( QPointF( 0, 0 ), QPointF( 1, 1 ), mComposition );
+ item1->setArrowHeadWidth( 0 );
+ item1->setArrowHeadOutlineWidth( 0 );
+ mComposition->addComposerArrow( item1 );
+ QCOMPARE( spyArrowAdded.count(), ++arrowsAdded );
+ item2 = new QgsComposerArrow( QPointF( -1, -2 ), QPointF( 1, 1 ), mComposition );
+ item2->setArrowHeadOutlineWidth( 0 );
+ item2->setArrowHeadWidth( 0 );
+ mComposition->addComposerArrow( item2 );
+ QCOMPARE( spyArrowAdded.count(), ++arrowsAdded );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 3 ); // paper, 2 shapes
+ QgsDebugMsg( QString( "addedItems stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
+ QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
+ QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
+ //dumpUndoStack(*us, "after initial items addition");
//group items
- QList<QgsComposerItem*> items;
- items << mItem1 << mItem2;
+ items.clear();
+ items << item1 << item2;
mGroup = mComposition->groupItems( items );
-
- //move, and ungroup
- mGroup->beginCommand( "move" );
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ QCOMPARE( mGroup->items().size(), 2 );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
+ QVERIFY( ! item1->isRemoved() );
+ QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
+ QVERIFY( ! item2->isRemoved() );
+ QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
+ QVERIFY( ! mGroup->isRemoved() );
+ QCOMPARE( mGroup->pos(), QPointF( -1, -2 ) );
+ //dumpUndoStack(*us, "after initial items addition");
+
+ //move group
+ QgsDebugMsg( QString( "moving group" ) );
+ mGroup->beginCommand( "move group" );
mGroup->move( 10.0, 20.0 );
mGroup->endCommand();
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ QgsDebugMsg( QString( "groupItems stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
+ QCOMPARE( mGroup->items().size(), 2 );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
+ QVERIFY( ! item1->isRemoved() );
+ QCOMPARE( item1->pos(), QPointF( 10, 20 ) );
+ QVERIFY( ! item2->isRemoved() );
+ QCOMPARE( item2->pos(), QPointF( 9, 18 ) );
+ QVERIFY( ! mGroup->isRemoved() );
+ QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) );
+
+ //ungroup
+ QgsDebugMsg( QString( "ungrouping" ) );
mComposition->ungroupItems( mGroup );
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), ++itemsRemoved );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 3 ); // paper, 2 shapes
+ QVERIFY( ! item1->isRemoved() );
+ QVERIFY( ! item2->isRemoved() );
+ QVERIFY( mGroup->isRemoved() );
+ QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) ); // should not rely on this
+ //dumpUndoStack(*us, "after ungroup");
+ // US 0: Items grouped
+ // US 1: move group
+ // US 2: Remove item group
+
+ //undo (groups again) -- crashed here before #11371 got fixed
+ QgsDebugMsg( QString( "undo ungrouping" ) );
+ us->undo();
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ QCOMPARE( mGroup->items().size(), 2 ); // WARNING: might not be alive anymore
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
+ QVERIFY( ! item1->isRemoved() );
+ QCOMPARE( item1->pos(), QPointF( 10, 20 ) );
+ QVERIFY( ! item2->isRemoved() );
+ QCOMPARE( item2->pos(), QPointF( 9, 18 ) );
+ QVERIFY( ! mGroup->isRemoved() );
+ QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) );
+ //dumpUndoStack(*us, "after undo ungroup");
+ // US 0: Items grouped
+ // US 1: move group
+ // US 2: -Remove item group
+
+ //remove group
+ QgsDebugMsg( QString( "remove group" ) );
+ mComposition->removeComposerItem( mGroup, true, true );
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), groupsAdded );
+ itemsRemoved += 3; // the group and the two items
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 1 ); // paper only
+ QgsDebugMsg( QString( "remove stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
+ //dumpUndoStack(*us, "after remove group");
+ // US 0: Items grouped
+ // US 1: move group
+ // US 2: Remove item group
+
+ //undo remove group
+ QgsDebugMsg( QString( "undo remove group" ) );
+ us->undo();
+ arrowsAdded += 2;
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ mComposition->composerItems( items );
+ QCOMPARE( mGroup->items().size(), 2 );
+ QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
+ QgsDebugMsg( QString( "undo stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
+ //dumpUndoStack(*us, "after undo remove group");
+ // US 0: Items grouped
+ // US 1: move group
+ // US 2: -Remove item group
+
+ //undo move group
+ QgsDebugMsg( QString( "undo move group" ) );
+ us->undo();
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ QCOMPARE( mGroup->items().size(), 2 );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
+ QCOMPARE( item1->isGroupMember(), true );
+ QCOMPARE( item2->isGroupMember(), true );
+ QVERIFY( ! item1->isRemoved() );
+ QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
+ QVERIFY( ! item2->isRemoved() );
+ QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
+ QVERIFY( ! mGroup->isRemoved() );
+ QCOMPARE( mGroup->pos(), QPointF( -1, -2 ) );
+ //dumpUndoStack(*us, "after undo move group");
+ // US 0: Items grouped
+ // US 1: -move group
+ // US 2: -Remove item group
+
+ //undo group
+ QgsDebugMsg( QString( "undo group" ) );
+ us->undo();
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), ++itemsRemoved );
+ //QCOMPARE( mGroup->items().size(), 2 ); // not important
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 3 ); // paper, 2 shapes
+ QCOMPARE( item1->isGroupMember(), false );
+ QCOMPARE( item2->isGroupMember(), false );
+ QVERIFY( ! item1->isRemoved() );
+ QCOMPARE( item1->pos(), QPointF( 0, 0 ) );
+ QVERIFY( ! item2->isRemoved() );
+ QCOMPARE( item2->pos(), QPointF( -1, -2 ) );
+ QVERIFY( mGroup->isRemoved() );
+ //QCOMPARE( mGroup->pos(), QPointF( -1, -2 ) );
+ //dumpUndoStack(*us, "after undo group");
+ // US 0: -Items grouped
+ // US 1: -move group
+ // US 2: -Remove item group
+
+ //redo group
+ QgsDebugMsg( QString( "redo group" ) );
+ us->redo();
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), ++groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
+ QCOMPARE( item1->isGroupMember(), true );
+ QCOMPARE( item2->isGroupMember(), true );
+ //// QCOMPARE( mGroup->pos(), QPointF( 0, 0 ) ); // getting nan,nan here
+ //dumpUndoStack(*us, "after redo group");
+ // US 0: Items grouped
+ // US 1: -move group
+ // US 2: -Remove item group
+
+ //redo move group
+ QgsDebugMsg( QString( "redo move group" ) );
+ us->redo();
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), groupsAdded );
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 4 ); // paper, 2 shapes, 1 group
+ QCOMPARE( item1->isGroupMember(), true );
+ QCOMPARE( item2->isGroupMember(), true );
+ QCOMPARE( mGroup->pos(), QPointF( 9, 18 ) );
+ //dumpUndoStack(*us, "after redo move group");
+ // US 0: Items grouped
+ // US 1: move group
+ // US 2: -Remove item group
+
+ //redo remove group
+ QgsDebugMsg( QString( "redo remove group" ) );
+ us->redo();
+ QCOMPARE( spyArrowAdded.count(), arrowsAdded );
+ QCOMPARE( spyGroupAdded.count(), groupsAdded );
+ itemsRemoved += 3; // 1 group, 2 contained items
+ QCOMPARE( spyItemRemoved.count(), itemsRemoved );
+ mComposition->composerItems( items );
+ QCOMPARE( items.size(), 1 ); // paper only
+ QgsDebugMsg( QString( "undo stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
+ //dumpUndoStack(*us, "after redo remove group");
+ // US 0: Items grouped
+ // US 1: move group
+ // US 2: Remove item group
- //undo
- mComposition->undoStack()->undo();
+ //unwind the whole stack
+ us->clear();
- //redo
- mComposition->undoStack()->redo();
-#endif
+ QgsDebugMsg( QString( "clear stack count:%1 index:%2" ) .arg( us->count() ) .arg( us->index() ) );
}
QTEST_MAIN( TestQgsComposerGroup )
diff --git a/tests/src/core/testqgscomposerpicture.cpp b/tests/src/core/testqgscomposerpicture.cpp
index 92b582c..5557cee 100644
--- a/tests/src/core/testqgscomposerpicture.cpp
+++ b/tests/src/core/testqgscomposerpicture.cpp
@@ -57,10 +57,12 @@ class TestQgsComposerPicture : public QObject
void pictureSvgFrameToImage();
void svgParameters();
+ void issue_14644();
void pictureExpression();
void pictureInvalidExpression();
+
private:
QgsComposition* mComposition;
QgsComposerPicture* mComposerPicture;
@@ -387,6 +389,22 @@ void TestQgsComposerPicture::svgParameters()
mComposerPicture->setPicturePath( mPngImage );
}
+void TestQgsComposerPicture::issue_14644()
+{
+ //test rendering SVG file with text
+ mComposition->addComposerPicture( mComposerPicture );
+ mComposerPicture->setResizeMode( QgsComposerPicture::Zoom );
+ mComposerPicture->setPicturePath( QString( TEST_DATA_DIR ) + "/svg/issue_14644.svg" );
+
+ QgsCompositionChecker checker( "composerpicture_issue_14644", mComposition );
+ checker.setControlPathPrefix( "composer_picture" );
+ QVERIFY( checker.testComposition( mReport, 0, 0 ) );
+
+ mComposition->removeItem( mComposerPicture );
+ mComposerPicture->setSceneRect( QRectF( 70, 70, 100, 100 ) );
+ mComposerPicture->setPicturePath( mPngImage );
+}
+
void TestQgsComposerPicture::pictureExpression()
{
//test picture source via expression
diff --git a/tests/src/core/testqgscoordinatereferencesystem.cpp b/tests/src/core/testqgscoordinatereferencesystem.cpp
index 9b1f576..afa1c02 100644
--- a/tests/src/core/testqgscoordinatereferencesystem.cpp
+++ b/tests/src/core/testqgscoordinatereferencesystem.cpp
@@ -61,6 +61,7 @@ class TestQgsCoordinateReferenceSystem: public QObject
void mapUnits();
void setValidationHint();
void axisInverted();
+ void createFromProj4Invalid();
private:
void debugPrint( QgsCoordinateReferenceSystem &theCrs );
// these used by createFromESRIWkt()
@@ -464,5 +465,11 @@ void TestQgsCoordinateReferenceSystem::debugPrint(
}
}
+void TestQgsCoordinateReferenceSystem::createFromProj4Invalid()
+{
+ QgsCoordinateReferenceSystem myCrs;
+ QVERIFY( !myCrs.createFromProj4( "+proj=longlat +no_defs" ) );
+}
+
QTEST_MAIN( TestQgsCoordinateReferenceSystem )
#include "testqgscoordinatereferencesystem.moc"
diff --git a/tests/src/core/testqgsdistancearea.cpp b/tests/src/core/testqgsdistancearea.cpp
index 9ebbdc7..02f337c 100644
--- a/tests/src/core/testqgsdistancearea.cpp
+++ b/tests/src/core/testqgsdistancearea.cpp
@@ -42,6 +42,8 @@ class TestQgsDistanceArea: public QObject
void collections();
void measureUnits();
void measureAreaAndUnits();
+ void emptyPolygon();
+ void regression14675();
};
@@ -344,6 +346,28 @@ void TestQgsDistanceArea::measureAreaAndUnits()
QVERIFY( qgsDoubleNear( area, 220240.8172549, 0.00001 ) );
}
+void TestQgsDistanceArea::emptyPolygon()
+{
+ QgsDistanceArea da;
+ da.setSourceCrs( 3452 );
+ da.setEllipsoidalMode( true );
+ da.setEllipsoid( "WGS84" );
+
+ //test that measuring an empty polygon doesn't crash
+ da.measurePolygon( QList< QgsPoint >() );
+}
+
+void TestQgsDistanceArea::regression14675()
+{
+ //test regression #14675
+ QgsDistanceArea calc;
+ calc.setEllipsoidalMode( true );
+ calc.setEllipsoid( "GRS80" );
+ calc.setSourceCrs( 145L );
+ QgsGeometry geom( QgsGeometryFactory::geomFromWkt( "Polygon ((917593.5791854317067191 6833700.00807378999888897, 917596.43389983859378844 6833700.67099479306489229, 917599.53056440979707986 6833700.78673478215932846, 917593.5791854317067191 6833700.00807378999888897))" ) );
+ QVERIFY( qgsDoubleNear( calc.measureArea( &geom ), 0.83301, 0.0001 ) );
+}
+
QTEST_MAIN( TestQgsDistanceArea )
#include "testqgsdistancearea.moc"
diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp
index 4edfe0b..b76db34 100644
--- a/tests/src/core/testqgsexpression.cpp
+++ b/tests/src/core/testqgsexpression.cpp
@@ -299,6 +299,20 @@ class TestQgsExpression: public QObject
QTest::newRow( "'nan'='x'" ) << "'nan'='x'" << false << QVariant( 0 );
QTest::newRow( "'inf'='inf'" ) << "'inf'='inf'" << false << QVariant( 1 );
QTest::newRow( "'inf'='x'" ) << "'inf'='x'" << false << QVariant( 0 );
+ QTest::newRow( "'1.1'='1.1'" ) << "'1.1'='1.1'" << false << QVariant( 1 );
+ QTest::newRow( "'1.1'!='1.1'" ) << "'1.1'!='1.1'" << false << QVariant( 0 );
+ QTest::newRow( "'1.1'='1.10'" ) << "'1.1'='1.10'" << false << QVariant( 0 );
+ QTest::newRow( "'1.1'!='1.10'" ) << "'1.1'!='1.10'" << false << QVariant( 1 );
+ QTest::newRow( "1.1=1.10" ) << "1.1=1.10" << false << QVariant( 1 );
+ QTest::newRow( "1.1 != 1.10" ) << "1.1 != 1.10" << false << QVariant( 0 );
+ QTest::newRow( "'1.1'=1.1" ) << "'1.1'=1.1" << false << QVariant( 1 );
+ QTest::newRow( "'1.10'=1.1" ) << "'1.10'=1.1" << false << QVariant( 1 );
+ QTest::newRow( "1.1='1.10'" ) << "1.1='1.10'" << false << QVariant( 1 );
+ QTest::newRow( "'1.1'='1.10000'" ) << "'1.1'='1.10000'" << false << QVariant( 0 );
+ QTest::newRow( "'1E-23'='1E-23'" ) << "'1E-23'='1E-23'" << false << QVariant( 1 );
+ QTest::newRow( "'1E-23'!='1E-23'" ) << "'1E-23'!='1E-23'" << false << QVariant( 0 );
+ QTest::newRow( "'1E-23'='2E-23'" ) << "'1E-23'='2E-23'" << false << QVariant( 0 );
+ QTest::newRow( "'1E-23'!='2E-23'" ) << "'1E-23'!='2E-23'" << false << QVariant( 1 );
// is, is not
QTest::newRow( "is null,null" ) << "null is null" << false << QVariant( 1 );
@@ -309,6 +323,10 @@ class TestQgsExpression: public QObject
QTest::newRow( "is not int" ) << "1 is not 1" << false << QVariant( 0 );
QTest::newRow( "is text" ) << "'x' is 'y'" << false << QVariant( 0 );
QTest::newRow( "is not text" ) << "'x' is not 'y'" << false << QVariant( 1 );
+ QTest::newRow( "'1.1' is '1.10'" ) << "'1.1' is '1.10'" << false << QVariant( 0 );
+ QTest::newRow( "'1.1' is '1.10000'" ) << "'1.1' is '1.10000'" << false << QVariant( 0 );
+ QTest::newRow( "1.1 is '1.10'" ) << "1.1 is '1.10'" << false << QVariant( 1 );
+ QTest::newRow( "'1.10' is 1.1" ) << "'1.10' is 1.1" << false << QVariant( 1 );
// logical
QTest::newRow( "T or F" ) << "1=1 or 2=3" << false << QVariant( 1 );
diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp
index 9a05468..8fe4cdd 100644
--- a/tests/src/core/testqgsgeometry.cpp
+++ b/tests/src/core/testqgsgeometry.cpp
@@ -604,7 +604,7 @@ void TestQgsGeometry::pointV2()
p16.transform( tr, QgsCoordinateTransform::ForwardTransform );
QVERIFY( qgsDoubleNear( p16.x(), 175.771, 0.001 ) );
QVERIFY( qgsDoubleNear( p16.y(), -39.722, 0.001 ) );
- QVERIFY( qgsDoubleNear( p16.z(), 57.2958, 0.001 ) );
+ QVERIFY( qgsDoubleNear( p16.z(), 1.0, 0.001 ) );
QCOMPARE( p16.m(), 2.0 );
p16.transform( tr, QgsCoordinateTransform::ReverseTransform );
QVERIFY( qgsDoubleNear( p16.x(), 6374985, 1 ) );
@@ -1489,11 +1489,11 @@ void TestQgsGeometry::lineStringV2()
l22.transform( tr, QgsCoordinateTransform::ForwardTransform );
QVERIFY( qgsDoubleNear( l22.pointN( 0 ).x(), 175.771, 0.001 ) );
QVERIFY( qgsDoubleNear( l22.pointN( 0 ).y(), -39.722, 0.001 ) );
- QVERIFY( qgsDoubleNear( l22.pointN( 0 ).z(), 57.2958, 0.001 ) );
+ QVERIFY( qgsDoubleNear( l22.pointN( 0 ).z(), 1.0, 0.001 ) );
QCOMPARE( l22.pointN( 0 ).m(), 2.0 );
QVERIFY( qgsDoubleNear( l22.pointN( 1 ).x(), 176.959, 0.001 ) );
QVERIFY( qgsDoubleNear( l22.pointN( 1 ).y(), -38.798, 0.001 ) );
- QVERIFY( qgsDoubleNear( l22.pointN( 1 ).z(), 171.887, 0.001 ) );
+ QVERIFY( qgsDoubleNear( l22.pointN( 1 ).z(), 3.0, 0.001 ) );
QCOMPARE( l22.pointN( 1 ).m(), 4.0 );
//reverse transform
diff --git a/tests/src/core/testqgslabelingenginev2.cpp b/tests/src/core/testqgslabelingenginev2.cpp
index daea089..9f70edf 100644
--- a/tests/src/core/testqgslabelingenginev2.cpp
+++ b/tests/src/core/testqgslabelingenginev2.cpp
@@ -259,7 +259,7 @@ void TestQgsLabelingEngineV2::testRuleBased()
delete rl2;
- /*
+#if 0
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
@@ -267,7 +267,8 @@ void TestQgsLabelingEngineV2::testRuleBased()
QgsLabelingEngineV2 engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsRuleBasedLabelProvider( , vl ) );
- engine.run( context );*/
+ engine.run( context );
+#endif
}
void TestQgsLabelingEngineV2::zOrder()
diff --git a/tests/src/gui/testqgsrubberband.cpp b/tests/src/gui/testqgsrubberband.cpp
index e5715b0..258e3b5 100644
--- a/tests/src/gui/testqgsrubberband.cpp
+++ b/tests/src/gui/testqgsrubberband.cpp
@@ -46,6 +46,7 @@ class TestQgsRubberband : public QObject
void testAddSingleMultiGeometries(); //test for #7728
void testBoundingRect(); //test for #12392
void testVisibility(); //test for 12486
+ void testClose(); //test closing geometry
private:
QgsMapCanvas* mCanvas;
@@ -193,6 +194,34 @@ void TestQgsRubberband::testVisibility()
}
+void TestQgsRubberband::testClose()
+{
+ QgsRubberBand r( mCanvas, QGis::Polygon );
+
+ // try closing empty rubber band, don't want to crash
+ r.closePoints();
+ QCOMPARE( r.partSize( 0 ), 0 );
+
+ r.addPoint( QgsPoint( 1, 2 ) );
+ r.addPoint( QgsPoint( 1, 3 ) );
+ r.addPoint( QgsPoint( 2, 3 ) );
+ QCOMPARE( r.partSize( 0 ), 3 );
+
+ // test with some bad geometry indexes - don't want to crash!
+ r.closePoints( true, -1 );
+ QCOMPARE( r.partSize( 0 ), 3 );
+ r.closePoints( true, 100 );
+ QCOMPARE( r.partSize( 0 ), 3 );
+
+ // valid close
+ r.closePoints();
+ QCOMPARE( r.partSize( 0 ), 4 );
+
+ // close already closed polygon, should be no change
+ r.closePoints();
+ QCOMPARE( r.partSize( 0 ), 4 );
+}
+
QTEST_MAIN( TestQgsRubberband )
#include "testqgsrubberband.moc"
diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt
index 4eb1a52..38ab9e9 100644
--- a/tests/src/python/CMakeLists.txt
+++ b/tests/src/python/CMakeLists.txt
@@ -1,4 +1,5 @@
SET (ENABLE_MSSQLTEST FALSE CACHE BOOL "Enable MsSQL provider tests")
+SET (ENABLE_ORACLETEST FALSE CACHE BOOL "Enable Oracle provider tests")
INCLUDE(UsePythonTest)
# Run one of the two server tests at the beginning so they don't run in
@@ -14,6 +15,7 @@ ADD_PYTHON_TEST(PyQgsAttributeTableModel test_qgsattributetablemodel.py)
#ADD_PYTHON_TEST(PyQgsAuthenticationSystem test_qgsauthsystem.py)
ADD_PYTHON_TEST(PyQgsBlendModes test_qgsblendmodes.py)
ADD_PYTHON_TEST(PyQgsCategorizedSymbolRendererV2 test_qgscategorizedsymbolrendererv2.py)
+ADD_PYTHON_TEST(PyQgsColorButtonV2 test_qgscolorbuttonv2.py)
ADD_PYTHON_TEST(PyQgsColorScheme test_qgscolorscheme.py)
ADD_PYTHON_TEST(PyQgsColorSchemeRegistry test_qgscolorschemeregistry.py)
ADD_PYTHON_TEST(PyQgsComposerEffects test_qgscomposereffects.py)
@@ -23,6 +25,7 @@ ADD_PYTHON_TEST(PyQgsComposerMap test_qgscomposermap.py)
ADD_PYTHON_TEST(PyQgsComposerMapGrid test_qgscomposermapgrid.py)
ADD_PYTHON_TEST(PyQgsComposerPicture test_qgscomposerpicture.py)
ADD_PYTHON_TEST(PyQgsComposerShapes test_qgscomposershapes.py)
+ADD_PYTHON_TEST(PyQgsComposerView test_qgscomposerview.py)
ADD_PYTHON_TEST(PyQgsComposition test_qgscomposition.py)
ADD_PYTHON_TEST(PyQgsConditionalStyle test_qgsconditionalstyle.py)
ADD_PYTHON_TEST(PyQgsCoordinateTransform test_qgscoordinatetransform.py)
@@ -56,6 +59,7 @@ 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(PyQgsTabfileProvider test_provider_tabfile.py)
+ADD_PYTHON_TEST(PyQgsOGRProvider test_provider_ogr.py)
ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py)
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py)
ADD_PYTHON_TEST(PyQgsSymbolLayerV2 test_qgssymbollayerv2.py)
@@ -94,6 +98,10 @@ IF (ENABLE_MSSQLTEST)
ADD_PYTHON_TEST(PyQgsMssqlProvider test_provider_mssql.py)
ENDIF (ENABLE_MSSQLTEST)
+IF (ENABLE_ORACLETEST)
+ ADD_PYTHON_TEST(PyQgsOracleProvider test_provider_oracle.py)
+ENDIF (ENABLE_ORACLETEST)
+
IF (WITH_APIDOC)
ADD_PYTHON_TEST(PyQgsDocCoverage test_qgsdoccoverage.py)
ADD_PYTHON_TEST(PyQgsSipCoverage test_qgssipcoverage.py)
diff --git a/tests/src/python/providertestbase.py b/tests/src/python/providertestbase.py
index c2ff215..df76136 100644
--- a/tests/src/python/providertestbase.py
+++ b/tests/src/python/providertestbase.py
@@ -33,6 +33,8 @@ class ProviderTestCase(object):
attributes = {}
geometries = {}
while it.nextFeature(f):
+ # expect feature to be valid
+ self.assertTrue(f.isValid())
# split off the first 5 attributes only - some provider test datasets will include
# additional attributes which we ignore
attrs = f.attributes()[0:5]
@@ -62,8 +64,10 @@ class ProviderTestCase(object):
self.assertFalse(geometries[pk], 'Expected null geometry for {}'.format(pk))
def assert_query(self, provider, expression, expected):
- result = set([f['pk'] for f in provider.getFeatures(QgsFeatureRequest().setFilterExpression(expression).setFlags(QgsFeatureRequest.NoGeometry))])
+ request = QgsFeatureRequest().setFilterExpression(expression).setFlags(QgsFeatureRequest.NoGeometry)
+ result = set([f['pk'] for f in provider.getFeatures(request)])
assert set(expected) == result, 'Expected {} and got {} when testing expression "{}"'.format(set(expected), result, expression)
+ self.assertTrue(all(f.isValid() for f in provider.getFeatures(request)))
# Also check that filter works when referenced fields are not being retrieved by request
result = set([f['pk'] for f in provider.getFeatures(QgsFeatureRequest().setFilterExpression(expression).setSubsetOfAttributes([0]))])
@@ -180,18 +184,23 @@ class ProviderTestCase(object):
self.provider.setSubsetString(subset)
self.assertEqual(self.provider.subsetString(), subset)
result = set([f['pk'] for f in self.provider.getFeatures()])
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures()))
self.provider.setSubsetString(None)
expected = set([2, 3, 4])
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), result, subset)
+ self.assertTrue(all_valid)
# Subset string AND filter rect
self.provider.setSubsetString(subset)
extent = QgsRectangle(-70, 70, -60, 75)
- result = set([f['pk'] for f in self.provider.getFeatures(QgsFeatureRequest().setFilterRect(extent))])
+ request = QgsFeatureRequest().setFilterRect(extent)
+ result = set([f['pk'] for f in self.provider.getFeatures(request)])
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
self.provider.setSubsetString(None)
expected = set([2])
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), result, subset)
+ self.assertTrue(all_valid)
# Subset string AND filter rect, version 2
self.provider.setSubsetString(subset)
@@ -203,10 +212,13 @@ class ProviderTestCase(object):
# Subset string AND expression
self.provider.setSubsetString(subset)
- result = set([f['pk'] for f in self.provider.getFeatures(QgsFeatureRequest().setFilterExpression('length("name")=5'))])
+ request = QgsFeatureRequest().setFilterExpression('length("name")=5')
+ result = set([f['pk'] for f in self.provider.getFeatures(request)])
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
self.provider.setSubsetString(None)
expected = set([2, 4])
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), result, subset)
+ self.assertTrue(all_valid)
def getSubsetString(self):
"""Individual providers may need to override this depending on their subset string formats"""
@@ -309,16 +321,32 @@ class ProviderTestCase(object):
fids = [f.id() for f in self.provider.getFeatures()]
assert len(fids) == 5, 'Expected 5 features, got {} instead'.format(len(fids))
for id in fids:
- result = [f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFid(id))]
+ features = [f for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFid(id))]
+ self.assertEqual(len(features), 1)
+ feature = features[0]
+ self.assertTrue(feature.isValid())
+
+ result = [feature.id()]
expected = [id]
assert result == expected, 'Expected {} and got {} when testing for feature ID filter'.format(expected, result)
+ # bad features
+ it = self.provider.getFeatures(QgsFeatureRequest().setFilterFid(-99999999))
+ feature = QgsFeature(5)
+ feature.setValid(False)
+ self.assertFalse(it.nextFeature(feature))
+ self.assertFalse(feature.isValid())
+
def testGetFeaturesFidsTests(self):
fids = [f.id() for f in self.provider.getFeatures()]
+ self.assertEqual(len(fids), 5)
- result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([fids[0], fids[2]]))])
+ request = QgsFeatureRequest().setFilterFids([fids[0], fids[2]])
+ result = set([f.id() for f in self.provider.getFeatures(request)])
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
expected = set([fids[0], fids[2]])
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
+ self.assertTrue(all_valid)
result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]))])
expected = set([fids[1], fids[3], fids[4]])
@@ -330,8 +358,11 @@ class ProviderTestCase(object):
def testGetFeaturesFilterRectTests(self):
extent = QgsRectangle(-70, 67, -60, 80)
- features = [f['pk'] for f in self.provider.getFeatures(QgsFeatureRequest().setFilterRect(extent))]
+ request = QgsFeatureRequest().setFilterRect(extent)
+ features = [f['pk'] for f in self.provider.getFeatures(request)]
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
assert set(features) == set([2, 4]), 'Got {} instead'.format(features)
+ self.assertTrue(all_valid)
def testGetFeaturesPolyFilterRectTests(self):
""" Test fetching features from a polygon layer with filter rect"""
@@ -342,20 +373,33 @@ class ProviderTestCase(object):
return
extent = QgsRectangle(-73, 70, -63, 80)
- features = [f['pk'] for f in self.poly_provider.getFeatures(QgsFeatureRequest().setFilterRect(extent))]
+ request = QgsFeatureRequest().setFilterRect(extent)
+ features = [f['pk'] for f in self.poly_provider.getFeatures(request)]
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
# Some providers may return the exact intersection matches (2, 3) even without the ExactIntersect flag, so we accept that too
assert set(features) == set([2, 3]) or set(features) == set([1, 2, 3]), 'Got {} instead'.format(features)
+ self.assertTrue(all_valid)
# Test with exact intersection
- features = [f['pk'] for f in self.poly_provider.getFeatures(QgsFeatureRequest().setFilterRect(extent).setFlags(QgsFeatureRequest.ExactIntersect))]
+ request = QgsFeatureRequest().setFilterRect(extent).setFlags(QgsFeatureRequest.ExactIntersect)
+ features = [f['pk'] for f in self.poly_provider.getFeatures(request)]
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
assert set(features) == set([2, 3]), 'Got {} instead'.format(features)
+ self.assertTrue(all_valid)
+
+ # test with an empty rectangle
+ extent = QgsRectangle()
+ features = [f['pk'] for f in self.provider.getFeatures(QgsFeatureRequest().setFilterRect(extent))]
+ assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features)
def testRectAndExpression(self):
extent = QgsRectangle(-70, 67, -60, 80)
- result = set([f['pk'] for f in self.provider.getFeatures(
- QgsFeatureRequest().setFilterExpression('"cnt">200').setFilterRect(extent))])
+ request = QgsFeatureRequest().setFilterExpression('"cnt">200').setFilterRect(extent)
+ result = set([f['pk'] for f in self.provider.getFeatures(request)])
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
expected = [4]
assert set(expected) == result, 'Expected {} and got {} when testing for combination of filterRect and expression'.format(set(expected), result)
+ self.assertTrue(all_valid)
def testGetFeaturesLimit(self):
it = self.provider.getFeatures(QgsFeatureRequest().setLimit(2))
@@ -465,6 +509,8 @@ class ProviderTestCase(object):
'cnt': set([-200, 300, 100, 200, 400]),
'name': set(['Pear', 'Orange', 'Apple', 'Honey', NULL]),
'name2': set(['NuLl', 'PEaR', 'oranGe', 'Apple', 'Honey'])}
- for field, expected in tests.iteritems():
- result = set([f[field] for f in self.provider.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field], self.provider.fields()))])
+ for field, expected in tests.items():
+ request = QgsFeatureRequest().setSubsetOfAttributes([field], self.provider.fields())
+ result = set([f[field] for f in self.provider.getFeatures(request)])
+ all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
self.assertEqual(result, expected, 'Expected {}, got {}'.format(expected, result))
diff --git a/tests/src/python/test_provider_ogr.py b/tests/src/python/test_provider_ogr.py
new file mode 100644
index 0000000..9a0e824
--- /dev/null
+++ b/tests/src/python/test_provider_ogr.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+"""QGIS Unit tests for the non-shapefile, non-tabfile datasources handled by OGR provider.
+
+.. 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__ = 'Even Rouault'
+__date__ = '2016-04-11'
+__copyright__ = 'Copyright 2016, Even Rouault'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+import os
+import shutil
+import sys
+import tempfile
+
+from qgis.core import QgsVectorLayer, QgsVectorDataProvider, QgsWKBTypes
+from qgis.testing import (
+ start_app,
+ unittest
+)
+from utilities import unitTestDataPath
+
+start_app()
+TEST_DATA_DIR = unitTestDataPath()
+
+# Note - doesn't implement ProviderTestCase as most OGR provider is tested by the shapefile provider test
+
+
+def count_opened_filedescriptors(filename_to_test):
+ count = -1
+ if sys.platform.startswith('linux'):
+ count = 0
+ open_files_dirname = '/proc/%d/fd' % os.getpid()
+ filenames = os.listdir(open_files_dirname)
+ for filename in filenames:
+ full_filename = open_files_dirname + '/' + filename
+ if os.path.exists(full_filename):
+ link = os.readlink(full_filename)
+ if os.path.basename(link) == os.path.basename(filename_to_test):
+ count += 1
+ return count
+
+
+class PyQgsOGRProvider(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ """Run before all tests"""
+ # Create test layer
+ cls.basetestpath = tempfile.mkdtemp()
+ cls.datasource = os.path.join(cls.basetestpath, 'test.csv')
+ with open(cls.datasource, 'wt') as f:
+ f.write('id,WKT\n')
+ f.write('1,POINT(2 49)\n')
+
+ cls.dirs_to_cleanup = [cls.basetestpath]
+
+ @classmethod
+ def tearDownClass(cls):
+ """Run after all tests"""
+ for dirname in cls.dirs_to_cleanup:
+ shutil.rmtree(dirname, True)
+
+ def testUpdateMode(self):
+
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(self.datasource), u'test', u'ogr')
+ self.assertTrue(vl.isValid())
+ caps = vl.dataProvider().capabilities()
+ self.assertTrue(caps & QgsVectorDataProvider.AddFeatures)
+
+ self.assertEqual(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ # No-op
+ self.assertTrue(vl.dataProvider().enterUpdateMode())
+ self.assertEqual(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ # No-op
+ self.assertTrue(vl.dataProvider().leaveUpdateMode())
+ self.assertEqual(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ def testNoDanglingFileDescriptorAfterCloseVariant1(self):
+ ''' Test that when closing the provider all file handles are released '''
+
+ datasource = os.path.join(self.basetestpath, 'testNoDanglingFileDescriptorAfterCloseVariant1.csv')
+ with open(datasource, 'wt') as f:
+ f.write('id,WKT\n')
+ f.write('1,\n')
+ f.write('2,POINT(2 49)\n')
+
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+ self.assertTrue(vl.isValid())
+ # The iterator will take one extra connection
+ myiter = vl.getFeatures()
+ # Consume one feature but the iterator is still opened
+ f = next(myiter)
+ self.assertTrue(f.isValid())
+
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(datasource), 2)
+
+ # Should release one file descriptor
+ del vl
+
+ # Non portable, but Windows testing is done with trying to unlink
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(datasource), 1)
+
+ f = next(myiter)
+ self.assertTrue(f.isValid())
+
+ # Should release one file descriptor
+ del myiter
+
+ # Non portable, but Windows testing is done with trying to unlink
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(datasource), 0)
+
+ # Check that deletion works well (can only fail on Windows)
+ os.unlink(datasource)
+ self.assertFalse(os.path.exists(datasource))
+
+ def testNoDanglingFileDescriptorAfterCloseVariant2(self):
+ ''' Test that when closing the provider all file handles are released '''
+
+ datasource = os.path.join(self.basetestpath, 'testNoDanglingFileDescriptorAfterCloseVariant2.csv')
+ with open(datasource, 'wt') as f:
+ f.write('id,WKT\n')
+ f.write('1,\n')
+ f.write('2,POINT(2 49)\n')
+
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+ self.assertTrue(vl.isValid())
+ # Consume all features.
+ myiter = vl.getFeatures()
+ for feature in myiter:
+ pass
+ # The iterator is closed, but the corresponding connection still not closed
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(datasource), 2)
+
+ # Should release one file descriptor
+ del vl
+
+ # Non portable, but Windows testing is done with trying to unlink
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(datasource), 0)
+
+ # Check that deletion works well (can only fail on Windows)
+ os.unlink(datasource)
+ self.assertFalse(os.path.exists(datasource))
+
+ def testGpxElevation(self):
+ # GPX without elevation data
+ datasource = os.path.join(TEST_DATA_DIR, 'noelev.gpx')
+ vl = QgsVectorLayer(u'{}|layername=routes'.format(datasource), u'test', u'ogr')
+ self.assertTrue(vl.isValid())
+ f = next(vl.getFeatures())
+ self.assertEqual(f.constGeometry().geometry().wkbType(), QgsWKBTypes.LineString)
+
+ # GPX with elevation data
+ datasource = os.path.join(TEST_DATA_DIR, 'elev.gpx')
+ vl = QgsVectorLayer(u'{}|layername=routes'.format(datasource), u'test', u'ogr')
+ self.assertTrue(vl.isValid())
+ f = next(vl.getFeatures())
+ self.assertEqual(f.constGeometry().geometry().wkbType(), QgsWKBTypes.LineString25D)
+ self.assertEqual(f.constGeometry().geometry().pointN(0).z(), 1)
+ self.assertEqual(f.constGeometry().geometry().pointN(1).z(), 2)
+ self.assertEqual(f.constGeometry().geometry().pointN(2).z(), 3)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/src/python/test_provider_oracle.py b/tests/src/python/test_provider_oracle.py
new file mode 100644
index 0000000..12ce1d8
--- /dev/null
+++ b/tests/src/python/test_provider_oracle.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+"""QGIS Unit tests for the Oracle provider.
+
+.. 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__ = '2016-07-06'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+import qgis # NOQA
+
+import os
+
+from qgis.core import QgsVectorLayer, QgsFeatureRequest, NULL
+
+from qgis.PyQt.QtCore import QSettings, QDate, QTime, QDateTime, QVariant
+
+from utilities import unitTestDataPath
+from qgis.testing import start_app, unittest
+from providertestbase import ProviderTestCase
+
+start_app()
+TEST_DATA_DIR = unitTestDataPath()
+
+
+class TestPyQgsOracleProvider(unittest.TestCase, ProviderTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ """Run before all tests"""
+ cls.dbconn = u"host=localhost port=1521 user='QGIS' password='qgis'"
+ if 'QGIS_ORACLETEST_DB' in os.environ:
+ cls.dbconn = os.environ['QGIS_ORACLETEST_DB']
+ # Create test layers
+ cls.vl = QgsVectorLayer(
+ cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="QGIS"."SOME_DATA" (GEOM) sql=', 'test', 'oracle')
+ assert(cls.vl.isValid())
+ cls.provider = cls.vl.dataProvider()
+ cls.poly_vl = QgsVectorLayer(
+ cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="QGIS"."SOME_POLY_DATA" (GEOM) sql=', 'test', 'oracle')
+ assert(cls.poly_vl.isValid())
+ cls.poly_provider = cls.poly_vl.dataProvider()
+
+ @classmethod
+ def tearDownClass(cls):
+ """Run after all tests"""
+
+ def enableCompiler(self):
+ QSettings().setValue(u'/qgis/compileExpressions', True)
+
+ def disableCompiler(self):
+ QSettings().setValue(u'/qgis/compileExpressions', False)
+
+ def uncompiledFilters(self):
+ filters = set([
+ '(name = \'Apple\') is not null',
+ '"name" || \' \' || "name" = \'Orange Orange\'',
+ '"name" || \' \' || "cnt" = \'Orange 100\'',
+ '\'x\' || "name" IS NOT NULL',
+ '\'x\' || "name" IS NULL',
+ 'false and NULL',
+ 'true and NULL',
+ 'NULL and false',
+ 'NULL and true',
+ 'NULL and NULL',
+ 'false or NULL',
+ 'true or NULL',
+ 'NULL or false',
+ 'NULL or true',
+ 'NULL or NULL',
+ 'not null',
+ 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))'])
+ return filters
+
+ # HERE GO THE PROVIDER SPECIFIC TESTS
+ def testDateTimeTypes(self):
+ vl = QgsVectorLayer('%s table="QGIS"."DATE_TIMES" sql=' %
+ (self.dbconn), "testdatetimes", "oracle")
+ self.assertTrue(vl.isValid())
+
+ fields = vl.dataProvider().fields()
+ self.assertEqual(fields.at(fields.indexFromName(
+ 'date_field')).type(), QVariant.Date)
+ self.assertEqual(fields.at(fields.indexFromName(
+ 'datetime_field')).type(), QVariant.DateTime)
+
+ f = next(vl.getFeatures(QgsFeatureRequest()))
+
+ date_idx = vl.fieldNameIndex('date_field')
+ self.assertTrue(isinstance(f.attributes()[date_idx], QDate))
+ self.assertEqual(f.attributes()[date_idx], QDate(2004, 3, 4))
+ datetime_idx = vl.fieldNameIndex('datetime_field')
+ self.assertTrue(isinstance(f.attributes()[datetime_idx], QDateTime))
+ self.assertEqual(f.attributes()[datetime_idx], QDateTime(
+ QDate(2004, 3, 4), QTime(13, 41, 52)))
+
+ def testDefaultValue(self):
+ self.assertEqual(self.provider.defaultValue(1), NULL)
+ self.assertEqual(self.provider.defaultValue(2), "'qgis'")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/src/python/test_provider_shapefile.py b/tests/src/python/test_provider_shapefile.py
index d198941..31577de 100644
--- a/tests/src/python/test_provider_shapefile.py
+++ b/tests/src/python/test_provider_shapefile.py
@@ -12,20 +12,25 @@ __copyright__ = 'Copyright 2015, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
-import qgis
import os
import tempfile
import shutil
import glob
+import osgeo.gdal
+import sys
-from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsProviderRegistry
-from PyQt4.QtCore import QSettings
-from qgis.testing import (start_app,
- unittest
- )
+from qgis.core import QgsFeature, QgsField, QgsGeometry, QgsVectorLayer, QgsFeatureRequest, QgsVectorDataProvider
+from PyQt4.QtCore import QSettings, QVariant
+from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
from providertestbase import ProviderTestCase
+try:
+ from osgeo import gdal
+ gdal_ok = True
+except:
+ gdal_ok = False
+
start_app()
TEST_DATA_DIR = unitTestDataPath()
@@ -55,11 +60,13 @@ class TestPyQgsShapefileProvider(unittest.TestCase, ProviderTestCase):
assert (cls.vl_poly.isValid())
cls.poly_provider = cls.vl_poly.dataProvider()
+ cls.dirs_to_cleanup = [cls.basetestpath, cls.repackfilepath]
+
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
- shutil.rmtree(cls.basetestpath, True)
- shutil.rmtree(cls.repackfilepath, True)
+ for dirname in cls.dirs_to_cleanup:
+ shutil.rmtree(dirname, True)
def enableCompiler(self):
QSettings().setValue(u'/qgis/compileExpressions', True)
@@ -79,6 +86,181 @@ class TestPyQgsShapefileProvider(unittest.TestCase, ProviderTestCase):
assert vl.commitChanges()
assert vl.selectedFeatureCount() == 0 or vl.selectedFeatures()[0]['pk'] == 1
+ def testUpdateMode(self):
+ """ Test that on-the-fly re-opening in update/read-only mode works """
+
+ tmpdir = tempfile.mkdtemp()
+ self.dirs_to_cleanup.append(tmpdir)
+ srcpath = os.path.join(TEST_DATA_DIR, 'provider')
+ for file in glob.glob(os.path.join(srcpath, 'shapefile.*')):
+ shutil.copy(os.path.join(srcpath, file), tmpdir)
+ datasource = os.path.join(tmpdir, 'shapefile.shp')
+
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+ caps = vl.dataProvider().capabilities()
+ self.assertTrue(caps & QgsVectorDataProvider.AddFeatures)
+ self.assertTrue(caps & QgsVectorDataProvider.DeleteFeatures)
+ self.assertTrue(caps & QgsVectorDataProvider.ChangeAttributeValues)
+ self.assertTrue(caps & QgsVectorDataProvider.AddAttributes)
+ self.assertTrue(caps & QgsVectorDataProvider.DeleteAttributes)
+ self.assertTrue(caps & QgsVectorDataProvider.CreateSpatialIndex)
+ self.assertTrue(caps & QgsVectorDataProvider.SelectAtId)
+ self.assertTrue(caps & QgsVectorDataProvider.ChangeGeometries)
+ #self.assertTrue(caps & QgsVectorDataProvider.ChangeFeatures)
+
+ # We should be really opened in read-only mode even if write capabilities are declared
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-only")
+
+ # Unbalanced call to leaveUpdateMode()
+ self.assertFalse(vl.dataProvider().leaveUpdateMode())
+
+ # Test that startEditing() / commitChanges() plays with enterUpdateMode() / leaveUpdateMode()
+ self.assertTrue(vl.startEditing())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+ self.assertTrue(vl.dataProvider().isValid())
+
+ self.assertTrue(vl.commitChanges())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-only")
+ self.assertTrue(vl.dataProvider().isValid())
+
+ # Manual enterUpdateMode() / leaveUpdateMode() with 2 depths
+ self.assertTrue(vl.dataProvider().enterUpdateMode())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+ caps = vl.dataProvider().capabilities()
+ self.assertTrue(caps & QgsVectorDataProvider.AddFeatures)
+
+ f = QgsFeature()
+ f.setAttributes([200])
+ f.setGeometry(QgsGeometry.fromWkt('Point (2 49)'))
+ (ret, feature_list) = vl.dataProvider().addFeatures([f])
+ self.assertTrue(ret)
+ fid = feature_list[0].id()
+
+ features = [f_iter for f_iter in vl.getFeatures(QgsFeatureRequest().setFilterFid(fid))]
+ values = [f_iter['pk'] for f_iter in features]
+ self.assertEquals(values, [200])
+
+ got_geom = [f_iter.geometry() for f_iter in features][0].geometry()
+ self.assertEquals((got_geom.x(), got_geom.y()), (2.0, 49.0))
+
+ self.assertTrue(vl.dataProvider().changeGeometryValues({fid: QgsGeometry.fromWkt('Point (3 50)')}))
+ self.assertTrue(vl.dataProvider().changeAttributeValues({fid: {0: 100}}))
+
+ features = [f_iter for f_iter in vl.getFeatures(QgsFeatureRequest().setFilterFid(fid))]
+ values = [f_iter['pk'] for f_iter in features]
+
+ got_geom = [f_iter.geometry() for f_iter in features][0].geometry()
+ self.assertEquals((got_geom.x(), got_geom.y()), (3.0, 50.0))
+
+ self.assertTrue(vl.dataProvider().deleteFeatures([fid]))
+
+ # Check that it has really disappeared
+ if gdal_ok:
+ gdal.PushErrorHandler('CPLQuietErrorHandler')
+ features = [f_iter for f_iter in vl.getFeatures(QgsFeatureRequest().setFilterFid(fid))]
+ if gdal_ok:
+ gdal.PopErrorHandler()
+ self.assertEquals(features, [])
+
+ self.assertTrue(vl.dataProvider().addAttributes([QgsField("new_field", QVariant.Int, "integer")]))
+ self.assertTrue(vl.dataProvider().deleteAttributes([len(vl.dataProvider().fields()) - 1]))
+
+ self.assertTrue(vl.startEditing())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ self.assertTrue(vl.commitChanges())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ self.assertTrue(vl.dataProvider().enterUpdateMode())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ self.assertTrue(vl.dataProvider().leaveUpdateMode())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ self.assertTrue(vl.dataProvider().leaveUpdateMode())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-only")
+
+ # Test that update mode will be implictly enabled if doing an action
+ # that requires update mode
+ (ret, _) = vl.dataProvider().addFeatures([QgsFeature()])
+ self.assertTrue(ret)
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+
+ def testUpdateModeFailedReopening(self):
+ ''' Test that methods on provider don't crash after a failed reopening '''
+
+ # Windows doesn't like removing files opened by OGR, whatever
+ # their open mode, so that makes it hard to test
+ if sys.platform == 'win32':
+ return
+
+ tmpdir = tempfile.mkdtemp()
+ self.dirs_to_cleanup.append(tmpdir)
+ srcpath = os.path.join(TEST_DATA_DIR, 'provider')
+ for file in glob.glob(os.path.join(srcpath, 'shapefile.*')):
+ shutil.copy(os.path.join(srcpath, file), tmpdir)
+ datasource = os.path.join(tmpdir, 'shapefile.shp')
+
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+
+ os.unlink(datasource)
+
+ self.assertFalse(vl.dataProvider().enterUpdateMode())
+ self.assertFalse(vl.dataProvider().enterUpdateMode())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "invalid")
+
+ self.assertFalse(vl.dataProvider().isValid())
+ self.assertEquals(len([f for f in vl.dataProvider().getFeatures()]), 0)
+ self.assertEquals(len(vl.dataProvider().subLayers()), 0)
+ self.assertFalse(vl.dataProvider().setSubsetString('TRUE'))
+ (ret, _) = vl.dataProvider().addFeatures([QgsFeature()])
+ self.assertFalse(ret)
+ self.assertFalse(vl.dataProvider().deleteFeatures([1]))
+ self.assertFalse(vl.dataProvider().addAttributes([QgsField()]))
+ self.assertFalse(vl.dataProvider().deleteAttributes([1]))
+ self.assertFalse(vl.dataProvider().changeGeometryValues({0: QgsGeometry.fromWkt('Point (3 50)')}))
+ self.assertFalse(vl.dataProvider().changeAttributeValues({0: {0: 0}}))
+ self.assertFalse(vl.dataProvider().createSpatialIndex())
+ self.assertFalse(vl.dataProvider().createAttributeIndex(0))
+
+ def testreloadData(self):
+ ''' Test reloadData() '''
+
+ tmpdir = tempfile.mkdtemp()
+ self.dirs_to_cleanup.append(tmpdir)
+ srcpath = os.path.join(TEST_DATA_DIR, 'provider')
+ for file in glob.glob(os.path.join(srcpath, 'shapefile.*')):
+ shutil.copy(os.path.join(srcpath, file), tmpdir)
+ datasource = os.path.join(tmpdir, 'shapefile.shp')
+
+ vl1 = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+ vl2 = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+ self.assertTrue(vl1.startEditing())
+ self.assertTrue(vl1.deleteAttributes([1]))
+ self.assertTrue(vl1.commitChanges())
+ self.assertEquals(len(vl1.fields()) + 1, len(vl2.fields()))
+ # Reload
+ vl2.reload()
+ # And now check that fields are up-to-date
+ self.assertEquals(len(vl1.fields()), len(vl2.fields()))
+
+ def testDeleteGeometry(self):
+ ''' Test changeGeometryValues() with a null geometry '''
+
+ tmpdir = tempfile.mkdtemp()
+ self.dirs_to_cleanup.append(tmpdir)
+ srcpath = os.path.join(TEST_DATA_DIR, 'provider')
+ for file in glob.glob(os.path.join(srcpath, 'shapefile.*')):
+ shutil.copy(os.path.join(srcpath, file), tmpdir)
+ datasource = os.path.join(tmpdir, 'shapefile.shp')
+
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+ self.assertTrue(vl.dataProvider().changeGeometryValues({0: QgsGeometry()}))
+ vl = None
+
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
+ fet = next(vl.getFeatures())
+ self.assertIsNone(fet.geometry())
if __name__ == '__main__':
unittest.main()
diff --git a/tests/src/python/test_provider_spatialite.py b/tests/src/python/test_provider_spatialite.py
index ef7ca26..21dd102 100644
--- a/tests/src/python/test_provider_spatialite.py
+++ b/tests/src/python/test_provider_spatialite.py
@@ -15,6 +15,7 @@ __revision__ = '$Format:%H$'
import qgis
import os
import shutil
+import sys
import tempfile
from qgis.core import QgsVectorLayer, QgsPoint, QgsFeature
@@ -41,6 +42,21 @@ def die(error_message):
raise Exception(error_message)
+def count_opened_filedescriptors(filename_to_test):
+ count = -1
+ if sys.platform.startswith('linux'):
+ count = 0
+ open_files_dirname = '/proc/%d/fd' % os.getpid()
+ filenames = os.listdir(open_files_dirname)
+ for filename in filenames:
+ full_filename = open_files_dirname + '/' + filename
+ if os.path.exists(full_filename):
+ link = os.readlink(full_filename)
+ if os.path.basename(link) == os.path.basename(filename_to_test):
+ count += 1
+ return count
+
+
class TestQgsSpatialiteProvider(unittest.TestCase, ProviderTestCase):
@classmethod
@@ -214,5 +230,73 @@ class TestQgsSpatialiteProvider(unittest.TestCase, ProviderTestCase):
layer = None
os.unlink(corrupt_dbname)
+ def testNoDanglingFileDescriptorAfterCloseVariant1(self):
+ ''' Test that when closing the provider all file handles are released '''
+
+ temp_dbname = self.dbname + '.no_dangling_test1'
+ shutil.copy(self.dbname, temp_dbname)
+
+ vl = QgsVectorLayer("dbname=%s table=test_n (geometry)" % temp_dbname, "test_n", "spatialite")
+ self.assertTrue(vl.isValid())
+ # The iterator will take one extra connection
+ myiter = vl.getFeatures()
+ print(vl.featureCount())
+ # Consume one feature but the iterator is still opened
+ f = next(myiter)
+ self.assertTrue(f.isValid())
+
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(temp_dbname), 2)
+
+ # does NO release one file descriptor, because shared with the iterator
+ del vl
+
+ # Non portable, but Windows testing is done with trying to unlink
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(temp_dbname), 2)
+
+ f = next(myiter)
+ self.assertTrue(f.isValid())
+
+ # Should release one file descriptor
+ del myiter
+
+ # Non portable, but Windows testing is done with trying to unlink
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(temp_dbname), 0)
+
+ # Check that deletion works well (can only fail on Windows)
+ os.unlink(temp_dbname)
+ self.assertFalse(os.path.exists(temp_dbname))
+
+ def testNoDanglingFileDescriptorAfterCloseVariant2(self):
+ ''' Test that when closing the provider all file handles are released '''
+
+ temp_dbname = self.dbname + '.no_dangling_test2'
+ shutil.copy(self.dbname, temp_dbname)
+
+ vl = QgsVectorLayer("dbname=%s table=test_n (geometry)" % temp_dbname, "test_n", "spatialite")
+ self.assertTrue(vl.isValid())
+ self.assertTrue(vl.isValid())
+ # Consume all features.
+ myiter = vl.getFeatures()
+ for feature in myiter:
+ pass
+ # The iterator is closed
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(temp_dbname), 2)
+
+ # Should release one file descriptor
+ del vl
+
+ # Non portable, but Windows testing is done with trying to unlink
+ if sys.platform.startswith('linux'):
+ self.assertEqual(count_opened_filedescriptors(temp_dbname), 0)
+
+ # Check that deletion works well (can only fail on Windows)
+ os.unlink(temp_dbname)
+ self.assertFalse(os.path.exists(temp_dbname))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/src/python/test_provider_tabfile.py b/tests/src/python/test_provider_tabfile.py
index a6ff204..365c4ed 100644
--- a/tests/src/python/test_provider_tabfile.py
+++ b/tests/src/python/test_provider_tabfile.py
@@ -17,12 +17,13 @@ import tempfile
import shutil
import glob
-from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsProviderRegistry
+from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsVectorDataProvider
from PyQt4.QtCore import QSettings, QDate, QTime, QDateTime, QVariant
from qgis.testing import (
start_app,
unittest
)
+import osgeo.gdal
from utilities import unitTestDataPath
start_app()
@@ -55,5 +56,30 @@ class TestPyQgsTabfileProvider(unittest.TestCase):
assert isinstance(f.attributes()[datetime_idx], QDateTime)
self.assertEqual(f.attributes()[datetime_idx], QDateTime(QDate(2004, 5, 3), QTime(13, 41, 00)))
+ # This test fails with GDAL version < 2 because tab update is new in GDAL 2.0
+ # @unittest.expectedFailure(int(osgeo.gdal.VersionInfo()[:1]) < 2)
+ def testUpdateMode(self):
+ """ Test that on-the-fly re-opening in update/read-only mode works """
+
+ if int(osgeo.gdal.VersionInfo()[:1]) < 2:
+ return
+
+ basetestfile = os.path.join(TEST_DATA_DIR, 'tab_file.tab')
+ vl = QgsVectorLayer(u'{}|layerid=0'.format(basetestfile), u'test', u'ogr')
+ caps = vl.dataProvider().capabilities()
+ self.assertTrue(caps & QgsVectorDataProvider.AddFeatures)
+
+ # We should be really opened in read-only mode even if write capabilities are declared
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-only")
+
+ # Test that startEditing() / commitChanges() plays with enterUpdateMode() / leaveUpdateMode()
+ self.assertTrue(vl.startEditing())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-write")
+ self.assertTrue(vl.dataProvider().isValid())
+
+ self.assertTrue(vl.commitChanges())
+ self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-only")
+ self.assertTrue(vl.dataProvider().isValid())
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/src/python/test_qgscolorbuttonv2.py b/tests/src/python/test_qgscolorbuttonv2.py
new file mode 100644
index 0000000..595c6e5
--- /dev/null
+++ b/tests/src/python/test_qgscolorbuttonv2.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+"""QGIS Unit tests for QgsColorButtonV2.
+
+.. 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__ = '25/05/2016'
+__copyright__ = 'Copyright 2016, 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 QgsColorButtonV2
+from qgis.testing import start_app, unittest
+from qgis.PyQt.QtGui import QColor
+start_app()
+
+
+class TestQgsColorButtonV2(unittest.TestCase):
+
+ def testClearingColors(self):
+ """
+ Test setting colors to transparent
+ """
+
+ # start with a valid color
+ button = QgsColorButtonV2()
+ button.setAllowAlpha(True)
+ button.setColor(QColor(255, 100, 200, 255))
+ self.assertEqual(button.color(), QColor(255, 100, 200, 255))
+
+ # now set to no color
+ button.setToNoColor()
+ # ensure that only the alpha channel has changed - not the other color components
+ self.assertEqual(button.color(), QColor(255, 100, 200, 0))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/src/python/test_qgscomposerview.py b/tests/src/python/test_qgscomposerview.py
new file mode 100644
index 0000000..05b8807
--- /dev/null
+++ b/tests/src/python/test_qgscomposerview.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+"""QGIS Unit tests for QgsComposerView.
+
+.. 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__ = '29/05/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+import qgis # NOQA
+
+import os
+
+from qgis.gui import (QgsComposerView)
+from qgis.PyQt.QtCore import QRectF
+from qgis.PyQt.QtGui import QTransform
+
+from qgis.testing import start_app, unittest
+
+start_app()
+
+
+class TestQgsComposerView(unittest.TestCase):
+
+ def testScaleSafe(self):
+ """ test scaleSafe method """
+
+ view = QgsComposerView()
+ view.fitInView(QRectF(0, 0, 10, 10))
+ scale = view.transform().m11()
+ view.scaleSafe(2)
+ self.assertAlmostEqual(view.transform().m11(), 2)
+ view.scaleSafe(4)
+ self.assertAlmostEqual(view.transform().m11(), 8)
+
+ # try to zoom in heaps
+ view.scaleSafe(99999999)
+ # assume we have hit the limit
+ scale = view.transform().m11()
+ view.scaleSafe(2)
+ self.assertAlmostEqual(view.transform().m11(), scale)
+
+ view.setTransform(QTransform.fromScale(1, 1))
+ self.assertAlmostEqual(view.transform().m11(), 1)
+ # test zooming out
+ view.scaleSafe(0.5)
+ self.assertAlmostEqual(view.transform().m11(), 0.5)
+ view.scaleSafe(0.1)
+ self.assertAlmostEqual(view.transform().m11(), 0.05)
+
+ # try zooming out heaps
+ view.scaleSafe(0.000000001)
+ # assume we have hit the limit
+ scale = view.transform().m11()
+ view.scaleSafe(0.5)
+ self.assertAlmostEqual(view.transform().m11(), scale)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/src/python/test_qgsexpression.py b/tests/src/python/test_qgsexpression.py
index e52463a..bee50ab 100644
--- a/tests/src/python/test_qgsexpression.py
+++ b/tests/src/python/test_qgsexpression.py
@@ -15,7 +15,7 @@ __revision__ = '$Format:%H$'
import qgis
from qgis.testing import unittest
from qgis.utils import qgsfunction
-from qgis.core import QgsExpression
+from qgis.core import QgsExpression, QgsFeatureRequest
class TestQgsExpressionCustomFunctions(unittest.TestCase):
@@ -45,6 +45,14 @@ class TestQgsExpressionCustomFunctions(unittest.TestCase):
def geomtest(values, feature, parent):
pass
+ @qgsfunction(args=0, group='testing', register=False)
+ def no_referenced_columns_set(values, feature, parent):
+ return 1
+
+ @qgsfunction(args=0, group='testing', register=False, referenced_columns=['a', 'b'])
+ def referenced_columns_set(values, feature, parent):
+ return 2
+
def tearDown(self):
QgsExpression.unregisterFunction('testfun')
@@ -117,6 +125,16 @@ class TestQgsExpressionCustomFunctions(unittest.TestCase):
success = QgsExpression.registerFunction(self.geomtest)
self.assertTrue(success)
+ def testReferencedColumnsNoSet(self):
+ success = QgsExpression.registerFunction(self.no_referenced_columns_set)
+ exp = QgsExpression('no_referenced_columns_set()')
+ self.assertEqual(exp.referencedColumns(), [QgsFeatureRequest.AllAttributes])
+
+ def testReferencedColumnsSet(self):
+ success = QgsExpression.registerFunction(self.referenced_columns_set)
+ exp = QgsExpression('referenced_columns_set()')
+ self.assertEqual(exp.referencedColumns(), ['a', 'b'])
+
def testCantOverrideBuiltinsWithUnregister(self):
success = QgsExpression.unregisterFunction("sqrt")
self.assertFalse(success)
diff --git a/tests/src/python/test_qgsfeatureiterator.py b/tests/src/python/test_qgsfeatureiterator.py
index 2e6da68..526157b 100644
--- a/tests/src/python/test_qgsfeatureiterator.py
+++ b/tests/src/python/test_qgsfeatureiterator.py
@@ -15,10 +15,11 @@ __revision__ = '$Format:%H$'
import qgis
import os
-from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature
+from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsField, NULL
from qgis.testing import (start_app,
unittest
)
+from PyQt4.QtCore import QVariant
from utilities import (unitTestDataPath,
compareWkt
)
@@ -111,5 +112,52 @@ class TestQgsFeatureIterator(unittest.TestCase):
feat['Staff'] = 2
vl.addFeature(feat)
+ def test_ExpressionFieldNested(self):
+ myShpFile = os.path.join(TEST_DATA_DIR, 'points.shp')
+ layer = QgsVectorLayer(myShpFile, 'Points', 'ogr')
+ self.assertTrue(layer.isValid())
+
+ cnt = layer.pendingFields().count()
+ idx = layer.addExpressionField('"Staff"*2', QgsField('exp1', QVariant.LongLong))
+ idx = layer.addExpressionField('"exp1"-1', QgsField('exp2', QVariant.LongLong))
+
+ fet = next(layer.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(['exp2'], layer.fields())))
+ self.assertEqual(fet['Class'], NULL)
+ # nested virtual fields should make all these attributes be fetched
+ self.assertEqual(fet['Staff'], 2)
+ self.assertEqual(fet['exp2'], 3)
+ self.assertEqual(fet['exp1'], 4)
+
+ def test_ExpressionFieldNestedGeometry(self):
+ myShpFile = os.path.join(TEST_DATA_DIR, 'points.shp')
+ layer = QgsVectorLayer(myShpFile, 'Points', 'ogr')
+ self.assertTrue(layer.isValid())
+
+ cnt = layer.pendingFields().count()
+ idx = layer.addExpressionField('$x*2', QgsField('exp1', QVariant.LongLong))
+ idx = layer.addExpressionField('"exp1"/1.5', QgsField('exp2', QVariant.LongLong))
+
+ fet = next(layer.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(['exp2'], layer.fields())))
+ # nested virtual fields should have made geometry be fetched
+ self.assertEqual(fet['exp2'], -156)
+ self.assertEqual(fet['exp1'], -234)
+
+ def test_ExpressionFieldNestedCircular(self):
+ """ test circular virtual field definitions """
+
+ myShpFile = os.path.join(TEST_DATA_DIR, 'points.shp')
+ layer = QgsVectorLayer(myShpFile, 'Points', 'ogr')
+ self.assertTrue(layer.isValid())
+
+ cnt = layer.pendingFields().count()
+ idx = layer.addExpressionField('"exp3"*2', QgsField('exp1', QVariant.LongLong))
+ idx = layer.addExpressionField('"exp1"-1', QgsField('exp2', QVariant.LongLong))
+ idx = layer.addExpressionField('"exp2"*3', QgsField('exp3', QVariant.LongLong))
+
+ # really just testing that this doesn't hang/crash... there's no good result here!
+ fet = next(layer.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(['exp2'], layer.fields())))
+ self.assertEqual(fet['Class'], NULL)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/src/python/test_qgssymbollayerv2.py b/tests/src/python/test_qgssymbollayerv2.py
index bc96748..088247b 100644
--- a/tests/src/python/test_qgssymbollayerv2.py
+++ b/tests/src/python/test_qgssymbollayerv2.py
@@ -730,5 +730,19 @@ class TestQgsSymbolLayerV2(unittest.TestCase):
mMessage = 'Expected "%s" got "%s"' % (mExpectedValue, mValue)
assert mExpectedValue == mValue, mMessage
+ def testQgsVectorFieldSymbolLayer(self):
+ """
+ Test QgsVectorFieldSymbolLayer
+ """
+ # test colors, need to make sure colors are passed/retrieved from subsymbol
+ mSymbolLayer = QgsVectorFieldSymbolLayer.create()
+
+ mSymbolLayer.setColor(QColor(150, 50, 100))
+ self.assertEqual(mSymbolLayer.color(), QColor(150, 50, 100))
+ self.assertEqual(mSymbolLayer.subSymbol().color(), QColor(150, 50, 100))
+ mSymbolLayer.subSymbol().setColor(QColor(250, 150, 200))
+ self.assertEqual(mSymbolLayer.subSymbol().color(), QColor(250, 150, 200))
+ self.assertEqual(mSymbolLayer.color(), QColor(250, 150, 200))
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py
index 6849901..97ce5a2 100644
--- a/tests/src/python/test_qgsvectorlayer.py
+++ b/tests/src/python/test_qgsvectorlayer.py
@@ -58,6 +58,21 @@ def createLayerWithOnePoint():
return layer
+def createLayerWithTwoPoints():
+ layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
+ "addfeat", "memory")
+ pr = layer.dataProvider()
+ f = QgsFeature()
+ f.setAttributes(["test", 123])
+ f.setGeometry(QgsGeometry.fromPoint(QgsPoint(100, 200)))
+ f2 = QgsFeature()
+ f2.setAttributes(["test2", 457])
+ f2.setGeometry(QgsGeometry.fromPoint(QgsPoint(100, 200)))
+ assert pr.addFeatures([f, f2])
+ assert layer.pendingFeatureCount() == 2
+ return layer
+
+
def createJoinLayer():
joinLayer = QgsVectorLayer(
"Point?field=x:string&field=y:integer&field=z:integer",
@@ -69,8 +84,14 @@ def createJoinLayer():
f2 = QgsFeature()
f2.setAttributes(["bar", 456, 654])
f2.setGeometry(QgsGeometry.fromPoint(QgsPoint(2, 2)))
- assert pr.addFeatures([f1, f2])
- assert joinLayer.pendingFeatureCount() == 2
+ f3 = QgsFeature()
+ f3.setAttributes(["qar", 457, 111])
+ f3.setGeometry(QgsGeometry.fromPoint(QgsPoint(2, 2)))
+ f4 = QgsFeature()
+ f4.setAttributes(["a", 458, 19])
+ f4.setGeometry(QgsGeometry.fromPoint(QgsPoint(2, 2)))
+ assert pr.addFeatures([f1, f2, f3, f4])
+ assert joinLayer.pendingFeatureCount() == 4
return joinLayer
@@ -900,6 +921,24 @@ class TestQgsVectorLayer(unittest.TestCase):
assert f2[2] == "foo"
assert f2[3] == 321
+ def test_JoinStats(self):
+ """ test calculating min/max/uniqueValues on joined field """
+ joinLayer = createJoinLayer()
+ layer = createLayerWithTwoPoints()
+ QgsMapLayerRegistry.instance().addMapLayers([joinLayer, layer])
+
+ join = QgsVectorJoinInfo()
+ join.targetFieldName = "fldint"
+ join.joinLayerId = joinLayer.id()
+ join.joinFieldName = "y"
+ join.memoryCache = True
+ layer.addJoin(join)
+
+ # stats on joined fields should only include values present by join
+ self.assertEqual(layer.minimumValue(3), 111)
+ self.assertEqual(layer.maximumValue(3), 321)
+ self.assertEqual(set(layer.uniqueValues(3)), set([111, 321]))
+
def test_InvalidOperations(self):
layer = createLayerWithOnePoint()
diff --git a/tests/testdata/control_images/composer_picture/expected_composerpicture_issue_14644/expected_composerpicture_issue_14644.png b/tests/testdata/control_images/composer_picture/expected_composerpicture_issue_14644/expected_composerpicture_issue_14644.png
new file mode 100644
index 0000000..ff586a9
Binary files /dev/null and b/tests/testdata/control_images/composer_picture/expected_composerpicture_issue_14644/expected_composerpicture_issue_14644.png differ
diff --git a/tests/testdata/control_images/composer_picture/expected_composerpicture_issue_14644/expected_composerpicture_issue_14644_mask.png b/tests/testdata/control_images/composer_picture/expected_composerpicture_issue_14644/expected_composerpicture_issue_14644_mask.png
new file mode 100644
index 0000000..cf8d78c
Binary files /dev/null and b/tests/testdata/control_images/composer_picture/expected_composerpicture_issue_14644/expected_composerpicture_issue_14644_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_basic/expected_legend_basic_mask.png b/tests/testdata/control_images/legend/expected_legend_basic/expected_legend_basic_mask.png
index 52f5ef1..9599faa 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_basic/expected_legend_basic_mask.png and b/tests/testdata/control_images/legend/expected_legend_basic/expected_legend_basic_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_big_marker/expected_legend_big_marker_mask.png b/tests/testdata/control_images/legend/expected_legend_big_marker/expected_legend_big_marker_mask.png
index 135770c..3eabee2 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_big_marker/expected_legend_big_marker_mask.png and b/tests/testdata/control_images/legend/expected_legend_big_marker/expected_legend_big_marker_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression.png b/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression.png
index 383075e..2372844 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression.png and b/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression_mask.png b/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression_mask.png
index 239d050..4a2e874 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression_mask.png and b/tests/testdata/control_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map.png b/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map.png
index 2f2e292..4d3ee3a 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map.png and b/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map_mask.png b/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map_mask.png
index 4f0a269..d91b7a2 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map_mask.png and b/tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_filter_by_map_dupe/expected_legend_filter_by_map_dupe_mask.png b/tests/testdata/control_images/legend/expected_legend_filter_by_map_dupe/expected_legend_filter_by_map_dupe_mask.png
index 3f6a2b7..a361927 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_filter_by_map_dupe/expected_legend_filter_by_map_dupe_mask.png and b/tests/testdata/control_images/legend/expected_legend_filter_by_map_dupe/expected_legend_filter_by_map_dupe_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon.png b/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon.png
index 383075e..2372844 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon.png and b/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon_mask.png b/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon_mask.png
index 239d050..6521453 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon_mask.png and b/tests/testdata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_long_symbol_text/expected_legend_long_symbol_text_mask.png b/tests/testdata/control_images/legend/expected_legend_long_symbol_text/expected_legend_long_symbol_text_mask.png
index ebe1fb3..3ea7733 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_long_symbol_text/expected_legend_long_symbol_text_mask.png and b/tests/testdata/control_images/legend/expected_legend_long_symbol_text/expected_legend_long_symbol_text_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_mapunits/expected_legend_mapunits_mask.png b/tests/testdata/control_images/legend/expected_legend_mapunits/expected_legend_mapunits_mask.png
index 74a3009..1f6d335 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_mapunits/expected_legend_mapunits_mask.png and b/tests/testdata/control_images/legend/expected_legend_mapunits/expected_legend_mapunits_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_raster_border/expected_legend_raster_border_mask.png b/tests/testdata/control_images/legend/expected_legend_raster_border/expected_legend_raster_border_mask.png
index fce1c35..2147885 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_raster_border/expected_legend_raster_border_mask.png and b/tests/testdata/control_images/legend/expected_legend_raster_border/expected_legend_raster_border_mask.png differ
diff --git a/tests/testdata/control_images/legend/expected_legend_three_columns/expected_legend_three_columns_mask.png b/tests/testdata/control_images/legend/expected_legend_three_columns/expected_legend_three_columns_mask.png
index 0b50ada..cf00342 100644
Binary files a/tests/testdata/control_images/legend/expected_legend_three_columns/expected_legend_three_columns_mask.png and b/tests/testdata/control_images/legend/expected_legend_three_columns/expected_legend_three_columns_mask.png differ
diff --git a/tests/testdata/elev.gpx b/tests/testdata/elev.gpx
new file mode 100644
index 0000000..961a591
--- /dev/null
+++ b/tests/testdata/elev.gpx
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<gpx version="1.1" creator="GDAL 1.11.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
+<metadata><bounds minlat="56.134840267383296" minlon="-3.877982991747363" maxlat="56.135514185147493" maxlon="-3.852565020533009"/></metadata>
+<rte>
+ <rtept lat="56.134840267383296" lon="-3.877982991747363">
+ <ele>1</ele>
+ </rtept>
+ <rtept lat="56.134933668432737" lon="-3.865962243986197">
+ <ele>2</ele>
+ </rtept>
+ <rtept lat="56.135514185147493" lon="-3.852565020533009">
+ <ele>3</ele>
+ </rtept>
+</rte>
+</gpx>
diff --git a/tests/testdata/noelev.gpx b/tests/testdata/noelev.gpx
new file mode 100644
index 0000000..2c56fad
--- /dev/null
+++ b/tests/testdata/noelev.gpx
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<gpx version="1.1" creator="GDAL 1.11.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
+<metadata><bounds minlat="56.134840267383240" minlon="-3.877982991747359" maxlat="56.135514185147436" maxlon="-3.852565020533005"/></metadata>
+<rte>
+ <rtept lat="56.13484026738324" lon="-3.877982991747359">
+ </rtept>
+ <rtept lat="56.13493366843268" lon="-3.865962243986193">
+ </rtept>
+ <rtept lat="56.135514185147436" lon="-3.852565020533005">
+ </rtept>
+</rte>
+</gpx>
diff --git a/tests/testdata/provider/testdata_oracle.sql b/tests/testdata/provider/testdata_oracle.sql
new file mode 100644
index 0000000..4117c99
--- /dev/null
+++ b/tests/testdata/provider/testdata_oracle.sql
@@ -0,0 +1,32 @@
+CREATE TABLE QGIS.SOME_DATA ( "pk" INTEGER PRIMARY KEY, "cnt" INTEGER, "name" VARCHAR2(100) DEFAULT 'qgis', "name2" VARCHAR2(100) DEFAULT 'qgis', "num_char" VARCHAR2(100), GEOM SDO_GEOMETRY);
+
+INSERT INTO QGIS.SOME_DATA ("pk", "cnt", "name", "name2", "num_char", GEOM)
+ SELECT 5, -200, NULL, 'NuLl', '5', SDO_GEOMETRY( 2001,4326,SDO_POINT_TYPE(-71.123, 78.23, NULL), NULL, NULL) from dual
+ UNION ALL SELECT 3, 300, 'Pear', 'PEaR', '3', NULL from dual
+ UNION ALL SELECT 1, 100, 'Orange', 'oranGe', '1', SDO_GEOMETRY( 2001,4326,SDO_POINT_TYPE(-70.332, 66.33, NULL), NULL, NULL) from dual
+ UNION ALL SELECT 2, 200, 'Apple', 'Apple', '2', SDO_GEOMETRY( 2001,4326,SDO_POINT_TYPE(-68.2, 70.8, NULL), NULL, NULL) from dual
+ UNION ALL SELECT 4, 400, 'Honey', 'Honey', '4', SDO_GEOMETRY( 2001,4326,SDO_POINT_TYPE(-65.32, 78.3, NULL), NULL, NULL) from dual;
+
+INSERT INTO user_sdo_geom_metadata (TABLE_NAME, COLUMN_NAME, DIMINFO, SRID) VALUES ( 'SOME_DATA', 'GEOM', sdo_dim_array(sdo_dim_element('X',-75,-55,0.005),sdo_dim_element('Y',65,85,0.005)),4326);
+
+CREATE INDEX some_data_spatial_idx ON QGIS.SOME_DATA(GEOM) INDEXTYPE IS MDSYS.SPATIAL_INDEX;
+
+CREATE TABLE QGIS.SOME_POLY_DATA ( "pk" INTEGER PRIMARY KEY, GEOM SDO_GEOMETRY);
+
+INSERT INTO QGIS.SOME_POLY_DATA ("pk", GEOM)
+ SELECT 1, SDO_GEOMETRY( 2003,4326,NULL, SDO_ELEM_INFO_ARRAY(1,1003,1), SDO_ORDINATE_ARRAY(-69.0,81.4 , -69.0,80.2 , -73.7,80.2 , -73.7,76.3 , -74.9,76.3 , -74.9,81.4 , -69.0,81.4)) from dual
+ UNION ALL SELECT 2, SDO_GEOMETRY( 2003,4326,NULL, SDO_ELEM_INFO_ARRAY(1,1003,1), SDO_ORDINATE_ARRAY(-67.6,81.2 , -66.3,81.2 , -66.3,76.9 , -67.6,76.9 , -67.6,81.2))from dual
+ UNION ALL SELECT 3, SDO_GEOMETRY( 2003,4326,NULL, SDO_ELEM_INFO_ARRAY(1,1003,1), SDO_ORDINATE_ARRAY(-68.4,75.8 , -67.5,72.6 , -68.6,73.7 , -70.2,72.9 , -68.4,75.8)) from dual
+ UNION ALL SELECT 4, NULL from dual;
+
+
+INSERT INTO user_sdo_geom_metadata (TABLE_NAME, COLUMN_NAME, DIMINFO, SRID) VALUES ( 'SOME_POLY_DATA', 'GEOM', sdo_dim_array(sdo_dim_element('X',-80,-55,0.005),sdo_dim_element('Y',65,85,0.005)),4326);
+
+CREATE INDEX some_poly_data_spatial_idx ON QGIS.SOME_POLY_DATA(GEOM) INDEXTYPE IS MDSYS.SPATIAL_INDEX;
+
+
+CREATE TABLE QGIS.DATE_TIMES ( "id" INTEGER PRIMARY KEY, "date_field" DATE, "datetime_field" TIMESTAMP );
+
+INSERT INTO QGIS.DATE_TIMES ("id", "date_field", "datetime_field" ) VALUES (1, DATE '2004-03-04', TIMESTAMP '2004-03-04 13:41:52' );
+
+COMMIT;
diff --git a/tests/testdata/raster/test.asc b/tests/testdata/raster/test.asc
new file mode 100644
index 0000000..8fc0131
--- /dev/null
+++ b/tests/testdata/raster/test.asc
@@ -0,0 +1,6 @@
+ncols 7
+nrows 1
+xllcorner 0
+yllcorner 0
+cellsize 1
+ -999.9 -999.987 1.2345678 123456 1234567 -999.9876 1.2345678901234
diff --git a/tests/testdata/svg/issue_14644.svg b/tests/testdata/svg/issue_14644.svg
new file mode 100644
index 0000000..9f5f971
--- /dev/null
+++ b/tests/testdata/svg/issue_14644.svg
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="242.95674"
+ height="88.58268"
+ viewbox="0 0 245 90"
+ id="svg3960"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="qgis_problem.svg">
+ <defs
+ id="defs3962" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2"
+ inkscape:cx="167.38856"
+ inkscape:cy="12.175374"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1855"
+ inkscape:window-height="1056"
+ inkscape:window-x="1345"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ units="cm" />
+ <metadata
+ id="metadata3965">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-122.7856,54.496612)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:46.9240303px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#999999;fill-opacity:1;stroke:none"
+ x="257.45532"
+ y="33.536175"
+ id="text3831-0"
+ sodipodi:linespacing="125%"><tspan
+ id="tspan3833-2"
+ sodipodi:role="line"
+ x="257.45532"
+ y="33.536175">Talk</tspan></text>
+ <path
+ inkscape:export-ydpi="300.08182"
+ inkscape:export-xdpi="300.08182"
+ inkscape:export-filename=""
+ clip-path="none"
+ style="display:inline;fill:#80808f;fill-opacity:1"
+ d="m 154.59272,-22.11995 c 0.435,0.257398 0.832,0.533097 1.17019,0.849395 1.1575,1.082353 2.28139,2.683174 2.09539,2.989872 -0.05,0.092 -1.41289,1.128324 -3.01358,2.292017 l -2.90768,2.098448 -1.2877,-0.787795 -1.26989,-0.820196 -0.137,-3.888867 -0.187,-3.874268 1.2034,-0.054 c 1.39269,-0.053 3.02958,0.424797 4.33367,1.195983 z m 4.17368,5.551238 0.27999,1.787709 c 0.277,1.886729 0.111,5.5380377 -0.27599,6.177644 -0.127,0.2111988 -1.5082,0.347998 -3.08789,0.2815984 -1.57979,-0.067 [...]
+ id="path3918-6"
+ inkscape:connector-curvature="0" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text3077-7"
+ y="32.731239"
+ x="256.65042"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:46.9240303px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#006500;fill-opacity:1;stroke:none"
+ xml:space="preserve"><tspan
+ y="32.731239"
+ x="256.65042"
+ sodipodi:role="line"
+ id="tspan3875-7">Talk</tspan></text>
+ <g
+ style="display:inline;fill:#000098;fill-opacity:1"
+ id="g3815-5"
+ transform="matrix(1.0425656,0,0,0.72452471,50.973221,-59.466354)">
+ <path
+ sodipodi:nodetypes="cscscc"
+ inkscape:connector-curvature="0"
+ id="path3817-8"
+ d="m 223.195,72.653166 c 10.3555,1.148817 16.80222,5.350565 16.82722,11.886671 0.0232,6.052693 -6.44914,11.048103 -16.7397,11.818553 5.84387,-1.06162 12.78687,-5.88369 12.78687,-11.682316 0,-6.576511 -7.78791,-10.843594 -12.67003,-11.903701 -0.17966,0.110931 -0.16845,-0.09826 -0.20436,-0.119207 z"
+ style="fill:#000098;fill-opacity:1;stroke:none" />
+ <path
+ style="fill:#000098;fill-opacity:1;stroke:none"
+ d="m 230.54121,67.335169 c 15.00178,1.664265 24.341,7.751243 24.37721,17.219952 0.0336,8.7684 -9.34272,16.005139 -24.25042,17.121269 8.46588,-1.53794 18.52405,-8.523567 18.52405,-16.923905 0,-9.527243 -11.28217,-15.70887 -18.35479,-17.244623 -0.26027,0.160703 -0.24403,-0.142347 -0.29605,-0.172693 z"
+ id="path3819-5"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscscc" />
+ <path
+ sodipodi:nodetypes="cscscc"
+ inkscape:connector-curvature="0"
+ id="path3821-4"
+ d="m 238.7263,63.198946 c 18.61555,2.065169 30.20449,9.618436 30.24943,21.368059 0.0417,10.880616 -11.59329,19.860605 -30.0921,21.245605 10.50523,-1.90842 22.9863,-10.576806 22.9863,-21.000699 0,-11.822257 -13.99993,-19.492973 -22.77626,-21.398673 -0.32297,0.199415 -0.30282,-0.176637 -0.36737,-0.214292 z"
+ style="fill:#000098;fill-opacity:1;stroke:none" />
+ </g>
+ <text
+ clip-path="none"
+ sodipodi:linespacing="125%"
+ id="text3910-8"
+ y="-2.5704708"
+ x="129.89906"
+ style="font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:61.58447266px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold Oblique';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#999999;fill-opacity:1;stroke:none"
+ xml:space="preserve"><tspan
+ y="-2.5704708"
+ x="129.89906"
+ id="tspan3912-7"
+ sodipodi:role="line">Walking</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:61.58447266px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold Oblique';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000098;fill-opacity:1;stroke:none"
+ x="129.09416"
+ y="-3.8301911"
+ id="text2985-1"
+ sodipodi:linespacing="125%"
+ clip-path="none"><tspan
+ sodipodi:role="line"
+ id="tspan2987-7"
+ x="129.09416"
+ y="-3.8301911">Walking</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text3835"
+ y="15.369775"
+ x="174.28571"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:24.6932888px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#999999;fill-opacity:1;stroke:none"
+ xml:space="preserve"><tspan
+ y="15.369775"
+ x="174.28571"
+ id="tspan3837"
+ sodipodi:role="line">the</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:24.6932888px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#4d4d4d;fill-opacity:1;stroke:none"
+ x="173.83084"
+ y="14.914875"
+ id="text3879-6"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3881-4"
+ x="173.83084"
+ y="14.914875">the</tspan></text>
+ <g
+ transform="matrix(-1.0425656,0,0,0.72452471,492.68186,-57.574645)"
+ id="g3867-9"
+ style="display:inline;fill:#006500;fill-opacity:1">
+ <path
+ style="fill:#006500;fill-opacity:1;stroke:none"
+ d="m 223.195,72.653166 c 10.3555,1.148817 16.80222,5.350565 16.82722,11.886671 0.0232,6.052693 -6.44914,11.048103 -16.7397,11.818553 5.84387,-1.06162 12.78687,-5.88369 12.78687,-11.682316 0,-6.576511 -7.78791,-10.843594 -12.67003,-11.903701 -0.17966,0.110931 -0.16845,-0.09826 -0.20436,-0.119207 z"
+ id="path3869-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscscc" />
+ <path
+ sodipodi:nodetypes="cscscc"
+ inkscape:connector-curvature="0"
+ id="path3871-0"
+ d="m 230.54121,67.335169 c 15.00178,1.664265 24.341,7.751243 24.37721,17.219952 0.0336,8.7684 -9.34272,16.005139 -24.25042,17.121269 8.46588,-1.53794 18.52405,-8.523567 18.52405,-16.923905 0,-9.527243 -11.28217,-15.70887 -18.35479,-17.244623 -0.26027,0.160703 -0.24403,-0.142347 -0.29605,-0.172693 z"
+ style="fill:#006500;fill-opacity:1;stroke:none" />
+ <path
+ style="fill:#006500;fill-opacity:1;stroke:none"
+ d="m 238.7263,63.198946 c 18.61555,2.065169 30.20449,9.618436 30.24943,21.368059 0.0417,10.880616 -11.59329,19.860605 -30.0921,21.245605 10.50523,-1.90842 22.9863,-10.576806 22.9863,-21.000699 0,-11.822257 -13.99993,-19.492973 -22.77626,-21.398673 -0.32297,0.199415 -0.30282,-0.176637 -0.36737,-0.214292 z"
+ id="path3873-5"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscscc" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path3700-5"
+ d="m 194.13099,-53.29621 c 0.435,0.257399 0.832,0.533097 1.17019,0.849395 1.1575,1.082334 2.28139,2.683155 2.09539,2.989873 -0.05,0.092 -1.41289,1.128313 -3.01358,2.291997 l -2.90768,2.098477 -1.2877,-0.787795 -1.26989,-0.820195 -0.137,-3.888878 -0.187,-3.874277 1.2034,-0.055 c 1.39259,-0.053 3.02948,0.424797 4.33367,1.195993 z m 4.17368,5.551218 0.28,1.78772 c 0.27699,1.886729 0.111,5.538028 -0.276,6.177644 -0.127,0.211199 -1.50819,0.347998 -3.08788,0.281598 -1.5798,-0.067 -3.029 [...]
+ style="display:inline;fill:#80808f;fill-opacity:1"
+ clip-path="none"
+ inkscape:export-filename=""
+ inkscape:export-xdpi="300.08182"
+ inkscape:export-ydpi="300.08182" />
+ </g>
+</svg>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/qgis.git
More information about the Pkg-grass-devel
mailing list