9. Authors and Acknowledgments
diff --git a/doc/index.dox b/doc/index.dox
index 5ac09c9dfc6e..cba11bb42fe9 100644
--- a/doc/index.dox
+++ b/doc/index.dox
@@ -32,6 +32,8 @@ See \ref api_break for information about incompatible changes to API between rel
Earlier versions of the documentation are also available on the QGIS
website:
+3.12 ,
+3.10 (LTR) ,
3.8 ,
3.6 ,
3.4 (LTR) ,
diff --git a/doc/linux.t2t b/doc/linux.t2t
index 9bd04e70350e..3788e9e5c2db 100644
--- a/doc/linux.t2t
+++ b/doc/linux.t2t
@@ -44,11 +44,11 @@ sudo apt-get update
== Install build dependencies ==
|| Distribution | install command for packages |
-| buster | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
-| bionic | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
-| eoan | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
-| focal | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
-| sid | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
+| buster | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libprotobuf-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils protobuf-compiler pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
+| bionic | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libprotobuf-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils protobuf-compiler pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
+| eoan | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libprotobuf-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils protobuf-compiler pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
+| focal | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libprotobuf-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils protobuf-compiler pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
+| sid | ``apt-get install bison ca-certificates ccache cmake cmake-curses-gui dh-python doxygen expect flex flip gdal-bin git graphviz grass-dev libexiv2-dev libexpat1-dev libfcgi-dev libgdal-dev libgeos-dev libgsl-dev libpq-dev libproj-dev libprotobuf-dev libqca-qt5-2-dev libqca-qt5-2-plugins libqscintilla2-qt5-dev libqt5opengl5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5webkit5-dev libqt5xmlpatterns5-dev libqwt-qt5-dev libspatialindex-dev libspatialite-dev libsqlite3-dev libsqlite3-mod-spatialite libyaml-tiny-perl libzip-dev lighttpd locales ninja-build ocl-icd-opencl-dev opencl-headers pkg-config poppler-utils protobuf-compiler pyqt5-dev pyqt5-dev-tools pyqt5.qsci-dev python3-all-dev python3-autopep8 python3-dateutil python3-dev python3-future python3-gdal python3-httplib2 python3-jinja2 python3-lxml python3-markupsafe python3-mock python3-nose2 python3-owslib python3-plotly python3-psycopg2 python3-pygments python3-pyproj python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsql python3-pyqt5.qtsvg python3-pyqt5.qtwebkit python3-requests python3-sip python3-sip-dev python3-six python3-termcolor python3-tz python3-yaml qt3d-assimpsceneimport-plugin qt3d-defaultgeometryloader-plugin qt3d-gltfsceneio-plugin qt3d-scene2d-plugin qt3d5-dev qt5-default qt5keychain-dev qtbase5-dev qtbase5-private-dev qtpositioning5-dev qttools5-dev qttools5-dev-tools saga spawn-fcgi txt2tags xauth xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable xvfb`` |
(extracted from the control.in file in ``debian/``)
@@ -128,7 +128,7 @@ When you run ccmake (note the .. is required!), a menu will appear where
you can configure various aspects of the build. If you want QGIS to have
debugging capabilities then set CMAKE_BUILD_TYPE to Debug. If you do not have
root access or do not want to overwrite existing QGIS installs (by your
-packagemanager for example), set the CMAKE_INSTALL_PREFIX to somewhere you
+package manager for example), set the CMAKE_INSTALL_PREFIX to somewhere you
have write access to (I usually use ${HOME}/apps). Now press
'c' to configure, 'e' to dismiss any error messages that may appear.
and 'g' to generate the make files. Note that sometimes 'c' needs to
@@ -137,7 +137,7 @@ After the 'g' generation is complete, press 'q' to exit the ccmake
interactive dialog.
/!\ **Warning:** Make sure that your build directory is completely empty when you
-enter the command. Do never try to "re-use" an existing **Qt4** build directory.
+enter the command. Do never try to "re-use" an existing **Qt5** build directory.
If you want to use `ccmake` or other interactive tools, run the command in
the empty build directory once before starting to use the interactive tools.
@@ -210,10 +210,46 @@ Build and install with ninja:
ninja (uses all cores by default; also supports the above described -jX option)
ninja install
```
-You can build just the targets you need using, for example:
+
+To build even faster, you can build just the targets you need using, for example:
```
ninja qgis
ninja pycore
+# if it's on desktop related code only:
+ninja qgis_desktop
+```
+
+== Compiling with 3D ==
+
+In the cmake, you need to enable:
+```
+WITH_3D=True
+```
+=== Compiling with 3D on Debian based distributions ===
+
+QGIS 3D requires Qt53DExtras. These headers have been removed
+from Qt upstream on Debian based distributions. A copy has been made in the
+QGIS repository in ``external/qt3dextra-headers``.
+To compile with 3D enabled, you need to add some cmake options:
+```
+CMAKE_PREFIX_PATH={path to QGIS Git repo}/external/qt3dextra-headers/cmake
+QT5_3DEXTRA_INCLUDE_DIR={path to QGIS Git repo}/external/qt3dextra-headers
+QT5_3DEXTRA_LIBRARY=/usr/lib/x86_64-linux-gnu/libQt53DExtras.so
+```
+
+== Building different branches ==
+
+By using ``git worktree``, you can switch between different branches to use
+several sources in parallel, based on the same Git configuration.
+We recommand you to read the documentation about this Git command:
+```
+git commit
+git worktree add ../my_new_functionality
+cd ../my_new_functionality
+git fetch qgis/master
+git rebase -i qgis/master
+# only keep the commits to be pushed
+git push -u my_own_repo my_new_functionality
```
== Building Debian packages ==
@@ -280,7 +316,7 @@ dnf install fcgi-devel
```
Make sure that your build directory is completely empty when you enter the
-following command. Do never try to "re-use" an existing Qt4 build directory.
+following command. Do never try to "re-use" an existing Qt5 build directory.
If you want to use `ccmake` or other interactive tools, run the following
command in the empty build directory once before starting to use the interactive
tools.
diff --git a/external/mdal/cmake/FindNetCDF.cmake b/external/mdal/cmake/FindNetCDF.cmake
index 49ac69c450c7..75be47ba1f49 100644
--- a/external/mdal/cmake/FindNetCDF.cmake
+++ b/external/mdal/cmake/FindNetCDF.cmake
@@ -18,12 +18,12 @@ PKG_CHECK_MODULES(PC_NETCDF QUIET netcdf)
SET(NETCDF_DEFINITIONS ${PC_NETCDF_CFLAGS_OTHER})
FIND_PATH (NETCDF_INCLUDE_DIR netcdf.h
- HINTS ${PC_NETCDF_INCLUDEDIR} ${PC_NETCDF_INCLUDE_DIRS} ${NETCDF_PREFIX}/include
+ HINTS $ENV{LIB_DIR}/include ${PC_NETCDF_INCLUDEDIR} ${PC_NETCDF_INCLUDE_DIRS} ${NETCDF_PREFIX}/include
PATH_SUFFIXES libnetcdf )
-
-FIND_LIBRARY (NETCDF_LIBRARY
- NAMES netcdf libnetcdf
- HINTS HINTS ${PC_NETCDF_LIBDIR} ${PC_NETCDF_LIBRARY_DIRS} ${NETCDF_PREFIX}/lib)
+
+FIND_LIBRARY (NETCDF_LIBRARY
+ NAMES netcdf libnetcdf
+ HINTS $ENV{LIB_DIR}/lib ${PC_NETCDF_LIBDIR} ${PC_NETCDF_LIBRARY_DIRS} ${NETCDF_PREFIX}/lib)
INCLUDE (FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS (NetCDF
diff --git a/images/images.qrc b/images/images.qrc
index 55b9b61d4a98..59f4edffacd5 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -112,9 +112,11 @@
themes/default/algorithms/mAlgorithmNearestNeighbour.svg
themes/default/algorithms/mAlgorithmNetworkAnalysis.svg
themes/default/algorithms/mAlgorithmPolygonToLine.svg
+ themes/default/algorithms/mAlgorithmRandomPointsOnLines.svg
themes/default/algorithms/mAlgorithmRandomPointsWithinPolygon.svg
themes/default/algorithms/mAlgorithmRandomPointsWithinExtent.svg
themes/default/algorithms/mAlgorithmRegularPoints.svg
+ themes/default/algorithms/mAlgorithmRoundRastervalues.svg
themes/default/algorithms/mAlgorithmSelectLocation.svg
themes/default/algorithms/mAlgorithmSelectRandom.svg
themes/default/algorithms/mAlgorithmSimplify.svg
@@ -197,6 +199,9 @@
themes/default/mActionAddArrow.svg
themes/default/mActionAddBasicShape.svg
themes/default/mActionAddBasicCircle.svg
+ themes/default/mActionAllowIntersections.svg
+ themes/default/mActionAvoidIntersectionsCurrentLayer.svg
+ themes/default/mActionAvoidIntersectionsLayers.svg
themes/default/mActionEditNodesItem.svg
themes/default/mActionEditHelpContent.svg
themes/default/mActionIconView.svg
@@ -268,6 +273,7 @@
themes/default/mActionDeleteSelected.svg
themes/default/mActionDeleteTable.svg
themes/default/mActionDeselectAll.svg
+ themes/default/mActionDeselectActiveLayer.svg
themes/default/mActionDuplicateLayer.svg
themes/default/mActionDuplicateComposer.svg
themes/default/mActionEditCopy.svg
@@ -297,6 +303,7 @@
themes/default/mActionHelpSponsors.png
themes/default/mActionHideAllLayers.svg
themes/default/mActionToggleAllLayers.svg
+ themes/default/mActionToggleSelectedLayers.svg
themes/default/mActionHideSelectedLayers.svg
themes/default/mActionHideDeselectedLayers.svg
themes/default/mActionHistory.svg
@@ -512,6 +519,7 @@
themes/default/mIconTimerPause.svg
themes/default/mIconTreeView.svg
themes/default/mIconVector.svg
+ themes/default/mIconVectorTileLayer.svg
themes/default/mIconVirtualLayer.svg
themes/default/mIconWcs.svg
themes/default/mIconWfs.svg
@@ -680,6 +688,7 @@
themes/default/mIconSnappingArea.svg
themes/default/mIconSnappingCentroid.svg
themes/default/mIconSnappingMiddle.svg
+ themes/default/mIconSnappingOnScale.svg
themes/default/mIconSnappingVertex.svg
themes/default/mIconSnappingSegment.svg
themes/default/mIconTopologicalEditing.svg
@@ -832,6 +841,10 @@
themes/default/temporal_navigation/rewindToStart.svg
themes/default/temporal_navigation/skipToEnd.svg
themes/default/temporal_navigation/pause.svg
+ themes/default/mIconIterate.svg
+ themes/default/mIconNetworkLogger.svg
+ themes/default/mActionAddMarker.svg
+ themes/default/mLayoutItemMarker.svg
qgis_tips/symbol_levels.png
diff --git a/images/themes/default/algorithms/mAlgorithmRandomPointsOnLines.svg b/images/themes/default/algorithms/mAlgorithmRandomPointsOnLines.svg
new file mode 100644
index 000000000000..ebb78fed061b
--- /dev/null
+++ b/images/themes/default/algorithms/mAlgorithmRandomPointsOnLines.svg
@@ -0,0 +1,120 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/themes/default/algorithms/mAlgorithmRoundRastervalues.svg b/images/themes/default/algorithms/mAlgorithmRoundRastervalues.svg
new file mode 100644
index 000000000000..86a2c5148d0a
--- /dev/null
+++ b/images/themes/default/algorithms/mAlgorithmRoundRastervalues.svg
@@ -0,0 +1 @@
+
diff --git a/images/themes/default/mActionAddMarker.svg b/images/themes/default/mActionAddMarker.svg
new file mode 100644
index 000000000000..4f9ae01457b4
--- /dev/null
+++ b/images/themes/default/mActionAddMarker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/themes/default/mActionAllowIntersections.svg b/images/themes/default/mActionAllowIntersections.svg
new file mode 100644
index 000000000000..c6e23ea46967
--- /dev/null
+++ b/images/themes/default/mActionAllowIntersections.svg
@@ -0,0 +1 @@
+
diff --git a/images/themes/default/mActionAvoidIntersectionsCurrentLayer.svg b/images/themes/default/mActionAvoidIntersectionsCurrentLayer.svg
new file mode 100644
index 000000000000..2d03088f0334
--- /dev/null
+++ b/images/themes/default/mActionAvoidIntersectionsCurrentLayer.svg
@@ -0,0 +1 @@
+
diff --git a/images/themes/default/mActionAvoidIntersectionsLayers.svg b/images/themes/default/mActionAvoidIntersectionsLayers.svg
new file mode 100644
index 000000000000..36b6dc0596ca
--- /dev/null
+++ b/images/themes/default/mActionAvoidIntersectionsLayers.svg
@@ -0,0 +1 @@
+
diff --git a/images/themes/default/mActionDeselectActiveLayer.svg b/images/themes/default/mActionDeselectActiveLayer.svg
new file mode 100644
index 000000000000..7a6ef4ed19cf
--- /dev/null
+++ b/images/themes/default/mActionDeselectActiveLayer.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/themes/default/mActionDeselectAll.svg b/images/themes/default/mActionDeselectAll.svg
index 7a6ef4ed19cf..229848dd6b45 100644
--- a/images/themes/default/mActionDeselectAll.svg
+++ b/images/themes/default/mActionDeselectAll.svg
@@ -1,6 +1,7 @@
+
diff --git a/images/themes/default/mActionToggleSelectedLayers.svg b/images/themes/default/mActionToggleSelectedLayers.svg
new file mode 100644
index 000000000000..e71939b9da76
--- /dev/null
+++ b/images/themes/default/mActionToggleSelectedLayers.svg
@@ -0,0 +1,126 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/themes/default/mIconIterate.svg b/images/themes/default/mIconIterate.svg
new file mode 100644
index 000000000000..5ad34d826e61
--- /dev/null
+++ b/images/themes/default/mIconIterate.svg
@@ -0,0 +1,61 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
diff --git a/images/themes/default/mIconNetworkLogger.svg b/images/themes/default/mIconNetworkLogger.svg
new file mode 100644
index 000000000000..32d1e04035c8
--- /dev/null
+++ b/images/themes/default/mIconNetworkLogger.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/themes/default/mIconSnappingOnScale.svg b/images/themes/default/mIconSnappingOnScale.svg
new file mode 100644
index 000000000000..f2869a29e397
--- /dev/null
+++ b/images/themes/default/mIconSnappingOnScale.svg
@@ -0,0 +1,158 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/themes/default/mIconVectorTileLayer.svg b/images/themes/default/mIconVectorTileLayer.svg
new file mode 100644
index 000000000000..fa10e503b7d5
--- /dev/null
+++ b/images/themes/default/mIconVectorTileLayer.svg
@@ -0,0 +1,195 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/themes/default/mIndicatorTemporal.svg b/images/themes/default/mIndicatorTemporal.svg
index 14ecd0617e60..2407e400515b 100644
--- a/images/themes/default/mIndicatorTemporal.svg
+++ b/images/themes/default/mIndicatorTemporal.svg
@@ -1,62 +1 @@
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/images/themes/default/mLayoutItemMarker.svg b/images/themes/default/mLayoutItemMarker.svg
new file mode 100644
index 000000000000..00e286c67574
--- /dev/null
+++ b/images/themes/default/mLayoutItemMarker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mac/CMakeLists.txt b/mac/CMakeLists.txt
index 3a9d7a85f1b6..645c9295af36 100644
--- a/mac/CMakeLists.txt
+++ b/mac/CMakeLists.txt
@@ -1,6 +1,6 @@
# mac bundling must happen at end, so all binaries installed
# and install_names can be adjusted
-IF (APPLE)
+IF (APPLE AND QGIS_MACAPP_BUNDLE GREATER -1)
# for included scripts that set policies (ie OS X bundling)
INSTALL (CODE "cmake_policy(SET CMP0011 NEW)")
CONFIGURE_FILE (cmake/0vars.cmake.in 0vars.cmake @ONLY)
@@ -30,4 +30,4 @@ IF (APPLE)
ENDIF (QGIS_MACAPP_BUNDLE_USER)
# tickle app bundle
INSTALL (CODE "EXECUTE_PROCESS(COMMAND touch \"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/../../.\")")
-ENDIF (APPLE)
+ENDIF (APPLE AND QGIS_MACAPP_BUNDLE GREATER -1)
diff --git a/mac/cmake/1qt.cmake.in b/mac/cmake/1qt.cmake.in
index 27981a8d44e3..5aa8feca618c 100644
--- a/mac/cmake/1qt.cmake.in
+++ b/mac/cmake/1qt.cmake.in
@@ -56,7 +56,7 @@ IF (NOT @OSX_HAVE_LOADERPATH@)
EXECUTE_PROCESS (COMMAND ln -sfn @QGIS_CGIBIN_SUBDIR_REV@/@QGIS_LIB_SUBDIR@ "${QCGIDIR}/lib")
ENDIF (NOT @OSX_HAVE_LOADERPATH@)
-### copy files
+### copy files & strip qt rpath
# Qt frameworks
# Qt5 cmake does not create overall qt prefix var, only individual lib prefixes.
@@ -67,6 +67,7 @@ EXECUTE_PROCESS (COMMAND mkdir -p "${QFWDIR}")
FOREACH (QFW ${QTLISTQG})
IF (NOT IS_DIRECTORY "${QFWDIR}/${QFW}.framework")
COPY_FRAMEWORK("${QT_LIBRARY_DIR}" ${QFW} "${QFWDIR}")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QFWDIR}/${QFW}.framework/${QFW}")
ENDIF ()
ENDFOREACH (QFW)
@@ -76,12 +77,14 @@ EXECUTE_PROCESS (COMMAND mkdir -p "${QPLUGDIR}/../imageformats")
FOREACH (QI qgif;qico;qjpeg;qsvg;qtiff)
IF (NOT EXISTS "${QPLUGDIR}/../imageformats/lib${QI}.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QT_PLUGINS_DIR@/imageformats/lib${QI}.dylib" "${QPLUGDIR}/../imageformats/lib${QI}.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QPLUGDIR}/../imageformats/lib${QI}.dylib")
ENDIF ()
ENDFOREACH (QI)
EXECUTE_PROCESS (COMMAND mkdir -p "${QPLUGDIR}/../platforms")
FOREACH (QTP cocoa;minimal;offscreen)
IF (NOT EXISTS "${QPLUGDIR}/../platforms/libq${QTP}.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QT_PLUGINS_DIR@/platforms/libq${QTP}.dylib" "${QPLUGDIR}/../platforms/libq${QTP}.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QPLUGDIR}/../platforms/libq${QTP}.dylib")
ENDIF ()
ENDFOREACH (QTP)
EXECUTE_PROCESS (COMMAND mkdir -p "${QPLUGDIR}/../sqldrivers")
@@ -91,11 +94,13 @@ IF (@WITH_QSPATIALITE@)
LIST(APPEND QTLISTSQL spatialite)
IF (EXISTS "${QPLUGDIR}/../sqldrivers/libqsqlspatialite.dylib")
EXECUTE_PROCESS (COMMAND ${CMAKE_COMMAND} -E remove "${QPLUGDIR}/../sqldrivers/libqsqlspatialite.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QPLUGDIR}/../sqldrivers/libqsqlspatialite.dylib")
ENDIF ()
ENDIF ()
FOREACH (QSL ${QTLISTSQL})
IF (NOT EXISTS "${QPLUGDIR}/../sqldrivers/libqsql${QSL}.dylib" AND EXISTS "@QT_PLUGINS_DIR@/sqldrivers/libqsql${QSL}.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QT_PLUGINS_DIR@/sqldrivers/libqsql${QSL}.dylib" "${QPLUGDIR}/../sqldrivers/libqsql${QSL}.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QPLUGDIR}/../sqldrivers/libqsql${QSL}.dylib")
ENDIF ()
ENDFOREACH (QSL)
IF (NOT @WITH_QSPATIALITE@ AND EXISTS "${QPLUGDIR}/../sqldrivers/libqsqlspatialite.dylib")
@@ -104,10 +109,12 @@ ENDIF ()
EXECUTE_PROCESS (COMMAND mkdir -p "${QPLUGDIR}/../iconengines")
IF (NOT EXISTS "${QPLUGDIR}/../iconengines/libqsvgicon.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QT_PLUGINS_DIR@/iconengines/libqsvgicon.dylib" "${QPLUGDIR}/../iconengines/libqsvgicon.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QPLUGDIR}/../iconengines/libqsvgicon.dylib")
ENDIF ()
EXECUTE_PROCESS (COMMAND mkdir -p "${QPLUGDIR}/../styles")
IF (NOT EXISTS "${QPLUGDIR}/../styles/libqmacstyle.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QT_PLUGINS_DIR@/styles/libqmacstyle.dylib" "${QPLUGDIR}/../styles/libqmacstyle.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QPLUGDIR}/../styles/libqmacstyle.dylib")
ENDIF ()
# Qwt
@@ -119,6 +126,7 @@ IF (QWT_ISLIB)
MESSAGE (STATUS "Copying Qwt and updating library paths...")
IF (NOT EXISTS "${QLIBDIR}/libqwt.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QWT_LIBRARY@" "${QLIBDIR}/libqwt.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QLIBDIR}/libqwt.dylib")
ENDIF ()
GET_INSTALL_NAME ("@QWT_LIBRARY@" "libqwt" QWT_CHG)
IF (QWT_CHG)
@@ -130,6 +138,7 @@ ELSEIF (QWT_ISFW AND EXISTS "@QWT_LIBRARY@")
STRING(REGEX REPLACE "/qwt.framework.*" "" QWT_PARENT "@QWT_LIBRARY@")
IF (NOT IS_DIRECTORY "${QFWDIR}/qwt.framework")
COPY_FRAMEWORK("${QWT_PARENT}" "qwt" "${QFWDIR}")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QFWDIR}/qwt.framework/qwt")
ENDIF ()
GET_INSTALL_NAME ("${QWT_PARENT}/qwt.framework/qwt" "qwt.framework" QWT_CHG)
IF (QWT_CHG)
@@ -149,6 +158,7 @@ IF (@WITH_DESKTOP@)
MESSAGE (STATUS "Copying QwtPolar and updating library paths...")
IF (NOT EXISTS "${QLIBDIR}/libqwtpolar.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QWT_LIBRARY@" "${QLIBDIR}/libqwtpolar.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QLIBDIR}/libqwtpolar.dylib")
ENDIF ()
GET_INSTALL_NAME ("@QWTPOLAR_LIBRARY@" "libqwtpolar" QWTP_CHG)
IF (QWTP_CHG)
@@ -169,6 +179,7 @@ IF (@WITH_DESKTOP@)
STRING(REGEX REPLACE "/qwtpolar.framework.*" "" QWTP_PARENT "@QWTPOLAR_LIBRARY@")
IF (NOT IS_DIRECTORY "${QFWDIR}/qwtpolar.framework")
COPY_FRAMEWORK("${QWTP_PARENT}" "qwtpolar" "${QFWDIR}")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QFWDIR}/qwtpolar.framework/qwtpolar")
ENDIF ()
GET_INSTALL_NAME ("${QWTP_PARENT}/qwtpolar.framework/qwtpolar" "qwtpolar.framework" QWTP_CHG)
IF (QWTP_CHG)
@@ -197,6 +208,7 @@ IF (ISLIB)
SET (QCA_CHG_TO "${ATLOADER}/@QGIS_PLUGIN_SUBDIR_REV@/${QGIS_LIB_SUBDIR}/libqca.dylib")
IF (NOT EXISTS "${QLIBDIR}/libqca.dylib")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QCA_LIBRARY@" "${QLIBDIR}/libqca.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QLIBDIR}/libqca.dylib")
ENDIF ()
GET_INSTALL_NAME ("@QCA_LIBRARY@" "libqca" QCA_CHG)
IF (QCA_CHG)
@@ -220,6 +232,7 @@ ELSEIF (ISFW AND EXISTS "@QCA_LIBRARY@")
IF (NOT IS_DIRECTORY "${QFWDIR}/${_qca_libname}.framework")
STRING(REGEX REPLACE "/${_qca_libname}.framework.*" "" QCA_PARENT "@QCA_LIBRARY@")
COPY_FRAMEWORK("${QCA_PARENT}" "${_qca_libname}" "${QFWDIR}")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QFWDIR}/${_qca_libname}.framework/${_qca_libname}")
ENDIF ()
GET_INSTALL_NAME ("${QCA_LIBRARY}" "${_qca_libname}" QCA_CHG)
IF (QCA_CHG)
@@ -234,6 +247,7 @@ MESSAGE (STATUS "Updating QCA plugins with QCA library path in ${QCA_PLUGIN_DIR}
SET(QCA_PLUGINS logger ossl softstore)
FOREACH (qca_plugin ${QCA_PLUGINS})
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "${QCA_PLUGIN_DIR}/crypto/libqca-${qca_plugin}.dylib" "${QPLUGDIR}/../crypto/")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QCA_PLUGIN_DIR}/crypto/libqca-${qca_plugin}.dylib")
IF (QCA_CHG)
INSTALLNAMETOOL_CHANGE ("${QCA_CHG}" "${QCA_CHG_TO}" "${QPLUGDIR}/../crypto/libqca-${qca_plugin}.dylib")
ENDIF ()
@@ -243,6 +257,7 @@ ENDFOREACH ()
# linked to qca and qgis_core frameworks (see also 2lib.cmake.in)
IF (@WITH_QSPATIALITE@ AND EXISTS "${QPLUGDIR}/../sqldrivers/libqsqlspatialite.dylib")
# qca.framework
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QPLUGDIR}/../sqldrivers/libqsqlspatialite.dylib")
INSTALLNAMETOOL_CHANGE ("${QCA_CHG}" "${QCA_CHG_TO}" "${QPLUGDIR}/../sqldrivers/libqsqlspatialite.dylib")
# qgis_core.framework
GET_INSTALL_NAME ("@QGIS_OUTPUT_DIRECTORY@/lib/qgis_core.framework/qgis_core" qgis_core.framework QGCORE)
@@ -260,6 +275,7 @@ ENDIF ()
IF (QSCI_LIB)
MESSAGE (STATUS "Copying QScintilla2 library and updating library paths...")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QSCINTILLA_LIBRARY@" "${QLIBDIR}/${QSCI_LIB}.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QLIBDIR}/${QSCI_LIB}.dylib")
GET_INSTALL_NAME ("@QSCINTILLA_LIBRARY@" "${QSCI_LIB}" QSCI_CHG)
IF (QSCI_CHG)
UPDATEQGISPATHS (${QSCI_CHG} ${QSCI_LIB}.dylib)
@@ -271,6 +287,7 @@ ENDIF ()
IF (@QTKEYCHAIN_LIBRARY@ MATCHES ".*libqt5keychain.dylib")
MESSAGE (STATUS "Copying QtKeychain library and updating library paths...")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "@QTKEYCHAIN_LIBRARY@" "${QLIBDIR}/libqt5keychain.dylib")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QLIBDIR}/libqt5keychain.dylib")
GET_INSTALL_NAME ("@QTKEYCHAIN_LIBRARY@" "libqt5keychain" QTKEY_CHG)
IF (QTKEY_CHG)
UPDATEQGISPATHS (${QTKEY_CHG} libqt5keychain.dylib)
@@ -290,12 +307,14 @@ ENDFOREACH (PYPATH)
MESSAGE (STATUS "Copying sip...")
PYTHONMODULEDIR("sip.so" SIPMODDIR)
IF (SIPMODDIR)
- IF (NOT EXISTS "${QGISPYDIR}/sip.so")
+ IF (NOT EXISTS "${QGISPYDIR}/sip.so" AND NOT EXISTS "${QGISPYDIR}/PyQt5/sip.so")
# MYMESSAGE ("ditto ${QARCHS} \"${SIPMODDIR}/sip.so\" \"${QGISPYDIR}/\"")
IF (${SIPMODDIR} MATCHES ".*PyQt5.*")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "${SIPMODDIR}/sip.so" "${QGISPYDIR}/PyQt5/")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QGISPYDIR}/PyQt5/sip.so")
ELSE ()
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "${SIPMODDIR}/sip.so" "${QGISPYDIR}/")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QGISPYDIR}/sip.so")
ENDIF ()
EXECUTE_PROCESS (COMMAND cp -fp "${SIPMODDIR}/sipconfig.py" "${QGISPYDIR}/")
ENDIF ()
@@ -316,6 +335,7 @@ FOREACH (PQ ${PYQTLIST})
IF (NOT EXISTS "${QGISPYDIR}/${MODSUBPATH}")
# MESSAGE (STATUS "ditto ${QARCHS} \"${PYQT5MOD}\" \"${QGISPYDIR}/${MODSUBPATH}\"")
EXECUTE_PROCESS (COMMAND ditto ${QARCHS} "${MODDIR}/${MODSUBPATH}" "${QGISPYDIR}/${MODSUBPATH}")
+ EXECUTE_PROCESS (COMMAND install_name_tool -delete_rpath "@QT_LIBRARY_DIR@" "${QGISPYDIR}/${MODSUBPATH}")
IF (EXISTS "${MODDIR}/${MODPYI}")
EXECUTE_PROCESS (COMMAND ditto "${MODDIR}/${MODPYI}" "${QGISPYDIR}/${MODPYI}")
ENDIF ()
@@ -505,6 +525,11 @@ FOREACH (QFW ${QTLISTQG})
INSTALLNAMETOOL_CHANGE ("${QFW_CHG}" "${QFW_CHG_TO}" "${QPLUGDIR}/../iconengines/libqsvgicon.dylib")
INSTALLNAMETOOL_CHANGE ("${QFW_CHG}" "${QFW_CHG_TO}" "${QPLUGDIR}/../phonon_backend/libphonon_qt7.dylib")
INSTALLNAMETOOL_CHANGE ("${QFW_CHG}" "${QFW_CHG_TO}" "${QPLUGDIR}/../styles/libqmacstyle.dylib")
+ # quick plugin
+ IF (@OSX_HAVE_LOADERPATH@)
+ SET (QFW_CHG_TO "${ATLOADER}/../../../${LIBPOST}")
+ ENDIF ()
+ INSTALLNAMETOOL_CHANGE ("${QFW_CHG}" "${QFW_CHG_TO}" "${QAPPDIR}/qml/QgsQuick/libqgis_quick_plugin.dylib")
# qt fw
IF (@OSX_HAVE_LOADERPATH@)
SET (QFW_CHG_TO "${ATLOADER}/../../../${LIBPOST}")
diff --git a/mac/readme.txt b/mac/readme.txt
index 4b8ef24daaef..d1885a64c1b1 100644
--- a/mac/readme.txt
+++ b/mac/readme.txt
@@ -5,6 +5,7 @@ application package and fixing up the library paths. It is automatic during
installation. There are 2 levels currently, specified with the cmake config
option QGIS_MACAPP_BUNDLE, and one that always occurs:
+-1 = do not run bundle install scrips
0 = (default) fixup the library paths for all QGIS libraries if @loader_path
is available in the system (OS X 10.5+)
1 = bundle Qt, PyQt, PyQwt and OSG/osgEarth
diff --git a/ms-windows/mxe/README.md b/ms-windows/mxe/README.md
index 4460a5d78c6a..78c3835d8fbb 100644
--- a/ms-windows/mxe/README.md
+++ b/ms-windows/mxe/README.md
@@ -44,7 +44,10 @@ make MXE_TARGETS=i686-w64-mingw32.shared.posix -j 16 \
qtwinextras \
libzip \
gsl \
- libspatialindex
+ libspatialindex \
+ exiv2 \
+ protobuf
+
```
When done, you can check and edit the `build-mxe.sh` script and set the `MXE` path to your mxe installation directory, `MXE` can also be passed as an environment variable.
diff --git a/ms-windows/mxe/build-mxe.sh b/ms-windows/mxe/build-mxe.sh
index 22b372538d0f..1166f04c3164 100755
--- a/ms-windows/mxe/build-mxe.sh
+++ b/ms-windows/mxe/build-mxe.sh
@@ -105,13 +105,17 @@ cat <<__TXT__ > ${RELEASE_DIR}/qt.conf
Plugins = qt5plugins
__TXT__
+# First cleanup
+rm -rf ${BUILD_DIR}
+rm -rf ${CCACHE_DIR}
+
# Make the zip
cd ${RELEASE_DIR}/..
ZIP_NAME=qgis-mxe-release-$(date +%Y-%m-%d-%H-%I-%S).zip
-zip -r ${ZIP_NAME} $(basename ${RELEASE_DIR})
+zip -r -m ${ZIP_NAME} $(basename ${RELEASE_DIR})
-# Cleanup
+# Second cleanup
rm -rf ${RELEASE_DIR}
popd
diff --git a/ms-windows/mxe/mxe.Dockerfile b/ms-windows/mxe/mxe.Dockerfile
index 8d2f9cdb1e34..9cd266259302 100644
--- a/ms-windows/mxe/mxe.Dockerfile
+++ b/ms-windows/mxe/mxe.Dockerfile
@@ -20,7 +20,8 @@ RUN make MXE_TARGETS=x86_64-w64-mingw32.shared.posix -j 16 \
libzip \
gsl \
libspatialindex \
- exiv2
+ exiv2 \
+ protobuf
RUN chmod -R a+rw /mxe/usr/x86_64-w64-mingw32.shared.posix
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index e87ac9aad67a..566dd66c0630 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -123,6 +123,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/classification
${CMAKE_SOURCE_DIR}/src/core/validity
+ ${CMAKE_SOURCE_DIR}/src/core/vectortile
${CMAKE_SOURCE_DIR}/src/plugins
${CMAKE_SOURCE_DIR}/external
${CMAKE_SOURCE_DIR}/external/nlohmann
@@ -141,6 +142,7 @@ IF (WITH_GUI)
${CMAKE_SOURCE_DIR}/src/gui/raster
${CMAKE_SOURCE_DIR}/src/gui/attributetable
${CMAKE_SOURCE_DIR}/src/gui/auth
+ ${CMAKE_SOURCE_DIR}/src/gui/devtools
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets/core
${CMAKE_SOURCE_DIR}/src/gui/effects
diff --git a/python/analysis/auto_generated/mesh/qgsmeshcontours.sip.in b/python/analysis/auto_generated/mesh/qgsmeshcontours.sip.in
index 3cc3abeb7d5d..988ab8dc0798 100644
--- a/python/analysis/auto_generated/mesh/qgsmeshcontours.sip.in
+++ b/python/analysis/auto_generated/mesh/qgsmeshcontours.sip.in
@@ -36,7 +36,7 @@ Caches the native and triangular mesh from data provider
QgsGeometry exportLines( const QgsMeshDatasetIndex &index,
double value,
- QgsMeshRendererScalarSettings::DataInterpolationMethod method,
+ QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = 0 );
%Docstring
Exports multi line string containing the contour line for particular dataset and value
@@ -52,7 +52,7 @@ Exports multi line string containing the contour line for particular dataset and
QgsGeometry exportPolygons( const QgsMeshDatasetIndex &index,
double min_value,
double max_value,
- QgsMeshRendererScalarSettings::DataInterpolationMethod method,
+ QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = 0 );
%Docstring
Exports multi polygons representing the areas with values in range for particular dataset
diff --git a/python/core/additions/processing.py b/python/core/additions/processing.py
index 95545e674baa..40e8f621faec 100644
--- a/python/core/additions/processing.py
+++ b/python/core/additions/processing.py
@@ -20,8 +20,12 @@
# add some __repr__ methods to processing classes
def processing_source_repr(self):
- return "".format(
- self.source.staticValue(), self.selectedFeaturesOnly)
+ if self.featureLimit != -1:
+ return "".format(
+ self.source.staticValue(), self.selectedFeaturesOnly, self.featureLimit)
+ else:
+ return "".format(
+ self.source.staticValue(), self.selectedFeaturesOnly)
def processing_output_layer_repr(self):
diff --git a/python/core/auto_additions/qgsmaplayer.py b/python/core/auto_additions/qgsmaplayer.py
index 4535f58d3d3d..fde3f16868da 100644
--- a/python/core/auto_additions/qgsmaplayer.py
+++ b/python/core/auto_additions/qgsmaplayer.py
@@ -9,7 +9,9 @@
QgsMapLayer.PluginLayer.__doc__ = ""
QgsMapLayer.MeshLayer = QgsMapLayerType.MeshLayer
QgsMapLayer.MeshLayer.__doc__ = "Added in 3.2"
-QgsMapLayerType.__doc__ = 'Types of layers that can be added to a map\n\n.. versionadded:: 3.8\n\n' + '* ``VectorLayer``: ' + QgsMapLayerType.VectorLayer.__doc__ + '\n' + '* ``RasterLayer``: ' + QgsMapLayerType.RasterLayer.__doc__ + '\n' + '* ``PluginLayer``: ' + QgsMapLayerType.PluginLayer.__doc__ + '\n' + '* ``MeshLayer``: ' + QgsMapLayerType.MeshLayer.__doc__
+QgsMapLayer.VectorTileLayer = QgsMapLayerType.VectorTileLayer
+QgsMapLayer.VectorTileLayer.__doc__ = "Added in 3.14"
+QgsMapLayerType.__doc__ = 'Types of layers that can be added to a map\n\n.. versionadded:: 3.8\n\n' + '* ``VectorLayer``: ' + QgsMapLayerType.VectorLayer.__doc__ + '\n' + '* ``RasterLayer``: ' + QgsMapLayerType.RasterLayer.__doc__ + '\n' + '* ``PluginLayer``: ' + QgsMapLayerType.PluginLayer.__doc__ + '\n' + '* ``MeshLayer``: ' + QgsMapLayerType.MeshLayer.__doc__ + '\n' + '* ``VectorTileLayer``: ' + QgsMapLayerType.VectorTileLayer.__doc__
# --
QgsMapLayer.LayerFlag.baseClass = QgsMapLayer
QgsMapLayer.LayerFlags.baseClass = QgsMapLayer
diff --git a/python/core/auto_additions/qgsproject.py b/python/core/auto_additions/qgsproject.py
index 8f7747e4d2ad..43f6e31a0bac 100644
--- a/python/core/auto_additions/qgsproject.py
+++ b/python/core/auto_additions/qgsproject.py
@@ -12,3 +12,10 @@
QgsProject.FileFormat.__doc__ = 'Flags which control project read behavior.\n\n.. versionadded:: 3.12\n\n' + '* ``Qgz``: ' + QgsProject.FileFormat.Qgz.__doc__ + '\n' + '* ``Qgs``: ' + QgsProject.FileFormat.Qgs.__doc__
# --
QgsProject.FileFormat.baseClass = QgsProject
+# monkey patching scoped based enum
+QgsProject.AvoidIntersectionsMode.AllowIntersections.__doc__ = "Overlap with any feature allowed when digitizing new features"
+QgsProject.AvoidIntersectionsMode.AvoidIntersectionsCurrentLayer.__doc__ = "Overlap with features from the active layer when digitizing new features not allowed"
+QgsProject.AvoidIntersectionsMode.AvoidIntersectionsLayers.__doc__ = "Overlap with features from a specified list of layers when digitizing new features not allowed"
+QgsProject.AvoidIntersectionsMode.__doc__ = 'Flags which control how intersections of pre-existing feature are handled when digitizing new features.\n\n.. versionadded:: 3.14\n\n' + '* ``AllowIntersections``: ' + QgsProject.AvoidIntersectionsMode.AllowIntersections.__doc__ + '\n' + '* ``AvoidIntersectionsCurrentLayer``: ' + QgsProject.AvoidIntersectionsMode.AvoidIntersectionsCurrentLayer.__doc__ + '\n' + '* ``AvoidIntersectionsLayers``: ' + QgsProject.AvoidIntersectionsMode.AvoidIntersectionsLayers.__doc__
+# --
+QgsProject.AvoidIntersectionsMode.baseClass = QgsProject
diff --git a/python/core/auto_additions/qgsproviderconnectionmodel.py b/python/core/auto_additions/qgsproviderconnectionmodel.py
new file mode 100644
index 000000000000..0d1db9650bed
--- /dev/null
+++ b/python/core/auto_additions/qgsproviderconnectionmodel.py
@@ -0,0 +1,2 @@
+# The following has been generated automatically from src/core/qgsproviderconnectionmodel.h
+QgsProviderConnectionModel.Role.baseClass = QgsProviderConnectionModel
diff --git a/python/core/auto_additions/qgsscalebarrenderer.py b/python/core/auto_additions/qgsscalebarrenderer.py
new file mode 100644
index 000000000000..2406a9e901de
--- /dev/null
+++ b/python/core/auto_additions/qgsscalebarrenderer.py
@@ -0,0 +1,15 @@
+# The following has been generated automatically from src/core/scalebar/qgsscalebarrenderer.h
+# monkey patching scoped based enum
+QgsScaleBarRenderer.Flag.FlagUsesLineSymbol.__doc__ = "Renderer utilizes the scalebar line symbol (see QgsScaleBarSettings::lineSymbol() )"
+QgsScaleBarRenderer.Flag.FlagUsesFillSymbol.__doc__ = "Renderer utilizes the scalebar fill symbol (see QgsScaleBarSettings::fillSymbol() )"
+QgsScaleBarRenderer.Flag.FlagUsesAlternateFillSymbol.__doc__ = "Renderer utilizes the alternate scalebar fill symbol (see QgsScaleBarSettings::alternateFillSymbol() )"
+QgsScaleBarRenderer.Flag.FlagRespectsUnits.__doc__ = "Renderer respects the QgsScaleBarSettings::units() setting"
+QgsScaleBarRenderer.Flag.FlagRespectsMapUnitsPerScaleBarUnit.__doc__ = "Renderer respects the QgsScaleBarSettings::mapUnitsPerScaleBarUnit() setting"
+QgsScaleBarRenderer.Flag.FlagUsesUnitLabel.__doc__ = "Renderer uses the QgsScaleBarSettings::unitLabel() setting"
+QgsScaleBarRenderer.Flag.FlagUsesSegments.__doc__ = "Renderer uses the scalebar segments"
+QgsScaleBarRenderer.Flag.FlagUsesLabelBarSpace.__doc__ = "Renderer uses the QgsScaleBarSettings::labelBarSpace() setting"
+QgsScaleBarRenderer.Flag.FlagUsesLabelVerticalPlacement.__doc__ = "Renderer uses the QgsScaleBarSettings::labelVerticalPlacement() setting"
+QgsScaleBarRenderer.Flag.FlagUsesLabelHorizontalPlacement.__doc__ = "Renderer uses the QgsScaleBarSettings::labelHorizontalPlacement() setting"
+QgsScaleBarRenderer.Flag.FlagUsesAlignment.__doc__ = "Renderer uses the QgsScaleBarSettings::alignment() setting"
+QgsScaleBarRenderer.Flag.__doc__ = 'Flags which control scalebar renderer behavior.\n\n.. versionadded:: 3.14\n\n' + '* ``FlagUsesLineSymbol``: ' + QgsScaleBarRenderer.Flag.FlagUsesLineSymbol.__doc__ + '\n' + '* ``FlagUsesFillSymbol``: ' + QgsScaleBarRenderer.Flag.FlagUsesFillSymbol.__doc__ + '\n' + '* ``FlagUsesAlternateFillSymbol``: ' + QgsScaleBarRenderer.Flag.FlagUsesAlternateFillSymbol.__doc__ + '\n' + '* ``FlagRespectsUnits``: ' + QgsScaleBarRenderer.Flag.FlagRespectsUnits.__doc__ + '\n' + '* ``FlagRespectsMapUnitsPerScaleBarUnit``: ' + QgsScaleBarRenderer.Flag.FlagRespectsMapUnitsPerScaleBarUnit.__doc__ + '\n' + '* ``FlagUsesUnitLabel``: ' + QgsScaleBarRenderer.Flag.FlagUsesUnitLabel.__doc__ + '\n' + '* ``FlagUsesSegments``: ' + QgsScaleBarRenderer.Flag.FlagUsesSegments.__doc__ + '\n' + '* ``FlagUsesLabelBarSpace``: ' + QgsScaleBarRenderer.Flag.FlagUsesLabelBarSpace.__doc__ + '\n' + '* ``FlagUsesLabelVerticalPlacement``: ' + QgsScaleBarRenderer.Flag.FlagUsesLabelVerticalPlacement.__doc__ + '\n' + '* ``FlagUsesLabelHorizontalPlacement``: ' + QgsScaleBarRenderer.Flag.FlagUsesLabelHorizontalPlacement.__doc__ + '\n' + '* ``FlagUsesAlignment``: ' + QgsScaleBarRenderer.Flag.FlagUsesAlignment.__doc__
+# --
diff --git a/python/core/auto_additions/qgssnappingconfig.py b/python/core/auto_additions/qgssnappingconfig.py
index 46280cd94f95..2a877dbf1042 100644
--- a/python/core/auto_additions/qgssnappingconfig.py
+++ b/python/core/auto_additions/qgssnappingconfig.py
@@ -3,3 +3,4 @@
QgsSnappingConfig.SnappingTypes.baseClass = QgsSnappingConfig
QgsSnappingConfig.SnappingTypeFlag.baseClass = QgsSnappingConfig
SnappingTypeFlag = QgsSnappingConfig # dirty hack since SIP seems to introduce the flags in module
+QgsSnappingConfig.ScaleDependencyMode.baseClass = QgsSnappingConfig
diff --git a/python/core/auto_additions/qgstaskmanager.py b/python/core/auto_additions/qgstaskmanager.py
new file mode 100644
index 000000000000..e6ca414d0485
--- /dev/null
+++ b/python/core/auto_additions/qgstaskmanager.py
@@ -0,0 +1,2 @@
+# The following has been generated automatically from src/core/qgstaskmanager.h
+QgsTask.TaskStatus.baseClass = QgsTask
diff --git a/python/core/auto_generated/fieldformatter/qgsvaluerelationfieldformatter.sip.in b/python/core/auto_generated/fieldformatter/qgsvaluerelationfieldformatter.sip.in
index c363b0894437..543c5b86b997 100644
--- a/python/core/auto_generated/fieldformatter/qgsvaluerelationfieldformatter.sip.in
+++ b/python/core/auto_generated/fieldformatter/qgsvaluerelationfieldformatter.sip.in
@@ -25,7 +25,7 @@ features on another layer.
public:
struct ValueRelationItem
{
- ValueRelationItem( const QVariant &key, const QString &value );
+ ValueRelationItem( const QVariant &key, const QString &value, const QString &description = QString() );
%Docstring
Constructor for ValueRelationItem
%End
@@ -37,6 +37,7 @@ Constructor for ValueRelationItem
QVariant key;
QString value;
+ QString description;
};
typedef QVector < QgsValueRelationFieldFormatter::ValueRelationItem > ValueRelationCache;
diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in
index 4bfddd3ab202..1d9ac9f3ccb4 100644
--- a/python/core/auto_generated/geometry/qgsgeometry.sip.in
+++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in
@@ -839,7 +839,7 @@ Rotate this geometry around the Z axis
:return: OperationResult a result code: success or reason of failure
%End
- OperationResult splitGeometry( const QVector &splitLine, QVector &newGeometries /Out/, bool topological, QVector &topologyTestPoints /Out/ ) /Deprecated/;
+ OperationResult splitGeometry( const QVector &splitLine, QVector &newGeometries /Out/, bool topological, QVector &topologyTestPoints /Out/, bool splitFeature = true ) /Deprecated/;
%Docstring
Splits this geometry according to a given line.
@@ -847,6 +847,7 @@ Splits this geometry according to a given line.
\param[out] newGeometries list of new geometries that have been created with the split
:param topological: ``True`` if topological editing is enabled
\param[out] topologyTestPoints points that need to be tested for topological completeness in the dataset
+:param splitFeature: Set to True if you want to split a feature, otherwise set to False to split parts
:return: OperationResult a result code: success or reason of failure
@@ -854,7 +855,7 @@ Splits this geometry according to a given line.
- will be removed in QGIS 4.0. Use the variant which accepts QgsPoint objects instead of QgsPointXY.
%End
- OperationResult splitGeometry( const QgsPointSequence &splitLine, QVector &newGeometries /Out/, bool topological, QgsPointSequence &topologyTestPoints /Out/ );
+ OperationResult splitGeometry( const QgsPointSequence &splitLine, QVector &newGeometries /Out/, bool topological, QgsPointSequence &topologyTestPoints /Out/, bool splitFeature = true );
%Docstring
Splits this geometry according to a given line.
@@ -862,6 +863,8 @@ Splits this geometry according to a given line.
\param[out] newGeometries list of new geometries that have been created with the split
:param topological: ``True`` if topological editing is enabled
\param[out] topologyTestPoints points that need to be tested for topological completeness in the dataset
+:param splitFeature: Set to True if you want to split a feature, otherwise set to False to split parts
+ fix this bug?
:return: OperationResult a result code: success or reason of failure
%End
@@ -1539,7 +1542,32 @@ Exports the geometry to a GeoJSON string.
%End
- QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const /Factory/;
+ QVector< QgsGeometry > coerceToType( QgsWkbTypes::Type type ) const;
+%Docstring
+Attempts to coerce this geometry into the specified destination ``type``.
+
+This method will do anything possible to force the current geometry into the specified type. E.g.
+- lines or polygons will be converted to points by return either a single multipoint geometry or multiple
+single point geometries.
+- polygons will be converted to lines by extracting their exterior and interior rings, returning
+either a multilinestring or multiple single line strings as dictated by ``type``.
+- lines will be converted to polygon rings if ``type`` is a polygon type
+- curved geometries will be segmented if ``type`` is non-curved.
+- multi geometries will be converted to a list of single geometries
+- single geometries will be upgraded to multi geometries
+- z or m values will be added or dropped as required.
+
+.. note::
+
+ This method is much stricter than convertToType(), as it considers the exact WKB type
+ of geometries instead of the geometry family (point/line/polygon), and tries more exhaustively
+ to coerce geometries to the desired ``type``. It also correctly maintains curves and z/m values
+ wherever appropriate.
+
+.. versionadded:: 3.14
+%End
+
+ QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const;
%Docstring
Try to convert the geometry to the requested type
@@ -1548,6 +1576,12 @@ Try to convert the geometry to the requested type
:return: the converted geometry or ``None`` if the conversion fails.
+.. note::
+
+ The coerceToType() method applies much stricter and more exhaustive attempts to convert
+ between geometry types, and is recommended instead of this method. This method force drops
+ curves and any z or m values present in the geometry.
+
.. versionadded:: 2.2
%End
diff --git a/python/core/auto_generated/layertree/qgslayertreelayer.sip.in b/python/core/auto_generated/layertree/qgslayertreelayer.sip.in
index 589f5da50681..863625fb5146 100644
--- a/python/core/auto_generated/layertree/qgslayertreelayer.sip.in
+++ b/python/core/auto_generated/layertree/qgslayertreelayer.sip.in
@@ -136,6 +136,24 @@ set the expression to evaluate
Returns the expression member of the LayerTreeNode
.. versionadded:: 3.10
+%End
+
+ QgsLegendPatchShape patchShape() const;
+%Docstring
+Returns the symbol patch shape to use when rendering the legend node symbol.
+
+.. seealso:: :py:func:`setPatchShape`
+
+.. versionadded:: 3.14
+%End
+
+ void setPatchShape( const QgsLegendPatchShape &shape );
+%Docstring
+Sets the symbol patch ``shape`` to use when rendering the legend node symbol.
+
+.. seealso:: :py:func:`patchShape`
+
+.. versionadded:: 3.14
%End
signals:
diff --git a/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in b/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in
index 0a54f85db4f1..ca0ccc71f447 100644
--- a/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in
+++ b/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in
@@ -99,6 +99,7 @@ Default implementation does nothing. *
double maxSiblingSymbolWidth;
+ QgsLegendPatchShape patchShape;
};
struct ItemMetrics
@@ -322,6 +323,24 @@ Sets format of text to be shown on top of the symbol.
Label of the symbol, user defined label will be used, otherwise will default to the label made by QGIS.
.. versionadded:: 3.10
+%End
+
+ QgsLegendPatchShape patchShape() const;
+%Docstring
+Returns the symbol patch shape to use when rendering the legend node symbol.
+
+.. seealso:: :py:func:`setPatchShape`
+
+.. versionadded:: 3.14
+%End
+
+ void setPatchShape( const QgsLegendPatchShape &shape );
+%Docstring
+Sets the symbol patch ``shape`` to use when rendering the legend node symbol.
+
+.. seealso:: :py:func:`patchShape`
+
+.. versionadded:: 3.14
%End
QString evaluateLabel( const QgsExpressionContext &context = QgsExpressionContext(), const QString &label = QString() );
diff --git a/python/core/auto_generated/layertree/qgslayertreenode.sip.in b/python/core/auto_generated/layertree/qgslayertreenode.sip.in
index 3c35af387864..f15c41925f9d 100644
--- a/python/core/auto_generated/layertree/qgslayertreenode.sip.in
+++ b/python/core/auto_generated/layertree/qgslayertreenode.sip.in
@@ -208,6 +208,13 @@ Returns a list of any checked layers which belong to this node or its
children.
.. versionadded:: 3.0
+%End
+
+ int depth() const;
+%Docstring
+Returns the depth of this node, i.e. the number of its ancestors
+
+.. versionadded:: 3.14
%End
bool isExpanded() const;
diff --git a/python/core/auto_generated/layertree/qgslegendpatchshape.sip.in b/python/core/auto_generated/layertree/qgslegendpatchshape.sip.in
new file mode 100644
index 000000000000..029348438bf0
--- /dev/null
+++ b/python/core/auto_generated/layertree/qgslegendpatchshape.sip.in
@@ -0,0 +1,148 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/layertree/qgslegendpatchshape.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+class QgsLegendPatchShape
+{
+%Docstring
+Represents a patch shape for use in map legends.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgslegendpatchshape.h"
+%End
+ public:
+
+ QgsLegendPatchShape();
+%Docstring
+Constructor for a null QgsLegendPatchShape.
+
+A null QgsLegendPatchShape indicates that the default legend patch shape
+should be used instead.
+%End
+
+ QgsLegendPatchShape( QgsSymbol::SymbolType type,
+ const QgsGeometry &geometry,
+ bool preserveAspectRatio = true );
+%Docstring
+Constructor for QgsLegendPatchShape.
+
+The ``type`` argument specifies the symbol type associated with this patch.
+
+The ``geometry`` argument gives the shape of the patch to render. See setGeometry()
+for further details on the geometry requirements.
+
+If ``preserveAspectRatio`` is ``True``, then the patch shape should preserve its aspect ratio when
+it is resized to fit a desired legend patch size.
+%End
+
+ bool isNull() const;
+%Docstring
+Returns ``True`` if the patch shape is a null QgsLegendPatchShape,
+which indicates that the default legend patch shape should be used instead.
+%End
+
+ QgsSymbol::SymbolType symbolType() const;
+%Docstring
+Returns the symbol type associated with this patch.
+
+.. seealso:: :py:func:`setSymbolType`
+%End
+
+ void setSymbolType( QgsSymbol::SymbolType type );
+%Docstring
+Sets the symbol ``type`` associated with this patch.
+
+.. seealso:: :py:func:`symbolType`
+%End
+
+ QgsGeometry geometry() const;
+%Docstring
+Returns the geometry for the patch shape.
+
+.. seealso:: :py:func:`setGeometry`
+%End
+
+ void setGeometry( const QgsGeometry &geometry );
+%Docstring
+Sets the ``geometry`` for the patch shape.
+
+The origin and size of the ``geometry`` is not important, as the legend
+renderer will automatically scale and transform the geometry to match
+the desired overall patch bounds.
+
+Geometries for legend patches are rendered respecting the traditional
+"y values increase toward the top of the map" convention, as opposed
+to the standard computer graphics convention of "y values increase toward
+the bottom of the display".
+
+.. warning::
+
+ The geometry type should match the patch shape's symbolType(),
+ e.g. a fill symbol type should only have Polygon or MultiPolygon geometries
+ set, while a line symbol type must have LineString or MultiLineString geometries.
+
+.. seealso:: :py:func:`geometry`
+%End
+
+ bool preserveAspectRatio() const;
+%Docstring
+Returns ``True`` if the patch shape should preserve its aspect ratio when
+it is resized to fit a desired legend patch size.
+
+.. seealso:: :py:func:`setPreserveAspectRatio`
+%End
+
+ void setPreserveAspectRatio( bool preserve );
+%Docstring
+Sets whether the patch shape should ``preserve`` its aspect ratio when
+it is resized to fit a desired legend patch size.
+
+The default behavior is to respect the geometry()'s aspect ratio.
+
+.. seealso:: :py:func:`setPreserveAspectRatio`
+%End
+
+ QList< QList< QPolygonF > > toQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const;
+%Docstring
+Converts the patch shape to a set of QPolygonF objects representing
+how the patch should be drawn for a symbol of the given ``type`` at the specified ``size`` (as
+geometry parts and rings).
+%End
+
+ static QList< QList< QPolygonF > > defaultPatch( QgsSymbol::SymbolType type, QSizeF size );
+%Docstring
+Returns the default patch geometry for the given symbol ``type`` and ``size`` as a set of QPolygonF objects (parts and rings).
+%End
+
+ void readXml( const QDomElement &element, const QgsReadWriteContext &context );
+%Docstring
+Read settings from a DOM ``element``.
+
+.. seealso:: :py:func:`writeXml`
+%End
+
+ void writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) const;
+%Docstring
+Write settings into a DOM ``element``.
+
+.. seealso:: :py:func:`readXml`
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/layertree/qgslegendpatchshape.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in
index c4309900beb6..c5b9b6fe2368 100644
--- a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in
+++ b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in
@@ -502,6 +502,15 @@ Sets the ``map`` to associate with the legend.
Returns the associated map.
.. seealso:: :py:func:`setLinkedMap`
+%End
+
+ QString themeName() const;
+%Docstring
+Returns the name of the theme currently linked to the legend.
+
+This usually equates to the theme rendered in the linkedMap().
+
+.. versionadded:: 3.14
%End
void updateLegend();
diff --git a/python/core/auto_generated/layout/qgslayoutitemmap.sip.in b/python/core/auto_generated/layout/qgslayoutitemmap.sip.in
index 7854144cdb57..d36d56f3de5e 100644
--- a/python/core/auto_generated/layout/qgslayoutitemmap.sip.in
+++ b/python/core/auto_generated/layout/qgslayoutitemmap.sip.in
@@ -296,6 +296,8 @@ Preset name that decides which layers and layer styles are used for map renderin
used when followVisibilityPreset() returns ``True``.
.. seealso:: :py:func:`setFollowVisibilityPresetName`
+
+.. seealso:: :py:func:`themeChanged`
%End
void setFollowVisibilityPresetName( const QString &name );
@@ -303,6 +305,8 @@ used when followVisibilityPreset() returns ``True``.
Sets preset name for map rendering. See followVisibilityPresetName() for more details.
.. seealso:: :py:func:`followVisibilityPresetName`
+
+.. seealso:: :py:func:`themeChanged`
%End
virtual void moveContent( double dx, double dy );
@@ -680,6 +684,18 @@ Emitted when the map has been prepared for atlas rendering, just before actual r
%Docstring
Emitted when layer style overrides are changed... a means to let
associated legend items know they should update
+%End
+
+ void themeChanged( const QString &theme );
+%Docstring
+Emitted when the map's associated ``theme`` is changed.
+
+.. note::
+
+ This signal is not emitted when the definition of the theme changes, only the map
+ is linked to a different theme then it previously was.
+
+.. versionadded:: 3.14
%End
public slots:
diff --git a/python/core/auto_generated/layout/qgslayoutitemmarker.sip.in b/python/core/auto_generated/layout/qgslayoutitemmarker.sip.in
new file mode 100644
index 000000000000..8e195ff72a74
--- /dev/null
+++ b/python/core/auto_generated/layout/qgslayoutitemmarker.sip.in
@@ -0,0 +1,152 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/layout/qgslayoutitemmarker.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsLayoutItemMarker : QgsLayoutItem
+{
+%Docstring
+A layout item for showing marker symbols.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgslayoutitemmarker.h"
+%End
+ public:
+
+ explicit QgsLayoutItemMarker( QgsLayout *layout );
+%Docstring
+Constructor for QgsLayoutItemMarker, with the specified parent ``layout``.
+%End
+ ~QgsLayoutItemMarker();
+
+ static QgsLayoutItemMarker *create( QgsLayout *layout ) /Factory/;
+%Docstring
+Returns a new marker item for the specified ``layout``.
+
+The caller takes responsibility for deleting the returned object.
+%End
+
+
+ virtual int type() const;
+
+ virtual QIcon icon() const;
+
+
+ void setSymbol( QgsMarkerSymbol *symbol /Transfer/ );
+%Docstring
+Sets the marker ``symbol`` used to draw the shape. Ownership is transferred.
+
+.. seealso:: :py:func:`symbol`
+%End
+
+ QgsMarkerSymbol *symbol();
+%Docstring
+Returns the marker symbol used to draw the shape.
+
+.. seealso:: :py:func:`setSymbol`
+%End
+
+ void setLinkedMap( QgsLayoutItemMap *map );
+%Docstring
+Sets the ``map`` object for rotation.
+
+If this is set then the marker will be rotated by the same
+amount as the specified map object. This is useful especially for
+syncing north arrows with a map item.
+
+.. seealso:: :py:func:`linkedMap`
+%End
+
+ QgsLayoutItemMap *linkedMap() const;
+%Docstring
+Returns the linked rotation map, if set. An ``None`` means map rotation is
+disabled. If this is set then the marker is rotated by the same amount
+as the specified map object.
+
+.. seealso:: :py:func:`setLinkedMap`
+%End
+
+ double northArrowRotation() const;
+%Docstring
+When the marker is linked to a map in north arrow rotation mode,
+returns the current north arrow rotation for the marker.
+
+.. seealso:: :py:func:`setLinkedMap`
+%End
+
+ QgsLayoutNorthArrowHandler::NorthMode northMode() const;
+%Docstring
+Returns the mode used to align the marker to a map's North.
+
+.. seealso:: :py:func:`setNorthMode`
+
+.. seealso:: :py:func:`northOffset`
+%End
+
+ void setNorthMode( QgsLayoutNorthArrowHandler::NorthMode mode );
+%Docstring
+Sets the ``mode`` used to align the marker to a map's North.
+
+.. seealso:: :py:func:`northMode`
+
+.. seealso:: :py:func:`setNorthOffset`
+%End
+
+ double northOffset() const;
+%Docstring
+Returns the offset added to the marker's rotation from a map's North.
+
+.. seealso:: :py:func:`setNorthOffset`
+
+.. seealso:: :py:func:`northMode`
+%End
+
+ void setNorthOffset( double offset );
+%Docstring
+Sets the ``offset`` added to the marker's rotation from a map's North.
+
+.. seealso:: :py:func:`northOffset`
+
+.. seealso:: :py:func:`setNorthMode`
+%End
+
+ virtual QRectF boundingRect() const;
+
+
+ virtual QgsLayoutSize fixedSize() const;
+
+
+ virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const;
+
+
+ protected:
+
+ virtual void draw( QgsLayoutItemRenderContext &context );
+
+
+ virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
+
+ virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
+
+
+ virtual void finalizeRestoreFromXml();
+
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/layout/qgslayoutitemmarker.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/layout/qgslayoutitempicture.sip.in b/python/core/auto_generated/layout/qgslayoutitempicture.sip.in
index 09b824e4c16c..c477a27b23d8 100644
--- a/python/core/auto_generated/layout/qgslayoutitempicture.sip.in
+++ b/python/core/auto_generated/layout/qgslayoutitempicture.sip.in
@@ -62,12 +62,14 @@ The caller takes responsibility for deleting the returned object.
%End
- void setPicturePath( const QString &path );
+ void setPicturePath( const QString &path, Format format = FormatUnknown );
%Docstring
Sets the source ``path`` of the image (may be svg or a raster format). Data defined
picture source may override this value. The path can either be a local path
or a remote (http) path.
+Ideally, the ``format`` argument should specify the image format.
+
.. seealso:: :py:func:`picturePath`
%End
@@ -249,6 +251,17 @@ Sets the stroke ``width`` (in layout units) used for parametrized SVG files.
Format mode() const;
%Docstring
Returns the current picture mode (image format).
+
+.. seealso:: :py:func:`setMode`
+%End
+
+ void setMode( Format mode );
+%Docstring
+Sets the current picture ``mode`` (image format).
+
+.. seealso:: :py:func:`mode`
+
+.. versionadded:: 3.14
%End
virtual void finalizeRestoreFromXml();
diff --git a/python/core/auto_generated/layout/qgslayoutitemregistry.sip.in b/python/core/auto_generated/layout/qgslayoutitemregistry.sip.in
index 076cd516167d..23c8e74b4437 100644
--- a/python/core/auto_generated/layout/qgslayoutitemregistry.sip.in
+++ b/python/core/auto_generated/layout/qgslayoutitemregistry.sip.in
@@ -192,6 +192,7 @@ of layout items.
Layout3DMap,
LayoutManualTable,
+ LayoutMarker,
// item types provided by plugins
PluginItem,
diff --git a/python/core/auto_generated/layout/qgslayoutitemscalebar.sip.in b/python/core/auto_generated/layout/qgslayoutitemscalebar.sip.in
index 98b512b78d03..4a00c6aeef19 100644
--- a/python/core/auto_generated/layout/qgslayoutitemscalebar.sip.in
+++ b/python/core/auto_generated/layout/qgslayoutitemscalebar.sip.in
@@ -208,6 +208,77 @@ Sets the text ``format`` used for drawing text in the scalebar.
.. seealso:: :py:func:`textFormat`
.. versionadded:: 3.2
+%End
+
+ QgsLineSymbol *lineSymbol() const;
+%Docstring
+Returns the line symbol used to render the scalebar (only used for some scalebar types).
+
+Ownership is not transferred.
+
+.. seealso:: :py:func:`setLineSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ void setLineSymbol( QgsLineSymbol *symbol /Transfer/ );
+%Docstring
+Sets the line ``symbol`` used to render the scalebar (only used for some scalebar types). Ownership of ``symbol`` is
+transferred to the scalebar.
+
+.. seealso:: :py:func:`lineSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ QgsFillSymbol *fillSymbol() const;
+%Docstring
+Returns the primary fill symbol used to render the scalebar (only used for some scalebar types).
+
+Ownership is not transferred.
+
+.. seealso:: :py:func:`setFillSymbol`
+
+.. seealso:: :py:func:`alternateFillSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ void setFillSymbol( QgsFillSymbol *symbol /Transfer/ );
+%Docstring
+Sets the primary fill ``symbol`` used to render the scalebar (only used for some scalebar types). Ownership of ``symbol`` is
+transferred to the scalebar.
+
+.. seealso:: :py:func:`fillSymbol`
+
+.. seealso:: :py:func:`setAlternateFillSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ QgsFillSymbol *alternateFillSymbol() const;
+%Docstring
+Returns the secondary fill symbol used to render the scalebar (only used for some scalebar types).
+
+Ownership is not transferred.
+
+.. seealso:: :py:func:`setAlternateFillSymbol`
+
+.. seealso:: :py:func:`fillSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ void setAlternateFillSymbol( QgsFillSymbol *symbol /Transfer/ );
+%Docstring
+Sets the secondary fill ``symbol`` used to render the scalebar (only used for some scalebar types). Ownership of ``symbol`` is
+transferred to the scalebar.
+
+.. seealso:: :py:func:`alternateFillSymbol`
+
+.. seealso:: :py:func:`setFillSymbol`
+
+.. versionadded:: 3.14
%End
QFont font() const /Deprecated/;
@@ -254,78 +325,105 @@ Sets the ``color`` used for drawing text in the scalebar.
use setTextFormat() instead
%End
- QColor fillColor() const;
+ QColor fillColor() const /Deprecated/;
%Docstring
Returns the color used for fills in the scalebar.
.. seealso:: :py:func:`setFillColor`
.. seealso:: :py:func:`fillColor2`
+
+.. deprecated::
+ use fillSymbol() instead
%End
- void setFillColor( const QColor &color );
+ void setFillColor( const QColor &color ) /Deprecated/;
%Docstring
Sets the ``color`` used for fills in the scalebar.
.. seealso:: :py:func:`fillColor`
.. seealso:: :py:func:`setFillColor2`
+
+.. deprecated::
+ use setFillSymbol() instead
%End
- QColor fillColor2() const;
+ QColor fillColor2() const /Deprecated/;
%Docstring
Returns the secondary color used for fills in the scalebar.
.. seealso:: :py:func:`setFillColor2`
.. seealso:: :py:func:`fillColor`
+
+.. deprecated::
+ use alternateFillSymbol() instead
%End
- void setFillColor2( const QColor &color );
+ void setFillColor2( const QColor &color ) /Deprecated/;
%Docstring
Sets the secondary ``color`` used for fills in the scalebar.
.. seealso:: :py:func:`fillColor2`
.. seealso:: :py:func:`setFillColor2`
+
+.. deprecated::
+ use setAlternateFillSymbol() instead
%End
- QColor lineColor() const;
+ QColor lineColor() const /Deprecated/;
%Docstring
Returns the color used for lines in the scalebar.
.. seealso:: :py:func:`setLineColor`
+
+.. deprecated::
+ use lineSymbol() instead
%End
- void setLineColor( const QColor &color );
+ void setLineColor( const QColor &color ) /Deprecated/;
%Docstring
Sets the ``color`` used for lines in the scalebar.
.. seealso:: :py:func:`lineColor`
+
+.. deprecated::
+ use setLineSymbol() instead
%End
- double lineWidth() const;
+ double lineWidth() const /Deprecated/;
%Docstring
Returns the line width in millimeters for lines in the scalebar.
.. seealso:: :py:func:`setLineWidth`
+
+.. deprecated::
+ use lineSymbol() instead
%End
- void setLineWidth( double width );
+ void setLineWidth( double width ) /Deprecated/;
%Docstring
Sets the line ``width`` in millimeters for lines in the scalebar.
.. seealso:: :py:func:`lineWidth`
+
+.. deprecated::
+ use setLineSymbol() instead
%End
- QPen pen() const;
+ QPen pen() const /Deprecated/;
%Docstring
Returns the pen used for drawing outlines in the scalebar.
.. seealso:: :py:func:`brush`
+
+.. deprecated::
+ use lineSymbol() instead
%End
- QBrush brush() const;
+ QBrush brush() const /Deprecated/;
%Docstring
Returns the primary brush for the scalebar.
@@ -334,9 +432,12 @@ Returns the primary brush for the scalebar.
.. seealso:: :py:func:`brush2`
.. seealso:: :py:func:`pen`
+
+.. deprecated::
+ use fillSymbol() instead
%End
- QBrush brush2() const;
+ QBrush brush2() const /Deprecated/;
%Docstring
Returns the secondary brush for the scalebar. This is used for alternating color style scalebars, such
as single and double box styles.
@@ -344,6 +445,9 @@ as single and double box styles.
:return: QBrush used for secondary color areas
.. seealso:: :py:func:`brush`
+
+.. deprecated::
+ use alternateFillSymbol() instead
%End
double height() const;
@@ -466,32 +570,44 @@ Sets the distance ``units`` used by the scalebar.
.. seealso:: :py:func:`units`
%End
- Qt::PenJoinStyle lineJoinStyle() const;
+ Qt::PenJoinStyle lineJoinStyle() const /Deprecated/;
%Docstring
Returns the join style used for drawing lines in the scalebar.
.. seealso:: :py:func:`setLineJoinStyle`
+
+.. deprecated::
+ use lineSymbol() instead
%End
- void setLineJoinStyle( Qt::PenJoinStyle style );
+ void setLineJoinStyle( Qt::PenJoinStyle style ) /Deprecated/;
%Docstring
Sets the join ``style`` used when drawing the lines in the scalebar
.. seealso:: :py:func:`lineJoinStyle`
+
+.. deprecated::
+ use setLineSymbol() instead
%End
- Qt::PenCapStyle lineCapStyle() const;
+ Qt::PenCapStyle lineCapStyle() const /Deprecated/;
%Docstring
Returns the cap style used for drawing lines in the scalebar.
.. seealso:: :py:func:`setLineCapStyle`
+
+.. deprecated::
+ use lineSymbol() instead
%End
- void setLineCapStyle( Qt::PenCapStyle style );
+ void setLineCapStyle( Qt::PenCapStyle style ) /Deprecated/;
%Docstring
Sets the cap ``style`` used when drawing the lines in the scalebar.
.. seealso:: :py:func:`lineCapStyle`
+
+.. deprecated::
+ use setLineSymbol() instead
%End
void applyDefaultSettings();
@@ -499,6 +615,15 @@ Sets the cap ``style`` used when drawing the lines in the scalebar.
Applies the default scalebar settings to the scale bar.
.. seealso:: :py:func:`applyDefaultSize`
+%End
+
+ bool applyDefaultRendererSettings( QgsScaleBarRenderer *renderer );
+%Docstring
+Applies any default settings relating to the specified ``renderer`` to the item.
+
+Returns ``True`` if settings were applied.
+
+.. versionadded:: 3.14
%End
QgsUnitTypes::DistanceUnit guessUnits() const;
diff --git a/python/core/auto_generated/layout/qgslayoutnortharrowhandler.sip.in b/python/core/auto_generated/layout/qgslayoutnortharrowhandler.sip.in
new file mode 100644
index 000000000000..374eee17d35b
--- /dev/null
+++ b/python/core/auto_generated/layout/qgslayoutnortharrowhandler.sip.in
@@ -0,0 +1,106 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/layout/qgslayoutnortharrowhandler.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsLayoutNorthArrowHandler: QObject
+{
+%Docstring
+An object which handles north-arrow type behavior for layout items.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgslayoutnortharrowhandler.h"
+%End
+ public:
+
+ enum NorthMode
+ {
+ GridNorth,
+ TrueNorth,
+ };
+
+ QgsLayoutNorthArrowHandler( QObject *parent /TransferThis/ );
+%Docstring
+Constructor for QgsLayoutNorthArrowHandler, with the specified parent ``object``.
+%End
+
+ double arrowRotation() const;
+%Docstring
+Returns the rotation to be used for the arrow, in degrees clockwise.
+%End
+
+ void setLinkedMap( QgsLayoutItemMap *map );
+%Docstring
+Sets the linked ``map`` item.
+
+.. seealso:: :py:func:`linkedMap`
+%End
+
+ QgsLayoutItemMap *linkedMap() const;
+%Docstring
+Returns the linked rotation map, if set. An ``None`` means arrow calculation is
+disabled.
+
+.. seealso:: :py:func:`setLinkedMap`
+%End
+
+ NorthMode northMode() const;
+%Docstring
+Returns the mode used to calculate the arrow rotation.
+
+.. seealso:: :py:func:`setNorthMode`
+
+.. seealso:: :py:func:`northOffset`
+%End
+
+ void setNorthMode( NorthMode mode );
+%Docstring
+Sets the ``mode`` used to calculate the arrow rotation.
+
+.. seealso:: :py:func:`northMode`
+
+.. seealso:: :py:func:`setNorthOffset`
+%End
+
+ double northOffset() const;
+%Docstring
+Returns the offset added to the arrows's rotation from a map's North.
+
+.. seealso:: :py:func:`setNorthOffset`
+
+.. seealso:: :py:func:`northMode`
+%End
+
+ void setNorthOffset( double offset );
+%Docstring
+Sets the ``offset`` added to the arrows's rotation from a map's North.
+
+.. seealso:: :py:func:`northOffset`
+
+.. seealso:: :py:func:`setNorthMode`
+%End
+
+ signals:
+ void arrowRotationChanged( double newRotation );
+%Docstring
+Emitted on arrow rotation change
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/layout/qgslayoutnortharrowhandler.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/locator/qgslocatorfilter.sip.in b/python/core/auto_generated/locator/qgslocatorfilter.sip.in
index 88112361fea8..e6d22dd86c39 100644
--- a/python/core/auto_generated/locator/qgslocatorfilter.sip.in
+++ b/python/core/auto_generated/locator/qgslocatorfilter.sip.in
@@ -252,6 +252,14 @@ Tests a ``candidate`` string to see if it should be considered a match for
a specified ``search`` string.
Filter subclasses should use this method when comparing strings instead
of directly using QString.contains() or Python 'in' checks.
+%End
+
+ static double fuzzyScore( const QString &candidate, const QString &search );
+%Docstring
+Tests a ``candidate`` string to see how likely it is a match for
+a specified ``search`` string.
+
+.. versionadded:: 3.14
%End
bool enabled() const;
diff --git a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in
index a749ac506e60..380da3cc81be 100644
--- a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in
+++ b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in
@@ -12,43 +12,6 @@
-class QgsMeshDatasetIndex
-{
-%Docstring
-
-QgsMeshDatasetIndex is index that identifies the dataset group (e.g. wind speed)
-and a dataset in this group (e.g. magnitude of wind speed in particular time)
-
-.. note::
-
- The API is considered EXPERIMENTAL and can be changed without a notice
-
-.. versionadded:: 3.4
-%End
-
-%TypeHeaderCode
-#include "qgsmeshdataprovider.h"
-%End
- public:
- QgsMeshDatasetIndex( int group = -1, int dataset = -1 );
-%Docstring
-Creates an index. -1 represents invalid group/dataset
-%End
- int group() const;
-%Docstring
-Returns a group index
-%End
- int dataset() const;
-%Docstring
-Returns a dataset index within group()
-%End
- bool isValid() const;
-%Docstring
-Returns whether index is valid, ie at least groups is set
-%End
- bool operator == ( QgsMeshDatasetIndex other ) const;
- bool operator != ( QgsMeshDatasetIndex other ) const;
-};
typedef QgsPoint QgsMeshVertex;
@@ -114,495 +77,6 @@ Remove all vertices, edges and faces
};
-class QgsMeshDatasetValue
-{
-%Docstring
-
-QgsMeshDatasetValue represents single dataset value
-
-could be scalar or vector. Nodata values are represented by NaNs.
-
-.. note::
-
- The API is considered EXPERIMENTAL and can be changed without a notice
-
-.. versionadded:: 3.2
-%End
-
-%TypeHeaderCode
-#include "qgsmeshdataprovider.h"
-%End
- public:
- QgsMeshDatasetValue( double x,
- double y );
-%Docstring
-Constructor for vector value
-%End
-
- QgsMeshDatasetValue( double scalar );
-%Docstring
-Constructor for scalar value
-%End
-
- QgsMeshDatasetValue();
-%Docstring
-Default Ctor, initialize to NaN
-%End
-
- ~QgsMeshDatasetValue();
-
- void set( double scalar );
-%Docstring
-Sets scalar value
-%End
-
- void setX( double x );
-%Docstring
-Sets X value
-%End
-
- void setY( double y );
-%Docstring
-Sets Y value
-%End
-
- double scalar() const;
-%Docstring
-Returns magnitude of vector for vector data or scalar value for scalar data
-%End
-
- double x() const;
-%Docstring
-Returns x value
-%End
-
- double y() const;
-%Docstring
-Returns y value
-%End
-
- bool operator==( QgsMeshDatasetValue other ) const;
-
-};
-
-class QgsMeshDataBlock
-{
-%Docstring
-
-QgsMeshDataBlock is a block of integers/doubles that can be used
-to retrieve:
-active flags (e.g. face's active integer flag)
-scalars (e.g. scalar dataset double values)
-vectors (e.g. vector dataset doubles x,y values)
-
-data are implicitly shared, so the class can be quickly copied
-std.numeric_limits.quiet_NaN() represents NODATA value
-
-Data can be accessed all at once with values() (faster) or
-value by value (slower) with active() or value()
-
-.. versionadded:: 3.6
-%End
-
-%TypeHeaderCode
-#include "qgsmeshdataprovider.h"
-%End
- public:
- enum DataType
- {
- ActiveFlagInteger,
- ScalarDouble,
- Vector2DDouble,
- };
-
- QgsMeshDataBlock();
-%Docstring
-Constructs an invalid block
-%End
-
- QgsMeshDataBlock( DataType type, int count );
-%Docstring
-Constructs a new block
-%End
-
- DataType type() const;
-%Docstring
-Type of data stored in the block
-%End
-
- int count() const;
-%Docstring
-Number of items stored in the block
-%End
-
- bool isValid() const;
-%Docstring
-Whether the block is valid
-%End
-
- QgsMeshDatasetValue value( int index ) const;
-%Docstring
-Returns a value represented by the index
-For active flag the behavior is undefined
-%End
-
- bool active( int index ) const;
-%Docstring
-Returns a value for active flag by the index
-For scalar and vector 2d the behavior is undefined
-%End
-
- void setActive( const QVector &vals );
-%Docstring
-Sets active flag values.
-
-If the data provider/datasets does not have active
-flag capability (== all values are valid), just
-set block validity by setValid( true )
-
-:param vals: value vector with size count()
-
-For scalar and vector 2d the behavior is undefined
-
-.. versionadded:: 3.12
-%End
-
- QVector active() const;
-%Docstring
-Returns active flag array
-
-Even for active flag valid dataset, the returned array could be empty.
-This means that the data provider/dataset does not support active flag
-capability, so all faces are active by default.
-
-For scalar and vector 2d the behavior is undefined
-
-.. versionadded:: 3.12
-%End
-
- QVector values() const;
-%Docstring
-Returns buffer to the array with values
-For vector it is pairs (x1, y1, x2, y2, ... )
-
-.. versionadded:: 3.12
-%End
-
- void setValues( const QVector &vals );
-%Docstring
-Sets values
-
-For scalar datasets, it must have size count()
-For vector datasets, it must have size 2 * count()
-For active flag the behavior is undefined
-
-.. versionadded:: 3.12
-%End
-
- void setValid( bool valid );
-%Docstring
-Sets block validity
-%End
-
-};
-
-class QgsMesh3dDataBlock
-{
-%Docstring
-
-QgsMesh3dDataBlock is a block of 3d stacked mesh data related N
-faces defined on base mesh frame.
-
-data are implicitly shared, so the class can be quickly copied
-std.numeric_limits.quiet_NaN() represents NODATA value
-
-.. note::
-
- The API is considered EXPERIMENTAL and can be changed without a notice
-
-.. versionadded:: 3.12
-%End
-
-%TypeHeaderCode
-#include "qgsmeshdataprovider.h"
-%End
- public:
- QgsMesh3dDataBlock();
-%Docstring
-Constructs an invalid block
-%End
-
- ~QgsMesh3dDataBlock();
-
- QgsMesh3dDataBlock( int count, bool isVector );
-%Docstring
-Constructs a new block for count faces
-%End
-
- void setValid( bool valid );
-%Docstring
-Sets block validity
-%End
-
- bool isValid() const;
-%Docstring
-Whether the block is valid
-%End
-
- bool isVector() const;
-%Docstring
-Whether we store vector values
-%End
-
- int count() const;
-%Docstring
-Number of 2d faces for which the volume data is stored in the block
-%End
-
- int firstVolumeIndex() const;
-%Docstring
-Index of the first volume stored in the buffer (absolute)
-%End
-
- int lastVolumeIndex() const;
-%Docstring
-Index of the last volume stored in the buffer (absolute)
-%End
-
- int volumesCount() const;
-%Docstring
-Returns number of volumes stored in the buffer
-%End
-
- QVector verticalLevelsCount() const;
-%Docstring
-Returns number of vertical level above 2d faces
-%End
-
- void setVerticalLevelsCount( const QVector &verticalLevelsCount );
-%Docstring
-Sets the vertical level counts
-%End
-
- QVector verticalLevels() const;
-%Docstring
-Returns the vertical levels height
-%End
-
- void setVerticalLevels( const QVector &verticalLevels );
-%Docstring
-Sets the vertical levels height
-%End
-
- QVector faceToVolumeIndex() const;
-%Docstring
-Returns the indexing between faces and volumes
-%End
-
- void setFaceToVolumeIndex( const QVector &faceToVolumeIndex );
-%Docstring
-Sets the indexing between faces and volumes
-%End
-
- QVector values() const;
-%Docstring
-Returns the values at volume centers
-
-For vector datasets the number of values is doubled (x1, y1, x2, y2, ... )
-%End
-
- QgsMeshDatasetValue value( int volumeIndex ) const;
-%Docstring
-Returns the value at volume centers
-
-:param volumeIndex: volume index relative to firstVolumeIndex()
-
-:return: value (scalar or vector)
-%End
-
- void setValues( const QVector &doubleBuffer );
-%Docstring
-Sets the values at volume centers
-
-For vector datasets the number of values is doubled (x1, y1, x2, y2, ... )
-%End
-
-};
-
-class QgsMeshDatasetGroupMetadata
-{
-%Docstring
-
-QgsMeshDatasetGroupMetadata is a collection of dataset group metadata
-such as whether the data is vector or scalar, name
-
-.. note::
-
- The API is considered EXPERIMENTAL and can be changed without a notice
-
-.. versionadded:: 3.4
-%End
-
-%TypeHeaderCode
-#include "qgsmeshdataprovider.h"
-%End
- public:
-
- enum DataType
- {
- DataOnFaces,
- DataOnVertices,
- DataOnVolumes,
- DataOnEdges
- };
-
- QgsMeshDatasetGroupMetadata();
-%Docstring
-Constructs an empty metadata object
-%End
-
- QgsMeshDatasetGroupMetadata( const QString &name,
- bool isScalar,
- DataType dataType,
- double minimum,
- double maximum,
- int maximumVerticalLevels,
- const QDateTime &referenceTime,
- const QMap &extraOptions );
-%Docstring
-Constructs a valid metadata object
-
-:param name: name of the dataset group
-:param isScalar: dataset contains scalar data, specifically the y-value of QgsMeshDatasetValue is NaN
-:param dataType: where the data are defined on (vertices, faces or volumes)
-:param minimum: minimum value (magnitude for vectors) present among all group's dataset values
-:param maximum: maximum value (magnitude for vectors) present among all group's dataset values
-:param maximumVerticalLevels: maximum number of vertical levels for 3d stacked meshes, 0 for 2d meshes
-:param referenceTime: reference time of the dataset group
-:param extraOptions: dataset's extra options stored by the provider. Usually contains the name, time value, time units, data file vendor, ...
-%End
-
- QString name() const;
-%Docstring
-Returns name of the dataset group
-%End
-
- QMap extraOptions() const;
-%Docstring
-Returns extra metadata options, for example description
-%End
-
- bool isVector() const;
-%Docstring
-Returns whether dataset group has vector data
-%End
-
- bool isScalar() const;
-%Docstring
-Returns whether dataset group has scalar data
-%End
-
- DataType dataType() const;
-%Docstring
-Returns whether dataset group data is defined on vertices or faces or volumes
-
-.. versionadded:: 3.12
-%End
-
- double minimum() const;
-%Docstring
-Returns minimum scalar value/vector magnitude present for whole dataset group
-%End
-
- double maximum() const;
-%Docstring
-Returns maximum scalar value/vector magnitude present for whole dataset group
-%End
-
- int maximumVerticalLevelsCount() const;
-%Docstring
-Returns maximum number of vertical levels for 3d stacked meshes
-
-.. versionadded:: 3.12
-%End
-
- QDateTime referenceTime() const;
-%Docstring
-Returns the reference time
-
-.. versionadded:: 3.12
-%End
-
-};
-
-class QgsMeshDatasetMetadata
-{
-%Docstring
-
-QgsMeshDatasetMetadata is a collection of mesh dataset metadata such
-as whether the data is valid or associated time for the dataset
-
-.. note::
-
- The API is considered EXPERIMENTAL and can be changed without a notice
-
-.. versionadded:: 3.2
-%End
-
-%TypeHeaderCode
-#include "qgsmeshdataprovider.h"
-%End
- public:
- QgsMeshDatasetMetadata();
-%Docstring
-Constructs an empty metadata object
-%End
-
- QgsMeshDatasetMetadata( double time,
- bool isValid,
- double minimum,
- double maximum,
- int maximumVerticalLevels
- );
-%Docstring
-Constructs a valid metadata object
-
-:param time: a time which this dataset represents in the dataset group
-:param isValid: dataset is loadad and valid for fetching the data
-:param minimum: minimum value (magnitude for vectors) present among dataset values
-:param maximum: maximum value (magnitude for vectors) present among dataset values
-:param maximumVerticalLevels: maximum number of vertical levels for 3d stacked meshes, 0 for 2d meshes
-%End
-
- double time() const;
-%Docstring
-Returns the time value for this dataset
-%End
-
- bool isValid() const;
-%Docstring
-Returns whether dataset is valid
-%End
-
- double minimum() const;
-%Docstring
-Returns minimum scalar value/vector magnitude present for the dataset
-%End
-
- double maximum() const;
-%Docstring
-Returns maximum scalar value/vector magnitude present for the dataset
-%End
-
- int maximumVerticalLevelsCount() const;
-%Docstring
-Returns maximum number of vertical levels for 3d stacked meshes
-
-.. versionadded:: 3.12
-%End
-
-};
-
class QgsMeshDataSourceInterface /Abstract/
{
%Docstring
@@ -840,6 +314,18 @@ Responsible for reading native mesh data
QgsMeshDataProvider( const QString &uri, const QgsDataProvider::ProviderOptions &providerOptions );
%Docstring
Ctor
+%End
+
+ virtual QgsMeshDataProviderTemporalCapabilities *temporalCapabilities();
+
+
+ void setTemporalUnit( QgsUnitTypes::TemporalUnit unit );
+%Docstring
+Sets the temporal unit of the provider and reload data if it changes.
+
+:param unit: the temporal unit
+
+.. versionadded:: 3.14
%End
signals:
@@ -847,6 +333,7 @@ Ctor
%Docstring
Emitted when some new dataset groups have been added
%End
+
};
/************************************************************************
diff --git a/python/core/auto_generated/mesh/qgsmeshdataprovidertemporalcapabilities.sip.in b/python/core/auto_generated/mesh/qgsmeshdataprovidertemporalcapabilities.sip.in
new file mode 100644
index 000000000000..cf5b5f97b8b1
--- /dev/null
+++ b/python/core/auto_generated/mesh/qgsmeshdataprovidertemporalcapabilities.sip.in
@@ -0,0 +1,99 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+class QgsMeshDataProviderTemporalCapabilities: QgsDataProviderTemporalCapabilities
+{
+%Docstring
+Class for handling properties relating to a mesh data provider's temporal capabilities.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsmeshdataprovidertemporalcapabilities.h"
+%End
+ public:
+
+ QgsMeshDataProviderTemporalCapabilities();
+%Docstring
+Constructor for QgsMeshDataProviderTemporalCapabilities
+%End
+
+ QgsMeshDatasetIndex datasetIndexFromRelativeTimeRange( int group, qint64 startTimeSinceGlobalReference, qint64 endTimeSinceGlobalReference ) const;
+%Docstring
+Returns the first dataset that are include in the range [``startTimeSinceGlobalReference``,``endTimeSinceGlobalReference``[ (in milliseconds)
+from the dataset ``group``. If no dataset is present in this range return the last dataset before this range if it not the last one
+of whole the dataset group
+
+Returns invalid dataset index if there is no data set in the range
+
+.. note::
+
+ for non temporal dataset group, the range is not used and the unique dataset is returned
+%End
+
+
+
+
+ bool hasReferenceTime() const;
+%Docstring
+Returns whether the reference time is set
+%End
+
+ QDateTime referenceTime() const;
+%Docstring
+Returns the reference time
+%End
+
+ QgsDateTimeRange timeExtent() const;
+%Docstring
+Returns the time extent using the internal reference time
+and the first and last times available from the all the dataset
+%End
+
+ QgsDateTimeRange timeExtent( const QDateTime &reference ) const;
+%Docstring
+Returns the time extent using an external ``reference`` date time
+and the first and last times available from the all the dataset
+%End
+
+ void setTemporalUnit( QgsUnitTypes::TemporalUnit temporalUnit );
+%Docstring
+Sets the temporal unit (``temporalUnit``) used to read data by the data provider
+
+Temporal units supported are milliseconds, seconds, minutes, hors, days and weeks
+%End
+
+ QgsUnitTypes::TemporalUnit temporalUnit() const;
+%Docstring
+Returns the temporal unit used to read data by the data provider
+%End
+
+ qint64 datasetTime( const QgsMeshDatasetIndex &index ) const;
+%Docstring
+Returns the relative time in milliseconds of the dataset
+%End
+
+ void clear();
+%Docstring
+Clears alls stored reference times and dataset times
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/mesh/qgsmeshdataset.sip.in b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in
new file mode 100644
index 000000000000..ac51f0b06724
--- /dev/null
+++ b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in
@@ -0,0 +1,558 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/mesh/qgsmeshdataset.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+
+
+class QgsMeshDatasetIndex
+{
+%Docstring
+
+QgsMeshDatasetIndex is index that identifies the dataset group (e.g. wind speed)
+and a dataset in this group (e.g. magnitude of wind speed in particular time)
+
+.. note::
+
+ The API is considered EXPERIMENTAL and can be changed without a notice
+
+.. versionadded:: 3.4
+%End
+
+%TypeHeaderCode
+#include "qgsmeshdataset.h"
+%End
+ public:
+ QgsMeshDatasetIndex( int group = -1, int dataset = -1 );
+%Docstring
+Creates an index. -1 represents invalid group/dataset
+%End
+ int group() const;
+%Docstring
+Returns a group index
+%End
+ int dataset() const;
+%Docstring
+Returns a dataset index within group()
+%End
+ bool isValid() const;
+%Docstring
+Returns whether index is valid, ie at least groups is set
+%End
+ bool operator == ( QgsMeshDatasetIndex other ) const;
+ bool operator != ( QgsMeshDatasetIndex other ) const;
+};
+
+class QgsMeshDatasetValue
+{
+%Docstring
+
+QgsMeshDatasetValue represents single dataset value
+
+could be scalar or vector. Nodata values are represented by NaNs.
+
+.. note::
+
+ The API is considered EXPERIMENTAL and can be changed without a notice
+
+.. versionadded:: 3.2
+%End
+
+%TypeHeaderCode
+#include "qgsmeshdataset.h"
+%End
+ public:
+ QgsMeshDatasetValue( double x,
+ double y );
+%Docstring
+Constructor for vector value
+%End
+
+ QgsMeshDatasetValue( double scalar );
+%Docstring
+Constructor for scalar value
+%End
+
+ QgsMeshDatasetValue();
+%Docstring
+Default Ctor, initialize to NaN
+%End
+
+ ~QgsMeshDatasetValue();
+
+ void set( double scalar );
+%Docstring
+Sets scalar value
+%End
+
+ void setX( double x );
+%Docstring
+Sets X value
+%End
+
+ void setY( double y );
+%Docstring
+Sets Y value
+%End
+
+ double scalar() const;
+%Docstring
+Returns magnitude of vector for vector data or scalar value for scalar data
+%End
+
+ double x() const;
+%Docstring
+Returns x value
+%End
+
+ double y() const;
+%Docstring
+Returns y value
+%End
+
+ bool operator==( QgsMeshDatasetValue other ) const;
+
+};
+
+class QgsMeshDataBlock
+{
+%Docstring
+
+QgsMeshDataBlock is a block of integers/doubles that can be used
+to retrieve:
+active flags (e.g. face's active integer flag)
+scalars (e.g. scalar dataset double values)
+vectors (e.g. vector dataset doubles x,y values)
+
+data are implicitly shared, so the class can be quickly copied
+std.numeric_limits.quiet_NaN() represents NODATA value
+
+Data can be accessed all at once with values() (faster) or
+value by value (slower) with active() or value()
+
+.. versionadded:: 3.6
+%End
+
+%TypeHeaderCode
+#include "qgsmeshdataset.h"
+%End
+ public:
+ enum DataType
+ {
+ ActiveFlagInteger,
+ ScalarDouble,
+ Vector2DDouble,
+ };
+
+ QgsMeshDataBlock();
+%Docstring
+Constructs an invalid block
+%End
+
+ QgsMeshDataBlock( DataType type, int count );
+%Docstring
+Constructs a new block
+%End
+
+ DataType type() const;
+%Docstring
+Type of data stored in the block
+%End
+
+ int count() const;
+%Docstring
+Number of items stored in the block
+%End
+
+ bool isValid() const;
+%Docstring
+Whether the block is valid
+%End
+
+ QgsMeshDatasetValue value( int index ) const;
+%Docstring
+Returns a value represented by the index
+For active flag the behavior is undefined
+%End
+
+ bool active( int index ) const;
+%Docstring
+Returns a value for active flag by the index
+For scalar and vector 2d the behavior is undefined
+%End
+
+ void setActive( const QVector &vals );
+%Docstring
+Sets active flag values.
+
+If the data provider/datasets does not have active
+flag capability (== all values are valid), just
+set block validity by setValid( true )
+
+:param vals: value vector with size count()
+
+For scalar and vector 2d the behavior is undefined
+
+.. versionadded:: 3.12
+%End
+
+ QVector active() const;
+%Docstring
+Returns active flag array
+
+Even for active flag valid dataset, the returned array could be empty.
+This means that the data provider/dataset does not support active flag
+capability, so all faces are active by default.
+
+For scalar and vector 2d the behavior is undefined
+
+.. versionadded:: 3.12
+%End
+
+ QVector values() const;
+%Docstring
+Returns buffer to the array with values
+For vector it is pairs (x1, y1, x2, y2, ... )
+
+.. versionadded:: 3.12
+%End
+
+ void setValues( const QVector &vals );
+%Docstring
+Sets values
+
+For scalar datasets, it must have size count()
+For vector datasets, it must have size 2 * count()
+For active flag the behavior is undefined
+
+.. versionadded:: 3.12
+%End
+
+ void setValid( bool valid );
+%Docstring
+Sets block validity
+%End
+
+};
+
+class QgsMesh3dDataBlock
+{
+%Docstring
+
+QgsMesh3dDataBlock is a block of 3d stacked mesh data related N
+faces defined on base mesh frame.
+
+data are implicitly shared, so the class can be quickly copied
+std.numeric_limits.quiet_NaN() represents NODATA value
+
+.. note::
+
+ The API is considered EXPERIMENTAL and can be changed without a notice
+
+.. versionadded:: 3.12
+%End
+
+%TypeHeaderCode
+#include "qgsmeshdataset.h"
+%End
+ public:
+ QgsMesh3dDataBlock();
+%Docstring
+Constructs an invalid block
+%End
+
+ ~QgsMesh3dDataBlock();
+
+ QgsMesh3dDataBlock( int count, bool isVector );
+%Docstring
+Constructs a new block for count faces
+%End
+
+ void setValid( bool valid );
+%Docstring
+Sets block validity
+%End
+
+ bool isValid() const;
+%Docstring
+Whether the block is valid
+%End
+
+ bool isVector() const;
+%Docstring
+Whether we store vector values
+%End
+
+ int count() const;
+%Docstring
+Number of 2d faces for which the volume data is stored in the block
+%End
+
+ int firstVolumeIndex() const;
+%Docstring
+Index of the first volume stored in the buffer (absolute)
+%End
+
+ int lastVolumeIndex() const;
+%Docstring
+Index of the last volume stored in the buffer (absolute)
+%End
+
+ int volumesCount() const;
+%Docstring
+Returns number of volumes stored in the buffer
+%End
+
+ QVector verticalLevelsCount() const;
+%Docstring
+Returns number of vertical level above 2d faces
+%End
+
+ void setVerticalLevelsCount( const QVector &verticalLevelsCount );
+%Docstring
+Sets the vertical level counts
+%End
+
+ QVector verticalLevels() const;
+%Docstring
+Returns the vertical levels height
+%End
+
+ void setVerticalLevels( const QVector &verticalLevels );
+%Docstring
+Sets the vertical levels height
+%End
+
+ QVector faceToVolumeIndex() const;
+%Docstring
+Returns the indexing between faces and volumes
+%End
+
+ void setFaceToVolumeIndex( const QVector &faceToVolumeIndex );
+%Docstring
+Sets the indexing between faces and volumes
+%End
+
+ QVector values() const;
+%Docstring
+Returns the values at volume centers
+
+For vector datasets the number of values is doubled (x1, y1, x2, y2, ... )
+%End
+
+ QgsMeshDatasetValue value( int volumeIndex ) const;
+%Docstring
+Returns the value at volume centers
+
+:param volumeIndex: volume index relative to firstVolumeIndex()
+
+:return: value (scalar or vector)
+%End
+
+ void setValues( const QVector &doubleBuffer );
+%Docstring
+Sets the values at volume centers
+
+For vector datasets the number of values is doubled (x1, y1, x2, y2, ... )
+%End
+
+};
+
+class QgsMeshDatasetGroupMetadata
+{
+%Docstring
+
+QgsMeshDatasetGroupMetadata is a collection of dataset group metadata
+such as whether the data is vector or scalar, name
+
+.. note::
+
+ The API is considered EXPERIMENTAL and can be changed without a notice
+
+.. versionadded:: 3.4
+%End
+
+%TypeHeaderCode
+#include "qgsmeshdataset.h"
+%End
+ public:
+
+ enum DataType
+ {
+ DataOnFaces,
+ DataOnVertices,
+ DataOnVolumes,
+ DataOnEdges
+ };
+
+ QgsMeshDatasetGroupMetadata();
+%Docstring
+Constructs an empty metadata object
+%End
+
+ QgsMeshDatasetGroupMetadata( const QString &name,
+ bool isScalar,
+ DataType dataType,
+ double minimum,
+ double maximum,
+ int maximumVerticalLevels,
+ const QDateTime &referenceTime,
+ bool isTemporal,
+ const QMap &extraOptions );
+%Docstring
+Constructs a valid metadata object
+
+:param name: name of the dataset group
+:param isScalar: dataset contains scalar data, specifically the y-value of QgsMeshDatasetValue is NaN
+:param dataType: where the data are defined on (vertices, faces or volumes)
+:param minimum: minimum value (magnitude for vectors) present among all group's dataset values
+:param maximum: maximum value (magnitude for vectors) present among all group's dataset values
+:param maximumVerticalLevels: maximum number of vertical levels for 3d stacked meshes, 0 for 2d meshes
+:param referenceTime: reference time of the dataset group
+:param isTemporal: weither the dataset group is temporal (contains time-related dataset)
+:param extraOptions: dataset's extra options stored by the provider. Usually contains the name, time value, time units, data file vendor, ...
+%End
+
+ QString name() const;
+%Docstring
+Returns name of the dataset group
+%End
+
+ QMap extraOptions() const;
+%Docstring
+Returns extra metadata options, for example description
+%End
+
+ bool isVector() const;
+%Docstring
+Returns whether dataset group has vector data
+%End
+
+ bool isScalar() const;
+%Docstring
+Returns whether dataset group has scalar data
+%End
+
+
+ bool isTemporal() const;
+%Docstring
+Returns whether the dataset group is temporal (contains time-related dataset)
+%End
+
+
+ DataType dataType() const;
+%Docstring
+Returns whether dataset group data is defined on vertices or faces or volumes
+
+.. versionadded:: 3.12
+%End
+
+ double minimum() const;
+%Docstring
+Returns minimum scalar value/vector magnitude present for whole dataset group
+%End
+
+ double maximum() const;
+%Docstring
+Returns maximum scalar value/vector magnitude present for whole dataset group
+%End
+
+ int maximumVerticalLevelsCount() const;
+%Docstring
+Returns maximum number of vertical levels for 3d stacked meshes
+
+.. versionadded:: 3.12
+%End
+
+ QDateTime referenceTime() const;
+%Docstring
+Returns the reference time
+
+.. versionadded:: 3.12
+%End
+
+};
+
+class QgsMeshDatasetMetadata
+{
+%Docstring
+
+QgsMeshDatasetMetadata is a collection of mesh dataset metadata such
+as whether the data is valid or associated time for the dataset
+
+.. note::
+
+ The API is considered EXPERIMENTAL and can be changed without a notice
+
+.. versionadded:: 3.2
+%End
+
+%TypeHeaderCode
+#include "qgsmeshdataset.h"
+%End
+ public:
+ QgsMeshDatasetMetadata();
+%Docstring
+Constructs an empty metadata object
+%End
+
+ QgsMeshDatasetMetadata( double time,
+ bool isValid,
+ double minimum,
+ double maximum,
+ int maximumVerticalLevels
+ );
+%Docstring
+Constructs a valid metadata object
+
+:param time: a time which this dataset represents in the dataset group
+:param isValid: dataset is loadad and valid for fetching the data
+:param minimum: minimum value (magnitude for vectors) present among dataset values
+:param maximum: maximum value (magnitude for vectors) present among dataset values
+:param maximumVerticalLevels: maximum number of vertical levels for 3d stacked meshes, 0 for 2d meshes
+%End
+
+ double time() const;
+%Docstring
+Returns the time value for this dataset
+%End
+
+ bool isValid() const;
+%Docstring
+Returns whether dataset is valid
+%End
+
+ double minimum() const;
+%Docstring
+Returns minimum scalar value/vector magnitude present for the dataset
+%End
+
+ double maximum() const;
+%Docstring
+Returns maximum scalar value/vector magnitude present for the dataset
+%End
+
+ int maximumVerticalLevelsCount() const;
+%Docstring
+Returns maximum number of vertical levels for 3d stacked meshes
+
+.. versionadded:: 3.12
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/mesh/qgsmeshdataset.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in
index 0dbb1b54cacd..22f0b94f61d8 100644
--- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in
+++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in
@@ -46,11 +46,11 @@ E.g. to create mesh with one quad and one triangle
"3.0, 2.0 \n" \
"2.0, 3.0 \n" \
"1.0, 3.0 \n" \
- "---"
+ "---" \
"0, 1, 3, 4 \n" \
"1, 2, 3 \n"
);
- QgsMeshLayer *scratchLayer = new QgsMeshLayer(uri, "My Scratch layer", "memory_mesh");
+ QgsMeshLayer *scratchLayer = new QgsMeshLayer(uri, "My Scratch layer", "mesh_memory");
\subsection mdal MDAL data provider (mdal)
@@ -126,6 +126,10 @@ QgsMeshLayer cannot be copied.
QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories );
virtual bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage,
const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ) const;
+ virtual bool writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context, StyleCategories categories = AllStyleCategories ) const;
+
+ virtual bool readStyle( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context, StyleCategories categories = AllStyleCategories );
+
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const;
@@ -134,6 +138,8 @@ QgsMeshLayer cannot be copied.
virtual bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const;
+ virtual QgsMeshLayerTemporalProperties *temporalProperties();
+
virtual void reload();
@@ -230,6 +236,55 @@ Returns the 3d values of stacked 3d mesh defined by the given point
.. versionadded:: 3.12
+%End
+
+ QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const;
+%Docstring
+Returns dataset index from active scalar group depending on the time range.
+If the temporal properties is not active, return the static dataset
+
+:param timeRange: the time range
+
+:return: dataset index
+
+.. versionadded:: 3.14
+%End
+
+ QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const;
+%Docstring
+Returns dataset index from active vector group depending on the time range
+If the temporal properties is not active, return the static dataset
+
+:param timeRange: the time range
+
+:return: dataset index
+
+.. versionadded:: 3.14
+%End
+
+
+
+ QgsMeshDatasetIndex staticScalarDatasetIndex() const;
+%Docstring
+Returns the static scalar dataset index that is rendered if the temporal properties is not active
+
+.. versionadded:: 3.14
+%End
+
+ QgsMeshDatasetIndex staticVectorDatasetIndex() const;
+%Docstring
+Returns the static vector dataset index that is rendered if the temporal properties is not active
+
+.. versionadded:: 3.14
+%End
+
+ void setReferenceTime( const QDateTime &referenceTime );
+%Docstring
+Sets the reference time of the layer
+
+:param referenceTime: the reference time
+
+.. versionadded:: 3.14
%End
public slots:
@@ -244,18 +299,18 @@ Sets the coordinate transform context to ``transformContext``.
signals:
- void activeScalarDatasetChanged( const QgsMeshDatasetIndex &index );
+ void activeScalarDatasetGroupChanged( int index );
%Docstring
-Emitted when active scalar dataset is changed
+Emitted when active scalar group dataset is changed
-.. versionadded:: 3.4
+.. versionadded:: 3.14
%End
- void activeVectorDatasetChanged( const QgsMeshDatasetIndex &index );
+ void activeVectorDatasetGroupChanged( int index );
%Docstring
-Emitted when active vector dataset is changed
+Emitted when active vector group dataset is changed
-.. versionadded:: 3.4
+.. versionadded:: 3.14
%End
void timeSettingsChanged( );
diff --git a/python/core/auto_generated/mesh/qgsmeshlayertemporalproperties.sip.in b/python/core/auto_generated/mesh/qgsmeshlayertemporalproperties.sip.in
new file mode 100644
index 000000000000..4c1738b27d67
--- /dev/null
+++ b/python/core/auto_generated/mesh/qgsmeshlayertemporalproperties.sip.in
@@ -0,0 +1,84 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/mesh/qgsmeshlayertemporalproperties.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+class QgsMeshLayerTemporalProperties : QgsMapLayerTemporalProperties
+{
+%Docstring
+Implementation of map layer temporal properties for mesh layers.
+
+The time in a mesh layer is defined by :
+- a reference time provided by the data, the project or the user
+- each dataset is associated with a relative times
+- time extent is defined by the first time and the last time of all dataset
+
+Reference time : AT
+Dataset 1 time o-----RT------RT-----RT-----------RT
+Dataset 2 time o---------RT------RT--------RT
+Dataset 3 time o------------------------------RT-------RT----------RT
+Time extent of layer o-----<--------------------------------------------->
+
+AT : absolute time (QDateTime)
+RT : relative time (qint64)
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsmeshlayertemporalproperties.h"
+%End
+ public:
+
+ QgsMeshLayerTemporalProperties( QObject *parent /TransferThis/ = 0, bool enabled = true );
+%Docstring
+Constructor for QgsMeshLayerTemporalProperties
+
+:param parent: pointer to the parent object
+:param enabled: argument specifies whether the temporal properties are initially enabled or not (see isActive()).
+%End
+
+ public:
+ virtual QDomElement writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context );
+
+ virtual bool readXml( const QDomElement &element, const QgsReadWriteContext &context );
+
+ virtual void setDefaultsFromDataProviderTemporalCapabilities( const QgsDataProviderTemporalCapabilities *capabilities );
+
+
+ QgsDateTimeRange timeExtent() const;
+%Docstring
+Returns the time extent
+%End
+
+ QDateTime referenceTime() const;
+%Docstring
+Returns the reference time
+%End
+
+ void setReferenceTime( const QDateTime &referenceTime, const QgsDataProviderTemporalCapabilities *capabilities );
+%Docstring
+Sets the reference time and update the time extent from the temporal capabilities,
+if the temporal capabilities is null, set a void time extent (reference time to reference time)
+
+:param referenceTime: the reference time
+:param capabilities: the temporal capabilities of the data provider
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/mesh/qgsmeshlayertemporalproperties.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in b/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in
index 8ec828b11754..3abbc26ef64e 100644
--- a/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in
+++ b/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in
@@ -96,7 +96,8 @@ Represents a mesh renderer settings for scalar datasets
#include "qgsmeshrenderersettings.h"
%End
public:
- enum DataInterpolationMethod
+
+ enum DataResamplingMethod
{
None,
@@ -135,7 +136,7 @@ Returns opacity
Sets opacity
%End
- DataInterpolationMethod dataInterpolationMethod() const;
+ DataResamplingMethod dataResamplingMethod() const;
%Docstring
Returns the type of interpolation to use to
convert face defined datasets to
@@ -144,7 +145,7 @@ values on vertices
.. versionadded:: 3.12
%End
- void setDataInterpolationMethod( const DataInterpolationMethod &dataInterpolationMethod );
+ void setDataResamplingMethod( const DataResamplingMethod &dataResamplingMethod );
%Docstring
Sets data interpolation method
@@ -334,7 +335,7 @@ Represents a streamline renderer settings for vector datasets displayed by strea
MeshGridded,
- Random,
+ Random
};
SeedingStartPointsMethod seedingMethod() const;
@@ -448,6 +449,14 @@ Represents a renderer settings for vector datasets
Traces
};
+ enum ColoringMethod
+ {
+ //! Render the vector with a single color
+ SingleColor,
+ //! Render the vector with a color ramp
+ ColorRamp
+ };
+
double lineWidth() const;
%Docstring
@@ -534,6 +543,34 @@ Returns the displaying method used to render vector datasets
Sets the displaying method used to render vector datasets
.. versionadded:: 3.12
+%End
+
+ ColoringMethod coloringMethod() const;
+%Docstring
+Returns the coloring method used to render vector datasets
+
+.. versionadded:: 3.14
+%End
+
+ void setColoringMethod( const ColoringMethod &coloringMethod );
+%Docstring
+Sets the coloring method used to render vector datasets
+
+.. versionadded:: 3.14
+%End
+
+ QgsColorRampShader colorRampShader() const;
+%Docstring
+Sets the color ramp shader used to render vector datasets
+
+.. versionadded:: 3.14
+%End
+
+ void setColorRampShader( const QgsColorRampShader &colorRampShader );
+%Docstring
+Returns the color ramp shader used to render vector datasets
+
+.. versionadded:: 3.14
%End
QgsMeshRendererVectorArrowSettings arrowSettings() const;
@@ -587,7 +624,6 @@ Writes configuration to a new DOM element
Reads configuration from the given DOM element
%End
-
};
class QgsMeshRendererSettings
@@ -678,31 +714,41 @@ Sets averaging method for conversion of 3d stacked mesh data to 2d data
Ownership of the method is not transferred.
%End
- QgsMeshDatasetIndex activeScalarDataset() const;
+ QDomElement writeXml( QDomDocument &doc ) const;
%Docstring
-Returns active scalar dataset
+Writes configuration to a new DOM element
%End
- void setActiveScalarDataset( QgsMeshDatasetIndex index = QgsMeshDatasetIndex() );
+ void readXml( const QDomElement &elem );
%Docstring
-Sets active scalar dataset for rendering
+Reads configuration from the given DOM element
%End
- QgsMeshDatasetIndex activeVectorDataset() const;
+ int activeScalarDatasetGroup() const;
%Docstring
-Returns active vector dataset
+Returns the active scalar dataset group
+
+.. versionadded:: 3.14
%End
- void setActiveVectorDataset( QgsMeshDatasetIndex index = QgsMeshDatasetIndex() );
+
+ void setActiveScalarDatasetGroup( int activeScalarDatasetGroup );
%Docstring
-Sets active vector dataset for rendering.
+Sets the active scalar dataset group
+
+.. versionadded:: 3.14
%End
- QDomElement writeXml( QDomDocument &doc ) const;
+ int activeVectorDatasetGroup() const;
%Docstring
-Writes configuration to a new DOM element
+Returns the active vector dataset group
+
+.. versionadded:: 3.14
%End
- void readXml( const QDomElement &elem );
+
+ void setActiveVectorDatasetGroup( int activeVectorDatasetGroup );
%Docstring
-Reads configuration from the given DOM element
+Sets the active vector dataset group
+
+.. versionadded:: 3.14
%End
};
diff --git a/python/core/auto_generated/mesh/qgsmeshtimesettings.sip.in b/python/core/auto_generated/mesh/qgsmeshtimesettings.sip.in
index 90061ffa0f66..2ef65eb5dd73 100644
--- a/python/core/auto_generated/mesh/qgsmeshtimesettings.sip.in
+++ b/python/core/auto_generated/mesh/qgsmeshtimesettings.sip.in
@@ -37,14 +37,6 @@ Represents a mesh time settings for mesh datasets
};
QgsMeshTimeSettings();
- QgsMeshTimeSettings( double relativeTimeOffsetHours, const QString &relativeTimeFormat );
-%Docstring
-Constructs relative time format settings with defined offset in hours
-%End
- QgsMeshTimeSettings( const QDateTime &absoluteTimeReferenceTime, const QString &absoluteTimeFormat );
-%Docstring
-Constructs absolute time format settings with defined reference time
-%End
QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
%Docstring
@@ -53,24 +45,6 @@ Writes configuration to a new DOM element
void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
%Docstring
Reads configuration from the given DOM element
-%End
-
- bool useAbsoluteTime() const;
-%Docstring
-Returns whether to use absolute time format
-%End
- void setUseAbsoluteTime( bool useAbsoluteTime );
-%Docstring
-Sets use absolute time flag
-%End
-
- double relativeTimeOffsetHours() const;
-%Docstring
-Returns number of offset hours for relative time formatting
-%End
- void setRelativeTimeOffsetHours( double relativeTimeOffsetHours );
-%Docstring
-Sets number of offset hours for relative time formatting
%End
QString relativeTimeFormat() const;
@@ -80,15 +54,6 @@ Returns format used for relative time
void setRelativeTimeFormat( const QString &relativeTimeFormat );
%Docstring
Sets format used for relative time
-%End
-
- QDateTime absoluteTimeReferenceTime() const;
-%Docstring
-Returns reference time used for absolute time format
-%End
- void setAbsoluteTimeReferenceTime( const QDateTime &absoluteTimeReferenceTime );
-%Docstring
-Sets reference time used for absolute time format
%End
QString absoluteTimeFormat() const;
@@ -98,20 +63,6 @@ Returns format used for absolute time
void setAbsoluteTimeFormat( const QString &absoluteTimeFormat );
%Docstring
Sets format used for absolute time
-%End
-
- TimeUnit providerTimeUnit() const;
-%Docstring
-Returns the provider time unit
-
-.. versionadded:: 3.12
-%End
-
- void setProviderTimeUnit( const TimeUnit &providerTimeUnit );
-%Docstring
-Sets the provider time unit
-
-.. versionadded:: 3.12
%End
};
diff --git a/python/core/auto_generated/numericformats/qgsfractionnumericformat.sip.in b/python/core/auto_generated/numericformats/qgsfractionnumericformat.sip.in
new file mode 100644
index 000000000000..7c9b6ed4d923
--- /dev/null
+++ b/python/core/auto_generated/numericformats/qgsfractionnumericformat.sip.in
@@ -0,0 +1,172 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/numericformats/qgsfractionnumericformat.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+class QgsFractionNumericFormat : QgsNumericFormat
+{
+%Docstring
+A numeric formatter which returns a vulgar fractional representation of a decimal value (e.g. "1/2" instead of 0.5).
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsfractionnumericformat.h"
+%End
+ public:
+
+ QgsFractionNumericFormat();
+%Docstring
+Default constructor
+%End
+
+ virtual QString id() const;
+
+ virtual QString visibleName() const;
+
+ virtual int sortKey();
+
+ virtual QString formatDouble( double value, const QgsNumericFormatContext &context ) const;
+
+ virtual QgsNumericFormat *clone() const /Factory/;
+
+ virtual QgsNumericFormat *create( const QVariantMap &configuration, const QgsReadWriteContext &context ) const /Factory/;
+
+ virtual QVariantMap configuration( const QgsReadWriteContext &context ) const;
+
+ virtual double suggestSampleValue() const;
+
+
+ bool useDedicatedUnicodeCharacters() const;
+%Docstring
+Returns ``True`` if dedicated unicode characters should be used, when the are available for the
+particular fraction (e.g. ½, ¼).
+
+.. seealso:: :py:func:`setUseDedicatedUnicodeCharacters`
+
+.. seealso:: :py:func:`useUnicodeSuperSubscript`
+%End
+
+ void setUseDedicatedUnicodeCharacters( bool enabled );
+%Docstring
+Sets whether dedicated unicode characters should be used, when the are available for the
+particular fraction (e.g. ½, ¼).
+
+.. seealso:: :py:func:`useDedicatedUnicodeCharacters`
+
+.. seealso:: :py:func:`setUseUnicodeSuperSubscript`
+%End
+
+ bool useUnicodeSuperSubscript() const;
+%Docstring
+Returns ``True`` if unicode superscript and subscript characters should be used, (e.g. "⁶/₇").
+
+.. seealso:: :py:func:`setUseUnicodeSuperSubscript`
+
+.. seealso:: :py:func:`useDedicatedUnicodeCharacters`
+%End
+
+ void setUseUnicodeSuperSubscript( bool enabled );
+%Docstring
+Sets whether unicode superscript and subscript characters should be used, (e.g. "⁶/₇").
+
+.. seealso:: :py:func:`useUnicodeSuperSubscript`
+
+.. seealso:: :py:func:`setUseDedicatedUnicodeCharacters`
+%End
+
+ bool showThousandsSeparator() const;
+%Docstring
+Returns ``True`` if the thousands grouping separator will be shown.
+
+.. seealso:: :py:func:`setShowThousandsSeparator`
+%End
+
+ void setShowThousandsSeparator( bool show );
+%Docstring
+Sets whether the thousands grouping separator will be shown.
+
+.. seealso:: :py:func:`showThousandsSeparator`
+%End
+
+ bool showPlusSign() const;
+%Docstring
+Returns ``True`` if a leading plus sign will be shown for positive values.
+
+.. seealso:: :py:func:`setShowPlusSign`
+%End
+
+ void setShowPlusSign( bool show );
+%Docstring
+Sets whether a leading plus sign will be shown for positive values.
+
+.. seealso:: :py:func:`showPlusSign`
+%End
+
+ QChar thousandsSeparator() const;
+%Docstring
+Returns any override for the thousands separator character. If an invalid QChar is returned,
+then the QGIS locale separator is used instead.
+
+.. seealso:: :py:func:`setThousandsSeparator`
+%End
+
+ void setThousandsSeparator( QChar character );
+%Docstring
+Sets an override ``character`` for the thousands separator character. If an invalid QChar is set,
+then the QGIS locale separator is used instead.
+
+.. seealso:: :py:func:`thousandsSeparator`
+%End
+
+ static bool doubleToVulgarFraction( const double value, unsigned long long &numerator /Out/, unsigned long long &denominator /Out/, int &sign /Out/, const double tolerance = 1e-10 );
+%Docstring
+Converts a double ``value`` to a vulgar fraction (e.g. ⅓, ¼, etc) by attempting to calculate
+the corresponding ``numerator`` and ``denominator``, within the specified ``tolerance``.
+
+This method is based of Richard's algorithm (1981) from "Continued Fractions without Tears" (University of Minnesota).
+
+:param value: input value to convert
+:param denominator: will be set to the calculated fraction denominator
+:param sign: will be set to the sign of the result (as -1 or +1 values)
+:param tolerance: acceptable tolerance. Larger values will give "nicer" fractions.
+
+:return: - ``True`` if ``value`` was successfully converted to a fraction
+ - numerator: will be set to calculated fraction numerator
+%End
+
+ static QString toUnicodeSuperscript( const QString &input );
+%Docstring
+Converts numbers in an ``input`` string to unicode superscript equivalents.
+
+.. seealso:: :py:func:`toUnicodeSubscript`
+%End
+
+ static QString toUnicodeSubscript( const QString &input );
+%Docstring
+Converts numbers in an ``input`` string to unicode subscript equivalents.
+
+.. seealso:: :py:func:`toUnicodeSuperscript`
+%End
+
+ protected:
+
+ virtual void setConfiguration( const QVariantMap &configuration, const QgsReadWriteContext &context );
+%Docstring
+Sets the format's ``configuration``.
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/numericformats/qgsfractionnumericformat.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/numericformats/qgsnumericformat.sip.in b/python/core/auto_generated/numericformats/qgsnumericformat.sip.in
index b1792b5fd79e..fc1528b6ac9c 100644
--- a/python/core/auto_generated/numericformats/qgsnumericformat.sip.in
+++ b/python/core/auto_generated/numericformats/qgsnumericformat.sip.in
@@ -168,6 +168,8 @@ This is an abstract base class and will always need to be subclassed.
sipType = sipType_QgsCurrencyNumericFormat;
else if ( dynamic_cast< QgsBasicNumericFormat * >( sipCpp ) )
sipType = sipType_QgsBasicNumericFormat;
+ else if ( dynamic_cast< QgsFractionNumericFormat * >( sipCpp ) )
+ sipType = sipType_QgsFractionNumericFormat;
else
sipType = NULL;
%End
diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelalgorithm.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelalgorithm.sip.in
index fead741a03cc..d34891dc92eb 100644
--- a/python/core/auto_generated/processing/models/qgsprocessingmodelalgorithm.sip.in
+++ b/python/core/auto_generated/processing/models/qgsprocessingmodelalgorithm.sip.in
@@ -281,6 +281,37 @@ this name a new component will be added to the model and returned.
Updates the model's parameter definitions to include all relevant destination
parameters as required by child algorithm ModelOutputs.
Must be called whenever child algorithm ModelOutputs are altered.
+%End
+
+ void addGroupBox( const QgsProcessingModelGroupBox &groupBox );
+%Docstring
+Adds a new group ``box`` to the model.
+
+If an existing group box with the same uuid already exists then its definition will be replaced.
+
+.. seealso:: :py:func:`groupBoxes`
+
+.. versionadded:: 3.14
+%End
+
+ QList< QgsProcessingModelGroupBox > groupBoxes() const;
+%Docstring
+Returns a list of the group boxes within the model.
+
+.. seealso:: :py:func:`addGroupBox`
+
+.. versionadded:: 3.14
+%End
+
+ void removeGroupBox( const QString &uuid );
+%Docstring
+Removes the group box with matching ``uuid`` from the model.
+
+.. seealso:: :py:func:`addGroupBox`
+
+.. seealso:: :py:func:`groupBoxes`
+
+.. versionadded:: 3.14
%End
bool toFile( const QString &path ) const;
diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip.in
index dc01d7dc98cf..13d030136118 100644
--- a/python/core/auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip.in
+++ b/python/core/auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip.in
@@ -30,6 +30,7 @@ Source for the value of a parameter for a child algorithm within a model.
StaticValue,
Expression,
ExpressionText,
+ ModelOutput,
};
QgsProcessingModelChildParameterSource();
@@ -120,6 +121,13 @@ algorithms already executed by the model.
Source source() const;
%Docstring
Returns the parameter value's source.
+%End
+
+ void setSource( Source source );
+%Docstring
+Sets the parameter's source.
+
+.. versionadded:: 3.14
%End
QVariant staticValue() const;
@@ -245,6 +253,13 @@ Loads this source from a QVariantMap.
Attempts to convert the source to executable Python code.
The ``friendlyChildNames`` argument gives a map of child id to a friendly algorithm name, to be used in the code to identify that algorithm instead of the raw child id.
+%End
+
+ QString friendlyIdentifier( QgsProcessingModelAlgorithm *model ) const;
+%Docstring
+Returns a user-friendly identifier for this source, given the context of the specified ``model``.
+
+.. versionadded:: 3.14
%End
};
diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in
index d395352fe3c6..0de7ed00de7a 100644
--- a/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in
+++ b/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in
@@ -69,6 +69,27 @@ Sets the ``size`` of the model component within the graphical modeler.
.. seealso:: :py:func:`size`
+.. versionadded:: 3.14
+%End
+
+ QColor color() const;
+%Docstring
+Returns the color of the model component within the graphical modeler.
+
+An invalid color indicates that the default color for the component should be used.
+
+.. seealso:: :py:func:`setColor`
+
+.. versionadded:: 3.14
+%End
+
+ void setColor( const QColor &color );
+%Docstring
+Sets the ``color`` of the model component within the graphical modeler. An invalid ``color``
+indicates that the default color for the component should be used.
+
+.. seealso:: :py:func:`color`
+
.. versionadded:: 3.14
%End
diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelgroupbox.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelgroupbox.sip.in
new file mode 100644
index 000000000000..fe9449389cad
--- /dev/null
+++ b/python/core/auto_generated/processing/models/qgsprocessingmodelgroupbox.sip.in
@@ -0,0 +1,62 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/processing/models/qgsprocessingmodelgroupbox.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+class QgsProcessingModelGroupBox : QgsProcessingModelComponent
+{
+%Docstring
+Represents a group box in a model.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingmodelgroupbox.h"
+%End
+ public:
+
+ QgsProcessingModelGroupBox( const QString &description = QString() );
+%Docstring
+Constructor for QgsProcessingModelGroupBox with the specified ``description``.
+%End
+
+ virtual QgsProcessingModelGroupBox *clone() const /Factory/;
+
+
+ QVariant toVariant() const;
+%Docstring
+Saves this group box to a QVariant.
+
+.. seealso:: :py:func:`loadVariant`
+%End
+
+ bool loadVariant( const QVariantMap &map );
+%Docstring
+Loads this group box from a QVariantMap.
+
+.. seealso:: :py:func:`toVariant`
+%End
+
+ QString uuid() const;
+%Docstring
+Returns the unique ID associated with this group box.
+%End
+
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/processing/models/qgsprocessingmodelgroupbox.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in b/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in
index a5223e116689..482e2692f661 100644
--- a/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in
+++ b/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in
@@ -47,6 +47,8 @@ Abstract base class for processing algorithms.
FlagSupportsInPlaceEdits,
FlagKnownIssues,
FlagCustomException,
+ FlagPruneModelBranchesBasedOnAlgorithmResults,
+ FlagSkipGenericModelLogging,
FlagDeprecated,
};
typedef QFlags Flags;
@@ -294,6 +296,43 @@ manner.
bool hasHtmlOutputs() const;
%Docstring
Returns ``True`` if this algorithm generates HTML outputs.
+%End
+
+ enum PropertyAvailability
+ {
+ NotAvailable,
+ Available,
+ };
+
+ struct VectorProperties
+ {
+ QgsFields fields;
+
+ QgsWkbTypes::Type wkbType;
+
+ QgsCoordinateReferenceSystem crs;
+
+ QgsProcessingAlgorithm::PropertyAvailability availability;
+ };
+
+ virtual QgsProcessingAlgorithm::VectorProperties sinkProperties( const QString &sink,
+ const QVariantMap ¶meters,
+ QgsProcessingContext &context,
+ const QMap< QString, QgsProcessingAlgorithm::VectorProperties > &sourceProperties ) const;
+%Docstring
+Returns the vector properties which will be used for the ``sink`` with matching name.
+
+The ``parameters`` argument specifies the values of all parameters which would be used to generate
+the sink. These can be used alongside the provided ``context`` in order to pre-evaluate inputs
+when required in order to determine the sink's properties.
+
+The ``sourceProperties`` map will contain the vector properties of the various sources used
+as inputs to the algorithm. These will only be available in certain circumstances (e.g. when the
+algorithm is used within a model), so implementations will need to be adaptable to circumstances
+when either ``sourceParameters`` is empty or ``parameters`` is empty, and use whatever information
+is passed in order to make a best guess determination of the output properties.
+
+.. versionadded:: 3.14
%End
QVariantMap run( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok /Out/ = 0, const QVariantMap &configuration = QVariantMap() ) const;
@@ -623,7 +662,7 @@ Evaluates the parameter with matching ``name`` to a static boolean value.
%End
QgsFeatureSink *parameterAsSink( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context, QString &destinationIdentifier /Out/,
- const QgsFields &fields, QgsWkbTypes::Type geometryType = QgsWkbTypes::NoGeometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem(), QgsFeatureSink::SinkFlags sinkFlags = 0 ) const /Factory/;
+ const QgsFields &fields, QgsWkbTypes::Type geometryType = QgsWkbTypes::NoGeometry, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem(), QgsFeatureSink::SinkFlags sinkFlags = 0 ) const throw( QgsProcessingException ) /Factory/;
%Docstring
Evaluates the parameter with matching ``name`` to a feature sink.
@@ -637,6 +676,8 @@ The ``destinationIdentifier`` argument will be set to a string which can be used
to the sink, e.g. via calling :py:func:`QgsProcessingUtils.mapLayerFromString()`
This function creates a new object and the caller takes responsibility for deleting the returned object.
+
+:raises :: py:class:`QgsProcessingException`
%End
QgsProcessingFeatureSource *parameterAsSource( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const /Factory/;
@@ -872,6 +913,20 @@ Evaluates the parameter with matching ``name`` to a color, or returns an invalid
%Docstring
Evaluates the parameter with matching ``name`` to a connection name string.
+.. versionadded:: 3.14
+%End
+
+ QString parameterAsSchema( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context );
+%Docstring
+Evaluates the parameter with matching ``name`` to a database schema name string.
+
+.. versionadded:: 3.14
+%End
+
+ QString parameterAsDatabaseTableName( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context );
+%Docstring
+Evaluates the parameter with matching ``name`` to a database table name string.
+
.. versionadded:: 3.14
%End
@@ -1136,6 +1191,11 @@ Read the source from ``parameters`` and ``context`` and set it
.. versionadded:: 3.4
%End
+ virtual QgsProcessingAlgorithm::VectorProperties sinkProperties( const QString &sink,
+ const QVariantMap ¶meters,
+ QgsProcessingContext &context,
+ const QMap< QString, QgsProcessingAlgorithm::VectorProperties > &sourceProperties ) const;
+
};
diff --git a/python/core/auto_generated/processing/qgsprocessingcontext.sip.in b/python/core/auto_generated/processing/qgsprocessingcontext.sip.in
index 34e0be8fa1b4..154f0598cb04 100644
--- a/python/core/auto_generated/processing/qgsprocessingcontext.sip.in
+++ b/python/core/auto_generated/processing/qgsprocessingcontext.sip.in
@@ -290,6 +290,7 @@ called using the feature with invalid geometry as a parameter.
%End
+
void setTransformErrorCallback( SIP_PYCALLABLE / AllowNone / );
%Docstring
Sets a callback function to use when encountering a transform error when iterating
diff --git a/python/core/auto_generated/processing/qgsprocessingfeedback.sip.in b/python/core/auto_generated/processing/qgsprocessingfeedback.sip.in
index 1482de49d6a4..4da88e0d2db1 100644
--- a/python/core/auto_generated/processing/qgsprocessingfeedback.sip.in
+++ b/python/core/auto_generated/processing/qgsprocessingfeedback.sip.in
@@ -27,6 +27,14 @@ it to users via the GUI.
%End
public:
+ QgsProcessingFeedback( bool logFeedback = true );
+%Docstring
+Constructor for QgsProcessingFeedback.
+
+If ``logFeedback`` is ``True``, then all feedback received will be directed
+to :py:class:`QgsMessageLog`.
+%End
+
virtual void setProgressText( const QString &text );
%Docstring
Sets a progress report text string. This can be used in conjunction with
@@ -98,6 +106,24 @@ report the output from executing an external command or subprocess.
Pushes a summary of the QGIS (and underlying library) version information to the log.
.. versionadded:: 3.4.7
+%End
+
+ virtual QString htmlLog() const;
+%Docstring
+Returns the HTML formatted contents of the log, which contains all messages pushed to the feedback object.
+
+.. seealso:: :py:func:`textLog`
+
+.. versionadded:: 3.14
+%End
+
+ virtual QString textLog() const;
+%Docstring
+Returns the plain text contents of the log, which contains all messages pushed to the feedback object.
+
+.. seealso:: :py:func:`htmlLog`
+
+.. versionadded:: 3.14
%End
};
@@ -146,6 +172,9 @@ to scale the current progress to account for progress through the overall proces
virtual void pushConsoleInfo( const QString &info );
+ virtual QString htmlLog() const;
+
+ virtual QString textLog() const;
};
diff --git a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in
index ad680468e00e..6cdf1f0463f5 100644
--- a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in
+++ b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in
@@ -25,20 +25,76 @@ Encapsulates settings relating to a feature source input to a processing algorit
%End
public:
- QgsProcessingFeatureSourceDefinition( const QString &source = QString(), bool selectedFeaturesOnly = false );
+ enum Flag
+ {
+ FlagOverrideDefaultGeometryCheck,
+ FlagCreateIndividualOutputPerInputFeature,
+ };
+ typedef QFlags Flags;
+
+
+ QgsProcessingFeatureSourceDefinition( const QString &source = QString(), bool selectedFeaturesOnly = false, long long featureLimit = -1,
+ QgsProcessingFeatureSourceDefinition::Flags flags = 0, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid );
%Docstring
-Constructor for QgsProcessingFeatureSourceDefinition, accepting a static string source.
+Constructor for QgsProcessingFeatureSourceDefinition, accepting a static string ``source``.
+
+If ``selectedFeaturesOnly`` is ``True``, then only selected features from the source will be used.
+
+The optional ``featureLimit`` can be set to a value > 0 to place a hard limit on the maximum number
+of features which will be read from the source.
+
+The ``flags`` argument can be used to specify flags which dictate the source behavior.
+
+If the QgsProcessingFeatureSourceDefinition.Flag.FlagOverrideDefaultGeometryCheck is set in ``flags``, then the value of ``geometryCheck`` will override
+the default geometry check method (as dictated by :py:class:`QgsProcessingContext`) for this source.
%End
- QgsProcessingFeatureSourceDefinition( const QgsProperty &source, bool selectedFeaturesOnly = false );
+ QgsProcessingFeatureSourceDefinition( const QgsProperty &source, bool selectedFeaturesOnly = false, long long featureLimit = -1,
+ QgsProcessingFeatureSourceDefinition::Flags flags = 0, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid );
%Docstring
Constructor for QgsProcessingFeatureSourceDefinition, accepting a QgsProperty source.
+
+If ``selectedFeaturesOnly`` is ``True``, then only selected features from the source will be used.
+
+The optional ``featureLimit`` can be set to a value > 0 to place a hard limit on the maximum number
+of features which will be read from the source.
+
+The ``flags`` argument can be used to specify flags which dictate the source behavior.
+
+If the QgsProcessingFeatureSourceDefinition.Flag.FlagOverrideDefaultGeometryCheck is set in ``flags``, then the value of ``geometryCheck`` will override
+the default geometry check method (as dictated by :py:class:`QgsProcessingContext`) for this source.
%End
QgsProperty source;
bool selectedFeaturesOnly;
+ long long featureLimit;
+
+ Flags flags;
+
+ QgsFeatureRequest::InvalidGeometryCheck geometryCheck;
+
+ QVariant toVariant() const;
+%Docstring
+Saves this source definition to a QVariantMap, wrapped in a QVariant.
+You can use QgsXmlUtils.writeVariant to save it to an XML document.
+
+.. seealso:: :py:func:`loadVariant`
+
+.. versionadded:: 3.14
+%End
+
+ bool loadVariant( const QVariantMap &map );
+%Docstring
+Loads this source definition from a QVariantMap, wrapped in a QVariant.
+You can use QgsXmlUtils.readVariant to load it from an XML document.
+
+.. seealso:: :py:func:`toVariant`
+
+.. versionadded:: 3.14
+%End
+
bool operator==( const QgsProcessingFeatureSourceDefinition &other );
bool operator!=( const QgsProcessingFeatureSourceDefinition &other );
@@ -47,6 +103,8 @@ Constructor for QgsProcessingFeatureSourceDefinition, accepting a QgsProperty so
};
+QFlags operator|(QgsProcessingFeatureSourceDefinition::Flag f1, QFlags f2);
+
class QgsProcessingOutputLayerDefinition
@@ -85,6 +143,39 @@ to automatically load the resulting sink/layer after completing processing.
QVariantMap createOptions;
+ bool useRemapping() const;
+%Docstring
+Returns ``True`` if the output uses a remapping definition.
+
+.. seealso:: :py:func:`remappingDefinition`
+
+.. versionadded:: 3.14
+%End
+
+ QgsRemappingSinkDefinition remappingDefinition() const;
+%Docstring
+Returns the output remapping definition, if useRemapping() is ``True``.
+
+.. seealso:: :py:func:`useRemapping`
+
+.. seealso:: :py:func:`setRemappingDefinition`
+
+.. versionadded:: 3.14
+%End
+
+ void setRemappingDefinition( const QgsRemappingSinkDefinition &definition );
+%Docstring
+Sets the remapping ``definition`` to use when adding features to the output layer.
+
+Calling this method will set useRemapping() to ``True``.
+
+.. seealso:: :py:func:`remappingDefinition`
+
+.. seealso:: :py:func:`useRemapping`
+
+.. versionadded:: 3.14
+%End
+
QVariant toVariant() const;
%Docstring
Saves this output layer definition to a QVariantMap, wrapped in a QVariant.
@@ -107,6 +198,9 @@ You can use QgsXmlUtils.readVariant to load it from an XML document.
operator QVariant() const;
+ bool operator==( const QgsProcessingOutputLayerDefinition &other ) const;
+ bool operator!=( const QgsProcessingOutputLayerDefinition &other ) const;
+
};
@@ -199,6 +293,10 @@ their acceptable ranges, defaults, etc.
sipType = sipType_QgsProcessingParameterDateTime;
else if ( sipCpp->type() == QgsProcessingParameterProviderConnection::typeName() )
sipType = sipType_QgsProcessingParameterProviderConnection;
+ else if ( sipCpp->type() == QgsProcessingParameterDatabaseSchema::typeName() )
+ sipType = sipType_QgsProcessingParameterDatabaseSchema;
+ else if ( sipCpp->type() == QgsProcessingParameterDatabaseTable::typeName() )
+ sipType = sipType_QgsProcessingParameterDatabaseTable;
else
sipType = nullptr;
%End
@@ -754,7 +852,7 @@ This function creates a new object and the caller takes responsibility for delet
static QgsFeatureSink *parameterAsSink( const QgsProcessingParameterDefinition *definition, const QVariant &value,
const QgsFields &fields, QgsWkbTypes::Type geometryType, const QgsCoordinateReferenceSystem &crs,
- QgsProcessingContext &context, QString &destinationIdentifier /Out/, QgsFeatureSink::SinkFlags sinkFlags = 0 ) /Factory/;
+ QgsProcessingContext &context, QString &destinationIdentifier /Out/, QgsFeatureSink::SinkFlags sinkFlags = 0 ) throw( QgsProcessingException ) /Factory/;
%Docstring
Evaluates the parameter with matching ``definition`` and ``value`` to a feature sink.
@@ -768,6 +866,8 @@ to the sink, e.g. via calling :py:func:`QgsProcessingUtils.mapLayerFromString()`
This function creates a new object and the caller takes responsibility for deleting the returned object.
+:raises :: py:class:`QgsProcessingException`
+
.. versionadded:: 3.4
%End
@@ -845,7 +945,7 @@ a conversion in this case and will return the target layer name in the ``layerNa
.. versionadded:: 3.10
%End
- static QgsMapLayer *parameterAsLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context );
+ static QgsMapLayer *parameterAsLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingUtils::LayerHint layerHint = QgsProcessingUtils::LayerHint::UnknownType );
%Docstring
Evaluates the parameter with matching ``definition`` to a map layer.
@@ -854,7 +954,7 @@ sources and stored temporarily in the ``context``. In either case, callers do no
need to handle deletion of the returned layer.
%End
- static QgsMapLayer *parameterAsLayer( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context );
+ static QgsMapLayer *parameterAsLayer( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context, QgsProcessingUtils::LayerHint layerHint = QgsProcessingUtils::LayerHint::UnknownType );
%Docstring
Evaluates the parameter with matching ``definition`` and ``value`` to a map layer.
@@ -1014,6 +1114,14 @@ Returns the coordinate reference system associated with an extent parameter valu
.. seealso:: :py:func:`parameterAsExtent`
%End
+ static QgsCoordinateReferenceSystem parameterAsExtentCrs( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context );
+%Docstring
+Returns the coordinate reference system associated with an extent parameter value.
+
+.. seealso:: :py:func:`parameterAsExtent`
+%End
+
+
static QgsPointXY parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
%Docstring
@@ -1201,6 +1309,34 @@ Evaluates the parameter with matching ``definition`` to a connection name string
%Docstring
Evaluates the parameter with matching ``definition`` and ``value`` to a connection name string.
+.. versionadded:: 3.14
+%End
+
+ static QString parameterAsSchema( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, const QgsProcessingContext &context );
+%Docstring
+Evaluates the parameter with matching ``definition`` to a database schema name.
+
+.. versionadded:: 3.14
+%End
+
+ static QString parameterAsSchema( const QgsProcessingParameterDefinition *definition, const QVariant &value, const QgsProcessingContext &context );
+%Docstring
+Evaluates the parameter with matching ``definition`` and ``value`` to a database schema name.
+
+.. versionadded:: 3.14
+%End
+
+ static QString parameterAsDatabaseTableName( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, const QgsProcessingContext &context );
+%Docstring
+Evaluates the parameter with matching ``definition`` to a database table name.
+
+.. versionadded:: 3.14
+%End
+
+ static QString parameterAsDatabaseTableName( const QgsProcessingParameterDefinition *definition, const QVariant &value, const QgsProcessingContext &context );
+%Docstring
+Evaluates the parameter with matching ``definition`` and ``value`` to a database table name.
+
.. versionadded:: 3.14
%End
@@ -1302,44 +1438,6 @@ Creates a new parameter using the definition from a script code.
};
-class QgsProcessingParameterMapLayer : QgsProcessingParameterDefinition
-{
-%Docstring
-A map layer parameter for processing algorithms.
-
-.. versionadded:: 3.0
-%End
-
-%TypeHeaderCode
-#include "qgsprocessingparameters.h"
-%End
- public:
-
- QgsProcessingParameterMapLayer( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(),
- bool optional = false );
-%Docstring
-Constructor for QgsProcessingParameterMapLayer.
-%End
-
- static QString typeName();
-%Docstring
-Returns the type name for the parameter class.
-%End
- virtual QgsProcessingParameterDefinition *clone() const /Factory/;
-
- virtual QString type() const;
- virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const;
-
- virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const;
-
-
- static QgsProcessingParameterMapLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/;
-%Docstring
-Creates a new parameter using the definition from a script code.
-%End
-
-};
-
class QgsProcessingParameterExtent : QgsProcessingParameterDefinition
{
%Docstring
@@ -1633,7 +1731,7 @@ Creates a new parameter using the definition from a script code.
};
-class QgsProcessingParameterMultipleLayers : QgsProcessingParameterDefinition
+class QgsProcessingParameterMultipleLayers : QgsProcessingParameterDefinition, QgsFileFilterGenerator
{
%Docstring
A parameter for processing algorithms which accepts multiple map layers.
@@ -1668,6 +1766,8 @@ Returns the type name for the parameter class.
virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+ virtual QString createFileFilter() const;
+
QgsProcessing::SourceType layerType() const;
%Docstring
@@ -2015,7 +2115,7 @@ Creates a new parameter using the definition from a script code.
};
-class QgsProcessingParameterRasterLayer : QgsProcessingParameterDefinition
+class QgsProcessingParameterRasterLayer : QgsProcessingParameterDefinition, QgsFileFilterGenerator
{
%Docstring
A raster layer parameter for processing algorithms.
@@ -2045,6 +2145,8 @@ Returns the type name for the parameter class.
virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const;
+ virtual QString createFileFilter() const;
+
static QgsProcessingParameterRasterLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/;
%Docstring
@@ -2331,7 +2433,7 @@ Sets the geometry ``types`` for sources acceptable by the parameter.
};
-class QgsProcessingParameterVectorLayer : QgsProcessingParameterDefinition, QgsProcessingParameterLimitedDataTypes
+class QgsProcessingParameterVectorLayer : QgsProcessingParameterDefinition, QgsProcessingParameterLimitedDataTypes, QgsFileFilterGenerator
{
%Docstring
A vector layer (with or without geometry) parameter for processing algorithms. Consider using
@@ -2367,6 +2469,8 @@ Returns the type name for the parameter class.
virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+ virtual QString createFileFilter() const;
+
virtual QVariantMap toVariantMap() const;
@@ -2380,7 +2484,7 @@ Creates a new parameter using the definition from a script code.
};
-class QgsProcessingParameterMeshLayer : QgsProcessingParameterDefinition
+class QgsProcessingParameterMeshLayer : QgsProcessingParameterDefinition, QgsFileFilterGenerator
{
%Docstring
A mesh layer parameter for processing algorithms.
@@ -2412,6 +2516,8 @@ Returns the type name for the parameter class.
virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const;
+ virtual QString createFileFilter() const;
+
static QgsProcessingParameterMeshLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/;
%Docstring
@@ -2419,6 +2525,56 @@ Creates a new parameter using the definition from a script code.
%End
};
+class QgsProcessingParameterMapLayer : QgsProcessingParameterDefinition, QgsProcessingParameterLimitedDataTypes, QgsFileFilterGenerator
+{
+%Docstring
+A map layer parameter for processing algorithms.
+
+.. versionadded:: 3.0
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingparameters.h"
+%End
+ public:
+
+ QgsProcessingParameterMapLayer( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(),
+ bool optional = false,
+ const QList< int > &types = QList< int >() );
+%Docstring
+Constructor for QgsProcessingParameterMapLayer.
+%End
+
+ static QString typeName();
+%Docstring
+Returns the type name for the parameter class.
+%End
+ virtual QgsProcessingParameterDefinition *clone() const /Factory/;
+
+ virtual QString type() const;
+ virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const;
+
+ virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const;
+
+ virtual QString asScriptCode() const;
+
+ virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+
+ virtual QString createFileFilter() const;
+
+
+ virtual QVariantMap toVariantMap() const;
+
+ virtual bool fromVariantMap( const QVariantMap &map );
+
+
+ static QgsProcessingParameterMapLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/;
+%Docstring
+Creates a new parameter using the definition from a script code.
+%End
+
+};
+
class QgsProcessingParameterField : QgsProcessingParameterDefinition
{
%Docstring
@@ -2547,7 +2703,7 @@ Creates a new parameter using the definition from a script code.
};
-class QgsProcessingParameterFeatureSource : QgsProcessingParameterDefinition, QgsProcessingParameterLimitedDataTypes
+class QgsProcessingParameterFeatureSource : QgsProcessingParameterDefinition, QgsProcessingParameterLimitedDataTypes, QgsFileFilterGenerator
{
%Docstring
An input feature source (such as vector layers) parameter for processing algorithms.
@@ -2582,6 +2738,8 @@ Returns the type name for the parameter class.
virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+ virtual QString createFileFilter() const;
+
virtual QVariantMap toVariantMap() const;
@@ -2595,7 +2753,7 @@ Creates a new parameter using the definition from a script code.
};
-class QgsProcessingDestinationParameter : QgsProcessingParameterDefinition
+class QgsProcessingDestinationParameter : QgsProcessingParameterDefinition, QgsFileFilterGenerator
{
%Docstring
Base class for all parameter definitions which represent file or layer destinations, e.g. parameters
@@ -2625,6 +2783,8 @@ output will not be created by default.
virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+ virtual QString createFileFilter() const;
+
virtual QgsProcessingOutputDefinition *toOutputDefinition() const = 0 /Factory/;
%Docstring
@@ -2709,7 +2869,7 @@ A parameter which represents the destination feature sink for features created b
public:
QgsProcessingParameterFeatureSink( const QString &name, const QString &description = QString(), QgsProcessing::SourceType type = QgsProcessing::TypeVectorAnyGeometry, const QVariant &defaultValue = QVariant(),
- bool optional = false, bool createByDefault = true );
+ bool optional = false, bool createByDefault = true, bool supportsAppend = false );
%Docstring
Constructor for QgsProcessingParameterFeatureSink.
@@ -2736,6 +2896,8 @@ Returns the type name for the parameter class.
virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+ virtual QString createFileFilter() const;
+
virtual QStringList supportedOutputVectorLayerExtensions() const;
%Docstring
@@ -2764,6 +2926,30 @@ cannot be reliably determined in advance, this method will default to returning
Sets the layer ``type`` for the sinks associated with the parameter.
.. seealso:: :py:func:`dataType`
+%End
+
+ bool supportsAppend() const;
+%Docstring
+Returns ``True`` if the sink supports appending features to an existing table.
+
+A sink only supports appending if the algorithm implements QgsProcessingAlgorithm.sinkProperties for the sink parameter.
+
+.. seealso:: :py:func:`setSupportsAppend`
+
+.. versionadded:: 3.14
+%End
+
+ void setSupportsAppend( bool supportsAppend );
+%Docstring
+Sets whether the sink supports appending features to an existing table.
+
+.. warning::
+
+ A sink only supports appending if the algorithm implements QgsProcessingAlgorithm.sinkProperties for the sink parameter.
+
+.. seealso:: :py:func:`supportsAppend`
+
+.. versionadded:: 3.14
%End
virtual QVariantMap toVariantMap() const;
@@ -2828,6 +3014,8 @@ Returns the type name for the parameter class.
virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+ virtual QString createFileFilter() const;
+
virtual QStringList supportedOutputVectorLayerExtensions() const;
%Docstring
@@ -2911,6 +3099,8 @@ Returns the type name for the parameter class.
virtual QString defaultFileExtension() const;
+ virtual QString createFileFilter() const;
+
virtual QStringList supportedOutputRasterLayerExtensions() const;
%Docstring
@@ -2970,6 +3160,8 @@ Returns the type name for the parameter class.
virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+ virtual QString createFileFilter() const;
+
QString fileFilter() const;
%Docstring
@@ -3687,6 +3879,187 @@ Creates a new parameter using the definition from a script code.
};
+class QgsProcessingParameterDatabaseSchema : QgsProcessingParameterDefinition
+{
+%Docstring
+A database schema parameter for processing algorithms, allowing users to select from existing schemas
+on a registered database connection.
+
+QgsProcessingParameterDatabaseSchema should be evaluated by calling :py:func:`QgsProcessingAlgorithm.parameterAsSchema()`
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingparameters.h"
+%End
+ public:
+
+ QgsProcessingParameterDatabaseSchema( const QString &name, const QString &description, const QString &connectionParameterName = QString(), const QVariant &defaultValue = QVariant(),
+ bool optional = false );
+%Docstring
+Constructor for QgsProcessingParameterDatabaseSchema.
+
+The ``connectionParameterName`` specifies the name of the parent QgsProcessingParameterProviderConnection parameter.
+
+.. warning::
+
+ The provider must support the connection API methods in its QgsProviderMetadata implementation
+ in order for the model to work correctly. This is only implemented for a subset of current data providers.
+%End
+
+ static QString typeName();
+%Docstring
+Returns the type name for the parameter class.
+%End
+ virtual QgsProcessingParameterDefinition *clone() const /Factory/;
+
+ virtual QString type() const;
+ virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const;
+
+ virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const;
+
+ virtual QString asScriptCode() const;
+
+ virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+
+ virtual QVariantMap toVariantMap() const;
+
+ virtual bool fromVariantMap( const QVariantMap &map );
+
+ virtual QStringList dependsOnOtherParameters() const;
+
+
+ QString parentConnectionParameterName() const;
+%Docstring
+Returns the name of the parent connection parameter, or an empty string if this is not set.
+
+.. seealso:: :py:func:`setParentConnectionParameterName`
+%End
+
+ void setParentConnectionParameterName( const QString &name );
+%Docstring
+Sets the ``name`` of the parent connection parameter. Use an empty string if this is not required.
+
+.. seealso:: :py:func:`parentConnectionParameterName`
+%End
+
+ static QgsProcessingParameterDatabaseSchema *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/;
+%Docstring
+Creates a new parameter using the definition from a script code.
+%End
+
+};
+
+
+class QgsProcessingParameterDatabaseTable : QgsProcessingParameterDefinition
+{
+%Docstring
+A database table name parameter for processing algorithms, allowing users to select from existing database tables
+on a registered database connection (or optionally to enter a new table name).
+
+QgsProcessingParameterDatabaseTable should be evaluated by calling :py:func:`QgsProcessingAlgorithm.parameterAsDatabaseTableName()`
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingparameters.h"
+%End
+ public:
+
+ QgsProcessingParameterDatabaseTable( const QString &name, const QString &description,
+ const QString &connectionParameterName = QString(),
+ const QString &schemaParameterName = QString(),
+ const QVariant &defaultValue = QVariant(),
+ bool optional = false,
+ bool allowNewTableNames = false );
+%Docstring
+Constructor for QgsProcessingParameterDatabaseTable.
+
+The ``connectionParameterName`` specifies the name of the parent QgsProcessingParameterProviderConnection parameter.
+The ``schemaParameterName`` specifies the name of the parent QgsProcessingParameterDatabaseSchema parameter.
+
+.. warning::
+
+ The provider must support the connection API methods in its QgsProviderMetadata implementation
+ in order for the model to work correctly. This is only implemented for a subset of current data providers.
+%End
+
+ static QString typeName();
+%Docstring
+Returns the type name for the parameter class.
+%End
+ virtual QgsProcessingParameterDefinition *clone() const /Factory/;
+
+ virtual QString type() const;
+ virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const;
+
+ virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const;
+
+ virtual QString asScriptCode() const;
+
+ virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const;
+
+ virtual QVariantMap toVariantMap() const;
+
+ virtual bool fromVariantMap( const QVariantMap &map );
+
+ virtual QStringList dependsOnOtherParameters() const;
+
+
+ QString parentConnectionParameterName() const;
+%Docstring
+Returns the name of the parent connection parameter, or an empty string if this is not set.
+
+.. seealso:: :py:func:`setParentConnectionParameterName`
+%End
+
+ void setParentConnectionParameterName( const QString &name );
+%Docstring
+Sets the ``name`` of the parent connection parameter. Use an empty string if this is not required.
+
+.. seealso:: :py:func:`parentConnectionParameterName`
+%End
+
+ QString parentSchemaParameterName() const;
+%Docstring
+Returns the name of the parent schema parameter, or an empty string if this is not set.
+
+.. seealso:: :py:func:`setParentSchemaParameterName`
+%End
+
+ void setParentSchemaParameterName( const QString &name );
+%Docstring
+Sets the ``name`` of the parent schema parameter. Use an empty string if this is not required.
+
+.. seealso:: :py:func:`parentSchemaParameterName`
+%End
+
+ static QgsProcessingParameterDatabaseTable *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/;
+%Docstring
+Creates a new parameter using the definition from a script code.
+%End
+
+ bool allowNewTableNames() const;
+%Docstring
+Returns ``True`` if the parameter allows users to enter names for
+a new (non-existing) tables.
+
+.. seealso:: :py:func:`setAllowNewTableNames`
+%End
+
+ void setAllowNewTableNames( bool allowed );
+%Docstring
+Sets whether the parameter allows users to enter names for
+a new (non-existing) tables.
+
+.. seealso:: :py:func:`allowNewTableNames`
+%End
+
+};
+
+
diff --git a/python/core/auto_generated/processing/qgsprocessingprovider.sip.in b/python/core/auto_generated/processing/qgsprocessingprovider.sip.in
index 888830ed9074..1486af167fb6 100644
--- a/python/core/auto_generated/processing/qgsprocessingprovider.sip.in
+++ b/python/core/auto_generated/processing/qgsprocessingprovider.sip.in
@@ -24,6 +24,13 @@ to a common area of analysis.
%End
public:
+ enum Flag
+ {
+ FlagDeemphasiseSearchResults,
+ };
+ typedef QFlags Flags;
+
+
QgsProcessingProvider( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsProcessingProvider.
@@ -44,6 +51,14 @@ Returns an icon for the provider.
Returns a path to an SVG version of the provider's icon.
.. seealso:: :py:func:`icon`
+%End
+
+ virtual Flags flags() const;
+%Docstring
+Returns the flags indicating how and when the provider operates and should be exposed to users.
+Default is no flags.
+
+.. versionadded:: 3.14
%End
virtual QString id() const = 0;
@@ -284,6 +299,9 @@ Adds an ``algorithm`` to the provider. Ownership of the algorithm is transferred
QgsProcessingProvider( const QgsProcessingProvider &other );
};
+QFlags operator|(QgsProcessingProvider::Flag f1, QFlags f2);
+
+
/************************************************************************
diff --git a/python/core/auto_generated/processing/qgsprocessingutils.sip.in b/python/core/auto_generated/processing/qgsprocessingutils.sip.in
index 08377dcc75fe..944b88ec602e 100644
--- a/python/core/auto_generated/processing/qgsprocessingutils.sip.in
+++ b/python/core/auto_generated/processing/qgsprocessingutils.sip.in
@@ -90,6 +90,28 @@ value.
.. seealso:: :py:func:`compatibleRasterLayers`
.. seealso:: :py:func:`compatibleVectorLayers`
+%End
+
+ static QString encodeProviderKeyAndUri( const QString &providerKey, const QString &uri );
+%Docstring
+Encodes a provider key and layer ``uri`` to a single string, for use with
+decodeProviderKeyAndUri()
+
+.. versionadded:: 3.14
+%End
+
+ static bool decodeProviderKeyAndUri( const QString &string, QString &providerKey /Out/, QString &uri /Out/ );
+%Docstring
+Decodes a provider key and layer ``uri`` from an encoded string, for use with
+encodeProviderKeyAndUri()
+
+:param string: encoded string, as returned by encodeProviderKeyAndUri()
+:param uri: decoded layer uri
+
+:return: - ``True`` if ``string`` was successfully decoded
+ - providerKey: ID key for corresponding data provider
+
+.. versionadded:: 3.14
%End
enum class LayerHint
@@ -241,7 +263,7 @@ a specified ``algorithm``.
const QStringList &compatibleFormats,
const QString &preferredFormat,
QgsProcessingContext &context,
- QgsProcessingFeedback *feedback );
+ QgsProcessingFeedback *feedback, long long featureLimit = -1 );
%Docstring
Converts a source vector ``layer`` to a file path of a vector layer of compatible format.
@@ -254,6 +276,8 @@ in a temporary location using ``baseName``. The function will then return the pa
The ``preferredFormat`` argument is used to specify to desired file extension to use when a temporary
layer export is required. This defaults to shapefiles.
+The ``featureLimit`` argument can be used to specify a limit on the number of features read from the layer.
+
When an algorithm is capable of handling multi-layer input files (such as Geopackage), it is preferable
to use convertToCompatibleFormatAndLayerName() which may avoid conversion in more situations.
@@ -267,7 +291,7 @@ to use convertToCompatibleFormatAndLayerName() which may avoid conversion in mor
const QString &preferredFormat,
QgsProcessingContext &context,
QgsProcessingFeedback *feedback,
- QString &layerName /Out/ );
+ QString &layerName /Out/, long long featureLimit = -1 );
%Docstring
Converts a source vector ``layer`` to a file path and layer name of a vector layer of compatible format.
@@ -277,6 +301,8 @@ in a temporary location using ``baseName``. The function will then return the pa
``compatibleFormats`` should consist entirely of lowercase file extensions, e.g. 'shp'.
+The ``featureLimit`` argument can be used to specify a limit on the number of features read from the layer.
+
The ``preferredFormat`` argument is used to specify to desired file extension to use when a temporary
layer export is required. This defaults to shapefiles.
@@ -291,6 +317,7 @@ a conversion in this case and will return the target layer name in the ``layerNa
:param preferredFormat: preferred format extension to use if conversion if required
:param context: processing context
:param feedback: feedback object
+:param featureLimit: can be used to place a limit on the maximum number of features read from the layer
:return: - path to source layer, or nearly converted compatible layer
- layerName: will be set to the target layer name for multi-layer sources (e.g. Geopackage)
@@ -379,13 +406,17 @@ results according to the settings in a :py:class:`QgsProcessingContext`.
typedef QFlags Flags;
- QgsProcessingFeatureSource( QgsFeatureSource *originalSource, const QgsProcessingContext &context, bool ownsOriginalSource = false );
+ QgsProcessingFeatureSource( QgsFeatureSource *originalSource, const QgsProcessingContext &context, bool ownsOriginalSource = false,
+ long long featureLimit = -1 );
%Docstring
Constructor for QgsProcessingFeatureSource, accepting an original feature source ``originalSource``
and processing ``context``.
Ownership of ``originalSource`` is dictated by ``ownsOriginalSource``. If ``ownsOriginalSource`` is ``False``,
ownership is not transferred, and callers must ensure that ``originalSource`` exists for the lifetime of this object.
If ``ownsOriginalSource`` is ``True``, then this object will take ownership of ``originalSource``.
+
+If ``featureLimit`` is set to a value > 0, then a limit is placed on the maximum number of features which will be
+read from the source.
%End
~QgsProcessingFeatureSource();
@@ -428,6 +459,13 @@ iterator, eg by restricting the returned attributes or geometry.
QgsExpressionContextScope *createExpressionContextScope() const /Factory/;
%Docstring
Returns an expression context scope suitable for this source.
+%End
+
+ void setInvalidGeometryCheck( QgsFeatureRequest::InvalidGeometryCheck method );
+%Docstring
+Overrides the default geometry check method for the source.
+
+.. versionadded:: 3.14
%End
};
diff --git a/python/core/auto_generated/qgsabstractdatabaseproviderconnection.sip.in b/python/core/auto_generated/qgsabstractdatabaseproviderconnection.sip.in
index 438e7b05323f..1cfa07de3115 100644
--- a/python/core/auto_generated/qgsabstractdatabaseproviderconnection.sip.in
+++ b/python/core/auto_generated/qgsabstractdatabaseproviderconnection.sip.in
@@ -231,6 +231,9 @@ This information is calculated from the geometry columns types.
SqlLayers,
TableExists,
Spatial,
+ CreateSpatialIndex,
+ SpatialIndexExists,
+ DeleteSpatialIndex,
};
typedef QFlags Capabilities;
@@ -270,7 +273,7 @@ Raises a QgsProviderConnectionException if any errors are encountered.
virtual void createVectorTable( const QString &schema, const QString &name, const QgsFields &fields, QgsWkbTypes::Type wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, const QMap *options ) const throw( QgsProviderConnectionException );
%Docstring
-Creates an empty table with ``name`` in the given ``schema`` (schema is ignored if not supported by the backend).
+Creates an empty table with ``name`` in the given ``schema`` (schema is ignored if not supported by the backend).
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
@@ -298,7 +301,7 @@ Raises a QgsProviderConnectionException if any errors are encountered.
virtual void dropRasterTable( const QString &schema, const QString &name ) const throw( QgsProviderConnectionException );
%Docstring
-Drops a raster table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
+Drops a raster table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
@@ -310,7 +313,7 @@ Raises a QgsProviderConnectionException if any errors are encountered.
virtual void renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const throw( QgsProviderConnectionException );
%Docstring
-Renames a vector or aspatial table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
+Renames a vector or aspatial table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
@@ -322,7 +325,7 @@ Raises a QgsProviderConnectionException if any errors are encountered.
virtual void renameRasterTable( const QString &schema, const QString &name, const QString &newName ) const throw( QgsProviderConnectionException );
%Docstring
-Renames a raster table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
+Renames a raster table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
@@ -376,12 +379,54 @@ Raises a QgsProviderConnectionException if any errors are encountered.
virtual void vacuum( const QString &schema, const QString &name ) const throw( QgsProviderConnectionException );
%Docstring
-Vacuum the database table with given ``schema`` and ``name`` (schema is ignored if not supported by the backend).
+Vacuum the database table with given ``schema`` and ``name`` (schema is ignored if not supported by the backend).
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End
+ struct SpatialIndexOptions
+ {
+ QString geometryColumnName;
+ };
+
+ virtual void createSpatialIndex( const QString &schema, const QString &name, const QgsAbstractDatabaseProviderConnection::SpatialIndexOptions &options = QgsAbstractDatabaseProviderConnection::SpatialIndexOptions() ) const throw( QgsProviderConnectionException );
+%Docstring
+Creates a spatial index for the database table with given ``schema`` and ``name`` (schema is ignored if not supported by the backend).
+
+The ``options`` argument can be used to provide extra options controlling the spatial index creation.
+
+Raises a QgsProviderConnectionException if any errors are encountered.
+
+:raises :: py:class:`QgsProviderConnectionException`
+
+.. versionadded:: 3.14
+%End
+
+ virtual bool spatialIndexExists( const QString &schema, const QString &name, const QString &geometryColumn ) const throw( QgsProviderConnectionException );
+%Docstring
+Determines whether a spatial index exists for the database table with given ``schema``, ``name`` and ``geometryColumn`` (``schema`` and ``geometryColumn`` are
+ignored if not supported by the backend).
+
+Raises a QgsProviderConnectionException if any errors are encountered.
+
+:raises :: py:class:`QgsProviderConnectionException`
+
+.. versionadded:: 3.14
+%End
+
+ virtual void deleteSpatialIndex( const QString &schema, const QString &name, const QString &geometryColumn ) const throw( QgsProviderConnectionException );
+%Docstring
+Deletes the existing spatial index for the database table with given ``schema``, ``name`` and ``geometryColumn`` (``schema`` and ``geometryColumn`` are
+ignored if not supported by the backend).
+
+Raises a QgsProviderConnectionException if any errors are encountered.
+
+:raises :: py:class:`QgsProviderConnectionException`
+
+.. versionadded:: 3.14
+%End
+
virtual QgsAbstractDatabaseProviderConnection::TableProperty table( const QString &schema, const QString &table ) const;
%Docstring
diff --git a/python/core/auto_generated/qgsactionscope.sip.in b/python/core/auto_generated/qgsactionscope.sip.in
index 1c4a6439bdcd..32984bb89466 100644
--- a/python/core/auto_generated/qgsactionscope.sip.in
+++ b/python/core/auto_generated/qgsactionscope.sip.in
@@ -36,6 +36,9 @@ form.
#include "qgsactionscope.h"
%End
public:
+%TypeCode
+#include
+%End
explicit QgsActionScope();
%Docstring
@@ -109,6 +112,10 @@ in here, they are extracted automatically from the expressionContextScope().
Returns if this scope is valid.
.. versionadded:: 3.0
+%End
+ long __hash__();
+%MethodCode
+ sipRes = qHash( *sipCpp );
%End
};
diff --git a/python/core/auto_generated/qgsapplication.sip.in b/python/core/auto_generated/qgsapplication.sip.in
index b45b54d93549..c83163800373 100644
--- a/python/core/auto_generated/qgsapplication.sip.in
+++ b/python/core/auto_generated/qgsapplication.sip.in
@@ -810,6 +810,13 @@ Returns the application's page size registry, used for managing layout page size
Returns the action scope registry.
.. versionadded:: 3.0
+%End
+
+ static QgsConnectionRegistry *connectionRegistry();
+%Docstring
+Returns the application's connection registry, used for managing saved data provider connections.
+
+.. versionadded:: 3.14
%End
static QgsRuntimeProfiler *profiler();
@@ -836,6 +843,13 @@ Gets the registry of available field formatters.
Returns registry of available 3D renderers.
.. versionadded:: 3.0
+%End
+
+ static QgsScaleBarRendererRegistry *scaleBarRendererRegistry() /KeepReference/;
+%Docstring
+Gets the registry of available scalebar renderers.
+
+.. versionadded:: 3.14
%End
static QgsProjectStorageRegistry *projectStorageRegistry() /KeepReference/;
diff --git a/python/core/auto_generated/qgsconnectionregistry.sip.in b/python/core/auto_generated/qgsconnectionregistry.sip.in
new file mode 100644
index 000000000000..099550c6e74f
--- /dev/null
+++ b/python/core/auto_generated/qgsconnectionregistry.sip.in
@@ -0,0 +1,61 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgsconnectionregistry.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+class QgsConnectionRegistry : QObject
+{
+%Docstring
+A registry for saved data provider connections, allowing retrieval of
+saved connections by name and provider type.
+
+QgsConnectionRegistry is not usually directly created, but rather accessed through
+:py:func:`QgsApplication.connectionRegistry()`
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsconnectionregistry.h"
+%End
+ public:
+
+ QgsConnectionRegistry( QObject *parent /TransferThis/ = 0 );
+%Docstring
+Constructor for QgsConnectionRegistry.
+%End
+
+
+ QgsAbstractProviderConnection *createConnection( const QString &name ) throw( QgsProviderConnectionException ) /Factory/;
+%Docstring
+Creates a new connection by loading the connection with the given ``id`` from the settings.
+
+The ``id`` string must be of the format "provider://connection_name", e.g. "postgres://my_connection" for
+the PostgreSQL connection saved as "my_connection".
+
+Ownership is transferred to the caller.
+
+:raises :: py:class:`QgsProviderConnectionException`
+%End
+
+ private:
+ QgsConnectionRegistry( const QgsConnectionRegistry &other );
+};
+
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgsconnectionregistry.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/qgsdatabaseschemamodel.sip.in b/python/core/auto_generated/qgsdatabaseschemamodel.sip.in
index 63395862a8ad..46199d17c399 100644
--- a/python/core/auto_generated/qgsdatabaseschemamodel.sip.in
+++ b/python/core/auto_generated/qgsdatabaseschemamodel.sip.in
@@ -28,6 +28,11 @@ called.
%End
public:
+ enum Role
+ {
+ RoleEmpty,
+ };
+
explicit QgsDatabaseSchemaModel( const QString &provider, const QString &connection, QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsDatabaseSchemaModel, for the specified ``provider`` and ``connection`` name.
@@ -56,6 +61,20 @@ Ownership of ``connection`` is transferred to the model.
virtual QModelIndex index( int row, int column, const QModelIndex &parent ) const;
+ void setAllowEmptySchema( bool allowEmpty );
+%Docstring
+Sets whether an optional empty schema ("not set") option is present in the model.
+
+.. seealso:: :py:func:`allowEmptySchema`
+%End
+
+ bool allowEmptySchema() const;
+%Docstring
+Returns ``True`` if the model allows the empty schema ("not set") choice.
+
+.. seealso:: :py:func:`setAllowEmptySchema`
+%End
+
public slots:
void refresh();
diff --git a/python/core/auto_generated/qgsdatabasetablemodel.sip.in b/python/core/auto_generated/qgsdatabasetablemodel.sip.in
index c62e1f060455..3c9ae3974b3b 100644
--- a/python/core/auto_generated/qgsdatabasetablemodel.sip.in
+++ b/python/core/auto_generated/qgsdatabasetablemodel.sip.in
@@ -36,6 +36,7 @@ called.
RoleCustomInfo,
RoleWkbType,
RoleCrs,
+ RoleEmpty,
};
explicit QgsDatabaseTableModel( const QString &provider, const QString &connection, const QString &schema = QString(), QObject *parent /TransferThis/ = 0 );
@@ -70,6 +71,20 @@ Ownership of ``connection`` is transferred to the model.
virtual QModelIndex index( int row, int column, const QModelIndex &parent ) const;
+ void setAllowEmptyTable( bool allowEmpty );
+%Docstring
+Sets whether an optional empty table ("not set") option is present in the model.
+
+.. seealso:: :py:func:`allowEmptyTable`
+%End
+
+ bool allowEmptyTable() const;
+%Docstring
+Returns ``True`` if the model allows the empty table ("not set") choice.
+
+.. seealso:: :py:func:`setAllowEmptyTable`
+%End
+
public slots:
void refresh();
diff --git a/python/core/auto_generated/qgsdataitem.sip.in b/python/core/auto_generated/qgsdataitem.sip.in
index 4edac4ac530a..c44ca229b69e 100644
--- a/python/core/auto_generated/qgsdataitem.sip.in
+++ b/python/core/auto_generated/qgsdataitem.sip.in
@@ -479,7 +479,8 @@ Item that represents a layer that can be opened with one of the providers
Database,
Table,
Plugin,
- Mesh
+ Mesh,
+ VectorTile
};
@@ -573,6 +574,10 @@ Use QgsDataItemGuiProvider.deleteLayer instead
static QIcon iconMesh();
%Docstring
Returns icon for mesh layer type
+%End
+ static QIcon iconVectorTile();
+%Docstring
+Returns icon for vector tile layer
%End
virtual QString layerName() const;
diff --git a/python/core/auto_generated/qgsdataprovider.sip.in b/python/core/auto_generated/qgsdataprovider.sip.in
index fe1cbd10788f..c72c2a33ce3a 100644
--- a/python/core/auto_generated/qgsdataprovider.sip.in
+++ b/python/core/auto_generated/qgsdataprovider.sip.in
@@ -102,6 +102,18 @@ connection string
only when needed within a provider
%End
+ virtual QString dataComment() const;
+%Docstring
+Returns a short comment for the data that this provider is
+providing access to (e.g. the comment for postgres table).
+
+.. note::
+
+ The default implementation returns an empty string.
+
+.. versionadded:: 3.14
+%End
+
void setUri( const QgsDataSourceUri &uri );
%Docstring
Set the data source specification.
@@ -125,6 +137,7 @@ This may be ``None``, depending on the data provider.
.. versionadded:: 3.14
%End
+
virtual QgsRectangle extent() const = 0;
%Docstring
Returns the extent of the layer
diff --git a/python/core/auto_generated/qgsdataprovidertemporalcapabilities.sip.in b/python/core/auto_generated/qgsdataprovidertemporalcapabilities.sip.in
index 22f18321130b..1b1cd1c3e7d2 100644
--- a/python/core/auto_generated/qgsdataprovidertemporalcapabilities.sip.in
+++ b/python/core/auto_generated/qgsdataprovidertemporalcapabilities.sip.in
@@ -30,6 +30,10 @@ Base class for handling properties relating to a data provider's temporal capabi
{
sipType = sipType_QgsVectorDataProviderTemporalCapabilities;
}
+ else if ( dynamic_cast < QgsMeshDataProviderTemporalCapabilities * >( sipCpp ) )
+ {
+ sipType = sipType_QgsMeshDataProviderTemporalCapabilities;
+ }
else
{
sipType = 0;
diff --git a/python/core/auto_generated/qgsfeaturesink.sip.in b/python/core/auto_generated/qgsfeaturesink.sip.in
index 596a37c8ba2e..b958e7ece934 100644
--- a/python/core/auto_generated/qgsfeaturesink.sip.in
+++ b/python/core/auto_generated/qgsfeaturesink.sip.in
@@ -77,41 +77,6 @@ QFlags operator|(QgsFeatureSink::Flag f1, QFlags" ).arg( sipCpp->name(), sipCpp->dataProvider()->name() );
+ QString str = QStringLiteral( "" ).arg( sipCpp->name(), sipCpp->dataProvider() ? sipCpp->dataProvider()->name() : QStringLiteral( "Invalid" ) );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
diff --git a/python/core/auto_generated/qgsmaplayerlegend.sip.in b/python/core/auto_generated/qgsmaplayerlegend.sip.in
index b5f7e3aa20c6..5bd98d90b215 100644
--- a/python/core/auto_generated/qgsmaplayerlegend.sip.in
+++ b/python/core/auto_generated/qgsmaplayerlegend.sip.in
@@ -96,6 +96,24 @@ Miscellaneous utility functions for handling of map layer legend
static QString legendNodeUserLabel( QgsLayerTreeLayer *nodeLayer, int originalIndex );
static bool hasLegendNodeUserLabel( QgsLayerTreeLayer *nodeLayer, int originalIndex );
+ static void setLegendNodePatchShape( QgsLayerTreeLayer *nodeLayer, int originalIndex, const QgsLegendPatchShape &shape );
+%Docstring
+Sets the legend patch ``shape`` for the legend node belonging to ``nodeLayer`` at the specified ``originalIndex``.
+
+.. seealso:: :py:func:`legendNodePatchShape`
+
+.. versionadded:: 3.14
+%End
+
+ static QgsLegendPatchShape legendNodePatchShape( QgsLayerTreeLayer *nodeLayer, int originalIndex );
+%Docstring
+Returns the legend patch shape for the legend node belonging to ``nodeLayer`` at the specified ``originalIndex``.
+
+.. seealso:: :py:func:`setLegendNodePatchShape`
+
+.. versionadded:: 3.14
+%End
+
static void applyLayerNodeProperties( QgsLayerTreeLayer *nodeLayer, QList &nodes );
%Docstring
update according to layer node's custom properties (order of items, user labels for items)
diff --git a/python/core/auto_generated/qgsmaplayerproxymodel.sip.in b/python/core/auto_generated/qgsmaplayerproxymodel.sip.in
index 6f07901d899a..cd78381587d9 100644
--- a/python/core/auto_generated/qgsmaplayerproxymodel.sip.in
+++ b/python/core/auto_generated/qgsmaplayerproxymodel.sip.in
@@ -34,6 +34,7 @@ The QgsMapLayerProxyModel class provides an easy to use model to display the lis
PluginLayer,
WritableLayer,
MeshLayer,
+ VectorTileLayer,
All
};
typedef QFlags Filters;
diff --git a/python/core/auto_generated/qgsmaplayertemporalproperties.sip.in b/python/core/auto_generated/qgsmaplayertemporalproperties.sip.in
index 2c76a37159f5..088296a52f81 100644
--- a/python/core/auto_generated/qgsmaplayertemporalproperties.sip.in
+++ b/python/core/auto_generated/qgsmaplayertemporalproperties.sip.in
@@ -31,6 +31,10 @@ how an individual QgsMapLayer behaves in a temporal context, e.g. while animatin
{
sipType = sipType_QgsRasterLayerTemporalProperties;
}
+ else if ( qobject_cast( sipCpp ) )
+ {
+ sipType = sipType_QgsMeshLayerTemporalProperties;
+ }
else
{
sipType = 0;
@@ -57,26 +61,6 @@ Writes the properties to a DOM ``element``, to be used later with readXml().
Reads temporal properties from a DOM ``element`` previously written by writeXml().
.. seealso:: :py:func:`writeXml`
-%End
-
- enum TemporalSource
- {
- Layer,
- Project
- };
-
- TemporalSource temporalSource() const;
-%Docstring
-Returns the temporal properties temporal range source, can be layer or project.
-
-.. seealso:: :py:func:`setTemporalSource`
-%End
-
- void setTemporalSource( TemporalSource source );
-%Docstring
-Sets the temporal properties temporal range ``source``.
-
-.. seealso:: :py:func:`temporalSource`
%End
virtual bool isVisibleInTemporalRange( const QgsDateTimeRange &range ) const;
diff --git a/python/core/auto_generated/qgsnetworkcontentfetchertask.sip.in b/python/core/auto_generated/qgsnetworkcontentfetchertask.sip.in
index c81dce840140..86910db4869a 100644
--- a/python/core/auto_generated/qgsnetworkcontentfetchertask.sip.in
+++ b/python/core/auto_generated/qgsnetworkcontentfetchertask.sip.in
@@ -34,7 +34,7 @@ without danger of the task being first removed by the QgsTaskManager.
%End
public:
- QgsNetworkContentFetcherTask( const QUrl &url, const QString &authcfg = QString() );
+ QgsNetworkContentFetcherTask( const QUrl &url, const QString &authcfg = QString(), QgsTask::Flags flags = QgsTask::CanCancel );
%Docstring
Constructor for a QgsNetworkContentFetcherTask which fetches
the specified ``url``.
@@ -42,7 +42,7 @@ the specified ``url``.
Optionally, authentication configuration can be set via the ``authcfg`` argument.
%End
- QgsNetworkContentFetcherTask( const QNetworkRequest &request, const QString &authcfg = QString() );
+ QgsNetworkContentFetcherTask( const QNetworkRequest &request, const QString &authcfg = QString(), QgsTask::Flags flags = QgsTask::CanCancel );
%Docstring
Constructor for a QgsNetworkContentFetcherTask which fetches
the specified network ``request``.
diff --git a/python/core/auto_generated/qgsproject.sip.in b/python/core/auto_generated/qgsproject.sip.in
index b8c790abbfbf..38b01d97ea06 100644
--- a/python/core/auto_generated/qgsproject.sip.in
+++ b/python/core/auto_generated/qgsproject.sip.in
@@ -48,6 +48,13 @@ open within the main QGIS application.
Qgs,
};
+ enum class AvoidIntersectionsMode
+ {
+ AllowIntersections,
+ AvoidIntersectionsCurrentLayer,
+ AvoidIntersectionsLayers,
+ };
+
static QgsProject *instance();
%Docstring
Returns the QgsProject singleton instance
@@ -740,9 +747,24 @@ A list of layers with which intersections should be avoided.
void setAvoidIntersectionsLayers( const QList &layers );
%Docstring
-A list of layers with which intersections should be avoided.
+Sets the list of layers with which intersections should be avoided.
+Only used if the avoid intersection mode is set to advanced.
.. versionadded:: 3.0
+%End
+
+ void setAvoidIntersectionsMode( const AvoidIntersectionsMode mode );
+%Docstring
+Sets the avoid intersections mode.
+
+.. versionadded:: 3.14
+%End
+
+ AvoidIntersectionsMode avoidIntersectionsMode() const;
+%Docstring
+Returns the current avoid intersections mode.
+
+.. versionadded:: 3.14
%End
QVariantMap customVariables() const;
@@ -1369,6 +1391,13 @@ Emitted when the home path of the project changes.
void snappingConfigChanged( const QgsSnappingConfig &config );
%Docstring
Emitted whenever the configuration for snapping has changed.
+%End
+
+ void avoidIntersectionsModeChanged();
+%Docstring
+Emitted whenever the avoid intersections mode has changed.
+
+.. versionadded:: 3.14
%End
void customVariablesChanged();
diff --git a/python/core/auto_generated/qgsproperty.sip.in b/python/core/auto_generated/qgsproperty.sip.in
index 7d73e02e2cab..07a716599b30 100644
--- a/python/core/auto_generated/qgsproperty.sip.in
+++ b/python/core/auto_generated/qgsproperty.sip.in
@@ -491,6 +491,39 @@ be called in non-performance critical code.
operator QVariant() const;
+
+ SIP_PYOBJECT __repr__();
+%MethodCode
+ QString typeString;
+ QString definitionString;
+ switch ( sipCpp->propertyType() )
+ {
+ case QgsProperty::StaticProperty:
+ typeString = QStringLiteral( "static" );
+ definitionString = sipCpp->staticValue().toString();
+ break;
+
+ case QgsProperty::FieldBasedProperty:
+ typeString = QStringLiteral( "field" );
+ definitionString = sipCpp->field();
+ break;
+
+ case QgsProperty::ExpressionBasedProperty:
+ typeString = QStringLiteral( "expression" );
+ definitionString = sipCpp->expressionString();
+ break;
+
+ case QgsProperty::InvalidProperty:
+ typeString = QStringLiteral( "invalid" );
+ break;
+ }
+
+ QString str = QStringLiteral( "" ).arg( !sipCpp->isActive() && sipCpp->propertyType() != QgsProperty::InvalidProperty ? QStringLiteral( "INACTIVE " ) : QString(),
+ typeString,
+ definitionString.isEmpty() ? QString() : QStringLiteral( " (%1)" ).arg( definitionString ) );
+ sipRes = PyUnicode_FromString( str.toUtf8().constData() );
+%End
+
};
diff --git a/python/core/auto_generated/qgsproviderregistry.sip.in b/python/core/auto_generated/qgsproviderregistry.sip.in
index 34ded27e6e3b..5c06732dd813 100644
--- a/python/core/auto_generated/qgsproviderregistry.sip.in
+++ b/python/core/auto_generated/qgsproviderregistry.sip.in
@@ -170,7 +170,7 @@ Either the ``parent`` widget must be set or the caller becomes
responsible for deleting the returned widget.
.. deprecated::
- QGIS 3.10 - use QgsGui.providerGuiRegistry()->createDataSourceWidget() instead
+ QGIS 3.10 - use QgsGui.sourceSelectProviderRegistry()->createDataSourceWidget() instead
%End
QList< QgsDataItemProvider * > dataItemProviders( const QString &providerKey ) const /Factory/;
diff --git a/python/core/auto_generated/qgsproxyfeaturesink.sip.in b/python/core/auto_generated/qgsproxyfeaturesink.sip.in
new file mode 100644
index 000000000000..af5b16d34cb3
--- /dev/null
+++ b/python/core/auto_generated/qgsproxyfeaturesink.sip.in
@@ -0,0 +1,58 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgsproxyfeaturesink.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+class QgsProxyFeatureSink : QgsFeatureSink
+{
+%Docstring
+A simple feature sink which proxies feature addition on to another feature sink.
+
+This class is designed to allow factory methods which always return new QgsFeatureSink
+objects. Since it is not always possible to create an entirely new QgsFeatureSink
+(e.g. if the feature sink is a layer's data provider), a new QgsProxyFeatureSink
+can instead be returned which forwards features on to the destination sink. The
+proxy sink can be safely deleted without affecting the destination sink.
+
+.. versionadded:: 3.0
+%End
+
+%TypeHeaderCode
+#include "qgsproxyfeaturesink.h"
+%End
+ public:
+
+ QgsProxyFeatureSink( QgsFeatureSink *sink );
+%Docstring
+Constructs a new QgsProxyFeatureSink which forwards features onto a destination ``sink``.
+%End
+ virtual bool addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags = 0 );
+ virtual bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = 0 );
+ virtual bool addFeatures( QgsFeatureIterator &iterator, QgsFeatureSink::Flags flags = 0 );
+
+ QgsFeatureSink *destinationSink();
+%Docstring
+Returns the destination QgsFeatureSink which the proxy will forward features to.
+%End
+
+};
+
+
+
+
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgsproxyfeaturesink.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/qgsremappingproxyfeaturesink.sip.in b/python/core/auto_generated/qgsremappingproxyfeaturesink.sip.in
new file mode 100644
index 000000000000..706e34464ef0
--- /dev/null
+++ b/python/core/auto_generated/qgsremappingproxyfeaturesink.sip.in
@@ -0,0 +1,213 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgsremappingproxyfeaturesink.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsRemappingSinkDefinition
+{
+%Docstring
+Defines the parameters used to remap features when creating a QgsRemappingProxyFeatureSink.
+
+The definition includes parameters required to correctly map incoming features to the structure
+of the destination sink, e.g. information about how to create output field values and how to transform
+geometries to match the destination CRS.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsremappingproxyfeaturesink.h"
+%End
+ public:
+
+ QMap< QString, QgsProperty > fieldMap() const;
+%Docstring
+Returns the field mapping, which defines how to map the values from incoming features to destination
+field values.
+
+Field values are mapped using a QgsProperty source object, which allows either direct field value to field value
+mapping or use of QgsExpression expressions to transform values to the destination field.
+
+.. seealso:: :py:func:`setFieldMap`
+
+.. seealso:: :py:func:`addMappedField`
+%End
+
+ void setFieldMap( const QMap< QString, QgsProperty > &map );
+%Docstring
+Sets the field mapping, which defines how to map the values from incoming features to destination
+field values.
+
+Field values are mapped using a QgsProperty source object, which allows either direct field value to field value
+mapping or use of QgsExpression expressions to transform values to the destination field.
+
+.. seealso:: :py:func:`fieldMap`
+
+.. seealso:: :py:func:`addMappedField`
+%End
+
+ void addMappedField( const QString &destinationField, const QgsProperty &property );
+%Docstring
+Adds a mapping for a destination field.
+
+Field values are mapped using a QgsProperty source object, which allows either direct field value to field value
+mapping or use of QgsExpression expressions to transform values to the destination field.
+
+.. seealso:: :py:func:`setFieldMap`
+
+.. seealso:: :py:func:`fieldMap`
+%End
+
+ QgsCoordinateReferenceSystem sourceCrs() const;
+%Docstring
+Returns the source CRS used for reprojecting incoming features to the sink's destination CRS.
+
+.. seealso:: :py:func:`setSourceCrs`
+%End
+
+ void setSourceCrs( const QgsCoordinateReferenceSystem &source );
+%Docstring
+Sets the ``source`` crs used for reprojecting incoming features to the sink's destination CRS.
+
+.. seealso:: :py:func:`sourceCrs`
+%End
+
+ QgsCoordinateReferenceSystem destinationCrs() const;
+%Docstring
+Returns the destination CRS used for reprojecting incoming features to the sink's destination CRS.
+
+.. seealso:: :py:func:`setDestinationCrs`
+%End
+
+ void setDestinationCrs( const QgsCoordinateReferenceSystem &destination );
+%Docstring
+Sets the ``destination`` crs used for reprojecting incoming features to the sink's destination CRS.
+
+.. seealso:: :py:func:`destinationCrs`
+%End
+
+ QgsWkbTypes::Type destinationWkbType() const;
+%Docstring
+Returns the WKB geometry type for the destination.
+
+.. seealso:: :py:func:`setDestinationWkbType`
+%End
+
+ void setDestinationWkbType( QgsWkbTypes::Type type );
+%Docstring
+Sets the WKB geometry ``type`` for the destination.
+
+.. seealso:: :py:func:`setDestinationWkbType`
+%End
+
+ QgsFields destinationFields() const;
+%Docstring
+Returns the fields for the destination sink.
+
+.. seealso:: :py:func:`setDestinationFields`
+%End
+
+ void setDestinationFields( const QgsFields &fields );
+%Docstring
+Sets the ``fields`` for the destination sink.
+
+.. seealso:: :py:func:`destinationFields`
+%End
+
+ QVariant toVariant() const;
+%Docstring
+Saves this remapping definition to a QVariantMap, wrapped in a QVariant.
+You can use QgsXmlUtils.writeVariant to save it to an XML document.
+
+.. seealso:: :py:func:`loadVariant`
+%End
+
+ bool loadVariant( const QVariantMap &map );
+%Docstring
+Loads this remapping definition from a QVariantMap, wrapped in a QVariant.
+You can use QgsXmlUtils.readVariant to load it from an XML document.
+
+.. seealso:: :py:func:`toVariant`
+%End
+
+ bool operator==( const QgsRemappingSinkDefinition &other ) const;
+ bool operator!=( const QgsRemappingSinkDefinition &other ) const;
+
+};
+
+
+
+
+class QgsRemappingProxyFeatureSink : QgsFeatureSink
+{
+%Docstring
+A QgsFeatureSink which proxies incoming features to a destination feature sink, after applying
+transformations and field value mappings.
+
+This sink allows for transformation of incoming features to match the requirements of storing
+in an existing destination layer, e.g. by reprojecting the features to the destination's CRS
+and by coercing geometries to the format required by the destination sink.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsremappingproxyfeaturesink.h"
+%End
+ public:
+
+
+ QgsRemappingProxyFeatureSink( const QgsRemappingSinkDefinition &mappingDefinition, QgsFeatureSink *sink );
+%Docstring
+Constructor for QgsRemappingProxyFeatureSink, using the specified ``mappingDefinition``
+to manipulate features before sending them to the destination ``sink``.
+%End
+
+ ~QgsRemappingProxyFeatureSink();
+
+ void setExpressionContext( const QgsExpressionContext &context );
+%Docstring
+Sets the expression ``context`` to use when evaluating mapped field values.
+%End
+
+ void setTransformContext( const QgsCoordinateTransformContext &context );
+%Docstring
+Sets the transform ``context`` to use when reprojecting features.
+%End
+
+ QgsFeatureList remapFeature( const QgsFeature &feature ) const;
+%Docstring
+Remaps a ``feature`` to a set of features compatible with the destination sink.
+%End
+
+ virtual bool addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags = 0 );
+
+ virtual bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = 0 );
+
+ virtual bool addFeatures( QgsFeatureIterator &iterator, QgsFeatureSink::Flags flags = 0 );
+
+
+ QgsFeatureSink *destinationSink();
+%Docstring
+Returns the destination QgsFeatureSink which the proxy will forward features to.
+%End
+
+};
+
+
+
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgsremappingproxyfeaturesink.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/qgssnappingconfig.sip.in b/python/core/auto_generated/qgssnappingconfig.sip.in
index 4563c10d41b5..c2839faeffd9 100644
--- a/python/core/auto_generated/qgssnappingconfig.sip.in
+++ b/python/core/auto_generated/qgssnappingconfig.sip.in
@@ -51,6 +51,13 @@ This is a container for configuration of the snapping of the project
typedef QFlags SnappingTypeFlag;
+ enum ScaleDependencyMode
+ {
+ Disabled,
+ Global,
+ PerLayer
+ };
+
static const QString snappingTypeFlagToString( SnappingTypeFlag type );
%Docstring
Convenient method to returns the translated name of the enum type
@@ -86,7 +93,7 @@ IndividualLayerSettings
use the method with SnappingTypeFlag instead.
%End
- IndividualLayerSettings( bool enabled, SnappingTypeFlag type, double tolerance, QgsTolerance::UnitType units );
+ IndividualLayerSettings( bool enabled, SnappingTypeFlag type, double tolerance, QgsTolerance::UnitType units, double minScale = 0.0, double maxScale = 0.0 );
%Docstring
IndividualLayerSettings
@@ -94,6 +101,8 @@ IndividualLayerSettings
:param type:
:param tolerance:
:param units:
+:param minScale: 0.0 disable scale limit
+:param maxScale: 0.0 disable scale limit
.. versionadded:: 3.12
%End
@@ -166,6 +175,34 @@ Returns the type of units
void setUnits( QgsTolerance::UnitType units );
%Docstring
Sets the type of units
+%End
+
+ double minimumScale() const;
+%Docstring
+Returns minimum scale on which snapping is limited
+
+.. versionadded:: 3.14
+%End
+
+ void setMinimumScale( double minScale );
+%Docstring
+Sets the min scale value on which snapping is used, 0.0 disable scale limit
+
+.. versionadded:: 3.14
+%End
+
+ double maximumScale() const;
+%Docstring
+Returns max scale on which snapping is limited
+
+.. versionadded:: 3.14
+%End
+
+ void setMaximumScale( double maxScale );
+%Docstring
+Sets the max scale value on which snapping is used, 0.0 disable scale limit
+
+.. versionadded:: 3.14
%End
bool operator!= ( const QgsSnappingConfig::IndividualLayerSettings &other ) const;
@@ -244,6 +281,48 @@ Returns the tolerance
void setTolerance( double tolerance );
%Docstring
Sets the tolerance
+%End
+
+ double minimumScale() const;
+%Docstring
+Returns the min scale
+
+.. versionadded:: 3.14
+%End
+
+ void setMinimumScale( double minScale );
+%Docstring
+Sets the min scale on which snapping is enabled, 0.0 disable scale limit
+
+.. versionadded:: 3.14
+%End
+
+ double maximumScale() const;
+%Docstring
+Returns the max scale
+
+.. versionadded:: 3.14
+%End
+
+ void setMaximumScale( double maxScale );
+%Docstring
+Set the max scale on which snapping is enabled, 0.0 disable scale limit
+
+.. versionadded:: 3.14
+%End
+
+ void setScaleDependencyMode( ScaleDependencyMode mode );
+%Docstring
+Set the scale dependency mode
+
+.. versionadded:: 3.14
+%End
+
+ ScaleDependencyMode scaleDependencyMode() const;
+%Docstring
+Returns the scale dependency mode
+
+.. versionadded:: 3.14
%End
QgsTolerance::UnitType units() const;
diff --git a/python/core/auto_generated/qgsstatisticalsummary.sip.in b/python/core/auto_generated/qgsstatisticalsummary.sip.in
index ab16d31d90bc..afcbe9263368 100644
--- a/python/core/auto_generated/qgsstatisticalsummary.sip.in
+++ b/python/core/auto_generated/qgsstatisticalsummary.sip.in
@@ -263,20 +263,20 @@ or via setStatistics.
double minority() const;
%Docstring
-Returns minority of values. The minority is the value with least occurrences in the list
+Returns minority of values. The minority is the value with least occurrences in the list.
This is only calculated if Statistic.Minority has been specified in the constructor
-or via setStatistics. A NaN value may be returned if the minority cannot
-be calculated.
+or via setStatistics. If multiple values match, return the first value relative to the
+initial values order. A NaN value may be returned if the minority cannot be calculated.
.. seealso:: :py:func:`majority`
%End
double majority() const;
%Docstring
-Returns majority of values. The majority is the value with most occurrences in the list
+Returns majority of values. The majority is the value with most occurrences in the list.
This is only calculated if Statistic.Majority has been specified in the constructor
-or via setStatistics. A NaN value may be returned if the majority cannot
-be calculated.
+or via setStatistics. If multiple values match, return the first value relative to the
+initial values order. A NaN value may be returned if the minority cannot be calculated.
.. seealso:: :py:func:`minority`
%End
diff --git a/python/core/auto_generated/qgsstringstatisticalsummary.sip.in b/python/core/auto_generated/qgsstringstatisticalsummary.sip.in
index 382de1623c34..de9e7101fb2c 100644
--- a/python/core/auto_generated/qgsstringstatisticalsummary.sip.in
+++ b/python/core/auto_generated/qgsstringstatisticalsummary.sip.in
@@ -39,6 +39,8 @@ specifying the statistic in the constructor or via setStatistics().
MinimumLength,
MaximumLength,
MeanLength,
+ Minority,
+ Majority,
All,
};
typedef QFlags Statistics;
@@ -213,6 +215,30 @@ Returns the maximum length of strings.
Returns the mean length of strings.
.. versionadded:: 3.0
+%End
+
+ QString minority() const;
+%Docstring
+Returns the least common string. The minority is the value with least occurrences in the list
+This is only calculated if Statistic.Minority has been specified in the constructor
+or via setStatistics. If multiple values match, return the first value relative to the
+initial values order.
+
+.. seealso:: :py:func:`majority`
+
+.. versionadded:: 3.14
+%End
+
+ QString majority() const;
+%Docstring
+Returns the most common string. The majority is the value with most occurrences in the list
+This is only calculated if Statistic.Majority has been specified in the constructor
+or via setStatistics. If multiple values match, return the first value relative to the
+initial values order.
+
+.. seealso:: :py:func:`minority`
+
+.. versionadded:: 3.14
%End
static QString displayName( QgsStringStatisticalSummary::Statistic statistic );
diff --git a/python/core/auto_generated/qgsstringutils.sip.in b/python/core/auto_generated/qgsstringutils.sip.in
index cf9358f75ac0..478f679e3095 100644
--- a/python/core/auto_generated/qgsstringutils.sip.in
+++ b/python/core/auto_generated/qgsstringutils.sip.in
@@ -248,6 +248,23 @@ so strings with similar sounds should be represented by the same Soundex code.
:param string: input string
:return: 4 letter Soundex code
+%End
+
+ static double fuzzyScore( const QString &candidate, const QString &search );
+%Docstring
+Tests a ``candidate`` string to see how likely it is a match for
+a specified ``search`` string. Values are normalized between 0 and 1.
+
+:param candidate: candidate string
+:param search: search term string
+
+:return: Normalized value of how likely is the ``search`` to be in the ``candidate``
+
+.. note::
+
+ Use this function only to calculate the fuzzy score between two strings and later compare these values, but do not depend on the actual numbers. They are implementation detail that may change in a future release.
+
+.. versionadded:: 3.14
%End
static QString insertLinks( const QString &string, bool *foundLinks = 0 );
diff --git a/python/core/auto_generated/qgstaskmanager.sip.in b/python/core/auto_generated/qgstaskmanager.sip.in
index 231e6ad48e41..f55dc361700f 100644
--- a/python/core/auto_generated/qgstaskmanager.sip.in
+++ b/python/core/auto_generated/qgstaskmanager.sip.in
@@ -47,6 +47,7 @@ clean up and terminate at the earliest possible convenience.
enum Flag
{
CanCancel,
+ CancelWithoutPrompt,
AllFlags,
};
typedef QFlags Flags;
diff --git a/python/core/auto_generated/qgstemporalutils.sip.in b/python/core/auto_generated/qgstemporalutils.sip.in
new file mode 100644
index 000000000000..375bfdc309bc
--- /dev/null
+++ b/python/core/auto_generated/qgstemporalutils.sip.in
@@ -0,0 +1,43 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgstemporalutils.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+class QgsTemporalUtils
+{
+%Docstring
+Contains utility methods for working with temporal layers and projects.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgstemporalutils.h"
+%End
+ public:
+
+ static QgsDateTimeRange calculateTemporalRangeForProject( QgsProject *project );
+%Docstring
+Calculates the temporal range for a ``project``.
+
+This method considers the temporal range available from layers contained within the project and
+returns the maximal combined temporal extent of these layers.
+%End
+
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgstemporalutils.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/qgstiles.sip.in b/python/core/auto_generated/qgstiles.sip.in
new file mode 100644
index 000000000000..befad4c77ee3
--- /dev/null
+++ b/python/core/auto_generated/qgstiles.sip.in
@@ -0,0 +1,144 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgstiles.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsTileXYZ
+{
+%Docstring
+Stores coordinates of a tile in a tile matrix set. Tile matrix is identified
+by the zoomLevel(), and the position within tile matrix is given by column()
+and row().
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgstiles.h"
+%End
+ public:
+ QgsTileXYZ( int tc = -1, int tr = -1, int tz = -1 );
+%Docstring
+Constructs a tile identifier from given column, row and zoom level indices
+%End
+
+ int column() const;
+%Docstring
+Returns tile's column index (X)
+%End
+ int row() const;
+%Docstring
+Returns tile's row index (Y)
+%End
+ int zoomLevel() const;
+%Docstring
+Returns tile's zoom level (Z)
+%End
+
+ QString toString() const;
+%Docstring
+Returns tile coordinates in a formatted string
+%End
+
+};
+
+
+class QgsTileRange
+{
+%Docstring
+Range of tiles in a tile matrix to be rendered. The selection is rectangular,
+given by start/end row and column numbers.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgstiles.h"
+%End
+ public:
+ QgsTileRange( int c1 = -1, int c2 = -1, int r1 = -1, int r2 = -1 );
+%Docstring
+Constructs a range of tiles from given span of columns and rows
+%End
+ bool isValid() const;
+%Docstring
+Returns whether the range is valid (when all row/column numbers are not negative)
+%End
+
+ int startColumn() const;
+%Docstring
+Returns index of the first column in the range
+%End
+ int endColumn() const;
+%Docstring
+Returns index of the last column in the range
+%End
+ int startRow() const;
+%Docstring
+Returns index of the first row in the range
+%End
+ int endRow() const;
+%Docstring
+Returns index of the last row in the range
+%End
+
+};
+
+
+class QgsTileMatrix
+{
+%Docstring
+Defines a matrix of tiles for a single zoom level: it is defined by its size (width * height)
+and map extent that it covers.
+
+Please note that we follow the XYZ convention of X/Y axes, i.e. top-left tile has [0,0] coordinate
+(which is different from TMS convention where bottom-left tile has [0,0] coordinate).
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgstiles.h"
+%End
+ public:
+
+ static QgsTileMatrix fromWebMercator( int mZoomLevel );
+%Docstring
+Returns a tile matrix for the usual web mercator
+%End
+
+ QgsRectangle tileExtent( QgsTileXYZ id ) const;
+%Docstring
+Returns extent of the given tile in this matrix
+%End
+
+ QgsPointXY tileCenter( QgsTileXYZ id ) const;
+%Docstring
+Returns center of the given tile in this matrix
+%End
+
+ QgsTileRange tileRangeFromExtent( const QgsRectangle &mExtent );
+%Docstring
+Returns tile range that fully covers the given extent
+%End
+
+ QPointF mapToTileCoordinates( const QgsPointXY &mapPoint ) const;
+%Docstring
+Returns row/column coordinates (floating point number) from the given point in map coordinates
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/qgstiles.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/qgsvectorlayerjoininfo.sip.in b/python/core/auto_generated/qgsvectorlayerjoininfo.sip.in
index c19b53d08650..ef842fc66018 100644
--- a/python/core/auto_generated/qgsvectorlayerjoininfo.sip.in
+++ b/python/core/auto_generated/qgsvectorlayerjoininfo.sip.in
@@ -77,9 +77,11 @@ Returns prefix of fields from the joined layer. If ``None``, joined layer's name
%Docstring
Sets whether values from the joined layer should be cached in memory to speed up lookups
%End
+
bool isUsingMemoryCache() const;
%Docstring
-Returns whether values from the joined layer should be cached in memory to speed up lookups
+Returns whether values from the joined layer should be cached in memory to speed up lookups.
+Will return false if upsertOnEdit is enabled.
%End
bool isDynamicFormEnabled() const;
diff --git a/python/core/auto_generated/raster/qgsrastercontourrenderer.sip.in b/python/core/auto_generated/raster/qgsrastercontourrenderer.sip.in
new file mode 100644
index 000000000000..a7a0e0eb0cf0
--- /dev/null
+++ b/python/core/auto_generated/raster/qgsrastercontourrenderer.sip.in
@@ -0,0 +1,125 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/raster/qgsrastercontourrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+class QgsRasterContourRenderer : QgsRasterRenderer
+{
+%Docstring
+Raster renderer that generates contours on the fly for a source raster band.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsrastercontourrenderer.h"
+%End
+ public:
+ explicit QgsRasterContourRenderer( QgsRasterInterface *input );
+%Docstring
+Creates a contour renderer
+%End
+ ~QgsRasterContourRenderer();
+
+
+ virtual QgsRasterContourRenderer *clone() const /Factory/;
+
+%Docstring
+QgsRasterContourRenderer cannot be copied. Use clone() instead.
+%End
+
+ static QgsRasterRenderer *create( const QDomElement &elem, QgsRasterInterface *input ) /Factory/;
+%Docstring
+Creates an instance of the renderer based on definition from XML (used by renderer registry)
+%End
+
+ virtual void writeXml( QDomDocument &doc, QDomElement &parentElem ) const;
+
+
+ virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback = 0 ) /Factory/;
+
+
+ virtual QList usesBands() const;
+
+
+
+ int inputBand() const;
+%Docstring
+Returns the number of the input raster band
+%End
+ void setInputBand( int band );
+%Docstring
+Sets the number of the input raster band
+%End
+
+ double contourInterval() const;
+%Docstring
+Returns the interval of contour lines generation
+%End
+ void setContourInterval( double interval );
+%Docstring
+Sets the interval of contour lines generation
+%End
+
+ QgsLineSymbol *contourSymbol() const;
+%Docstring
+Returns the symbol used for contour lines
+%End
+ void setContourSymbol( QgsLineSymbol *symbol /Transfer/ );
+%Docstring
+Sets the symbol used for contour lines. Takes ownership of the passed symbol
+%End
+
+ double contourIndexInterval() const;
+%Docstring
+Returns the interval of index contour lines (index contour lines are typical further apart and with a wider line symbol)
+%End
+ void setContourIndexInterval( double interval );
+%Docstring
+Sets the interval of index contour lines (index contour lines are typical further apart and with a wider line symbol)
+%End
+
+ QgsLineSymbol *contourIndexSymbol() const;
+%Docstring
+Returns the symbol of index contour lines
+%End
+ void setContourIndexSymbol( QgsLineSymbol *symbol /Transfer/ );
+%Docstring
+Sets the symbol of index contour lines
+%End
+
+ double downscale() const;
+%Docstring
+Returns by how much the renderer will scale down the request to the data provider.
+For example, for a raster block 1000x500 with downscale 10, the renderer will request raster 100x50 from provider.
+Higher downscale makes contour lines more simplified (at the expense of losing some detail).
+The value of one means there will be no downscaling.
+%End
+
+ void setDownscale( double scale );
+%Docstring
+Sets by how much the renderer will scale down the request to the data provider.
+
+.. seealso:: :py:func:`downscale`
+%End
+
+ private:
+ QgsRasterContourRenderer( const QgsRasterContourRenderer & );
+ const QgsRasterContourRenderer &operator=( const QgsRasterContourRenderer & );
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/raster/qgsrastercontourrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/raster/qgsrasterdataprovider.sip.in b/python/core/auto_generated/raster/qgsrasterdataprovider.sip.in
index 77e769019f92..0d32757918c7 100644
--- a/python/core/auto_generated/raster/qgsrasterdataprovider.sip.in
+++ b/python/core/auto_generated/raster/qgsrasterdataprovider.sip.in
@@ -117,6 +117,13 @@ It makes no sense to set input on provider */
Returns data type for the band specified by number
%End
+ virtual QgsFields fields() const;
+%Docstring
+Returns the fields of the raster layer for data providers that expose them,
+the default implementation returns an empty list.
+
+.. versionadded:: 3.14
+%End
virtual Qgis::DataType sourceDataType( int bandNo ) const = 0;
%Docstring
diff --git a/python/core/auto_generated/raster/qgsrasterdataprovidertemporalcapabilities.sip.in b/python/core/auto_generated/raster/qgsrasterdataprovidertemporalcapabilities.sip.in
index f2993c5d2804..1d05bc0ca1d8 100644
--- a/python/core/auto_generated/raster/qgsrasterdataprovidertemporalcapabilities.sip.in
+++ b/python/core/auto_generated/raster/qgsrasterdataprovidertemporalcapabilities.sip.in
@@ -38,6 +38,8 @@ The ``enabled`` argument specifies whether the data provider has temporal capabi
MatchUsingWholeRange,
MatchExactUsingStartOfRange,
MatchExactUsingEndOfRange,
+ FindClosestMatchToStartOfRange,
+ FindClosestMatchToEndOfRange
};
IntervalHandlingMethod intervalHandlingMethod() const;
@@ -90,60 +92,6 @@ extent of datetime values available for reference temporal ranges from the provi
%Docstring
Returns the requested temporal range.
Intended to be used by the provider in fetching data.
-%End
-
- void setRequestedReferenceTemporalRange( const QgsDateTimeRange &range );
-%Docstring
-Sets the requested reference temporal ``range`` to retrieve when
-returning data from the associated data provider.
-
-.. note::
-
- this is not normally manually set, and is intended for use by
- QgsRasterLayerRenderer to automatically set the requested temporal range
- on a clone of the data provider during a render job.
-
-.. seealso:: :py:func:`requestedReferenceTemporalRange`
-%End
-
- const QgsDateTimeRange &requestedReferenceTemporalRange() const;
-%Docstring
-Returns the requested reference temporal range.
-Intended to be used by the provider in fetching data.
-
-.. seealso:: :py:func:`setRequestedReferenceTemporalRange`
-%End
-
- void setEnableTime( bool enabled );
-%Docstring
-Sets the time enabled status.
-This enables whether time part in the temporal range should be
-used when updated the temporal range of these capabilities.
-
-This is useful in some temporal layers who use dates only.
-
-.. seealso:: :py:func:`isTimeEnabled`
-%End
-
- bool isTimeEnabled() const;
-%Docstring
-Returns the temporal property status.
-
-.. seealso:: :py:func:`setEnableTime`
-%End
-
- void setReferenceEnable( bool enabled );
-%Docstring
-Sets the usage status of the reference range.
-
-.. seealso:: :py:func:`isReferenceEnable`
-%End
-
- bool isReferenceEnable() const;
-%Docstring
-Returns the enabled status of the reference range.
-
-.. seealso:: :py:func:`setReferenceEnable`
%End
};
diff --git a/python/core/auto_generated/raster/qgsrasterlayertemporalproperties.sip.in b/python/core/auto_generated/raster/qgsrasterlayertemporalproperties.sip.in
index d29f3dbcd08d..66f2032ce92e 100644
--- a/python/core/auto_generated/raster/qgsrasterlayertemporalproperties.sip.in
+++ b/python/core/auto_generated/raster/qgsrasterlayertemporalproperties.sip.in
@@ -93,70 +93,6 @@ Returns the fixed temporal range for the layer.
QgsRasterLayerTemporalProperties.ModeFixedTemporalRange
.. seealso:: :py:func:`setFixedTemporalRange`
-%End
-
- void setFixedReferenceTemporalRange( const QgsDateTimeRange &range );
-%Docstring
-Sets a fixed reference temporal ``range`` to apply to the whole layer. All bands from
-the raster layer will be rendered whenever the current datetime range of
-a render context intersects the specified ``range``.
-
-.. warning::
-
- This setting is only effective when mode() is
- QgsRasterLayerTemporalProperties.ModeFixedTemporalRange
-
-.. seealso:: :py:func:`fixedReferenceTemporalRange`
-%End
-
- const QgsDateTimeRange &fixedReferenceTemporalRange() const;
-%Docstring
-Returns the fixed reference temporal range for the layer.
-
-.. warning::
-
- To be used only when mode() is
- QgsRasterLayerTemporalProperties.ModeFixedTemporalRange
-
-.. seealso:: :py:func:`setFixedReferenceTemporalRange`
-%End
-
- void setTemporalRange( const QgsDateTimeRange &dateTimeRange );
-%Docstring
-Sets the current active datetime range for the temporal properties.
-
-.. note::
-
- This can be set by user, through raster layer properties widget.
-
-.. seealso:: :py:func:`temporalRange`
-%End
-
- const QgsDateTimeRange &temporalRange() const;
-%Docstring
-Returns the current active datetime range for these temporal properties.
-
-.. seealso:: :py:func:`setTemporalRange`
-%End
-
- void setReferenceTemporalRange( const QgsDateTimeRange &dateTimeRange );
-%Docstring
-Sets the current active reference datetime range for the temporal properties.
-
-This will be used by bi-temporal data.
-
-.. note::
-
- This can be set by user, through raster layer properties widget.
-
-.. seealso:: :py:func:`referenceTemporalRange`
-%End
-
- const QgsDateTimeRange &referenceTemporalRange() const;
-%Docstring
-Returns the current active reference datetime range for these temporal properties.
-
-.. seealso:: :py:func:`setReferenceTemporalRange`
%End
virtual QDomElement writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context );
diff --git a/python/core/auto_generated/scalebar/qgsdoubleboxscalebarrenderer.sip.in b/python/core/auto_generated/scalebar/qgsdoubleboxscalebarrenderer.sip.in
index 916917e0739a..6801cc345f79 100644
--- a/python/core/auto_generated/scalebar/qgsdoubleboxscalebarrenderer.sip.in
+++ b/python/core/auto_generated/scalebar/qgsdoubleboxscalebarrenderer.sip.in
@@ -26,7 +26,16 @@ Double box with alternating colors.
Constructor for QgsDoubleBoxScaleBarRenderer.
%End
- virtual QString name() const;
+ virtual QString id() const;
+
+ virtual QString visibleName() const;
+
+ virtual Flags flags() const;
+
+ virtual int sortKey() const;
+
+ virtual QgsDoubleBoxScaleBarRenderer *clone() const /Factory/;
+
virtual void draw( QgsRenderContext &context,
const QgsScaleBarSettings &settings,
diff --git a/python/core/auto_generated/scalebar/qgshollowscalebarrenderer.sip.in b/python/core/auto_generated/scalebar/qgshollowscalebarrenderer.sip.in
new file mode 100644
index 000000000000..0e5972af0f87
--- /dev/null
+++ b/python/core/auto_generated/scalebar/qgshollowscalebarrenderer.sip.in
@@ -0,0 +1,55 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/scalebar/qgshollowscalebarrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+class QgsHollowScaleBarRenderer: QgsScaleBarRenderer
+{
+%Docstring
+Scalebar style that draws a single box with alternating color for the segments, with horizontal lines through
+alternating segments. AKA "South African" style.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgshollowscalebarrenderer.h"
+%End
+ public:
+
+ QgsHollowScaleBarRenderer();
+%Docstring
+Constructor for QgsHollowScaleBarRenderer.
+%End
+
+ virtual QString id() const;
+
+ virtual QString visibleName() const;
+
+ virtual Flags flags() const;
+
+ virtual int sortKey() const;
+
+ virtual QgsHollowScaleBarRenderer *clone() const /Factory/;
+
+
+ virtual void draw( QgsRenderContext &context,
+ const QgsScaleBarSettings &settings,
+ const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const;
+ virtual bool applyDefaultSettings( QgsScaleBarSettings &settings ) const;
+
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/scalebar/qgshollowscalebarrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/scalebar/qgsnumericscalebarrenderer.sip.in b/python/core/auto_generated/scalebar/qgsnumericscalebarrenderer.sip.in
index fea56cbca065..b46f996c3149 100644
--- a/python/core/auto_generated/scalebar/qgsnumericscalebarrenderer.sip.in
+++ b/python/core/auto_generated/scalebar/qgsnumericscalebarrenderer.sip.in
@@ -26,15 +26,28 @@ A scale bar style that draws text in the form of '1:XXXXX'.
Constructor for QgsNumericScaleBarRenderer.
%End
- virtual QString name() const;
+ virtual QString id() const;
+
+ virtual QString visibleName() const;
+
+ virtual int sortKey() const;
+
+ virtual Flags flags() const;
+
+ virtual QgsNumericScaleBarRenderer *clone() const /Factory/;
+
virtual void draw( QgsRenderContext &context,
const QgsScaleBarSettings &settings,
const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const;
- virtual QSizeF calculateBoxSize( const QgsScaleBarSettings &settings,
+ virtual QSizeF calculateBoxSize( QgsRenderContext &context,
+ const QgsScaleBarSettings &settings,
const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const;
+ virtual QSizeF calculateBoxSize( const QgsScaleBarSettings &settings, const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const /Deprecated/;
+
+
};
/************************************************************************
diff --git a/python/core/auto_generated/scalebar/qgsscalebarrenderer.sip.in b/python/core/auto_generated/scalebar/qgsscalebarrenderer.sip.in
index 34fb04002dd0..33de3a36d53e 100644
--- a/python/core/auto_generated/scalebar/qgsscalebarrenderer.sip.in
+++ b/python/core/auto_generated/scalebar/qgsscalebarrenderer.sip.in
@@ -36,15 +36,68 @@ custom labeling.
};
+ enum class Flag
+ {
+ FlagUsesLineSymbol,
+ FlagUsesFillSymbol,
+ FlagUsesAlternateFillSymbol,
+ FlagRespectsUnits,
+ FlagRespectsMapUnitsPerScaleBarUnit,
+ FlagUsesUnitLabel,
+ FlagUsesSegments,
+ FlagUsesLabelBarSpace,
+ FlagUsesLabelVerticalPlacement,
+ FlagUsesLabelHorizontalPlacement,
+ FlagUsesAlignment,
+ };
+ typedef QFlags Flags;
+
+
QgsScaleBarRenderer();
%Docstring
Constructor for QgsScaleBarRenderer.
%End
virtual ~QgsScaleBarRenderer();
- virtual QString name() const = 0;
+ QString name() const /Deprecated/;
%Docstring
Returns the unique name for this style.
+
+.. deprecated::
+ use id() instead
+%End
+
+ virtual QString id() const = 0;
+%Docstring
+Returns the unique ID for this renderer.
+
+.. versionadded:: 3.14
+%End
+
+ virtual QString visibleName() const = 0;
+%Docstring
+Returns the user friendly, translated name for the renderer.
+
+.. versionadded:: 3.14
+%End
+
+ virtual Flags flags() const;
+%Docstring
+Returns the scalebar rendering flags, which dictates the renderer's behavior.
+
+.. versionadded:: 3.14
+%End
+
+ virtual int sortKey() const;
+%Docstring
+Returns a sorting key value, where renderers with a lower sort key will be shown earlier in lists.
+
+Generally, subclasses should return QgsScaleBarRenderer.sortKey() as their sorting key.
+%End
+
+ virtual QgsScaleBarRenderer *clone() const = 0 /Factory/;
+%Docstring
+Returns a clone of the renderer. The caller takes ownership of the returned value.
%End
virtual void draw( QgsRenderContext &context,
@@ -54,10 +107,31 @@ Returns the unique name for this style.
Draws the scalebar using the specified ``settings`` and ``scaleContext`` to a destination render ``context``.
%End
- virtual QSizeF calculateBoxSize( const QgsScaleBarSettings &settings,
+ virtual QSizeF calculateBoxSize( const QgsScaleBarSettings &settings,
+ const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const /Deprecated/;
+%Docstring
+Calculates the required box size (in millimeters) for a scalebar using the specified ``settings`` and ``scaleContext``.
+
+.. deprecated::
+ Use the version with a QgsRenderContext instead.
+%End
+
+ virtual QSizeF calculateBoxSize( QgsRenderContext &context,
+ const QgsScaleBarSettings &settings,
const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const;
%Docstring
Calculates the required box size (in millimeters) for a scalebar using the specified ``settings`` and ``scaleContext``.
+
+.. versionadded:: 3.14
+%End
+
+ virtual bool applyDefaultSettings( QgsScaleBarSettings &settings ) const;
+%Docstring
+Applies any default settings relating to the scalebar to the passed ``settings`` object.
+
+Returns ``True`` if settings were applied.
+
+.. versionadded:: 3.14
%End
protected:
@@ -89,9 +163,19 @@ Returns the x-offset (in render context painter units) used for the first label
.. versionadded:: 3.2
%End
- QList segmentPositions( const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const;
+ QList segmentPositions( const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const /Deprecated/;
%Docstring
Returns a list of positions for each segment within the scalebar.
+
+.. deprecated::
+ use the version with a QgsRenderContext instead
+%End
+
+ QList segmentPositions( QgsRenderContext &context, const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const;
+%Docstring
+Returns a list of positions for each segment within the scalebar.
+
+.. versionadded:: 3.14
%End
QList segmentWidths( const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const;
@@ -101,6 +185,9 @@ Returns a list of widths of each segment of the scalebar.
};
+QFlags operator|(QgsScaleBarRenderer::Flag f1, QFlags f2);
+
+
/************************************************************************
* This file has been generated automatically from *
* *
diff --git a/python/core/auto_generated/scalebar/qgsscalebarrendererregistry.sip.in b/python/core/auto_generated/scalebar/qgsscalebarrendererregistry.sip.in
new file mode 100644
index 000000000000..545e7b3fb187
--- /dev/null
+++ b/python/core/auto_generated/scalebar/qgsscalebarrendererregistry.sip.in
@@ -0,0 +1,84 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/scalebar/qgsscalebarrendererregistry.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsScaleBarRendererRegistry
+{
+%Docstring
+The QgsScaleBarRendererRegistry manages registered scalebar renderers.
+
+A reference to the QgsScaleBarRendererRegistry can be obtained from
+:py:func:`QgsApplication.scalebarRendererRegistry()`
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsscalebarrendererregistry.h"
+%End
+ public:
+
+ explicit QgsScaleBarRendererRegistry();
+%Docstring
+You should not normally need to create your own scalebar renderer registry.
+
+Use the one provided by `QgsApplication.scalebarRendererRegistry()` instead.
+%End
+ ~QgsScaleBarRendererRegistry();
+
+ QStringList renderers() const;
+%Docstring
+Returns a list of the renderer ids currently contained in the registry.
+%End
+
+ QStringList sortedRendererList() const;
+%Docstring
+Returns a list of the renderer ids currently contained in the registry,
+sorted in an order respecting the renderer's sort keys and display strings.
+%End
+
+ void addRenderer( QgsScaleBarRenderer *renderer /Transfer/ );
+%Docstring
+Adds a new ``renderer`` to the registry.
+
+Ownership is transferred to the registry.
+%End
+
+ void removeRenderer( const QString &id );
+%Docstring
+Removes the renderer with matching ``id`` from the registry.
+%End
+
+ QgsScaleBarRenderer *renderer( const QString &id ) const /TransferBack/;
+%Docstring
+Creates a new scalebar renderer by ``id``. If there is no such ``id`` registered,
+``None`` will be returned instead.
+
+The caller takes ownership of the returned object.
+%End
+
+ QString visibleName( const QString &id ) const;
+%Docstring
+Returns the translated, user-visible name for the renderer with matching ``id``.
+%End
+
+ int sortKey( const QString &id ) const;
+%Docstring
+Returns the sorting key for the renderer with matching ``id``.
+%End
+
+};
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/scalebar/qgsscalebarrendererregistry.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/scalebar/qgsscalebarsettings.sip.in b/python/core/auto_generated/scalebar/qgsscalebarsettings.sip.in
index 6bf986f48548..5724292010b7 100644
--- a/python/core/auto_generated/scalebar/qgsscalebarsettings.sip.in
+++ b/python/core/auto_generated/scalebar/qgsscalebarsettings.sip.in
@@ -9,7 +9,6 @@
-
class QgsScaleBarSettings
{
%Docstring
@@ -291,87 +290,189 @@ Sets the ``color`` used for drawing text in the scalebar.
use textFormat() instead
%End
- QColor fillColor() const;
+ QColor fillColor() const /Deprecated/;
%Docstring
Returns the color used for fills in the scalebar.
.. seealso:: :py:func:`setFillColor`
.. seealso:: :py:func:`fillColor2`
+
+.. deprecated::
+ use fillSymbol() instead.
%End
- void setFillColor( const QColor &color );
+ void setFillColor( const QColor &color ) /Deprecated/;
%Docstring
Sets the ``color`` used for fills in the scalebar.
.. seealso:: :py:func:`fillColor`
.. seealso:: :py:func:`setFillColor2`
+
+.. deprecated::
+ use setFillSymbol() instead.
%End
- QColor fillColor2() const;
+ QColor fillColor2() const /Deprecated/;
%Docstring
Returns the secondary color used for fills in the scalebar.
.. seealso:: :py:func:`setFillColor2`
.. seealso:: :py:func:`fillColor`
+
+.. deprecated::
+ use alternateFillSymbol() instead
%End
- void setFillColor2( const QColor &color );
+ void setFillColor2( const QColor &color ) /Deprecated/;
%Docstring
Sets the secondary ``color`` used for fills in the scalebar.
.. seealso:: :py:func:`fillColor2`
.. seealso:: :py:func:`setFillColor2`
+
+.. deprecated::
+ use setAlternateFillSymbol() instead.
%End
- QColor lineColor() const;
+ QColor lineColor() const /Deprecated/;
%Docstring
Returns the color used for lines in the scalebar.
.. seealso:: :py:func:`setLineColor`
+
+.. deprecated::
+ use lineSymbol() instead.
%End
- void setLineColor( const QColor &color );
+ void setLineColor( const QColor &color ) /Deprecated/;
%Docstring
Sets the ``color`` used for lines in the scalebar.
.. seealso:: :py:func:`lineColor`
+
+.. deprecated::
+ use setLineSymbol() instead.
%End
- double lineWidth() const;
+ double lineWidth() const /Deprecated/;
%Docstring
Returns the line width in millimeters for lines in the scalebar.
.. seealso:: :py:func:`setLineWidth`
+
+.. deprecated::
+ use lineSymbol() instead.
%End
- void setLineWidth( double width );
+ void setLineWidth( double width ) /Deprecated/;
%Docstring
Sets the line ``width`` in millimeters for lines in the scalebar.
.. seealso:: :py:func:`lineWidth`
+
+.. deprecated::
+ use setLineSymbol() instead.
%End
- QPen pen() const;
+ QPen pen() const /Deprecated/;
%Docstring
Returns the pen used for drawing outlines in the scalebar.
.. seealso:: :py:func:`setPen`
.. seealso:: :py:func:`brush`
+
+.. deprecated::
+ use lineSymbol() instead.
%End
- void setPen( const QPen &pen );
+ void setPen( const QPen &pen ) /Deprecated/;
%Docstring
Sets the pen used for drawing outlines in the scalebar.
.. seealso:: :py:func:`pen`
+
+.. deprecated::
+ use setLineSymbol() instead.
+%End
+
+ QgsLineSymbol *lineSymbol() const;
+%Docstring
+Returns the line symbol used to render the scalebar (only used for some scalebar types).
+
+Ownership is not transferred.
+
+.. seealso:: :py:func:`setLineSymbol`
+
+.. versionadded:: 3.14
%End
- QBrush brush() const;
+ void setLineSymbol( QgsLineSymbol *symbol /Transfer/ );
+%Docstring
+Sets the line ``symbol`` used to render the scalebar (only used for some scalebar types). Ownership of ``symbol`` is
+transferred to the scalebar.
+
+.. seealso:: :py:func:`lineSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ QgsFillSymbol *fillSymbol() const;
+%Docstring
+Returns the primary fill symbol used to render the scalebar (only used for some scalebar types).
+
+Ownership is not transferred.
+
+.. seealso:: :py:func:`setFillSymbol`
+
+.. seealso:: :py:func:`alternateFillSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ void setFillSymbol( QgsFillSymbol *symbol /Transfer/ );
+%Docstring
+Sets the primary fill ``symbol`` used to render the scalebar (only used for some scalebar types). Ownership of ``symbol`` is
+transferred to the scalebar.
+
+.. seealso:: :py:func:`fillSymbol`
+
+.. seealso:: :py:func:`setAlternateFillSymbol`
+
+.. versionadded:: 3.14
+%End
+
+
+ QgsFillSymbol *alternateFillSymbol() const;
+%Docstring
+Returns the secondary fill symbol used to render the scalebar (only used for some scalebar types).
+
+Ownership is not transferred.
+
+.. seealso:: :py:func:`setAlternateFillSymbol`
+
+.. seealso:: :py:func:`fillSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ void setAlternateFillSymbol( QgsFillSymbol *symbol /Transfer/ );
+%Docstring
+Sets the secondary fill ``symbol`` used to render the scalebar (only used for some scalebar types). Ownership of ``symbol`` is
+transferred to the scalebar.
+
+.. seealso:: :py:func:`alternateFillSymbol`
+
+.. seealso:: :py:func:`setFillSymbol`
+
+.. versionadded:: 3.14
+%End
+
+ QBrush brush() const /Deprecated/;
%Docstring
Returns the primary brush used for filling the scalebar.
@@ -380,16 +481,22 @@ Returns the primary brush used for filling the scalebar.
.. seealso:: :py:func:`brush2`
.. seealso:: :py:func:`pen`
+
+.. deprecated::
+ use fillSymbol() instead
%End
- void setBrush( const QBrush &brush );
+ void setBrush( const QBrush &brush ) /Deprecated/;
%Docstring
Sets the primary brush used for filling the scalebar.
.. seealso:: :py:func:`brush`
+
+.. deprecated::
+ use setFillSymbol() instead
%End
- QBrush brush2() const;
+ QBrush brush2() const /Deprecated/;
%Docstring
Returns the secondary brush for the scalebar. This is used for alternating color style scalebars, such
as single and double box styles.
@@ -397,13 +504,19 @@ as single and double box styles.
.. seealso:: :py:func:`setBrush2`
.. seealso:: :py:func:`brush`
+
+.. deprecated::
+ use alternateFillSymbol() instead
%End
- void setBrush2( const QBrush &brush );
+ void setBrush2( const QBrush &brush ) /Deprecated/;
%Docstring
Sets the secondary brush used for filling the scalebar.
.. seealso:: :py:func:`brush`
+
+.. deprecated::
+ use setAlternateFillSymbol() instead
%End
double height() const;
@@ -498,32 +611,44 @@ Sets the scalebar ``alignment``.
.. seealso:: :py:func:`alignment`
%End
- Qt::PenJoinStyle lineJoinStyle() const;
+ Qt::PenJoinStyle lineJoinStyle() const /Deprecated/;
%Docstring
Returns the join style used for drawing lines in the scalebar.
.. seealso:: :py:func:`setLineJoinStyle`
+
+.. deprecated::
+ use lineSymbol() instead
%End
- void setLineJoinStyle( Qt::PenJoinStyle style );
+ void setLineJoinStyle( Qt::PenJoinStyle style ) /Deprecated/;
%Docstring
Sets the join ``style`` used when drawing the lines in the scalebar
.. seealso:: :py:func:`lineJoinStyle`
+
+.. deprecated::
+ use setLineSymbol() instead
%End
- Qt::PenCapStyle lineCapStyle() const;
+ Qt::PenCapStyle lineCapStyle() const /Deprecated/;
%Docstring
Returns the cap style used for drawing lines in the scalebar.
.. seealso:: :py:func:`setLineCapStyle`
+
+.. deprecated::
+ use lineSymbol() instead
%End
- void setLineCapStyle( Qt::PenCapStyle style );
+ void setLineCapStyle( Qt::PenCapStyle style ) /Deprecated/;
%Docstring
Sets the cap ``style`` used when drawing the lines in the scalebar.
.. seealso:: :py:func:`lineCapStyle`
+
+.. deprecated::
+ use setLineSymbol() instead
%End
const QgsNumericFormat *numericFormat() const;
diff --git a/python/core/auto_generated/scalebar/qgssingleboxscalebarrenderer.sip.in b/python/core/auto_generated/scalebar/qgssingleboxscalebarrenderer.sip.in
index 886ecc3dd737..33168ab3ee95 100644
--- a/python/core/auto_generated/scalebar/qgssingleboxscalebarrenderer.sip.in
+++ b/python/core/auto_generated/scalebar/qgssingleboxscalebarrenderer.sip.in
@@ -27,7 +27,16 @@ color for the segments.
Constructor for QgsSingleBoxScaleBarRenderer.
%End
- virtual QString name() const;
+ virtual QString id() const;
+
+ virtual QString visibleName() const;
+
+ virtual int sortKey() const;
+
+ virtual Flags flags() const;
+
+ virtual QgsSingleBoxScaleBarRenderer *clone() const /Factory/;
+
virtual void draw( QgsRenderContext &context,
const QgsScaleBarSettings &settings,
diff --git a/python/core/auto_generated/scalebar/qgssteppedlinescalebarrenderer.sip.in b/python/core/auto_generated/scalebar/qgssteppedlinescalebarrenderer.sip.in
new file mode 100644
index 000000000000..37e4dde8fdc5
--- /dev/null
+++ b/python/core/auto_generated/scalebar/qgssteppedlinescalebarrenderer.sip.in
@@ -0,0 +1,52 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/scalebar/qgssteppedlinescalebarrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+class QgsSteppedLineScaleBarRenderer: QgsScaleBarRenderer
+{
+%Docstring
+Scalebar style that draws a stepped line representation of a scalebar.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgssteppedlinescalebarrenderer.h"
+%End
+ public:
+
+ QgsSteppedLineScaleBarRenderer();
+%Docstring
+Constructor for QgsSteppedLineScaleBarRenderer.
+%End
+
+ virtual QString id() const;
+
+ virtual QString visibleName() const;
+
+ virtual int sortKey() const;
+
+ virtual Flags flags() const;
+
+ virtual QgsSteppedLineScaleBarRenderer *clone() const /Factory/;
+
+
+ virtual void draw( QgsRenderContext &context,
+ const QgsScaleBarSettings &settings,
+ const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const;
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/scalebar/qgssteppedlinescalebarrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/scalebar/qgsticksscalebarrenderer.sip.in b/python/core/auto_generated/scalebar/qgsticksscalebarrenderer.sip.in
index 30e6f95c2a24..25801851f446 100644
--- a/python/core/auto_generated/scalebar/qgsticksscalebarrenderer.sip.in
+++ b/python/core/auto_generated/scalebar/qgsticksscalebarrenderer.sip.in
@@ -28,12 +28,20 @@ A scale bar that draws segments using short ticks.
TicksMiddle,
};
- QgsTicksScaleBarRenderer();
+ QgsTicksScaleBarRenderer( TickPosition position = TicksMiddle );
%Docstring
Constructor for QgsTicksScaleBarRenderer.
%End
- virtual QString name() const;
+ virtual QString id() const;
+
+ virtual QString visibleName() const;
+
+ virtual int sortKey() const;
+
+ virtual Flags flags() const;
+
+ virtual QgsTicksScaleBarRenderer *clone() const /Factory/;
virtual void draw( QgsRenderContext &context,
diff --git a/python/core/auto_generated/symbology/qgsmarkersymbollayer.sip.in b/python/core/auto_generated/symbology/qgsmarkersymbollayer.sip.in
index da85da0591e5..6188fd48dcde 100644
--- a/python/core/auto_generated/symbology/qgsmarkersymbollayer.sip.in
+++ b/python/core/auto_generated/symbology/qgsmarkersymbollayer.sip.in
@@ -877,8 +877,6 @@ class QgsFontMarkerSymbolLayer : QgsMarkerSymbolLayer
Constructs a font marker symbol layer.
%End
- ~QgsFontMarkerSymbolLayer();
-
static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/;
%Docstring
@@ -925,6 +923,24 @@ Returns the font family name for the associated font which will be used to rende
Sets the font ``family`` for the font which will be used to render the point.
.. seealso:: :py:func:`fontFamily`
+%End
+
+ QString fontStyle() const;
+%Docstring
+Returns the font style for the associated font which will be used to render the point.
+
+.. seealso:: :py:func:`setFontStyle`
+
+.. versionadded:: 3.14
+%End
+
+ void setFontStyle( const QString &style );
+%Docstring
+Sets the font ``style`` for the font which will be used to render the point.
+
+.. seealso:: :py:func:`fontStyle`
+
+.. versionadded:: 3.14
%End
QString character() const;
@@ -1043,10 +1059,6 @@ Sets the stroke join ``style``.
virtual QRectF bounds( QPointF point, QgsSymbolRenderContext &context );
- protected:
-
-
-
};
diff --git a/python/core/auto_generated/symbology/qgsstyle.sip.in b/python/core/auto_generated/symbology/qgsstyle.sip.in
index 97223e0dea57..26dea77bc1c7 100644
--- a/python/core/auto_generated/symbology/qgsstyle.sip.in
+++ b/python/core/auto_generated/symbology/qgsstyle.sip.in
@@ -353,6 +353,15 @@ Removes all tags for the specified symbol or colorramp
bool removeSymbol( const QString &name );
%Docstring
Removes symbol from style (and delete it)
+%End
+
+ bool renameEntity( StyleEntity type, const QString &oldName, const QString &newName );
+%Docstring
+Renames an entity of the specified ``type`` from ``oldName`` to ``newName``.
+
+Returns ``True`` if the entity was successfully renamed.
+
+.. versionadded:: 3.14
%End
bool renameSymbol( const QString &oldName, const QString &newName );
@@ -744,6 +753,36 @@ Emitted whenever an ``entity``'s tags are changed.
Emitted whenever an ``entity`` is either favorited or un-favorited.
.. versionadded:: 3.4
+%End
+
+ void entityAdded( QgsStyle::StyleEntity entity, const QString &name );
+%Docstring
+Emitted every time a new entity has been added to the database.
+
+.. versionadded:: 3.14
+%End
+
+ void entityRemoved( QgsStyle::StyleEntity entity, const QString &name );
+%Docstring
+Emitted whenever an entity of the specified type is removed from the style and the database
+has been updated as a result.
+
+.. versionadded:: 3.14
+%End
+
+ void entityRenamed( QgsStyle::StyleEntity entity, const QString &oldName, const QString &newName );
+%Docstring
+Emitted whenever a entity of the specified type has been renamed from ``oldName`` to ``newName``
+
+.. versionadded:: 3.14
+%End
+
+ void entityChanged( QgsStyle::StyleEntity entity, const QString &name );
+%Docstring
+Emitted whenever an entity's definition is changed. This does not include
+name or tag changes.
+
+.. versionadded:: 3.14
%End
void symbolRemoved( const QString &name );
diff --git a/python/core/auto_generated/symbology/qgssymbol.sip.in b/python/core/auto_generated/symbology/qgssymbol.sip.in
index 5831f506cd29..9d52954af1c4 100644
--- a/python/core/auto_generated/symbology/qgssymbol.sip.in
+++ b/python/core/auto_generated/symbology/qgssymbol.sip.in
@@ -272,7 +272,8 @@ layer.
.. seealso:: :py:func:`setColor`
%End
- void drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext *customContext = 0, bool selected = false, const QgsExpressionContext *expressionContext = 0 );
+ void drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext *customContext = 0, bool selected = false, const QgsExpressionContext *expressionContext = 0,
+ const QgsLegendPatchShape *patchShape = 0 );
%Docstring
Draws an icon of the symbol that occupies an area given by ``size`` using the specified ``painter``.
@@ -284,6 +285,7 @@ matches the settings from that context.
:param customContext: the context in which the rendering happens
:param selected: set to ``True`` to render the symbol in a selected state
:param expressionContext: optional custom expression context
+:param patchShape: optional patch shape to use for symbol preview. If not specified a default shape will be used instead.
.. seealso:: :py:func:`exportImage`
@@ -613,6 +615,8 @@ Constructor for QgsSymbolRenderContext
:param mapUnitScale:
%End
+ ~QgsSymbolRenderContext();
+
QgsRenderContext &renderContext();
%Docstring
@@ -800,6 +804,24 @@ Set an expression scope for this symbol.
Will take ownership.
:param contextScope: An expression scope for details about this symbol
+%End
+
+ const QgsLegendPatchShape *patchShape() const;
+%Docstring
+Returns the symbol patch shape, to use if rendering symbol preview icons.
+
+.. seealso:: :py:func:`setPatchShape`
+
+.. versionadded:: 3.14
+%End
+
+ void setPatchShape( const QgsLegendPatchShape &shape );
+%Docstring
+Sets the symbol patch ``shape``, to use if rendering symbol preview icons.
+
+.. seealso:: :py:func:`patchShape`
+
+.. versionadded:: 3.14
%End
private:
diff --git a/python/core/auto_generated/symbology/qgssymbollayer.sip.in b/python/core/auto_generated/symbology/qgssymbollayer.sip.in
index 59673017e5bc..7cb68f80d9ed 100644
--- a/python/core/auto_generated/symbology/qgssymbollayer.sip.in
+++ b/python/core/auto_generated/symbology/qgssymbollayer.sip.in
@@ -143,6 +143,8 @@ class QgsSymbolLayer
PropertyRandomSeed,
PropertyClipPoints,
PropertyDensityArea,
+ PropertyFontFamily,
+ PropertyFontStyle,
};
static const QgsPropertiesDefinition &propertyDefinitions();
diff --git a/python/core/auto_generated/vectortile/qgsvectortilebasicrenderer.sip.in b/python/core/auto_generated/vectortile/qgsvectortilebasicrenderer.sip.in
new file mode 100644
index 000000000000..18f1fa3442d3
--- /dev/null
+++ b/python/core/auto_generated/vectortile/qgsvectortilebasicrenderer.sip.in
@@ -0,0 +1,206 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/vectortile/qgsvectortilebasicrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+class QgsVectorTileBasicRendererStyle
+{
+%Docstring
+Definition of map rendering of a subset of vector tile data. The subset of data is defined by:
+1. sub-layer name
+2. geometry type (a single sub-layer may have multiple geometry types)
+3. filter expression
+
+Renering is determined by the associated symbol (QgsSymbol). Symbol has to be of the same
+type as the chosen geometryType() - i.e. QgsMarkerSymbol for points, QgsLineSymbol for linestrings
+and QgsFillSymbol for polygons.
+
+It is possible to further constrain when this style is applied by setting a range of allowed
+zoom levels, or by disabling it.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsvectortilebasicrenderer.h"
+%End
+ public:
+ QgsVectorTileBasicRendererStyle( const QString &stName = QString(), const QString &laName = QString(), QgsWkbTypes::GeometryType geomType = QgsWkbTypes::UnknownGeometry );
+%Docstring
+Constructs a style object
+%End
+ QgsVectorTileBasicRendererStyle( const QgsVectorTileBasicRendererStyle &other );
+%Docstring
+Constructs a style object as a copy of another style
+%End
+ ~QgsVectorTileBasicRendererStyle();
+
+ void setStyleName( const QString &name );
+%Docstring
+Sets human readable name of this style
+%End
+ QString styleName() const;
+%Docstring
+Returns human readable name of this style
+%End
+
+ void setLayerName( const QString &name );
+%Docstring
+Sets name of the sub-layer to render (empty layer means that all layers match)
+%End
+ QString layerName() const;
+%Docstring
+Returns name of the sub-layer to render (empty layer means that all layers match)
+%End
+
+ void setGeometryType( QgsWkbTypes::GeometryType geomType );
+%Docstring
+Sets type of the geometry that will be used (point / line / polygon)
+%End
+ QgsWkbTypes::GeometryType geometryType() const;
+%Docstring
+Returns type of the geometry that will be used (point / line / polygon)
+%End
+
+ void setFilterExpression( const QString &expr );
+%Docstring
+Sets filter expression (empty filter means that all features match)
+%End
+ QString filterExpression() const;
+%Docstring
+Returns filter expression (empty filter means that all features match)
+%End
+
+ void setSymbol( QgsSymbol *sym /Transfer/ );
+%Docstring
+Sets symbol for rendering. Takes ownership of the symbol.
+%End
+ QgsSymbol *symbol() const;
+%Docstring
+Returns symbol for rendering
+%End
+
+ void setEnabled( bool enabled );
+%Docstring
+Sets whether this style is enabled (used for rendering)
+%End
+ bool isEnabled() const;
+%Docstring
+Returns whether this style is enabled (used for rendering)
+%End
+
+ void setMinZoomLevel( int minZoom );
+%Docstring
+Sets minimum zoom level index (negative number means no limit)
+%End
+ int minZoomLevel() const;
+%Docstring
+Returns minimum zoom level index (negative number means no limit)
+%End
+
+ void setMaxZoomLevel( int maxZoom );
+%Docstring
+Sets maximum zoom level index (negative number means no limit)
+%End
+ int maxZoomLevel() const;
+%Docstring
+Returns maxnimum zoom level index (negative number means no limit)
+%End
+
+ bool isActive( int zoomLevel ) const;
+%Docstring
+Returns whether the style is active at given zoom level (also checks "enabled" flag)
+%End
+
+ void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const;
+%Docstring
+Writes object content to given DOM element
+%End
+ void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
+%Docstring
+Reads object content from given DOM element
+%End
+
+};
+
+
+class QgsVectorTileBasicRenderer : QgsVectorTileRenderer
+{
+%Docstring
+The default vector tile renderer implementation. It has an ordered list of "styles",
+each defines a rendering rule.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsvectortilebasicrenderer.h"
+%End
+ public:
+ QgsVectorTileBasicRenderer();
+%Docstring
+Constructs renderer with no styles
+%End
+
+ virtual QString type() const;
+
+ virtual QgsVectorTileBasicRenderer *clone() const /Factory/;
+
+ virtual void startRender( QgsRenderContext &context, int tileZoom, const QgsTileRange &tileRange );
+
+ virtual void stopRender( QgsRenderContext &context );
+
+ virtual void renderTile( const QgsVectorTileRendererData &tile, QgsRenderContext &context );
+
+ virtual void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const;
+
+ virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
+
+
+ void setStyles( const QList &styles );
+%Docstring
+Sets list of styles of the renderer
+%End
+ QList styles() const;
+%Docstring
+Returns list of styles of the renderer
+%End
+ void setStyle( int index, const QgsVectorTileBasicRendererStyle &style );
+%Docstring
+Updates style definition at the paricular index of the list (the index must be in interval [0,N-1] otherwise this function does nothing)
+%End
+ QgsVectorTileBasicRendererStyle style( int index ) const;
+%Docstring
+Returns style definition at the particular index
+%End
+
+ static QList simpleStyle(
+ const QColor &polygonFillColor, const QColor &polygonStrokeColor, double polygonStrokeWidth,
+ const QColor &lineStrokeColor, double lineStrokeWidth,
+ const QColor &pointFillColor, const QColor &pointStrokeColor, double pointSize );
+%Docstring
+Returns a list of styles to render all layers with the given fill/stroke colors, stroke widths and marker sizes
+%End
+
+ static QList simpleStyleWithRandomColors();
+%Docstring
+Returns a list of styles to render all layers, using random colors
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/vectortile/qgsvectortilebasicrenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/vectortile/qgsvectortilelayer.sip.in b/python/core/auto_generated/vectortile/qgsvectortilelayer.sip.in
new file mode 100644
index 000000000000..196d732ff912
--- /dev/null
+++ b/python/core/auto_generated/vectortile/qgsvectortilelayer.sip.in
@@ -0,0 +1,146 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/vectortile/qgsvectortilelayer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+class QgsVectorTileLayer : QgsMapLayer
+{
+%Docstring
+Implements a map layer that is dedicated to rendering of vector tiles.
+Vector tiles compared to "ordinary" vector layers are pre-processed data
+optimized for fast rendering. A dataset is provided with a series of zoom levels
+for different map scales. Each zoom level has a matrix of tiles that contain
+actual data. A single vector tile may be a a file stored on a local drive,
+requested over HTTP request or retrieved from a database.
+
+Content of a vector tile is divided into one or more named sub-layers. Each such
+sub-layer may contain many features which consist of geometry and attributes.
+Contrary to traditional vector layers, these sub-layers do not need to have a rigid
+schema where geometry type and attributes are the same for all features. A single
+sub-layer may have multiple geometry types in a single tile or have some attributes
+defined only at particular zoom levels.
+
+Vector tile layer currently does not use the concept of data providers that other
+layer types use. The process of rendering of vector tiles looks like this:
+
++--------+ +------+ +---------+
+| DATA | | RAW | | DECODED |
+| | --> LOADER --> | | --> DECODER --> | | --> RENDERER
+| SOURCE | | TILE | | TILE |
++--------+ +------+ +---------+
+
+Data source is a place from where tiles are fetched from (URL for HTTP access, local
+files, MBTiles file, GeoPackage file or others. Loader (QgsVectorTileLoader) class
+takes care of loading data from the data source. The "raw tile" data is just a blob
+(QByteArray) that is encoded in some way. There are multiple ways how vector tiles
+are encoded just like there are different formats how to store images. For example,
+tiles can be encoded using Mapbox Vector Tiles (MVT) format or in GeoJSON. Decoder
+(QgsVectorTileDecoder) takes care of decoding raw tile data into QgsFeature objects.
+A decoded tile is essentially an array of vector features for each sub-layer found
+in the tile - this is what vector tile renderer (QgsVectorTileRenderer) expects
+and does the map rendering.
+
+To construct a vector tile layer, it is best to use QgsDataSourceUri class and set
+the following parameters to get a valid encoded URI:
+- "type" - what kind of data source will be used
+- "url" - URL or path of the data source (specific to each data source type, see below)
+
+Currently supported data source types:
+- "xyz" - the "url" should be a template like http://example.com/{z}/{x}/{y}.pbf where
+{x},{y},{z} will be replaced by tile coordinates
+- "mbtiles" - tiles read from a MBTiles file (a SQLite database)
+
+Currently supported decoders:
+- MVT - following Mapbox Vector Tiles specification
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsvectortilelayer.h"
+%End
+ public:
+ explicit QgsVectorTileLayer( const QString &path = QString(), const QString &baseName = QString() );
+%Docstring
+Constructs a new vector tile layer
+%End
+ ~QgsVectorTileLayer();
+
+
+ virtual QgsVectorTileLayer *clone() const /Factory/;
+
+
+ virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) /Factory/;
+
+ virtual bool readXml( const QDomNode &layerNode, QgsReadWriteContext &context );
+
+ virtual bool writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const;
+
+ virtual bool readSymbology( const QDomNode &node, QString &errorMessage,
+ QgsReadWriteContext &context, StyleCategories categories = AllStyleCategories );
+
+ virtual bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context,
+ StyleCategories categories = AllStyleCategories ) const;
+
+ virtual void setTransformContext( const QgsCoordinateTransformContext &transformContext );
+
+
+ QString sourceType() const;
+%Docstring
+Returns type of the data source
+%End
+ QString sourcePath() const;
+%Docstring
+Returns URL/path of the data source (syntax different to each data source type)
+%End
+
+ int sourceMinZoom() const;
+%Docstring
+Returns minimum zoom level at which source has any valid tiles (negative = unconstrained)
+%End
+ int sourceMaxZoom() const;
+%Docstring
+Returns maximum zoom level at which source has any valid tiles (negative = unconstrained)
+%End
+
+
+ void setRenderer( QgsVectorTileRenderer *r /Transfer/ );
+%Docstring
+Sets renderer for the map layer.
+
+.. note::
+
+ Takes ownership of the passed renderer
+%End
+ QgsVectorTileRenderer *renderer() const;
+%Docstring
+Returns currently assigned renderer
+%End
+
+ void setTileBorderRenderingEnabled( bool enabled );
+%Docstring
+Sets whether to render also borders of tiles (useful for debugging)
+%End
+ bool isTileBorderRenderingEnabled() const;
+%Docstring
+Returns whether to render also borders of tiles (useful for debugging)
+%End
+
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/vectortile/qgsvectortilelayer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/auto_generated/vectortile/qgsvectortilerenderer.sip.in b/python/core/auto_generated/vectortile/qgsvectortilerenderer.sip.in
new file mode 100644
index 000000000000..de0b552eca55
--- /dev/null
+++ b/python/core/auto_generated/vectortile/qgsvectortilerenderer.sip.in
@@ -0,0 +1,133 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/vectortile/qgsvectortilerenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+
+class QgsVectorTileRendererData
+{
+%Docstring
+Contains decoded features of a single vector tile and any other data necessary
+for rendering of it.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsvectortilerenderer.h"
+%End
+ public:
+ explicit QgsVectorTileRendererData( QgsTileXYZ id );
+%Docstring
+Constructs the object
+%End
+
+ QgsTileXYZ id() const;
+%Docstring
+Returns coordinates of the tile
+%End
+
+ void setTilePolygon( QPolygon polygon );
+%Docstring
+Sets polygon of the tile
+%End
+ QPolygon tilePolygon() const;
+%Docstring
+Returns polygon (made out of four corners of the tile) in screen coordinates calculated from render context
+%End
+
+ QStringList layers() const;
+%Docstring
+Returns list of layer names present in the tile
+%End
+ QVector layerFeatures( const QString &layerName ) const;
+%Docstring
+Returns list of all features within a single sub-layer
+%End
+
+};
+
+class QgsVectorTileRenderer
+{
+%Docstring
+Abstract base class for all vector tile renderer implementations.
+
+For rendering it is expected that client code calls:
+1. startRender() to prepare renderer
+2. renderTile() for each tile
+3. stopRender() to clean up renderer and free resources
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsvectortilerenderer.h"
+%End
+%ConvertToSubClassCode
+
+ const QString type = sipCpp->type();
+
+ if ( type == QStringLiteral( "basic" ) )
+ sipType = sipType_QgsVectorTileBasicRenderer;
+ else
+ sipType = 0;
+%End
+ public:
+ virtual ~QgsVectorTileRenderer();
+
+ virtual QString type() const = 0;
+%Docstring
+Returns unique type name of the renderer implementation
+%End
+
+ virtual QgsVectorTileRenderer *clone() const = 0 /Factory/;
+%Docstring
+Returns a clone of the renderer
+%End
+
+ virtual void startRender( QgsRenderContext &context, int tileZoom, const QgsTileRange &tileRange ) = 0;
+%Docstring
+Initializes rendering. It should be paired with a stopRender() call.
+%End
+
+
+ virtual void stopRender( QgsRenderContext &context ) = 0;
+%Docstring
+Finishes rendering and cleans up any resources
+%End
+
+ virtual void renderTile( const QgsVectorTileRendererData &tile, QgsRenderContext &context ) = 0;
+%Docstring
+Renders given vector tile. Must be called between startRender/stopRender.
+%End
+
+ virtual void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const = 0;
+%Docstring
+Writes renderer's properties to given XML element
+%End
+ virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) = 0;
+%Docstring
+Reads renderer's properties from given XML element
+%End
+ virtual void resolveReferences( const QgsProject &project );
+%Docstring
+Resolves references to other objects - second phase of loading - after readXml()
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/core/vectortile/qgsvectortilerenderer.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip
index ccaf34747479..0ad2cdb021c7 100644
--- a/python/core/core_auto.sip
+++ b/python/core/core_auto.sip
@@ -30,6 +30,7 @@
%Include auto_generated/qgscolorscheme.sip
%Include auto_generated/qgscolorschemeregistry.sip
%Include auto_generated/qgsconditionalstyle.sip
+%Include auto_generated/qgsconnectionregistry.sip
%Include auto_generated/qgscoordinateformatter.sip
%Include auto_generated/qgscoordinatereferencesystem.sip
%Include auto_generated/qgscoordinatetransform.sip
@@ -77,6 +78,7 @@
%Include auto_generated/qgsfieldproxymodel.sip
%Include auto_generated/qgsfields.sip
%Include auto_generated/qgsfiledownloader.sip
+%Include auto_generated/qgsfilefiltergenerator.sip
%Include auto_generated/qgsfileutils.sip
%Include auto_generated/qgsfontutils.sip
%Include auto_generated/qgsgeometryoptions.sip
@@ -165,6 +167,7 @@
%Include auto_generated/qgsproviderconnectionmodel.sip
%Include auto_generated/qgsprovidermetadata.sip
%Include auto_generated/qgsproviderregistry.sip
+%Include auto_generated/qgsproxyfeaturesink.sip
%Include auto_generated/qgsproxyprogresstask.sip
%Include auto_generated/qgspythonrunner.sip
%Include auto_generated/qgsrange.sip
@@ -172,6 +175,7 @@
%Include auto_generated/qgsreadwritelocker.sip
%Include auto_generated/qgsrelation.sip
%Include auto_generated/qgsrelationcontext.sip
+%Include auto_generated/qgsremappingproxyfeaturesink.sip
%Include auto_generated/qgsrelationmanager.sip
%Include auto_generated/qgsrenderchecker.sip
%Include auto_generated/qgsrendercontext.sip
@@ -199,9 +203,11 @@
%Include auto_generated/qgstemporalnavigationobject.sip
%Include auto_generated/qgstemporalproperty.sip
%Include auto_generated/qgstemporalrangeobject.sip
+%Include auto_generated/qgstemporalutils.sip
%Include auto_generated/qgstessellator.sip
%Include auto_generated/qgstestutils.sip
%Include auto_generated/qgstextrenderer.sip
+%Include auto_generated/qgstiles.sip
%Include auto_generated/qgstolerance.sip
%Include auto_generated/qgstracer.sip
%Include auto_generated/qgstrackedvectorlayertools.sip
@@ -339,6 +345,7 @@
%Include auto_generated/layertree/qgslayertreenode.sip
%Include auto_generated/layertree/qgslayertreeregistrybridge.sip
%Include auto_generated/layertree/qgslayertreeutils.sip
+%Include auto_generated/layertree/qgslegendpatchshape.sip
%Include auto_generated/layout/qgsabstractlayoutiterator.sip
%Include auto_generated/layout/qgsabstractreportsection.sip
%Include auto_generated/layout/qgslayout.sip
@@ -360,6 +367,7 @@
%Include auto_generated/layout/qgslayoutitemmapgrid.sip
%Include auto_generated/layout/qgslayoutitemmapitem.sip
%Include auto_generated/layout/qgslayoutitemmapoverview.sip
+%Include auto_generated/layout/qgslayoutitemmarker.sip
%Include auto_generated/layout/qgslayoutitemnodeitem.sip
%Include auto_generated/layout/qgslayoutitempage.sip
%Include auto_generated/layout/qgslayoutitempicture.sip
@@ -374,6 +382,7 @@
%Include auto_generated/layout/qgslayoutmeasurementconverter.sip
%Include auto_generated/layout/qgslayoutmodel.sip
%Include auto_generated/layout/qgslayoutmultiframe.sip
+%Include auto_generated/layout/qgslayoutnortharrowhandler.sip
%Include auto_generated/layout/qgslayoutobject.sip
%Include auto_generated/layout/qgslayoutpagecollection.sip
%Include auto_generated/layout/qgslayoutpoint.sip
@@ -400,12 +409,15 @@
%Include auto_generated/locator/qgslocatormodelbridge.sip
%Include auto_generated/mesh/qgsmesh3daveraging.sip
%Include auto_generated/mesh/qgsmeshdataprovider.sip
+%Include auto_generated/mesh/qgsmeshdataset.sip
%Include auto_generated/mesh/qgsmeshlayer.sip
%Include auto_generated/mesh/qgsmeshlayerinterpolator.sip
%Include auto_generated/mesh/qgsmeshrenderersettings.sip
%Include auto_generated/mesh/qgsmeshspatialindex.sip
%Include auto_generated/mesh/qgsmeshtimesettings.sip
%Include auto_generated/mesh/qgsmeshtracerenderer.sip
+%Include auto_generated/mesh/qgsmeshlayertemporalproperties.sip
+%Include auto_generated/mesh/qgsmeshdataprovidertemporalcapabilities.sip
%Include auto_generated/metadata/qgsabstractmetadatabase.sip
%Include auto_generated/metadata/qgslayermetadata.sip
%Include auto_generated/metadata/qgslayermetadataformatter.sip
@@ -415,6 +427,7 @@
%Include auto_generated/numericformats/qgsbearingnumericformat.sip
%Include auto_generated/numericformats/qgscurrencynumericformat.sip
%Include auto_generated/numericformats/qgsfallbacknumericformat.sip
+%Include auto_generated/numericformats/qgsfractionnumericformat.sip
%Include auto_generated/numericformats/qgsnumericformat.sip
%Include auto_generated/numericformats/qgsnumericformatregistry.sip
%Include auto_generated/numericformats/qgspercentagenumericformat.sip
@@ -424,6 +437,7 @@
%Include auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip
%Include auto_generated/processing/models/qgsprocessingmodelcomment.sip
%Include auto_generated/processing/models/qgsprocessingmodelcomponent.sip
+%Include auto_generated/processing/models/qgsprocessingmodelgroupbox.sip
%Include auto_generated/processing/models/qgsprocessingmodeloutput.sip
%Include auto_generated/processing/models/qgsprocessingmodelparameter.sip
%Include auto_generated/processing/qgsprocessing.sip
@@ -455,6 +469,7 @@
%Include auto_generated/raster/qgsrasterbandstats.sip
%Include auto_generated/raster/qgsrasterblock.sip
%Include auto_generated/raster/qgsrasterchecker.sip
+%Include auto_generated/raster/qgsrastercontourrenderer.sip
%Include auto_generated/raster/qgsrasterdataprovider.sip
%Include auto_generated/raster/qgsrasterdataprovidertemporalcapabilities.sip
%Include auto_generated/raster/qgsrasterdrawer.sip
@@ -483,10 +498,13 @@
%Include auto_generated/raster/qgssinglebandgrayrenderer.sip
%Include auto_generated/raster/qgssinglebandpseudocolorrenderer.sip
%Include auto_generated/scalebar/qgsdoubleboxscalebarrenderer.sip
+%Include auto_generated/scalebar/qgshollowscalebarrenderer.sip
%Include auto_generated/scalebar/qgsnumericscalebarrenderer.sip
%Include auto_generated/scalebar/qgsscalebarrenderer.sip
+%Include auto_generated/scalebar/qgsscalebarrendererregistry.sip
%Include auto_generated/scalebar/qgsscalebarsettings.sip
%Include auto_generated/scalebar/qgssingleboxscalebarrenderer.sip
+%Include auto_generated/scalebar/qgssteppedlinescalebarrenderer.sip
%Include auto_generated/scalebar/qgsticksscalebarrenderer.sip
%Include auto_generated/symbology/qgs25drenderer.sip
%Include auto_generated/symbology/qgsarrowsymbollayer.sip
@@ -524,6 +542,9 @@
%Include auto_generated/validity/qgsabstractvaliditycheck.sip
%Include auto_generated/validity/qgsvaliditycheckcontext.sip
%Include auto_generated/validity/qgsvaliditycheckregistry.sip
+%Include auto_generated/vectortile/qgsvectortilebasicrenderer.sip
+%Include auto_generated/vectortile/qgsvectortilelayer.sip
+%Include auto_generated/vectortile/qgsvectortilerenderer.sip
%Include auto_generated/gps/qgsqtlocationconnection.sip
%Include auto_generated/gps/qgsgpsconnectionregistry.sip
%Include auto_generated/symbology/qgsmasksymbollayer.sip
diff --git a/python/gui/auto_additions/qgsexpressionbuilderwidget.py b/python/gui/auto_additions/qgsexpressionbuilderwidget.py
new file mode 100644
index 000000000000..80927bf17a8f
--- /dev/null
+++ b/python/gui/auto_additions/qgsexpressionbuilderwidget.py
@@ -0,0 +1,3 @@
+# The following has been generated automatically from src/gui/qgsexpressionbuilderwidget.h
+QgsExpressionBuilderWidget.Flag.baseClass = QgsExpressionBuilderWidget
+Flag = QgsExpressionBuilderWidget # dirty hack since SIP seems to introduce the flags in module
diff --git a/python/gui/auto_additions/qgsfieldmappingmodel.py b/python/gui/auto_additions/qgsfieldmappingmodel.py
new file mode 100644
index 000000000000..8267c8ef2ef7
--- /dev/null
+++ b/python/gui/auto_additions/qgsfieldmappingmodel.py
@@ -0,0 +1,11 @@
+# The following has been generated automatically from src/gui/qgsfieldmappingmodel.h
+# monkey patching scoped based enum
+QgsFieldMappingModel.ColumnDataIndex.SourceExpression.__doc__ = "Expression"
+QgsFieldMappingModel.ColumnDataIndex.DestinationName.__doc__ = "Destination field name"
+QgsFieldMappingModel.ColumnDataIndex.DestinationType.__doc__ = "Destination field QVariant::Type casted to (int)"
+QgsFieldMappingModel.ColumnDataIndex.DestinationLength.__doc__ = "Destination field length"
+QgsFieldMappingModel.ColumnDataIndex.DestinationPrecision.__doc__ = "Destination field precision"
+QgsFieldMappingModel.ColumnDataIndex.DestinationConstraints.__doc__ = "Destination field constraints"
+QgsFieldMappingModel.ColumnDataIndex.__doc__ = 'The ColumnDataIndex enum represents the column index for the view\n\n' + '* ``SourceExpression``: ' + QgsFieldMappingModel.ColumnDataIndex.SourceExpression.__doc__ + '\n' + '* ``DestinationName``: ' + QgsFieldMappingModel.ColumnDataIndex.DestinationName.__doc__ + '\n' + '* ``DestinationType``: ' + QgsFieldMappingModel.ColumnDataIndex.DestinationType.__doc__ + '\n' + '* ``DestinationLength``: ' + QgsFieldMappingModel.ColumnDataIndex.DestinationLength.__doc__ + '\n' + '* ``DestinationPrecision``: ' + QgsFieldMappingModel.ColumnDataIndex.DestinationPrecision.__doc__ + '\n' + '* ``DestinationConstraints``: ' + QgsFieldMappingModel.ColumnDataIndex.DestinationConstraints.__doc__
+# --
+QgsFieldMappingModel.ColumnDataIndex.baseClass = QgsFieldMappingModel
diff --git a/python/gui/auto_generated/attributetable/qgsattributetablefiltermodel.sip.in b/python/gui/auto_generated/attributetable/qgsattributetablefiltermodel.sip.in
index e3960387ef0b..348da96c73a9 100644
--- a/python/gui/auto_generated/attributetable/qgsattributetablefiltermodel.sip.in
+++ b/python/gui/auto_generated/attributetable/qgsattributetablefiltermodel.sip.in
@@ -196,6 +196,14 @@ Returns -1 if none is defined.
Set the attribute table configuration to control which fields are shown,
in which order they are shown as well as if and where an action column
is shown.
+%End
+
+ void setFilterExpression( const QgsExpression &expression, const QgsExpressionContext &context );
+%Docstring
+Set the ``expression`` and the ``context`` to be stored in case of the features
+need to be filtered again (like on filter or on main model data change).
+
+.. versionadded:: 3.10.3
%End
signals:
@@ -234,10 +242,21 @@ selection state of the feature in case selected features are to be shown on top.
public slots:
- void extentsChanged();
+ void extentsChanged();
%Docstring
Is called upon every change of the visible extents on the map canvas.
When a change is signalled, the filter is updated and invalidated if needed.
+
+.. deprecated:: QGIS 3.10.3
+ - made private as reloadVisible()
+%End
+
+ void filterFeatures();
+%Docstring
+Updates the filtered features in the filter model. It is called when the data of the
+main table or the filter expression changed.
+
+.. versionadded:: 3.10.3
%End
};
diff --git a/python/gui/auto_generated/attributetable/qgsdualview.sip.in b/python/gui/auto_generated/attributetable/qgsdualview.sip.in
index 958356388ec4..401d434d7c9b 100644
--- a/python/gui/auto_generated/attributetable/qgsdualview.sip.in
+++ b/python/gui/auto_generated/attributetable/qgsdualview.sip.in
@@ -122,11 +122,22 @@ filter restrictions
:return: Number of features
%End
- void setFilteredFeatures( const QgsFeatureIds &filteredFeatures );
+ void setFilteredFeatures( const QgsFeatureIds &filteredFeatures );
%Docstring
Set a list of currently visible features
:param filteredFeatures: A list of feature ids
+
+.. deprecated::
+ since filterFeatures is handled in the attribute filter model itself
+%End
+
+ void filterFeatures( const QgsExpression &filterExpression, const QgsExpressionContext &context );
+%Docstring
+Sets the expression and Updates the filtered features in the filter model.
+It is called when the filter expression changed.
+
+.. versionadded:: 3.10.3
%End
QgsFeatureIds filteredFeatures();
diff --git a/python/gui/auto_generated/qgstemporalvcrdockwidget.sip.in b/python/gui/auto_generated/devtools/qgsdevtoolwidget.sip.in
similarity index 71%
rename from python/gui/auto_generated/qgstemporalvcrdockwidget.sip.in
rename to python/gui/auto_generated/devtools/qgsdevtoolwidget.sip.in
index 9834319ed182..4c8806f91686 100644
--- a/python/gui/auto_generated/qgstemporalvcrdockwidget.sip.in
+++ b/python/gui/auto_generated/devtools/qgsdevtoolwidget.sip.in
@@ -1,42 +1,36 @@
/************************************************************************
* This file has been generated automatically from *
* *
- * src/gui/qgstemporalvcrdockwidget.h *
+ * src/gui/devtools/qgsdevtoolwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
-
-
-
-
-class QgsTemporalVcrDockWidget : QgsDockWidget
+class QgsDevToolWidget : QgsPanelWidget
{
%Docstring
-The QgsTemporalVcrDockWidget class
+A panel widget that can be shown in the developer tools panel.
.. versionadded:: 3.14
%End
%TypeHeaderCode
-#include "qgstemporalvcrdockwidget.h"
+#include "qgsdevtoolwidget.h"
%End
public:
- QgsTemporalVcrDockWidget( const QString &name, QWidget *parent = 0 );
+ QgsDevToolWidget( QWidget *parent /TransferThis/ = 0 );
%Docstring
-Constructor for QgsTemporalVcrDockWidget
+Constructor for QgsDevToolWidget, with the specified ``parent`` widget.
%End
- ~QgsTemporalVcrDockWidget();
-
};
/************************************************************************
* This file has been generated automatically from *
* *
- * src/gui/qgstemporalvcrdockwidget.h *
+ * src/gui/devtools/qgsdevtoolwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
diff --git a/python/gui/auto_generated/devtools/qgsdevtoolwidgetfactory.sip.in b/python/gui/auto_generated/devtools/qgsdevtoolwidgetfactory.sip.in
new file mode 100644
index 000000000000..5f1fee84f3f8
--- /dev/null
+++ b/python/gui/auto_generated/devtools/qgsdevtoolwidgetfactory.sip.in
@@ -0,0 +1,73 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/devtools/qgsdevtoolwidgetfactory.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsDevToolWidgetFactory
+{
+%Docstring
+Factory class for creating custom developer/debugging tool pages
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsdevtoolwidgetfactory.h"
+%End
+ public:
+
+ QgsDevToolWidgetFactory( const QString &title = QString(), const QIcon &icon = QIcon() );
+%Docstring
+Constructor for a QgsDevToolWidgetFactory with the specified ``title`` and ``icon``.
+%End
+
+ virtual ~QgsDevToolWidgetFactory();
+
+ virtual QIcon icon() const;
+%Docstring
+Returns the icon that will be shown in the tool in the panel.
+
+.. seealso:: :py:func:`setIcon`
+%End
+
+ void setIcon( const QIcon &icon );
+%Docstring
+Sets the ``icon`` for the factory object, which will be shown for the tool in the panel.
+
+.. seealso:: :py:func:`icon`
+%End
+
+ virtual QString title() const;
+%Docstring
+Returns the (translated) title of the tool.
+
+.. seealso:: :py:func:`setTitle`
+%End
+
+ void setTitle( const QString &title );
+%Docstring
+Set the translated ``title`` for the tool.
+%End
+
+ virtual QgsDevToolWidget *createWidget( QWidget *parent = 0 ) const = 0 /Factory/;
+%Docstring
+Factory function to create the widget on demand as needed by the dock.
+
+The ``parent`` argument gives the correct parent for the newly created widget.
+%End
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/devtools/qgsdevtoolwidgetfactory.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/editorwidgets/qgsrelationreferencewidget.sip.in b/python/gui/auto_generated/editorwidgets/qgsrelationreferencewidget.sip.in
index b62a2e075ea9..adbdf46c02e8 100644
--- a/python/gui/auto_generated/editorwidgets/qgsrelationreferencewidget.sip.in
+++ b/python/gui/auto_generated/editorwidgets/qgsrelationreferencewidget.sip.in
@@ -77,6 +77,14 @@ Returns the related feature foreign keys
%End
void setEditorContext( const QgsAttributeEditorContext &context, QgsMapCanvas *canvas, QgsMessageBar *messageBar );
+%Docstring
+Sets the editor ``context``
+
+.. note::
+
+ if context cadDockWidget is null, it won't be possible to digitize
+ the geometry of a referenced feature from this widget
+%End
bool embedForm();
%Docstring
diff --git a/python/gui/auto_generated/layertree/qgslayertreeviewdefaultactions.sip.in b/python/gui/auto_generated/layertree/qgslayertreeviewdefaultactions.sip.in
index 4a951e3baedb..53074355552c 100644
--- a/python/gui/auto_generated/layertree/qgslayertreeviewdefaultactions.sip.in
+++ b/python/gui/auto_generated/layertree/qgslayertreeviewdefaultactions.sip.in
@@ -80,6 +80,14 @@ Action to zoom to selected features of a vector layer
.. seealso:: :py:func:`moveToTop`
.. versionadded:: 3.2
+%End
+
+ QAction *actionMoveToBottom( QObject *parent = 0 ) /Factory/;
+%Docstring
+
+.. seealso:: :py:func:`moveToBottom`
+
+.. versionadded:: 3.14
%End
QAction *actionGroupSelected( QObject *parent = 0 ) /Factory/;
@@ -139,6 +147,14 @@ Moves selected layer(s) and/or group(s) to the top of the layer panel
or the top of the group if the layer/group is placed within a group.
.. versionadded:: 3.2
+%End
+
+ void moveToBottom();
+%Docstring
+Moves selected layer(s) and/or group(s) to the bottom of the layer panel
+or the bottom of the group if the layer/group is placed within a group.
+
+.. versionadded:: 3.14
%End
void groupSelected();
diff --git a/python/gui/auto_generated/numericformats/qgsnumericformatwidget.sip.in b/python/gui/auto_generated/numericformats/qgsnumericformatwidget.sip.in
index ad4afd249a91..22f2a7d78232 100644
--- a/python/gui/auto_generated/numericformats/qgsnumericformatwidget.sip.in
+++ b/python/gui/auto_generated/numericformats/qgsnumericformatwidget.sip.in
@@ -7,6 +7,7 @@
************************************************************************/
+
class QgsNumericFormatWidget : QgsPanelWidget
{
%Docstring
@@ -231,6 +232,33 @@ Constructor for QgsScientificNumericFormatWidget, initially showing the specifie
};
+
+class QgsFractionNumericFormatWidget : QgsNumericFormatWidget
+{
+%Docstring
+A widget which allow control over the properties of a :py:class:`QgsFractionNumericFormat`.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsnumericformatwidget.h"
+%End
+ public:
+
+ QgsFractionNumericFormatWidget( const QgsNumericFormat *format, QWidget *parent /TransferThis/ = 0 );
+%Docstring
+Constructor for QgsFractionNumericFormatWidget, initially showing the specified ``format``.
+%End
+ ~QgsFractionNumericFormatWidget();
+
+ virtual void setFormat( QgsNumericFormat *format );
+
+
+ virtual QgsNumericFormat *format() /Factory/;
+
+
+};
/************************************************************************
* This file has been generated automatically from *
* *
diff --git a/python/gui/auto_generated/processing/models/qgsmodelarrowitem.sip.in b/python/gui/auto_generated/processing/models/qgsmodelarrowitem.sip.in
index 086354f8705c..dc91803fb423 100644
--- a/python/gui/auto_generated/processing/models/qgsmodelarrowitem.sip.in
+++ b/python/gui/auto_generated/processing/models/qgsmodelarrowitem.sip.in
@@ -28,8 +28,8 @@ A link arrow item for use in the model designer.
%End
public:
- QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Qt::Edge startEdge, int startIndex,
- QgsModelComponentGraphicItem *endItem, Qt::Edge endEdge, int endIndex );
+ QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Qt::Edge startEdge, int startIndex, bool startIsOutgoing,
+ QgsModelComponentGraphicItem *endItem, Qt::Edge endEdge, int endIndex, bool endIsIncoming );
%Docstring
Constructor for QgsModelArrowItem, with the specified ``parent`` item.
diff --git a/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in b/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in
index 06faf9a94756..cc2d319119d3 100644
--- a/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in
+++ b/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in
@@ -106,11 +106,6 @@ Shows a preview of moving the item from its stored position by ``dx``, ``dy``.
void setItemRect( QRectF rect );
%Docstring
Sets a new scene ``rect`` for the item.
-%End
-
- void previewItemRectChange( QRectF rect );
-%Docstring
-Shows a preview of setting a new ``rect`` for the item.
%End
virtual void mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event );
@@ -125,6 +120,8 @@ Shows a preview of setting a new ``rect`` for the item.
virtual QRectF boundingRect() const;
+ virtual bool contains( const QPointF &point ) const;
+
virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0 );
@@ -162,7 +159,7 @@ Returns the number of link points associated with the component on the specified
Returns the text to use for the link point with the specified ``index`` on the specified ``edge``.
%End
- QPointF linkPoint( Qt::Edge edge, int index ) const;
+ QPointF linkPoint( Qt::Edge edge, int index, bool incoming ) const;
%Docstring
Returns the location of the link point with the specified ``index`` on the specified ``edge``.
%End
@@ -279,6 +276,11 @@ Returns the label text color for the item for the specified ``state``.
virtual Qt::PenStyle strokeStyle( State state ) const;
%Docstring
Returns the stroke style to use while rendering the outline of the item.
+%End
+
+ virtual Qt::Alignment titleAlignment() const;
+%Docstring
+Returns the title alignment
%End
virtual QPicture iconPicture() const;
@@ -392,6 +394,16 @@ Ownership of ``child`` is transferred to the item.
virtual bool canDeleteComponent();
+ void setResults( const QVariantMap &results );
+%Docstring
+Sets the results obtained for this child algorithm for the last model execution through the dialog.
+%End
+
+ void setInputs( const QVariantMap &inputs );
+%Docstring
+Sets the inputs used for this child algorithm for the last model execution through the dialog.
+%End
+
protected:
virtual QColor fillColor( State state ) const;
@@ -529,6 +541,64 @@ Ownership of ``output`` is transferred to the item.
};
+
+class QgsModelGroupBoxGraphicItem : QgsModelComponentGraphicItem
+{
+%Docstring
+A graphic item representing a group box in the model designer.
+
+.. warning::
+
+ Not stable API
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsmodelcomponentgraphicitem.h"
+%End
+ public:
+
+ QgsModelGroupBoxGraphicItem( QgsProcessingModelGroupBox *box /Transfer/,
+ QgsProcessingModelAlgorithm *model,
+ QGraphicsItem *parent /TransferThis/ );
+%Docstring
+Constructor for QgsModelGroupBoxGraphicItem for the specified group ``box``, with the specified ``parent`` item.
+
+The ``model`` argument specifies the associated processing model. Ownership of ``model`` is not transferred, and
+it must exist for the lifetime of this object.
+
+Ownership of ``output`` is transferred to the item.
+%End
+ ~QgsModelGroupBoxGraphicItem();
+ virtual void contextMenuEvent( QGraphicsSceneContextMenuEvent *event );
+
+ virtual bool canDeleteComponent();
+
+ protected:
+
+ virtual QColor fillColor( State state ) const;
+
+ virtual QColor strokeColor( State state ) const;
+
+ virtual QColor textColor( State state ) const;
+
+ virtual Qt::PenStyle strokeStyle( State state ) const;
+
+ virtual Qt::Alignment titleAlignment() const;
+
+ virtual void updateStoredComponentPosition( const QPointF &pos, const QSizeF &size );
+
+
+ protected slots:
+
+ virtual void deleteComponent();
+
+ virtual void editComponent();
+
+};
+
+
/************************************************************************
* This file has been generated automatically from *
* *
diff --git a/python/gui/auto_generated/processing/models/qgsmodeldesignerdialog.sip.in b/python/gui/auto_generated/processing/models/qgsmodeldesignerdialog.sip.in
index 09e8a502733e..b14695e69ffd 100644
--- a/python/gui/auto_generated/processing/models/qgsmodeldesignerdialog.sip.in
+++ b/python/gui/auto_generated/processing/models/qgsmodeldesignerdialog.sip.in
@@ -95,6 +95,16 @@ Checks if the model can current be saved, and returns ``True`` if it can.
Checks if there are unsaved changes in the model, and if so, prompts the user to save them.
Returns ``False`` if the cancel option was selected
+%End
+
+ void setLastRunChildAlgorithmResults( const QVariantMap &results );
+%Docstring
+Sets the results of child algorithms for the last run of the model through the designer window.
+%End
+
+ void setLastRunChildAlgorithmInputs( const QVariantMap &inputs );
+%Docstring
+Sets the inputs for child algorithms for the last run of the model through the designer window.
%End
};
diff --git a/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in b/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in
index cdd7a07bbc70..701e1e2d5999 100644
--- a/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in
+++ b/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in
@@ -29,6 +29,7 @@ QGraphicsScene subclass representing the model designer.
enum ZValues
{
+ GroupBox,
ArrowLink,
ModelComponent,
MouseHandles,
@@ -94,6 +95,16 @@ Returns list of selected component items.
QgsModelComponentGraphicItem *componentItemAt( QPointF position ) const;
%Docstring
Returns the topmost component item at a specified ``position``.
+%End
+
+ QgsModelComponentGraphicItem *groupBoxItem( const QString &uuid );
+%Docstring
+Returns the graphic item corresponding to the specified group box ``uuid``.
+%End
+
+ void selectAll();
+%Docstring
+Selects all the components in the scene.
%End
void deselectAll();
@@ -107,6 +118,16 @@ not correctly emit signals to allow the scene's model to update.
void setSelectedItem( QgsModelComponentGraphicItem *item );
%Docstring
Clears any selected items and sets ``item`` as the current selection.
+%End
+
+ void setChildAlgorithmResults( const QVariantMap &results );
+%Docstring
+Sets the results for child algorithms for the last model execution.
+%End
+
+ void setChildAlgorithmInputs( const QVariantMap &inputs );
+%Docstring
+Sets the inputs for child algorithms for the last model execution.
%End
signals:
@@ -142,7 +163,7 @@ If ``None``, no item is selected.
Creates a new graphic item for a model parameter.
%End
- virtual QgsModelComponentGraphicItem *createChildAlgGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelChildAlgorithm *child ) const /Factory/;
+ virtual QgsModelChildAlgorithmGraphicItem *createChildAlgGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelChildAlgorithm *child ) const /Factory/;
%Docstring
Creates a new graphic item for a model child algorithm.
%End
@@ -158,6 +179,10 @@ Creates a new graphic item for a model output.
Creates a new graphic item for a model comment.
%End
+ QgsModelComponentGraphicItem *createGroupBoxGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelGroupBox *box ) const /Factory/;
+%Docstring
+Creates a new graphic item for a model group box.
+%End
};
diff --git a/python/gui/auto_generated/processing/models/qgsmodelgraphicsview.sip.in b/python/gui/auto_generated/processing/models/qgsmodelgraphicsview.sip.in
index 757be4b4c6b6..5b3846320e3a 100644
--- a/python/gui/auto_generated/processing/models/qgsmodelgraphicsview.sip.in
+++ b/python/gui/auto_generated/processing/models/qgsmodelgraphicsview.sip.in
@@ -78,6 +78,13 @@ Starts a macro command, containing a group of interactions in the view.
void endMacroCommand();
%Docstring
Ends a macro command, containing a group of interactions in the view.
+%End
+
+ public slots:
+
+ void snapSelected();
+%Docstring
+Snaps the selected items to the grid.
%End
signals:
diff --git a/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in b/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in
index 08a57654acf6..9a0e6379241e 100644
--- a/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in
+++ b/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in
@@ -11,7 +11,7 @@
-class QgsProcessingAlgorithmDialogBase : QDialog
+class QgsProcessingAlgorithmDialogBase : QDialog, QgsProcessingParametersGenerator
{
%Docstring
Base class for processing algorithm dialogs.
@@ -56,7 +56,7 @@ Returns the algorithm running in the dialog.
.. seealso:: :py:func:`setAlgorithm`
%End
- void setMainWidget( QWidget *widget /Transfer/ );
+ void setMainWidget( QgsPanelWidget *widget /Transfer/ );
%Docstring
Sets the main ``widget`` for the dialog, usually a panel for configuring algorithm parameters.
@@ -97,11 +97,6 @@ Returns the results returned by the algorithm executed.
%Docstring
Creates a new processing feedback object, automatically connected to the appropriate
slots in this dialog.
-%End
-
- virtual QVariantMap getParameterValues() const;
-%Docstring
-Returns the parameter values for the algorithm to run in the dialog.
%End
void saveLogToFile( const QString &path, LogFormat format = FormatPlainText );
@@ -180,6 +175,11 @@ Opens a dialog allowing users to save the current log contents.
Copies the current log contents to the clipboard.
.. versionadded:: 3.2
+%End
+
+ void showParameters();
+%Docstring
+Switches the dialog to the parameters page.
%End
protected:
@@ -195,6 +195,11 @@ Returns the dialog's run button.
QPushButton *cancelButton();
%Docstring
Returns the dialog's cancel button.
+%End
+
+ QPushButton *changeParametersButton();
+%Docstring
+Returns the dialog's change parameters button.
%End
QDialogButtonBox *buttonBox();
@@ -219,6 +224,11 @@ Sets whether the algorithm was executed through the dialog.
.. seealso:: :py:func:`wasExecuted`
.. seealso:: :py:func:`setResults`
+%End
+
+ void setExecutedAnyResult( bool executedAnyResult );
+%Docstring
+Sets whether the algorithm was executed through the dialog (no matter the result).
%End
void setResults( const QVariantMap &results );
@@ -238,6 +248,29 @@ Displays an info ``message`` in the dialog's log.
void resetGui();
%Docstring
Resets the dialog's gui, ready for another algorithm execution.
+%End
+
+ virtual void resetAdditionalGui();
+%Docstring
+For subclasses to register their own GUI controls to be reset, ready
+for another algorithm execution.
+%End
+
+ void updateRunButtonVisibility();
+%Docstring
+Sets visibility for mutually exclusive buttons Run and Change Parameters.
+%End
+
+ void blockControlsWhileRunning();
+%Docstring
+Blocks run and changeParameters buttons and parameters tab while the
+algorithm is running.
+%End
+
+ virtual void blockAdditionalControlsWhileRunning();
+%Docstring
+For subclasses to register their own GUI controls to be blocked while
+the algorithm is running.
%End
QgsMessageBar *messageBar();
@@ -261,6 +294,15 @@ by the dialog. Ownership of ``task`` is transferred to the dialog.
Formats an input ``string`` for display in the log tab.
.. versionadded:: 3.0.1
+%End
+
+ signals:
+
+ void algorithmFinished( bool successful, const QVariantMap &result );
+%Docstring
+Emitted whenever an algorithm has finished executing in the dialog.
+
+.. versionadded:: 3.14
%End
protected slots:
diff --git a/python/gui/auto_generated/processing/qgsprocessingmaplayercombobox.sip.in b/python/gui/auto_generated/processing/qgsprocessingmaplayercombobox.sip.in
index f9eac231a335..a7e5b2fcd587 100644
--- a/python/gui/auto_generated/processing/qgsprocessingmaplayercombobox.sip.in
+++ b/python/gui/auto_generated/processing/qgsprocessingmaplayercombobox.sip.in
@@ -10,7 +10,6 @@
-
class QgsProcessingMapLayerComboBox : QWidget
{
%Docstring
@@ -28,7 +27,7 @@ Processing map layer combo box.
%End
public:
- QgsProcessingMapLayerComboBox( const QgsProcessingParameterDefinition *parameter, QWidget *parent = 0 );
+ QgsProcessingMapLayerComboBox( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = 0 );
%Docstring
Constructor for QgsProcessingMapLayerComboBox, with the specified ``parameter`` definition.
%End
@@ -80,17 +79,36 @@ Returns the current value of the widget.
.. seealso:: :py:func:`setValue`
%End
- signals:
+ void setWidgetContext( const QgsProcessingParameterWidgetContext &context );
+%Docstring
+Sets the ``context`` in which the widget is shown.
- void valueChanged();
+.. versionadded:: 3.14
+%End
+
+ void setEditable( bool editable );
%Docstring
-Emitted whenever the value is changed in the widget.
+Sets whether the combo box value can be freely edited.
+
+.. seealso:: :py:func:`isEditable`
+
+.. versionadded:: 3.14
%End
- void triggerFileSelection();
+ bool isEditable() const;
%Docstring
-Emitted when the widget has triggered a file selection operation (to be
-handled in Python for now).
+Returns whether the combo box value can be freely edited.
+
+.. seealso:: :py:func:`setEditable`
+
+.. versionadded:: 3.14
+%End
+
+ signals:
+
+ void valueChanged();
+%Docstring
+Emitted whenever the value is changed in the widget.
%End
protected:
diff --git a/python/gui/auto_generated/processing/qgsprocessingmodelerparameterwidget.sip.in b/python/gui/auto_generated/processing/qgsprocessingmodelerparameterwidget.sip.in
index e16ea67b3c4b..ed8f030e0753 100644
--- a/python/gui/auto_generated/processing/qgsprocessingmodelerparameterwidget.sip.in
+++ b/python/gui/auto_generated/processing/qgsprocessingmodelerparameterwidget.sip.in
@@ -114,7 +114,49 @@ Sets the current ``value`` for the parameter.
.. seealso:: :py:func:`value`
%End
- virtual QgsProcessingModelChildParameterSource value() const;
+ void setWidgetValue( const QList< QgsProcessingModelChildParameterSource > &values );
+%Docstring
+Sets the current ``values`` for the parameter.
+
+.. seealso:: :py:func:`value`
+
+.. versionadded:: 3.14
+%End
+
+ void setToModelOutput( const QString &value );
+%Docstring
+Sets the widget to a model output, for destination parameters only.
+
+.. seealso:: :py:func:`isModelOutput`
+
+.. seealso:: :py:func:`modelOutputName`
+
+.. versionadded:: 3.14
+%End
+
+ bool isModelOutput() const;
+%Docstring
+Returns ``True`` if the widget is set to the model output mode.
+
+.. seealso:: :py:func:`setToModelOutput`
+
+.. seealso:: :py:func:`modelOutputName`
+
+.. versionadded:: 3.14
+%End
+
+ QString modelOutputName() const;
+%Docstring
+Returns the model output name, if isModelOutput() is ``True``.
+
+.. seealso:: :py:func:`setToModelOutput`
+
+.. seealso:: :py:func:`isModelOutput`
+
+.. versionadded:: 3.14
+%End
+
+ virtual QVariant value() const;
%Docstring
Returns the current value of the parameter.
diff --git a/python/gui/auto_generated/processing/qgsprocessingmultipleselectiondialog.sip.in b/python/gui/auto_generated/processing/qgsprocessingmultipleselectiondialog.sip.in
index e8d57d7cfc81..a8e1563595a8 100644
--- a/python/gui/auto_generated/processing/qgsprocessingmultipleselectiondialog.sip.in
+++ b/python/gui/auto_generated/processing/qgsprocessingmultipleselectiondialog.sip.in
@@ -10,11 +10,101 @@
+class QgsProcessingMultipleSelectionPanelWidget : QgsPanelWidget
+{
+%Docstring
+A panel widget for selection of multiple options from a fixed list of options.
+
+.. note::
+
+ Not stable API
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingmultipleselectiondialog.h"
+%End
+ public:
+
+ QgsProcessingMultipleSelectionPanelWidget( const QVariantList &availableOptions = QVariantList(),
+ const QVariantList &selectedOptions = QVariantList(),
+ QWidget *parent /TransferThis/ = 0 );
+%Docstring
+Constructor for QgsProcessingMultipleSelectionPanelWidget.
+
+The ``availableOptions`` list specifies the list of standard known options for the parameter,
+whilst the ``selectedOptions`` list specifies which options should be initially selected.
+
+The ``selectedOptions`` list may contain extra options which are not present in ``availableOptions``,
+in which case they will be also added as existing options within the dialog.
+%End
+
+ void setValueFormatter( SIP_PYCALLABLE );
+%Docstring
+Sets a callback function to use when encountering an invalid geometry and
+%End
+%MethodCode
+
+ Py_BEGIN_ALLOW_THREADS
+
+ sipCpp->setValueFormatter( [a0]( const QVariant &v )->QString
+ {
+ QString res;
+ SIP_BLOCK_THREADS
+ PyObject *s = sipCallMethod( NULL, a0, "D", &v, sipType_QVariant, NULL );
+ int state;
+ int sipIsError = 0;
+ QString *t1 = reinterpret_cast( sipConvertToType( s, sipType_QString, 0, SIP_NOT_NONE, &state, &sipIsError ) );
+ if ( sipIsError == 0 )
+ {
+ res = QString( *t1 );
+ }
+ sipReleaseType( t1, sipType_QString, state );
+ SIP_UNBLOCK_THREADS
+ return res;
+ } );
+
+ Py_END_ALLOW_THREADS
+%End
+
+
+ QVariantList selectedOptions() const;
+%Docstring
+Returns the ordered list of selected options.
+%End
+
+ QDialogButtonBox *buttonBox();
+%Docstring
+Returns the widget's button box.
+%End
+
+ signals:
+
+ void acceptClicked();
+%Docstring
+Emitted when the accept button is clicked.
+%End
+
+ void selectionChanged();
+%Docstring
+Emitted when the selection changes in the widget.
+%End
+
+ protected:
+
+ void addOption( const QVariant &value, const QString &title, bool selected, bool updateExistingTitle = false );
+%Docstring
+Adds a new option to the widget.
+%End
+
+};
+
class QgsProcessingMultipleSelectionDialog : QDialog
{
%Docstring
-Dialog for configuration of a matrix (fixed table) parameter.
+A dialog for selection of multiple options from a fixed list of options.
.. note::
@@ -32,7 +122,7 @@ Dialog for configuration of a matrix (fixed table) parameter.
const QVariantList &selectedOptions = QVariantList(),
QWidget *parent /TransferThis/ = 0, Qt::WindowFlags flags = 0 );
%Docstring
-Constructor for QgsProcessingMultipleSelectionDialog.
+Constructor for :py:class:`QgsProcessingMultipleSelectionPanelWidget`.
The ``availableOptions`` list specifies the list of standard known options for the parameter,
whilst the ``selectedOptions`` list specifies which options should be initially selected.
@@ -79,6 +169,83 @@ Returns the ordered list of selected options.
};
+class QgsProcessingMultipleInputPanelWidget : QgsProcessingMultipleSelectionPanelWidget
+{
+%Docstring
+A panel widget for selection of multiple inputs from a fixed list of options.
+
+.. note::
+
+ Not stable API
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingmultipleselectiondialog.h"
+%End
+ public:
+
+ QgsProcessingMultipleInputPanelWidget( const QgsProcessingParameterMultipleLayers *parameter,
+ const QVariantList &selectedOptions,
+ const QList< QgsProcessingModelChildParameterSource > &modelSources,
+ QgsProcessingModelAlgorithm *model = 0,
+ QWidget *parent /TransferThis/ = 0 );
+%Docstring
+Constructor for QgsProcessingMultipleInputPanelWidget.
+%End
+
+ void setProject( QgsProject *project );
+%Docstring
+Sets the project associated with the widget.
+%End
+
+};
+
+
+class QgsProcessingMultipleInputDialog : QDialog
+{
+%Docstring
+A dialog for selection of multiple layer inputs.
+
+.. note::
+
+ Not stable API
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingmultipleselectiondialog.h"
+%End
+ public:
+
+ QgsProcessingMultipleInputDialog( const QgsProcessingParameterMultipleLayers *parameter,
+ const QVariantList &selectedOptions,
+ const QList< QgsProcessingModelChildParameterSource > &modelSources,
+ QgsProcessingModelAlgorithm *model = 0,
+ QWidget *parent /TransferThis/ = 0, Qt::WindowFlags flags = 0 );
+%Docstring
+Constructor for QgsProcessingMultipleInputDialog.
+
+The ``selectedOptions`` list may contain extra options which are not present in ``availableOptions``,
+in which case they will be also added as existing options within the dialog.
+%End
+
+ QVariantList selectedOptions() const;
+%Docstring
+Returns the ordered list of selected options.
+%End
+
+ void setProject( QgsProject *project );
+%Docstring
+Sets the project associated with the dialog.
+%End
+
+};
+
+
+
/************************************************************************
* This file has been generated automatically from *
* *
diff --git a/python/gui/auto_generated/processing/qgsprocessingoutputdestinationwidget.sip.in b/python/gui/auto_generated/processing/qgsprocessingoutputdestinationwidget.sip.in
new file mode 100644
index 000000000000..121855ee78da
--- /dev/null
+++ b/python/gui/auto_generated/processing/qgsprocessingoutputdestinationwidget.sip.in
@@ -0,0 +1,108 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/processing/qgsprocessingoutputdestinationwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsProcessingLayerOutputDestinationWidget : QWidget
+{
+%Docstring
+A widget which allows users to select the destination path for an output style Processing parameter.
+
+.. note::
+
+ Not stable API
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingoutputdestinationwidget.h"
+%End
+ public:
+
+ QgsProcessingLayerOutputDestinationWidget( const QgsProcessingDestinationParameter *parameter, bool defaultSelection, QWidget *parent /TransferThis/ = 0 );
+%Docstring
+Constructor for QgsProcessingLayerOutputDestinationWidget, associated with the specified ``parameter``.
+%End
+
+ bool outputIsSkipped() const;
+%Docstring
+Returns ``True`` if the output is set to be skipped.
+%End
+
+ void setValue( const QVariant &value );
+%Docstring
+Sets the ``value`` to show in the widget.
+%End
+
+ QVariant value() const;
+%Docstring
+Returns the widgets current value.
+%End
+
+ void setWidgetContext( const QgsProcessingParameterWidgetContext &context );
+%Docstring
+Sets the ``context`` in which the widget is shown, e.g., the
+parent model algorithm, a linked map canvas, and other relevant information which allows the widget
+to fine-tune its behavior.
+%End
+
+ void setContext( QgsProcessingContext *context );
+%Docstring
+Sets the processing ``context`` in which this widget is being shown.
+%End
+
+ void registerProcessingParametersGenerator( QgsProcessingParametersGenerator *generator );
+%Docstring
+Registers a Processing parameters ``generator`` class that will be used to retrieve
+algorithm parameters for the widget when required.
+
+.. versionadded:: 3.14
+%End
+
+ void addOpenAfterRunningOption();
+%Docstring
+Adds the "Open output file after running" option to the widget.
+%End
+
+ bool openAfterRunning() const;
+%Docstring
+Returns ``True`` if the widget has the "Open output file after running" option checked.
+%End
+
+ signals:
+
+ void skipOutputChanged( bool skipped );
+%Docstring
+Emitted whenever the "skip output" option is toggled in the widget.
+%End
+
+ void destinationChanged();
+%Docstring
+Emitted whenever the destination value is changed in the widget.
+%End
+ protected:
+
+ virtual void dragEnterEvent( QDragEnterEvent *event );
+
+ virtual void dragLeaveEvent( QDragLeaveEvent *event );
+
+ virtual void dropEvent( QDropEvent *event );
+
+
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/processing/qgsprocessingoutputdestinationwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in b/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in
index db3d6457d0e5..8fb9dad414a8 100644
--- a/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in
+++ b/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in
@@ -157,6 +157,24 @@ Returns the comments for the parameter.
.. seealso:: :py:func:`setComments`
+.. versionadded:: 3.14
+%End
+
+ void setCommentColor( const QColor &color );
+%Docstring
+Sets the color for the comments for the parameter.
+
+.. seealso:: :py:func:`commentColor`
+
+.. versionadded:: 3.14
+%End
+
+ QColor commentColor() const;
+%Docstring
+Returns the color for the comments for the parameter.
+
+.. seealso:: :py:func:`setCommentColor`
+
.. versionadded:: 3.14
%End
diff --git a/python/gui/auto_generated/processing/qgsprocessingparameterswidget.sip.in b/python/gui/auto_generated/processing/qgsprocessingparameterswidget.sip.in
new file mode 100644
index 000000000000..2baa2b1e02a1
--- /dev/null
+++ b/python/gui/auto_generated/processing/qgsprocessingparameterswidget.sip.in
@@ -0,0 +1,58 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/processing/qgsprocessingparameterswidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+class QgsProcessingParametersWidget : QgsPanelWidget, QgsProcessingParametersGenerator
+{
+%Docstring
+A widget which allows users to select the value for the parameters for an algorithm.
+
+.. note::
+
+ Not stable API
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingparameterswidget.h"
+%End
+ public:
+
+ QgsProcessingParametersWidget( const QgsProcessingAlgorithm *algorithm, QWidget *parent /TransferThis/ = 0 );
+%Docstring
+Constructor for QgsProcessingParametersWidget, for the specified ``algorithm``.
+%End
+
+ const QgsProcessingAlgorithm *algorithm() const;
+
+ protected:
+
+ virtual void initWidgets();
+
+ void addParameterWidget( const QgsProcessingParameterDefinition *parameter, QWidget *widget /Transfer/ );
+ void addParameterLabel( const QgsProcessingParameterDefinition *parameter, QWidget *label /Transfer/ );
+
+ void addOutputLabel( QWidget *label /Transfer/ );
+ void addOutputWidget( QWidget *widget /Transfer/ );
+
+ void addExtraWidget( QWidget *widget /Transfer/ );
+
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/processing/qgsprocessingparameterswidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in b/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in
index 0fea68b611a7..d287b837df93 100644
--- a/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in
+++ b/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in
@@ -256,6 +256,7 @@ of this model.
RoleAlgorithmName,
RoleAlgorithmShortDescription,
RoleAlgorithmTags,
+ RoleProviderFlags,
};
QgsProcessingToolboxModel( QObject *parent /TransferThis/ = 0, QgsProcessingRegistry *registry = 0,
@@ -447,6 +448,8 @@ Returns the current filter string, if set.
virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;
+ virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
+
};
diff --git a/python/gui/auto_generated/processing/qgsprocessingwidgetwrapper.sip.in b/python/gui/auto_generated/processing/qgsprocessingwidgetwrapper.sip.in
index 44f778c2aff3..823e562a2994 100644
--- a/python/gui/auto_generated/processing/qgsprocessingwidgetwrapper.sip.in
+++ b/python/gui/auto_generated/processing/qgsprocessingwidgetwrapper.sip.in
@@ -36,6 +36,30 @@ return a pointer to a context which they have already created and own.
virtual ~QgsProcessingContextGenerator();
};
+class QgsProcessingParametersGenerator
+{
+%Docstring
+
+An interface for objects which can create sets of parameter values for processing algorithms.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsprocessingwidgetwrapper.h"
+%End
+ public:
+
+ virtual QVariantMap createProcessingParameters() = 0;
+%Docstring
+This method needs to be reimplemented in all classes which implement this interface
+and return a algorithm parameters.
+%End
+
+ virtual ~QgsProcessingParametersGenerator();
+};
+
+
class QgsProcessingParameterWidgetContext
{
%Docstring
@@ -88,6 +112,24 @@ to the user.
.. seealso:: :py:func:`setMessageBar`
+.. versionadded:: 3.12
+%End
+
+ void setBrowserModel( QgsBrowserGuiModel *model );
+%Docstring
+Sets the browser ``model`` associated with the widget. This will usually be the shared app instance of the browser model
+
+.. seealso:: :py:func:`browserModel`
+
+.. versionadded:: 3.14
+%End
+
+ QgsBrowserGuiModel *browserModel() const;
+%Docstring
+Returns the browser model associated with the widget.
+
+.. seealso:: :py:func:`setBrowserModel`
+
.. versionadded:: 3.12
%End
@@ -142,6 +184,24 @@ Sets the child algorithm ``id`` within the model which the parameter widget is a
.. seealso:: :py:func:`modelChildAlgorithmId`
.. seealso:: :py:func:`setModel`
+%End
+
+ QgsMapLayer *activeLayer() const;
+%Docstring
+Returns the current active layer.
+
+.. seealso:: :py:func:`setActiveLayer`
+
+.. versionadded:: 3.14
+%End
+
+ void setActiveLayer( QgsMapLayer *layer );
+%Docstring
+Sets the current active ``layer``.
+
+.. seealso:: :py:func:`activeLayer`
+
+.. versionadded:: 3.14
%End
};
@@ -266,12 +326,26 @@ current value is associated with.
Returns the current value of the parameter.
.. seealso:: :py:func:`setParameterValue`
+%End
+
+ virtual QVariantMap customProperties() const;
+%Docstring
+Returns any custom properties set by the wrapper.
%End
void registerProcessingContextGenerator( QgsProcessingContextGenerator *generator );
%Docstring
Registers a Processing context ``generator`` class that will be used to retrieve
a Processing context for the wrapper when required.
+%End
+
+ void registerProcessingParametersGenerator( QgsProcessingParametersGenerator *generator );
+%Docstring
+Registers a Processing parameters ``generator`` class that will be used to retrieve
+algorithm parameters for the wrapper when required (e.g. when a wrapper needs access
+to other parameter's values).
+
+.. versionadded:: 3.14
%End
virtual void postInitialize( const QList< QgsAbstractProcessingParameterWidgetWrapper * > &wrappers );
@@ -477,10 +551,10 @@ values which can be used as values for the parameter.
.. seealso:: :py:func:`compatibleDataTypes`
%End
- virtual QList< int > compatibleDataTypes() const = 0;
+ virtual QList< int > compatibleDataTypes( const QgsProcessingParameterDefinition *parameter ) const;
%Docstring
Returns a list of compatible Processing data types for inputs
-for this parameter.
+for this widget for the specified ``parameter``.
In order to determine the available sources for the parameter in a model
the types returned by this method are checked. The returned list corresponds
diff --git a/python/gui/auto_generated/qgisinterface.sip.in b/python/gui/auto_generated/qgisinterface.sip.in
index 06c7c2dbffb5..e48ba3753358 100644
--- a/python/gui/auto_generated/qgisinterface.sip.in
+++ b/python/gui/auto_generated/qgisinterface.sip.in
@@ -578,6 +578,20 @@ Statistical summary action.
virtual QAction *actionShowAllLayers() = 0;
virtual QAction *actionHideSelectedLayers() = 0;
+ virtual QAction *actionToggleSelectedLayers() = 0;
+%Docstring
+Returns the Toggle Selected Layers action.
+
+.. versionadded:: 3.14
+%End
+
+ virtual QAction *actionToggleSelectedLayersIndependently() = 0;
+%Docstring
+Returns the Toggle Selected Layers Independently action.
+
+.. versionadded:: 3.14
+%End
+
virtual QAction *actionHideDeselectedLayers() = 0;
%Docstring
Returns the Hide Deselected Layers action.
@@ -1118,15 +1132,38 @@ Unregister a previously registered tab in the options dialog.
.. versionadded:: 3.0
%End
- virtual void registerCustomDropHandler( QgsCustomDropHandler *handler ) = 0;
+ virtual void registerDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) = 0;
%Docstring
-Register a new custom drop handler.
+Register a new tool in the development/debugging tools dock.
.. note::
Ownership of the factory is not transferred, and the factory must
be unregistered when plugin is unloaded.
+.. seealso:: :py:func:`unregisterDevToolWidgetFactory`
+
+.. versionadded:: 3.14
+%End
+
+ virtual void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) = 0;
+%Docstring
+Unregister a previously registered tool factory from the development/debugging tools dock.
+
+.. seealso:: :py:func:`registerDevToolWidgetFactory`
+
+.. versionadded:: 3.14
+%End
+
+ virtual void registerCustomDropHandler( QgsCustomDropHandler *handler ) = 0;
+%Docstring
+Register a new custom drop ``handler``.
+
+.. note::
+
+ Ownership of ``handler`` is not transferred, and the handler must
+ be unregistered when plugin is unloaded.
+
.. seealso:: :py:class:`QgsCustomDropHandler`
.. seealso:: :py:func:`unregisterCustomDropHandler`
@@ -1136,13 +1173,40 @@ Register a new custom drop handler.
virtual void unregisterCustomDropHandler( QgsCustomDropHandler *handler ) = 0;
%Docstring
-Unregister a previously registered custom drop handler.
+Unregister a previously registered custom drop ``handler``.
.. seealso:: :py:class:`QgsCustomDropHandler`
.. seealso:: :py:func:`registerCustomDropHandler`
.. versionadded:: 3.0
+%End
+
+ virtual void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) = 0;
+%Docstring
+Register a new custom project open ``handler``.
+
+.. note::
+
+ Ownership of ``handler`` is not transferred, and the handler must
+ be unregistered when plugin is unloaded.
+
+.. seealso:: :py:class:`QgsCustomProjectOpenHandler`
+
+.. seealso:: :py:func:`unregisterCustomProjectOpenHandler`
+
+.. versionadded:: 3.14
+%End
+
+ virtual void unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) = 0;
+%Docstring
+Unregister a previously registered custom project open ``handler``.
+
+.. seealso:: :py:class:`QgsCustomDropHandler`
+
+.. seealso:: :py:func:`registerCustomProjectOpenHandler`
+
+.. versionadded:: 3.14
%End
virtual void registerCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler ) = 0;
@@ -1151,7 +1215,7 @@ Register a new custom drop ``handler`` for layout windows.
.. note::
- Ownership of the factory is not transferred, and the factory must
+ Ownership of ``handler`` is not transferred, and the handler must
be unregistered when plugin is unloaded.
.. seealso:: :py:class:`QgsLayoutCustomDropHandler`
diff --git a/python/gui/auto_generated/qgscheckablecombobox.sip.in b/python/gui/auto_generated/qgscheckablecombobox.sip.in
index 1374b8b80abc..fbdb1a28caf4 100644
--- a/python/gui/auto_generated/qgscheckablecombobox.sip.in
+++ b/python/gui/auto_generated/qgscheckablecombobox.sip.in
@@ -69,6 +69,14 @@ no items selected.
Returns currently checked items.
.. seealso:: :py:func:`setCheckedItems`
+%End
+
+ QVariantList checkedItemsData() const;
+%Docstring
+Returns userData (stored in the Qt.UserRole) associated with
+currently checked items.
+
+.. seealso:: :py:func:`checkedItems`
%End
Qt::CheckState itemCheckState( int index ) const;
diff --git a/python/gui/auto_generated/qgscolorbutton.sip.in b/python/gui/auto_generated/qgscolorbutton.sip.in
index 13f086f34bf4..2f048047cf0a 100644
--- a/python/gui/auto_generated/qgscolorbutton.sip.in
+++ b/python/gui/auto_generated/qgscolorbutton.sip.in
@@ -210,11 +210,12 @@ Sets the string to use for the "no color" option in the button's drop-down menu.
dialog
%End
- void setShowNull( bool showNull );
+ void setShowNull( bool showNull, const QString &nullString = QString() );
%Docstring
Sets whether a set to null (clear) option is shown in the button's drop-down menu.
:param showNull: set to ``True`` to show a null option
+:param nullString: translated string to use for the null option. If not set, a default "Clear Color" string will be used.
.. seealso:: :py:func:`showNull`
diff --git a/python/gui/auto_generated/qgscustomprojectopenhandler.sip.in b/python/gui/auto_generated/qgscustomprojectopenhandler.sip.in
new file mode 100644
index 000000000000..4ffcb7948925
--- /dev/null
+++ b/python/gui/auto_generated/qgscustomprojectopenhandler.sip.in
@@ -0,0 +1,70 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgscustomprojectopenhandler.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+class QgsCustomProjectOpenHandler : QObject
+{
+%Docstring
+Abstract base class that may be implemented to handle new project file types within
+the QGIS application.
+
+This interface allows extending the QGIS interface by adding support for opening additional
+(non QGS/QGZ) project files, e.g. allowing plugins to add support for opening other
+vendor project formats (such as ArcGIS MXD documents or MapInfo WOR workspaces).
+
+Handler implementations should indicate the file types they support via their filters()
+implementation, and then implement handleProjectOpen() to open the associated files.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgscustomprojectopenhandler.h"
+%End
+ public:
+
+ virtual bool handleProjectOpen( const QString &file ) = 0;
+%Docstring
+Called when the specified project ``file`` has been opened within QGIS. If ``True``
+is returned, then the handler has accepted this file and it should not
+be further processed (e.g. by other QgsCustomProjectOpenHandler).
+
+It it is the subclasses' responsibility to ignore file types it cannot handle
+by returning ``False`` for these.
+
+The base class implementation does nothing.
+%End
+
+ virtual QStringList filters() const = 0;
+%Docstring
+Returns file filters associated with this handler, e.g. "MXD Documents (*.mxd)", "MapInfo Workspaces (*.wor)".
+
+Each individual filter should be reflected as one entry in the returned list.
+%End
+
+ virtual bool createDocumentThumbnailAfterOpen() const;
+%Docstring
+Returns ``True`` if a document thumbnail should automatically be created after opening the project.
+
+The default behavior is to return ``False``.
+%End
+
+ virtual QIcon icon() const;
+%Docstring
+Returns a custom icon used to represent this handler.
+%End
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgscustomprojectopenhandler.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/qgsdatabaseschemacombobox.sip.in b/python/gui/auto_generated/qgsdatabaseschemacombobox.sip.in
index df658c5a338d..bbd752c2a901 100644
--- a/python/gui/auto_generated/qgsdatabaseschemacombobox.sip.in
+++ b/python/gui/auto_generated/qgsdatabaseschemacombobox.sip.in
@@ -10,6 +10,7 @@
+
class QgsDatabaseSchemaComboBox : QWidget
{
%Docstring
@@ -43,6 +44,20 @@ Constructor for QgsDatabaseSchemaComboBox, for the specified ``provider`` and ``
Constructor for QgsDatabaseSchemaComboBox, for the specified ``connection``.
Ownership of ``connection`` is transferred to the combobox.
+%End
+
+ void setAllowEmptySchema( bool allowEmpty );
+%Docstring
+Sets whether an optional empty schema ("not set") option is present in the combobox.
+
+.. seealso:: :py:func:`allowEmptySchema`
+%End
+
+ bool allowEmptySchema() const;
+%Docstring
+Returns ``True`` if the combobox allows the empty schema ("not set") choice.
+
+.. seealso:: :py:func:`setAllowEmptySchema`
%End
QString currentSchema() const;
diff --git a/python/gui/auto_generated/qgsdatabasetablecombobox.sip.in b/python/gui/auto_generated/qgsdatabasetablecombobox.sip.in
index b875b73cdfa8..2770d33a0308 100644
--- a/python/gui/auto_generated/qgsdatabasetablecombobox.sip.in
+++ b/python/gui/auto_generated/qgsdatabasetablecombobox.sip.in
@@ -10,6 +10,7 @@
+
class QgsDatabaseTableComboBox : QWidget
{
%Docstring
@@ -47,6 +48,20 @@ Constructor for QgsDatabaseTableComboBox, for the specified ``connection``.
The optional ``schema`` argument can be used to restrict the listed tables to a specific schema.
Ownership of ``connection`` is transferred to the combobox.
+%End
+
+ void setAllowEmptyTable( bool allowEmpty );
+%Docstring
+Sets whether an optional empty table ("not set") option is present in the combobox.
+
+.. seealso:: :py:func:`allowEmptyTable`
+%End
+
+ bool allowEmptyTable() const;
+%Docstring
+Returns ``True`` if the combobox allows the empty table ("not set") choice.
+
+.. seealso:: :py:func:`setAllowEmptyTable`
%End
QString currentTable() const;
diff --git a/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in b/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in
index c232885fcad2..24772d5dc177 100644
--- a/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in
+++ b/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in
@@ -9,10 +9,10 @@
-class QgsDataSourceSelectDialog: QDialog
+class QgsDataSourceSelectWidget: QgsPanelWidget
{
%Docstring
-The QgsDataSourceSelectDialog class embeds the browser view to
+The QgsDataSourceSelectWidget class embeds the browser view to
select an existing data source.
By default any layer type can be chosen, the valid layer
@@ -23,7 +23,7 @@ directly from the constructor.
To retrieve the selected data source, uri() can be called and it
will return a (possibly invalid) QgsMimeDataUtils.Uri.
-.. versionadded:: 3.6
+.. versionadded:: 3.14
%End
%TypeHeaderCode
@@ -31,12 +31,12 @@ will return a (possibly invalid) QgsMimeDataUtils.Uri.
%End
public:
- QgsDataSourceSelectDialog( QgsBrowserGuiModel *browserModel = 0,
+ QgsDataSourceSelectWidget( QgsBrowserGuiModel *browserModel = 0,
bool setFilterByLayerType = false,
QgsMapLayerType layerType = QgsMapLayerType::VectorLayer,
QWidget *parent = 0 );
%Docstring
-Constructs a QgsDataSourceSelectDialog, optionally filtering by layer type
+Constructs a QgsDataSourceSelectWidget, optionally filtering by layer type
:param browserModel: an existing browser model (typically from app), if ``None`` an instance will be created
:param setFilterByLayerType: activates filtering by layer type
@@ -45,7 +45,7 @@ Constructs a QgsDataSourceSelectDialog, optionally filtering by layer type
%End
- ~QgsDataSourceSelectDialog();
+ ~QgsDataSourceSelectWidget();
void setLayerTypeFilter( QgsMapLayerType layerType );
%Docstring
@@ -90,6 +90,103 @@ Apply filter to the model
%Docstring
Scroll to last selected index and expand it's children
+%End
+
+ signals:
+
+ void validationChanged( bool isValid );
+%Docstring
+This signal is emitted whenever the validation status of the widget changes.
+
+:param isValid: ``True`` if the current status of the widget is valid
+%End
+
+ void selectionChanged();
+%Docstring
+Emitted when the current selection changes in the widget.
+%End
+
+ void itemTriggered( const QgsMimeDataUtils::Uri &uri );
+%Docstring
+Emitted when an item is triggered, e.g. via a double-click.
+%End
+
+};
+
+
+class QgsDataSourceSelectDialog: QDialog
+{
+%Docstring
+The QgsDataSourceSelectDialog class embeds the browser view to
+select an existing data source.
+
+By default any layer type can be chosen, the valid layer
+type can be restricted by setting a layer type filter with
+setLayerTypeFilter(layerType) or by activating the filter
+directly from the constructor.
+
+To retrieve the selected data source, uri() can be called and it
+will return a (possibly invalid) QgsMimeDataUtils.Uri.
+
+.. versionadded:: 3.6
+%End
+
+%TypeHeaderCode
+#include "qgsdatasourceselectdialog.h"
+%End
+ public:
+
+ QgsDataSourceSelectDialog( QgsBrowserGuiModel *browserModel = 0,
+ bool setFilterByLayerType = false,
+ QgsMapLayerType layerType = QgsMapLayerType::VectorLayer,
+ QWidget *parent = 0 );
+%Docstring
+Constructs a QgsDataSourceSelectDialog, optionally filtering by layer type
+
+:param browserModel: an existing browser model (typically from app), if ``None`` an instance will be created
+:param setFilterByLayerType: activates filtering by layer type
+:param layerType: sets the layer type filter, this is in effect only if filtering by layer type is also active
+:param parent: the object
+%End
+
+ void setLayerTypeFilter( QgsMapLayerType layerType );
+%Docstring
+Sets layer type filter to ``layerType`` and activates the filtering
+%End
+
+ void setDescription( const QString &description );
+%Docstring
+Sets a description label
+
+:param description: a description string
+
+.. note::
+
+ the description will be displayed at the bottom of the dialog
+
+.. versionadded:: 3.8
+%End
+
+ QgsMimeDataUtils::Uri uri() const;
+%Docstring
+Returns the (possibly invalid) uri of the selected data source
+%End
+
+ void showFilterWidget( bool visible );
+%Docstring
+Show/hide filter widget
+%End
+ void setFilterSyntax( QAction * );
+%Docstring
+Sets filter syntax
+%End
+ void setCaseSensitive( bool caseSensitive );
+%Docstring
+Sets filter case sensitivity
+%End
+ void setFilter();
+%Docstring
+Apply filter to the model
%End
};
diff --git a/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in b/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in
index 3e1d5052dce1..ee186e536341 100644
--- a/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in
+++ b/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in
@@ -10,104 +10,58 @@
-class QgsExpressionItem : QStandardItem
+
+
+
+class QgsExpressionBuilderWidget : QWidget
{
%Docstring
-An expression item that can be used in the QgsExpressionBuilderWidget tree.
+A reusable widget that can be used to build a expression string.
+See QgsExpressionBuilderDialog for example of usage.
%End
%TypeHeaderCode
#include "qgsexpressionbuilderwidget.h"
%End
public:
- enum ItemType
+
+ enum Flag
{
- Header,
- Field,
- ExpressionNode
+ LoadNothing,
+ LoadRecent,
+ LoadUserExpressions,
+ LoadAll,
};
+ typedef QFlags Flags;
- QgsExpressionItem( const QString &label,
- const QString &expressionText,
- const QString &helpText,
- QgsExpressionItem::ItemType itemType = ExpressionNode );
-
- QgsExpressionItem( const QString &label,
- const QString &expressionText,
- QgsExpressionItem::ItemType itemType = ExpressionNode );
- QString getExpressionText() const;
- QString getHelpText() const;
+ QgsExpressionBuilderWidget( QWidget *parent /TransferThis/ = 0 );
%Docstring
-Gets the help text that is associated with this expression item.
-
-:return: The help text.
+Create a new expression builder widget with an optional parent.
%End
+ ~QgsExpressionBuilderWidget();
- void setHelpText( const QString &helpText );
+ void init( const QgsExpressionContext &context = QgsExpressionContext(), const QString &recentCollection = QStringLiteral( "generic" ), const Flags &flags = LoadAll );
%Docstring
-Set the help text for the current item
+Initialize without any layer
-.. note::
-
- The help text can be set as a html string.
+.. versionadded:: 3.14
%End
- QgsExpressionItem::ItemType getItemType() const;
+ void initWithLayer( QgsVectorLayer *layer, const QgsExpressionContext &context = QgsExpressionContext(), const QString &recentCollection = QStringLiteral( "generic" ), const Flags &flags = LoadAll );
%Docstring
-Gets the type of expression item, e.g., header, field, ExpressionNode.
+Initialize with a layer
-:return: The QgsExpressionItem.ItemType
+.. versionadded:: 3.14
%End
- static const int CUSTOM_SORT_ROLE;
- static const int ITEM_TYPE_ROLE;
- static const int SEARCH_TAGS_ROLE;
-
-};
-
-class QgsExpressionItemSearchProxy : QSortFilterProxyModel
-{
+ void initWithFields( const QgsFields &fields, const QgsExpressionContext &context = QgsExpressionContext(), const QString &recentCollection = QStringLiteral( "generic" ), const Flags &flags = LoadAll );
%Docstring
-Search proxy used to filter the QgsExpressionBuilderWidget tree.
-The default search for a tree model only searches top level this will handle one
-level down
-%End
-
-%TypeHeaderCode
-#include "qgsexpressionbuilderwidget.h"
-%End
- public:
- QgsExpressionItemSearchProxy();
-
- virtual bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const;
-
-
- protected:
-
- virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;
-
-};
-
-
-class QgsExpressionBuilderWidget : QWidget
-{
-%Docstring
-A reusable widget that can be used to build a expression string.
-See QgsExpressionBuilderDialog for example of usage.
-%End
+Initialize with given fields without any layer
-%TypeHeaderCode
-#include "qgsexpressionbuilderwidget.h"
-%End
- public:
-
- QgsExpressionBuilderWidget( QWidget *parent /TransferThis/ = 0 );
-%Docstring
-Create a new expression builder widget with an optional parent.
+.. versionadded:: 3.14
%End
- ~QgsExpressionBuilderWidget();
void setLayer( QgsVectorLayer *layer );
%Docstring
@@ -118,23 +72,33 @@ Sets layer in order to get the fields and values
this needs to be called before calling loadFieldNames().
%End
- void loadFieldNames();
+ QgsVectorLayer *layer() const;
%Docstring
-Loads all the field names from the layer.
-@remarks Should this really be public couldn't we just do this for the user?
+Returns the current layer or a None.
%End
- void loadFieldNames( const QgsFields &fields );
+ void loadFieldNames();
+%Docstring
+
+.. deprecated:: QGIS 3.14
+ this is now done automatically
+%End
- void loadFieldsAndValues( const QMap &fieldValues );
+ void loadFieldNames( const QgsFields &fields );
%Docstring
-Loads field names and values from the specified map.
-.. note::
+.. deprecated:: QGIS 3.14
+ use expressionTree()->loadFieldNames() instead
+%End
- The field values must be quoted appropriately if they are strings.
+ void loadFieldsAndValues( const QMap &fieldValues ) /Deprecated/;
+%Docstring
+Loads field names and values from the specified map.
.. versionadded:: 2.12
+
+.. deprecated:: QGIS 3.14
+ this will not do anything, use setLayer() instead
%End
void setGeomCalculator( const QgsDistanceArea &da );
@@ -187,7 +151,7 @@ preview result and for populating the list of available functions and variables.
void setExpressionContext( const QgsExpressionContext &context );
%Docstring
Sets the expression context for the widget. The context is used for the expression
-preview result and for populating the list of available functions and variables.
+preview result and to populate the list of available functions and variables.
:param context: expression context
@@ -196,58 +160,63 @@ preview result and for populating the list of available functions and variables.
.. versionadded:: 2.12
%End
- void registerItem( const QString &group, const QString &label, const QString &expressionText,
- const QString &helpText = QString(),
- QgsExpressionItem::ItemType type = QgsExpressionItem::ExpressionNode,
- bool highlightedItem = false, int sortOrder = 1,
- QIcon icon = QIcon(),
- const QStringList &tags = QStringList() );
+ bool isExpressionValid();
%Docstring
-Registers a node item for the expression builder.
-
-:param group: The group the item will be show in the tree view. If the group doesn't exist it will be created.
-:param label: The label that is show to the user for the item in the tree.
-:param expressionText: The text that is inserted into the expression area when the user double clicks on the item.
-:param helpText: The help text that the user will see when item is selected.
-:param type: The type of the expression item.
-:param highlightedItem: set to ``True`` to make the item highlighted, which inserts a bold copy of the item at the top level
-:param sortOrder: sort ranking for item
-:param icon: custom icon to show for item
-:param tags: tags to find function
+Returns if the expression is valid
%End
- bool isExpressionValid();
-
- void saveToRecent( const QString &collection = "generic" );
+ void saveToRecent( const QString &collection = "generic" ) /Deprecated/;
%Docstring
Adds the current expression to the given ``collection``.
By default it is saved to the collection "generic".
+
+.. deprecated:: QGIS 3.14
+ use expressionTree()->saveRecent() instead
%End
- void loadRecent( const QString &collection = QStringLiteral( "generic" ) );
+ void loadRecent( const QString &collection = QStringLiteral( "generic" ) )/Deprecated/;
%Docstring
Loads the recent expressions from the given ``collection``.
By default it is loaded from the collection "generic".
+
+.. deprecated:: QGIS 3.14
+ use expressionTree()->loadRecent() instead
%End
- void loadUserExpressions( );
+ QgsExpressionTreeView *expressionTree() const;
+%Docstring
+Returns the expression tree
+
+.. versionadded:: 3.14
+%End
+
+ void loadUserExpressions() /Deprecated/;
%Docstring
Loads the user expressions.
+.. deprecated:: QGIS 3.14
+ use expressionTree()->loadUserExpressions() instead
+
.. versionadded:: 3.12
%End
- void saveToUserExpressions( const QString &label, const QString expression, const QString &helpText );
+ void saveToUserExpressions( const QString &label, const QString expression, const QString &helpText ) /Deprecated/;
%Docstring
Stores the user ``expression`` with given ``label`` and ``helpText``.
+.. deprecated:: QGIS 3.14
+ use expressionTree()->saveToUserExpressions() instead
+
.. versionadded:: 3.12
%End
- void removeFromUserExpressions( const QString &label );
+ void removeFromUserExpressions( const QString &label ) /Deprecated/;
%Docstring
Removes the expression ``label`` from the user stored expressions.
+.. deprecated:: QGIS 3.14
+ use expressionTree()->removeFromUserExpressions() instead
+
.. versionadded:: 3.12
%End
@@ -276,12 +245,14 @@ Loads code into the function editor
Updates the list of function files found at the given path
%End
- QStandardItemModel *model();
+ QStandardItemModel *model() /Deprecated/;
%Docstring
Returns a pointer to the dialog's function item model.
This method is exposed for testing purposes only - it should not be used to modify the model.
.. versionadded:: 3.0
+
+.. deprecated:: QGIS 3.14
%End
QgsProject *project();
@@ -379,6 +350,14 @@ Removes the selected expression from the stored user expressions,
the selected expression must be a user stored expression.
.. versionadded:: 3.12
+%End
+
+ void editSelectedUserExpression();
+%Docstring
+Edits the selected expression from the stored user expressions,
+the selected expression must be a user stored expression.
+
+.. versionadded:: 3.14
%End
const QList findExpressions( const QString &label );
@@ -386,6 +365,9 @@ the selected expression must be a user stored expression.
Returns the list of expression items matching a ``label``.
.. versionadded:: 3.12
+
+.. deprecated:: QGIS 3.14
+ use expressionTree()->findExpressions instead
%End
@@ -420,6 +402,7 @@ with the context.
virtual void showEvent( QShowEvent *e );
+ public:
};
diff --git a/python/gui/auto_generated/qgsexpressionstoredialog.sip.in b/python/gui/auto_generated/qgsexpressionstoredialog.sip.in
index 0a2112dc8b62..1ca660d6a2f8 100644
--- a/python/gui/auto_generated/qgsexpressionstoredialog.sip.in
+++ b/python/gui/auto_generated/qgsexpressionstoredialog.sip.in
@@ -26,7 +26,7 @@ A generic dialog for editing expression text, label and help text.
QgsExpressionStoreDialog( const QString &label,
const QString &expression,
const QString &helpText,
- const QStringList &existingLabels,
+ const QStringList &existingLabels = QStringList(),
QWidget *parent = 0 );
%Docstring
Creates a QgsExpressionStoreDialog with given ``label``, ``expression`` and ``helpText``.
diff --git a/python/gui/auto_generated/qgsexpressiontreeview.sip.in b/python/gui/auto_generated/qgsexpressiontreeview.sip.in
new file mode 100644
index 000000000000..4155558c97e2
--- /dev/null
+++ b/python/gui/auto_generated/qgsexpressiontreeview.sip.in
@@ -0,0 +1,277 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsexpressiontreeview.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+
+
+class QgsExpressionItem : QStandardItem
+{
+%Docstring
+An expression item that can be used in the QgsExpressionBuilderWidget tree.
+%End
+
+%TypeHeaderCode
+#include "qgsexpressiontreeview.h"
+%End
+ public:
+ enum ItemType
+ {
+ Header,
+ Field,
+ ExpressionNode
+ };
+
+ QgsExpressionItem( const QString &label,
+ const QString &expressionText,
+ const QString &helpText,
+ QgsExpressionItem::ItemType itemType = ExpressionNode );
+
+ QgsExpressionItem( const QString &label,
+ const QString &expressionText,
+ QgsExpressionItem::ItemType itemType = ExpressionNode );
+
+ QString getExpressionText() const;
+
+ QString getHelpText() const;
+%Docstring
+Gets the help text that is associated with this expression item.
+
+:return: The help text.
+%End
+
+ void setHelpText( const QString &helpText );
+%Docstring
+Set the help text for the current item
+
+.. note::
+
+ The help text can be set as a html string.
+%End
+
+ QgsExpressionItem::ItemType getItemType() const;
+%Docstring
+Gets the type of expression item, e.g., header, field, ExpressionNode.
+
+:return: The QgsExpressionItem.ItemType
+%End
+
+ static const int CUSTOM_SORT_ROLE;
+ static const int ITEM_TYPE_ROLE;
+ static const int SEARCH_TAGS_ROLE;
+
+};
+
+
+class QgsExpressionItemSearchProxy : QSortFilterProxyModel
+{
+%Docstring
+Search proxy used to filter the QgsExpressionBuilderWidget tree.
+The default search for a tree model only searches top level this will handle one
+level down
+%End
+
+%TypeHeaderCode
+#include "qgsexpressiontreeview.h"
+%End
+ public:
+ QgsExpressionItemSearchProxy();
+
+ virtual bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const;
+
+
+ protected:
+
+ virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;
+
+};
+
+class QgsExpressionTreeView : QTreeView
+{
+%Docstring
+QgsExpressionTreeView is a tree view to list all expressions
+functions, variables and fields that can be used in an expression.
+
+.. seealso:: :py:class:`QgsExpressionBuilderWidget`
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsexpressiontreeview.h"
+%End
+ public:
+
+ class MenuProvider
+{
+%Docstring
+Implementation of this interface can be implemented to allow QgsExpressionTreeView
+instance to provide custom context menus (opened upon right-click).
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsexpressiontreeview.h"
+%End
+ public:
+ explicit MenuProvider();
+%Docstring
+Constructor
+%End
+ virtual ~MenuProvider();
+
+ virtual QMenu *createContextMenu( QgsExpressionItem *item ) /Factory/;
+%Docstring
+Returns a newly created menu instance
+%End
+ };
+
+ QgsExpressionTreeView( QWidget *parent = 0 );
+%Docstring
+Constructor
+%End
+
+ void setLayer( QgsVectorLayer *layer );
+%Docstring
+Sets layer in order to get the fields and values
+%End
+
+ void loadFieldNames( const QgsFields &fields );
+%Docstring
+This allows loading fields without specifying a layer
+%End
+
+ void setExpressionContext( const QgsExpressionContext &context );
+%Docstring
+Sets the expression context for the tree view. The context is used
+to populate the list of available functions and variables.
+
+:param context: expression context
+
+.. seealso:: :py:func:`expressionContext`
+%End
+
+ QgsExpressionContext expressionContext() const;
+%Docstring
+Returns the expression context for the widget. The context is used for the expression
+preview result and for populating the list of available functions and variables.
+
+.. seealso:: :py:func:`setExpressionContext`
+%End
+
+ QgsProject *project();
+%Docstring
+Returns the project currently associated with the widget.
+
+.. seealso:: :py:func:`setProject`
+%End
+
+ void setProject( QgsProject *project );
+%Docstring
+Sets the ``project`` currently associated with the widget. This
+controls which layers and relations and other project-specific items are shown in the widget.
+
+.. seealso:: :py:func:`project`
+%End
+
+ void setMenuProvider( MenuProvider *provider );
+%Docstring
+Sets the menu provider.
+This does not take ownership of the provider
+%End
+
+ void refresh();
+%Docstring
+Refreshes the content of the tree
+%End
+
+ QgsExpressionItem *currentItem() const;
+%Docstring
+Returns the current item or a None
+%End
+
+
+ void loadRecent( const QString &collection = QStringLiteral( "generic" ) );
+%Docstring
+Loads the recent expressions from the given ``collection``.
+By default it is loaded from the collection "generic".
+%End
+
+ void saveToRecent( const QString &expressionText, const QString &collection = "generic" );
+%Docstring
+Adds the current expression to the given ``collection``.
+By default it is saved to the collection "generic".
+%End
+
+ void saveToUserExpressions( const QString &label, const QString expression, const QString &helpText );
+%Docstring
+Stores the user ``expression`` with given ``label`` and ``helpText``.
+%End
+
+ void removeFromUserExpressions( const QString &label );
+%Docstring
+Removes the expression ``label`` from the user stored expressions.
+%End
+
+ void loadUserExpressions( );
+%Docstring
+Loads the user expressions.
+This is done on request since it can be very slow if there are thousands of user expressions
+%End
+
+ const QList findExpressions( const QString &label );
+%Docstring
+Returns the list of expression items matching a ``label``.
+%End
+
+
+ QJsonDocument exportUserExpressions();
+%Docstring
+Create the expressions JSON document storing all the user expressions to be exported.
+
+:return: the created expressions JSON file
+%End
+
+ void loadExpressionsFromJson( const QJsonDocument &expressionsDocument );
+%Docstring
+Load and permanently store the expressions from the expressions JSON document.
+
+:param expressionsDocument: the parsed expressions JSON file
+%End
+
+ signals:
+ void expressionItemDoubleClicked( const QString &text );
+%Docstring
+Emitted when a expression item is double clicked
+%End
+
+ void currentExpressionItemChanged( QgsExpressionItem *item );
+%Docstring
+Emitter when the current expression item changed
+%End
+
+ public slots:
+ void setSearchText( const QString &text );
+%Docstring
+Sets the text to filter the expression tree
+%End
+
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsexpressiontreeview.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/qgsextentgroupbox.sip.in b/python/gui/auto_generated/qgsextentgroupbox.sip.in
index 2426cb489e98..6184944c0a17 100644
--- a/python/gui/auto_generated/qgsextentgroupbox.sip.in
+++ b/python/gui/auto_generated/qgsextentgroupbox.sip.in
@@ -11,7 +11,6 @@
-
class QgsExtentGroupBox : QgsCollapsibleGroupBox
{
%Docstring
@@ -20,7 +19,9 @@ Collapsible group box for configuration of extent, typically for a save operatio
Besides allowing the user to enter the extent manually, it comes with options to use
original extent or extent defined by the current view in map canvas.
-When using the widget, make sure to call setOriginalExtent(), setCurrentExtent() and setOutputCrs() during initialization.
+When using the group box, make sure to call setOriginalExtent(), setCurrentExtent() and setOutputCrs() during initialization.
+
+.. seealso:: :py:class:`QgsExtentWidget`
.. versionadded:: 2.4
%End
@@ -30,6 +31,7 @@ When using the widget, make sure to call setOriginalExtent(), setCurrentExtent()
%End
public:
+
enum ExtentState
{
OriginalExtent,
diff --git a/python/gui/auto_generated/qgsextentwidget.sip.in b/python/gui/auto_generated/qgsextentwidget.sip.in
new file mode 100644
index 000000000000..eba2cbd29ee3
--- /dev/null
+++ b/python/gui/auto_generated/qgsextentwidget.sip.in
@@ -0,0 +1,247 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsextentwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+
+
+
+class QgsExtentWidget : QWidget
+{
+%Docstring
+A widget for configuration of a map extent.
+
+Besides allowing the user to enter the extent manually, it comes with options to use
+original extent or extent defined by the current view in map canvas.
+
+When using the widget, make sure to call setOriginalExtent(), setCurrentExtent() and setOutputCrs() during initialization.
+
+.. seealso:: :py:class:`QgsExtentGroupBox`
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsextentwidget.h"
+%End
+ public:
+
+ enum ExtentState
+ {
+ OriginalExtent,
+ CurrentExtent,
+ UserExtent,
+ ProjectLayerExtent,
+ DrawOnCanvas,
+ };
+
+ enum WidgetStyle
+ {
+ CondensedStyle,
+ ExpandedStyle,
+ };
+
+ explicit QgsExtentWidget( QWidget *parent /TransferThis/ = 0, WidgetStyle style = CondensedStyle );
+%Docstring
+Constructor for QgsExtentWidget.
+%End
+
+ void setOriginalExtent( const QgsRectangle &originalExtent, const QgsCoordinateReferenceSystem &originalCrs );
+%Docstring
+Sets the original extent and coordinate reference system for the widget. This should be called as part of initialization.
+
+.. seealso:: :py:func:`originalExtent`
+
+.. seealso:: :py:func:`originalCrs`
+%End
+
+ QgsRectangle originalExtent() const;
+%Docstring
+Returns the original extent set for the widget.
+
+.. seealso:: :py:func:`setOriginalExtent`
+
+.. seealso:: :py:func:`originalCrs`
+%End
+
+ QgsCoordinateReferenceSystem originalCrs() const;
+%Docstring
+Returns the original coordinate reference system set for the widget.
+
+.. seealso:: :py:func:`originalExtent`
+
+.. seealso:: :py:func:`setOriginalExtent`
+%End
+
+ void setCurrentExtent( const QgsRectangle ¤tExtent, const QgsCoordinateReferenceSystem ¤tCrs );
+%Docstring
+Sets the current extent to show in the widget - should be called as part of initialization (or whenever current extent changes).
+The current extent is usually set to match the current map canvas extent.
+
+.. seealso:: :py:func:`currentExtent`
+
+.. seealso:: :py:func:`currentCrs`
+%End
+
+ QgsRectangle currentExtent() const;
+%Docstring
+Returns the current extent set for the widget. The current extent is usually set to match the
+current map canvas extent.
+
+.. seealso:: :py:func:`setCurrentExtent`
+
+.. seealso:: :py:func:`currentCrs`
+%End
+
+ QgsCoordinateReferenceSystem currentCrs() const;
+%Docstring
+Returns the coordinate reference system for the current extent set for the widget. The current
+extent and CRS usually reflects the map canvas extent and CRS.
+
+.. seealso:: :py:func:`setCurrentExtent`
+
+.. seealso:: :py:func:`currentExtent`
+%End
+
+ void setOutputCrs( const QgsCoordinateReferenceSystem &outputCrs );
+%Docstring
+Sets the output CRS - may need to be used for transformation from original/current extent.
+Should be called as part of initialization and whenever the the output CRS is changed.
+The current extent will be reprojected into the new output CRS.
+%End
+
+ QgsRectangle outputExtent() const;
+%Docstring
+Returns the extent shown in the widget - in output CRS coordinates.
+
+.. seealso:: :py:func:`outputCrs`
+%End
+
+ QgsCoordinateReferenceSystem outputCrs() const;
+%Docstring
+Returns the current output CRS, used in the display.
+
+.. seealso:: :py:func:`outputExtent`
+%End
+
+ QgsExtentWidget::ExtentState extentState() const;
+%Docstring
+Returns the currently selected state for the widget's extent.
+%End
+
+ void setMapCanvas( QgsMapCanvas *canvas );
+%Docstring
+Sets the map canvas to enable dragging of extent on a canvas.
+
+:param canvas: the map canvas
+%End
+
+ QSize ratio() const;
+%Docstring
+Returns the current fixed aspect ratio to be used when dragging extent onto the canvas.
+If the aspect ratio isn't fixed, the width and height will be set to zero.
+%End
+
+ QString extentLayerName() const;
+%Docstring
+Returns the name of the extent layer.
+%End
+
+ bool isValid() const;
+%Docstring
+Returns ``True`` if the widget is in a valid state, i.e. has an extent set.
+%End
+
+ void setNullValueAllowed( bool allowed, const QString ¬SetText = QString() );
+%Docstring
+Sets whether the widget can be set to a "not set" (null) state.
+
+The specified ``notSetText`` will be used for showing null values.
+
+.. note::
+
+ This mode only applies to widgets in the condensed state!
+%End
+
+ public slots:
+
+ void setOutputExtentFromOriginal();
+%Docstring
+Sets the output extent to be the same as original extent (may be transformed to output CRS).
+%End
+
+ void setOutputExtentFromCurrent();
+%Docstring
+Sets the output extent to be the same as current extent (may be transformed to output CRS).
+%End
+
+ void setOutputExtentFromUser( const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs );
+%Docstring
+Sets the output extent to a custom extent (may be transformed to output CRS).
+%End
+
+ void setOutputExtentFromLayer( const QgsMapLayer *layer );
+%Docstring
+Sets the output extent to match a ``layer``'s extent (may be transformed to output CRS).
+%End
+
+ void setOutputExtentFromDrawOnCanvas();
+%Docstring
+Sets the output extent by dragging on the canvas.
+%End
+
+ void setRatio( QSize ratio );
+%Docstring
+Sets a fixed aspect ratio to be used when dragging extent onto the canvas.
+To unset a fixed aspect ratio, set the width and height to zero.
+
+:param ratio: aspect ratio's width and height
+%End
+
+ void clear();
+%Docstring
+Clears the widget, setting it to a null value.
+%End
+
+ signals:
+
+ void extentChanged( const QgsRectangle &r );
+%Docstring
+Emitted when the widget's extent is changed.
+%End
+
+ void validationChanged( bool valid );
+%Docstring
+Emitted when the widget's validation state changes.
+%End
+
+ void toggleDialogVisibility( bool visible );
+%Docstring
+Emitted when the parent dialog visibility must be changed (e.g.
+to permit access to the map canvas)
+%End
+
+ protected:
+
+ virtual void dragEnterEvent( QDragEnterEvent *event );
+
+ virtual void dragLeaveEvent( QDragLeaveEvent *event );
+
+ virtual void dropEvent( QDropEvent *event );
+
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsextentwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/qgsfieldexpressionwidget.sip.in b/python/gui/auto_generated/qgsfieldexpressionwidget.sip.in
index c26cb0c5a409..6dbaabf72b1e 100644
--- a/python/gui/auto_generated/qgsfieldexpressionwidget.sip.in
+++ b/python/gui/auto_generated/qgsfieldexpressionwidget.sip.in
@@ -16,7 +16,7 @@ class QgsFieldExpressionWidget : QWidget
{
%Docstring
The QgsFieldExpressionWidget class reates a widget to choose fields and edit expressions
-It contains a combo boxto display the fields and expression and a button to open the expression dialog.
+It contains a combo box to display the fields and expression and a button to open the expression dialog.
The combo box is editable, allowing expressions to be edited inline.
The validity of the expression is checked live on key press, invalid expressions are displayed in red.
The expression will be added to the model (and the fieldChanged signals emitted)
@@ -200,6 +200,13 @@ sets the current row in the widget
void setField( const QString &fieldName );
%Docstring
sets the current field or expression in the widget
+%End
+
+ void setFields( const QgsFields &fields );
+%Docstring
+Sets the fields used in the widget to ``fields``, this allows the widget to work without a layer.
+
+.. versionadded:: 3.14
%End
void setExpression( const QString &expression );
diff --git a/python/gui/auto_generated/qgsfieldmappingmodel.sip.in b/python/gui/auto_generated/qgsfieldmappingmodel.sip.in
new file mode 100644
index 000000000000..5a3d72db2d5d
--- /dev/null
+++ b/python/gui/auto_generated/qgsfieldmappingmodel.sip.in
@@ -0,0 +1,161 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsfieldmappingmodel.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsFieldMappingModel: QAbstractTableModel
+{
+%Docstring
+The QgsFieldMappingModel holds mapping information for mapping from one set of QgsFields to another,
+for each set of "destination" fields an expression defines how to obtain the values of the
+"destination" fields.
+The model can be optionally set "editable" allowing to modify all the fields, by default only
+the mapping expression is editable.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsfieldmappingmodel.h"
+%End
+ public:
+
+ enum class ColumnDataIndex
+ {
+ SourceExpression,
+ DestinationName,
+ DestinationType,
+ DestinationLength,
+ DestinationPrecision,
+ DestinationConstraints,
+ };
+
+
+ struct Field
+ {
+ QString originalName;
+ QgsField field;
+ QString expression;
+ };
+
+ QgsFieldMappingModel( const QgsFields &sourceFields = QgsFields(),
+ const QgsFields &destinationFields = QgsFields(),
+ const QMap &expressions = QMap(),
+ QObject *parent = 0 );
+%Docstring
+Constructs a QgsFieldMappingModel from a set of ``sourceFields``
+and ``destinationFields``, initial values for the expressions can be
+optionally specified through ``expressions`` which is a map from the original
+field name to the corresponding expression. A ``parent`` object
+can be also specified.
+%End
+
+ bool destinationEditable() const;
+%Docstring
+Returns ``True`` if the destination fields are editable
+%End
+
+ void setDestinationEditable( bool editable );
+%Docstring
+Sets the destination fields editable state to ``editable``
+%End
+
+ const QMap dataTypes() const;
+%Docstring
+Returns a static map of supported data types
+%End
+
+ QgsFields sourceFields() const;
+%Docstring
+Returns a list of source fields
+%End
+
+ QList mapping() const;
+%Docstring
+Returns a list of Field objects representing the current status of the model
+%End
+
+ QMap< QString, QgsProperty > fieldPropertyMap() const;
+%Docstring
+Returns a map of destination field name to QgsProperty definition for field value,
+representing the current status of the model.
+
+.. seealso:: :py:func:`setFieldPropertyMap`
+%End
+
+ void setFieldPropertyMap( const QMap< QString, QgsProperty > &map );
+%Docstring
+Sets a map of destination field name to QgsProperty definition for field value.
+
+.. seealso:: :py:func:`fieldPropertyMap`
+%End
+
+ void appendField( const QgsField &field, const QString &expression = QString() );
+%Docstring
+Appends a new ``field`` to the model, with an optional ``expression``
+%End
+
+ bool removeField( const QModelIndex &index );
+%Docstring
+Removes the field at ``index`` from the model, returns ``True`` on success
+%End
+
+ bool moveUp( const QModelIndex &index );
+%Docstring
+Moves down the field at ``index``
+%End
+
+ bool moveDown( const QModelIndex &index );
+%Docstring
+Moves up the field at ``index``
+%End
+
+ void setSourceFields( const QgsFields &sourceFields );
+%Docstring
+Set source fields to ``sourceFields``
+%End
+
+ QgsExpressionContextGenerator *contextGenerator() const;
+%Docstring
+Returns the context generator with the source fields
+%End
+
+ void setDestinationFields( const QgsFields &destinationFields,
+ const QMap &expressions = QMap() );
+%Docstring
+Set destination fields to ``destinationFields``, initial values for the expressions can be
+optionally specified through ``expressions`` which is a map from the original
+field name to the corresponding expression.
+%End
+
+ virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
+
+ virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
+
+ virtual QVariant data( const QModelIndex &index, int role ) const;
+
+ virtual QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
+
+ virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
+
+ virtual bool setData( const QModelIndex &index, const QVariant &value, int role );
+
+
+ public:
+};
+
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsfieldmappingmodel.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/qgsfieldmappingwidget.sip.in b/python/gui/auto_generated/qgsfieldmappingwidget.sip.in
new file mode 100644
index 000000000000..37a221103a88
--- /dev/null
+++ b/python/gui/auto_generated/qgsfieldmappingwidget.sip.in
@@ -0,0 +1,130 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsfieldmappingwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+class QgsFieldMappingWidget : QgsPanelWidget
+{
+%Docstring
+The QgsFieldMappingWidget class creates a mapping from one set of QgsFields to another,
+for each set of "destination" fields an expression defines how to obtain the values of the
+"destination" fields.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsfieldmappingwidget.h"
+%End
+ public:
+
+ explicit QgsFieldMappingWidget( QWidget *parent = 0,
+ const QgsFields &sourceFields = QgsFields(),
+ const QgsFields &destinationFields = QgsFields(),
+ const QMap &expressions = QMap() );
+%Docstring
+Constructs a QgsFieldMappingWidget from a set of ``sourceFields``
+and ``destinationFields``, initial values for the expressions can be
+optionally specified through ``expressions`` which is a map from the original
+field name to the corresponding expression. A ``parent`` object
+can also be specified.
+%End
+
+ void setDestinationEditable( bool editable );
+%Docstring
+Sets the destination fields editable state to ``editable``
+%End
+
+ bool destinationEditable() const;
+%Docstring
+Returns ``True`` if the destination fields are editable in the model
+%End
+
+ QgsFieldMappingModel *model() const;
+%Docstring
+Returns the underlying mapping model
+%End
+
+ QList mapping() const;
+%Docstring
+Returns a list of Field objects representing the current status of the underlying mapping model
+%End
+
+ QMap< QString, QgsProperty > fieldPropertyMap() const;
+%Docstring
+Returns a map of destination field name to QgsProperty definition for field value,
+representing the current status of the widget.
+
+.. seealso:: :py:func:`setFieldPropertyMap`
+%End
+
+ void setFieldPropertyMap( const QMap< QString, QgsProperty > &map );
+%Docstring
+Sets a map of destination field name to QgsProperty definition for field value.
+
+.. seealso:: :py:func:`fieldPropertyMap`
+%End
+
+ QItemSelectionModel *selectionModel();
+%Docstring
+Returns the selection model
+%End
+
+ void setSourceFields( const QgsFields &sourceFields );
+%Docstring
+Set source fields of the underlying mapping model to ``sourceFields``
+%End
+
+ void setDestinationFields( const QgsFields &destinationFields,
+ const QMap &expressions = QMap() );
+%Docstring
+Set destination fields to ``destinationFields`` in the underlying model,
+initial values for the expressions can be optionally specified through
+``expressions`` which is a map from the original field name to the
+corresponding expression.
+%End
+
+ void scrollTo( const QModelIndex &index ) const;
+%Docstring
+Scroll the fields view to ``index``
+%End
+
+ public slots:
+
+ void appendField( const QgsField &field, const QString &expression = QString() );
+%Docstring
+Appends a new ``field`` to the model, with an optional ``expression``
+%End
+
+ bool removeSelectedFields( );
+%Docstring
+Removes the currently selected field from the model
+%End
+
+ bool moveSelectedFieldsUp( );
+%Docstring
+Moves down currently selected field
+%End
+
+ bool moveSelectedFieldsDown( );
+%Docstring
+Moves up the currently selected field
+%End
+
+ public:
+ public:
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgsfieldmappingwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/qgshighlightablelineedit.sip.in b/python/gui/auto_generated/qgshighlightablelineedit.sip.in
new file mode 100644
index 000000000000..cd6504e456a9
--- /dev/null
+++ b/python/gui/auto_generated/qgshighlightablelineedit.sip.in
@@ -0,0 +1,58 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgshighlightablelineedit.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+
+class QgsHighlightableLineEdit: QgsFilterLineEdit
+{
+%Docstring
+
+A QgsFilterLineEdit subclass with the ability to "highlight" the edges of the widget.
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgshighlightablelineedit.h"
+%End
+ public:
+
+ QgsHighlightableLineEdit( QWidget *parent /TransferThis/ = 0 );
+%Docstring
+Constructor for QgsHighlightableLineEdit, with the specified ``parent`` widget.
+%End
+
+ bool isHighlighted() const;
+%Docstring
+Returns ``True`` if the line edit is currently highlighted.
+
+.. seealso:: :py:func:`setHighlighted`
+%End
+
+ void setHighlighted( bool highlighted );
+%Docstring
+Sets whether the line edit is currently ``highlighted``.
+
+.. seealso:: :py:func:`isHighlighted`
+%End
+
+ protected:
+ virtual void paintEvent( QPaintEvent *e );
+
+
+};
+
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/qgshighlightablelineedit.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/qgsmapcanvas.sip.in b/python/gui/auto_generated/qgsmapcanvas.sip.in
index 0ab7f1ab185a..c4ed7b9c0cf3 100644
--- a/python/gui/auto_generated/qgsmapcanvas.sip.in
+++ b/python/gui/auto_generated/qgsmapcanvas.sip.in
@@ -78,6 +78,14 @@ Gets access to properties used for map rendering
Sets the temporal controller, this controller will be used to
update the canvas temporal range.
+.. versionadded:: 3.14
+%End
+
+ const QgsTemporalController *temporalController() const;
+%Docstring
+Gets access to the temporal controller that will be used to
+update the canvas temporal range.
+
.. versionadded:: 3.14
%End
@@ -184,7 +192,15 @@ Returns the combined extent for all layers on the map canvas
void setExtent( const QgsRectangle &r, bool magnified = false );
%Docstring
-Sets the extent of the map canvas
+Sets the extent of the map canvas to the specified rectangle.
+
+The ``magnified`` argument dictates whether existing canvas constraints such
+as a scale lock should be respected or not during the operation. If ``magnified`` is
+``True`` then an existing scale lock constraint will be applied. This means that the final
+visible canvas extent may not match the specified extent.
+
+If ``magnified`` is ``False`` then scale lock settings will be ignored, and the specified
+rectangle will ALWAYS be visible in the canvas.
%End
bool setReferencedExtent( const QgsReferencedRectangle &extent ) throw( QgsCsException );
@@ -483,16 +499,22 @@ returns current layer (set by legend widget)
Sets wheel zoom factor (should be greater than 1)
%End
- void zoomScale( double scale );
+ void zoomScale( double scale, bool ignoreScaleLock = false );
%Docstring
Zooms the canvas to a specific ``scale``.
The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
+
+If ``ignoreScaleLock`` is set to ``True``, then any existing constraint on the map scale
+of the canvas will be ignored during the zoom operation.
%End
- void zoomByFactor( double scaleFactor, const QgsPointXY *center = 0 );
+ void zoomByFactor( double scaleFactor, const QgsPointXY *center = 0, bool ignoreScaleLock = false );
%Docstring
Zoom with the factor supplied. Factor > 1 zooms out, interval (0,1) zooms in
-If point is given, re-center on it
+If point is given, re-center on it.
+
+If ``ignoreScaleLock`` is set to ``True``, then any existing constraint on the map scale
+of the canvas will be ignored during the zoom operation.
%End
void zoomWithCenter( int x, int y, bool zoomIn );
diff --git a/python/gui/auto_generated/qgsmaplayerconfigwidgetfactory.sip.in b/python/gui/auto_generated/qgsmaplayerconfigwidgetfactory.sip.in
index 9dd7fe506cf3..c0875d47388c 100644
--- a/python/gui/auto_generated/qgsmaplayerconfigwidgetfactory.sip.in
+++ b/python/gui/auto_generated/qgsmaplayerconfigwidgetfactory.sip.in
@@ -108,7 +108,7 @@ Check if the layer is supported for this widget.
:return: ``True`` if this layer is supported for this widget
%End
- virtual QgsMapLayerConfigWidget *createWidget( QgsMapLayer *layer, QgsMapCanvas *canvas, bool dockWidget = true, QWidget *parent /TransferThis/ = 0 ) const = 0 /Factory/;
+ virtual QgsMapLayerConfigWidget *createWidget( QgsMapLayer *layer, QgsMapCanvas *canvas, bool dockWidget = true, QWidget *parent = 0 ) const = 0 /Factory/;
%Docstring
Factory function to create the widget on demand as needed by the dock.
diff --git a/python/gui/auto_generated/qgsmaptoolextent.sip.in b/python/gui/auto_generated/qgsmaptoolextent.sip.in
index c1fccf94a43c..c9e18c149a4d 100644
--- a/python/gui/auto_generated/qgsmaptoolextent.sip.in
+++ b/python/gui/auto_generated/qgsmaptoolextent.sip.in
@@ -10,7 +10,6 @@
-
class QgsMapToolExtent : QgsMapTool
{
%Docstring
diff --git a/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in b/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in
index cbefe2312891..713ee1c23e9a 100644
--- a/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in
+++ b/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in
@@ -10,7 +10,7 @@
-class QgsNewDatabaseTableNameWidget : QWidget
+class QgsNewDatabaseTableNameWidget : QgsPanelWidget
{
%Docstring
The QgsNewDatabaseTableNameWidget class embeds the browser view to
@@ -44,6 +44,13 @@ Constructs a new QgsNewDatabaseTableNameWidget
shown in the widget, if not specified all providers data items with database
capabilities will be shown
:param parent: optional parent for this widget
+%End
+
+ void setAcceptButtonVisible( bool visible );
+%Docstring
+Sets whether the optional "Ok"/accept button should be visible.
+
+By default this is hidden, to better allow the widget to be embedded inside other widgets and dialogs.
%End
QString schema() const;
@@ -121,9 +128,81 @@ This signal is emitted when the URI of the new table changes, whether or not it
:param uri: URI string representation
%End
+ void accepted();
+%Docstring
+Emitted when the OK/accept button is clicked.
+%End
};
+
+class QgsNewDatabaseTableNameDialog: QDialog
+{
+%Docstring
+QgsNewDatabaseTableNameDialog is a dialog which allows selection of a DB schema and a new table name.
+
+The table name is validated for uniqueness and the selected
+data item provider, schema and table names can be retrieved with
+getters.
+
+.. warning::
+
+ The data provider that originated the data item provider
+ must support the connections API
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsnewdatabasetablenamewidget.h"
+%End
+ public:
+
+ explicit QgsNewDatabaseTableNameDialog( QgsBrowserGuiModel *browserModel = 0,
+ const QStringList &providersFilter = QStringList(),
+ QWidget *parent = 0 );
+%Docstring
+Constructs a new QgsNewDatabaseTableNameDialog
+
+:param browserModel: an existing browser model (typically from app), if NULL an instance will be created
+:param providersFilter: optional white list of data provider keys that should be
+ shown in the widget, if not specified all providers data items with database
+ capabilities will be shown
+:param parent: optional parent for this widget
+%End
+
+ QString schema() const;
+%Docstring
+Returns the currently selected schema or file path (in case of filesystem-based DBs like spatialite or GPKG) for the new table
+%End
+
+ QString uri() const;
+%Docstring
+Returns the (possibly blank) string representation of the new table data source URI.
+The URI might be invalid in case the widget is not in a valid state.
+%End
+
+ QString table() const;
+%Docstring
+Returns the current name of the new table
+%End
+
+ QString dataProviderKey() const;
+%Docstring
+Returns the currently selected data item provider key
+%End
+
+ bool isValid() const;
+%Docstring
+Returns ``True`` if the widget contains a valid new table name
+%End
+
+ QString validationError() const;
+%Docstring
+Returns the validation error or an empty string is the widget status is valid
+%End
+
+};
/************************************************************************
* This file has been generated automatically from *
* *
diff --git a/python/gui/auto_generated/qgsproviderconnectioncombobox.sip.in b/python/gui/auto_generated/qgsproviderconnectioncombobox.sip.in
index 8fcda4f2b014..9d04b3342e43 100644
--- a/python/gui/auto_generated/qgsproviderconnectioncombobox.sip.in
+++ b/python/gui/auto_generated/qgsproviderconnectioncombobox.sip.in
@@ -33,6 +33,17 @@ The QgsProviderConnectionComboBox class is a combo box which displays the list o
%Docstring
Constructor for QgsProviderConnectionComboBox, for the specified ``provider``.
+.. warning::
+
+ The provider must support the connection API methods in its QgsProviderMetadata implementation
+ in order for the model to work correctly.
+%End
+
+
+ void setProvider( const QString &provider );
+%Docstring
+Sets the provider to be used.
+
.. warning::
The provider must support the connection API methods in its QgsProviderMetadata implementation
diff --git a/python/gui/auto_generated/qgsrelationeditorwidget.sip.in b/python/gui/auto_generated/qgsrelationeditorwidget.sip.in
index fa7f7b1c0f5b..1cbca547d003 100644
--- a/python/gui/auto_generated/qgsrelationeditorwidget.sip.in
+++ b/python/gui/auto_generated/qgsrelationeditorwidget.sip.in
@@ -74,6 +74,18 @@ Sets the ``feature`` being edited and updates the UI unless ``update`` is set to
void setEditorContext( const QgsAttributeEditorContext &context );
%Docstring
Sets the editor ``context``
+
+.. note::
+
+ if context cadDockWidget is null, it won't be possible to digitize
+ the geometry of a referencing feature from this widget
+%End
+
+ QgsAttributeEditorContext editorContext( ) const;
+%Docstring
+Returns the attribute editor context.
+
+.. versionadded:: 3.14
%End
QgsIFeatureSelectionManager *featureSelectionManager();
diff --git a/python/gui/auto_generated/qgstemporalcontrollerdockwidget.sip.in b/python/gui/auto_generated/qgstemporalcontrollerwidget.sip.in
similarity index 72%
rename from python/gui/auto_generated/qgstemporalcontrollerdockwidget.sip.in
rename to python/gui/auto_generated/qgstemporalcontrollerwidget.sip.in
index 6514d5c8d260..5577472cc1eb 100644
--- a/python/gui/auto_generated/qgstemporalcontrollerdockwidget.sip.in
+++ b/python/gui/auto_generated/qgstemporalcontrollerwidget.sip.in
@@ -1,7 +1,7 @@
/************************************************************************
* This file has been generated automatically from *
* *
- * src/gui/qgstemporalcontrollerdockwidget.h *
+ * src/gui/qgstemporalcontrollerwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
@@ -11,22 +11,22 @@
-class QgsTemporalControllerDockWidget : QgsDockWidget
+class QgsTemporalControllerWidget : QgsPanelWidget
{
%Docstring
-The QgsTemporalControllerDockWidget class
+A widget for controlling playback properties of a QgsTemporalController.
.. versionadded:: 3.14
%End
%TypeHeaderCode
-#include "qgstemporalcontrollerdockwidget.h"
+#include "qgstemporalcontrollerwidget.h"
%End
public:
- QgsTemporalControllerDockWidget( const QString &name, QWidget *parent /TransferThis/ = 0 );
+ QgsTemporalControllerWidget( QWidget *parent /TransferThis/ = 0 );
%Docstring
-Constructor for QgsTemporalControllerDockWidget, with the specified ``parent`` widget.
+Constructor for QgsTemporalControllerWidget, with the specified ``parent`` widget.
%End
QgsTemporalController *temporalController();
@@ -41,7 +41,7 @@ The dock widget retains ownership of the returned object.
/************************************************************************
* This file has been generated automatically from *
* *
- * src/gui/qgstemporalcontrollerdockwidget.h *
+ * src/gui/qgstemporalcontrollerwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
diff --git a/python/gui/auto_generated/raster/qgsrasterbandcombobox.sip.in b/python/gui/auto_generated/raster/qgsrasterbandcombobox.sip.in
index 1ca926852204..06609d6fafb7 100644
--- a/python/gui/auto_generated/raster/qgsrasterbandcombobox.sip.in
+++ b/python/gui/auto_generated/raster/qgsrasterbandcombobox.sip.in
@@ -56,6 +56,11 @@ Optionally the built in "not set" text can be overridden by specifying
a ``string``.
.. seealso:: :py:func:`setShowNotSetOption`
+%End
+
+ static QString displayBandName( QgsRasterDataProvider *provider, int band );
+%Docstring
+Returns a user-friendly band name for the specified ``band``.
%End
public slots:
diff --git a/python/gui/auto_generated/raster/qgsrastercontourrendererwidget.sip.in b/python/gui/auto_generated/raster/qgsrastercontourrendererwidget.sip.in
new file mode 100644
index 000000000000..6d71beba279d
--- /dev/null
+++ b/python/gui/auto_generated/raster/qgsrastercontourrendererwidget.sip.in
@@ -0,0 +1,43 @@
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/raster/qgsrastercontourrendererwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
+
+
+
+class QgsRasterContourRendererWidget : QgsRasterRendererWidget
+{
+%Docstring
+Configuration widget for QgsRasterContourRenderer
+
+.. versionadded:: 3.14
+%End
+
+%TypeHeaderCode
+#include "qgsrastercontourrendererwidget.h"
+%End
+ public:
+ QgsRasterContourRendererWidget( QgsRasterLayer *layer, const QgsRectangle &extent = QgsRectangle() );
+%Docstring
+Constructs the widget
+%End
+
+ static QgsRasterRendererWidget *create( QgsRasterLayer *layer, const QgsRectangle &extent ) /Factory/;
+%Docstring
+Widget creation function (mainly for the use by the renderer registry)
+%End
+
+ virtual QgsRasterRenderer *renderer();
+
+};
+
+/************************************************************************
+ * This file has been generated automatically from *
+ * *
+ * src/gui/raster/qgsrastercontourrendererwidget.h *
+ * *
+ * Do not edit manually ! Edit header and run scripts/sipify.pl again *
+ ************************************************************************/
diff --git a/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in b/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in
index e8d8b3ce8394..bdf404bc4661 100644
--- a/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in
+++ b/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in
@@ -34,6 +34,13 @@ Constructor
:param canvas: the QgsMapCanvas instance
:param parent: the parent of this widget
:param fl: windows flag
+%End
+
+ void setCurrentPage( const QString &page );
+%Docstring
+Sets the dialog ``page`` (by object name) to show.
+
+.. versionadded:: 3.14
%End
protected slots:
diff --git a/python/gui/auto_generated/tableeditor/qgstableeditordialog.sip.in b/python/gui/auto_generated/tableeditor/qgstableeditordialog.sip.in
index bf9e76c8ce5b..f1341793f12c 100644
--- a/python/gui/auto_generated/tableeditor/qgstableeditordialog.sip.in
+++ b/python/gui/auto_generated/tableeditor/qgstableeditordialog.sip.in
@@ -34,6 +34,16 @@ Constructor for QgsTableEditorDialog with the specified ``parent`` widget.
%Docstring
Sets the ``contents`` to show in the editor widget.
+.. seealso:: :py:func:`tableContents`
+%End
+
+
+ bool setTableContentsFromClipboard();
+%Docstring
+Parses the clipboard text into contents to show in the editor widget.
+
+:return: ``True`` if the clipboard was successfully parsed
+
.. seealso:: :py:func:`tableContents`
%End
diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip
index e37e508ecf23..16e83f5f76fd 100644
--- a/python/gui/gui_auto.sip
+++ b/python/gui/gui_auto.sip
@@ -54,6 +54,7 @@
%Include auto_generated/qgscredentialdialog.sip
%Include auto_generated/qgscurveeditorwidget.sip
%Include auto_generated/qgscustomdrophandler.sip
+%Include auto_generated/qgscustomprojectopenhandler.sip
%Include auto_generated/qgsdatabaseschemacombobox.sip
%Include auto_generated/qgsdatabasetablecombobox.sip
%Include auto_generated/qgsdataitemguiprovider.sip
@@ -74,12 +75,16 @@
%Include auto_generated/qgsexpressionhighlighter.sip
%Include auto_generated/qgsexpressionlineedit.sip
%Include auto_generated/qgsexpressionselectiondialog.sip
+%Include auto_generated/qgsexpressiontreeview.sip
%Include auto_generated/qgsextentgroupbox.sip
+%Include auto_generated/qgsextentwidget.sip
%Include auto_generated/qgsexternalresourcewidget.sip
%Include auto_generated/qgsfeaturelistcombobox.sip
%Include auto_generated/qgsfeatureselectiondlg.sip
%Include auto_generated/qgsfieldcombobox.sip
%Include auto_generated/qgsfieldexpressionwidget.sip
+%Include auto_generated/qgsfieldmappingwidget.sip
+%Include auto_generated/qgsfieldmappingmodel.sip
%Include auto_generated/qgsfieldvalidator.sip
%Include auto_generated/qgsfieldvalueslineedit.sip
%Include auto_generated/qgsfilecontentsourcelineedit.sip
@@ -98,6 +103,7 @@
%Include auto_generated/qgsgui.sip
%Include auto_generated/qgshelp.sip
%Include auto_generated/qgshighlight.sip
+%Include auto_generated/qgshighlightablelineedit.sip
%Include auto_generated/qgshistogramwidget.sip
%Include auto_generated/qgsidentifymenu.sip
%Include auto_generated/qgskeyvaluewidget.sip
@@ -197,7 +203,7 @@
%Include auto_generated/qgstablewidgetitem.sip
%Include auto_generated/qgstabwidget.sip
%Include auto_generated/qgstaskmanagerwidget.sip
-%Include auto_generated/qgstemporalcontrollerdockwidget.sip
+%Include auto_generated/qgstemporalcontrollerwidget.sip
%Include auto_generated/qgstextformatwidget.sip
%Include auto_generated/qgstextpreview.sip
%Include auto_generated/qgstreewidgetitem.sip
@@ -239,6 +245,8 @@
%Include auto_generated/auth/qgsauthsslimportdialog.sip
%Include auto_generated/auth/qgsauthtrustedcasdialog.sip
%Include auto_generated/callouts/qgscalloutwidget.sip
+%Include auto_generated/devtools/qgsdevtoolwidget.sip
+%Include auto_generated/devtools/qgsdevtoolwidgetfactory.sip
%Include auto_generated/editorwidgets/core/qgseditorconfigwidget.sip
%Include auto_generated/editorwidgets/core/qgseditorwidgetautoconf.sip
%Include auto_generated/editorwidgets/core/qgseditorwidgetfactory.sip
@@ -309,7 +317,9 @@
%Include auto_generated/processing/qgsprocessingmaplayercombobox.sip
%Include auto_generated/processing/qgsprocessingmodelerparameterwidget.sip
%Include auto_generated/processing/qgsprocessingmultipleselectiondialog.sip
+%Include auto_generated/processing/qgsprocessingoutputdestinationwidget.sip
%Include auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip
+%Include auto_generated/processing/qgsprocessingparameterswidget.sip
%Include auto_generated/processing/qgsprocessingrecentalgorithmlog.sip
%Include auto_generated/processing/qgsprocessingtoolboxmodel.sip
%Include auto_generated/processing/qgsprocessingtoolboxtreeview.sip
@@ -325,6 +335,7 @@
%Include auto_generated/raster/qgsmultibandcolorrendererwidget.sip
%Include auto_generated/raster/qgspalettedrendererwidget.sip
%Include auto_generated/raster/qgsrasterbandcombobox.sip
+%Include auto_generated/raster/qgsrastercontourrendererwidget.sip
%Include auto_generated/raster/qgsrasterhistogramwidget.sip
%Include auto_generated/raster/qgsrasterminmaxwidget.sip
%Include auto_generated/raster/qgsrasterrendererwidget.sip
diff --git a/python/plugins/db_manager/db_manager_plugin.py b/python/plugins/db_manager/db_manager_plugin.py
index 049d4a77e854..5e137a6d7a01 100644
--- a/python/plugins/db_manager/db_manager_plugin.py
+++ b/python/plugins/db_manager/db_manager_plugin.py
@@ -85,7 +85,7 @@ def unload(self):
def onLayerWasAdded(self, aMapLayer):
# Be able to update every Db layer from Postgres, Spatialite and Oracle
- if hasattr(aMapLayer, 'dataProvider') and aMapLayer.dataProvider().name() in ['postgres', 'spatialite', 'oracle']:
+ if hasattr(aMapLayer, 'dataProvider') and aMapLayer.dataProvider() and aMapLayer.dataProvider().name() in ['postgres', 'spatialite', 'oracle']:
self.iface.addCustomActionForLayer(self.layerAction, aMapLayer)
# virtual has QUrl source
# url = QUrl(QUrl.fromPercentEncoding(l.source()))
diff --git a/python/plugins/db_manager/db_plugins/postgis/connector.py b/python/plugins/db_manager/db_plugins/postgis/connector.py
index 5a267c65d020..52621d46069c 100644
--- a/python/plugins/db_manager/db_plugins/postgis/connector.py
+++ b/python/plugins/db_manager/db_plugins/postgis/connector.py
@@ -732,7 +732,16 @@ def getTableEstimatedExtent(self, table, geom):
schema, tablename = self.getSchemaTableName(table)
schema_part = u"%s," % self.quoteString(schema) if schema is not None else ""
- subquery = u"SELECT st_estimated_extent(%s%s,%s) AS extent" % (
+ pgis_versions = self.getSpatialInfo()[0].split('.')
+ pgis_major_version = int(pgis_versions[0])
+ pgis_minor_version = int(pgis_versions[1])
+ pgis_old = False
+ if pgis_major_version < 2:
+ pgis_old = True
+ elif pgis_major_version == 2 and pgis_minor_version < 1:
+ pgis_old = True
+ subquery = u"SELECT %s(%s%s,%s) AS extent" % (
+ 'st_estimated_extent' if pgis_old else 'st_estimatedextent',
schema_part, self.quoteString(tablename), self.quoteString(geom))
sql = u"""SELECT st_xmin(extent), st_ymin(extent), st_xmax(extent), st_ymax(extent) FROM (%s) AS subquery """ % subquery
diff --git a/python/plugins/db_manager/dlg_create_table.py b/python/plugins/db_manager/dlg_create_table.py
index 4dc14020a843..490190ed2a92 100644
--- a/python/plugins/db_manager/dlg_create_table.py
+++ b/python/plugins/db_manager/dlg_create_table.py
@@ -309,6 +309,19 @@ def createTable(self):
except (ConnectionError, DbError) as e:
DlgDbError.showError(e, self)
- return
+
+ # clear UI
+ self.editName.clear()
+ self.fields.model().removeRows(0, self.fields.model().rowCount())
+ self.cboPrimaryKey.clear()
+ self.chkGeomColumn.setChecked(False)
+ self.chkSpatialIndex.setChecked(False)
+ self.editGeomSrid.clear()
+
+ self.cboGeomType.setEnabled(False)
+ self.editGeomColumn.setEnabled(False)
+ self.spinGeomDim.setEnabled(False)
+ self.editGeomSrid.setEnabled(False)
+ self.chkSpatialIndex.setEnabled(False)
QMessageBox.information(self, self.tr("DB Manager"), self.tr("Table created successfully."))
diff --git a/python/plugins/db_manager/ui/DlgQueryBuilder.ui b/python/plugins/db_manager/ui/DlgQueryBuilder.ui
index 8438266d835f..45817d453597 100644
--- a/python/plugins/db_manager/ui/DlgQueryBuilder.ui
+++ b/python/plugins/db_manager/ui/DlgQueryBuilder.ui
@@ -108,21 +108,21 @@
-
- Where
+ Where
-
- Group by
+ Group by
-
- Order by
+ Order by
diff --git a/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py b/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py
index d99fbf41a6b7..f0c0d79601f6 100644
--- a/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py
+++ b/python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py
@@ -37,14 +37,14 @@
QgsProcessingParameterDefinition)
from qgis.gui import (QgsMessageBar,
QgsProjectionSelectionWidget,
- QgsProcessingAlgorithmDialogBase)
+ QgsProcessingAlgorithmDialogBase,
+ QgsProcessingLayerOutputDestinationWidget)
from processing.gui.AlgorithmDialog import AlgorithmDialog
from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
from processing.gui.ParametersPanel import ParametersPanel
from processing.gui.MultipleInputPanel import MultipleInputPanel
from processing.gui.NumberInputPanel import NumberInputPanel
-from processing.gui.DestinationSelectionPanel import DestinationSelectionPanel
from processing.gui.wrappers import WidgetWrapper
from processing.tools.dataobjects import createContext
@@ -62,8 +62,9 @@ def getParametersPanel(self, alg, parent):
class GdalParametersPanel(ParametersPanel):
def __init__(self, parent, alg):
- ParametersPanel.__init__(self, parent, alg)
+ super().__init__(parent, alg)
+ self.dialog = parent
w = QWidget()
layout = QVBoxLayout()
layout.setMargin(0)
@@ -75,7 +76,7 @@ def __init__(self, parent, alg):
self.text.setReadOnly(True)
layout.addWidget(self.text)
w.setLayout(layout)
- self.layoutMain.addWidget(w)
+ self.addExtraWidget(w)
self.connectParameterSignals()
self.parametersHaveChanged()
@@ -99,9 +100,6 @@ def connectParameterSignals(self):
for c in w.findChildren(QWidget):
self.connectWidgetChangedSignals(c)
- for output_widget in self.outputWidgets.values():
- self.connectWidgetChangedSignals(output_widget)
-
def connectWidgetChangedSignals(self, w):
if isinstance(w, QLineEdit):
w.textChanged.connect(self.parametersHaveChanged)
@@ -115,18 +113,18 @@ def connectWidgetChangedSignals(self, w):
w.selectionChanged.connect(self.parametersHaveChanged)
elif isinstance(w, NumberInputPanel):
w.hasChanged.connect(self.parametersHaveChanged)
- elif isinstance(w, DestinationSelectionPanel):
+ elif isinstance(w, QgsProcessingLayerOutputDestinationWidget):
w.destinationChanged.connect(self.parametersHaveChanged)
def parametersHaveChanged(self):
context = createContext()
feedback = QgsProcessingFeedback()
try:
- parameters = self.parent.getParameterValues()
- for output in self.alg.destinationParameterDefinitions():
+ parameters = self.dialog.createProcessingParameters()
+ for output in self.algorithm().destinationParameterDefinitions():
if not output.name() in parameters or parameters[output.name()] is None:
parameters[output.name()] = self.tr("[temporary file]")
- for p in self.alg.parameterDefinitions():
+ for p in self.algorithm().parameterDefinitions():
if p.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
@@ -136,7 +134,7 @@ def parametersHaveChanged(self):
self.text.setPlainText('')
return
- commands = self.alg.getConsoleCommands(parameters, context, feedback, executing=False)
+ commands = self.algorithm().getConsoleCommands(parameters, context, feedback, executing=False)
commands = [c for c in commands if c not in ['cmd.exe', '/C ']]
self.text.setPlainText(" ".join(commands))
except AlgorithmDialogBase.InvalidParameterValue as e:
diff --git a/python/plugins/processing/algs/gdal/GdalAlgorithmProvider.py b/python/plugins/processing/algs/gdal/GdalAlgorithmProvider.py
index bd10cdbf107d..5628537281e4 100644
--- a/python/plugins/processing/algs/gdal/GdalAlgorithmProvider.py
+++ b/python/plugins/processing/algs/gdal/GdalAlgorithmProvider.py
@@ -37,7 +37,7 @@
from .ClipRasterByExtent import ClipRasterByExtent
from .ClipRasterByMask import ClipRasterByMask
from .ColorRelief import ColorRelief
-from .contour import contour
+from .contour import contour, contour_polygon
from .Datasources2Vrt import Datasources2Vrt
from .fillnodata import fillnodata
from .gdalinfo import gdalinfo
@@ -147,6 +147,7 @@ def loadAlgorithms(self):
ClipRasterByMask(),
ColorRelief(),
contour(),
+ contour_polygon(),
Datasources2Vrt(),
fillnodata(),
gdalinfo(),
diff --git a/python/plugins/processing/algs/gdal/GdalUtils.py b/python/plugins/processing/algs/gdal/GdalUtils.py
index 265eda3907cc..c571934b9767 100644
--- a/python/plugins/processing/algs/gdal/GdalUtils.py
+++ b/python/plugins/processing/algs/gdal/GdalUtils.py
@@ -41,7 +41,9 @@
QgsMessageLog,
QgsSettings,
QgsCredentials,
- QgsDataSourceUri)
+ QgsDataSourceUri,
+ QgsProjUtils,
+ QgsCoordinateReferenceSystem)
from processing.core.ProcessingConfig import ProcessingConfig
from processing.tools.system import isWindows, isMac
@@ -432,8 +434,12 @@ def gdal_crs_string(crs):
:param crs: crs to convert
:return: gdal friendly string
"""
- if crs.authid().upper().startswith('EPSG:'):
+ if crs.authid().upper().startswith('EPSG:') or crs.authid().upper().startswith('IGNF:') or crs.authid().upper().startswith('ESRI:'):
return crs.authid()
+ if QgsProjUtils.projVersionMajor() >= 6:
+ # use WKT
+ return crs.toWkt(QgsCoordinateReferenceSystem.WKT2_2018)
+
# fallback to proj4 string, stripping out newline characters
return crs.toProj().replace('\n', ' ').replace('\r', ' ')
diff --git a/python/plugins/processing/algs/gdal/contour.py b/python/plugins/processing/algs/gdal/contour.py
index 42328dc1f8bc..de09ac58e555 100644
--- a/python/plugins/processing/algs/gdal/contour.py
+++ b/python/plugins/processing/algs/gdal/contour.py
@@ -139,7 +139,7 @@ def groupId(self):
def commandName(self):
return 'gdal_contour'
- def getConsoleCommands(self, parameters, context, feedback, executing=True):
+ def _buildArgsList(self, parameters, context, feedback, executing):
inLayer = self.parameterAsRasterLayer(parameters, self.INPUT, context)
if inLayer is None:
raise QgsProcessingException(self.invalidRasterError(parameters, self.INPUT))
@@ -192,5 +192,61 @@ def getConsoleCommands(self, parameters, context, feedback, executing=True):
arguments.append(inLayer.source())
arguments.append(output)
+ return arguments
+
+ def getConsoleCommands(self, parameters, context, feedback, executing=True):
+ arguments = self._buildArgsList(parameters, context, feedback, executing)
+ return [self.commandName(), GdalUtils.escapeAndJoin(arguments)]
+
+
+class contour_polygon(contour):
+
+ FIELD_NAME_MIN = 'FIELD_NAME_MIN'
+ FIELD_NAME_MAX = 'FIELD_NAME_MAX'
+
+ def __init__(self):
+ super().__init__()
+
+ def initAlgorithm(self, config=None):
+ super().initAlgorithm(config)
+ # FIELD_NAME isn't used in polygon mode
+ self.removeParameter(contour.FIELD_NAME)
+
+ self.addParameter(QgsProcessingParameterString(self.FIELD_NAME_MIN,
+ self.tr('Attribute name for minimum elevation of contour polygon'),
+ defaultValue='ELEV_MIN',
+ optional=True))
+
+ self.addParameter(QgsProcessingParameterString(self.FIELD_NAME_MAX,
+ self.tr('Attribute name for maximum elevation of contour polygon'),
+ defaultValue='ELEV_MAX',
+ optional=True))
+
+ # Need to replace the output parameter, as we are producing a different type of output
+ self.removeParameter(contour.OUTPUT)
+ self.addParameter(QgsProcessingParameterVectorDestination(
+ contour.OUTPUT, self.tr('Contours'), QgsProcessing.TypeVectorPolygon))
+
+ def name(self):
+ return 'contour_polygon'
+
+ def displayName(self):
+ return self.tr('Contour Polygons')
+
+ def getConsoleCommands(self, parameters, context, feedback, executing=True):
+ arguments = self._buildArgsList(parameters, context, feedback, executing)
+
+ fieldNameMin = self.parameterAsString(parameters, self.FIELD_NAME_MIN, context)
+ fieldNameMax = self.parameterAsString(parameters, self.FIELD_NAME_MAX, context)
+
+ if fieldNameMin:
+ arguments.insert(0, fieldNameMin)
+ arguments.insert(0, '-amin')
+
+ if fieldNameMax:
+ arguments.insert(0, fieldNameMax)
+ arguments.insert(0, '-amax')
+
+ arguments.insert(0, "-p")
return [self.commandName(), GdalUtils.escapeAndJoin(arguments)]
diff --git a/python/plugins/processing/algs/gdal/ogr2ogrtabletopostgislist.py b/python/plugins/processing/algs/gdal/ogr2ogrtabletopostgislist.py
deleted file mode 100644
index becffe5dba04..000000000000
--- a/python/plugins/processing/algs/gdal/ogr2ogrtabletopostgislist.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- ogr2ogrtabletopostgislist.py
- ---------------------
- Date : November 2012
- Copyright : (C) 2012 by Victor Olaya
- Email : volayaf at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-__author__ = 'Victor Olaya'
-__date__ = 'November 2012'
-__copyright__ = '(C) 2012, Victor Olaya'
-
-from qgis.core import QgsSettings
-
-from processing.algs.gdal.GdalAlgorithm import GdalAlgorithm
-from processing.algs.gdal.GdalUtils import GdalUtils
-
-from processing.tools.postgis import uri_from_name, GeoDB
-from processing.tools.system import isWindows
-
-
-class Ogr2OgrTableToPostGisList(GdalAlgorithm):
-
- DATABASE = 'DATABASE'
- INPUT_LAYER = 'INPUT_LAYER'
- SHAPE_ENCODING = 'SHAPE_ENCODING'
- HOST = 'HOST'
- PORT = 'PORT'
- USER = 'USER'
- DBNAME = 'DBNAME'
- PASSWORD = 'PASSWORD'
- SCHEMA = 'SCHEMA'
- TABLE = 'TABLE'
- PK = 'PK'
- PRIMARY_KEY = 'PRIMARY_KEY'
- WHERE = 'WHERE'
- GT = 'GT'
- OVERWRITE = 'OVERWRITE'
- APPEND = 'APPEND'
- ADDFIELDS = 'ADDFIELDS'
- LAUNDER = 'LAUNDER'
- SKIPFAILURES = 'SKIPFAILURES'
- PRECISION = 'PRECISION'
- OPTIONS = 'OPTIONS'
-
- def __init__(self):
- GdalAlgorithm.__init__(self)
-
- def dbConnectionNames(self):
- settings = QgsSettings()
- settings.beginGroup('/PostgreSQL/connections/')
- return settings.childGroups()
-
- def __init__(self):
- super().__init__()
-
- def initAlgorithm(self, config=None):
- self.DB_CONNECTIONS = self.dbConnectionNames()
- self.addParameter(ParameterSelection(self.DATABASE,
- self.tr('Database (connection name)'), self.DB_CONNECTIONS))
- self.addParameter(ParameterTable(self.INPUT_LAYER,
- self.tr('Input layer')))
- self.addParameter(ParameterString(self.SHAPE_ENCODING,
- self.tr('Shape encoding'), "", optional=True))
- self.addParameter(ParameterString(self.SCHEMA,
- self.tr('Schema name'), 'public', optional=True))
- self.addParameter(ParameterString(self.TABLE,
- self.tr('Table name, leave blank to use input name'),
- '', optional=True))
- self.addParameter(ParameterString(self.PK,
- self.tr('Primary key'), 'id', optional=True))
- self.addParameter(ParameterTableField(self.PRIMARY_KEY,
- self.tr('Primary key (existing field, used if the above option is left empty)'),
- self.INPUT_LAYER, optional=True))
- self.addParameter(ParameterString(self.WHERE,
- self.tr('Select features using a SQL "WHERE" statement (Ex: column=\'value\')'),
- '', optional=True))
- self.addParameter(ParameterString(self.GT,
- self.tr('Group N features per transaction (Default: 20000)'),
- '', optional=True))
- self.addParameter(ParameterBoolean(self.OVERWRITE,
- self.tr('Overwrite existing table'), True))
- self.addParameter(ParameterBoolean(self.APPEND,
- self.tr('Append to existing table'), False))
- self.addParameter(ParameterBoolean(self.ADDFIELDS,
- self.tr('Append and add new fields to existing table'), False))
- self.addParameter(ParameterBoolean(self.LAUNDER,
- self.tr('Do not launder columns/table names'), False))
- self.addParameter(ParameterBoolean(self.SKIPFAILURES,
- self.tr('Continue after a failure, skipping the failed record'),
- False))
- self.addParameter(ParameterBoolean(self.PRECISION,
- self.tr('Keep width and precision of input attributes'),
- True))
- self.addParameter(ParameterString(self.OPTIONS,
- self.tr('Additional creation options'), '', optional=True))
-
- def name(self):
- return 'importlayertableasgeometrylesstableintopostgresqldatabase'
-
- def displayName(self):
- return self.tr('Import layer/table as geometryless table into PostgreSQL database')
-
- def group(self):
- return self.tr('Vector miscellaneous')
-
- def groupId(self):
- return 'vectormiscellaneous'
-
- def getConsoleCommands(self, parameters, context, feedback, executing=True):
- connection = self.DB_CONNECTIONS[self.getParameterValue(self.DATABASE)]
- uri = uri_from_name(connection)
- if executing:
- # to get credentials input when needed
- uri = GeoDB(uri=uri).uri
-
- inLayer = self.getParameterValue(self.INPUT_LAYER)
- ogrLayer, layerName = self.getOgrCompatibleSource(self.INPUT, parameters, context, feedback, executing)
- shapeEncoding = self.getParameterValue(self.SHAPE_ENCODING)
- schema = str(self.getParameterValue(self.SCHEMA))
- table = str(self.getParameterValue(self.TABLE))
- pk = str(self.getParameterValue(self.PK))
- pkstring = "-lco FID=" + pk
- primary_key = self.getParameterValue(self.PRIMARY_KEY)
- where = str(self.getParameterValue(self.WHERE))
- wherestring = '-where "' + where + '"'
- gt = str(self.getParameterValue(self.GT))
- overwrite = self.getParameterValue(self.OVERWRITE)
- append = self.getParameterValue(self.APPEND)
- addfields = self.getParameterValue(self.ADDFIELDS)
- launder = self.getParameterValue(self.LAUNDER)
- launderstring = "-lco LAUNDER=NO"
- skipfailures = self.getParameterValue(self.SKIPFAILURES)
- precision = self.getParameterValue(self.PRECISION)
- options = str(self.getParameterValue(self.OPTIONS))
-
- arguments = []
- arguments.append('-progress')
- arguments.append('--config PG_USE_COPY YES')
- if len(shapeEncoding) > 0:
- arguments.append('--config')
- arguments.append('SHAPE_ENCODING')
- arguments.append('"' + shapeEncoding + '"')
- arguments.append('-f')
- arguments.append('PostgreSQL')
- arguments.append('PG:"')
- for token in uri.connectionInfo(executing).split(' '):
- arguments.append(token)
- arguments.append('active_schema={}'.format(schema or 'public'))
- arguments.append('"')
- arguments.append(ogrLayer)
- arguments.append('-nlt NONE')
- arguments.append(layerName)
- if launder:
- arguments.append(launderstring)
- if append:
- arguments.append('-append')
- if addfields:
- arguments.append('-addfields')
- if overwrite:
- arguments.append('-overwrite')
- if len(pk) > 0:
- arguments.append(pkstring)
- elif primary_key is not None:
- arguments.append("-lco FID=" + primary_key)
- if len(table) == 0:
- table = layerName.lower()
- if schema:
- table = '{}.{}'.format(schema, table)
- arguments.append('-nln')
- arguments.append(table)
- if skipfailures:
- arguments.append('-skipfailures')
- if where:
- arguments.append(wherestring)
- if len(gt) > 0:
- arguments.append('-gt')
- arguments.append(gt)
- if not precision:
- arguments.append('-lco PRECISION=NO')
- if len(options) > 0:
- arguments.append(options)
-
- commands = []
- if isWindows():
- commands = ['cmd.exe', '/C ', 'ogr2ogr.exe',
- GdalUtils.escapeAndJoin(arguments)]
- else:
- commands = ['ogr2ogr', GdalUtils.escapeAndJoin(arguments)]
-
- return commands
-
- def commandName(self):
- return "ogr2ogr"
diff --git a/python/plugins/processing/algs/gdal/ogr2ogrtopostgislist.py b/python/plugins/processing/algs/gdal/ogr2ogrtopostgislist.py
index 10a2def429af..2518d7082409 100644
--- a/python/plugins/processing/algs/gdal/ogr2ogrtopostgislist.py
+++ b/python/plugins/processing/algs/gdal/ogr2ogrtopostgislist.py
@@ -29,12 +29,17 @@
QgsProcessingParameterField,
QgsProcessingParameterExtent,
QgsProcessingParameterBoolean,
- QgsProcessingParameterProviderConnection)
+ QgsProcessingParameterProviderConnection,
+ QgsProcessingParameterDatabaseSchema,
+ QgsProcessingParameterDatabaseTable,
+ QgsProviderRegistry,
+ QgsProcessingException,
+ QgsProviderConnectionException,
+ QgsDataSourceUri)
from processing.algs.gdal.GdalAlgorithm import GdalAlgorithm
from processing.algs.gdal.GdalUtils import GdalUtils
-from processing.tools.postgis import uri_from_name, GeoDB
from processing.tools.system import isWindows
@@ -101,22 +106,15 @@ def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterCrs(self.S_SRS,
self.tr('Override source CRS'), defaultValue='', optional=True))
- schema_param = QgsProcessingParameterString(
+ schema_param = QgsProcessingParameterDatabaseSchema(
self.SCHEMA,
- self.tr('Schema (schema name)'), 'public', False, True)
- schema_param.setMetadata({
- 'widget_wrapper': {
- 'class': 'processing.gui.wrappers_postgis.SchemaWidgetWrapper',
- 'connection_param': self.DATABASE}})
+ self.tr('Schema (schema name)'), defaultValue='public', connectionParameterName=self.DATABASE, optional=True)
self.addParameter(schema_param)
- table_param = QgsProcessingParameterString(
+ table_param = QgsProcessingParameterDatabaseTable(
self.TABLE,
- self.tr('Table to import to (leave blank to use layer name)'), '', False, True)
- table_param.setMetadata({
- 'widget_wrapper': {
- 'class': 'processing.gui.wrappers_postgis.TableWidgetWrapper',
- 'schema_param': self.SCHEMA}})
+ self.tr('Table to import to (leave blank to use layer name)'), defaultValue=None, connectionParameterName=self.DATABASE,
+ schemaParameterName=self.SCHEMA, optional=True, allowNewTableNames=True)
self.addParameter(table_param)
self.addParameter(QgsProcessingParameterString(self.PK,
@@ -200,19 +198,27 @@ def groupId(self):
return 'vectormiscellaneous'
def getConsoleCommands(self, parameters, context, feedback, executing=True):
- connection = self.parameterAsConnectionName(parameters, self.DATABASE, context)
- uri = uri_from_name(connection)
- if executing:
- # to get credentials input when needed
- uri = GeoDB(uri=uri).uri
+ connection_name = self.parameterAsConnectionName(parameters, self.DATABASE, context)
+ if not connection_name:
+ raise QgsProcessingException(
+ self.tr('No connection specified'))
+
+ # resolve connection details to uri
+ try:
+ md = QgsProviderRegistry.instance().providerMetadata('postgres')
+ conn = md.createConnection(connection_name)
+ except QgsProviderConnectionException:
+ raise QgsProcessingException(self.tr('Could not retrieve connection details for {}').format(connection_name))
+
+ uri = conn.uri()
ogrLayer, layername = self.getOgrCompatibleSource(self.INPUT, parameters, context, feedback, executing)
shapeEncoding = self.parameterAsString(parameters, self.SHAPE_ENCODING, context)
ssrs = self.parameterAsCrs(parameters, self.S_SRS, context)
tsrs = self.parameterAsCrs(parameters, self.T_SRS, context)
asrs = self.parameterAsCrs(parameters, self.A_SRS, context)
- table = self.parameterAsString(parameters, self.TABLE, context)
- schema = self.parameterAsString(parameters, self.SCHEMA, context)
+ table = self.parameterAsDatabaseTableName(parameters, self.TABLE, context)
+ schema = self.parameterAsSchema(parameters, self.SCHEMA, context)
pk = self.parameterAsString(parameters, self.PK, context)
pkstring = "-lco FID=" + pk
primary_key = self.parameterAsString(parameters, self.PRIMARY_KEY, context)
@@ -249,7 +255,7 @@ def getConsoleCommands(self, parameters, context, feedback, executing=True):
arguments.append('-f')
arguments.append('PostgreSQL')
arguments.append('PG:"')
- for token in uri.connectionInfo(executing).split(' '):
+ for token in QgsDataSourceUri(uri).connectionInfo(executing).split(' '):
arguments.append(token)
arguments.append('active_schema={}'.format(schema or 'public'))
arguments.append('"')
diff --git a/python/plugins/processing/algs/grass7/description/v.in.lidar.txt b/python/plugins/processing/algs/grass7/description/v.in.lidar.txt
index e0989fd7c64a..d0feb7438cf1 100644
--- a/python/plugins/processing/algs/grass7/description/v.in.lidar.txt
+++ b/python/plugins/processing/algs/grass7/description/v.in.lidar.txt
@@ -1,7 +1,7 @@
v.in.lidar
Converts LAS LiDAR point clouds to a GRASS vector map with libLAS.
Vector (v.*)
-QgsProcessingParameterFile|input|LiDAR input files in LAS format (*.las or *.laz)|QgsProcessingParameterFile.File|las|None|False
+QgsProcessingParameterFile|input|LiDAR input files in LAS format (*.las or *.laz)|QgsProcessingParameterFile.File||None|False|Lidar files (*.las *.LAS *.laz *.LAZ)
QgsProcessingParameterExtent|spatial|Import subregion only|None|True
QgsProcessingParameterRange|zrange|Filter range for z data|QgsProcessingParameterNumber.Double|None|True
QgsProcessingParameterEnum|return_filter|Only import points of selected return type|first;last;mid|True|None|True
diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml
index 0011b421c53a..a77487116aa2 100644
--- a/python/plugins/processing/algs/help/qgis.yaml
+++ b/python/plugins/processing/algs/help/qgis.yaml
@@ -330,15 +330,14 @@ qgis:refactorfields: >
The original layer is not modified. A new layer is generated, which contains a modified attribute table, according to the provided fields mapping.
+ Rows in orange have constraints in the template layer from which these fields were loaded. Treat this information as a hint during configuration. No constraints will be added on an output layer nor will they be checked or enforced by the algorithm.
+
qgis:regularpoints: >
This algorithm creates a point layer with a given number of regular points, all of them within a given extent.
qgis:relief: >
This algorithm creates a shaded relief layer from digital elevation data.
-qgis:removenullgeometries: >
- This algorithm removes any features which do not have a geometry from a vector layer. All other features will be copied unchanged.
-
qgis:reprojectlayer: >
This algorithm reprojects a vector layer. It creates a new layer with the same features as the input one, but with geometries reprojected to a new CRS.
diff --git a/python/plugins/processing/algs/otb/OtbChoiceWidget.py b/python/plugins/processing/algs/otb/OtbChoiceWidget.py
index 7ea0ed945109..f86eb3a3f7bd 100644
--- a/python/plugins/processing/algs/otb/OtbChoiceWidget.py
+++ b/python/plugins/processing/algs/otb/OtbChoiceWidget.py
@@ -70,10 +70,13 @@ def __updateWrapper(self, name, visible):
#Fur Qgis modeler
else:
- if name in self.dialog.wrappers:
- self.__setWrapperVisibility(self.dialog.wrappers[name], visible)
- if name in self.dialog.widget_labels:
- self.dialog.widget_labels[name].setVisible(visible)
+ try:
+ if name in self.dialog.widget.widget.wrappers:
+ self.__setWrapperVisibility(self.dialog.widget.widget.wrappers[name], visible)
+ if name in self.dialog.widget.widget.widget_labels:
+ self.dialog.widget.widget.widget_labels[name].setVisible(visible)
+ except AttributeError:
+ pass
def __setWrapperVisibility(self, wrapper, v):
# For compatibility with 3.x API, we need to check whether the wrapper is
diff --git a/python/plugins/processing/algs/otb/OtbUtils.py b/python/plugins/processing/algs/otb/OtbUtils.py
index e7e392bf40db..9b1037cf91cd 100644
--- a/python/plugins/processing/algs/otb/OtbUtils.py
+++ b/python/plugins/processing/algs/otb/OtbUtils.py
@@ -119,10 +119,12 @@ def getExecutableInPath(path, exe):
def getAuxiliaryDataDirectories():
gdal_data_dir = None
gtiff_csv_dir = None
+ proj_dir = None
otb_folder = OtbUtils.otbFolder()
if os.name == 'nt':
gdal_data_dir = os.path.join(otb_folder, 'share', 'data')
gtiff_csv_dir = os.path.join(otb_folder, 'share', 'epsg_csv')
+ proj_dir = os.path.join(otb_folder, 'share', 'proj')
else:
env_profile = os.path.join(otb_folder, 'otbenv.profile')
try:
@@ -137,12 +139,14 @@ def getAuxiliaryDataDirectories():
gdal_data_dir = line.split("GDAL_DATA=")[1]
if 'GEOTIFF_CSV='in line:
gtiff_csv_dir = line.split("GEOTIFF_CSV=")[1]
+ if 'PROJ_LIB='in line:
+ proj_dir = line.split("PROJ_LIB=")[1]
except BaseException as exc:
errmsg = "Cannot find gdal and geotiff data directory." + str(exc)
QgsMessageLog.logMessage(errmsg, OtbUtils.tr('Processing'), Qgis.Info)
pass
- return gdal_data_dir, gtiff_csv_dir
+ return gdal_data_dir, gtiff_csv_dir, proj_dir
@staticmethod
def executeOtb(commands, feedback, addToLog=True):
@@ -150,11 +154,13 @@ def executeOtb(commands, feedback, addToLog=True):
'LC_NUMERIC': 'C',
'GDAL_DRIVER_PATH': 'disable'
}
- gdal_data_dir, gtiff_csv_dir = OtbUtils.getAuxiliaryDataDirectories()
+ gdal_data_dir, gtiff_csv_dir, proj_dir = OtbUtils.getAuxiliaryDataDirectories()
if gdal_data_dir and os.path.exists(gdal_data_dir):
otb_env['GDAL_DATA'] = gdal_data_dir
if gtiff_csv_dir and os.path.exists(gtiff_csv_dir):
otb_env['GEOTIFF_CSV'] = gtiff_csv_dir
+ if proj_dir and os.path.exists(proj_dir):
+ otb_env['PROJ_LIB'] = proj_dir
otb_env['OTB_LOGGER_LEVEL'] = OtbUtils.loggerLevel()
max_ram_hint = OtbUtils.maxRAMHint()
diff --git a/python/plugins/processing/algs/qgis/CheckValidity.py b/python/plugins/processing/algs/qgis/CheckValidity.py
index 5b9a69dc4f62..0790e2873987 100644
--- a/python/plugins/processing/algs/qgis/CheckValidity.py
+++ b/python/plugins/processing/algs/qgis/CheckValidity.py
@@ -96,13 +96,13 @@ def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterBoolean(self.IGNORE_RING_SELF_INTERSECTION,
self.tr('Ignore ring self intersections'), defaultValue=False))
- self.addParameter(QgsProcessingParameterFeatureSink(self.VALID_OUTPUT, self.tr('Valid output'), QgsProcessing.TypeVectorAnyGeometry, '', True))
+ self.addParameter(QgsProcessingParameterFeatureSink(self.VALID_OUTPUT, self.tr('Valid output'), QgsProcessing.TypeVectorAnyGeometry, None, True))
self.addOutput(QgsProcessingOutputNumber(self.VALID_COUNT, self.tr('Count of valid features')))
- self.addParameter(QgsProcessingParameterFeatureSink(self.INVALID_OUTPUT, self.tr('Invalid output'), QgsProcessing.TypeVectorAnyGeometry, '', True))
+ self.addParameter(QgsProcessingParameterFeatureSink(self.INVALID_OUTPUT, self.tr('Invalid output'), QgsProcessing.TypeVectorAnyGeometry, None, True))
self.addOutput(QgsProcessingOutputNumber(self.INVALID_COUNT, self.tr('Count of invalid features')))
- self.addParameter(QgsProcessingParameterFeatureSink(self.ERROR_OUTPUT, self.tr('Error output'), QgsProcessing.TypeVectorAnyGeometry, '', True))
+ self.addParameter(QgsProcessingParameterFeatureSink(self.ERROR_OUTPUT, self.tr('Error output'), QgsProcessing.TypeVectorAnyGeometry, None, True))
self.addOutput(QgsProcessingOutputNumber(self.ERROR_COUNT, self.tr('Count of errors')))
def name(self):
diff --git a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py
index 6e93c65fa68c..bc87919fb5ac 100644
--- a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py
+++ b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py
@@ -22,7 +22,6 @@
__copyright__ = '(C) 2012, Victor Olaya'
from qgis.core import (QgsVectorLayerExporter,
- QgsSettings,
QgsFeatureSink,
QgsProcessing,
QgsProcessingException,
@@ -31,10 +30,15 @@
QgsProcessingParameterField,
QgsProcessingParameterBoolean,
QgsProcessingParameterProviderConnection,
- QgsWkbTypes)
+ QgsProcessingParameterDatabaseSchema,
+ QgsProcessingParameterDatabaseTable,
+ QgsWkbTypes,
+ QgsProviderRegistry,
+ QgsProviderConnectionException,
+ QgsDataSourceUri,
+ QgsAbstractDatabaseProviderConnection)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
-from processing.tools import postgis
class ImportIntoPostGIS(QgisAlgorithm):
@@ -71,22 +75,15 @@ def initAlgorithm(self, config=None):
)
self.addParameter(db_param)
- schema_param = QgsProcessingParameterString(
+ schema_param = QgsProcessingParameterDatabaseSchema(
self.SCHEMA,
- self.tr('Schema (schema name)'), 'public', False, True)
- schema_param.setMetadata({
- 'widget_wrapper': {
- 'class': 'processing.gui.wrappers_postgis.SchemaWidgetWrapper',
- 'connection_param': self.DATABASE}})
+ self.tr('Schema (schema name)'), connectionParameterName=self.DATABASE, defaultValue='public', optional=True)
self.addParameter(schema_param)
- table_param = QgsProcessingParameterString(
+ table_param = QgsProcessingParameterDatabaseTable(
self.TABLENAME,
- self.tr('Table to import to (leave blank to use layer name)'), '', False, True)
- table_param.setMetadata({
- 'widget_wrapper': {
- 'class': 'processing.gui.wrappers_postgis.TableWidgetWrapper',
- 'schema_param': self.SCHEMA}})
+ self.tr('Table to import to (leave blank to use layer name)'), defaultValue=None, connectionParameterName=self.DATABASE,
+ schemaParameterName=self.SCHEMA, optional=True, allowNewTableNames=True)
self.addParameter(table_param)
self.addParameter(QgsProcessingParameterField(self.PRIMARY_KEY,
@@ -122,10 +119,16 @@ def tags(self):
return self.tr('import,postgis,table,layer,into,copy').split(',')
def processAlgorithm(self, parameters, context, feedback):
- connection = self.parameterAsConnectionName(parameters, self.DATABASE, context)
- db = postgis.GeoDB.from_name(connection)
+ connection_name = self.parameterAsConnectionName(parameters, self.DATABASE, context)
- schema = self.parameterAsString(parameters, self.SCHEMA, context)
+ # resolve connection details to uri
+ try:
+ md = QgsProviderRegistry.instance().providerMetadata('postgres')
+ conn = md.createConnection(connection_name)
+ except QgsProviderConnectionException:
+ raise QgsProcessingException(self.tr('Could not retrieve connection details for {}').format(connection_name))
+
+ schema = self.parameterAsSchema(parameters, self.SCHEMA, context)
overwrite = self.parameterAsBoolean(parameters, self.OVERWRITE, context)
createIndex = self.parameterAsBoolean(parameters, self.CREATEINDEX, context)
convertLowerCase = self.parameterAsBoolean(parameters, self.LOWERCASE_NAMES, context)
@@ -138,7 +141,7 @@ def processAlgorithm(self, parameters, context, feedback):
if source is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
- table = self.parameterAsString(parameters, self.TABLENAME, context)
+ table = self.parameterAsDatabaseTableName(parameters, self.TABLENAME, context)
if table:
table.strip()
if not table or table == '':
@@ -166,8 +169,11 @@ def processAlgorithm(self, parameters, context, feedback):
if source.wkbType() == QgsWkbTypes.NoGeometry:
geomColumn = None
- uri = db.uri
- uri.setDataSource(schema, table, geomColumn, '', primaryKeyField)
+ uri = QgsDataSourceUri(conn.uri())
+ uri.setSchema(schema)
+ uri.setTable(table)
+ uri.setKeyColumn(primaryKeyField)
+ uri.setGeometryColumn(geomColumn)
if encoding:
options['fileEncoding'] = encoding
@@ -196,13 +202,16 @@ def processAlgorithm(self, parameters, context, feedback):
self.tr('Error importing to PostGIS\n{0}').format(exporter.errorMessage()))
if geomColumn and createIndex:
- db.create_spatial_index(table, schema, geomColumn)
-
- db.vacuum_analyze(table, schema)
+ try:
+ options = QgsAbstractDatabaseProviderConnection.SpatialIndexOptions()
+ options.geometryColumnName = geomColumn
+ conn.createSpatialIndex(schema, table, options)
+ except QgsProviderConnectionException as e:
+ raise QgsProcessingException(self.tr('Error creating spatial index:\n{0}').format(e))
+
+ try:
+ conn.vacuum(schema, table)
+ except QgsProviderConnectionException as e:
+ feedback.reportError(self.tr('Error vacuuming table:\n{0}').format(e))
return {}
-
- def dbConnectionNames(self):
- settings = QgsSettings()
- settings.beginGroup('/PostgreSQL/connections/')
- return settings.childGroups()
diff --git a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py
index deb937a8a401..b2ec059c30a2 100644
--- a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py
+++ b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py
@@ -32,10 +32,12 @@
QgsProcessingParameterField,
QgsProcessingParameterString,
QgsProcessingParameterBoolean,
- QgsWkbTypes)
+ QgsWkbTypes,
+ QgsProviderRegistry,
+ QgsProviderConnectionException,
+ QgsAbstractDatabaseProviderConnection)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
-from processing.tools import spatialite
class ImportIntoSpatialite(QgisAlgorithm):
@@ -98,6 +100,7 @@ def tags(self):
def processAlgorithm(self, parameters, context, feedback):
database = self.parameterAsVectorLayer(parameters, self.DATABASE, context)
+
databaseuri = database.dataProvider().dataSourceUri()
uri = QgsDataSourceUri(databaseuri)
if uri.database() is '':
@@ -106,7 +109,12 @@ def processAlgorithm(self, parameters, context, feedback):
elif '|layerid' in databaseuri:
databaseuri = databaseuri[:databaseuri.find('|layerid')]
uri = QgsDataSourceUri('dbname=\'%s\'' % (databaseuri))
- db = spatialite.GeoDB(uri)
+
+ try:
+ md = QgsProviderRegistry.instance().providerMetadata('spatialite')
+ conn = md.createConnection(uri.uri(), {})
+ except QgsProviderConnectionException:
+ raise QgsProcessingException(self.tr('Could not connect to {}').format(uri.uri()))
overwrite = self.parameterAsBoolean(parameters, self.OVERWRITE, context)
createIndex = self.parameterAsBoolean(parameters, self.CREATEINDEX, context)
@@ -148,7 +156,6 @@ def processAlgorithm(self, parameters, context, feedback):
if source.wkbType() == QgsWkbTypes.NoGeometry:
geomColumn = None
- uri = db.uri
uri.setDataSource('', table, geomColumn, '', primaryKeyField)
if encoding:
@@ -178,6 +185,11 @@ def processAlgorithm(self, parameters, context, feedback):
self.tr('Error importing to Spatialite\n{0}').format(exporter.errorMessage()))
if geomColumn and createIndex:
- db.create_spatial_index(table, geomColumn)
+ try:
+ options = QgsAbstractDatabaseProviderConnection.SpatialIndexOptions()
+ options.geometryColumnName = geomColumn
+ conn.createSpatialIndex('', table, options)
+ except QgsProviderConnectionException as e:
+ raise QgsProcessingException(self.tr('Error creating spatial index:\n{0}').format(e))
return {}
diff --git a/python/plugins/processing/algs/qgis/PostGISExecuteAndLoadSQL.py b/python/plugins/processing/algs/qgis/PostGISExecuteAndLoadSQL.py
index d2ed1f94a6af..5eb5ae66e1b1 100644
--- a/python/plugins/processing/algs/qgis/PostGISExecuteAndLoadSQL.py
+++ b/python/plugins/processing/algs/qgis/PostGISExecuteAndLoadSQL.py
@@ -23,20 +23,18 @@
__date__ = 'May 2018'
__copyright__ = '(C) 2018, Anita Graser'
-from qgis.core import (Qgis,
- QgsProcessingException,
+from qgis.core import (QgsProcessingException,
QgsProcessingParameterString,
- QgsApplication,
QgsVectorLayer,
- QgsProject,
+ QgsDataSourceUri,
QgsProcessing,
- QgsProcessingException,
QgsProcessingOutputVectorLayer,
QgsProcessingContext,
QgsProcessingParameterProviderConnection,
- QgsProcessingFeedback)
+ QgsProviderRegistry,
+ QgsProviderConnectionException
+ )
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
-from processing.tools import postgis
class PostGISExecuteAndLoadSQL(QgisAlgorithm):
@@ -92,11 +90,20 @@ def tags(self):
return self.tr('postgis,table,database').split(',')
def processAlgorithm(self, parameters, context, feedback):
- connection = self.parameterAsConnectionName(parameters, self.DATABASE, context)
+ connection_name = self.parameterAsConnectionName(parameters, self.DATABASE, context)
id_field = self.parameterAsString(parameters, self.ID_FIELD, context)
geom_field = self.parameterAsString(
parameters, self.GEOMETRY_FIELD, context)
- uri = postgis.uri_from_name(connection)
+
+ # resolve connection details to uri
+ try:
+ md = QgsProviderRegistry.instance().providerMetadata('postgres')
+ conn = md.createConnection(connection_name)
+ except QgsProviderConnectionException:
+ raise QgsProcessingException(self.tr('Could not retrieve connection details for {}').format(connection_name))
+
+ uri = QgsDataSourceUri(conn.uri())
+
sql = self.parameterAsString(parameters, self.SQL, context)
sql = sql.replace('\n', ' ')
uri.setDataSource("", "(" + sql.rstrip(';') + ")", geom_field, "", id_field)
diff --git a/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py b/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py
index ec60f05fe4b3..ef767841bbcd 100644
--- a/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py
+++ b/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py
@@ -24,10 +24,11 @@
from qgis.core import (
QgsProcessingException,
QgsProcessingParameterString,
- QgsProcessingParameterProviderConnection
+ QgsProcessingParameterProviderConnection,
+ QgsProviderRegistry,
+ QgsProviderConnectionException
)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
-from processing.tools import postgis
class PostGISExecuteSQL(QgisAlgorithm):
@@ -64,13 +65,19 @@ def tags(self):
return self.tr('postgis,database').split(',')
def processAlgorithm(self, parameters, context, feedback):
- connection = self.parameterAsConnectionName(parameters, self.DATABASE, context)
- db = postgis.GeoDB.from_name(connection)
+ connection_name = self.parameterAsConnectionName(parameters, self.DATABASE, context)
+
+ # resolve connection details to uri
+ try:
+ md = QgsProviderRegistry.instance().providerMetadata('postgres')
+ conn = md.createConnection(connection_name)
+ except QgsProviderConnectionException:
+ raise QgsProcessingException(self.tr('Could not retrieve connection details for {}').format(connection_name))
sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ')
try:
- db._exec_sql_and_commit(str(sql))
- except postgis.DbError as e:
- raise QgsProcessingException(
- self.tr('Error executing SQL:\n{0}').format(str(e)))
+ conn.executeSql(sql)
+ except QgsProviderConnectionException as e:
+ raise QgsProcessingException(self.tr('Error executing SQL:\n{0}').format(e))
+
return {}
diff --git a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py
index e775b46cc5fe..9a8fd30766f7 100644
--- a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py
+++ b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py
@@ -26,10 +26,11 @@
QgsProcessingAlgorithm,
QgsProcessingException,
QgsProcessingParameterVectorLayer,
- QgsProcessingParameterString)
+ QgsProcessingParameterString,
+ QgsProviderRegistry,
+ QgsProviderConnectionException)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
-from processing.tools import spatialite
class SpatialiteExecuteSQL(QgisAlgorithm):
@@ -72,12 +73,17 @@ def processAlgorithm(self, parameters, context, feedback):
elif '|layerid' in databaseuri:
databaseuri = databaseuri[:databaseuri.find('|layerid')]
uri = QgsDataSourceUri('dbname=\'%s\'' % (databaseuri))
- db = spatialite.GeoDB(uri)
+
+ try:
+ md = QgsProviderRegistry.instance().providerMetadata('spatialite')
+ conn = md.createConnection(uri.uri(), {})
+ except QgsProviderConnectionException:
+ raise QgsProcessingException(self.tr('Could not connect to {}').format(uri.uri()))
+
sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ')
try:
- db._exec_sql_and_commit(str(sql))
- except spatialite.DbError as e:
- raise QgsProcessingException(
- self.tr('Error executing SQL:\n{0}').format(str(e)))
+ conn.executeSql(sql)
+ except QgsProviderConnectionException as e:
+ raise QgsProcessingException(self.tr('Error executing SQL:\n{0}').format(e))
return {}
diff --git a/python/plugins/processing/algs/qgis/UniqueValues.py b/python/plugins/processing/algs/qgis/UniqueValues.py
index 27670a5c7933..ccf4c65e7c17 100644
--- a/python/plugins/processing/algs/qgis/UniqueValues.py
+++ b/python/plugins/processing/algs/qgis/UniqueValues.py
@@ -79,7 +79,7 @@ def initAlgorithm(self, config=None):
self.tr('Target field(s)'),
parentLayerParameterName=self.INPUT, type=QgsProcessingParameterField.Any, allowMultiple=True))
- self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Unique values'), optional=True, defaultValue=''))
+ self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Unique values'), optional=True, defaultValue=None))
self.addParameter(QgsProcessingParameterFileDestination(self.OUTPUT_HTML_FILE, self.tr('HTML report'), self.tr('HTML files (*.html)'), None, True))
self.addOutput(QgsProcessingOutputNumber(self.TOTAL_VALUES, self.tr('Total unique values')))
diff --git a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py b/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
index cee1ff993a6f..b53b9b66bca6 100644
--- a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
+++ b/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
@@ -47,8 +47,6 @@
from processing.gui.AlgorithmExecutor import execute
from processing.tools import dataobjects
from processing.gui.Postprocessing import handleAlgorithmResults
-from processing.gui.PostgisTableSelector import PostgisTableSelector
-from processing.gui.ParameterGuiUtils import getFileFilter
pluginPath = os.path.dirname(__file__)
with warnings.catch_warnings():
@@ -153,7 +151,7 @@ def setupSpinboxes(self, index):
def selectFile(self):
output = self.alg.parameterDefinition('OUTPUT')
- fileFilter = getFileFilter(output)
+ fileFilter = output.createFileFilter()
settings = QgsSettings()
if settings.contains('/Processing/LastOutputPath'):
diff --git a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
index 92f91e4ae69b..7587c926d673 100644
--- a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
+++ b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
@@ -22,42 +22,35 @@
__copyright__ = '(C) 2014, Arnaud Morvan'
import os
-from collections import OrderedDict
from qgis.PyQt import uic
from qgis.PyQt.QtCore import (
QItemSelectionModel,
- QAbstractTableModel,
QModelIndex,
- QVariant,
- Qt,
pyqtSlot,
- QCoreApplication
+ QCoreApplication,
+ QVariant,
)
+
from qgis.PyQt.QtWidgets import (
QComboBox,
- QHeaderView,
- QLineEdit,
QSpacerItem,
QMessageBox,
- QSpinBox,
- QStyledItemDelegate,
QWidget,
QVBoxLayout
)
from qgis.core import (
QgsApplication,
- QgsExpression,
QgsMapLayerProxyModel,
QgsProcessingFeatureSourceDefinition,
QgsProcessingUtils,
- QgsProject,
QgsVectorLayer,
+ QgsField,
+ QgsFields,
)
-from qgis.gui import QgsFieldExpressionWidget
-from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODELER, DIALOG_BATCH
+from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODELER
from processing.tools import dataobjects
from processing.algs.qgis.FieldsMapper import FieldsMapper
@@ -67,233 +60,6 @@
os.path.join(pluginPath, 'fieldsmappingpanelbase.ui'))
-class FieldsMappingModel(QAbstractTableModel):
-
- fieldTypes = OrderedDict([
- (QVariant.Date, "Date"),
- (QVariant.DateTime, "DateTime"),
- (QVariant.Double, "Double"),
- (QVariant.Int, "Integer"),
- (QVariant.LongLong, "Integer64"),
- (QVariant.String, "String"),
- (QVariant.List, "List"),
- (QVariant.Bool, "Boolean")])
-
- def __init__(self, parent=None):
- super(FieldsMappingModel, self).__init__(parent)
- self._mapping = []
- self._layer = None
- self.configure()
- self._generator = None
-
- def configure(self):
- self.columns = [{
- 'name': 'expression',
- 'type': QgsExpression,
- 'header': self.tr("Source expression"),
- 'persistentEditor': True
- }, {
- 'name': 'name',
- 'type': QVariant.String,
- 'header': self.tr("Field name")
- }, {
- 'name': 'type',
- 'type': QVariant.Type,
- 'header': self.tr("Type"),
- 'persistentEditor': True
- }, {
- 'name': 'length',
- 'type': QVariant.Int,
- 'header': self.tr("Length")
- }, {
- 'name': 'precision',
- 'type': QVariant.Int,
- 'header': self.tr("Precision")
- }]
-
- def columnIndex(self, column_name):
- for index, column in enumerate(self.columns):
- if column['name'] == column_name:
- return index
-
- def mapping(self):
- return self._mapping
-
- def setMapping(self, value):
- self.beginResetModel()
- self._mapping = value
- self.endResetModel()
-
- def setContextGenerator(self, generator):
- self._generator = generator
-
- def contextGenerator(self):
- if self._generator:
- return self._generator
- if self._layer:
- return self._layer
- return QgsProject.instance()
-
- def layer(self):
- return self._layer
-
- def setLayer(self, layer):
- self._layer = layer
-
- def columnCount(self, parent=QModelIndex()):
- if parent.isValid():
- return 0
- return len(self.columns)
-
- def rowCount(self, parent=QModelIndex()):
- if parent.isValid():
- return 0
- try:
- return len(self._mapping)
- except TypeError:
- return 0
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- if role == Qt.DisplayRole:
- if orientation == Qt.Horizontal:
- return self.columns[section]['header']
- if orientation == Qt.Vertical:
- return section
-
- def flags(self, index):
- return Qt.ItemFlags(Qt.ItemIsSelectable |
- Qt.ItemIsEditable |
- Qt.ItemIsEnabled)
-
- def data(self, index, role=Qt.DisplayRole):
- field = self._mapping[index.row()]
- column_def = self.columns[index.column()]
-
- if role == Qt.DisplayRole:
- value = field[column_def['name']]
- if column_def['type'] == QVariant.Type:
- if value == QVariant.Invalid:
- return ''
- return self.fieldTypes[value]
- return value
-
- if role == Qt.EditRole:
- return field[column_def['name']]
-
- if role == Qt.TextAlignmentRole:
- if column_def['type'] in [QVariant.Int]:
- hAlign = Qt.AlignRight
- else:
- hAlign = Qt.AlignLeft
- return hAlign + Qt.AlignVCenter
-
- def setData(self, index, value, role=Qt.EditRole):
- field = self._mapping[index.row()]
- column_def = self.columns[index.column()]
-
- if role == Qt.EditRole:
- field[column_def['name']] = value
- self.dataChanged.emit(index, index)
-
- return True
-
- def insertRows(self, row, count, index=QModelIndex()):
- self.beginInsertRows(index, row, row + count - 1)
-
- for i in range(count):
- field = self.newField()
- self._mapping.insert(row + i, field)
-
- self.endInsertRows()
- return True
-
- def removeRows(self, row, count, index=QModelIndex()):
- self.beginRemoveRows(index, row, row + count - 1)
-
- for i in range(row + count - 1, row + 1):
- self._mapping.pop(i)
-
- self.endRemoveRows()
- return True
-
- def newField(self, field=None):
- if field is None:
- return {'name': '',
- 'type': QVariant.Invalid,
- 'length': 0,
- 'precision': 0,
- 'expression': ''}
-
- return {'name': field.name(),
- 'type': field.type(),
- 'length': field.length(),
- 'precision': field.precision(),
- 'expression': QgsExpression.quotedColumnRef(field.name())}
-
- def loadLayerFields(self, layer):
- self.beginResetModel()
-
- self._mapping = []
- if layer is not None:
- for field in layer.fields():
- self._mapping.append(self.newField(field))
-
- self.endResetModel()
-
-
-class FieldTypeDelegate(QStyledItemDelegate):
-
- def createEditor(self, parent, option, index):
- editor = QComboBox(parent)
- for key, text in FieldsMappingModel.fieldTypes.items():
- editor.addItem(text, key)
- return editor
-
- def setEditorData(self, editor, index):
- if not editor:
- return
- value = index.model().data(index, Qt.EditRole)
- editor.setCurrentIndex(editor.findData(value))
-
- def setModelData(self, editor, model, index):
- if not editor:
- return
- value = editor.currentData()
- if value is None:
- value = QVariant.Invalid
- model.setData(index, value)
-
-
-class ExpressionDelegate(QStyledItemDelegate):
-
- def createEditor(self, parent, option, index):
- editor = QgsFieldExpressionWidget(parent)
- editor.setLayer(index.model().layer())
- editor.registerExpressionContextGenerator(index.model().contextGenerator())
- editor.fieldChanged.connect(self.on_expression_fieldChange)
- editor.setAutoFillBackground(True)
- editor.setAllowEvalErrors(self.parent().dialogType == DIALOG_MODELER)
- return editor
-
- def setEditorData(self, editor, index):
- if not editor:
- return
- value = index.model().data(index, Qt.EditRole)
- editor.setField(value)
-
- def setModelData(self, editor, model, index):
- if not editor:
- return
- (value, isExpression, isValid) = editor.currentField()
- if isExpression is True:
- model.setData(index, value)
- else:
- model.setData(index, QgsExpression.quotedColumnRef(value))
-
- def on_expression_fieldChange(self, fieldName):
- self.commitData.emit(self.sender())
-
-
class FieldsMappingPanel(BASE, WIDGET):
def __init__(self, parent=None):
@@ -308,35 +74,20 @@ def __init__(self, parent=None):
self.configure()
- self.model.modelReset.connect(self.on_model_modelReset)
- self.model.rowsInserted.connect(self.on_model_rowsInserted)
-
self.layerCombo.setAllowEmptyLayer(True)
self.layerCombo.setFilters(QgsMapLayerProxyModel.VectorLayer)
self.dialogType = None
+ self.layer = None
def configure(self):
- self.model = FieldsMappingModel()
- self.fieldsView.setModel(self.model)
-
- self.setDelegate('expression', ExpressionDelegate(self))
- self.setDelegate('type', FieldTypeDelegate(self))
-
- def setContextGenerator(self, generator):
- self.model.setContextGenerator(generator)
-
- def setDelegate(self, column_name, delegate):
- self.fieldsView.setItemDelegateForColumn(
- self.model.columnIndex(column_name),
- delegate)
+ self.model = self.fieldsView.model()
+ self.fieldsView.setDestinationEditable(True)
def setLayer(self, layer):
- if self.model.layer() == layer:
+ if layer is None or self.layer == layer:
return
- self.model.setLayer(layer)
- if layer is None:
- return
- if self.model.rowCount() == 0:
+ self.layer = layer
+ if self.model.rowCount(QModelIndex()) == 0:
self.on_resetButton_clicked()
return
dlg = QMessageBox(self)
@@ -349,15 +100,43 @@ def setLayer(self, layer):
self.on_resetButton_clicked()
def value(self):
- return self.model.mapping()
+ # Value is a dict with name, type, length, precision and expression
+ mapping = self.fieldsView.mapping()
+ results = []
+ for f in mapping:
+ results.append({
+ 'name': f.field.name(),
+ 'type': f.field.type(),
+ 'length': f.field.length(),
+ 'precision': f.field.precision(),
+ 'expression': f.expression,
+ })
+ return results
def setValue(self, value):
- self.model.setMapping(value)
+ if type(value) != dict:
+ return
+ destinationFields = QgsFields()
+ expressions = {}
+ for field_def in value:
+ f = QgsField(field_def.get('name'),
+ field_def.get('type', QVariant.Invalid),
+ field_def.get(QVariant.typeToName(field_def.get('type', QVariant.Invalid))),
+ field_def.get('length', 0),
+ field_def.get('precision', 0))
+ try:
+ expressions[f.name()] = field_def['expressions']
+ except AttributeError:
+ pass
+ destinationFields.append(f)
+
+ if len(destinationFields):
+ self.fieldsView.setDestinationFields(destinationFields, expressions)
@pyqtSlot(bool, name='on_addButton_clicked')
def on_addButton_clicked(self, checked=False):
- rowCount = self.model.rowCount()
- self.model.insertRows(rowCount, 1)
+ rowCount = self.model.rowCount(QModelIndex())
+ self.model.appendField(QgsField('new_field'))
index = self.model.index(rowCount, 0)
self.fieldsView.selectionModel().select(
index,
@@ -367,118 +146,32 @@ def on_addButton_clicked(self, checked=False):
QItemSelectionModel.Current |
QItemSelectionModel.Rows))
self.fieldsView.scrollTo(index)
- self.fieldsView.scrollTo(index)
@pyqtSlot(bool, name='on_deleteButton_clicked')
def on_deleteButton_clicked(self, checked=False):
- sel = self.fieldsView.selectionModel()
- if not sel.hasSelection():
- return
-
- indexes = sel.selectedRows()
- for index in indexes:
- self.model.removeRows(index.row(), 1)
+ self.fieldsView.removeSelectedFields()
@pyqtSlot(bool, name='on_upButton_clicked')
def on_upButton_clicked(self, checked=False):
- sel = self.fieldsView.selectionModel()
- if not sel.hasSelection():
- return
-
- row = sel.selectedRows()[0].row()
- if row == 0:
- return
-
- self.model.insertRows(row - 1, 1)
-
- for column in range(self.model.columnCount()):
- srcIndex = self.model.index(row + 1, column)
- dstIndex = self.model.index(row - 1, column)
- value = self.model.data(srcIndex, Qt.EditRole)
- self.model.setData(dstIndex, value, Qt.EditRole)
-
- self.model.removeRows(row + 1, 1)
-
- sel.select(
- self.model.index(row - 1, 0),
- QItemSelectionModel.SelectionFlags(
- QItemSelectionModel.Clear |
- QItemSelectionModel.Select |
- QItemSelectionModel.Current |
- QItemSelectionModel.Rows))
+ self.fieldsView.moveSelectedFieldsUp()
@pyqtSlot(bool, name='on_downButton_clicked')
def on_downButton_clicked(self, checked=False):
- sel = self.fieldsView.selectionModel()
- if not sel.hasSelection():
- return
-
- row = sel.selectedRows()[0].row()
- if row == self.model.rowCount() - 1:
- return
-
- self.model.insertRows(row + 2, 1)
-
- for column in range(self.model.columnCount()):
- srcIndex = self.model.index(row, column)
- dstIndex = self.model.index(row + 2, column)
- value = self.model.data(srcIndex, Qt.EditRole)
- self.model.setData(dstIndex, value, Qt.EditRole)
-
- self.model.removeRows(row, 1)
-
- sel.select(
- self.model.index(row + 1, 0),
- QItemSelectionModel.SelectionFlags(
- QItemSelectionModel.Clear |
- QItemSelectionModel.Select |
- QItemSelectionModel.Current |
- QItemSelectionModel.Rows))
+ self.fieldsView.moveSelectedFieldsDown()
@pyqtSlot(bool, name='on_resetButton_clicked')
def on_resetButton_clicked(self, checked=False):
- self.model.loadLayerFields(self.model.layer())
-
- def resizeColumns(self):
- header = self.fieldsView.horizontalHeader()
- header.resizeSections(QHeaderView.ResizeToContents)
- for section in range(header.count()):
- size = header.sectionSize(section)
- fieldType = self.model.columns[section]['type']
- if fieldType == QgsExpression:
- header.resizeSection(section, size + 100)
- else:
- header.resizeSection(section, size + 20)
-
- def openPersistentEditors(self, row):
- for index, column in enumerate(self.model.columns):
- if 'persistentEditor' in column.keys() and column['persistentEditor']:
- self.fieldsView.openPersistentEditor(self.model.index(row, index))
- continue
-
- editor = self.fieldsView.indexWidget(self.model.index(row, index))
- if isinstance(editor, QLineEdit):
- editor.deselect()
- if isinstance(editor, QSpinBox):
- lineEdit = editor.findChild(QLineEdit)
- lineEdit.setAlignment(Qt.AlignRight or Qt.AlignVCenter)
- lineEdit.deselect()
-
- def on_model_modelReset(self):
- for row in range(0, self.model.rowCount()):
- self.openPersistentEditors(row)
- self.resizeColumns()
-
- def on_model_rowsInserted(self, parent, start, end):
- for row in range(start, end + 1):
- self.openPersistentEditors(row)
+ """Load fields from layer"""
+ if self.layer:
+ self.fieldsView.setDestinationFields(self.layer.fields())
+ self.fieldsView.setSourceFields(self.layer.fields())
@pyqtSlot(bool, name='on_loadLayerFieldsButton_clicked')
def on_loadLayerFieldsButton_clicked(self, checked=False):
layer = self.layerCombo.currentLayer()
if layer is None:
return
- self.model.loadLayerFields(layer)
+ self.fieldsView.setSourceFields(layer.fields())
class FieldsMappingWidgetWrapper(WidgetWrapper):
@@ -494,8 +187,6 @@ def createWidget(self):
self.panel = self.createPanel()
self.panel.dialogType = self.dialogType
- self.panel.setContextGenerator(self)
-
if self.dialogType == DIALOG_MODELER:
self.combobox = QComboBox()
self.combobox.addItem(QCoreApplication.translate('Processing', '[Preconfigure]'), None)
diff --git a/python/plugins/processing/algs/qgis/ui/fieldsmappingpanelbase.ui b/python/plugins/processing/algs/qgis/ui/fieldsmappingpanelbase.ui
index 62f7511232ca..e42076152353 100644
--- a/python/plugins/processing/algs/qgis/ui/fieldsmappingpanelbase.ui
+++ b/python/plugins/processing/algs/qgis/ui/fieldsmappingpanelbase.ui
@@ -20,18 +20,27 @@
Fields
-
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
0
-
-
-
-
- QAbstractItemView::SingleSelection
-
-
- QAbstractItemView::SelectRows
+
+
+
+ 0
+ 0
+
@@ -109,7 +118,7 @@
-
- Load fields from layer
+ Load fields from template layer
@@ -132,7 +141,7 @@
- Load fields from selected layer
+ Load fields from selected template layer
Load Fields
@@ -150,6 +159,12 @@
1
+
+ QgsFieldMappingWidget
+ QWidget
+
+ 1
+
diff --git a/python/plugins/processing/algs/saga/SagaAlgorithmProvider.py b/python/plugins/processing/algs/saga/SagaAlgorithmProvider.py
index a8c8e049c3a2..85db6150061a 100755
--- a/python/plugins/processing/algs/saga/SagaAlgorithmProvider.py
+++ b/python/plugins/processing/algs/saga/SagaAlgorithmProvider.py
@@ -146,6 +146,11 @@ def supportedOutputVectorLayerExtensions(self):
def supportedOutputTableExtensions(self):
return ['dbf']
+ def flags(self):
+ # push users towards alternative algorithms instead, SAGA algorithms should only be used by experienced
+ # users who understand and can workaround the frequent issues encountered here
+ return QgsProcessingProvider.FlagDeemphasiseSearchResults
+
def supportsNonFileBasedOutput(self):
"""
SAGA Provider doesn't support non file based outputs
diff --git a/python/plugins/processing/core/parameters.py b/python/plugins/processing/core/parameters.py
index fb33f5a77741..731dc20f82bd 100755
--- a/python/plugins/processing/core/parameters.py
+++ b/python/plugins/processing/core/parameters.py
@@ -78,7 +78,7 @@
PARAMETER_MULTIPLE = 'multilayer'
PARAMETER_BAND = 'band'
PARAMETER_LAYOUTITEM = 'layoutitem'
-PARAMETER_MAP_LAYER = 'Map Layer'
+PARAMETER_MAP_LAYER = 'layer'
PARAMETER_RANGE = 'range'
PARAMETER_ENUM = 'enum'
PARAMETER_MATRIX = 'matrix'
@@ -117,6 +117,10 @@ def getParameterFromString(s, context=''):
elif clazz == QgsProcessingParameterMapLayer:
if len(params) > 3:
params[3] = True if params[3].lower() == 'true' else False
+ try:
+ params[4] = [int(p) for p in params[4].split(';')]
+ except:
+ params[4] = [getattr(QgsProcessing, p.split(".")[1]) for p in params[4].split(';')]
elif clazz == QgsProcessingParameterBoolean:
if len(params) > 2:
params[2] = True if params[2].lower() == 'true' else False
diff --git a/python/plugins/processing/gui/AlgorithmDialog.py b/python/plugins/processing/gui/AlgorithmDialog.py
index c6a7628ee46c..6ed92ea6eef8 100644
--- a/python/plugins/processing/gui/AlgorithmDialog.py
+++ b/python/plugins/processing/gui/AlgorithmDialog.py
@@ -21,31 +21,21 @@
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Victor Olaya'
-import os
from pprint import pformat
import time
-from qgis.PyQt.QtCore import QCoreApplication, Qt
-from qgis.PyQt.QtWidgets import QMessageBox, QPushButton, QSizePolicy, QDialogButtonBox
+from qgis.PyQt.QtCore import QCoreApplication
+from qgis.PyQt.QtWidgets import QMessageBox, QPushButton, QDialogButtonBox
from qgis.PyQt.QtGui import QColor, QPalette
from qgis.core import (Qgis,
- QgsProject,
QgsApplication,
- QgsProcessingUtils,
- QgsProcessingParameterDefinition,
QgsProcessingAlgRunnerTask,
QgsProcessingOutputHtml,
- QgsProcessingParameterVectorDestination,
- QgsProcessingOutputLayerDefinition,
- QgsProcessingParameterFeatureSink,
- QgsProcessingParameterRasterDestination,
QgsProcessingAlgorithm,
- QgsProcessingParameters,
QgsProxyProgressTask,
- QgsTaskManager)
+ QgsProcessingFeatureSourceDefinition)
from qgis.gui import (QgsGui,
- QgsMessageBar,
QgsProcessingAlgorithmDialogBase)
from qgis.utils import iface
@@ -57,7 +47,6 @@
from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
from processing.gui.AlgorithmExecutor import executeIterating, execute, execute_in_place
from processing.gui.Postprocessing import handleAlgorithmResults
-from processing.gui.wrappers import WidgetWrapper
from processing.tools import dataobjects
@@ -80,18 +69,24 @@ def __init__(self, alg, in_place=False, parent=None):
if not self.in_place:
self.runAsBatchButton = QPushButton(QCoreApplication.translate("AlgorithmDialog", "Run as Batch Process…"))
self.runAsBatchButton.clicked.connect(self.runAsBatch)
- self.buttonBox().addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment
+ self.buttonBox().addButton(self.runAsBatchButton,
+ QDialogButtonBox.ResetRole) # reset role to ensure left alignment
else:
self.active_layer = iface.activeLayer()
self.runAsBatchButton = None
has_selection = self.active_layer and (self.active_layer.selectedFeatureCount() > 0)
- self.buttonBox().button(QDialogButtonBox.Ok).setText(QCoreApplication.translate("AlgorithmDialog", "Modify Selected Features")
- if has_selection else QCoreApplication.translate("AlgorithmDialog", "Modify All Features"))
- self.buttonBox().button(QDialogButtonBox.Close).setText(QCoreApplication.translate("AlgorithmDialog", "Cancel"))
+ self.buttonBox().button(QDialogButtonBox.Ok).setText(
+ QCoreApplication.translate("AlgorithmDialog", "Modify Selected Features")
+ if has_selection else QCoreApplication.translate("AlgorithmDialog", "Modify All Features"))
+ self.buttonBox().button(QDialogButtonBox.Close).setText(
+ QCoreApplication.translate("AlgorithmDialog", "Cancel"))
self.setWindowTitle(self.windowTitle() + ' | ' + self.active_layer.name())
+ self.updateRunButtonVisibility()
+
def getParametersPanel(self, alg, parent):
- return ParametersPanel(parent, alg, self.in_place)
+ panel = ParametersPanel(parent, alg, self.in_place)
+ return panel
def runAsBatch(self):
self.close()
@@ -99,73 +94,22 @@ def runAsBatch(self):
dlg.show()
dlg.exec_()
+ def resetAdditionalGui(self):
+ if not self.in_place:
+ self.runAsBatchButton.setEnabled(True)
+
+ def blockAdditionalControlsWhileRunning(self):
+ if not self.in_place:
+ self.runAsBatchButton.setEnabled(False)
+
def setParameters(self, parameters):
self.mainWidget().setParameters(parameters)
- def getParameterValues(self):
- parameters = {}
-
+ def createProcessingParameters(self):
if self.mainWidget() is None:
- return parameters
-
- for param in self.algorithm().parameterDefinitions():
- if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
- continue
- if not param.isDestination():
-
- if self.in_place and param.name() == 'INPUT':
- parameters[param.name()] = self.active_layer
- continue
-
- try:
- wrapper = self.mainWidget().wrappers[param.name()]
- except KeyError:
- continue
-
- # For compatibility with 3.x API, we need to check whether the wrapper is
- # the deprecated WidgetWrapper class. If not, it's the newer
- # QgsAbstractProcessingParameterWidgetWrapper class
- # TODO QGIS 4.0 - remove
- if issubclass(wrapper.__class__, WidgetWrapper):
- widget = wrapper.widget
- else:
- widget = wrapper.wrappedWidget()
-
- if widget is None:
- continue
-
- value = wrapper.parameterValue()
- parameters[param.name()] = value
-
- if not param.checkValueIsAcceptable(value):
- raise AlgorithmDialogBase.InvalidParameterValue(param, widget)
- else:
- if self.in_place and param.name() == 'OUTPUT':
- parameters[param.name()] = 'memory:'
- continue
-
- dest_project = None
- if not param.flags() & QgsProcessingParameterDefinition.FlagHidden and \
- isinstance(param, (QgsProcessingParameterRasterDestination,
- QgsProcessingParameterFeatureSink,
- QgsProcessingParameterVectorDestination)):
- if self.mainWidget().checkBoxes[param.name()].isChecked():
- dest_project = QgsProject.instance()
-
- widget = self.mainWidget().outputWidgets[param.name()]
- value = widget.getValue()
-
- if value and isinstance(value, QgsProcessingOutputLayerDefinition):
- value.destinationProject = dest_project
- if value:
- parameters[param.name()] = value
- if param.isDestination():
- context = dataobjects.createContext()
- ok, error = self.algorithm().provider().isSupportedOutputValue(value, param, context)
- if not ok:
- raise AlgorithmDialogBase.InvalidOutputExtension(widget, error)
-
- return self.algorithm().preprocessParameters(parameters)
+ return {}
+ else:
+ return self.mainWidget().createProcessingParameters()
def runAlgorithm(self):
self.feedback = self.createFeedback()
@@ -173,7 +117,7 @@ def runAlgorithm(self):
checkCRS = ProcessingConfig.getSetting(ProcessingConfig.WARN_UNMATCHING_CRS)
try:
- parameters = self.getParameterValues()
+ parameters = self.createProcessingParameters()
if checkCRS and not self.algorithm().validateInputCrs(parameters, self.context):
reply = QMessageBox.question(self, self.tr("Unmatching CRS's"),
@@ -189,15 +133,17 @@ def runAlgorithm(self):
QMessageBox.warning(
self, self.tr('Unable to execute algorithm'), msg)
return
- self.runButton().setEnabled(False)
+
+ self.blockControlsWhileRunning()
+ self.setExecutedAnyResult(True)
self.cancelButton().setEnabled(False)
- buttons = self.mainWidget().iterateButtons
+
self.iterateParam = None
- for i in range(len(list(buttons.values()))):
- button = list(buttons.values())[i]
- if button.isChecked():
- self.iterateParam = list(buttons.keys())[i]
+ for param in self.algorithm().parameterDefinitions():
+ if isinstance(parameters.get(param.name(), None), QgsProcessingFeatureSourceDefinition) and parameters[
+ param.name()].flags & QgsProcessingFeatureSourceDefinition.FlagCreateIndividualOutputPerInputFeature:
+ self.iterateParam = param.name()
break
self.clearProgress()
@@ -208,12 +154,14 @@ def runAlgorithm(self):
self.setProgressText(QCoreApplication.translate('AlgorithmDialog', 'Processing algorithm…'))
self.setInfo(
- QCoreApplication.translate('AlgorithmDialog', 'Algorithm \'{0}\' starting… ').format(self.algorithm().displayName()), escapeHtml=False)
+ QCoreApplication.translate('AlgorithmDialog', 'Algorithm \'{0}\' starting… ').format(
+ self.algorithm().displayName()), escapeHtml=False)
self.feedback.pushInfo(self.tr('Input parameters:'))
display_params = []
for k, v in parameters.items():
- display_params.append("'" + k + "' : " + self.algorithm().parameterDefinition(k).valueAsPythonString(v, self.context))
+ display_params.append(
+ "'" + k + "' : " + self.algorithm().parameterDefinition(k).valueAsPythonString(v, self.context))
self.feedback.pushCommandInfo('{ ' + ', '.join(display_params) + ' }')
self.feedback.pushInfo('')
start_time = time.time()
@@ -244,9 +192,11 @@ def runAlgorithm(self):
def on_complete(ok, results):
if ok:
- self.feedback.pushInfo(self.tr('Execution completed in {0:0.2f} seconds').format(time.time() - start_time))
+ self.feedback.pushInfo(
+ self.tr('Execution completed in {0:0.2f} seconds').format(time.time() - start_time))
self.feedback.pushInfo(self.tr('Results:'))
- self.feedback.pushCommandInfo(pformat(results))
+ r = {k: v for k, v in results.items() if k not in ('CHILD_RESULTS', 'CHILD_INPUTS')}
+ self.feedback.pushCommandInfo(pformat(r))
else:
self.feedback.reportError(
self.tr('Execution failed after {0:0.2f} seconds').format(time.time() - start_time))
@@ -275,7 +225,9 @@ def on_complete(ok, results):
task.executed.connect(on_complete)
self.setCurrentTask(task)
else:
- self.proxy_progress = QgsProxyProgressTask(QCoreApplication.translate("AlgorithmDialog", "Executing “{}”").format(self.algorithm().displayName()))
+ self.proxy_progress = QgsProxyProgressTask(
+ QCoreApplication.translate("AlgorithmDialog", "Executing “{}”").format(
+ self.algorithm().displayName()))
QgsApplication.taskManager().addTask(self.proxy_progress)
self.feedback.progressChanged.connect(self.proxy_progress.setProxyProgress)
self.feedback_dialog = self.createProgressDialog()
@@ -298,8 +250,9 @@ def on_complete(ok, results):
except:
pass
self.messageBar().clearWidgets()
- self.messageBar().pushMessage("", self.tr("Wrong or missing parameter value: {0}").format(e.parameter.description()),
- level=Qgis.Warning, duration=5)
+ self.messageBar().pushMessage("", self.tr("Wrong or missing parameter value: {0}").format(
+ e.parameter.description()),
+ level=Qgis.Warning, duration=5)
except AlgorithmDialogBase.InvalidOutputExtension as e:
try:
self.buttonBox().accepted.connect(lambda e=e:
@@ -321,7 +274,8 @@ def finish(self, successful, result, context, feedback, in_place=False):
# add html results to results dock
for out in self.algorithm().outputDefinitions():
if isinstance(out, QgsProcessingOutputHtml) and out.name() in result and result[out.name()]:
- resultsList.addResult(icon=self.algorithm().icon(), name=out.description(), timestamp=time.localtime(),
+ resultsList.addResult(icon=self.algorithm().icon(), name=out.description(),
+ timestamp=time.localtime(),
result=result[out.name()])
if not handleAlgorithmResults(self.algorithm(), context, feedback, not keepOpen, result):
self.resetGui()
@@ -330,6 +284,7 @@ def finish(self, successful, result, context, feedback, in_place=False):
self.setExecuted(True)
self.setResults(result)
self.setInfo(self.tr('Algorithm \'{0}\' finished').format(self.algorithm().displayName()), escapeHtml=False)
+ self.algorithmFinished.emit(successful, result)
if not in_place and not keepOpen:
self.close()
diff --git a/python/plugins/processing/gui/AlgorithmLocatorFilter.py b/python/plugins/processing/gui/AlgorithmLocatorFilter.py
index 6722593f1cbc..cebf26a55478 100644
--- a/python/plugins/processing/gui/AlgorithmLocatorFilter.py
+++ b/python/plugins/processing/gui/AlgorithmLocatorFilter.py
@@ -30,7 +30,8 @@
QgsProcessing,
QgsWkbTypes,
QgsMapLayerType,
- QgsFields)
+ QgsFields,
+ QgsStringUtils)
from processing.gui.MessageBarProgress import MessageBarProgress
from processing.gui.MessageDialog import MessageDialog
from processing.gui.AlgorithmDialog import AlgorithmDialog
@@ -72,17 +73,31 @@ def fetchResults(self, string, context, feedback):
a.flags() & QgsProcessingAlgorithm.FlagKnownIssues:
continue
- if QgsLocatorFilter.stringMatches(a.displayName(), string) or [t for t in a.tags() if QgsLocatorFilter.stringMatches(t, string)] or \
- (context.usingPrefix and not string):
- result = QgsLocatorResult()
- result.filter = self
- result.displayString = a.displayName()
- result.icon = a.icon()
- result.userData = a.id()
- if string and QgsLocatorFilter.stringMatches(a.displayName(), string):
- result.score = float(len(string)) / len(a.displayName())
- else:
- result.score = 0
+ result = QgsLocatorResult()
+ result.filter = self
+ result.displayString = a.displayName()
+ result.icon = a.icon()
+ result.userData = a.id()
+ result.score = 0
+
+ if (context.usingPrefix and not string):
+ self.resultFetched.emit(result)
+
+ if not string:
+ return
+
+ string = string.lower()
+ tagScore = 0
+ tags = [*a.tags(), a.provider().name(), a.group()]
+
+ for t in tags:
+ if string in t.lower():
+ tagScore = 1
+ break
+
+ result.score = QgsStringUtils.fuzzyScore(result.displayString, string) * 0.5 + tagScore * 0.5
+
+ if result.score > 0:
self.resultFetched.emit(result)
def triggerResult(self, result):
diff --git a/python/plugins/processing/gui/BatchAlgorithmDialog.py b/python/plugins/processing/gui/BatchAlgorithmDialog.py
index e4fd423f9b26..643dd65a8dcc 100644
--- a/python/plugins/processing/gui/BatchAlgorithmDialog.py
+++ b/python/plugins/processing/gui/BatchAlgorithmDialog.py
@@ -77,6 +77,8 @@ def __init__(self, alg, parent=None):
self.btnRunSingle.clicked.connect(self.runAsSingle)
self.buttonBox().addButton(self.btnRunSingle, QDialogButtonBox.ResetRole) # reset role to ensure left alignment
+ self.updateRunButtonVisibility()
+
def runAsSingle(self):
self.close()
@@ -85,6 +87,12 @@ def runAsSingle(self):
dlg.show()
dlg.exec_()
+ def resetAdditionalGui(self):
+ self.btnRunSingle.setEnabled(True)
+
+ def blockAdditionalControlsWhileRunning(self):
+ self.btnRunSingle.setEnabled(False)
+
def runAlgorithm(self):
alg_parameters = []
@@ -94,8 +102,11 @@ def runAlgorithm(self):
project = QgsProject.instance() if load_layers else None
for row in range(self.mainWidget().batchRowCount()):
- parameters = self.mainWidget().parametersForRow(row, destinationProject=project, warnOnInvalid=True)
- alg_parameters.append(parameters)
+ parameters, ok = self.mainWidget().parametersForRow(row, destinationProject=project, warnOnInvalid=True)
+ if ok:
+ alg_parameters.append(parameters)
+ if not alg_parameters:
+ return
task = QgsScopedProxyProgressTask(self.tr('Batch Processing - {0}').format(self.algorithm().displayName()))
multi_feedback = BatchFeedback(len(alg_parameters), feedback)
@@ -106,7 +117,8 @@ def runAlgorithm(self):
with OverrideCursor(Qt.WaitCursor):
- self.mainWidget().setEnabled(False)
+ self.blockControlsWhileRunning()
+ self.setExecutedAnyResult(True)
self.cancelButton().setEnabled(True)
# Make sure the Log tab is visible before executing the algorithm
@@ -178,7 +190,6 @@ def finish(self, algorithm_results, errors):
self.loadHTMLResults(results['results'], count)
self.createSummaryTable(algorithm_results, errors)
- self.mainWidget().setEnabled(True)
self.resetGui()
def loadHTMLResults(self, results, num):
diff --git a/python/plugins/processing/gui/BatchInputSelectionPanel.py b/python/plugins/processing/gui/BatchInputSelectionPanel.py
index f04c851250b3..226a2e5ef368 100644
--- a/python/plugins/processing/gui/BatchInputSelectionPanel.py
+++ b/python/plugins/processing/gui/BatchInputSelectionPanel.py
@@ -43,7 +43,6 @@
from processing.gui.MultipleInputDialog import MultipleInputDialog
-from processing.gui.ParameterGuiUtils import getFileFilter
from processing.tools import dataobjects
@@ -174,7 +173,7 @@ def showFileDialog(self, seldir):
if not seldir:
ret, selected_filter = QFileDialog.getOpenFileNames(
- self, self.tr('Select Files'), path, getFileFilter(self.param)
+ self, self.tr('Select Files'), path, self.param.createFileFilter()
)
else:
ret = QFileDialog.getExistingDirectory(self, self.tr('Select Directory'), path)
diff --git a/python/plugins/processing/gui/BatchOutputSelectionPanel.py b/python/plugins/processing/gui/BatchOutputSelectionPanel.py
index e24741dfcf17..ced93362ffd8 100644
--- a/python/plugins/processing/gui/BatchOutputSelectionPanel.py
+++ b/python/plugins/processing/gui/BatchOutputSelectionPanel.py
@@ -37,7 +37,6 @@
from qgis.PyQt.QtWidgets import QWidget, QPushButton, QLineEdit, QHBoxLayout, QSizePolicy, QFileDialog
from processing.gui.AutofillDialog import AutofillDialog
-from processing.gui.ParameterGuiUtils import getFileFilter
class BatchOutputSelectionPanel(QWidget):
@@ -70,7 +69,7 @@ def showSelectionDialog(self):
self.selectDirectory()
return
- filefilter = getFileFilter(self.output)
+ filefilter = self.output.createFileFilter()
settings = QgsSettings()
if settings.contains('/Processing/LastBatchOutputPath'):
path = str(settings.value('/Processing/LastBatchOutputPath'))
diff --git a/python/plugins/processing/gui/BatchPanel.py b/python/plugins/processing/gui/BatchPanel.py
index 2b7be0d1762b..1042762d3976 100644
--- a/python/plugins/processing/gui/BatchPanel.py
+++ b/python/plugins/processing/gui/BatchPanel.py
@@ -24,6 +24,7 @@
import os
import json
import warnings
+from pathlib import Path
from qgis.PyQt import uic
from qgis.PyQt.QtWidgets import (
@@ -60,16 +61,21 @@
QgsProcessingParameterFeatureSource,
QgsProcessingParameterRasterDestination,
QgsProcessingParameterVectorDestination,
+ QgsProcessingParameterMultipleLayers,
QgsProcessingParameterFeatureSink,
QgsProcessingOutputLayerDefinition,
QgsExpressionContextUtils,
- QgsExpression
+ QgsProcessing,
+ QgsExpression,
+ QgsRasterLayer,
+ QgsProcessingUtils
)
from qgis.gui import (
QgsProcessingParameterWidgetContext,
QgsProcessingContextGenerator,
QgsFindFilesByPatternDialog,
- QgsExpressionBuilderDialog
+ QgsExpressionBuilderDialog,
+ QgsPanelWidget
)
from qgis.utils import iface
@@ -78,6 +84,7 @@
from processing.tools import dataobjects
from processing.tools.dataobjects import createContext
+from processing.gui.MultipleInputDialog import MultipleInputDialog
pluginPath = os.path.split(os.path.dirname(__file__))[0]
@@ -137,7 +144,8 @@ def createMenu(self):
QgsProcessingParameterRasterLayer,
QgsProcessingParameterMeshLayer,
QgsProcessingParameterVectorLayer,
- QgsProcessingParameterFeatureSource)):
+ QgsProcessingParameterFeatureSource,
+ QgsProcessingParameterMultipleLayers)):
self.menu.addSeparator()
find_by_pattern_action = QAction(QCoreApplication.translate('BatchPanel', 'Add Files by Pattern…'),
self.menu)
@@ -145,6 +153,22 @@ def createMenu(self):
find_by_pattern_action.setToolTip(self.tr('Adds files by a file pattern match'))
self.menu.addAction(find_by_pattern_action)
+ select_file_action = QAction(
+ QCoreApplication.translate('BatchInputSelectionPanel', 'Select Files…'), self.menu)
+ select_file_action.triggered.connect(self.showFileSelectionDialog)
+ self.menu.addAction(select_file_action)
+
+ select_directory_action = QAction(
+ QCoreApplication.translate('BatchInputSelectionPanel', 'Add All Files from a Directory…'), self.menu)
+ select_directory_action.triggered.connect(self.showDirectorySelectionDialog)
+ self.menu.addAction(select_directory_action)
+
+ if not isinstance(self.parameterDefinition, QgsProcessingParameterFile):
+ select_layer_action = QAction(
+ QCoreApplication.translate('BatchInputSelectionPanel', 'Select from Open Layers…'), self.menu)
+ select_layer_action.triggered.connect(self.showLayerSelectionDialog)
+ self.menu.addAction(select_layer_action)
+
def fillDown(self):
"""
Copy the top value down
@@ -190,6 +214,117 @@ def addFilesByPattern(self):
for row, file in enumerate(files):
self.setRowValue(first_row + row, file, context)
+ def showFileSelectionDialog(self):
+ settings = QgsSettings()
+ if settings.contains('/Processing/LastInputPath'):
+ path = str(settings.value('/Processing/LastInputPath'))
+ else:
+ path = QDir.homePath()
+
+ files, selected_filter = QFileDialog.getOpenFileNames(
+ self, self.tr('Select Files'), path, self.parameterDefinition.createFileFilter()
+ )
+
+ if not files:
+ return
+
+ settings.setValue('/Processing/LastInputPath', os.path.dirname(str(files[0])))
+
+ context = dataobjects.createContext()
+
+ first_row = self.panel.batchRowCount() if self.panel.batchRowCount() > 1 else 0
+ for row, file in enumerate(files):
+ self.setRowValue(first_row + row, file, context)
+
+ def showDirectorySelectionDialog(self):
+ settings = QgsSettings()
+ if settings.contains('/Processing/LastInputPath'):
+ path = str(settings.value('/Processing/LastInputPath'))
+ else:
+ path = QDir.homePath()
+
+ folder = QFileDialog.getExistingDirectory(self, self.tr('Select Directory'), path)
+ if not folder:
+ return
+
+ settings.setValue('/Processing/LastInputPath', folder)
+
+ files = []
+ for pp in Path(folder).rglob("*"):
+ if not pp.is_file():
+ continue
+
+ p = pp.as_posix()
+
+ if isinstance(self.parameterDefinition, QgsProcessingParameterRasterLayer) or \
+ (isinstance(self.parameterDefinition,
+ QgsProcessingParameterMultipleLayers) and self.param.layerType() == QgsProcessing.TypeRaster):
+ if not QgsRasterLayer.isValidRasterFileName(p):
+ continue
+
+ files.append(p)
+
+ if not files:
+ return
+
+ context = dataobjects.createContext()
+
+ first_row = self.panel.batchRowCount() if self.panel.batchRowCount() > 1 else 0
+ for row, file in enumerate(files):
+ self.setRowValue(first_row + row, file, context)
+
+ def showLayerSelectionDialog(self):
+ layers = []
+ if isinstance(self.parameterDefinition, QgsProcessingParameterRasterLayer):
+ layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance())
+ elif isinstance(self.parameterDefinition,
+ QgsProcessingParameterMultipleLayers) and self.parameterDefinition.layerType() == QgsProcessing.TypeRaster:
+ layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance())
+ elif isinstance(self.parameterDefinition, QgsProcessingParameterVectorLayer):
+ layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance())
+ elif isinstance(self.parameterDefinition, QgsProcessingParameterMapLayer):
+ layers = QgsProcessingUtils.compatibleLayers(QgsProject.instance())
+ elif isinstance(self.parameterDefinition, QgsProcessingParameterMeshLayer):
+ layers = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance())
+ elif isinstance(self.parameterDefinition,
+ QgsProcessingParameterMultipleLayers) and self.parameterDefinition.layerType() == QgsProcessing.TypeMesh:
+ layers = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance())
+ else:
+ datatypes = [QgsProcessing.TypeVectorAnyGeometry]
+ if isinstance(self.parameterDefinition, QgsProcessingParameterFeatureSource):
+ datatypes = self.parameterDefinition.dataTypes()
+ elif isinstance(self.parameterDefinition, QgsProcessingParameterMultipleLayers):
+ datatypes = [self.parameterDefinition.layerType()]
+
+ if QgsProcessing.TypeVectorAnyGeometry not in datatypes:
+ layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), datatypes)
+ else:
+ layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance())
+
+ dlg = MultipleInputDialog([layer.name() for layer in layers])
+ dlg.exec_()
+
+ def generate_layer_id(layer):
+ # prefer layer name if unique
+ if len([l for l in layers if l.name().lower() == layer.name().lower()]) == 1:
+ return layer.name()
+ else:
+ # otherwise fall back to layer id
+ return layer.id()
+
+ if not dlg.selectedoptions:
+ return
+
+ selected = dlg.selectedoptions
+
+ context = dataobjects.createContext()
+
+ first_row = self.panel.batchRowCount() if self.panel.batchRowCount() > 1 else 0
+ for row, selected_idx in enumerate(selected):
+ layer = layers[selected_idx]
+ value = generate_layer_id(layer)
+ self.setRowValue(first_row + row, value, context)
+
def calculateByExpression(self):
"""
Calculates parameter values by evaluating expressions.
@@ -210,7 +345,7 @@ def populateByExpression(self, adding=False):
expression_context = context.expressionContext()
# use the first row parameter values as a preview during expression creation
- params = self.panel.parametersForRow(0, warnOnInvalid=False)
+ params, ok = self.panel.parametersForRow(0, warnOnInvalid=False)
alg_scope = QgsExpressionContextUtils.processingAlgorithmScope(self.panel.alg, params, context)
# create explicit variables corresponding to every parameter
@@ -247,7 +382,7 @@ def populateByExpression(self, adding=False):
self.setRowValue(row + first_row, value, context)
else:
for row in range(self.panel.batchRowCount()):
- params = self.panel.parametersForRow(row, warnOnInvalid=False)
+ params, ok = self.panel.parametersForRow(row, warnOnInvalid=False)
# remove previous algorithm scope -- we need to rebuild this completely, using the
# other parameter values from the current row
@@ -269,7 +404,7 @@ def populateByExpression(self, adding=False):
self.setRowValue(row, value, context)
-class BatchPanel(BASE, WIDGET):
+class BatchPanel(QgsPanelWidget, WIDGET):
PARAMETERS = "PARAMETERS"
OUTPUTS = "OUTPUTS"
@@ -451,10 +586,9 @@ def save(self):
value = wrapper.parameterValue()
if not param.checkValueIsAcceptable(value, context):
- self.parent.messageBar().pushMessage("", self.tr(
- 'Wrong or missing parameter value: {0} (row {1})').format(
- param.description(), row + 1),
- level=Qgis.Warning, duration=5)
+ msg = self.tr('Wrong or missing parameter value: {0} (row {1})').format(
+ param.description(), row + 1)
+ self.parent.messageBar().pushMessage("", msg, level=Qgis.Warning, duration=5)
return
algParams[param.name()] = param.valueAsPythonString(value, context)
col += 1
@@ -494,6 +628,7 @@ def setCellWrapper(self, row, column, wrapper, context):
widget_context = QgsProcessingParameterWidgetContext()
widget_context.setProject(QgsProject.instance())
if iface is not None:
+ widget_context.setActiveLayer(iface.activeLayer())
widget_context.setMapCanvas(iface.mapCanvas())
widget_context.setMessageBar(self.parent.messageBar())
@@ -600,7 +735,7 @@ def parametersForRow(self, row, destinationProject=None, warnOnInvalid=True):
self.tr('Wrong or missing parameter value: {0} (row {1})').format(
param.description(), row + 1),
level=Qgis.Warning, duration=5)
- return {}
+ return {}, False
col += 1
count_visible_outputs = 0
for out in self.alg.destinationParameterDefinitions():
@@ -620,8 +755,7 @@ def parametersForRow(self, row, destinationProject=None, warnOnInvalid=True):
parameters[out.name()] = text
col += 1
else:
- self.parent.messageBar().pushMessage("", self.tr('Wrong or missing output value: {0} (row {1})').format(
- out.description(), row + 1),
- level=Qgis.Warning, duration=5)
- return {}
- return parameters
+ msg = self.tr('Wrong or missing output value: {0} (row {1})').format(out.description(), row + 1)
+ self.parent.messageBar().pushMessage("", msg, level=Qgis.Warning, duration=5)
+ return {}, False
+ return parameters, True
diff --git a/python/plugins/processing/gui/DestinationSelectionPanel.py b/python/plugins/processing/gui/DestinationSelectionPanel.py
deleted file mode 100644
index f92e4bebc826..000000000000
--- a/python/plugins/processing/gui/DestinationSelectionPanel.py
+++ /dev/null
@@ -1,357 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- OutputSelectionPanel.py
- ---------------------
- Date : August 2012
- Copyright : (C) 2012 by Victor Olaya
- Email : volayaf at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-__author__ = 'Victor Olaya'
-__date__ = 'August 2012'
-__copyright__ = '(C) 2012, Victor Olaya'
-
-import re
-import os
-import warnings
-
-from qgis.PyQt import uic
-from qgis.PyQt.QtCore import QCoreApplication, QDir, pyqtSignal, QFileInfo
-from qgis.PyQt.QtWidgets import QDialog, QMenu, QAction, QFileDialog, QInputDialog
-from qgis.PyQt.QtGui import QCursor
-from qgis.gui import QgsEncodingSelectionDialog
-from qgis.core import (QgsProcessing,
- QgsDataSourceUri,
- QgsCredentials,
- QgsExpression,
- QgsSettings,
- QgsProcessingParameterFeatureSink,
- QgsProcessingParameterRasterDestination,
- QgsProcessingOutputLayerDefinition,
- QgsProcessingParameterDefinition,
- QgsProcessingParameterFileDestination,
- QgsProcessingParameterFolderDestination,
- QgsProcessingParameterVectorDestination)
-from processing.core.ProcessingConfig import ProcessingConfig
-from processing.tools.dataobjects import createContext
-from processing.gui.PostgisTableSelector import PostgisTableSelector
-from processing.gui.ParameterGuiUtils import getFileFilter
-
-pluginPath = os.path.split(os.path.dirname(__file__))[0]
-
-with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=DeprecationWarning)
- WIDGET, BASE = uic.loadUiType(
- os.path.join(pluginPath, 'ui', 'widgetBaseSelector.ui'))
-
-
-class DestinationSelectionPanel(BASE, WIDGET):
-
- SAVE_TO_TEMP_FILE = QCoreApplication.translate(
- 'DestinationSelectionPanel', '[Save to temporary file]')
- SAVE_TO_TEMP_FOLDER = QCoreApplication.translate(
- 'DestinationSelectionPanel', '[Save to temporary folder]')
- SAVE_TO_TEMP_LAYER = QCoreApplication.translate(
- 'DestinationSelectionPanel', '[Create temporary layer]')
- SKIP_OUTPUT = QCoreApplication.translate(
- 'DestinationSelectionPanel', '[Skip output]')
-
- skipOutputChanged = pyqtSignal(bool)
- destinationChanged = pyqtSignal()
-
- def __init__(self, parameter, alg, default_selection=False):
- super(DestinationSelectionPanel, self).__init__(None)
- self.setupUi(self)
-
- self.parameter = parameter
- self.alg = alg
- self.default_selection = default_selection
- settings = QgsSettings()
- self.encoding = settings.value('/Processing/encoding', 'System')
- self.use_temporary = True
-
- self.setValue(self.parameter.defaultValue())
-
- self.btnSelect.clicked.connect(self.selectOutput)
- self.leText.textEdited.connect(self.textChanged)
-
- def textChanged(self):
- self.use_temporary = not self.leText.text()
- self.destinationChanged.emit()
-
- def outputIsSkipped(self):
- """
- Returns true if output is set to be skipped
- """
- return not self.leText.text() and not self.use_temporary
-
- def skipOutput(self):
- self.leText.setPlaceholderText(self.SKIP_OUTPUT)
- self.leText.setText('')
- self.use_temporary = False
- self.skipOutputChanged.emit(True)
- self.destinationChanged.emit()
-
- def selectOutput(self):
- popupMenu = QMenu()
-
- if not self.default_selection:
- if self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
- actionSkipOutput = QAction(
- self.tr('Skip Output'), self.btnSelect)
- actionSkipOutput.triggered.connect(self.skipOutput)
- popupMenu.addAction(actionSkipOutput)
-
- if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
- and self.parameter.supportsNonFileBasedOutput():
- # use memory layers for temporary layers if supported
- actionSaveToTemp = QAction(
- self.tr('Create Temporary Layer'), self.btnSelect)
- elif isinstance(self.parameter, QgsProcessingParameterFolderDestination):
- actionSaveToTemp = QAction(
- self.tr('Save to a Temporary Directory'), self.btnSelect)
- else:
- actionSaveToTemp = QAction(
- self.tr('Save to a Temporary File'), self.btnSelect)
- actionSaveToTemp.triggered.connect(self.saveToTemporary)
- popupMenu.addAction(actionSaveToTemp)
-
- if isinstance(self.parameter, QgsProcessingParameterFolderDestination):
- actionSaveToFile = QAction(
- QCoreApplication.translate('DestinationSelectionPanel', 'Save to Directory…'), self.btnSelect)
- actionSaveToFile.triggered.connect(self.selectDirectory)
- else:
- actionSaveToFile = QAction(
- QCoreApplication.translate('DestinationSelectionPanel', 'Save to File…'), self.btnSelect)
- actionSaveToFile.triggered.connect(self.selectFile)
- popupMenu.addAction(actionSaveToFile)
-
- if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
- and self.parameter.supportsNonFileBasedOutput():
- actionSaveToGpkg = QAction(
- QCoreApplication.translate('DestinationSelectionPanel', 'Save to GeoPackage…'), self.btnSelect)
- actionSaveToGpkg.triggered.connect(self.saveToGeopackage)
- popupMenu.addAction(actionSaveToGpkg)
- actionSaveToPostGIS = QAction(
- QCoreApplication.translate('DestinationSelectionPanel', 'Save to PostGIS Table…'), self.btnSelect)
- actionSaveToPostGIS.triggered.connect(self.saveToPostGIS)
- settings = QgsSettings()
- settings.beginGroup('/PostgreSQL/connections/')
- names = settings.childGroups()
- settings.endGroup()
- actionSaveToPostGIS.setEnabled(bool(names))
- popupMenu.addAction(actionSaveToPostGIS)
-
- actionSetEncoding = QAction(
- QCoreApplication.translate('DestinationSelectionPanel', 'Change File Encoding ({})…').format(self.encoding), self.btnSelect)
- actionSetEncoding.triggered.connect(self.selectEncoding)
- popupMenu.addAction(actionSetEncoding)
-
- popupMenu.exec_(QCursor.pos())
-
- def saveToTemporary(self):
- if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.supportsNonFileBasedOutput():
- self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER)
- elif isinstance(self.parameter, QgsProcessingParameterFolderDestination):
- self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FOLDER)
- else:
- self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FILE)
- self.leText.setText('')
- self.use_temporary = True
- self.skipOutputChanged.emit(False)
- self.destinationChanged.emit()
-
- def saveToPostGIS(self):
- dlg = PostgisTableSelector(self, self.parameter.name().lower())
- dlg.exec_()
- if dlg.connection:
- self.use_temporary = False
- settings = QgsSettings()
- mySettings = '/PostgreSQL/connections/' + dlg.connection
- dbname = settings.value(mySettings + '/database')
- user = settings.value(mySettings + '/username')
- host = settings.value(mySettings + '/host')
- port = settings.value(mySettings + '/port')
- password = settings.value(mySettings + '/password')
- uri = QgsDataSourceUri()
- uri.setConnection(host, str(port), dbname, user, password)
- uri.setDataSource(dlg.schema, dlg.table,
- "the_geom" if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None)
-
- connInfo = uri.connectionInfo()
- (success, user, passwd) = QgsCredentials.instance().get(connInfo, None, None)
- if success:
- QgsCredentials.instance().put(connInfo, user, passwd)
- self.leText.setText("postgis:" + uri.uri())
-
- self.skipOutputChanged.emit(False)
- self.destinationChanged.emit()
-
- def saveToGeopackage(self):
- file_filter = self.tr('GeoPackage files (*.gpkg);;All files (*.*)', 'OutputFile')
-
- settings = QgsSettings()
- if settings.contains('/Processing/LastOutputPath'):
- path = settings.value('/Processing/LastOutputPath')
- else:
- path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
-
- filename, filter = QFileDialog.getSaveFileName(self, self.tr("Save to GeoPackage"), path,
- file_filter, options=QFileDialog.DontConfirmOverwrite)
-
- if not filename:
- return
-
- layer_name, ok = QInputDialog.getText(self, self.tr('Save to GeoPackage'), self.tr('Layer name'), text=self.parameter.name().lower())
- if ok:
- self.use_temporary = False
- if not filename.lower().endswith('.gpkg'):
- filename += '.gpkg'
- settings.setValue('/Processing/LastOutputPath',
- os.path.dirname(filename))
-
- uri = QgsDataSourceUri()
- uri.setDatabase(filename)
- uri.setDataSource('', layer_name,
- 'geom' if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None)
- self.leText.setText("ogr:" + uri.uri())
-
- self.skipOutputChanged.emit(False)
- self.destinationChanged.emit()
-
- def selectFile(self):
- file_filter = getFileFilter(self.parameter)
- settings = QgsSettings()
- if isinstance(self.parameter, (QgsProcessingParameterFeatureSink, QgsProcessingParameterVectorDestination)):
- last_ext_path = '/Processing/LastVectorOutputExt'
- last_ext = settings.value(last_ext_path, '.{}'.format(self.parameter.defaultFileExtension()))
- elif isinstance(self.parameter, QgsProcessingParameterRasterDestination):
- last_ext_path = '/Processing/LastRasterOutputExt'
- last_ext = settings.value(last_ext_path, '.{}'.format(self.parameter.defaultFileExtension()))
- else:
- last_ext_path = None
- last_ext = None
-
- # get default filter
- filters = file_filter.split(';;')
- try:
- last_filter = [f for f in filters if '*{}'.format(last_ext) in f.lower()][0]
- except IndexError:
- last_filter = None
-
- if settings.contains('/Processing/LastOutputPath'):
- path = settings.value('/Processing/LastOutputPath')
- else:
- path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
-
- filename, filter = QFileDialog.getSaveFileName(self, self.tr("Save file"), path,
- file_filter, last_filter)
- if filename:
- self.use_temporary = False
- if not filename.lower().endswith(
- tuple(re.findall("\\*(\\.[a-z]{1,10})", file_filter))):
- ext = re.search("\\*(\\.[a-z]{1,10})", filter)
- if ext:
- filename += ext.group(1)
- self.leText.setText(filename)
- settings.setValue('/Processing/LastOutputPath',
- os.path.dirname(filename))
- if not last_ext_path is None:
- settings.setValue(last_ext_path, os.path.splitext(filename)[1].lower())
-
- self.skipOutputChanged.emit(False)
- self.destinationChanged.emit()
-
- def selectEncoding(self):
- dialog = QgsEncodingSelectionDialog(
- self, self.tr('File encoding'), self.encoding)
- if dialog.exec_() == QDialog.Accepted:
- self.encoding = dialog.encoding()
- settings = QgsSettings()
- settings.setValue('/Processing/encoding', self.encoding)
- self.destinationChanged.emit()
- dialog.deleteLater()
-
- def selectDirectory(self):
- lastDir = self.leText.text()
- settings = QgsSettings()
- if not lastDir:
- lastDir = settings.value("/Processing/LastOutputPath", QDir.homePath())
-
- dirName = QFileDialog.getExistingDirectory(self, self.tr('Select Directory'),
- lastDir, QFileDialog.ShowDirsOnly)
- if dirName:
- self.leText.setText(QDir.toNativeSeparators(dirName))
- settings.setValue('/Processing/LastOutputPath', dirName)
- self.use_temporary = False
- self.skipOutputChanged.emit(False)
- self.destinationChanged.emit()
-
- def setValue(self, value):
- if not value:
- if self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional and \
- not self.parameter.createByDefault():
- self.skipOutput()
- else:
- self.saveToTemporary()
- else:
- if value in ('memory:', QgsProcessing.TEMPORARY_OUTPUT):
- self.saveToTemporary()
- elif isinstance(value, QgsProcessingOutputLayerDefinition):
- if value.sink.staticValue() in ('memory:', ''):
- self.saveToTemporary()
- else:
- self.leText.setText(value.sink.staticValue())
- self.use_temporary = False
- self.skipOutputChanged.emit(False)
- self.destinationChanged.emit()
- self.encoding = value.createOptions['fileEncoding']
- else:
- self.leText.setText(value)
- self.use_temporary = False
- self.skipOutputChanged.emit(False)
- self.destinationChanged.emit()
-
- def getValue(self):
- key = None
- if self.use_temporary and isinstance(self.parameter, QgsProcessingParameterFeatureSink):
- key = QgsProcessing.TEMPORARY_OUTPUT
- elif self.use_temporary and not self.default_selection:
- key = QgsProcessing.TEMPORARY_OUTPUT
- else:
- key = self.leText.text()
-
- if not key and self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
- return None
-
- if key and not key == QgsProcessing.TEMPORARY_OUTPUT \
- and not key.startswith('memory:') \
- and not key.startswith('ogr:') \
- and not key.startswith('postgres:') \
- and not key.startswith('postgis:'):
- # output should be a file path
- folder = QFileInfo(key).path()
- if folder == '.':
- # output name does not include a folder - use default
- default_folder = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
- key = QDir(default_folder).filePath(key)
-
- if isinstance(self.parameter, QgsProcessingParameterFolderDestination):
- return key
-
- if isinstance(self.parameter, QgsProcessingParameterFileDestination):
- return key
-
- value = QgsProcessingOutputLayerDefinition(key)
- value.createOptions = {'fileEncoding': self.encoding}
- return value
diff --git a/python/plugins/processing/gui/HistoryDialog.py b/python/plugins/processing/gui/HistoryDialog.py
index 3bc514ff00d9..cab69b3067ce 100644
--- a/python/plugins/processing/gui/HistoryDialog.py
+++ b/python/plugins/processing/gui/HistoryDialog.py
@@ -120,7 +120,7 @@ def executeAlgorithm(self):
if isinstance(item, TreeLogEntryItem):
if item.isAlg:
script = 'import processing\n'
- script += 'from qgis.core import QgsProcessingOutputLayerDefinition, QgsProcessingFeatureSourceDefinition, QgsProperty, QgsCoordinateReferenceSystem\n'
+ script += 'from qgis.core import QgsProcessingOutputLayerDefinition, QgsProcessingFeatureSourceDefinition, QgsProperty, QgsCoordinateReferenceSystem, QgsFeatureRequest\n'
script += item.entry.text.replace('processing.run(', 'processing.execAlgorithmDialog(')
self.close()
exec(script)
diff --git a/python/plugins/processing/gui/ParameterGuiUtils.py b/python/plugins/processing/gui/ParameterGuiUtils.py
deleted file mode 100644
index 6dbc3321258f..000000000000
--- a/python/plugins/processing/gui/ParameterGuiUtils.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- ParameterGuiUtils.py
- ---------------------
- Date : June 2017
- Copyright : (C) 2017 by Nyall Dawson
- Email : nyall dot dawson at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-
-__author__ = 'Nyall Dawson'
-__date__ = 'June 2017'
-__copyright__ = '(C) 2017, Nyall Dawson'
-
-from qgis.core import (QgsProcessing,
- QgsProviderRegistry,
- QgsProcessingFeatureSourceDefinition,
- QgsVectorFileWriter,
- QgsRasterFileWriter)
-from qgis.PyQt.QtCore import QCoreApplication
-from processing.tools import dataobjects
-
-
-def tr(string, context=''):
- if context == '':
- context = 'Processing'
- return QCoreApplication.translate(context, string)
-
-
-def getFileFilter(param):
- """
- Returns a suitable file filter pattern for the specified parameter definition
- :param param:
- :return:
- """
- if param.type() == 'layer':
- vectors = QgsProviderRegistry.instance().fileVectorFilters().split(';;')
- vectors.pop(0)
- rasters = QgsProviderRegistry.instance().fileRasterFilters().split(';;')
- rasters.pop(0)
- filters = set(vectors + rasters)
- filters = sorted(filters)
- return tr('All files (*.*)') + ';;' + ";;".join(filters)
- elif param.type() == 'multilayer':
- if param.layerType() == QgsProcessing.TypeRaster:
- exts = QgsRasterFileWriter.supportedFormatExtensions()
- elif param.layerType() == QgsProcessing.TypeFile:
- return tr('All files (*.*)', 'QgsProcessingParameterMultipleLayers')
- else:
- exts = QgsVectorFileWriter.supportedFormatExtensions()
- for i in range(len(exts)):
- exts[i] = tr('{0} files (*.{1})', 'QgsProcessingParameterMultipleLayers').format(exts[i].upper(), exts[i].lower())
- return tr('All files (*.*)') + ';;' + ';;'.join(exts)
- elif param.type() == 'raster':
- return QgsProviderRegistry.instance().fileRasterFilters()
- elif param.type() == 'rasterDestination':
- exts = param.supportedOutputRasterLayerExtensions()
- for i in range(len(exts)):
- exts[i] = tr('{0} files (*.{1})', 'ParameterRaster').format(exts[i].upper(), exts[i].lower())
- return ';;'.join(exts) + ';;' + tr('All files (*.*)')
- elif param.type() in ('sink', 'vectorDestination'):
- exts = param.supportedOutputVectorLayerExtensions()
- for i in range(len(exts)):
- exts[i] = tr('{0} files (*.{1})', 'ParameterVector').format(exts[i].upper(), exts[i].lower())
- return ';;'.join(exts) + ';;' + tr('All files (*.*)')
- elif param.type() == 'source':
- return QgsProviderRegistry.instance().fileVectorFilters()
- elif param.type() == 'vector':
- return QgsProviderRegistry.instance().fileVectorFilters()
- elif param.type() == 'fileDestination':
- return param.fileFilter() + ';;' + tr('All files (*.*)')
- elif param.type() == 'mesh':
- return tr('All files (*.*)')
- if param.defaultFileExtension():
- return tr('Default extension') + ' (*.' + param.defaultFileExtension() + ')'
- else:
- return ''
diff --git a/python/plugins/processing/gui/ParametersPanel.py b/python/plugins/processing/gui/ParametersPanel.py
index 21ebea468f9f..af47af7799c1 100644
--- a/python/plugins/processing/gui/ParametersPanel.py
+++ b/python/plugins/processing/gui/ParametersPanel.py
@@ -25,67 +25,31 @@
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Victor Olaya'
-import os
-import warnings
-
-from functools import partial
-
from qgis.core import (QgsProcessingParameterDefinition,
QgsProcessingParameterExtent,
- QgsProcessingParameterPoint,
- QgsProcessingParameterFeatureSource,
- QgsProcessingParameterRasterDestination,
- QgsProcessingParameterFeatureSink,
- QgsProcessingParameterVectorDestination,
- QgsProcessingOutputLayerDefinition,
QgsProject,
QgsProcessingModelAlgorithm,
- QgsVectorFileWriter)
+ QgsProcessingOutputLayerDefinition)
from qgis.gui import (QgsProcessingContextGenerator,
- QgsProcessingParameterWidgetContext)
+ QgsProcessingParameterWidgetContext,
+ QgsProcessingParametersWidget,
+ QgsGui,
+ QgsProcessingGui,
+ QgsProcessingParametersGenerator)
from qgis.utils import iface
-from qgis.PyQt import uic
-from qgis.PyQt.QtCore import QCoreApplication, Qt
-from qgis.PyQt.QtWidgets import (QWidget, QHBoxLayout, QToolButton,
- QLabel, QCheckBox, QSizePolicy)
-from qgis.PyQt.QtGui import QIcon
-from osgeo import gdal
-
-from processing.gui.DestinationSelectionPanel import DestinationSelectionPanel
from processing.gui.wrappers import WidgetWrapperFactory, WidgetWrapper
+from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
from processing.tools.dataobjects import createContext
-pluginPath = os.path.split(os.path.dirname(__file__))[0]
-
-with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=DeprecationWarning)
- WIDGET, BASE = uic.loadUiType(
- os.path.join(pluginPath, 'ui', 'widgetParametersPanel.ui'))
-
-
-class ParametersPanel(BASE, WIDGET):
- NOT_SELECTED = QCoreApplication.translate('ParametersPanel', '[Not selected]')
+class ParametersPanel(QgsProcessingParametersWidget):
def __init__(self, parent, alg, in_place=False):
- super(ParametersPanel, self).__init__(None)
- self.setupUi(self)
+ super().__init__(alg, parent)
self.in_place = in_place
- self.grpAdvanced.hide()
-
- self.scrollAreaWidgetContents.setContentsMargins(4, 4, 4, 4)
- self.layoutMain = self.scrollAreaWidgetContents.layout()
- self.layoutAdvanced = self.grpAdvanced.layout()
-
- self.parent = parent
- self.alg = alg
self.wrappers = {}
- self.outputWidgets = {}
- self.checkBoxes = {}
- self.dependentItems = {}
- self.iterateButtons = {}
self.processing_context = createContext()
@@ -112,38 +76,32 @@ def layerRegistryChanged(self, layers):
except AttributeError:
pass
- def formatParameterTooltip(self, parameter):
- return '{}
{}
'.format(
- parameter.description(),
- QCoreApplication.translate('ParametersPanel', 'Python identifier: ‘{}’').format('{} '.format(parameter.name()))
- )
-
def initWidgets(self):
- # If there are advanced parameters — show corresponding groupbox
- for param in self.alg.parameterDefinitions():
- if param.flags() & QgsProcessingParameterDefinition.FlagAdvanced:
- self.grpAdvanced.show()
- break
+ super().initWidgets()
widget_context = QgsProcessingParameterWidgetContext()
widget_context.setProject(QgsProject.instance())
if iface is not None:
widget_context.setMapCanvas(iface.mapCanvas())
- widget_context.setMessageBar(self.parent.messageBar())
- if isinstance(self.alg, QgsProcessingModelAlgorithm):
- widget_context.setModel(self.alg)
+ widget_context.setBrowserModel(iface.browserModel())
+ widget_context.setActiveLayer(iface.activeLayer())
+
+ widget_context.setMessageBar(self.parent().messageBar())
+ if isinstance(self.algorithm(), QgsProcessingModelAlgorithm):
+ widget_context.setModel(self.algorithm())
# Create widgets and put them in layouts
- for param in self.alg.parameterDefinitions():
+ for param in self.algorithm().parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
if param.isDestination():
continue
else:
- wrapper = WidgetWrapperFactory.create_wrapper(param, self.parent)
+ wrapper = WidgetWrapperFactory.create_wrapper(param, self.parent())
wrapper.setWidgetContext(widget_context)
wrapper.registerProcessingContextGenerator(self.context_generator)
+ wrapper.registerProcessingParametersGenerator(self)
self.wrappers[param.name()] = wrapper
# For compatibility with 3.x API, we need to check whether the wrapper is
@@ -167,24 +125,6 @@ def initWidgets(self):
if is_python_wrapper:
widget.setToolTip(param.toolTip())
- if isinstance(param, QgsProcessingParameterFeatureSource):
- layout = QHBoxLayout()
- layout.setSpacing(6)
- layout.setMargin(0)
- layout.addWidget(widget)
- button = QToolButton()
- icon = QIcon(os.path.join(pluginPath, 'images', 'iterate.png'))
- button.setIcon(icon)
- button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
- button.setToolTip(self.tr('Iterate over this layer, creating a separate output for every feature in the layer'))
- button.setCheckable(True)
- layout.addWidget(button)
- layout.setAlignment(button, Qt.AlignTop)
- self.iterateButtons[param.name()] = button
- button.toggled.connect(self.buttonToggled)
- widget = QWidget()
- widget.setLayout(layout)
-
label = None
if not is_python_wrapper:
label = wrapper.createWrappedLabel()
@@ -192,93 +132,132 @@ def initWidgets(self):
label = wrapper.label
if label is not None:
- if param.flags() & QgsProcessingParameterDefinition.FlagAdvanced:
- self.layoutAdvanced.addWidget(label)
- else:
- self.layoutMain.insertWidget(
- self.layoutMain.count() - 2, label)
+ self.addParameterLabel(param, label)
elif is_python_wrapper:
desc = param.description()
if isinstance(param, QgsProcessingParameterExtent):
desc += self.tr(' (xmin, xmax, ymin, ymax)')
- if isinstance(param, QgsProcessingParameterPoint):
- desc += self.tr(' (x, y)')
if param.flags() & QgsProcessingParameterDefinition.FlagOptional:
desc += self.tr(' [optional]')
widget.setText(desc)
- if param.flags() & QgsProcessingParameterDefinition.FlagAdvanced:
- self.layoutAdvanced.addWidget(widget)
- else:
- self.layoutMain.insertWidget(
- self.layoutMain.count() - 2, widget)
- for output in self.alg.destinationParameterDefinitions():
+ self.addParameterWidget(param, widget)
+
+ for output in self.algorithm().destinationParameterDefinitions():
if output.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
- if self.in_place and param.name() in ('INPUT', 'OUTPUT'):
+ if self.in_place and output.name() in ('INPUT', 'OUTPUT'):
continue
- label = QLabel(output.description())
- widget = DestinationSelectionPanel(output, self.alg)
- self.layoutMain.insertWidget(self.layoutMain.count() - 1, label)
- self.layoutMain.insertWidget(self.layoutMain.count() - 1, widget)
- if isinstance(output, (QgsProcessingParameterRasterDestination, QgsProcessingParameterFeatureSink, QgsProcessingParameterVectorDestination)):
- check = QCheckBox()
- check.setText(QCoreApplication.translate('ParametersPanel', 'Open output file after running algorithm'))
-
- def skipOutputChanged(widget, checkbox, skipped):
-
- enabled = not skipped
-
- # Do not try to open formats that are write-only.
- value = widget.getValue()
- if value and isinstance(value, QgsProcessingOutputLayerDefinition) and isinstance(output, (
- QgsProcessingParameterFeatureSink, QgsProcessingParameterVectorDestination)):
- filename = value.sink.staticValue()
- if filename not in ('memory:', ''):
- path, ext = os.path.splitext(filename)
- format = QgsVectorFileWriter.driverForExtension(ext)
- drv = gdal.GetDriverByName(format)
- if drv:
- if drv.GetMetadataItem(gdal.DCAP_OPEN) is None:
- enabled = False
-
- checkbox.setEnabled(enabled)
- checkbox.setChecked(enabled)
-
- check.setChecked(not widget.outputIsSkipped())
- check.setEnabled(not widget.outputIsSkipped())
- widget.skipOutputChanged.connect(partial(skipOutputChanged, widget, check))
- self.layoutMain.insertWidget(self.layoutMain.count() - 1, check)
- self.checkBoxes[output.name()] = check
-
- widget.setToolTip(param.toolTip())
- self.outputWidgets[output.name()] = widget
+ wrapper = QgsGui.processingGuiRegistry().createParameterWidgetWrapper(output, QgsProcessingGui.Standard)
+ wrapper.setWidgetContext(widget_context)
+ wrapper.registerProcessingContextGenerator(self.context_generator)
+ wrapper.registerProcessingParametersGenerator(self)
+ self.wrappers[output.name()] = wrapper
+
+ label = wrapper.createWrappedLabel()
+ if label is not None:
+ self.addOutputLabel(label)
+
+ widget = wrapper.createWrappedWidget(self.processing_context)
+ self.addOutputWidget(widget)
+
+ # def skipOutputChanged(widget, checkbox, skipped):
+ # TODO
+ # enabled = not skipped
+ #
+ # # Do not try to open formats that are write-only.
+ # value = widget.value()
+ # if value and isinstance(value, QgsProcessingOutputLayerDefinition) and isinstance(output, (
+ # QgsProcessingParameterFeatureSink, QgsProcessingParameterVectorDestination)):
+ # filename = value.sink.staticValue()
+ # if filename not in ('memory:', ''):
+ # path, ext = os.path.splitext(filename)
+ # format = QgsVectorFileWriter.driverForExtension(ext)
+ # drv = gdal.GetDriverByName(format)
+ # if drv:
+ # if drv.GetMetadataItem(gdal.DCAP_OPEN) is None:
+ # enabled = False
+ #
+ # checkbox.setEnabled(enabled)
+ # checkbox.setChecked(enabled)
for wrapper in list(self.wrappers.values()):
wrapper.postInitialize(list(self.wrappers.values()))
+ def createProcessingParameters(self):
+ parameters = {}
+
+ for param in self.algorithm().parameterDefinitions():
+ if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
+ continue
+ if not param.isDestination():
+
+ if self.in_place and param.name() == 'INPUT':
+ parameters[param.name()] = self.active_layer
+ continue
+
+ try:
+ wrapper = self.wrappers[param.name()]
+ except KeyError:
+ continue
+
+ # For compatibility with 3.x API, we need to check whether the wrapper is
+ # the deprecated WidgetWrapper class. If not, it's the newer
+ # QgsAbstractProcessingParameterWidgetWrapper class
+ # TODO QGIS 4.0 - remove
+ if issubclass(wrapper.__class__, WidgetWrapper):
+ widget = wrapper.widget
+ else:
+ widget = wrapper.wrappedWidget()
+
+ if widget is None:
+ continue
+
+ value = wrapper.parameterValue()
+ parameters[param.name()] = value
+
+ if not param.checkValueIsAcceptable(value):
+ raise AlgorithmDialogBase.InvalidParameterValue(param, widget)
+ else:
+ if self.in_place and param.name() == 'OUTPUT':
+ parameters[param.name()] = 'memory:'
+ continue
+
+ try:
+ wrapper = self.wrappers[param.name()]
+ except KeyError:
+ continue
+
+ widget = wrapper.wrappedWidget()
+ value = wrapper.parameterValue()
+
+ dest_project = None
+ if wrapper.customProperties().get('OPEN_AFTER_RUNNING'):
+ dest_project = QgsProject.instance()
+
+ if value and isinstance(value, QgsProcessingOutputLayerDefinition):
+ value.destinationProject = dest_project
+ if value:
+ parameters[param.name()] = value
+ if param.isDestination():
+ context = createContext()
+ ok, error = self.algorithm().provider().isSupportedOutputValue(value, param, context)
+ if not ok:
+ raise AlgorithmDialogBase.InvalidOutputExtension(widget, error)
+
+ return self.algorithm().preprocessParameters(parameters)
+
def setParameters(self, parameters):
- for param in self.alg.parameterDefinitions():
+ for param in self.algorithm().parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
if not param.name() in parameters:
continue
- if not param.isDestination():
- value = parameters[param.name()]
+ value = parameters[param.name()]
- wrapper = self.wrappers[param.name()]
- wrapper.setParameterValue(value, self.processing_context)
- else:
- dest_widget = self.outputWidgets[param.name()]
- dest_widget.setValue(parameters[param.name()])
-
- def buttonToggled(self, value):
- if value:
- sender = self.sender()
- for button in list(self.iterateButtons.values()):
- if button is not sender:
- button.setChecked(False)
+ wrapper = self.wrappers[param.name()]
+ wrapper.setParameterValue(value, self.processing_context)
diff --git a/python/plugins/processing/gui/PostgisTableSelector.py b/python/plugins/processing/gui/PostgisTableSelector.py
deleted file mode 100644
index a7074cd42506..000000000000
--- a/python/plugins/processing/gui/PostgisTableSelector.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- PostgisTableSelector.py
- ---------------------
- Date : November 2015
- Copyright : (C) 2015 by Victor Olaya
- Email : volayaf at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-__author__ = 'Victor Olaya'
-__date__ = 'November 2015'
-__copyright__ = '(C) 2015, Victor Olaya'
-
-
-import os
-import warnings
-
-from qgis.PyQt.QtGui import QIcon
-from qgis.PyQt.QtWidgets import QTreeWidgetItem, QMessageBox
-from qgis.PyQt import uic
-from qgis.core import QgsSettings
-from processing.tools.postgis import GeoDB
-
-pluginPath = os.path.split(os.path.dirname(__file__))[0]
-with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=DeprecationWarning)
- WIDGET, BASE = uic.loadUiType(
- os.path.join(pluginPath, 'ui', 'DlgPostgisTableSelector.ui'))
-
-
-class PostgisTableSelector(BASE, WIDGET):
-
- def __init__(self, parent, tablename):
- super(PostgisTableSelector, self).__init__(parent)
- self.connection = None
- self.table = None
- self.schema = None
- self.setupUi(self)
- settings = QgsSettings()
- settings.beginGroup('/PostgreSQL/connections/')
- names = settings.childGroups()
- settings.endGroup()
- for n in names:
- item = ConnectionItem(n)
- self.treeConnections.addTopLevelItem(item)
-
- def itemExpanded(item):
- try:
- item.populateSchemas()
- except:
- pass
-
- self.treeConnections.itemExpanded.connect(itemExpanded)
-
- self.textTableName.setText(tablename)
-
- self.buttonBox.accepted.connect(self.okPressed)
- self.buttonBox.rejected.connect(self.cancelPressed)
-
- def cancelPressed(self):
- self.close()
-
- def okPressed(self):
- if self.textTableName.text().strip() == "":
- self.textTableName.setStyleSheet("QLineEdit{background: yellow}")
- return
- item = self.treeConnections.currentItem()
- if isinstance(item, ConnectionItem):
- QMessageBox.warning(self, "Wrong selection", "Select a schema item in the tree")
- return
- self.schema = item.text(0)
- self.table = self.textTableName.text().strip()
- self.connection = item.parent().text(0)
- self.close()
-
-
-class ConnectionItem(QTreeWidgetItem):
-
- def __init__(self, connection):
- self.connIcon = QIcon(os.path.dirname(__file__) + '/../images/postgis.png')
- self.schemaIcon = QIcon(os.path.dirname(__file__) + '/../images/namespace.png')
-
- QTreeWidgetItem.__init__(self)
- self.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
- self.connection = connection
- self.setText(0, connection)
- self.setIcon(0, self.connIcon)
-
- def populateSchemas(self):
- if self.childCount() != 0:
- return
- geodb = GeoDB.from_name(self.connection)
- schemas = geodb.list_schemas()
- for oid, name, owner, perms in schemas:
- item = QTreeWidgetItem()
- item.setText(0, name)
- item.setIcon(0, self.schemaIcon)
- self.addChild(item)
diff --git a/python/plugins/processing/gui/enummodelerwidget.py b/python/plugins/processing/gui/enummodelerwidget.py
deleted file mode 100644
index ba49a43fa8eb..000000000000
--- a/python/plugins/processing/gui/enummodelerwidget.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- EnumModelerWidget.py
- ---------------------
- Date : May 2018
- Copyright : (C) 2018 by Alexander Bruy
- Email : alexander dot bruy at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-__author__ = 'Alexander Bruy'
-__date__ = 'May 2018'
-__copyright__ = '(C) 2018, Alexander Bruy'
-
-import os
-import warnings
-
-from qgis.PyQt import uic
-from qgis.PyQt.QtCore import Qt
-from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem
-from qgis.PyQt.QtWidgets import QMessageBox
-
-from qgis.core import QgsApplication
-
-pluginPath = os.path.split(os.path.dirname(__file__))[0]
-
-with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=DeprecationWarning)
- WIDGET, BASE = uic.loadUiType(
- os.path.join(pluginPath, 'ui', 'enummodelerwidgetbase.ui'))
-
-
-class EnumModelerWidget(BASE, WIDGET):
-
- def __init__(self, parent=None):
- super(EnumModelerWidget, self).__init__(parent)
- self.setupUi(self)
-
- self.btnAdd.setIcon(QgsApplication.getThemeIcon('/symbologyAdd.svg'))
- self.btnRemove.setIcon(QgsApplication.getThemeIcon('/symbologyRemove.svg'))
- self.btnClear.setIcon(QgsApplication.getThemeIcon('console/iconClearConsole.svg'))
-
- self.btnAdd.clicked.connect(self.addItem)
- self.btnRemove.clicked.connect(lambda: self.removeItems())
- self.btnClear.clicked.connect(lambda: self.removeItems(True))
-
- self.lstItems.setModel(QStandardItemModel())
-
- self.lstItems.model().itemChanged.connect(self.onItemChanged)
-
- def onItemChanged(self, item):
- model = self.lstItems.model()
- checkedItem = None
- for i in range(model.rowCount()):
- itm = model.item(i)
- if itm.checkState() == Qt.Checked and itm.data() == Qt.Checked:
- checkedItem = i
- break
-
- model.blockSignals(True)
- if checkedItem is None:
- item.setData(item.checkState())
- else:
- if self.chkAllowMultiple.isChecked():
- item.setData(item.checkState())
- else:
- model.item(checkedItem).setCheckState(Qt.Unchecked)
- model.item(checkedItem).setData(Qt.Unchecked)
-
- item.setData(item.checkState())
- model.blockSignals(False)
-
- def addItem(self):
- model = self.lstItems.model()
-
- item = QStandardItem('new item')
- item.setCheckable(True)
- item.setDropEnabled(False)
- item.setData(Qt.Unchecked)
-
- model.appendRow(item)
-
- def removeItems(self, removeAll=False):
- if removeAll:
- res = QMessageBox.question(self, self.tr('Clear?'), self.tr('Are you sure you want to delete all items?'))
- if res == QMessageBox.Yes:
- self.lstItems.model().clear()
- else:
- self.lstItems.setUpdatesEnabled(False)
- indexes = sorted(self.lstItems.selectionModel().selectedIndexes())
- for i in reversed(indexes):
- self.lstItems.model().removeRow(i.row())
- self.lstItems.setUpdatesEnabled(True)
-
- def options(self):
- items = []
- model = self.lstItems.model()
- for i in range(model.rowCount()):
- item = model.item(i)
- items.append(item.text())
-
- return items
-
- def defaultOptions(self):
- options = []
- model = self.lstItems.model()
- for i in range(model.rowCount()):
- item = model.item(i)
- if item.checkState() == Qt.Checked:
- if not self.allowMultiple():
- return i
- options.append(i)
- return options if len(options) > 0 else None
-
- def allowMultiple(self):
- return self.chkAllowMultiple.isChecked()
-
- def setOptions(self, options):
- model = self.lstItems.model()
- for i in options:
- item = QStandardItem(i)
- item.setCheckable(True)
- item.setDropEnabled(False)
- item.setData(Qt.Unchecked)
-
- model.appendRow(item)
-
- def setDefault(self, indexes):
- if indexes is None:
- return
- model = self.lstItems.model()
- if not isinstance(indexes, (list, tuple)):
- indexes = [indexes]
- for i in indexes:
- item = model.item(i)
- if item:
- item.setCheckState(Qt.Checked)
- item.setData(Qt.Checked)
-
- def setAllowMultiple(self, allowMultiple):
- self.chkAllowMultiple.setChecked(allowMultiple)
diff --git a/python/plugins/processing/gui/matrixmodelerwidget.py b/python/plugins/processing/gui/matrixmodelerwidget.py
deleted file mode 100644
index 3be979f1c00c..000000000000
--- a/python/plugins/processing/gui/matrixmodelerwidget.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- MatrixModelerWidget.py
- ---------------------
- Date : May 2018
- Copyright : (C) 2018 by Alexander Bruy
- Email : alexander dot bruy at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-__author__ = 'Alexander Bruy'
-__date__ = 'May 2018'
-__copyright__ = '(C) 2018, Alexander Bruy'
-
-import os
-import warnings
-
-from qgis.PyQt import uic
-from qgis.PyQt.QtCore import Qt
-from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem
-from qgis.PyQt.QtWidgets import QInputDialog, QMessageBox
-
-from qgis.core import QgsApplication
-
-pluginPath = os.path.split(os.path.dirname(__file__))[0]
-
-with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=DeprecationWarning)
- WIDGET, BASE = uic.loadUiType(
- os.path.join(pluginPath, 'ui', 'matrixmodelerwidgetbase.ui'))
-
-
-class MatrixModelerWidget(BASE, WIDGET):
-
- def __init__(self, parent=None):
- super(MatrixModelerWidget, self).__init__(parent)
- self.setupUi(self)
-
- self.btnAddColumn.setIcon(QgsApplication.getThemeIcon('/mActionNewAttribute.svg'))
- self.btnRemoveColumn.setIcon(QgsApplication.getThemeIcon('/mActionDeleteAttribute.svg'))
- self.btnAddRow.setIcon(QgsApplication.getThemeIcon('/symbologyAdd.svg'))
- self.btnRemoveRow.setIcon(QgsApplication.getThemeIcon('/symbologyRemove.svg'))
- self.btnClear.setIcon(QgsApplication.getThemeIcon('console/iconClearConsole.svg'))
-
- self.btnAddColumn.clicked.connect(self.addColumn)
- self.btnRemoveColumn.clicked.connect(self.removeColumns)
- self.btnAddRow.clicked.connect(self.addRow)
- self.btnRemoveRow.clicked.connect(self.removeRows)
- self.btnClear.clicked.connect(self.clearTable)
-
- items = [QStandardItem('0')]
- model = QStandardItemModel()
- model.appendColumn(items)
- self.tblView.setModel(model)
-
- self.tblView.horizontalHeader().sectionDoubleClicked.connect(self.changeHeader)
-
- def addColumn(self):
- model = self.tblView.model()
- items = [QStandardItem('0') for i in range(model.rowCount())]
- model.appendColumn(items)
-
- def removeColumns(self):
- indexes = sorted(self.tblView.selectionModel().selectedColumns())
- self.tblView.setUpdatesEnabled(False)
- for i in reversed(indexes):
- self.tblView.model().removeColumns(i.column(), 1)
- self.tblView.setUpdatesEnabled(True)
-
- def addRow(self):
- model = self.tblView.model()
- items = [QStandardItem('0') for i in range(model.columnCount())]
- model.appendRow(items)
-
- def removeRows(self):
- indexes = sorted(self.tblView.selectionModel().selectedRows())
- self.tblView.setUpdatesEnabled(False)
- for i in reversed(indexes):
- self.tblView.model().removeRows(i.row(), 1)
- self.tblView.setUpdatesEnabled(True)
-
- def clearTable(self, removeAll=False):
- res = QMessageBox.question(self, self.tr('Clear?'), self.tr('Are you sure you want to clear table?'))
- if res == QMessageBox.Yes:
- self.tblView.model().clear()
-
- def changeHeader(self, index):
- txt, ok = QInputDialog.getText(self, self.tr("Enter column name"), self.tr("Column name"))
- if ok:
- self.tblView.model().setHeaderData(index, Qt.Horizontal, txt)
-
- def value(self):
- cols = self.tblView.model().columnCount()
- rows = self.tblView.model().rowCount()
-
- items = []
- for row in range(rows):
- for col in range(cols):
- items.append(str(self.tblView.model().item(row, col).text()))
-
- return items
-
- def setValue(self, headers, table):
- model = self.tblView.model()
- model.setHorizontalHeaderLabels(headers)
-
- cols = len(headers)
- rows = len(table) // cols
- model = QStandardItemModel(rows, cols)
-
- for row in range(rows):
- for col in range(cols):
- item = QStandardItem(str(table[row * cols + col]))
- model.setItem(row, col, item)
- self.tblView.setModel(model)
-
- def headers(self):
- headers = []
- model = self.tblView.model()
- for i in range(model.columnCount()):
- headers.append(str(model.headerData(i, Qt.Horizontal)))
-
- return headers
-
- def fixedRows(self):
- return self.chkFixedRows.isChecked()
-
- def setFixedRows(self, fixedRows):
- self.chkFixedRows.setChecked(fixedRows)
diff --git a/python/plugins/processing/gui/wrappers.py b/python/plugins/processing/gui/wrappers.py
index e10ca81094c3..702a2a45e88c 100755
--- a/python/plugins/processing/gui/wrappers.py
+++ b/python/plugins/processing/gui/wrappers.py
@@ -22,25 +22,18 @@
__date__ = 'May 2016'
__copyright__ = '(C) 2016, Arnaud Morvan'
-import locale
import os
import re
-from functools import cmp_to_key
from inspect import isclass
from copy import deepcopy
from qgis.core import (
QgsApplication,
- QgsUnitTypes,
QgsCoordinateReferenceSystem,
QgsExpression,
- QgsExpressionContextGenerator,
QgsFieldProxyModel,
- QgsMapLayerProxyModel,
- QgsWkbTypes,
QgsSettings,
QgsProject,
- QgsMapLayer,
QgsMapLayerType,
QgsVectorLayer,
QgsProcessing,
@@ -53,7 +46,6 @@
QgsProcessingParameterFile,
QgsProcessingParameterMultipleLayers,
QgsProcessingParameterNumber,
- QgsProcessingParameterDistance,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterEnum,
QgsProcessingParameterString,
@@ -75,8 +67,6 @@
QgsProcessingOutputString,
QgsProcessingOutputNumber,
QgsProcessingModelChildParameterSource,
- QgsProcessingModelAlgorithm,
- QgsRasterDataProvider,
NULL,
Qgis)
@@ -87,12 +77,13 @@
QDialog,
QFileDialog,
QHBoxLayout,
- QVBoxLayout,
QLineEdit,
QPlainTextEdit,
QToolButton,
QWidget,
+ QSizePolicy
)
+from qgis.PyQt.QtGui import QIcon
from qgis.gui import (
QgsGui,
QgsExpressionLineEdit,
@@ -107,7 +98,7 @@
QgsAbstractProcessingParameterWidgetWrapper,
QgsProcessingMapLayerComboBox
)
-from qgis.PyQt.QtCore import pyqtSignal, QObject, QVariant, Qt
+from qgis.PyQt.QtCore import QVariant, Qt
from qgis.utils import iface
from processing.core.ProcessingConfig import ProcessingConfig
@@ -122,7 +113,6 @@
from processing.gui.BatchInputSelectionPanel import BatchInputSelectionPanel
from processing.gui.FixedTablePanel import FixedTablePanel
from processing.gui.ExtentSelectionPanel import ExtentSelectionPanel
-from processing.gui.ParameterGuiUtils import getFileFilter
from processing.tools import dataobjects
@@ -130,6 +120,8 @@
DIALOG_BATCH = QgsProcessingGui.Batch
DIALOG_MODELER = QgsProcessingGui.Modeler
+pluginPath = os.path.split(os.path.dirname(__file__))[0]
+
class InvalidParameterValue(Exception):
pass
@@ -249,7 +241,7 @@ def getFileName(self, initial_value=''):
# TODO: should use selectedFilter argument for default file format
filename, selected_filter = QFileDialog.getOpenFileName(self.widget, self.tr('Select File'),
- path, getFileFilter(self.parameterDefinition()))
+ path, self.parameterDefinition().createFileFilter())
if filename:
settings.setValue('/Processing/LastInputPath',
os.path.dirname(str(filename)))
@@ -413,6 +405,16 @@ def value(self):
class ExtentWidgetWrapper(WidgetWrapper):
USE_MIN_COVERING_EXTENT = "[Use min covering extent]"
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ """
+ .. deprecated:: 3.14
+ Do not use, will be removed in QGIS 4.0
+ """
+
+ from warnings import warn
+ warn("ExtentWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
+
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
widget = ExtentSelectionPanel(self.dialog, self.parameterDefinition())
@@ -930,6 +932,17 @@ def value(self):
class MapLayerWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
+ def __init__(self, param, dialog, row=0, col=0, **kwargs):
+ """
+ .. deprecated:: 3.14
+ Do not use, will be removed in QGIS 4.0
+ """
+
+ from warnings import warn
+ warn("MapLayerWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
+
+ super().__init__(param, dialog, row, col, **kwargs)
+
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
@@ -940,14 +953,13 @@ def createWidget(self):
self.combo.setValue(self.parameterDefinition().defaultValue(), self.context)
else:
if self.parameterDefinition().defaultValue():
- self.combo.setvalue(self.parameterDefinition().defaultValue(), self.context)
+ self.combo.setValue(self.parameterDefinition().defaultValue(), self.context)
else:
self.combo.setLayer(iface.activeLayer())
except:
pass
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
- self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
@@ -1028,6 +1040,17 @@ def validator(v):
class RasterWidgetWrapper(MapLayerWidgetWrapper):
+ def __init__(self, param, dialog, row=0, col=0, **kwargs):
+ """
+ .. deprecated:: 3.14
+ Do not use, will be removed in QGIS 4.0
+ """
+
+ from warnings import warn
+ warn("RasterWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
+
+ super().__init__(param, dialog, row, col, **kwargs)
+
def getAvailableLayers(self):
return self.dialog.getAvailableValuesOfType((QgsProcessingParameterRasterLayer, QgsProcessingParameterString),
(QgsProcessingOutputRasterLayer, QgsProcessingOutputFile, QgsProcessingOutputString))
@@ -1050,6 +1073,17 @@ def selectFile(self):
class MeshWidgetWrapper(MapLayerWidgetWrapper):
+ def __init__(self, param, dialog, row=0, col=0, **kwargs):
+ """
+ .. deprecated:: 3.14
+ Do not use, will be removed in QGIS 4.0
+ """
+
+ from warnings import warn
+ warn("MeshWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
+
+ super().__init__(param, dialog, row, col, **kwargs)
+
def getAvailableLayers(self):
return self.dialog.getAvailableValuesOfType((QgsProcessingParameterMeshLayer, QgsProcessingParameterString),
())
@@ -1139,20 +1173,32 @@ def value(self):
class FeatureSourceWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
+ def __init__(self, *args, **kwargs):
+ """
+ .. deprecated:: 3.4
+ Do not use, will be removed in QGIS 4.0
+ """
+
+ from warnings import warn
+ warn("FeatureSourceWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
+ self.map_layer_combo = None
+ super().__init__(*args, **kwargs)
+
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
- self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
+ self.map_layer_combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
self.context = dataobjects.createContext()
try:
if iface.activeLayer().type() == QgsMapLayerType.VectorLayer:
- self.combo.setLayer(iface.activeLayer())
+ self.map_layer_combo.setLayer(iface.activeLayer())
except:
pass
- self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
- self.combo.triggerFileSelection.connect(self.selectFile)
- return self.combo
+ self.map_layer_combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
+
+ return self.map_layer_combo
+
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
@@ -1182,10 +1228,14 @@ def createWidget(self):
widget.setLayout(layout)
return widget
+ def setWidgetContext(self, context):
+ if self.map_layer_combo:
+ self.map_layer_combo.setWidgetContext(context)
+ super().setWidgetContext(context)
+
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
- filename = dataobjects.getRasterSublayer(filename, self.parameterDefinition())
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
@@ -1206,7 +1256,7 @@ def setValue(self, value):
layer = QgsProject.instance().mapLayer(value)
if layer is not None:
value = layer
- self.combo.setValue(value, self.context)
+ self.map_layer_combo.setValue(value, self.context)
elif self.dialogType == DIALOG_BATCH:
self.widget.setValue(value)
else:
@@ -1215,7 +1265,7 @@ def setValue(self, value):
def value(self):
if self.dialogType == DIALOG_STANDARD:
- return self.combo.value()
+ return self.map_layer_combo.value()
elif self.dialogType == DIALOG_BATCH:
return self.widget.getValue()
else:
@@ -1413,6 +1463,17 @@ def validator(v):
class VectorLayerWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
+ def __init__(self, param, dialog, row=0, col=0, **kwargs):
+ """
+ .. deprecated:: 3.14
+ Do not use, will be removed in QGIS 4.0
+ """
+
+ from warnings import warn
+ warn("VectorLayerWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
+
+ super().__init__(param, dialog, row, col, **kwargs)
+
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
@@ -1425,7 +1486,6 @@ def createWidget(self):
pass
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
- self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
@@ -1655,6 +1715,14 @@ class BandWidgetWrapper(WidgetWrapper):
NOT_SET = '[Not set]'
def __init__(self, param, dialog, row=0, col=0, **kwargs):
+ """
+ .. deprecated:: 3.14
+ Do not use, will be removed in QGIS 4.0
+ """
+
+ from warnings import warn
+ warn("BandWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
+
super().__init__(param, dialog, row, col, **kwargs)
self.context = dataobjects.createContext()
@@ -1825,6 +1893,7 @@ def create_wrapper_from_class(param, dialog, row=0, col=0):
# deprecated, moved to c++
wrapper = CrsWidgetWrapper
elif param.type() == 'extent':
+ # deprecated, moved to c++
wrapper = ExtentWidgetWrapper
elif param.type() == 'point':
# deprecated, moved to c++
@@ -1841,6 +1910,7 @@ def create_wrapper_from_class(param, dialog, row=0, col=0):
# deprecated, moved to c++
wrapper = DistanceWidgetWrapper
elif param.type() == 'raster':
+ # deprecated, moved to c++
wrapper = RasterWidgetWrapper
elif param.type() == 'enum':
# deprecated, moved to c++
@@ -1852,15 +1922,19 @@ def create_wrapper_from_class(param, dialog, row=0, col=0):
# deprecated, moved to c++
wrapper = ExpressionWidgetWrapper
elif param.type() == 'vector':
+ # deprecated, moved to c++
wrapper = VectorLayerWidgetWrapper
elif param.type() == 'field':
# deprecated, moved to c++
wrapper = TableFieldWidgetWrapper
elif param.type() == 'source':
+ # deprecated, moved to c++
wrapper = FeatureSourceWidgetWrapper
elif param.type() == 'band':
+ # deprecated, moved to c++
wrapper = BandWidgetWrapper
elif param.type() == 'layer':
+ # deprecated, moved to c++
wrapper = MapLayerWidgetWrapper
elif param.type() == 'range':
# deprecated, moved to c++
@@ -1869,6 +1943,7 @@ def create_wrapper_from_class(param, dialog, row=0, col=0):
# deprecated, moved to c++
wrapper = FixedTableWidgetWrapper
elif param.type() == 'mesh':
+ # deprecated, moved to c++
wrapper = MeshWidgetWrapper
else:
assert False, param.type()
diff --git a/python/plugins/processing/gui/wrappers_postgis.py b/python/plugins/processing/gui/wrappers_postgis.py
deleted file mode 100644
index f87c2e0b5ad4..000000000000
--- a/python/plugins/processing/gui/wrappers_postgis.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- postgis.py - Postgis widget wrappers
- ---------------------
- Date : December 2016
- Copyright : (C) 2016 by Arnaud Morvan
- Email : arnaud dot morvan at camptocamp 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. *
-* *
-***************************************************************************
-"""
-
-
-from qgis.core import (QgsProcessingParameterNumber,
- QgsProcessingParameterFile,
- QgsProcessingParameterField,
- QgsProcessingParameterExpression,
- QgsProcessingOutputString,
- QgsProcessingParameterString)
-
-from qgis.PyQt.QtWidgets import QComboBox
-
-from processing.gui.wrappers import (
- WidgetWrapper,
- DIALOG_MODELER,
-)
-from processing.tools.postgis import GeoDB
-
-
-class SchemaWidgetWrapper(WidgetWrapper):
- """
- WidgetWrapper for ParameterString that create and manage a combobox widget
- with existing schemas from a parent connection parameter.
- """
-
- def createWidget(self, connection_param=None):
- self._connection_param = connection_param
- self._connection = None
- self._database = None
-
- self._combo = QComboBox()
- self._combo.setEditable(True)
- self.refreshItems()
- self._combo.currentIndexChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
- self._combo.lineEdit().editingFinished.connect(lambda: self.widgetValueHasChanged.emit(self))
-
- return self._combo
-
- def postInitialize(self, wrappers):
- for wrapper in wrappers:
- if wrapper.parameterDefinition().name() == self._connection_param:
- self.connection_wrapper = wrapper
- self.setConnection(wrapper.parameterValue())
- wrapper.widgetValueHasChanged.connect(self.connectionChanged)
- break
-
- def connectionChanged(self, wrapper):
- connection = wrapper.parameterValue()
- if connection == self._connection:
- return
- self.setConnection(connection)
-
- def setConnection(self, connection):
- self._connection = connection
- # when there is NO connection (yet), this gets called with a ''-connection
- if isinstance(connection, str) and connection != '':
- self._database = GeoDB.from_name(connection)
- else:
- self._database = None
- self.refreshItems()
- self.widgetValueHasChanged.emit(self)
-
- def refreshItems(self):
- value = self.comboValue(combobox=self._combo)
-
- self._combo.clear()
-
- if self._database is not None:
- for schema in self._database.list_schemas():
- self._combo.addItem(schema[1], schema[1])
-
- if self.dialogType == DIALOG_MODELER:
- strings = self.dialog.getAvailableValuesOfType(
- [QgsProcessingParameterString, QgsProcessingParameterNumber, QgsProcessingParameterFile,
- QgsProcessingParameterField, QgsProcessingParameterExpression], QgsProcessingOutputString)
- for text, data in [(self.dialog.resolveValueDescription(s), s) for s in strings]:
- self._combo.addItem(text, data)
-
- self.setComboValue(value, self._combo)
-
- def setValue(self, value):
- self.setComboValue(value, self._combo)
- self.widgetValueHasChanged.emit(self)
-
- def value(self):
- return self.comboValue(combobox=self._combo)
-
- def database(self):
- return self._database
-
-
-class TableWidgetWrapper(WidgetWrapper):
- """
- WidgetWrapper for ParameterString that create and manage a combobox widget
- with existing tables from a parent schema parameter.
- """
-
- def createWidget(self, schema_param=None):
- self._schema_param = schema_param
- self._database = None
- self._schema = None
-
- self._combo = QComboBox()
- self._combo.setEditable(True)
- self.refreshItems()
- self._combo.currentIndexChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
- self._combo.lineEdit().editingFinished.connect(lambda: self.widgetValueHasChanged.emit(self))
-
- return self._combo
-
- def postInitialize(self, wrappers):
- for wrapper in wrappers:
- if wrapper.parameterDefinition().name() == self._schema_param:
- self.schema_wrapper = wrapper
- self.setSchema(wrapper.database(), wrapper.parameterValue())
- wrapper.widgetValueHasChanged.connect(self.schemaChanged)
- break
-
- def schemaChanged(self, wrapper):
- database = wrapper.database()
- schema = wrapper.parameterValue()
- if database == self._database and schema == self._schema:
- return
- self.setSchema(database, schema)
-
- def setSchema(self, database, schema):
- self._database = database
- self._schema = schema
- self.refreshItems()
- self.widgetValueHasChanged.emit(self)
-
- def refreshItems(self):
- value = self.comboValue(combobox=self._combo)
-
- self._combo.clear()
-
- if (self._database is not None and isinstance(self._schema, str)):
- for table in self._database.list_geotables(self._schema):
- self._combo.addItem(table[0], table[0])
-
- if self.dialogType == DIALOG_MODELER:
- strings = self.dialog.getAvailableValuesOfType(
- [QgsProcessingParameterString, QgsProcessingParameterNumber, QgsProcessingParameterFile,
- QgsProcessingParameterField, QgsProcessingParameterExpression], QgsProcessingOutputString)
- for text, data in [(self.dialog.resolveValueDescription(s), s) for s in strings]:
- self._combo.addItem(text, data)
-
- self.setComboValue(value, self._combo)
-
- def setValue(self, value):
- self.setComboValue(value, self._combo)
- self.widgetValueHasChanged.emit(self)
-
- def value(self):
- return self.comboValue(combobox=self._combo)
diff --git a/python/plugins/processing/images/delete.svg b/python/plugins/processing/images/delete.svg
deleted file mode 100644
index 6c1a09a1ca6e..000000000000
--- a/python/plugins/processing/images/delete.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/python/plugins/processing/images/edit.svg b/python/plugins/processing/images/edit.svg
deleted file mode 100644
index cc9af01b02bc..000000000000
--- a/python/plugins/processing/images/edit.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/python/plugins/processing/images/input.svg b/python/plugins/processing/images/input.svg
deleted file mode 100644
index 42fec1775544..000000000000
--- a/python/plugins/processing/images/input.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/python/plugins/processing/images/iterate.png b/python/plugins/processing/images/iterate.png
deleted file mode 100644
index 384147dcab29..000000000000
Binary files a/python/plugins/processing/images/iterate.png and /dev/null differ
diff --git a/python/plugins/processing/images/minus.svg b/python/plugins/processing/images/minus.svg
deleted file mode 100644
index 1cc42c97ce5e..000000000000
--- a/python/plugins/processing/images/minus.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/python/plugins/processing/images/namespace.png b/python/plugins/processing/images/namespace.png
deleted file mode 100644
index 4a9423807397..000000000000
Binary files a/python/plugins/processing/images/namespace.png and /dev/null differ
diff --git a/python/plugins/processing/images/networkanalysis.svg b/python/plugins/processing/images/networkanalysis.svg
deleted file mode 100644
index 1ecbadb6fa96..000000000000
--- a/python/plugins/processing/images/networkanalysis.svg
+++ /dev/null
@@ -1,798 +0,0 @@
-
-
-
-
- Fast road
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
- Fast road
- 2012-08-13
-
-
- Robert Szczepanek
-
-
-
-
- Robert Szczepanek
-
-
-
-
- vector
- network
- optimization
-
-
- GIS icons 0.2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/python/plugins/processing/images/output.svg b/python/plugins/processing/images/output.svg
deleted file mode 100644
index b11d50657b8b..000000000000
--- a/python/plugins/processing/images/output.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/python/plugins/processing/images/plus.svg b/python/plugins/processing/images/plus.svg
deleted file mode 100644
index eeb05d1a95d8..000000000000
--- a/python/plugins/processing/images/plus.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/python/plugins/processing/images/postgis.png b/python/plugins/processing/images/postgis.png
deleted file mode 100644
index 1ec9992e50cb..000000000000
Binary files a/python/plugins/processing/images/postgis.png and /dev/null differ
diff --git a/python/plugins/processing/images/zonalstats.png b/python/plugins/processing/images/zonalstats.png
deleted file mode 100644
index 1f812a74b8d9..000000000000
Binary files a/python/plugins/processing/images/zonalstats.png and /dev/null differ
diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py
index 3f787e85a968..36d4ba38b2f2 100644
--- a/python/plugins/processing/modeler/ModelerDialog.py
+++ b/python/plugins/processing/modeler/ModelerDialog.py
@@ -120,12 +120,17 @@ def runModel(self):
duration=5)
return
+ def on_finished(successful, results):
+ self.setLastRunChildAlgorithmResults(dlg.results().get('CHILD_RESULTS', {}))
+ self.setLastRunChildAlgorithmInputs(dlg.results().get('CHILD_INPUTS', {}))
+
dlg = AlgorithmDialog(self.model().create(), parent=self)
dlg.setParameters(self.model().designerParameterValues())
+ dlg.algorithmFinished.connect(on_finished)
dlg.exec_()
if dlg.wasExecuted():
- self.model().setDesignerParameterValues(dlg.getParameterValues())
+ self.model().setDesignerParameterValues(dlg.createProcessingParameters())
def saveInProject(self):
if not self.validateSave():
@@ -200,7 +205,7 @@ def repaintModel(self, showControls=True):
showComments = QgsSettings().value("/Processing/Modeler/ShowComments", True, bool)
if not showComments:
- self.scene.setFlag(QgsModelGraphicsScene.FlagHideComments)
+ scene.setFlag(QgsModelGraphicsScene.FlagHideComments)
context = createContext()
scene.createItems(self.model(), context)
@@ -214,6 +219,7 @@ def create_widget_context(self):
widget_context.setProject(QgsProject.instance())
if iface is not None:
widget_context.setMapCanvas(iface.mapCanvas())
+ widget_context.setActiveLayer(iface.activeLayer())
widget_context.setModel(self.model())
return widget_context
diff --git a/python/plugins/processing/modeler/ModelerGraphicItem.py b/python/plugins/processing/modeler/ModelerGraphicItem.py
index 9db0500875e1..86cc1c0634a6 100644
--- a/python/plugins/processing/modeler/ModelerGraphicItem.py
+++ b/python/plugins/processing/modeler/ModelerGraphicItem.py
@@ -55,23 +55,28 @@ def create_widget_context(self):
widget_context.setProject(QgsProject.instance())
if iface is not None:
widget_context.setMapCanvas(iface.mapCanvas())
+ widget_context.setActiveLayer(iface.activeLayer())
+
widget_context.setModel(self.model())
return widget_context
def edit(self, edit_comment=False):
existing_param = self.model().parameterDefinition(self.component().parameterName())
comment = self.component().comment().description()
+ comment_color = self.component().comment().color()
new_param = None
if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param):
# boo, old api
dlg = ModelerParameterDefinitionDialog(self.model(),
param=existing_param)
dlg.setComments(comment)
+ dlg.setCommentColor(comment_color)
if edit_comment:
dlg.switchToCommentTab()
if dlg.exec_():
new_param = dlg.param
comment = dlg.comments()
+ comment_color = dlg.commentColor()
else:
# yay, use new API!
context = createContext()
@@ -82,12 +87,14 @@ def edit(self, edit_comment=False):
definition=existing_param,
algorithm=self.model())
dlg.setComments(comment)
+ dlg.setCommentColor(comment_color)
if edit_comment:
dlg.switchToCommentTab()
if dlg.exec_():
new_param = dlg.createParameter(existing_param.name())
comment = dlg.comments()
+ comment_color = dlg.commentColor()
if new_param is not None:
self.aboutToChange.emit(self.tr('Edit {}').format(new_param.description()))
@@ -95,6 +102,7 @@ def edit(self, edit_comment=False):
self.component().setParameterName(new_param.name())
self.component().setDescription(new_param.name())
self.component().comment().setDescription(comment)
+ self.component().comment().setColor(comment_color)
self.model().addModelParameter(new_param, self.component())
self.setLabel(new_param.description())
self.requestModelRepaint.emit()
@@ -123,6 +131,7 @@ def edit(self, edit_comment=False):
dlg = ModelerParametersDialog(elemAlg, self.model(), self.component().childId(),
self.component().configuration())
dlg.setComments(self.component().comment().description())
+ dlg.setCommentColor(self.component().comment().color())
if edit_comment:
dlg.switchToCommentTab()
if dlg.exec_():
@@ -158,6 +167,7 @@ def edit(self, edit_comment=False):
dlg = ModelerParameterDefinitionDialog(self.model(),
param=self.model().parameterDefinition(param_name))
dlg.setComments(self.component().comment().description())
+ dlg.setCommentColor(self.component().comment().color())
if edit_comment:
dlg.switchToCommentTab()
@@ -167,6 +177,7 @@ def edit(self, edit_comment=False):
model_output.setDefaultValue(dlg.param.defaultValue())
model_output.setMandatory(not (dlg.param.flags() & QgsProcessingParameterDefinition.FlagOptional))
model_output.comment().setDescription(dlg.comments())
+ model_output.comment().setColor(dlg.commentColor())
self.aboutToChange.emit(self.tr('Edit {}').format(model_output.description()))
self.model().updateDestinationParameters()
self.requestModelRepaint.emit()
diff --git a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py
index 070562c64219..ea4bed28ca78 100755
--- a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py
+++ b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py
@@ -36,31 +36,16 @@
QMessageBox,
QTabWidget,
QWidget,
- QTextEdit)
+ QTextEdit,
+ QHBoxLayout)
+from qgis.PyQt.QtGui import QColor
-from qgis.gui import QgsExpressionLineEdit, QgsProjectionSelectionWidget
+from qgis.gui import (QgsProcessingLayerOutputDestinationWidget,
+ QgsColorButton)
from qgis.core import (QgsApplication,
QgsSettings,
QgsProcessing,
- QgsCoordinateReferenceSystem,
QgsProcessingParameterDefinition,
- QgsProcessingParameterCrs,
- QgsProcessingParameterMapLayer,
- QgsProcessingParameterExtent,
- QgsProcessingParameterPoint,
- QgsProcessingParameterMatrix,
- QgsProcessingParameterMultipleLayers,
- QgsProcessingParameterNumber,
- QgsProcessingParameterDistance,
- QgsProcessingParameterScale,
- QgsProcessingParameterRange,
- QgsProcessingParameterRasterLayer,
- QgsProcessingParameterEnum,
- QgsProcessingParameterExpression,
- QgsProcessingParameterVectorLayer,
- QgsProcessingParameterField,
- QgsProcessingParameterFeatureSource,
- QgsProcessingParameterBand,
QgsProcessingDestinationParameter,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterFileDestination,
@@ -68,9 +53,6 @@
QgsProcessingParameterRasterDestination,
QgsProcessingParameterVectorDestination)
-from processing.gui.DestinationSelectionPanel import DestinationSelectionPanel
-from processing.gui.enummodelerwidget import EnumModelerWidget
-from processing.gui.matrixmodelerwidget import MatrixModelerWidget
from processing.core import parameters
from processing.modeler.exceptions import UndefinedParameterException
@@ -79,34 +61,7 @@ class ModelerParameterDefinitionDialog(QDialog):
@staticmethod
def use_legacy_dialog(param=None, paramType=None):
- if paramType in (parameters.PARAMETER_TABLE_FIELD,
- parameters.PARAMETER_BAND,
- parameters.PARAMETER_VECTOR,
- parameters.PARAMETER_TABLE,
- parameters.PARAMETER_MULTIPLE,
- parameters.PARAMETER_NUMBER,
- parameters.PARAMETER_DISTANCE,
- parameters.PARAMETER_SCALE,
- parameters.PARAMETER_EXPRESSION,
- parameters.PARAMETER_POINT,
- parameters.PARAMETER_CRS,
- parameters.PARAMETER_ENUM,
- parameters.PARAMETER_MATRIX):
- return True
- elif isinstance(param, (QgsProcessingParameterField,
- QgsProcessingParameterBand,
- QgsProcessingParameterFeatureSource,
- QgsProcessingParameterVectorLayer,
- QgsProcessingParameterMultipleLayers,
- QgsProcessingParameterNumber,
- QgsProcessingParameterDistance,
- QgsProcessingParameterScale,
- QgsProcessingParameterExpression,
- QgsProcessingParameterPoint,
- QgsProcessingParameterCrs,
- QgsProcessingParameterEnum,
- QgsProcessingParameterMatrix,
- QgsProcessingDestinationParameter)):
+ if isinstance(param, QgsProcessingDestinationParameter):
return True
# yay, use new API!
@@ -152,203 +107,9 @@ def setupUi(self):
if isinstance(self.param, QgsProcessingParameterDefinition):
self.nameTextBox.setText(self.param.description())
- if self.paramType == parameters.PARAMETER_TABLE_FIELD or \
- isinstance(self.param, QgsProcessingParameterField):
- self.verticalLayout.addWidget(QLabel(self.tr('Parent layer')))
- self.parentCombo = QComboBox()
- idx = 0
- for param in list(self.alg.parameterComponents().values()):
- definition = self.alg.parameterDefinition(param.parameterName())
- if isinstance(definition, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer)):
- self.parentCombo.addItem(definition.description(), definition.name())
- if self.param is not None:
- if self.param.parentLayerParameterName() == definition.name():
- self.parentCombo.setCurrentIndex(idx)
- idx += 1
- self.verticalLayout.addWidget(self.parentCombo)
-
- # add the datatype selector
- self.verticalLayout.addWidget(QLabel(self.tr('Allowed data type')))
- self.datatypeCombo = QComboBox()
- self.datatypeCombo.addItem(self.tr('Any'), -1)
- self.datatypeCombo.addItem(self.tr('Number'), 0)
- self.datatypeCombo.addItem(self.tr('String'), 1)
- self.datatypeCombo.addItem(self.tr('Date/time'), 2)
- self.verticalLayout.addWidget(self.datatypeCombo)
-
- if self.param is not None and self.param.dataType() is not None:
- # QComboBoxes indexes start at 0,
- # self.param.datatype start with -1 that is why I need to do +1
- datatypeIndex = self.param.dataType() + 1
- self.datatypeCombo.setCurrentIndex(datatypeIndex)
-
- self.multipleCheck = QCheckBox()
- self.multipleCheck.setText(self.tr('Accept multiple fields'))
- self.multipleCheck.setChecked(False)
- if self.param is not None:
- self.multipleCheck.setChecked(self.param.allowMultiple())
- self.verticalLayout.addWidget(self.multipleCheck)
-
- self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
- self.defaultTextBox = QLineEdit()
- self.defaultTextBox.setToolTip(
- self.tr('Default field name, or ; separated list of field names for multiple field parameters'))
- if self.param is not None:
- default = self.param.defaultValue()
- if default is not None:
- self.defaultTextBox.setText(str(default))
- self.verticalLayout.addWidget(self.defaultTextBox)
-
- elif self.paramType == parameters.PARAMETER_BAND or \
- isinstance(self.param, QgsProcessingParameterBand):
- self.verticalLayout.addWidget(QLabel(self.tr('Parent layer')))
- self.parentCombo = QComboBox()
- idx = 0
- for param in list(self.alg.parameterComponents().values()):
- definition = self.alg.parameterDefinition(param.parameterName())
- if isinstance(definition, (QgsProcessingParameterRasterLayer)):
- self.parentCombo.addItem(definition.description(), definition.name())
- if self.param is not None:
- if self.param.parentLayerParameterName() == definition.name():
- self.parentCombo.setCurrentIndex(idx)
- idx += 1
- self.verticalLayout.addWidget(self.parentCombo)
- elif (self.paramType in (
- parameters.PARAMETER_VECTOR, parameters.PARAMETER_TABLE) or
- isinstance(self.param, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer))):
- self.verticalLayout.addWidget(QLabel(self.tr('Geometry type')))
- self.shapetypeCombo = QComboBox()
- self.shapetypeCombo.addItem(self.tr('Geometry Not Required'), QgsProcessing.TypeVector)
- self.shapetypeCombo.addItem(self.tr('Point'), QgsProcessing.TypeVectorPoint)
- self.shapetypeCombo.addItem(self.tr('Line'), QgsProcessing.TypeVectorLine)
- self.shapetypeCombo.addItem(self.tr('Polygon'), QgsProcessing.TypeVectorPolygon)
- self.shapetypeCombo.addItem(self.tr('Any Geometry Type'), QgsProcessing.TypeVectorAnyGeometry)
- if self.param is not None:
- self.shapetypeCombo.setCurrentIndex(self.shapetypeCombo.findData(self.param.dataTypes()[0]))
- self.verticalLayout.addWidget(self.shapetypeCombo)
- elif (self.paramType == parameters.PARAMETER_MULTIPLE
- or isinstance(self.param, QgsProcessingParameterMultipleLayers)):
- self.verticalLayout.addWidget(QLabel(self.tr('Data type')))
- self.datatypeCombo = QComboBox()
- self.datatypeCombo.addItem(self.tr('Any Map Layer'), QgsProcessing.TypeMapLayer)
- self.datatypeCombo.addItem(self.tr('Vector (No Geometry Required)'), QgsProcessing.TypeVector)
- self.datatypeCombo.addItem(self.tr('Vector (Point)'), QgsProcessing.TypeVectorPoint)
- self.datatypeCombo.addItem(self.tr('Vector (Line)'), QgsProcessing.TypeVectorLine)
- self.datatypeCombo.addItem(self.tr('Vector (Polygon)'), QgsProcessing.TypeVectorPolygon)
- self.datatypeCombo.addItem(self.tr('Vector (Any Geometry Type)'), QgsProcessing.TypeVectorAnyGeometry)
- self.datatypeCombo.addItem(self.tr('Raster'), QgsProcessing.TypeRaster)
- self.datatypeCombo.addItem(self.tr('File'), QgsProcessing.TypeFile)
- if self.param is not None:
- self.datatypeCombo.setCurrentIndex(self.datatypeCombo.findData(self.param.layerType()))
- self.verticalLayout.addWidget(self.datatypeCombo)
- elif (self.paramType in (parameters.PARAMETER_NUMBER, parameters.PARAMETER_DISTANCE, parameters.PARAMETER_SCALE)
- or isinstance(self.param, (QgsProcessingParameterNumber, QgsProcessingParameterDistance, QgsProcessingParameterScale))):
-
- if (self.paramType == parameters.PARAMETER_DISTANCE
- or isinstance(self.param, QgsProcessingParameterDistance)):
- self.verticalLayout.addWidget(QLabel(self.tr('Linked input')))
- self.parentCombo = QComboBox()
- self.parentCombo.addItem('', '')
- idx = 1
- for param in list(self.alg.parameterComponents().values()):
- definition = self.alg.parameterDefinition(param.parameterName())
- if isinstance(definition, (QgsProcessingParameterFeatureSource,
- QgsProcessingParameterVectorLayer,
- QgsProcessingParameterMapLayer,
- QgsProcessingParameterCrs)):
- self.parentCombo.addItem(definition.description(), definition.name())
- if self.param is not None:
- if self.param.parentParameterName() == definition.name():
- self.parentCombo.setCurrentIndex(idx)
- idx += 1
- self.verticalLayout.addWidget(self.parentCombo)
- elif (self.paramType != parameters.PARAMETER_SCALE and not
- isinstance(self.param, QgsProcessingParameterScale)):
- self.verticalLayout.addWidget(QLabel(self.tr('Number type')))
- self.type_combo = QComboBox()
- self.type_combo.addItem(self.tr('Float'), QgsProcessingParameterNumber.Double)
- self.type_combo.addItem(self.tr('Integer'), QgsProcessingParameterNumber.Integer)
- if self.param:
- self.type_combo.setCurrentIndex(self.type_combo.findData(self.param.dataType()))
- self.verticalLayout.addWidget(self.type_combo)
-
- if (self.paramType != parameters.PARAMETER_SCALE and not
- isinstance(self.param, QgsProcessingParameterScale)):
- self.verticalLayout.addWidget(QLabel(self.tr('Min value')))
- self.minTextBox = QLineEdit()
- self.verticalLayout.addWidget(self.minTextBox)
- self.verticalLayout.addWidget(QLabel(self.tr('Max value')))
- self.maxTextBox = QLineEdit()
- self.verticalLayout.addWidget(self.maxTextBox)
- if self.param is not None:
- self.minTextBox.setText(str(self.param.minimum()))
- self.maxTextBox.setText(str(self.param.maximum()))
- self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
- self.defaultTextBox = QLineEdit()
- self.defaultTextBox.setText(self.tr('0'))
- if self.param is not None:
- default = self.param.defaultValue()
- if self.param.dataType() == QgsProcessingParameterNumber.Integer:
- default = int(math.floor(float(default)))
- if default:
- self.defaultTextBox.setText(str(default))
- self.verticalLayout.addWidget(self.defaultTextBox)
- elif (self.paramType == parameters.PARAMETER_EXPRESSION
- or isinstance(self.param, QgsProcessingParameterExpression)):
- self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
- self.defaultEdit = QgsExpressionLineEdit()
- if self.param is not None:
- self.defaultEdit.setExpression(self.param.defaultValue())
- self.verticalLayout.addWidget(self.defaultEdit)
-
- self.verticalLayout.addWidget(QLabel(self.tr('Parent layer')))
- self.parentCombo = QComboBox()
- self.parentCombo.addItem(self.tr("None"), None)
- idx = 1
- for param in list(self.alg.parameterComponents().values()):
- definition = self.alg.parameterDefinition(param.parameterName())
- if isinstance(definition, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer)):
- self.parentCombo.addItem(definition.description(), definition.name())
- if self.param is not None:
- if self.param.parentLayerParameterName() == definition.name():
- self.parentCombo.setCurrentIndex(idx)
- idx += 1
- self.verticalLayout.addWidget(self.parentCombo)
- elif (self.paramType == parameters.PARAMETER_POINT
- or isinstance(self.param, QgsProcessingParameterPoint)):
- self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
- self.defaultTextBox = QLineEdit()
- if self.param is not None:
- self.defaultTextBox.setText(self.param.defaultValue())
- self.verticalLayout.addWidget(self.defaultTextBox)
- elif (self.paramType == parameters.PARAMETER_CRS
- or isinstance(self.param, QgsProcessingParameterCrs)):
- self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
- self.selector = QgsProjectionSelectionWidget()
- if self.param is not None:
- self.selector.setCrs(QgsCoordinateReferenceSystem(self.param.defaultValue()))
- else:
- self.selector.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
- self.verticalLayout.addWidget(self.selector)
- elif self.paramType == parameters.PARAMETER_ENUM or \
- isinstance(self.param, QgsProcessingParameterEnum):
- self.widget = EnumModelerWidget(self)
- if self.param is not None:
- self.widget.setAllowMultiple(bool(self.param.allowMultiple()))
- self.widget.setOptions(self.param.options())
- self.widget.setDefault(self.param.defaultValue())
- self.verticalLayout.addWidget(self.widget)
- elif self.paramType == parameters.PARAMETER_MATRIX or \
- isinstance(self.param, QgsProcessingParameterMatrix):
- self.widget = MatrixModelerWidget(self)
- if self.param is not None:
- self.widget.setValue(self.param.headers(), self.param.defaultValue())
- self.widget.setFixedRows(self.param.hasFixedNumberRows())
- self.verticalLayout.addWidget(self.widget)
-
- elif isinstance(self.param, QgsProcessingDestinationParameter):
+ if isinstance(self.param, QgsProcessingDestinationParameter):
self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
- self.defaultWidget = DestinationSelectionPanel(self.param, self.alg, default_selection=True)
+ self.defaultWidget = QgsProcessingLayerOutputDestinationWidget(self.param, defaultSelection=True)
self.verticalLayout.addWidget(self.defaultWidget)
self.verticalLayout.addSpacing(20)
@@ -388,15 +149,26 @@ def setupUi(self):
self.commentLayout = QVBoxLayout()
self.commentEdit = QTextEdit()
self.commentEdit.setAcceptRichText(False)
- self.commentLayout.addWidget(self.commentEdit)
+ self.commentLayout.addWidget(self.commentEdit, 1)
+
+ hl = QHBoxLayout()
+ hl.setContentsMargins(0, 0, 0, 0)
+ hl.addWidget(QLabel(self.tr('Color')))
+ self.comment_color_button = QgsColorButton()
+ self.comment_color_button.setAllowOpacity(True)
+ self.comment_color_button.setWindowTitle(self.tr('Comment Color'))
+ self.comment_color_button.setShowNull(True, self.tr('Default'))
+ hl.addWidget(self.comment_color_button)
+ self.commentLayout.addLayout(hl)
+
w2 = QWidget()
w2.setLayout(self.commentLayout)
self.tab.addTab(w2, self.tr('Comments'))
self.buttonBox = QDialogButtonBox(self)
self.buttonBox.setOrientation(Qt.Horizontal)
- self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel
- | QDialogButtonBox.Ok)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel |
+ QDialogButtonBox.Ok)
self.buttonBox.setObjectName('buttonBox')
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
@@ -411,6 +183,15 @@ def setComments(self, text):
def comments(self):
return self.commentEdit.toPlainText()
+ def setCommentColor(self, color):
+ if color.isValid():
+ self.comment_color_button.setColor(color)
+ else:
+ self.comment_color_button.setToNull()
+
+ def commentColor(self):
+ return self.comment_color_button.color() if not self.comment_color_button.isNull() else QColor()
+
def accept(self):
description = self.nameTextBox.text()
if description.strip() == '':
@@ -428,147 +209,36 @@ def accept(self):
i += 1
else:
name = self.param.name()
- if (self.paramType == parameters.PARAMETER_TABLE_FIELD
- or isinstance(self.param, QgsProcessingParameterField)):
- if self.parentCombo.currentIndex() < 0:
- QMessageBox.warning(self, self.tr('Unable to define parameter'),
- self.tr('Wrong or missing parameter values'))
- return
- parent = self.parentCombo.currentData()
- datatype = self.datatypeCombo.currentData()
- default = self.defaultTextBox.text()
- if not default:
- default = None
- self.param = QgsProcessingParameterField(name, description, defaultValue=default,
- parentLayerParameterName=parent, type=datatype,
- allowMultiple=self.multipleCheck.isChecked())
- elif (self.paramType == parameters.PARAMETER_BAND
- or isinstance(self.param, QgsProcessingParameterBand)):
- if self.parentCombo.currentIndex() < 0:
- QMessageBox.warning(self, self.tr('Unable to define parameter'),
- self.tr('Wrong or missing parameter values'))
- return
- parent = self.parentCombo.currentData()
- self.param = QgsProcessingParameterBand(name, description, None, parent)
- elif (self.paramType == parameters.PARAMETER_MAP_LAYER
- or isinstance(self.param, QgsProcessingParameterMapLayer)):
- self.param = QgsProcessingParameterMapLayer(
- name, description)
- elif (self.paramType == parameters.PARAMETER_RASTER
- or isinstance(self.param, QgsProcessingParameterRasterLayer)):
- self.param = QgsProcessingParameterRasterLayer(
- name, description)
- elif (self.paramType == parameters.PARAMETER_TABLE
- or isinstance(self.param, QgsProcessingParameterVectorLayer)):
- self.param = QgsProcessingParameterVectorLayer(
- name, description,
- [self.shapetypeCombo.currentData()])
- elif (self.paramType == parameters.PARAMETER_VECTOR
- or isinstance(self.param, QgsProcessingParameterFeatureSource)):
- self.param = QgsProcessingParameterFeatureSource(
- name, description,
- [self.shapetypeCombo.currentData()])
- elif (self.paramType == parameters.PARAMETER_MULTIPLE
- or isinstance(self.param, QgsProcessingParameterMultipleLayers)):
- self.param = QgsProcessingParameterMultipleLayers(
- name, description,
- self.datatypeCombo.currentData())
- elif (self.paramType == parameters.PARAMETER_DISTANCE
- or isinstance(self.param, QgsProcessingParameterDistance)):
- self.param = QgsProcessingParameterDistance(name, description,
- self.defaultTextBox.text())
- try:
- vmin = self.minTextBox.text().strip()
- if not vmin == '':
- self.param.setMinimum(float(vmin))
- vmax = self.maxTextBox.text().strip()
- if not vmax == '':
- self.param.setMaximum(float(vmax))
- except:
- QMessageBox.warning(self, self.tr('Unable to define parameter'),
- self.tr('Wrong or missing parameter values'))
- return
-
- if self.parentCombo.currentIndex() < 0:
- QMessageBox.warning(self, self.tr('Unable to define parameter'),
- self.tr('Wrong or missing parameter values'))
- return
- parent = self.parentCombo.currentData()
- if parent:
- self.param.setParentParameterName(parent)
- elif (self.paramType == parameters.PARAMETER_SCALE
- or isinstance(self.param, QgsProcessingParameterScale)):
- self.param = QgsProcessingParameterScale(name, description,
- self.defaultTextBox.text())
- elif (self.paramType == parameters.PARAMETER_NUMBER
- or isinstance(self.param, QgsProcessingParameterNumber)):
-
- type = self.type_combo.currentData()
- self.param = QgsProcessingParameterNumber(name, description, type,
- self.defaultTextBox.text())
- try:
- vmin = self.minTextBox.text().strip()
- if not vmin == '':
- self.param.setMinimum(float(vmin))
- vmax = self.maxTextBox.text().strip()
- if not vmax == '':
- self.param.setMaximum(float(vmax))
- except:
- QMessageBox.warning(self, self.tr('Unable to define parameter'),
- self.tr('Wrong or missing parameter values'))
- return
- elif (self.paramType == parameters.PARAMETER_EXPRESSION
- or isinstance(self.param, QgsProcessingParameterExpression)):
- parent = self.parentCombo.currentData()
- self.param = QgsProcessingParameterExpression(name, description,
- str(self.defaultEdit.expression()),
- parent)
- elif (self.paramType == parameters.PARAMETER_EXTENT
- or isinstance(self.param, QgsProcessingParameterExtent)):
- self.param = QgsProcessingParameterExtent(name, description)
- elif (self.paramType == parameters.PARAMETER_POINT
- or isinstance(self.param, QgsProcessingParameterPoint)):
- self.param = QgsProcessingParameterPoint(name, description,
- str(self.defaultTextBox.text()))
- elif (self.paramType == parameters.PARAMETER_CRS
- or isinstance(self.param, QgsProcessingParameterCrs)):
- self.param = QgsProcessingParameterCrs(name, description, self.selector.crs().authid())
- elif (self.paramType == parameters.PARAMETER_ENUM
- or isinstance(self.param, QgsProcessingParameterEnum)):
- self.param = QgsProcessingParameterEnum(name, description, self.widget.options(), self.widget.allowMultiple(), self.widget.defaultOptions())
- elif (self.paramType == parameters.PARAMETER_MATRIX
- or isinstance(self.param, QgsProcessingParameterMatrix)):
- self.param = QgsProcessingParameterMatrix(name, description, hasFixedNumberRows=self.widget.fixedRows(), headers=self.widget.headers(), defaultValue=self.widget.value())
# Destination parameter
- elif (isinstance(self.param, QgsProcessingParameterFeatureSink)):
+ if (isinstance(self.param, QgsProcessingParameterFeatureSink)):
self.param = QgsProcessingParameterFeatureSink(
name=name,
description=self.param.description(),
type=self.param.dataType(),
- defaultValue=self.defaultWidget.getValue())
+ defaultValue=self.defaultWidget.value())
elif (isinstance(self.param, QgsProcessingParameterFileDestination)):
self.param = QgsProcessingParameterFileDestination(
name=name,
description=self.param.description(),
fileFilter=self.param.fileFilter(),
- defaultValue=self.defaultWidget.getValue())
+ defaultValue=self.defaultWidget.value())
elif (isinstance(self.param, QgsProcessingParameterFolderDestination)):
self.param = QgsProcessingParameterFolderDestination(
name=name,
description=self.param.description(),
- defaultValue=self.defaultWidget.getValue())
+ defaultValue=self.defaultWidget.value())
elif (isinstance(self.param, QgsProcessingParameterRasterDestination)):
self.param = QgsProcessingParameterRasterDestination(
name=name,
description=self.param.description(),
- defaultValue=self.defaultWidget.getValue())
+ defaultValue=self.defaultWidget.value())
elif (isinstance(self.param, QgsProcessingParameterVectorDestination)):
self.param = QgsProcessingParameterVectorDestination(
name=name,
description=self.param.description(),
type=self.param.dataType(),
- defaultValue=self.defaultWidget.getValue())
+ defaultValue=self.defaultWidget.value())
else:
if self.paramType:
diff --git a/python/plugins/processing/modeler/ModelerParametersDialog.py b/python/plugins/processing/modeler/ModelerParametersDialog.py
index 0bdc8755b054..ad2ae70f7080 100644
--- a/python/plugins/processing/modeler/ModelerParametersDialog.py
+++ b/python/plugins/processing/modeler/ModelerParametersDialog.py
@@ -23,31 +23,19 @@
import webbrowser
-from qgis.PyQt.QtCore import (Qt,
- QUrl,
- QMetaObject,
- QByteArray)
+from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QLineEdit,
QFrame, QPushButton, QSizePolicy, QVBoxLayout,
QHBoxLayout, QWidget, QTabWidget, QTextEdit)
+from qgis.PyQt.QtGui import QColor
from qgis.core import (Qgis,
QgsProject,
QgsProcessingParameterDefinition,
- QgsProcessingParameterPoint,
- QgsProcessingParameterExtent,
- QgsProcessingModelAlgorithm,
QgsProcessingModelOutput,
QgsProcessingModelChildAlgorithm,
QgsProcessingModelChildParameterSource,
- QgsProcessingParameterFeatureSink,
- QgsProcessingParameterMultipleLayers,
- QgsProcessingParameterRasterDestination,
- QgsProcessingParameterFileDestination,
- QgsProcessingParameterFolderDestination,
- QgsProcessingParameterVectorDestination,
- QgsProcessingOutputDefinition,
- QgsSettings)
+ QgsProcessingOutputDefinition)
from qgis.gui import (QgsGui,
QgsMessageBar,
@@ -56,7 +44,10 @@
QgsHelp,
QgsProcessingContextGenerator,
QgsProcessingModelerParameterWidget,
- QgsProcessingParameterWidgetContext)
+ QgsProcessingParameterWidgetContext,
+ QgsPanelWidget,
+ QgsPanelWidgetStack,
+ QgsColorButton)
from qgis.utils import iface
from processing.gui.wrappers import WidgetWrapperFactory
@@ -69,15 +60,96 @@
class ModelerParametersDialog(QDialog):
def __init__(self, alg, model, algName=None, configuration=None):
- QDialog.__init__(self)
+ super().__init__()
+ self.setObjectName('ModelerParametersDialog')
self.setModal(True)
- self._alg = alg # The algorithm to define in this dialog. It is an instance of QgsProcessingAlgorithm
- self.model = model # The model this algorithm is going to be added to. It is an instance of QgsProcessingModelAlgorithm
- self.childId = algName # The name of the algorithm in the model, in case we are editing it and not defining it for the first time
+ if iface is not None:
+ self.setStyleSheet(iface.mainWindow().styleSheet())
+
+ # dammit this is SUCH as mess... stupid stable API
+ self._alg = alg # The algorithm to define in this dialog. It is an instance of QgsProcessingAlgorithm
+ self.model = model # The model this algorithm is going to be added to. It is an instance of QgsProcessingModelAlgorithm
+ self.childId = algName # The name of the algorithm in the model, in case we are editing it and not defining it for the first time
self.configuration = configuration
self.context = createContext()
+ self.setWindowTitle(self._alg.displayName())
+
+ self.widget = ModelerParametersWidget(alg, model, algName, configuration, context=self.context, dialog=self)
+ QgsGui.enableAutoGeometryRestore(self)
+
+ self.buttonBox = QDialogButtonBox()
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.Help)
+
+ self.buttonBox.accepted.connect(self.okPressed)
+ self.buttonBox.rejected.connect(self.reject)
+ self.buttonBox.helpRequested.connect(self.openHelp)
+
+ mainLayout = QVBoxLayout()
+ mainLayout.addWidget(self.widget, 1)
+ mainLayout.addWidget(self.buttonBox)
+ self.setLayout(mainLayout)
+
+ def setComments(self, text):
+ self.widget.setComments(text)
+
+ def comments(self):
+ return self.widget.comments()
+
+ def setCommentColor(self, color):
+ self.widget.setCommentColor(color)
+
+ def commentColor(self):
+ return self.widget.commentColor()
+
+ def switchToCommentTab(self):
+ self.widget.switchToCommentTab()
+
+ def getAvailableDependencies(self):
+ return self.widget.getAvailableDependencies()
+
+ def getDependenciesPanel(self):
+ return self.widget.getDependenciesPanel()
+
+ def getAvailableValuesOfType(self, paramType, outTypes=[], dataTypes=[]):
+ return self.widget.getAvailableValuesOfType(paramType, outTypes, dataTypes)
+
+ def resolveValueDescription(self, value):
+ return self.widget.resolveValueDescription(value)
+
+ def setPreviousValues(self):
+ self.widget.setPreviousValues()
+
+ def createAlgorithm(self):
+ return self.widget.createAlgorithm()
+
+ def okPressed(self):
+ if self.createAlgorithm() is not None:
+ self.accept()
+
+ def openHelp(self):
+ algHelp = self.widget.algorithm().helpUrl()
+ if not algHelp:
+ algHelp = QgsHelp.helpUrl("processing_algs/{}/{}.html#{}".format(
+ self.widget.algorithm().provider().helpId(), self.algorithm().groupId(),
+ "{}{}".format(self.algorithm().provider().helpId(), self.algorithm().name()))).toString()
+
+ if algHelp not in [None, ""]:
+ webbrowser.open(algHelp)
+
+
+class ModelerParametersPanelWidget(QgsPanelWidget):
+
+ def __init__(self, alg, model, algName=None, configuration=None, dialog=None, context=None):
+ super().__init__()
+ self._alg = alg # The algorithm to define in this dialog. It is an instance of QgsProcessingAlgorithm
+ self.model = model # The model this algorithm is going to be added to. It is an instance of QgsProcessingModelAlgorithm
+ self.childId = algName # The name of the algorithm in the model, in case we are editing it and not defining it for the first time
+ self.configuration = configuration
+ self.context = context
+ self.dialog = dialog
self.widget_labels = {}
class ContextGenerator(QgsProcessingContextGenerator):
@@ -94,48 +166,25 @@ def processingContext(self):
self.setupUi()
self.params = None
- settings = QgsSettings()
- self.restoreGeometry(settings.value("/Processing/modelParametersDialogGeometry", QByteArray()))
-
- def closeEvent(self, event):
- settings = QgsSettings()
- settings.setValue("/Processing/modelParametersDialogGeometry", self.saveGeometry())
- super(ModelerParametersDialog, self).closeEvent(event)
-
- def switchToCommentTab(self):
- self.tab.setCurrentIndex(1)
- self.commentEdit.setFocus()
- self.commentEdit.selectAll()
+ def algorithm(self):
+ return self._alg
def setupUi(self):
- self.checkBoxes = {}
self.showAdvanced = False
self.wrappers = {}
- self.valueItems = {}
- self.dependentItems = {}
self.algorithmItem = None
- self.resize(650, 450)
-
self.mainLayout = QVBoxLayout()
- self.tab = QTabWidget()
- self.mainLayout.addWidget(self.tab)
+ self.mainLayout.setContentsMargins(0, 0, 0, 0)
- self.buttonBox = QDialogButtonBox()
- self.buttonBox.setOrientation(Qt.Horizontal)
- self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.Help)
- self.setSizePolicy(QSizePolicy.Expanding,
- QSizePolicy.Expanding)
self.verticalLayout = QVBoxLayout()
- self.verticalLayout.setSpacing(5)
self.bar = QgsMessageBar()
self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.verticalLayout.addWidget(self.bar)
hLayout = QHBoxLayout()
- hLayout.setSpacing(5)
- hLayout.setMargin(0)
+ hLayout.setContentsMargins(0, 0, 0, 0)
descriptionLabel = QLabel(self.tr("Description"))
self.descriptionBox = QLineEdit()
self.descriptionBox.setText(self._alg.displayName())
@@ -151,6 +200,8 @@ def setupUi(self):
widget_context.setProject(QgsProject.instance())
if iface is not None:
widget_context.setMapCanvas(iface.mapCanvas())
+ widget_context.setActiveLayer(iface.activeLayer())
+
widget_context.setModel(self.model)
widget_context.setModelChildAlgorithmId(self.childId)
@@ -177,7 +228,7 @@ def setupUi(self):
if param.isDestination() or param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
- wrapper = WidgetWrapperFactory.create_wrapper(param, self)
+ wrapper = WidgetWrapperFactory.create_wrapper(param, self.dialog)
self.wrappers[param.name()] = wrapper
wrapper.setWidgetContext(widget_context)
@@ -187,8 +238,6 @@ def setupUi(self):
else:
widget = wrapper.widget
if widget is not None:
- self.valueItems[param.name()] = widget
-
if issubclass(wrapper.__class__, QgsProcessingModelerParameterWidget):
label = wrapper.createLabel()
else:
@@ -204,18 +253,29 @@ def setupUi(self):
self.verticalLayout.addWidget(label)
self.verticalLayout.addWidget(widget)
- for dest in self._alg.destinationParameterDefinitions():
- if dest.flags() & QgsProcessingParameterDefinition.FlagHidden:
+ for output in self._alg.destinationParameterDefinitions():
+ if output.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
- if isinstance(dest, (QgsProcessingParameterRasterDestination, QgsProcessingParameterVectorDestination,
- QgsProcessingParameterFeatureSink, QgsProcessingParameterFileDestination, QgsProcessingParameterFolderDestination)):
- label = QLabel(dest.description())
- item = QgsFilterLineEdit()
- if hasattr(item, 'setPlaceholderText'):
- item.setPlaceholderText(self.tr('[Enter name if this is a final result]'))
+
+ widget = QgsGui.processingGuiRegistry().createModelerParameterWidget(self.model,
+ self.childId,
+ output,
+ self.context)
+ widget.setDialog(self.dialog)
+ widget.setWidgetContext(widget_context)
+ widget.registerProcessingContextGenerator(self.context_generator)
+
+ self.wrappers[output.name()] = widget
+
+ item = QgsFilterLineEdit()
+ if hasattr(item, 'setPlaceholderText'):
+ item.setPlaceholderText(self.tr('[Enter name if this is a final result]'))
+
+ label = widget.createLabel()
+ if label is not None:
self.verticalLayout.addWidget(label)
- self.verticalLayout.addWidget(item)
- self.valueItems[dest.name()] = item
+
+ self.verticalLayout.addWidget(widget)
label = QLabel(' ')
self.verticalLayout.addWidget(label)
@@ -226,7 +286,6 @@ def setupUi(self):
self.verticalLayout.addStretch(1000)
self.setPreviousValues()
- self.setWindowTitle(self._alg.displayName())
self.verticalLayout2 = QVBoxLayout()
self.verticalLayout2.setSpacing(2)
self.verticalLayout2.setMargin(0)
@@ -236,36 +295,16 @@ def setupUi(self):
self.scrollArea = QgsScrollArea()
self.scrollArea.setWidget(self.paramPanel)
self.scrollArea.setWidgetResizable(True)
+ self.scrollArea.setFrameStyle(QFrame.NoFrame)
self.verticalLayout2.addWidget(self.scrollArea)
- self.buttonBox.accepted.connect(self.okPressed)
- self.buttonBox.rejected.connect(self.cancelPressed)
- self.buttonBox.helpRequested.connect(self.openHelp)
w = QWidget()
w.setLayout(self.verticalLayout2)
- self.tab.addTab(w, self.tr('Properties'))
-
- self.commentLayout = QVBoxLayout()
- self.commentEdit = QTextEdit()
- self.commentEdit.setAcceptRichText(False)
- self.commentLayout.addWidget(self.commentEdit)
- w2 = QWidget()
- w2.setLayout(self.commentLayout)
- self.tab.addTab(w2, self.tr('Comments'))
-
- self.mainLayout.addWidget(self.buttonBox)
+ self.mainLayout.addWidget(w)
self.setLayout(self.mainLayout)
- QMetaObject.connectSlotsByName(self)
-
- def setComments(self, text):
- self.commentEdit.setPlainText(text)
-
- def comments(self):
- return self.commentEdit.toPlainText()
-
- def getAvailableDependencies(self): # spellok
+ def getAvailableDependencies(self):
if self.childId is None:
dependent = []
else:
@@ -278,7 +317,7 @@ def getAvailableDependencies(self): # spellok
return opts
def getDependenciesPanel(self):
- return MultipleInputPanel([alg.description() for alg in self.getAvailableDependencies()]) # spellok
+ return MultipleInputPanel([alg.description() for alg in self.getAvailableDependencies()])
def showAdvancedParametersClicked(self):
self.showAdvanced = not self.showAdvanced
@@ -362,12 +401,37 @@ def setPreviousValues(self):
value = value.staticValue()
wrapper.setValue(value)
- for name, out in alg.modelOutputs().items():
- if out.childOutputName() in self.valueItems:
- self.valueItems[out.childOutputName()].setText(out.name())
+ for output in self.algorithm().destinationParameterDefinitions():
+ if output.flags() & QgsProcessingParameterDefinition.FlagHidden:
+ continue
+
+ model_output_name = None
+ for name, out in alg.modelOutputs().items():
+ if out.childId() == self.childId and out.childOutputName() == output.name():
+ # this destination parameter is linked to a model output
+ model_output_name = out.name()
+ break
+
+ value = None
+ if model_output_name is None and output.name() in alg.parameterSources():
+ value = alg.parameterSources()[output.name()]
+ if isinstance(value, list) and len(value) == 1:
+ value = value[0]
+ elif isinstance(value, list) and len(value) == 0:
+ value = None
+
+ wrapper = self.wrappers[output.name()]
+
+ if model_output_name is not None:
+ wrapper.setToModelOutput(model_output_name)
+ elif value is not None or output.defaultValue() is not None:
+ if value is None:
+ value = QgsProcessingModelChildParameterSource.fromStaticValue(output.defaultValue())
+
+ wrapper.setWidgetValue(value)
selected = []
- dependencies = self.getAvailableDependencies() # spellok
+ dependencies = self.getAvailableDependencies()
for idx, dependency in enumerate(dependencies):
if dependency.childId() in alg.dependencies():
selected.append(idx)
@@ -407,9 +471,9 @@ def createAlgorithm(self):
[isinstance(subval, QgsProcessingModelChildParameterSource) for subval in val])):
val = [QgsProcessingModelChildParameterSource.fromStaticValue(val)]
for subval in val:
- if (isinstance(subval, QgsProcessingModelChildParameterSource)
- and subval.source() == QgsProcessingModelChildParameterSource.StaticValue
- and not param.checkValueIsAcceptable(subval.staticValue())) \
+ if (isinstance(subval, QgsProcessingModelChildParameterSource) and
+ subval.source() == QgsProcessingModelChildParameterSource.StaticValue and
+ not param.checkValueIsAcceptable(subval.staticValue())) \
or (subval is None and not param.flags() & QgsProcessingParameterDefinition.FlagOptional):
self.bar.pushMessage(self.tr("Error"), self.tr("Wrong or missing value for parameter '{}'").format(
param.description()),
@@ -418,52 +482,145 @@ def createAlgorithm(self):
alg.addParameterSources(param.name(), val)
outputs = {}
- for dest in self._alg.destinationParameterDefinitions():
- if not dest.flags() & QgsProcessingParameterDefinition.FlagHidden:
- name = self.valueItems[dest.name()].text()
- if name.strip() != '':
- output = QgsProcessingModelOutput(name, name)
- output.setChildId(alg.childId())
- output.setChildOutputName(dest.name())
- outputs[name] = output
-
- if dest.flags() & QgsProcessingParameterDefinition.FlagIsModelOutput:
- if dest.name() not in outputs:
- output = QgsProcessingModelOutput(dest.name(), dest.name())
- output.setChildId(alg.childId())
- output.setChildOutputName(dest.name())
- outputs[dest.name()] = output
+ for output in self._alg.destinationParameterDefinitions():
+ if not output.flags() & QgsProcessingParameterDefinition.FlagHidden:
+ wrapper = self.wrappers[output.name()]
+
+ if wrapper.isModelOutput():
+ name = wrapper.modelOutputName()
+ if name:
+ model_output = QgsProcessingModelOutput(name, name)
+ model_output.setChildId(alg.childId())
+ model_output.setChildOutputName(output.name())
+ outputs[name] = model_output
+ else:
+ val = wrapper.value()
+
+ if isinstance(val, QgsProcessingModelChildParameterSource):
+ val = [val]
+
+ alg.addParameterSources(output.name(), val)
+
+ if output.flags() & QgsProcessingParameterDefinition.FlagIsModelOutput:
+ if output.name() not in outputs:
+ model_output = QgsProcessingModelOutput(output.name(), output.name())
+ model_output.setChildId(alg.childId())
+ model_output.setChildOutputName(output.name())
+ outputs[output.name()] = model_output
alg.setModelOutputs(outputs)
selectedOptions = self.dependenciesPanel.selectedoptions
- availableDependencies = self.getAvailableDependencies() # spellok
+ availableDependencies = self.getAvailableDependencies()
dep_ids = []
for selected in selectedOptions:
- dep_ids.append(availableDependencies[selected].childId()) # spellok
+ dep_ids.append(availableDependencies[selected].childId())
alg.setDependencies(dep_ids)
- #try:
- # self._alg.processBeforeAddingToModeler(alg, self.model)
- #except:
- # pass
-
- alg.comment().setDescription(self.comments())
return alg
- def okPressed(self):
- alg = self.createAlgorithm()
- if alg is not None:
- self.accept()
- def cancelPressed(self):
- self.reject()
+class ModelerParametersWidget(QWidget):
- def openHelp(self):
- algHelp = self._alg.helpUrl()
- if not algHelp:
- algHelp = QgsHelp.helpUrl("processing_algs/{}/{}.html#{}".format(
- self._alg.provider().helpId(), self._alg.groupId(), "{}{}".format(self._alg.provider().helpId(), self._alg.name()))).toString()
+ def __init__(self, alg, model, algName=None, configuration=None, dialog=None, context=None):
+ super().__init__()
+ self._alg = alg # The algorithm to define in this dialog. It is an instance of QgsProcessingAlgorithm
+ self.model = model # The model this algorithm is going to be added to. It is an instance of QgsProcessingModelAlgorithm
+ self.childId = algName # The name of the algorithm in the model, in case we are editing it and not defining it for the first time
+ self.configuration = configuration
+ self.context = context
+ self.dialog = dialog
- if algHelp not in [None, ""]:
- webbrowser.open(algHelp)
+ self.widget = ModelerParametersPanelWidget(alg, model, algName, configuration, dialog, context)
+
+ class ContextGenerator(QgsProcessingContextGenerator):
+
+ def __init__(self, context):
+ super().__init__()
+ self.processing_context = context
+
+ def processingContext(self):
+ return self.processing_context
+
+ self.context_generator = ContextGenerator(self.context)
+
+ self.setupUi()
+ self.params = None
+
+ def algorithm(self):
+ return self._alg
+
+ def switchToCommentTab(self):
+ self.tab.setCurrentIndex(1)
+ self.commentEdit.setFocus()
+ self.commentEdit.selectAll()
+
+ def setupUi(self):
+ self.mainLayout = QVBoxLayout()
+ self.mainLayout.setContentsMargins(0, 0, 0, 0)
+ self.tab = QTabWidget()
+ self.mainLayout.addWidget(self.tab)
+
+ self.param_widget = QgsPanelWidgetStack()
+ self.widget.setDockMode(True)
+ self.param_widget.setMainPanel(self.widget)
+
+ self.tab.addTab(self.param_widget, self.tr('Properties'))
+
+ self.commentLayout = QVBoxLayout()
+ self.commentEdit = QTextEdit()
+ self.commentEdit.setAcceptRichText(False)
+ self.commentLayout.addWidget(self.commentEdit, 1)
+
+ hl = QHBoxLayout()
+ hl.setContentsMargins(0, 0, 0, 0)
+ hl.addWidget(QLabel(self.tr('Color')))
+ self.comment_color_button = QgsColorButton()
+ self.comment_color_button.setAllowOpacity(True)
+ self.comment_color_button.setWindowTitle(self.tr('Comment Color'))
+ self.comment_color_button.setShowNull(True, self.tr('Default'))
+ hl.addWidget(self.comment_color_button)
+ self.commentLayout.addLayout(hl)
+
+ w2 = QWidget()
+ w2.setLayout(self.commentLayout)
+ self.tab.addTab(w2, self.tr('Comments'))
+
+ self.setLayout(self.mainLayout)
+
+ def setComments(self, text):
+ self.commentEdit.setPlainText(text)
+
+ def comments(self):
+ return self.commentEdit.toPlainText()
+
+ def setCommentColor(self, color):
+ if color.isValid():
+ self.comment_color_button.setColor(color)
+ else:
+ self.comment_color_button.setToNull()
+
+ def commentColor(self):
+ return self.comment_color_button.color() if not self.comment_color_button.isNull() else QColor()
+
+ def getAvailableDependencies(self):
+ return self.widget.getAvailableDependencies()
+
+ def getDependenciesPanel(self):
+ return self.widget.getDependenciesPanel()
+
+ def getAvailableValuesOfType(self, paramType, outTypes=[], dataTypes=[]):
+ return self.widget.getAvailableValuesOfType(paramType, outTypes, dataTypes)
+
+ def resolveValueDescription(self, value):
+ return self.widget.resolveValueDescription(value)
+
+ def setPreviousValues(self):
+ self.widget.setPreviousValues()
+
+ def createAlgorithm(self):
+ alg = self.widget.createAlgorithm()
+ if alg:
+ alg.comment().setDescription(self.comments())
+ alg.comment().setColor(self.commentColor())
+ return alg
diff --git a/python/plugins/processing/preconfigured/PreconfiguredAlgorithmDialog.py b/python/plugins/processing/preconfigured/PreconfiguredAlgorithmDialog.py
index d7c9ed82a4ab..4780a621ff48 100644
--- a/python/plugins/processing/preconfigured/PreconfiguredAlgorithmDialog.py
+++ b/python/plugins/processing/preconfigured/PreconfiguredAlgorithmDialog.py
@@ -50,7 +50,7 @@ def __init__(self, alg, toolbox):
def accept(self):
context = dataobjects.createContext()
try:
- parameters = self.getParameterValues()
+ parameters = self.createProcessingParameters()
self.setOutputValues()
ok, msg = self.algorithm().checkParameterValues(parameters, context)
if not ok:
diff --git a/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py b/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py
index ca332880bede..38d7978d133a 100644
--- a/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py
+++ b/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py
@@ -36,6 +36,7 @@
QgsProject,
QgsVectorLayer,
QgsRectangle,
+ QgsProjUtils,
QgsProcessingException,
QgsProcessingFeatureSourceDefinition)
@@ -318,15 +319,22 @@ def testCrsConversion(self):
crs.createFromProj(
'+proj=utm +zone=36 +south +a=600000 +b=70000 +towgs84=-143,-90,-294,0,0,0,0 +units=m +no_defs')
self.assertTrue(crs.isValid())
- self.assertEqual(GdalUtils.gdal_crs_string(crs),
- '+proj=utm +zone=36 +south +a=600000 +b=70000 +towgs84=-143,-90,-294,0,0,0,0 +units=m +no_defs')
- # check that newlines are stripped
- crs = QgsCoordinateReferenceSystem()
- crs.createFromProj(
- '+proj=utm +zone=36 +south\n +a=600000 +b=70000 \r\n +towgs84=-143,-90,-294,0,0,0,0 +units=m\n+no_defs')
- self.assertTrue(crs.isValid())
- self.assertEqual(GdalUtils.gdal_crs_string(crs),
- '+proj=utm +zone=36 +south +a=600000 +b=70000 +towgs84=-143,-90,-294,0,0,0,0 +units=m +no_defs')
+
+ if QgsProjUtils.projVersionMajor() >= 6:
+ # proj 6, WKT should be used
+ self.assertEqual(GdalUtils.gdal_crs_string(crs)[:40], 'BOUNDCRS[SOURCECRS[PROJCRS["unknown",BAS')
+
+ self.assertEqual(GdalUtils.gdal_crs_string(QgsCoordinateReferenceSystem('ESRI:102003')), 'ESRI:102003')
+ else:
+ self.assertEqual(GdalUtils.gdal_crs_string(crs),
+ '+proj=utm +zone=36 +south +a=600000 +b=70000 +towgs84=-143,-90,-294,0,0,0,0 +units=m +no_defs')
+ # check that newlines are stripped
+ crs = QgsCoordinateReferenceSystem()
+ crs.createFromProj(
+ '+proj=utm +zone=36 +south\n +a=600000 +b=70000 \r\n +towgs84=-143,-90,-294,0,0,0,0 +units=m\n+no_defs')
+ self.assertTrue(crs.isValid())
+ self.assertEqual(GdalUtils.gdal_crs_string(crs),
+ '+proj=utm +zone=36 +south +a=600000 +b=70000 +towgs84=-143,-90,-294,0,0,0,0 +units=m +no_defs')
if __name__ == '__main__':
diff --git a/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py b/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py
index ededbbbd4998..ab7cf7f0190a 100644
--- a/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py
+++ b/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py
@@ -47,7 +47,7 @@
from processing.algs.gdal.gdal2tiles import gdal2tiles
from processing.algs.gdal.gdalcalc import gdalcalc
from processing.algs.gdal.gdaltindex import gdaltindex
-from processing.algs.gdal.contour import contour
+from processing.algs.gdal.contour import contour, contour_polygon
from processing.algs.gdal.gdalinfo import gdalinfo
from processing.algs.gdal.hillshade import hillshade
from processing.algs.gdal.aspect import aspect
@@ -404,6 +404,25 @@ def testClipRasterByMask(self):
source + ' ' +
outdir + '/check.jpg'])
+ def testContourPolygon(self):
+ context = QgsProcessingContext()
+ feedback = QgsProcessingFeedback()
+ source = os.path.join(testDataPath, 'dem.tif')
+ alg = contour_polygon()
+ alg.initAlgorithm()
+ with tempfile.TemporaryDirectory() as outdir:
+ self.assertEqual(
+ alg.getConsoleCommands({'INPUT': source,
+ 'BAND': 1,
+ 'FIELD_NAME_MIN': 'min',
+ 'FIELD_NAME_MAX': 'max',
+ 'INTERVAL': 5,
+ 'OUTPUT': outdir + '/check.shp'}, context, feedback),
+ ['gdal_contour',
+ '-p -amax max -amin min -b 1 -i 5.0 -f "ESRI Shapefile" ' +
+ source + ' ' +
+ outdir + '/check.shp'])
+
def testContour(self):
context = QgsProcessingContext()
feedback = QgsProcessingFeedback()
diff --git a/python/plugins/processing/tests/GuiTest.py b/python/plugins/processing/tests/GuiTest.py
index 9a2ed9ca36f6..5626de01b320 100644
--- a/python/plugins/processing/tests/GuiTest.py
+++ b/python/plugins/processing/tests/GuiTest.py
@@ -33,7 +33,8 @@
QgsProcessingParameterRasterDestination,
QgsProcessingParameterRange,
QgsFeature,
- QgsVectorLayer,
+ QgsProcessingModelAlgorithm,
+ QgsUnitTypes,
QgsProject)
from qgis.analysis import QgsNativeAlgorithms
@@ -41,7 +42,6 @@
from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
from processing.modeler.ModelerParametersDialog import ModelerParametersDialog
from processing.gui.wrappers import *
-from processing.gui.DestinationSelectionPanel import DestinationSelectionPanel
start_app()
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
@@ -54,7 +54,7 @@ class AlgorithmDialogTest(unittest.TestCase):
def testCreation(self):
alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
a = AlgorithmDialog(alg)
- self.assertEqual(a.mainWidget().alg, alg)
+ self.assertEqual(a.mainWidget().algorithm(), alg)
class WrappersTest(unittest.TestCase):
@@ -355,134 +355,6 @@ def testNumber(self):
def testBand(self):
self.checkConstructWrapper(QgsProcessingParameterBand('test'), BandWidgetWrapper)
- def testFeatureSink(self):
- param = QgsProcessingParameterFeatureSink('test')
- alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
- panel = DestinationSelectionPanel(param, alg)
-
- panel.setValue(QgsProcessing.TEMPORARY_OUTPUT)
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), QgsProcessing.TEMPORARY_OUTPUT)
-
- panel.setValue('memory:')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), QgsProcessing.TEMPORARY_OUTPUT)
-
- panel.setValue('''ogr:dbname='/me/a.gpkg' table="d" (geom) sql=''')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), '''ogr:dbname='/me/a.gpkg' table="d" (geom) sql=''')
-
- panel.setValue('''postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table="stufff"."output" (the_geom) sql=''')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), '''postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table="stufff"."output" (the_geom) sql=''')
-
- panel.setValue('/home/me/test.shp')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), '/home/me/test.shp')
-
- ProcessingConfig.setSettingValue(ProcessingConfig.OUTPUT_FOLDER, testDataPath)
- panel.setValue('test.shp')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), os.path.join(testDataPath, 'test.shp'))
-
- def testVectorDestination(self):
- param = QgsProcessingParameterVectorDestination('test')
- alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
- panel = DestinationSelectionPanel(param, alg)
-
- panel.setValue(QgsProcessing.TEMPORARY_OUTPUT)
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.sink.staticValue(), QgsProcessing.TEMPORARY_OUTPUT)
-
- panel.setValue('''ogr:dbname='/me/a.gpkg' table="d" (geom) sql=''')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), '''ogr:dbname='/me/a.gpkg' table="d" (geom) sql=''')
-
- panel.setValue('''postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table="stufff"."output" (the_geom) sql=''')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), '''postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table="stufff"."output" (the_geom) sql=''')
-
- panel.setValue('/home/me/test.shp')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), '/home/me/test.shp')
-
- ProcessingConfig.setSettingValue(ProcessingConfig.OUTPUT_FOLDER, testDataPath)
- panel.setValue('test.shp')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), os.path.join(testDataPath, 'test.shp'))
-
- def testRasterDestination(self):
- param = QgsProcessingParameterRasterDestination('test')
- alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
- panel = DestinationSelectionPanel(param, alg)
-
- panel.setValue('/home/me/test.tif')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), '/home/me/test.tif')
-
- panel.setValue(QgsProcessing.TEMPORARY_OUTPUT)
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.sink.staticValue(), QgsProcessing.TEMPORARY_OUTPUT)
-
- ProcessingConfig.setSettingValue(ProcessingConfig.OUTPUT_FOLDER, testDataPath)
- panel.setValue('test.tif')
- v = panel.getValue()
- self.assertIsInstance(v, QgsProcessingOutputLayerDefinition)
- self.assertEqual(v.createOptions, {'fileEncoding': 'System'})
- self.assertEqual(v.sink.staticValue(), os.path.join(testDataPath, 'test.tif'))
-
- def testFolderDestination(self):
- param = QgsProcessingParameterFolderDestination('test')
- alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
- panel = DestinationSelectionPanel(param, alg)
-
- panel.setValue('/home/me/test.tif')
- v = panel.getValue()
- self.assertEqual(v, '/home/me/test.tif')
-
- ProcessingConfig.setSettingValue(ProcessingConfig.OUTPUT_FOLDER, testDataPath)
- panel.setValue('test.tif')
- v = panel.getValue()
- self.assertEqual(v, os.path.join(testDataPath, 'test.tif'))
-
- def testFileDestination(self):
- param = QgsProcessingParameterFileDestination('test')
- alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
- panel = DestinationSelectionPanel(param, alg)
-
- panel.setValue('/home/me/test.tif')
- v = panel.getValue()
- self.assertEqual(v, '/home/me/test.tif')
-
- ProcessingConfig.setSettingValue(ProcessingConfig.OUTPUT_FOLDER, testDataPath)
- panel.setValue('test.tif')
- v = panel.getValue()
- self.assertEqual(v, os.path.join(testDataPath, 'test.tif'))
-
if __name__ == '__main__':
unittest.main()
diff --git a/python/plugins/processing/tests/OtbAlgorithmsTest.py b/python/plugins/processing/tests/OtbAlgorithmsTest.py
index 0fbf2407984a..465f8802ee7f 100644
--- a/python/plugins/processing/tests/OtbAlgorithmsTest.py
+++ b/python/plugins/processing/tests/OtbAlgorithmsTest.py
@@ -36,7 +36,8 @@
QgsProcessingContext,
QgsProcessingUtils,
QgsProcessingFeedback,
- QgsProcessingParameterDefinition)
+ QgsProcessingParameterDefinition,
+ QgsProcessingModelAlgorithm)
from qgis.testing import start_app, unittest
from processing.core.ProcessingConfig import ProcessingConfig, Setting
from processing.gui.AlgorithmDialog import AlgorithmDialog
diff --git a/python/plugins/processing/tests/testdata/custom/multi_polys_with_empty_geoms.gpkg b/python/plugins/processing/tests/testdata/custom/multi_polys_with_empty_geoms.gpkg
new file mode 100644
index 000000000000..ab48db8e1d91
Binary files /dev/null and b/python/plugins/processing/tests/testdata/custom/multi_polys_with_empty_geoms.gpkg differ
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_line.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_line.gml
new file mode 100644
index 000000000000..44a84a4c234c
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_line.gml
@@ -0,0 +1,44 @@
+
+
+
+
+ -1 -3
+ 11 5
+
+
+
+
+
+ 6,2 9,2 9,3 11,5
+
+
+
+
+ -1,-1 1,-1
+
+
+
+
+ 2,0 2,2 3,2 3,3
+
+
+
+
+ 3,1 5,1
+
+
+
+
+ 7,-3 10,-3
+
+
+
+
+ 6,-3 10,1
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_line.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_line.xsd
new file mode 100644
index 000000000000..1f47f6781d46
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_line.xsd
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_null.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_null.gml
new file mode 100644
index 000000000000..4227e492428f
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_null.gml
@@ -0,0 +1,13 @@
+
+
+ missing
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_null.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_null.xsd
new file mode 100644
index 000000000000..114f8d7bdee0
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_null.xsd
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_point.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_point.gml
new file mode 100644
index 000000000000..eca047924b29
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_point.gml
@@ -0,0 +1,9 @@
+
+
+ missing
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_point.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_point.xsd
new file mode 100644
index 000000000000..024331792923
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_point.xsd
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_poly.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_poly.gml
new file mode 100644
index 000000000000..096e2f894c26
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_poly.gml
@@ -0,0 +1,9 @@
+
+
+ missing
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_lines_poly.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_poly.xsd
new file mode 100644
index 000000000000..bec3a032db0f
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_lines_poly.xsd
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_line.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_points_line.gml
new file mode 100644
index 000000000000..2e211fc06002
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_line.gml
@@ -0,0 +1,9 @@
+
+
+ missing
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_line.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_points_line.xsd
new file mode 100644
index 000000000000..13c60d78548f
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_line.xsd
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_null.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_points_null.gml
new file mode 100644
index 000000000000..4d7afbdb8509
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_null.gml
@@ -0,0 +1,9 @@
+
+
+ missing
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_null.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_points_null.xsd
new file mode 100644
index 000000000000..d9de16356449
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_null.xsd
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_point.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_points_point.gml
new file mode 100644
index 000000000000..6504d65999dc
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_point.gml
@@ -0,0 +1,77 @@
+
+
+
+
+ 0 -5
+ 8 3
+
+
+
+
+
+ 1,1
+ 1
+ 2
+
+
+
+
+ 3,3
+ 2
+ 1
+
+
+
+
+ 2,2
+ 3
+ 0
+
+
+
+
+ 5,2
+ 4
+ 2
+
+
+
+
+ 4,1
+ 5
+ 1
+
+
+
+
+ 0,-5
+ 6
+ 0
+
+
+
+
+ 8,-1
+ 7
+ 0
+
+
+
+
+ 7,-1
+ 8
+ 0
+
+
+
+
+ 0,-1
+ 9
+ 0
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_point.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_points_point.xsd
new file mode 100644
index 000000000000..cd4192236b0b
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_point.xsd
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_poly.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_points_poly.gml
new file mode 100644
index 000000000000..d2adb0a7182a
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_poly.gml
@@ -0,0 +1,9 @@
+
+
+ missing
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_points_poly.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_points_poly.xsd
new file mode 100644
index 000000000000..9a33ecc06a3e
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_points_poly.xsd
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_line.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_line.gml
new file mode 100644
index 000000000000..7d20eb385ee2
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_line.gml
@@ -0,0 +1,9 @@
+
+
+ missing
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_line.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_line.xsd
new file mode 100644
index 000000000000..fe878923e6ec
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_line.xsd
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_null.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_null.gml
new file mode 100644
index 000000000000..5b2a2382554f
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_null.gml
@@ -0,0 +1,16 @@
+
+
+ missing
+
+
+
+
+ 120
+ -100291.43213
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_null.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_null.xsd
new file mode 100644
index 000000000000..155f78893f83
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_null.xsd
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_point.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_point.gml
new file mode 100644
index 000000000000..61dfcb5d2a16
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_point.gml
@@ -0,0 +1,9 @@
+
+
+ missing
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_point.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_point.xsd
new file mode 100644
index 000000000000..e2a350424903
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_point.xsd
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_poly.gml b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_poly.gml
new file mode 100644
index 000000000000..8408a8062436
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_poly.gml
@@ -0,0 +1,54 @@
+
+
+
+
+ -1 -3
+ 10 6
+
+
+
+
+
+ -1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1
+ aaaaa
+ 33
+ 44.123456
+
+
+
+
+ 5,5 6,4 4,4 5,5
+ Aaaaa
+ -33
+ 0
+
+
+
+
+ 2,5 2,6 3,6 3,5 2,5
+ bbaaa
+
+ 0.123
+
+
+
+
+ 6,1 10,1 10,-3 6,-3 6,1 7,0 7,-2 9,-2 9,0 7,0
+ ASDF
+ 0
+
+
+
+
+
+ 3,2 6,1 6,-3 2,-1 2,2 3,2
+ elim
+ 2
+ 3.33
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/filter_geom_polys_poly.xsd b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_poly.xsd
new file mode 100644
index 000000000000..c0877a87b891
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/filter_geom_polys_poly.xsd
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_non_empty.gml b/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_non_empty.gml
new file mode 100644
index 000000000000..58280b3373b2
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_non_empty.gml
@@ -0,0 +1,54 @@
+
+
+
+
+ 1152380.710241179 991995.6506176119 0
+ 1155226.440646131 994672.9721792523 0
+
+
+
+
+
+ 1153212.63507019,991995.873315548,0 1153206.63873892,991995.650617612,0 1153206.28716221,992005.149060725,0 1153209.30632804,992005.260950644,0 1153212.28369425,992005.371359061,0 1153212.63507019,991995.873315548,0
+
+
+
+
+ 1155221.09556005,994660.22300975,0 1155225.70703642,994665.492121154,0 1155226.44064613,994664.81215781,0 1155221.82916974,994659.543046403,0 1155221.09556005,994660.22300975,0
+
+
+
+
+ 1155218.1610862,994662.942992519,0 1155216.84296042,994661.436981518,0 1155213.8673808,994664.195071552,0 1155215.22680442,994665.662838767,0 1155215.66683585,994665.25490937,0 1155218.1610862,994662.942992519,0
+
+
+
+
+ 1155215.66683187,994665.254893728,0 1155215.22680937,994665.662853824,0 1155213.02579582,994667.703152789,0 1155215.0023435,994669.961178547,0 1155217.6433796,994667.512919493,0 1155215.66683187,994665.254893728,0
+
+
+
+
+ 1155222.77277049,994668.212047819,0 1155218.16109298,994662.943021336,0 1155215.66683187,994665.254893728,0 1155217.6433796,994667.512919493,0 1155215.0023435,994669.961178547,0 1155217.63747314,994672.972179252,0 1155222.77277049,994668.212047819,0
+
+
+
+
+ 1155216.84296042,994661.436981518,0 1155218.1610862,994662.942992519,0 1155218.30792385,994662.806888921,0 1155216.94851719,994661.339140047,0 1155216.84296042,994661.436981518,0
+
+
+
+
+ 1155218.1610862,994662.942992519,0 1155222.77280779,994668.212065019,0 1155223.08625704,994667.921518022,0 1155225.47071869,994665.711277663,0 1155225.70703642,994665.492121158,0 1155221.09556006,994660.223009752,0 1155218.30792385,994662.806888921,0 1155218.1610862,994662.942992519,0
+
+
+
+
+ 1152391.40704313,992267.107529733,0 1152383.73923598,992267.815575151,0 1152380.71024118,992271.79589533,0 1152385.61043835,992275.260519417,0 1152391.40704313,992267.107529733,0
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_non_empty.xsd b/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_non_empty.xsd
new file mode 100644
index 000000000000..1c40b0d72ed1
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_non_empty.xsd
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_one_empty.gpkg b/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_one_empty.gpkg
new file mode 100644
index 000000000000..afe6ab8c578c
Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/multi_polys_non_null_one_empty.gpkg differ
diff --git a/python/plugins/processing/tests/testdata/expected/randompointsonlines.gml b/python/plugins/processing/tests/testdata/expected/randompointsonlines.gml
new file mode 100644
index 000000000000..d9ef27fca75a
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/randompointsonlines.gml
@@ -0,0 +1,194 @@
+
+
+
+
+ -0.7437511044553879 -1
+ 5.456604910231689 4.119686133816111
+
+
+
+
+
+ 0.994369616460531,-1.0
+ 0
+
+
+
+
+ 0.865114722736331,-1.0
+ 1
+
+
+
+
+ -0.743751104455388,-1.0
+ 2
+
+
+
+
+ 0.998081030930547,-1.0
+ 3
+
+
+
+
+ -0.527822047403662,-1.0
+ 4
+
+
+
+
+ -0.206838547674781,-1.0
+ 5
+
+
+
+
+ -0.224178519478878,-1.0
+ 6
+
+
+
+
+ 0.339492080894094,-1.0
+ 7
+
+
+
+
+ 0.87107814540936,-1.0
+ 8
+
+
+
+
+ 0.692621836689681,-1.0
+ 9
+
+
+
+
+ 4.06982464840608,1.0
+ 10
+
+
+
+
+ 4.79132461080717,1.0
+ 11
+
+
+
+
+ 4.51438540565048,1.0
+ 12
+
+
+
+
+ 3.78400298357133,1.0
+ 13
+
+
+
+
+ 4.8250159892403,1.0
+ 14
+
+
+
+
+ 5.00502180455731,1.29377556660287
+ 15
+
+
+
+
+ 4.5613480038941,1.0
+ 16
+
+
+
+
+ 4.47082956972335,1.0
+ 17
+
+
+
+
+ 5.00355294633537,1.2078473606193
+ 18
+
+
+
+
+ 5.01293482227269,1.75668710295238
+ 19
+
+
+
+
+ 5.45660491023169,4.11968613381611
+ 20
+
+
+
+
+ 3.78645266020438,2.98379593968487
+ 21
+
+
+
+
+ 2.0,0.844225610300582
+ 22
+
+
+
+
+ 3.65776768699273,4.06779659853037
+ 23
+
+
+
+
+ 4.35274779800432,2.9721280020792
+ 24
+
+
+
+
+ 4.02647088988085,2.97885060722278
+ 25
+
+
+
+
+ 4.03062245091484,2.97876506851358
+ 26
+
+
+
+
+ 2.48398460138168,2.0
+ 27
+
+
+
+
+ 2.0,0.538945986405747
+ 28
+
+
+
+
+ 5.04337850348996,4.10776614131395
+ 29
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/randompointsonlines.xsd b/python/plugins/processing/tests/testdata/expected/randompointsonlines.xsd
new file mode 100644
index 000000000000..86546b28f784
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/randompointsonlines.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/randompointsonlines_min1.gml b/python/plugins/processing/tests/testdata/expected/randompointsonlines_min1.gml
new file mode 100644
index 000000000000..1dd1557ce0a0
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/randompointsonlines_min1.gml
@@ -0,0 +1,80 @@
+
+
+
+
+ -0.7437511044553879 -1
+ 5.41449432037405 4.102675375546412
+
+
+
+
+
+ 0.994369616460531,-1.0
+ 0
+
+
+
+
+ -0.743751104455388,-1.0
+ 1
+
+
+
+
+ 5.01381944749508,1.80843767846222
+ 2
+
+
+
+
+ 4.21755080717389,1.0
+ 3
+
+
+
+
+ 3.03985013553821,1.0
+ 4
+
+
+
+
+ 2.0,0.455023484569141
+ 5
+
+
+
+
+ 3.22434857680558,2.99537752486052
+ 6
+
+
+
+
+ 2.71714772226361,2.0
+ 7
+
+
+
+
+ 5.41449432037405,2.95025179063198
+ 8
+
+
+
+
+ 4.86689862354882,4.10267537554641
+ 9
+
+
+
+
+ 3.08606511997889,4.05130517832805
+ 10
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/randompointsonlines_min1.xsd b/python/plugins/processing/tests/testdata/expected/randompointsonlines_min1.xsd
new file mode 100644
index 000000000000..6019529d624f
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/randompointsonlines_min1.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/results_remove_null_geometries.gpkg b/python/plugins/processing/tests/testdata/expected/results_remove_null_geometries.gpkg
new file mode 100644
index 000000000000..0ed28f607b05
Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/results_remove_null_geometries.gpkg differ
diff --git a/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.dbf b/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.dbf
index f4ec83fd660d..a27e59b1c18a 100644
Binary files a/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.dbf and b/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.dbf differ
diff --git a/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.prj b/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.prj
index a30c00a55de1..f45cbadf0074 100644
--- a/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.prj
+++ b/python/plugins/processing/tests/testdata/expected/split_lines_with_same2.prj
@@ -1 +1 @@
-GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
\ No newline at end of file
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]
\ No newline at end of file
diff --git a/python/plugins/processing/tests/testdata/otb_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/otb_algorithm_tests.yaml
index c3cfa0b8d7b4..aac405e0d4c1 100644
--- a/python/plugins/processing/tests/testdata/otb_algorithm_tests.yaml
+++ b/python/plugins/processing/tests/testdata/otb_algorithm_tests.yaml
@@ -23,7 +23,7 @@ tests:
name: raster.tif
type: file
type: gaussian
- type.gaussian.radius: 4.0
+ type.gaussian.stdev: 4.0
results:
out:
hash: b3fbccd6f41052317a435567a2633dae1d9b66772a4d8a3323d9b1c5
diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml
index 2b10cbbd8117..5ba1b11de351 100755
--- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml
+++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml
@@ -184,7 +184,7 @@ tests:
type: rasterhash
- algorithm: native:removenullgeometries
- name: Remove null geometries
+ name: Remove null geometries (do not remove empty)
params:
INPUT:
name: polys.gml
@@ -194,6 +194,75 @@ tests:
name: expected/remove_null_polys.gml
type: vector
+ - algorithm: native:removenullgeometries
+ name: Remove null geometries (remove empty)
+ params:
+ INPUT:
+ name: polys.gml
+ type: vector
+ REMOVE_EMPTY: true
+ results:
+ OUTPUT:
+ name: expected/remove_null_polys.gml
+ type: vector
+
+ - algorithm: native:removenullgeometries
+ name: Remove null geometries (do not remove empty, but there is empty data)
+ params:
+ INPUT:
+ name: custom/multi_polys_with_empty_geoms.gpkg|layername=multi_polys_with_empty_geoms
+ type: vector
+ results:
+ OUTPUT:
+ name: ogr:dbname='expected/multi_polys_non_null_one_empty.gpkg' table="output" (geom) sql=
+ uri: expected/multi_polys_non_null_one_empty.gpkg|layername=multi_polys_non_null_one_empty
+ type: vector
+
+ - algorithm: native:removenullgeometries
+ name: Remove null geometries (remove empty, and there is empty data)
+ params:
+ INPUT:
+ name: custom/multi_polys_with_empty_geoms.gpkg|layername=multi_polys_with_empty_geoms
+ type: vector
+ REMOVE_EMPTY: true
+ results:
+ OUTPUT:
+ name: expected/multi_polys_non_null_non_empty.gml
+ type: vector
+
+ - algorithm: native:removenullgeometries
+ name: Remove null geometries, 2 outputs (do not remove empty, but there is empty data)
+ params:
+ INPUT:
+ name: custom/multi_polys_with_empty_geoms.gpkg|layername=multi_polys_with_empty_geoms
+ type: vector
+ results:
+ OUTPUT:
+ name: ogr:dbname='expected/results_remove_null_geometries.gpkg' table="output" (geom) sql=
+ uri: expected/results_remove_null_geometries.gpkg|layername=do_not_remove_empty_non_null
+ type: vector
+ NULL_OUTPUT:
+ name: ogr:dbname='expected/results_remove_null_geometries.gpkg' table="output" (geom) sql=
+ uri: expected/results_remove_null_geometries.gpkg|layername=do_not_remove_empty_null
+ type: vector
+
+ - algorithm: native:removenullgeometries
+ name: Remove null geometries, 2 outputs (remove empty, and there is empty data)
+ params:
+ INPUT:
+ name: custom/multi_polys_with_empty_geoms.gpkg|layername=multi_polys_with_empty_geoms
+ type: vector
+ REMOVE_EMPTY: true
+ results:
+ OUTPUT:
+ name: ogr:dbname='expected/results_remove_null_geometries.gpkg' table="output" (geom) sql=
+ uri: expected/results_remove_null_geometries.gpkg|layername=remove_empty_non_null
+ type: vector
+ NULL_OUTPUT:
+ name: ogr:dbname='expected/results_remove_null_geometries.gpkg' table="output" (geom) sql=
+ uri: expected/results_remove_null_geometries.gpkg|layername=remove_empty_null
+ type: vector
+
- algorithm: native:extractbyexpression
name: Extract by Expression
params:
diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml
index 60e75cf7d17c..713687fdce1f 100755
--- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml
+++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml
@@ -588,6 +588,38 @@ tests:
type: vector
name: randompointsinextent.gml
+ - algorithm: native:randompointsonlines
+ name: Random points on lines, seed 1, multilines - one empty geom
+ params:
+ INCLUDE_LINE_ATTRIBUTES: false
+ INPUT:
+ name: multilines.gml|layername=multilines
+ type: vector
+ MAX_TRIES_PER_POINT: 10
+ MIN_DISTANCE: 0.0
+ POINTS_NUMBER: 10
+ SEED: 1
+ results:
+ OUTPUT:
+ name: expected/randompointsonlines.gml
+ type: vector
+
+ - algorithm: native:randompointsonlines
+ name: Random points on lines, min distance 1, seed 1, attributes, multilines - one empty geom
+ params:
+ INCLUDE_LINE_ATTRIBUTES: true
+ INPUT:
+ name: multilines.gml|layername=multilines
+ type: vector
+ MAX_TRIES_PER_POINT: 10
+ MIN_DISTANCE: 1.0
+ POINTS_NUMBER: 10
+ SEED: 1
+ results:
+ OUTPUT:
+ name: expected/randompointsonlines_min1.gml
+ type: vector
+
- algorithm: native:randomextract
name: Random extract by number
params:
diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests4.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests4.yaml
index 0ca21940ad15..15ee24781b24 100755
--- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests4.yaml
+++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests4.yaml
@@ -2365,5 +2365,65 @@ tests:
name: expected/renamed_field.gml
type: vector
+ - algorithm: native:filterbygeometry
+ name: Filter by geometry type polygon input
+ params:
+ INPUT:
+ name: polys.gml|layername=polys2
+ type: vector
+ results:
+ LINES:
+ name: expected/filter_geom_polys_line.gml
+ type: vector
+ NO_GEOMETRY:
+ name: expected/filter_geom_polys_null.gml
+ type: vector
+ POINTS:
+ name: expected/filter_geom_polys_point.gml
+ type: vector
+ POLYGONS:
+ name: expected/filter_geom_polys_poly.gml
+ type: vector
+
+ - algorithm: native:filterbygeometry
+ name: Filter by geometry type point input
+ params:
+ INPUT:
+ name: points.gml|layername=points
+ type: vector
+ results:
+ LINES:
+ name: expected/filter_geom_points_line.gml
+ type: vector
+ NO_GEOMETRY:
+ name: expected/filter_geom_points_null.gml
+ type: vector
+ POINTS:
+ name: expected/filter_geom_points_point.gml
+ type: vector
+ POLYGONS:
+ name: expected/filter_geom_points_poly.gml
+ type: vector
+
+ - algorithm: native:filterbygeometry
+ name: Filter by geometry type line input
+ params:
+ INPUT:
+ name: lines.gml|layername=lines
+ type: vector
+ results:
+ LINES:
+ name: expected/filter_geom_lines_line.gml
+ type: vector
+ NO_GEOMETRY:
+ name: expected/filter_geom_lines_null.gml
+ type: vector
+ POINTS:
+ name: expected/filter_geom_lines_point.gml
+ type: vector
+ POLYGONS:
+ name: expected/filter_geom_lines_poly.gml
+ type: vector
+
# See ../README.md for a description of the file format
diff --git a/python/plugins/processing/tools/postgis.py b/python/plugins/processing/tools/postgis.py
deleted file mode 100644
index 9fc69c34be08..000000000000
--- a/python/plugins/processing/tools/postgis.py
+++ /dev/null
@@ -1,921 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- postgis.py
- ---------------------
- Date : November 2012
- Copyright : (C) 2012 by Martin Dobias
- Email : volayaf at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-__author__ = 'Martin Dobias'
-__date__ = 'November 2012'
-__copyright__ = '(C) 2012, Martin Dobias'
-
-import psycopg2
-import psycopg2.extensions # For isolation levels
-import re
-import os
-
-from qgis.core import (QgsProcessingException,
- QgsDataSourceUri,
- QgsCredentials,
- QgsSettings)
-
-from qgis.PyQt.QtCore import QCoreApplication
-
-
-# Use unicode!
-psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
-
-
-class DbError(Exception):
-
- def __init__(self, message, query=None):
- self.message = str(message)
- self.query = (str(query) if query is not None else None)
-
- def __str__(self):
- return 'MESSAGE: %s\nQUERY: %s' % (self.message, self.query)
-
-
-def uri_from_name(conn_name):
- settings = QgsSettings()
- settings.beginGroup(u"/PostgreSQL/connections/%s" % conn_name)
-
- if not settings.contains("database"): # non-existent entry?
- raise QgsProcessingException(QCoreApplication.translate("PostGIS", 'There is no defined database connection "{0}".').format(conn_name))
-
- uri = QgsDataSourceUri()
-
- settingsList = ["service", "host", "port", "database", "username", "password", "authcfg"]
- service, host, port, database, username, password, authcfg = [settings.value(x, "", type=str) for x in settingsList]
-
- useEstimatedMetadata = settings.value("estimatedMetadata", False, type=bool)
- try:
- sslmode = settings.value("sslmode", QgsDataSourceUri.SslPrefer, type=int)
- except TypeError:
- sslmode = QgsDataSourceUri.SslPrefer
-
- settings.endGroup()
-
- if hasattr(authcfg, 'isNull') and authcfg.isNull():
- authcfg = ''
-
- if service:
- uri.setConnection(service, database, username, password, sslmode, authcfg)
- else:
- uri.setConnection(host, port, database, username, password, sslmode, authcfg)
-
- uri.setUseEstimatedMetadata(useEstimatedMetadata)
-
- return uri
-
-
-class TableAttribute(object):
-
- def __init__(self, row):
- (self.num,
- self.name,
- self.data_type,
- self.char_max_len,
- self.modifier,
- self.notnull,
- self.hasdefault,
- self.default,
- ) = row
-
-
-class TableConstraint(object):
-
- """Class that represents a constraint of a table (relation).
- """
-
- (TypeCheck, TypeForeignKey, TypePrimaryKey, TypeUnique) = list(range(4))
- types = {
- 'c': TypeCheck,
- 'f': TypeForeignKey,
- 'p': TypePrimaryKey,
- 'u': TypeUnique,
- }
-
- on_action = {
- 'a': 'NO ACTION',
- 'r': 'RESTRICT',
- 'c': 'CASCADE',
- 'n': 'SET NULL',
- 'd': 'SET DEFAULT',
- }
- match_types = {'u': 'UNSPECIFIED', 'f': 'FULL', 'p': 'PARTIAL'}
-
- def __init__(self, row):
- (self.name, con_type, self.is_deferable, self.is_deferred, keys) = row[:5]
- self.keys = list(map(int, keys.split(' ')))
- self.con_type = TableConstraint.types[con_type] # Convert to enum
- if self.con_type == TableConstraint.TypeCheck:
- self.check_src = row[5]
- elif self.con_type == TableConstraint.TypeForeignKey:
- self.foreign_table = row[6]
- self.foreign_on_update = TableConstraint.on_action[row[7]]
- self.foreign_on_delete = TableConstraint.on_action[row[8]]
- self.foreign_match_type = TableConstraint.match_types[row[9]]
- self.foreign_keys = row[10]
-
-
-class TableIndex(object):
-
- def __init__(self, row):
- (self.name, columns) = row
- self.columns = list(map(int, columns.split(' ')))
-
-
-class TableField(object):
-
- def __init__(self, name, data_type, is_null=None, default=None,
- modifier=None):
- (self.name, self.data_type, self.is_null, self.default,
- self.modifier) = (name, data_type, is_null, default, modifier)
-
- def is_null_txt(self):
- if self.is_null:
- return 'NULL'
- else:
- return 'NOT NULL'
-
- def field_def(self):
- """Return field definition as used for CREATE TABLE or
- ALTER TABLE command.
- """
-
- data_type = (self.data_type if not self.modifier or self.modifier
- < 0 else '%s(%d)' % (self.data_type, self.modifier))
- txt = '%s %s %s' % (self._quote(self.name), data_type,
- self.is_null_txt())
- if self.default and len(self.default) > 0:
- txt += ' DEFAULT %s' % self.default
- return txt
-
- def _quote(self, ident):
- if re.match(r"^\w+$", ident) is not None:
- return ident
- else:
- return '"%s"' % ident.replace('"', '""')
-
-
-class GeoDB(object):
-
- @classmethod
- def from_name(cls, conn_name):
- uri = uri_from_name(conn_name)
- return cls(uri=uri)
-
- def __init__(self, host=None, port=None, dbname=None, user=None,
- passwd=None, service=None, uri=None):
- # Regular expression for identifiers without need to quote them
- self.re_ident_ok = re.compile(r"^\w+$")
- port = str(port)
-
- if uri:
- self.uri = uri
- else:
- self.uri = QgsDataSourceUri()
- if service:
- self.uri.setConnection(service, dbname, user, passwd)
- else:
- self.uri.setConnection(host, port, dbname, user, passwd)
-
- conninfo = self.uri.connectionInfo(False)
- err = None
- for i in range(4):
- expandedConnInfo = self.uri.connectionInfo(True)
- try:
- self.con = psycopg2.connect(expandedConnInfo)
- if err is not None:
- QgsCredentials.instance().put(conninfo,
- self.uri.username(),
- self.uri.password())
- break
- except psycopg2.OperationalError as e:
- if i == 3:
- raise QgsProcessingException(str(e))
-
- err = str(e)
- user = self.uri.username()
- password = self.uri.password()
- (ok, user, password) = QgsCredentials.instance().get(conninfo,
- user,
- password,
- err)
- if not ok:
- raise QgsProcessingException(QCoreApplication.translate("PostGIS", 'Action canceled by user'))
- if user:
- self.uri.setUsername(user)
- if password:
- self.uri.setPassword(password)
- finally:
- # remove certs (if any) of the expanded connectionInfo
- expandedUri = QgsDataSourceUri(expandedConnInfo)
-
- sslCertFile = expandedUri.param("sslcert")
- if sslCertFile:
- sslCertFile = sslCertFile.replace("'", "")
- try:
- os.remove(sslCertFile)
- except OSError:
- pass
-
- sslKeyFile = expandedUri.param("sslkey")
- if sslKeyFile:
- sslKeyFile = sslKeyFile.replace("'", "")
- try:
- os.remove(sslKeyFile)
- except OSError:
- pass
-
- sslCAFile = expandedUri.param("sslrootcert")
- if sslCAFile:
- sslCAFile = sslCAFile.replace("'", "")
- try:
- os.remove(sslCAFile)
- except OSError:
- pass
-
- self.has_postgis = self.check_postgis()
-
- def get_info(self):
- c = self.con.cursor()
- self._exec_sql(c, 'SELECT version()')
- return c.fetchone()[0]
-
- def check_postgis(self):
- """Check whether postgis_version is present in catalog.
- """
-
- c = self.con.cursor()
- self._exec_sql(c,
- "SELECT COUNT(*) FROM pg_proc WHERE proname = 'postgis_version'")
- return c.fetchone()[0] > 0
-
- def get_postgis_info(self):
- """Returns tuple about PostGIS support:
- - lib version
- - installed scripts version
- - released scripts version
- - geos version
- - proj version
- - whether uses stats
- """
-
- c = self.con.cursor()
- self._exec_sql(c,
- 'SELECT postgis_lib_version(), postgis_scripts_installed(), \
- postgis_scripts_released(), postgis_geos_version(), \
- postgis_proj_version(), postgis_uses_stats()')
- return c.fetchone()
-
- def list_schemas(self):
- """Get list of schemas in tuples: (oid, name, owner, perms).
- """
-
- c = self.con.cursor()
- sql = "SELECT oid, nspname, pg_get_userbyid(nspowner), nspacl \
- FROM pg_namespace \
- WHERE nspname !~ '^pg_' AND nspname != 'information_schema'"
- self._exec_sql(c, sql)
- return c.fetchall()
-
- def list_geotables(self, schema=None):
- """Get list of tables with schemas, whether user has privileges,
- whether table has geometry column(s) etc.
-
- Geometry_columns:
- - f_table_schema
- - f_table_name
- - f_geometry_column
- - coord_dimension
- - srid
- - type
- """
-
- c = self.con.cursor()
-
- if schema:
- schema_where = " AND nspname = '%s' " % self._quote_unicode(schema)
- else:
- schema_where = \
- " AND (nspname != 'information_schema' AND nspname !~ 'pg_') "
-
- # LEFT OUTER JOIN: like LEFT JOIN but if there are more matches,
- # for join, all are used (not only one)
-
- # First find out whether PostGIS is enabled
- if not self.has_postgis:
- # Get all tables and views
- sql = """SELECT pg_class.relname, pg_namespace.nspname,
- pg_class.relkind, pg_get_userbyid(relowner),
- reltuples, relpages, NULL, NULL, NULL, NULL
- FROM pg_class
- JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
- WHERE pg_class.relkind IN ('v', 'r', 'm', 'p')""" \
- + schema_where + 'ORDER BY nspname, relname'
- else:
- # Discovery of all tables and whether they contain a
- # geometry column
- sql = """SELECT pg_class.relname, pg_namespace.nspname,
- pg_class.relkind, pg_get_userbyid(relowner),
- reltuples, relpages, pg_attribute.attname,
- pg_attribute.atttypid::regtype, NULL, NULL
- FROM pg_class
- JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
- LEFT OUTER JOIN pg_attribute ON
- pg_attribute.attrelid = pg_class.oid AND
- (pg_attribute.atttypid = 'geometry'::regtype
- OR pg_attribute.atttypid IN
- (SELECT oid FROM pg_type
- WHERE typbasetype='geometry'::regtype))
- WHERE pg_class.relkind IN ('v', 'r', 'm', 'p') """ \
- + schema_where + 'ORDER BY nspname, relname, attname'
-
- self._exec_sql(c, sql)
- items = c.fetchall()
-
- # Get geometry info from geometry_columns if exists
- if self.has_postgis:
- sql = """SELECT relname, nspname, relkind,
- pg_get_userbyid(relowner), reltuples, relpages,
- geometry_columns.f_geometry_column,
- geometry_columns.type,
- geometry_columns.coord_dimension,
- geometry_columns.srid
- FROM pg_class
- JOIN pg_namespace ON relnamespace=pg_namespace.oid
- LEFT OUTER JOIN geometry_columns ON
- relname=f_table_name AND nspname=f_table_schema
- WHERE relkind IN ('r','v','m','p') """ \
- + schema_where + 'ORDER BY nspname, relname, \
- f_geometry_column'
- self._exec_sql(c, sql)
-
- # Merge geometry info to "items"
- for (i, geo_item) in enumerate(c.fetchall()):
- if geo_item[7]:
- items[i] = geo_item
-
- return items
-
- def get_table_rows(self, table, schema=None):
- c = self.con.cursor()
- self._exec_sql(c, 'SELECT COUNT(*) FROM %s' % self._table_name(schema,
- table))
- return c.fetchone()[0]
-
- def get_table_fields(self, table, schema=None):
- """Return list of columns in table"""
-
- c = self.con.cursor()
- schema_where = (" AND nspname='%s' "
- % self._quote_unicode(schema) if schema is not None else ''
- )
-
- version_number = int(self.get_info().split(' ')[1].split('.')[0])
- ad_col_name = 'adsrc' if version_number < 12 else 'adbin'
-
- sql = """SELECT a.attnum AS ordinal_position,
- a.attname AS column_name,
- t.typname AS data_type,
- a.attlen AS char_max_len,
- a.atttypmod AS modifier,
- a.attnotnull AS notnull,
- a.atthasdef AS hasdefault,
- adef.%s AS default_value
- FROM pg_class c
- JOIN pg_attribute a ON a.attrelid = c.oid
- JOIN pg_type t ON a.atttypid = t.oid
- JOIN pg_namespace nsp ON c.relnamespace = nsp.oid
- LEFT JOIN pg_attrdef adef ON adef.adrelid = a.attrelid
- AND adef.adnum = a.attnum
- WHERE
- c.relname = '%s' %s AND
- a.attnum > 0
- ORDER BY a.attnum""" \
- % (ad_col_name, self._quote_unicode(table), schema_where)
-
- self._exec_sql(c, sql)
- attrs = []
- for row in c.fetchall():
- attrs.append(TableAttribute(row))
- return attrs
-
- def get_table_indexes(self, table, schema=None):
- """Get info about table's indexes. ignore primary key and unique
- index, they get listed in constraints.
- """
-
- c = self.con.cursor()
-
- schema_where = (" AND nspname='%s' "
- % self._quote_unicode(schema) if schema is not None else ''
- )
- sql = """SELECT relname, indkey
- FROM pg_class, pg_index
- WHERE pg_class.oid = pg_index.indexrelid AND pg_class.oid IN (
- SELECT indexrelid
- FROM pg_index, pg_class
- JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid
- WHERE pg_class.relname='%s' %s AND
- pg_class.oid=pg_index.indrelid
- AND indisunique != 't' AND indisprimary != 't' )""" \
- % (self._quote_unicode(table), schema_where)
- self._exec_sql(c, sql)
- indexes = []
- for row in c.fetchall():
- indexes.append(TableIndex(row))
- return indexes
-
- def get_table_constraints(self, table, schema=None):
- c = self.con.cursor()
-
- schema_where = (" AND nspname='%s' "
- % self._quote_unicode(schema) if schema is not None else ''
- )
-
- version_number = int(self.get_info().split(' ')[1].split('.')[0])
- con_col_name = 'consrc' if version_number < 12 else 'conbin'
-
- sql = """SELECT c.conname, c.contype, c.condeferrable, c.condeferred,
- array_to_string(c.conkey, ' '), c.%s, t2.relname,
- c.confupdtype, c.confdeltype, c.confmatchtype,
- array_to_string(c.confkey, ' ')
- FROM pg_constraint c
- LEFT JOIN pg_class t ON c.conrelid = t.oid
- LEFT JOIN pg_class t2 ON c.confrelid = t2.oid
- JOIN pg_namespace nsp ON t.relnamespace = nsp.oid
- WHERE t.relname = '%s' %s """ \
- % (con_col_name, self._quote_unicode(table), schema_where)
-
- self._exec_sql(c, sql)
-
- constrs = []
- for row in c.fetchall():
- constrs.append(TableConstraint(row))
- return constrs
-
- def get_view_definition(self, view, schema=None):
- """Returns definition of the view."""
-
- schema_where = (" AND nspname='%s' "
- % self._quote_unicode(schema) if schema is not None else ''
- )
- sql = """SELECT pg_get_viewdef(c.oid)
- FROM pg_class c
- JOIN pg_namespace nsp ON c.relnamespace = nsp.oid
- WHERE relname='%s' %s AND relkind IN ('v','m')""" \
- % (self._quote_unicode(view), schema_where)
- c = self.con.cursor()
- self._exec_sql(c, sql)
- return c.fetchone()[0]
-
- def add_geometry_column(self, table, geom_type, schema=None,
- geom_column='the_geom', srid=-1, dim=2):
- # Use schema if explicitly specified
- if schema:
- schema_part = "'%s', " % self._quote_unicode(schema)
- else:
- schema_part = ''
- sql = "SELECT AddGeometryColumn(%s'%s', '%s', %d, '%s', %d)" % (
- schema_part,
- self._quote_unicode(table),
- self._quote_unicode(geom_column),
- srid,
- self._quote_unicode(geom_type),
- dim,
- )
- self._exec_sql_and_commit(sql)
-
- def delete_geometry_column(self, table, geom_column, schema=None):
- """Use PostGIS function to delete geometry column correctly."""
-
- if schema:
- schema_part = "'%s', " % self._quote_unicode(schema)
- else:
- schema_part = ''
- sql = "SELECT DropGeometryColumn(%s'%s', '%s')" % (schema_part,
- self._quote_unicode(table), self._quote_unicode(geom_column))
- self._exec_sql_and_commit(sql)
-
- def delete_geometry_table(self, table, schema=None):
- """Delete table with one or more geometries using PostGIS function."""
-
- if schema:
- schema_part = "'%s', " % self._quote_unicode(schema)
- else:
- schema_part = ''
- sql = "SELECT DropGeometryTable(%s'%s')" % (schema_part,
- self._quote_unicode(table))
- self._exec_sql_and_commit(sql)
-
- def create_table(self, table, fields, pkey=None, schema=None):
- """Create ordinary table.
-
- 'fields' is array containing instances of TableField
- 'pkey' contains name of column to be used as primary key
- """
-
- if len(fields) == 0:
- return False
-
- table_name = self._table_name(schema, table)
-
- sql = 'CREATE TABLE %s (%s' % (table_name, fields[0].field_def())
- for field in fields[1:]:
- sql += ', %s' % field.field_def()
- if pkey:
- sql += ', PRIMARY KEY (%s)' % self._quote(pkey)
- sql += ')'
- self._exec_sql_and_commit(sql)
- return True
-
- def delete_table(self, table, schema=None):
- """Delete table from the database."""
-
- table_name = self._table_name(schema, table)
- sql = 'DROP TABLE %s' % table_name
- self._exec_sql_and_commit(sql)
-
- def empty_table(self, table, schema=None):
- """Delete all rows from table."""
-
- table_name = self._table_name(schema, table)
- sql = 'DELETE FROM %s' % table_name
- self._exec_sql_and_commit(sql)
-
- def rename_table(self, table, new_table, schema=None):
- """Rename a table in database."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s RENAME TO %s' % (table_name,
- self._quote(new_table))
- self._exec_sql_and_commit(sql)
-
- # Update geometry_columns if PostGIS is enabled
- if self.has_postgis:
- sql = "UPDATE geometry_columns SET f_table_name='%s' \
- WHERE f_table_name='%s'" \
- % (self._quote_unicode(new_table), self._quote_unicode(table))
- if schema is not None:
- sql += " AND f_table_schema='%s'" % self._quote_unicode(schema)
- self._exec_sql_and_commit(sql)
-
- def create_view(self, name, query, schema=None):
- view_name = self._table_name(schema, name)
- sql = 'CREATE VIEW %s AS %s' % (view_name, query)
- self._exec_sql_and_commit(sql)
-
- def delete_view(self, name, schema=None):
- view_name = self._table_name(schema, name)
- sql = 'DROP VIEW %s' % view_name
- self._exec_sql_and_commit(sql)
-
- def rename_view(self, name, new_name, schema=None):
- """Rename view in database."""
-
- self.rename_table(name, new_name, schema)
-
- def create_schema(self, schema):
- """Create a new empty schema in database."""
-
- sql = 'CREATE SCHEMA %s' % self._quote(schema)
- self._exec_sql_and_commit(sql)
-
- def delete_schema(self, schema):
- """Drop (empty) schema from database."""
-
- sql = 'DROP SCHEMA %s' % self._quote(schema)
- self._exec_sql_and_commit(sql)
-
- def rename_schema(self, schema, new_schema):
- """Rename a schema in database."""
-
- sql = 'ALTER SCHEMA %s RENAME TO %s' % (self._quote(schema),
- self._quote(new_schema))
- self._exec_sql_and_commit(sql)
-
- # Update geometry_columns if PostGIS is enabled
- if self.has_postgis:
- sql = \
- "UPDATE geometry_columns SET f_table_schema='%s' \
- WHERE f_table_schema='%s'" \
- % (self._quote_unicode(new_schema), self._quote_unicode(schema))
- self._exec_sql_and_commit(sql)
-
- def table_add_column(self, table, field, schema=None):
- """Add a column to table (passed as TableField instance)."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s ADD %s' % (table_name, field.field_def())
- self._exec_sql_and_commit(sql)
-
- def table_delete_column(self, table, field, schema=None):
- """Delete column from a table."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s DROP %s' % (table_name, self._quote(field))
- self._exec_sql_and_commit(sql)
-
- def table_column_rename(self, table, name, new_name, schema=None):
- """Rename column in a table."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s RENAME %s TO %s' % (table_name,
- self._quote(name), self._quote(new_name))
- self._exec_sql_and_commit(sql)
-
- # Update geometry_columns if PostGIS is enabled
- if self.has_postgis:
- sql = "UPDATE geometry_columns SET f_geometry_column='%s' \
- WHERE f_geometry_column='%s' AND f_table_name='%s'" \
- % (self._quote_unicode(new_name), self._quote_unicode(name),
- self._quote_unicode(table))
- if schema is not None:
- sql += " AND f_table_schema='%s'" % self._quote(schema)
- self._exec_sql_and_commit(sql)
-
- def table_column_set_type(self, table, column, data_type, schema=None):
- """Change column type."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s ALTER %s TYPE %s' % (table_name,
- self._quote(column), data_type)
- self._exec_sql_and_commit(sql)
-
- def table_column_set_default(self, table, column, default, schema=None):
- """Change column's default value.
-
- If default=None drop default value.
- """
-
- table_name = self._table_name(schema, table)
- if default:
- sql = 'ALTER TABLE %s ALTER %s SET DEFAULT %s' % (table_name,
- self._quote(column), default)
- else:
- sql = 'ALTER TABLE %s ALTER %s DROP DEFAULT' % (table_name,
- self._quote(column))
- self._exec_sql_and_commit(sql)
-
- def table_column_set_null(self, table, column, is_null, schema=None):
- """Change whether column can contain null values."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s ALTER %s ' % (table_name, self._quote(column))
- if is_null:
- sql += 'DROP NOT NULL'
- else:
- sql += 'SET NOT NULL'
- self._exec_sql_and_commit(sql)
-
- def table_add_primary_key(self, table, column, schema=None):
- """Add a primery key (with one column) to a table."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s ADD PRIMARY KEY (%s)' % (table_name,
- self._quote(column))
- self._exec_sql_and_commit(sql)
-
- def table_add_unique_constraint(self, table, column, schema=None):
- """Add a unique constraint to a table."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s ADD UNIQUE (%s)' % (table_name,
- self._quote(column))
- self._exec_sql_and_commit(sql)
-
- def table_delete_constraint(self, table, constraint, schema=None):
- """Delete constraint in a table."""
-
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s DROP CONSTRAINT %s' % (table_name,
- self._quote(constraint))
- self._exec_sql_and_commit(sql)
-
- def table_move_to_schema(self, table, new_schema, schema=None):
- if new_schema == schema:
- return
- table_name = self._table_name(schema, table)
- sql = 'ALTER TABLE %s SET SCHEMA %s' % (table_name,
- self._quote(new_schema))
- self._exec_sql_and_commit(sql)
-
- # Update geometry_columns if PostGIS is enabled
- if self.has_postgis:
- sql = "UPDATE geometry_columns SET f_table_schema='%s' \
- WHERE f_table_name='%s'" \
- % (self._quote_unicode(new_schema), self._quote_unicode(table))
- if schema is not None:
- sql += " AND f_table_schema='%s'" % self._quote_unicode(schema)
- self._exec_sql_and_commit(sql)
-
- def create_index(self, table, name, column, schema=None):
- """Create index on one column using default options."""
-
- table_name = self._table_name(schema, table)
- idx_name = self._quote(name)
- sql = 'CREATE INDEX "%s" ON %s (%s)' % (idx_name, table_name,
- self._quote(column))
- self._exec_sql_and_commit(sql)
-
- def create_spatial_index(self, table, schema=None, geom_column='the_geom'):
- table_name = self._table_name(schema, table)
- idx_name = self._quote(u"sidx_%s_%s" % (table, geom_column))
- sql = 'CREATE INDEX "%s" ON %s USING GIST(%s)' % (idx_name, table_name,
- self._quote(geom_column))
- self._exec_sql_and_commit(sql)
-
- def delete_index(self, name, schema=None):
- index_name = self._table_name(schema, name)
- sql = 'DROP INDEX "%s"' % index_name
- self._exec_sql_and_commit(sql)
-
- def get_database_privileges(self):
- """DB privileges: (can create schemas, can create temp. tables).
- """
-
- sql = "SELECT has_database_privilege('%(d)s', 'CREATE'), \
- has_database_privilege('%(d)s', 'TEMP')" \
- % {'d': self._quote_unicode(self.uri.database())}
- c = self.con.cursor()
- self._exec_sql(c, sql)
- return c.fetchone()
-
- def get_schema_privileges(self, schema):
- """Schema privileges: (can create new objects, can access objects
- in schema)."""
-
- sql = "SELECT has_schema_privilege('%(s)s', 'CREATE'), \
- has_schema_privilege('%(s)s', 'USAGE')" \
- % {'s': self._quote_unicode(schema)}
- c = self.con.cursor()
- self._exec_sql(c, sql)
- return c.fetchone()
-
- def get_table_privileges(self, table, schema=None):
- """Table privileges: (select, insert, update, delete).
- """
-
- t = self._table_name(schema, table)
- sql = """SELECT has_table_privilege('%(t)s', 'SELECT'),
- has_table_privilege('%(t)s', 'INSERT'),
- has_table_privilege('%(t)s', 'UPDATE'),
- has_table_privilege('%(t)s', 'DELETE')""" \
- % {'t': self._quote_unicode(t)}
- c = self.con.cursor()
- self._exec_sql(c, sql)
- return c.fetchone()
-
- def vacuum_analyze(self, table, schema=None):
- """Run VACUUM ANALYZE on a table."""
-
- t = self._table_name(schema, table)
-
- # VACUUM ANALYZE must be run outside transaction block - we
- # have to change isolation level
- self.con.set_isolation_level(
- psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
- c = self.con.cursor()
- self._exec_sql(c, 'VACUUM ANALYZE %s' % t)
- self.con.set_isolation_level(
- psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
-
- def sr_info_for_srid(self, srid):
- if not self.has_postgis:
- return 'Unknown'
-
- try:
- c = self.con.cursor()
- self._exec_sql(c,
- "SELECT srtext FROM spatial_ref_sys WHERE srid = '%d'"
- % srid)
- srtext = c.fetchone()[0]
-
- # Try to extract just SR name (should be quoted in double
- # quotes)
- x = re.search('"([^"]+)"', srtext)
- if x is not None:
- srtext = x.group()
- return srtext
- except DbError:
- return 'Unknown'
-
- def insert_table_row(self, table, values, schema=None, cursor=None):
- """Insert a row with specified values to a table.
-
- If a cursor is specified, it doesn't commit (expecting that
- there will be more inserts) otherwise it commits immediately.
- """
-
- t = self._table_name(schema, table)
- sql = ''
- for value in values:
- # TODO: quote values?
- if sql:
- sql += ', '
- sql += value
- sql = 'INSERT INTO %s VALUES (%s)' % (t, sql)
- if cursor:
- self._exec_sql(cursor, sql)
- else:
- self._exec_sql_and_commit(sql)
-
- def _exec_sql(self, cursor, sql):
- try:
- cursor.execute(sql)
- except psycopg2.Error as e:
- raise QgsProcessingException(str(e) + ' QUERY: '
- + e.cursor.query.decode(e.cursor.connection.encoding))
-
- def _exec_sql_and_commit(self, sql):
- """Tries to execute and commit some action, on error it rolls
- back the change.
- """
-
- try:
- c = self.con.cursor()
- self._exec_sql(c, sql)
- self.con.commit()
- except DbError:
- self.con.rollback()
- raise
-
- def _quote(self, identifier):
- """Quote identifier if needed."""
-
- # Make sure it's python unicode string
- identifier = str(identifier)
-
- # Is it needed to quote the identifier?
- if self.re_ident_ok.match(identifier) is not None:
- return identifier
-
- # It's needed - let's quote it (and double the double-quotes)
- return u'"%s"' % identifier.replace('"', '""')
-
- def _quote_unicode(self, txt):
- """Make the string safe - replace ' with ''.
- """
-
- # make sure it's python unicode string
- txt = str(txt)
- return txt.replace("'", "''")
-
- def _table_name(self, schema, table):
- if not schema:
- return self._quote(table)
- else:
- return u'"%s"."%s"' % (self._quote(schema), self._quote(table))
-
-
-# For debugging / testing
-if __name__ == '__main__':
-
- db = GeoDB(host='localhost', dbname='gis', user='gisak', passwd='g')
- # fix_print_with_import
- print(db.list_schemas())
- # fix_print_with_import
- print('==========')
-
- for row in db.list_geotables():
- # fix_print_with_import
- print(row)
- # fix_print_with_import
- print('==========')
-
- for row in db.get_table_indexes('trencin'):
- # fix_print_with_import
- print(row)
- # fix_print_with_import
- print('==========')
-
- for row in db.get_table_constraints('trencin'):
- # fix_print_with_import
- print(row)
- # fix_print_with_import
- print('==========')
-
- # fix_print_with_import
- print(db.get_table_rows('trencin'))
-
- # for fld in db.get_table_metadata('trencin'):
- # ....print fld
- # try:
- # ....db.create_table('trrrr', [('id','serial'), ('test','text')])
- # except DbError, e:
- # ....print unicode(e), e.query
diff --git a/python/plugins/processing/tools/spatialite.py b/python/plugins/processing/tools/spatialite.py
deleted file mode 100644
index aa7d2b78a206..000000000000
--- a/python/plugins/processing/tools/spatialite.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- spatialite.py
- ---------------------
- Date : November 2015
- Copyright : (C) 2015 by René-Luc Dhont
- Email : volayaf at gmail dot com
-***************************************************************************
-* *
-* This program is free software; you can redistribute it and/or modify *
-* it under the terms of the GNU General Public License as published by *
-* the Free Software Foundation; either version 2 of the License, or *
-* (at your option) any later version. *
-* *
-***************************************************************************
-"""
-
-__author__ = 'René-Luc Dhont'
-__date__ = 'November 2015'
-__copyright__ = '(C) 2015, René-Luc Dhont'
-
-from qgis.utils import spatialite_connect
-import sqlite3 as sqlite
-import re
-
-
-class DbError(Exception):
-
- def __init__(self, message, query=None):
- # Save error. funny that the variables are in utf-8
- self.message = str(message)
- self.query = (str(query) if query is not None else None)
-
- def __str__(self):
- return 'MESSAGE: %s\nQUERY: %s' % (self.message, self.query)
-
-
-class GeoDB:
-
- def __init__(self, uri=None):
- self.uri = uri
- self.dbname = uri.database()
-
- try:
- self.con = spatialite_connect(self.con_info())
-
- except (sqlite.InterfaceError, sqlite.OperationalError) as e:
- raise DbError(str(e))
-
- self.has_spatialite = self.check_spatialite()
- if not self.has_spatialite:
- self.has_spatialite = self.init_spatialite()
-
- def con_info(self):
- return str(self.dbname)
-
- def init_spatialite(self):
- # Get SpatiaLite version
- c = self.con.cursor()
- try:
- self._exec_sql(c, u'SELECT spatialite_version()')
- rep = c.fetchall()
- v = [int(x) if x.isdigit() else x for x in re.findall(r"\d+|[a-zA-Z]+", rep[0][0])]
-
- # Add SpatiaLite support
- if v >= [4, 1, 0]:
- # 4.1 and above
- sql = "SELECT initspatialmetadata(1)"
- else:
- # Under 4.1
- sql = "SELECT initspatialmetadata()"
- self._exec_sql_and_commit(sql)
- except:
- return False
- finally:
- self.con.close()
-
- try:
- self.con = spatialite_connect(self.con_info())
-
- except (sqlite.InterfaceError, sqlite.OperationalError) as e:
- raise DbError(str(e))
-
- return self.check_spatialite()
-
- def check_spatialite(self):
- try:
- c = self.con.cursor()
- self._exec_sql(c, u"SELECT CheckSpatialMetaData()")
- v = c.fetchone()[0]
- self.has_geometry_columns = v == 1 or v == 3
- self.has_spatialite4 = v == 3
- except Exception:
- self.has_geometry_columns = False
- self.has_spatialite4 = False
-
- self.has_geometry_columns_access = self.has_geometry_columns
- return self.has_geometry_columns
-
- def _exec_sql(self, cursor, sql):
- try:
- cursor.execute(sql)
- except (sqlite.Error, sqlite.ProgrammingError, sqlite.Warning, sqlite.InterfaceError, sqlite.OperationalError) as e:
- raise DbError(str(e), sql)
-
- def _exec_sql_and_commit(self, sql):
- """Tries to execute and commit some action, on error it rolls
- back the change.
- """
-
- try:
- c = self.con.cursor()
- self._exec_sql(c, sql)
- self.con.commit()
- except DbError:
- self.con.rollback()
- raise
-
- def create_spatial_index(self, table, geom_column='the_geom'):
- sql = u"SELECT CreateSpatialIndex(%s, %s)" % (self._quote(table), self._quote(geom_column))
- self._exec_sql_and_commit(sql)
-
- def _quote(self, identifier):
- """Quote identifier."""
-
- # quote identifier, and double the double-quotes
- return u"'%s'" % identifier.replace("'", "''")
diff --git a/python/plugins/processing/ui/DlgAlgorithmBase.ui b/python/plugins/processing/ui/DlgAlgorithmBase.ui
deleted file mode 100644
index 282f5619cc17..000000000000
--- a/python/plugins/processing/ui/DlgAlgorithmBase.ui
+++ /dev/null
@@ -1,151 +0,0 @@
-
-
- Dialog
-
-
-
- 0
- 0
- 685
- 525
-
-
-
- Dialog
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Qt::Horizontal
-
-
- 16
-
-
-
-
- 2
- 0
-
-
-
- 0
-
-
-
- Parameters
-
-
-
- 2
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
-
-
-
-
- Log
-
-
-
- 2
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
- -
-
-
- true
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
-
-
- false
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- 0
-
- -
-
-
- 0
-
-
-
- -
-
-
- Cancel
-
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Close|QDialogButtonBox::Help|QDialogButtonBox::Ok
-
-
-
-
-
-
-
-
diff --git a/python/plugins/processing/ui/DlgNumberInput.ui b/python/plugins/processing/ui/DlgNumberInput.ui
deleted file mode 100644
index 1f284381393d..000000000000
--- a/python/plugins/processing/ui/DlgNumberInput.ui
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
- DlgNumberInput
-
-
-
- 0
- 0
- 445
- 300
-
-
-
- Enter number or expression
-
-
-
- 6
-
-
- 9
-
- -
-
-
- <html><head/><body><p>Enter expression in the text field. Double-click on elements in the tree to add their values to the expression.</p></body></html>
-
-
- true
-
-
-
- -
-
-
- <html><head/><body><p><span style=" font-weight:600;">Warning</span>: if expression result is float value, but integer required, result will be rounded to integer.</p></body></html>
-
-
- true
-
-
-
- -
-
-
- false
-
-
-
- 1
-
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
-
-
-
-
-
- buttonBox
- accepted()
- DlgNumberInput
- accept()
-
-
- 248
- 254
-
-
- 157
- 274
-
-
-
-
- buttonBox
- rejected()
- DlgNumberInput
- reject()
-
-
- 316
- 260
-
-
- 286
- 274
-
-
-
-
-
diff --git a/python/plugins/processing/ui/DlgPostgisTableSelector.ui b/python/plugins/processing/ui/DlgPostgisTableSelector.ui
deleted file mode 100644
index 577e60af6230..000000000000
--- a/python/plugins/processing/ui/DlgPostgisTableSelector.ui
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
- Dialog
-
-
-
- 0
- 0
- 464
- 395
-
-
-
- output table
-
-
- -
-
-
- Select connection and schema
-
-
-
- -
-
-
- false
-
-
-
- 1
-
-
-
-
- -
-
- -
-
-
- Table name
-
-
-
- -
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
-
-
-
-
-
diff --git a/python/plugins/processing/ui/widgetBaseSelector.ui b/python/plugins/processing/ui/widgetBaseSelector.ui
index be82e5801e6c..1cac30036eef 100644
--- a/python/plugins/processing/ui/widgetBaseSelector.ui
+++ b/python/plugins/processing/ui/widgetBaseSelector.ui
@@ -7,7 +7,7 @@
0
0
249
- 23
+ 27
@@ -17,7 +17,16 @@
6
-
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
0
-
diff --git a/python/plugins/processing/ui/widgetGeometryPredicateSelector.ui b/python/plugins/processing/ui/widgetGeometryPredicateSelector.ui
deleted file mode 100644
index a9eb59a8699f..000000000000
--- a/python/plugins/processing/ui/widgetGeometryPredicateSelector.ui
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 609
- 213
-
-
-
- Form
-
-
-
- 0
-
- -
-
-
- equals
-
-
-
- -
-
-
- contains
-
-
-
- -
-
-
- touches
-
-
-
- -
-
-
- intersects
-
-
-
- -
-
-
- within
-
-
-
- -
-
-
- overlaps
-
-
-
- -
-
-
- crosses
-
-
-
- -
-
-
- disjoint
-
-
-
-
-
-
-
-
diff --git a/python/plugins/processing/ui/widgetLayerSelector.ui b/python/plugins/processing/ui/widgetLayerSelector.ui
deleted file mode 100644
index 0b5432010f2d..000000000000
--- a/python/plugins/processing/ui/widgetLayerSelector.ui
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 608
- 20
-
-
-
- Form
-
-
-
- 2
-
-
- 0
-
- -
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- …
-
-
-
- -
-
-
-
- 0
- 1
-
-
-
- Iterate over this layer
-
-
- …
-
-
- true
-
-
-
-
-
-
-
-
diff --git a/python/processing/algfactory.py b/python/processing/algfactory.py
index 58fa7d1e1fe2..14fda409de26 100644
--- a/python/processing/algfactory.py
+++ b/python/processing/algfactory.py
@@ -59,6 +59,11 @@
QgsProcessingParameterLayout,
QgsProcessingParameterLayoutItem,
QgsProcessingParameterDateTime,
+ QgsProcessingParameterMapTheme,
+ QgsProcessingParameterProviderConnection,
+ QgsProcessingParameterDatabaseSchema,
+ QgsProcessingParameterDatabaseTable,
+ QgsProcessingParameterCoordinateOperation,
QgsProcessingOutputString,
QgsProcessingOutputBoolean,
QgsProcessingOutputFile,
@@ -328,6 +333,11 @@ class ProcessingAlgFactory():
LAYOUT = "LAYOUT"
LAYOUT_ITEM = "LAYOUT_ITEM"
DATETIME = "DATETIME"
+ MAP_THEME = "MAP_THEME"
+ PROVIDER_CONNECTION = "PROVIDER_CONNECTION"
+ DATABASE_SCHEMA = "DATABASE_SCHEMA"
+ DATABASE_TABLE = "DATABASE_TABLE"
+ COORDINATE_OPERATION = "COORDINATE_OPERATION"
def __init__(self):
self._current = None
@@ -461,6 +471,11 @@ def input(self, type, *args, **kwargs):
alg.LAYOUT_ITEM: QgsProcessingParameterLayoutItem
alg.COLOR: QgsProcessingParameterColor
alg.DATETIME: QgsProcessingParameterDateTime
+ alg.MAP_THEME: QgsProcessingParameterMapTheme
+ alg.PROVIDER_CONNECTION: QgsProcessingParameterProviderConnection
+ alg.DATABASE_SCHEMA: QgsProcessingParameterDatabaseSchema
+ alg.DATABASE_TABLE: QgsProcessingParameterDatabaseTable
+ alg.COORDINATE_OPERATION: QgsProcessingParameterCoordinateOperation
:param type: The type of the input. This should be a type define on `alg` like alg.STRING, alg.DISTANCE
:keyword label: The label of the output. Translates into `description` arg.
@@ -512,7 +527,12 @@ def dec(f):
ProcessingAlgFactory.LAYOUT: QgsProcessingParameterLayout,
ProcessingAlgFactory.LAYOUT_ITEM: QgsProcessingParameterLayoutItem,
ProcessingAlgFactory.COLOR: QgsProcessingParameterColor,
- ProcessingAlgFactory.DATETIME: QgsProcessingParameterDateTime
+ ProcessingAlgFactory.DATETIME: QgsProcessingParameterDateTime,
+ ProcessingAlgFactory.MAP_THEME: QgsProcessingParameterMapTheme,
+ ProcessingAlgFactory.PROVIDER_CONNECTION: QgsProcessingParameterProviderConnection,
+ ProcessingAlgFactory.DATABASE_SCHEMA: QgsProcessingParameterDatabaseSchema,
+ ProcessingAlgFactory.DATABASE_TABLE: QgsProcessingParameterDatabaseTable,
+ ProcessingAlgFactory.COORDINATE_OPERATION: QgsProcessingParameterCoordinateOperation
}
output_type_mapping = {
diff --git a/python/pyplugin_installer/installer.py b/python/pyplugin_installer/installer.py
index 0b0ac08c6b24..0e50e976117c 100644
--- a/python/pyplugin_installer/installer.py
+++ b/python/pyplugin_installer/installer.py
@@ -220,14 +220,25 @@ def exportPluginsToManager(self):
"installed": plugin["installed"] and "true" or "false",
"available": plugin["available"] and "true" or "false",
"status": plugin["status"],
+ "status_exp": plugin["status_exp"],
"error": plugin["error"],
"error_details": plugin["error_details"],
+ "create_date": plugin["create_date"],
+ "update_date": plugin["update_date"],
+ "create_date_stable": plugin["create_date_stable"],
+ "update_date_stable": plugin["update_date_stable"],
+ "create_date_experimental": plugin["create_date_experimental"],
+ "update_date_experimental": plugin["update_date_experimental"],
"experimental": plugin["experimental"] and "true" or "false",
"deprecated": plugin["deprecated"] and "true" or "false",
"trusted": plugin["trusted"] and "true" or "false",
"version_available": plugin["version_available"],
+ "version_available_stable": plugin["version_available_stable"] or "",
+ "version_available_experimental": plugin["version_available_experimental"] or "",
"zip_repository": plugin["zip_repository"],
"download_url": plugin["download_url"],
+ "download_url_stable": plugin["download_url_stable"],
+ "download_url_experimental": plugin["download_url_experimental"],
"filename": plugin["filename"],
"downloads": plugin["downloads"],
"average_vote": plugin["average_vote"],
@@ -282,19 +293,20 @@ def upgradeAllUpgradeable(self):
self.installPlugin(key, quiet=True)
# ----------------------------------------- #
- def installPlugin(self, key, quiet=False):
+ def installPlugin(self, key, quiet=False, stable=True):
""" Install given plugin """
error = False
+ status_key = 'status' if stable else 'status_exp'
infoString = ('', '')
plugin = plugins.all()[key]
- previousStatus = plugin["status"]
+ previousStatus = plugin[status_key]
if not plugin:
return
- if plugin["status"] == "newer" and not plugin["error"]: # ask for confirmation if user downgrades an usable plugin
+ if plugin[status_key] == "newer" and not plugin["error"]: # ask for confirmation if user downgrades an usable plugin
if QMessageBox.warning(iface.mainWindow(), self.tr("QGIS Python Plugin Installer"), self.tr("Are you sure you want to downgrade the plugin to the latest available version? The installed one is newer!"), QMessageBox.Yes, QMessageBox.No) == QMessageBox.No:
return
- dlg = QgsPluginInstallerInstallingDialog(iface.mainWindow(), plugin)
+ dlg = QgsPluginInstallerInstallingDialog(iface.mainWindow(), plugin, stable=stable)
dlg.exec_()
if dlg.result():
diff --git a/python/pyplugin_installer/installer_data.py b/python/pyplugin_installer/installer_data.py
index 2c04973399b4..a6f2df9c12a4 100644
--- a/python/pyplugin_installer/installer_data.py
+++ b/python/pyplugin_installer/installer_data.py
@@ -85,6 +85,8 @@
"downloads" unicode, # number of downloads
"average_vote" unicode, # average vote
"rating_votes" unicode, # number of votes
+ "create_date" unicode, # ISO datetime when the plugin has been created
+ "update_date" unicode, # ISO datetime when the plugin has been last updated
"plugin_dependencies" unicode, # PIP-style comma separated list of plugin dependencies
}}
"""
@@ -418,16 +420,23 @@ def xmlDownloaded(self):
else:
plugin_id = None
+ version = pluginNodes.item(i).toElement().attribute("version")
+ download_url = pluginNodes.item(i).firstChildElement("download_url").text().strip()
+
plugin = {
"id": name,
"plugin_id": plugin_id,
"name": pluginNodes.item(i).toElement().attribute("name"),
- "version_available": pluginNodes.item(i).toElement().attribute("version"),
+ "version_available": version,
+ "version_available_stable": version if not experimental else "",
+ "version_available_experimental": version if experimental else "",
"description": pluginNodes.item(i).firstChildElement("description").text().strip(),
"about": pluginNodes.item(i).firstChildElement("about").text().strip(),
"author_name": pluginNodes.item(i).firstChildElement("author_name").text().strip(),
"homepage": pluginNodes.item(i).firstChildElement("homepage").text().strip(),
- "download_url": pluginNodes.item(i).firstChildElement("download_url").text().strip(),
+ "download_url": download_url,
+ "download_url_stable": download_url if not experimental else "",
+ "download_url_experimental": download_url if experimental else "",
"category": pluginNodes.item(i).firstChildElement("category").text().strip(),
"tags": pluginNodes.item(i).firstChildElement("tags").text().strip(),
"changelog": pluginNodes.item(i).firstChildElement("changelog").text().strip(),
@@ -437,6 +446,12 @@ def xmlDownloaded(self):
"downloads": pluginNodes.item(i).firstChildElement("downloads").text().strip(),
"average_vote": pluginNodes.item(i).firstChildElement("average_vote").text().strip(),
"rating_votes": pluginNodes.item(i).firstChildElement("rating_votes").text().strip(),
+ "create_date": pluginNodes.item(i).firstChildElement("create_date").text().strip(),
+ "update_date": pluginNodes.item(i).firstChildElement("update_date").text().strip(),
+ "create_date_stable": pluginNodes.item(i).firstChildElement("create_date").text().strip() if not experimental else "",
+ "update_date_stable": pluginNodes.item(i).firstChildElement("update_date").text().strip() if not experimental else "",
+ "create_date_experimental": pluginNodes.item(i).firstChildElement("create_date").text().strip() if experimental else "",
+ "update_date_experimental": pluginNodes.item(i).firstChildElement("update_date").text().strip() if experimental else "",
"icon": icon,
"experimental": experimental,
"deprecated": deprecated,
@@ -445,6 +460,7 @@ def xmlDownloaded(self):
"installed": False,
"available": True,
"status": "not installed",
+ "status_exp": "not installed",
"error": "",
"error_details": "",
"version_installed": "",
@@ -668,15 +684,26 @@ def pluginMetadata(fct):
"deprecated": pluginMetadata("deprecated").strip().upper() in ["TRUE", "YES"],
"trusted": False,
"version_available": "",
+ "version_available_stable": "",
+ "version_available_experimental": "",
"zip_repository": "",
"download_url": path, # warning: local path as url!
+ "download_url_stable": "",
+ "download_url_experimental": "",
"filename": "",
"downloads": "",
"average_vote": "",
"rating_votes": "",
+ "create_date": pluginMetadata("create_date"),
+ "update_date": pluginMetadata("update_date"),
+ "create_date_stable": pluginMetadata("create_date_stable"),
+ "update_date_stable": pluginMetadata("update_date_stable"),
+ "create_date_experimental": pluginMetadata("create_date_experimental"),
+ "update_date_experimental": pluginMetadata("update_date_experimental"),
"available": False, # Will be overwritten, if any available version found.
"installed": True,
"status": "orphan", # Will be overwritten, if any available version found.
+ "status_exp": "orphan", # Will be overwritten, if any available version found.
"error": error,
"error_details": errorDetails,
"readonly": readOnly,
@@ -736,7 +763,12 @@ def rebuild(self):
# check if the plugin is allowed and if there isn't any better one added already.
if (allowExperimental or not plugin["experimental"]) \
and (allowDeprecated or not plugin["deprecated"]) \
- and not (key in self.mPlugins and self.mPlugins[key]["version_available"] and compareVersions(self.mPlugins[key]["version_available"], plugin["version_available"]) < 2):
+ and not (
+ key in self.mPlugins and self.mPlugins[key]["version_available"]
+ and compareVersions(self.mPlugins[key]["version_available"], plugin["version_available"]) < 2
+ and self.mPlugins[key]["experimental"] and not plugin["experimental"]
+ ):
+
# The mPlugins dict contains now locally installed plugins.
# Now, add the available one if not present yet or update it if present already.
if key not in self.mPlugins:
@@ -753,36 +785,67 @@ def rebuild(self):
# other remote metadata is preferred:
for attrib in ["name", "plugin_id", "description", "about", "category", "tags", "changelog", "author_name", "author_email", "homepage",
"tracker", "code_repository", "experimental", "deprecated", "version_available", "zip_repository",
- "download_url", "filename", "downloads", "average_vote", "rating_votes", "trusted", "plugin_dependencies"]:
+ "download_url", "filename", "downloads", "average_vote", "rating_votes", "trusted", "plugin_dependencies",
+ "version_available_stable", "version_available_experimental", "download_url_stable", "download_url_experimental",
+ "create_date", "update_date", "create_date_stable", "update_date_stable", "create_date_experimental", "update_date_experimental"]:
if attrib not in translatableAttributes or attrib == "name": # include name!
if plugin.get(attrib, False):
self.mPlugins[key][attrib] = plugin[attrib]
+
+ # If the stable version is higher than the experimental version, we ignore the experimental version
+ if compareVersions(self.mPlugins[key]["version_available_stable"], self.mPlugins[key]["version_available_experimental"]) == 1:
+ self.mPlugins[key]["version_available_experimental"] = ''
+
# set status
#
# installed available status
# ---------------------------------------
# none any "not installed" (will be later checked if is "new")
- # any none "orphan"
+ # no none "none available"
+ # yes none "orphan"
# same same "installed"
# less greater "upgradeable"
# greater less "newer"
- if not self.mPlugins[key]["version_available"]:
+ if not self.mPlugins[key]["version_available_stable"] and not self.mPlugins[key]["version_installed"]:
+ self.mPlugins[key]["status"] = "none available"
+ elif not self.mPlugins[key]["version_available_stable"] and self.mPlugins[key]["version_installed"]:
self.mPlugins[key]["status"] = "orphan"
elif not self.mPlugins[key]["version_installed"]:
self.mPlugins[key]["status"] = "not installed"
elif self.mPlugins[key]["version_installed"] in ["?", "-1"]:
self.mPlugins[key]["status"] = "installed"
- elif compareVersions(self.mPlugins[key]["version_available"], self.mPlugins[key]["version_installed"]) == 0:
+ elif compareVersions(self.mPlugins[key]["version_available_stable"], self.mPlugins[key]["version_installed"]) == 0:
self.mPlugins[key]["status"] = "installed"
- elif compareVersions(self.mPlugins[key]["version_available"], self.mPlugins[key]["version_installed"]) == 1:
+ elif compareVersions(self.mPlugins[key]["version_available_stable"], self.mPlugins[key]["version_installed"]) == 1:
self.mPlugins[key]["status"] = "upgradeable"
else:
self.mPlugins[key]["status"] = "newer"
# debug: test if the status match the "installed" tag:
- if self.mPlugins[key]["status"] in ["not installed"] and self.mPlugins[key]["installed"]:
- raise Exception("Error: plugin status is ambiguous (1)")
+ if self.mPlugins[key]["status"] in ["not installed", "none available"] and self.mPlugins[key]["installed"]:
+ raise Exception("Error: plugin status is ambiguous (1) for plugin {}".format(key))
if self.mPlugins[key]["status"] in ["installed", "orphan", "upgradeable", "newer"] and not self.mPlugins[key]["installed"]:
- raise Exception("Error: plugin status is ambiguous (2)")
+ raise Exception("Error: plugin status is ambiguous (2) for plugin {}".format(key))
+
+ if not self.mPlugins[key]["version_available_experimental"] and not self.mPlugins[key]["version_installed"]:
+ self.mPlugins[key]["status_exp"] = "none available"
+ elif not self.mPlugins[key]["version_available_experimental"] and self.mPlugins[key]["version_installed"]:
+ self.mPlugins[key]["status_exp"] = "orphan"
+ elif not self.mPlugins[key]["version_installed"]:
+ self.mPlugins[key]["status_exp"] = "not installed"
+ elif self.mPlugins[key]["version_installed"] in ["?", "-1"]:
+ self.mPlugins[key]["status_exp"] = "installed"
+ elif compareVersions(self.mPlugins[key]["version_available_experimental"], self.mPlugins[key]["version_installed"]) == 0:
+ self.mPlugins[key]["status_exp"] = "installed"
+ elif compareVersions(self.mPlugins[key]["version_available_experimental"], self.mPlugins[key]["version_installed"]) == 1:
+ self.mPlugins[key]["status_exp"] = "upgradeable"
+ else:
+ self.mPlugins[key]["status_exp"] = "newer"
+ # debug: test if the status_exp match the "installed" tag:
+ if self.mPlugins[key]["status_exp"] in ["not installed", "none available"] and self.mPlugins[key]["installed"]:
+ raise Exception("Error: plugin status_exp is ambiguous (1) for plugin {}".format(key))
+ if self.mPlugins[key]["status_exp"] in ["installed", "orphan", "upgradeable", "newer"] and not self.mPlugins[key]["installed"]:
+ raise Exception("Error: plugin status_exp is ambiguous (2) for plugin {} (status_exp={})".format(key, self.mPlugins[key]["status_exp"]))
+
self.markNews()
# ----------------------------------------- #
diff --git a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py
index 212c8be22807..1b4dc7552b4d 100644
--- a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py
+++ b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py
@@ -40,7 +40,7 @@
class QgsPluginInstallerInstallingDialog(QDialog, Ui_QgsPluginInstallerInstallingDialogBase):
# ----------------------------------------- #
- def __init__(self, parent, plugin):
+ def __init__(self, parent, plugin, stable=True):
QDialog.__init__(self, parent)
self.setupUi(self)
self.plugin = plugin
@@ -50,7 +50,7 @@ def __init__(self, parent, plugin):
self.labelName.setText(plugin["name"])
self.buttonBox.clicked.connect(self.abort)
- self.url = QUrl(plugin["download_url"])
+ self.url = QUrl(plugin["download_url_stable"] if stable else plugin["download_url_experimental"])
self.redirectionCounter = 0
fileName = plugin["filename"]
diff --git a/resources/function_help/json/display_expression b/resources/function_help/json/display_expression
new file mode 100644
index 000000000000..0c612ad7e35c
--- /dev/null
+++ b/resources/function_help/json/display_expression
@@ -0,0 +1,39 @@
+{
+ "name": "display_expression",
+ "type": "function",
+ "description": "Returns the display expression for a given feature in a layer. If called with no parameters, it evaluates the current feature. The expression is evaluated by default.",
+ "arguments": [
+ {
+ "arg": "feature",
+ "optional": true,
+ "default": "current feature",
+ "description": "The feature which should be evaluated."
+ },
+ {
+ "arg": "layer",
+ "optional": true,
+ "default": "current layer",
+ "description": "The layer (or its id or name)."
+ },
+ {
+ "arg": "evaluate",
+ "description": "If the expression must be evaluated. If false, the expression will be returned as a string literal only (which could potentially be later evaluated using the 'eval' function).",
+ "optional": true,
+ "default": "true"
+ }
+ ],
+ "examples": [
+ {
+ "expression": "display_expression()",
+ "returns": "The display expression of the current feature."
+ },
+ {
+ "expression": "display_expression($currentfeature)",
+ "returns": "The display expression for a given feature."
+ },
+ {
+ "expression": "display_expression('a_layer_id', $currentfeature, 'False')",
+ "returns": "The display expression of the current feature not evaluated."
+ }
+ ]
+}
diff --git a/resources/function_help/json/eval_template b/resources/function_help/json/eval_template
new file mode 100644
index 000000000000..0fc55f2c35d5
--- /dev/null
+++ b/resources/function_help/json/eval_template
@@ -0,0 +1,13 @@
+{
+ "name": "eval_template",
+ "type": "function",
+ "description": "Evaluates a template which is passed in a string. Useful to expand dynamic parameters passed as context variables or fields.",
+ "arguments": [{
+ "arg": "template",
+ "description": "a template string"
+ }],
+ "examples": [{
+ "expression": "eval_template('QGIS [% upper(\\\\'rocks\\\\') %]')",
+ "returns": "QGIS ROCKS"
+ }]
+}
diff --git a/resources/function_help/json/layer_property b/resources/function_help/json/layer_property
index df547bdde6ae..fbc4810f56d7 100644
--- a/resources/function_help/json/layer_property
+++ b/resources/function_help/json/layer_property
@@ -4,7 +4,7 @@
"description": "Returns a matching layer property or metadata value.",
"arguments": [
{"arg":"layer", "description":"a string, representing either a layer name or layer ID"},
- {"arg":"property", "description":"a string corresponding to the property to return. Valid options are:name: layer name id: layer ID title: metadata title string abstract: metadata abstract string keywords: metadata keywords data_url: metadata URL attribution: metadata attribution string attribution_url: metadata attribution URL source: layer source min_scale: minimum display scale for layer max_scale: maximum display scale for layer crs: layer CRS crs_definition: layer CRS full definition crs_description: layer CRS description extent: layer extent (as a geometry object) type: layer type, e.g., Vector or Raster storage_type: storage format (vector layers only) geometry_type: geometry type, e.g., Point (vector layers only) feature_count: approximate feature count for layer (vector layers only) path: File path to the layer data source. Only available for file based layers. "}
+ {"arg":"property", "description":"a string corresponding to the property to return. Valid options are:name: layer name id: layer ID title: metadata title string abstract: metadata abstract string keywords: metadata keywords data_url: metadata URL attribution: metadata attribution string attribution_url: metadata attribution URL source: layer source min_scale: minimum display scale for layer max_scale: maximum display scale for layer is_editable: if layer is in edit mode crs: layer CRS crs_definition: layer CRS full definition crs_description: layer CRS description extent: layer extent (as a geometry object) distance_units: layer distance units type: layer type, e.g., Vector or Raster storage_type: storage format (vector layers only) geometry_type: geometry type, e.g., Point (vector layers only) feature_count: approximate feature count for layer (vector layers only) path: File path to the layer data source. Only available for file based layers. "}
],
"examples": [
{ "expression":"layer_property('streets','title')", "returns":"'Basemap Streets'"},
diff --git a/resources/function_help/json/maptip b/resources/function_help/json/maptip
new file mode 100644
index 000000000000..13011c128191
--- /dev/null
+++ b/resources/function_help/json/maptip
@@ -0,0 +1,39 @@
+{
+ "name": "maptip",
+ "type": "function",
+ "description": "Returns the maptip for a given feature in a layer. If called with no parameters, it evaluates the current feature. The maptip is evaluated by default.",
+ "arguments": [
+ {
+ "arg": "feature",
+ "optional": true,
+ "default": "current feature",
+ "description": "The feature which should be evaluated."
+ },
+ {
+ "arg": "layer",
+ "optional": true,
+ "default": "current layer",
+ "description": "The layer (or its id or name)."
+ },
+ {
+ "arg": "evaluate",
+ "description": "If the expression must be evaluated. If false, the expression will be returned as a string literal only (which could potentially be later evaluated using the 'eval_template' function).",
+ "optional": true,
+ "default": "true"
+ }
+ ],
+ "examples": [
+ {
+ "expression": "maptip()",
+ "returns": "The maptip of the current feature."
+ },
+ {
+ "expression": "maptip($currentFeature)",
+ "returns": "The maptip of the current feature."
+ },
+ {
+ "expression": "maptip('a_layer_id', $currentFeature, 'False')",
+ "returns": "The maptip of the current feature not evaluated."
+ }
+ ]
+}
diff --git a/resources/themes/Blend of Gray/style.qss b/resources/themes/Blend of Gray/style.qss
index d5cf9b366971..c5b844d8fc65 100644
--- a/resources/themes/Blend of Gray/style.qss
+++ b/resources/themes/Blend of Gray/style.qss
@@ -241,6 +241,10 @@ QToolButton:checked
{
border: 1px solid @selection;
}
+QToolButton:checked:disabled
+{
+ border: 1px solid rgba(240,240,240,100);
+}
QToolButton::menu-arrow
{
image: url(@theme_path/icons/arrow-down.svg);
diff --git a/resources/themes/Night Mapping/style.qss b/resources/themes/Night Mapping/style.qss
index 9ba96a00a575..3269ab1496cf 100644
--- a/resources/themes/Night Mapping/style.qss
+++ b/resources/themes/Night Mapping/style.qss
@@ -251,6 +251,10 @@ QToolButton:checked
{
border: 1px solid @focus;
}
+QToolButton:checked:disabled
+{
+ border: 1px solid rgba(215,128,26,100);
+}
QToolButton::menu-arrow
{
image: url(@theme_path/icons/arrow-down.svg);
diff --git a/rpm/qgis.spec.template b/rpm/qgis.spec.template
index 6e6086b9f9bc..be74ad0e1200 100644
--- a/rpm/qgis.spec.template
+++ b/rpm/qgis.spec.template
@@ -87,6 +87,9 @@ BuildRequires: sqlite-devel
BuildRequires: hdf5-devel
BuildRequires: netcdf-devel
BuildRequires: fcgi-devel
+BuildRequires: protobuf-compiler
+BuildRequires: protobuf-devel
+BuildRequires: protobuf-lite-devel
# OpenCL
BuildRequires: opencl-headers
@@ -99,7 +102,6 @@ BuildRequires: python3-OWSLib
BuildRequires: python3-psycopg2
BuildRequires: python3-pygments
BuildRequires: python3-PyYAML
-BuildRequires: python3-qscintilla-devel
BuildRequires: python3-qscintilla-qt5
BuildRequires: python3-qscintilla-qt5-devel
BuildRequires: python3-qt5-devel
@@ -197,7 +199,6 @@ Requires: python3-OWSLib
Requires: python3-psycopg2
Requires: python3-pygments
Requires: python3-PyYAML
-Requires: python3-qscintilla
Requires: python3-qscintilla-qt5
Requires: python3-qt5
%{?_sip_api:Requires: python3-pyqt5-sip-api(%{_sip_api_major}) >= %{_sip_api}}
diff --git a/scripts/astyle.sh b/scripts/astyle.sh
index 7217f9acee60..29565ee2eff5 100755
--- a/scripts/astyle.sh
+++ b/scripts/astyle.sh
@@ -105,7 +105,7 @@ astyleit() {
for f in "$@"; do
case "$f" in
- src/plugins/grass/qtermwidget/*|external/o2/*|external/qt-unix-signals/*|external/rtree/*|external/astyle/*|external/kdbush/*|external/poly2tri/*|external/wintoast/*|external/qt3dextra-headers/*|external/meshOptimizer/*|python/ext-libs/*|ui_*.py|*.astyle|tests/testdata/*|editors/*)
+ src/plugins/grass/qtermwidget/*|external/o2/*|external/qt-unix-signals/*|external/rtree/*|external/astyle/*|external/kdbush/*|external/poly2tri/*|external/wintoast/*|external/qt3dextra-headers/*|external/meshOptimizer/*|external/mapbox-vector-tile/*|python/ext-libs/*|ui_*.py|*.astyle|tests/testdata/*|editors/*)
echo -ne "$f skipped $elcr"
continue
;;
diff --git a/scripts/customwidget.cpp.template b/scripts/customwidget.cpp.template
index a96740828310..504eb95f79db 100644
--- a/scripts/customwidget.cpp.template
+++ b/scripts/customwidget.cpp.template
@@ -19,8 +19,8 @@
%CLASSMIXEDCASE%Plugin::%CLASSMIXEDCASE%Plugin( QObject *parent )
- : QObject( parent )
- , mInitialized( false )
+ : QObject( parent )
+ , mInitialized( false )
{
}
@@ -61,7 +61,7 @@ bool %CLASSMIXEDCASE%Plugin::isInitialized() const
void %CLASSMIXEDCASE%Plugin::initialize( QDesignerFormEditorInterface *core )
{
- Q_UNUSED( core );
+ Q_UNUSED( core )
if ( mInitialized )
return;
mInitialized = true;
@@ -81,7 +81,7 @@ QString %CLASSMIXEDCASE%Plugin::whatsThis() const
QString %CLASSMIXEDCASE%Plugin::domXml() const
{
return QString( "\n"
- " \n"
+ " \n"
" \n"
" \n"
" 0 \n"
diff --git a/scripts/customwidget.h.template b/scripts/customwidget.h.template
index 75becbaedaed..9b33b557d129 100644
--- a/scripts/customwidget.h.template
+++ b/scripts/customwidget.h.template
@@ -20,6 +20,7 @@
#include
#include
#include
+#include "qgis_customwidgets.h"
class CUSTOMWIDGETS_EXPORT %CLASSMIXEDCASE%Plugin : public QObject, public QDesignerCustomWidgetInterface
diff --git a/scripts/customwidget_create.sh b/scripts/customwidget_create.sh
index 282af0641ef0..4c18fccbdf9f 100755
--- a/scripts/customwidget_create.sh
+++ b/scripts/customwidget_create.sh
@@ -9,6 +9,12 @@
set -e
+# GNU prefix command for mac os support (gsed, gsplit)
+GP=
+if [[ "$OSTYPE" =~ darwin* ]]; then
+ GP=g
+fi
+
CLASSNAME=$1
TODAY=$(date '+%d.%m.%Y')
@@ -19,6 +25,7 @@ EMAIL=$(git config user.email)
CLASSUPPER="${CLASSNAME^^}"
CLASSLOWER="${CLASSNAME,,}"
+CLASSWITHOUTQGS=$(${GP}sed 's/^Qgs//' <<< ${CLASSNAME})
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
@@ -27,13 +34,12 @@ for i in "${EXT[@]}"
do
DESTFILE=$DIR/../src/customwidgets/${CLASSLOWER}plugin.$i
cp "$DIR"/customwidget."$i".template "$DESTFILE"
- sed -i s/%DATE%/"$TODAY"/g "$DESTFILE"
- sed -i s/%YEAR%/"$YEAR"/g "$DESTFILE"
- sed -i s/%AUTHOR%/"$AUTHOR"/g "$DESTFILE"
- sed -i s/%EMAIL%/"$EMAIL"/g "$DESTFILE"
- sed -i s/%CLASSUPPERCASE%/"$CLASSUPPER"/g "$DESTFILE"
- sed -i s/%CLASSLOWERCASE%/"$CLASSLOWER"/g "$DESTFILE"
- sed -i s/%CLASSMIXEDCASE%/"$CLASSNAME"/g "$DESTFILE"
+ ${GP}sed -i s/%DATE%/${TODAY}/g ${DESTFILE}
+ ${GP}sed -i s/%YEAR%/${YEAR}/g ${DESTFILE}
+ ${GP}sed -i s/%AUTHOR%/${AUTHOR}/g ${DESTFILE}
+ ${GP}sed -i s/%EMAIL%/${EMAIL}/g ${DESTFILE}
+ ${GP}sed -i s/%CLASSUPPERCASE%/${CLASSUPPER}/g ${DESTFILE}
+ ${GP}sed -i s/%CLASSLOWERCASE%/${CLASSLOWER}/g ${DESTFILE}
+ ${GP}sed -i s/%CLASSMIXEDCASE%/${CLASSNAME}/g ${DESTFILE}
+ ${GP}sed -i s/%CLASSWITHOUTQGS%/${CLASSWITHOUTQGS}/g ${DESTFILE}
done
-
-
diff --git a/scripts/sipify.pl b/scripts/sipify.pl
index b08a19d0773a..4f90a44bc693 100755
--- a/scripts/sipify.pl
+++ b/scripts/sipify.pl
@@ -1210,8 +1210,8 @@ sub detect_non_method_member{
# multiline definition (parenthesis left open)
if ( $MULTILINE_DEFINITION != MULTILINE_NO ){
dbg_info("on multiline");
- # https://regex101.com/r/DN01iM/2
- if ( $LINE =~ m/^([^()]+(\((?:[^()]++|(?1))*\)))*[^()]*\)[^()]*$/){
+ # https://regex101.com/r/DN01iM/4
+ if ( $LINE =~ m/^([^()]+(\((?:[^()]++|(?1))*\)))*[^()]*\)([^()](throw\([^()]+\))?)*$/){
dbg_info("ending multiline");
# remove potential following body
if ( $MULTILINE_DEFINITION != MULTILINE_CONDITIONAL_STATEMENT && $LINE !~ m/(\{.*\}|;)\s*(\/\/.*)?$/ ){
diff --git a/scripts/spell_check/.agignore b/scripts/spell_check/.agignore
index 5af581c846dc..e967542641f7 100644
--- a/scripts/spell_check/.agignore
+++ b/scripts/spell_check/.agignore
@@ -14,6 +14,7 @@ src/app/gps/qwtpolar-1.0/
src/app/gps/qwtpolar-1.1.1/
src/core/pal
src/plugins/grass/qtermwidget/
+tests/testdata/provider/postgresraster/
#Extensions
*.*.orig
diff --git a/scripts/spell_check/spelling.dat b/scripts/spell_check/spelling.dat
index 98b127a64fd3..20443d04cce4 100644
--- a/scripts/spell_check/spelling.dat
+++ b/scripts/spell_check/spelling.dat
@@ -4233,7 +4233,7 @@ kwno:know
labatory:laboratory
labelling:labeling
lable:label
-labled:labeled
+labled:labeled:*
lables:labels:*
labour:labor
labratory:laboratory
diff --git a/src/3d/mesh/qgsmesh3dentity_p.cpp b/src/3d/mesh/qgsmesh3dentity_p.cpp
index a1345dbe27a0..bd812f107311 100644
--- a/src/3d/mesh/qgsmesh3dentity_p.cpp
+++ b/src/3d/mesh/qgsmesh3dentity_p.cpp
@@ -54,7 +54,7 @@ void QgsMeshDataset3dEntity::buildGeometry()
if ( !layer() )
return;
- mesh->setGeometry( new QgsMeshDataset3dGeometry( layer(), mMapSettings.origin(), mSymbol, mesh ) );
+ mesh->setGeometry( new QgsMeshDataset3dGeometry( layer(), mMapSettings.temporalRange(), mMapSettings.origin(), mSymbol, mesh ) );
addComponent( mesh );
}
@@ -63,11 +63,11 @@ void QgsMeshDataset3dEntity::applyMaterial()
if ( mSymbol.renderingStyle() == QgsMesh3DSymbol::ColorRamp2DRendering && layer() )
{
const QgsMeshRendererSettings rendererSettings = layer()->rendererSettings();
- const QgsMeshDatasetIndex datasetIndex = rendererSettings.activeScalarDataset();
- if ( datasetIndex.isValid() )
- mSymbol.setColorRampShader( rendererSettings.scalarSettings( datasetIndex.group() ).colorRampShader() );
+ int datasetGroupIndex = rendererSettings.activeScalarDatasetGroup();
+ if ( datasetGroupIndex >= 0 )
+ mSymbol.setColorRampShader( rendererSettings.scalarSettings( datasetGroupIndex ).colorRampShader() );
}
- QgsMesh3dMaterial *material = new QgsMesh3dMaterial( layer(), mMapSettings.origin(), mSymbol, QgsMesh3dMaterial::ScalarDataSet );
+ QgsMesh3dMaterial *material = new QgsMesh3dMaterial( layer(), mMapSettings.temporalRange(), mMapSettings.origin(), mSymbol, QgsMesh3dMaterial::ScalarDataSet );
addComponent( material );
}
@@ -93,7 +93,11 @@ void QgsMesh3dTerrainTileEntity::buildGeometry()
void QgsMesh3dTerrainTileEntity::applyMaterial()
{
- QgsMesh3dMaterial *material = new QgsMesh3dMaterial( layer(), mMapSettings.origin(), mSymbol, QgsMesh3dMaterial::ZValue );
+ QgsMesh3dMaterial *material = new QgsMesh3dMaterial(
+ layer(), mMapSettings.temporalRange(),
+ mMapSettings.origin(),
+ mSymbol,
+ QgsMesh3dMaterial::ZValue );
addComponent( material );
}
diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.cpp b/src/3d/mesh/qgsmesh3dgeometry_p.cpp
index 8830fdf83cfd..40050d047d2e 100644
--- a/src/3d/mesh/qgsmesh3dgeometry_p.cpp
+++ b/src/3d/mesh/qgsmesh3dgeometry_p.cpp
@@ -324,12 +324,14 @@ QgsMesh3dGeometry::QgsMesh3dGeometry( QgsMeshLayer *layer,
QgsMeshDataset3dGeometry::QgsMeshDataset3dGeometry(
QgsMeshLayer *layer,
+ const QgsDateTimeRange &timeRange,
const QgsVector3D &origin,
const QgsMesh3DSymbol &symbol,
Qt3DCore::QNode *parent ):
QgsMesh3dGeometry( layer, origin, symbol, parent ),
mIsVerticalMagnitudeRelative( symbol.isVerticalMagnitudeRelative() ),
- mVerticalGroupDatasetIndex( symbol.verticalDatasetGroupIndex() )
+ mVerticalGroupDatasetIndex( symbol.verticalDatasetGroupIndex() ),
+ mTimeRange( timeRange )
{
init();
}
@@ -395,7 +397,8 @@ int QgsMeshDataset3dGeometry::extractDataset( QVector &verticalMagnitude
if ( !layer )
return 0;
- QgsMeshDatasetIndex scalarDatasetIndex = layer->rendererSettings().activeScalarDataset();
+ QgsMeshDatasetIndex scalarDatasetIndex = layer->activeScalarDatasetAtTime( mTimeRange );
+
if ( !scalarDatasetIndex.isValid() )
return 0;
@@ -405,7 +408,6 @@ int QgsMeshDataset3dGeometry::extractDataset( QVector &verticalMagnitude
QgsTriangularMesh triangularMesh = *layer->triangularMesh();
const QgsMesh nativeMesh = *layer->nativeMesh();
-
//extract the scalar dataset used to render vertical magnitude of geometry
//define the vertical magnitude datasetIndex
int verticalDataSetIndexNumber = 0;
diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.h b/src/3d/mesh/qgsmesh3dgeometry_p.h
index 472cb8073f9e..5c60c31db8f4 100644
--- a/src/3d/mesh/qgsmesh3dgeometry_p.h
+++ b/src/3d/mesh/qgsmesh3dgeometry_p.h
@@ -83,6 +83,7 @@ class QgsMeshDataset3dGeometry: public QgsMesh3dGeometry
public:
//! Constructs a mesh layer geometry from triangular mesh.
explicit QgsMeshDataset3dGeometry( QgsMeshLayer *layer,
+ const QgsDateTimeRange &timeRange,
const QgsVector3D &origin,
const QgsMesh3DSymbol &symbol,
QNode *parent );
@@ -96,6 +97,7 @@ class QgsMeshDataset3dGeometry: public QgsMesh3dGeometry
bool mIsVerticalMagnitudeRelative;
int mVerticalGroupDatasetIndex;
+ QgsDateTimeRange mTimeRange;
};
diff --git a/src/3d/mesh/qgsmesh3dmaterial_p.cpp b/src/3d/mesh/qgsmesh3dmaterial_p.cpp
index 0c60cd95a05f..e75fe2f419b5 100644
--- a/src/3d/mesh/qgsmesh3dmaterial_p.cpp
+++ b/src/3d/mesh/qgsmesh3dmaterial_p.cpp
@@ -23,6 +23,7 @@
#include
#include
+#include
#include
#include
#include
@@ -230,7 +231,11 @@ class ArrowsGridTexture: public Qt3DRender::QAbstractTextureImage
};
-QgsMesh3dMaterial::QgsMesh3dMaterial( QgsMeshLayer *layer, const QgsVector3D &origin, const QgsMesh3DSymbol &symbol, MagnitudeType magnitudeType ):
+QgsMesh3dMaterial::QgsMesh3dMaterial( QgsMeshLayer *layer,
+ const QgsDateTimeRange &timeRange,
+ const QgsVector3D &origin,
+ const QgsMesh3DSymbol &symbol,
+ MagnitudeType magnitudeType ):
mSymbol( symbol ),
mMagnitudeType( magnitudeType ),
mOrigin( origin )
@@ -238,7 +243,7 @@ QgsMesh3dMaterial::QgsMesh3dMaterial( QgsMeshLayer *layer, const QgsVector3D &or
Qt3DRender::QEffect *eff = new Qt3DRender::QEffect( this );
configure();
- configureArrows( layer );
+ configureArrows( layer, timeRange );
eff->addTechnique( mTechnique );
setEffect( eff );
@@ -309,12 +314,12 @@ void QgsMesh3dMaterial::configure()
mTechnique->addParameter( new Qt3DRender::QParameter( "isScalarMagnitude", ( mMagnitudeType == QgsMesh3dMaterial::ScalarDataSet ) ) );
}
-void QgsMesh3dMaterial::configureArrows( QgsMeshLayer *layer )
+void QgsMesh3dMaterial::configureArrows( QgsMeshLayer *layer, const QgsDateTimeRange &timeRange )
{
if ( !layer )
return;
- QgsMeshDatasetIndex datasetIndex = layer->rendererSettings().activeVectorDataset();
+ QgsMeshDatasetIndex datasetIndex = layer->activeVectorDatasetAtTime( timeRange );
mTechnique->addParameter( new Qt3DRender::QParameter( "arrowsSpacing", float( mSymbol.arrowsSpacing() ) ) ) ;
QColor arrowsColor = layer->rendererSettings().vectorSettings( datasetIndex.group() ).color();
@@ -326,7 +331,7 @@ void QgsMesh3dMaterial::configureArrows( QgsMeshLayer *layer )
QSize gridSize;
QgsPointXY minCorner;
Qt3DRender::QParameter *arrowsEnabledParameter = new Qt3DRender::QParameter( "arrowsEnabled", false );
- if ( mMagnitudeType != MagnitudeType::ScalarDataSet || !mSymbol.arrowsEnabled() )
+ if ( mMagnitudeType != MagnitudeType::ScalarDataSet || !mSymbol.arrowsEnabled() || meta.isScalar() || !datasetIndex.isValid() )
arrowsEnabledParameter->setValue( false );
else
{
diff --git a/src/3d/mesh/qgsmesh3dmaterial_p.h b/src/3d/mesh/qgsmesh3dmaterial_p.h
index 0ba8226c117a..dda571f06b54 100644
--- a/src/3d/mesh/qgsmesh3dmaterial_p.h
+++ b/src/3d/mesh/qgsmesh3dmaterial_p.h
@@ -66,7 +66,11 @@ class QgsMesh3dMaterial : public Qt3DRender::QMaterial
};
//! Constructor
- QgsMesh3dMaterial( QgsMeshLayer *layer, const QgsVector3D &origin, const QgsMesh3DSymbol &symbol, MagnitudeType magnitudeType = ZValue );
+ QgsMesh3dMaterial( QgsMeshLayer *layer,
+ const QgsDateTimeRange &timeRange,
+ const QgsVector3D &origin,
+ const QgsMesh3DSymbol &symbol,
+ MagnitudeType magnitudeType = ZValue );
private:
QgsMesh3DSymbol mSymbol;
@@ -75,7 +79,7 @@ class QgsMesh3dMaterial : public Qt3DRender::QMaterial
QgsVector3D mOrigin;
void configure();
- void configureArrows( QgsMeshLayer *layer );
+ void configureArrows( QgsMeshLayer *layer, const QgsDateTimeRange &timeRange );
};
///@endcond
diff --git a/src/3d/mesh/qgsmeshterraingenerator.cpp b/src/3d/mesh/qgsmeshterraingenerator.cpp
index ef3afe755811..bea257be8c97 100644
--- a/src/3d/mesh/qgsmeshterraingenerator.cpp
+++ b/src/3d/mesh/qgsmeshterraingenerator.cpp
@@ -60,7 +60,10 @@ QgsChunkLoader *QgsMeshTerrainGenerator::createChunkLoader( QgsChunkNode *node )
return new QgsMeshTerrainTileLoader( mTerrain, node, meshLayer(), symbol() );
}
-float QgsMeshTerrainGenerator::rootChunkError( const Qgs3DMapSettings &map ) const {Q_UNUSED( map ); return 0;}
+float QgsMeshTerrainGenerator::rootChunkError( const Qgs3DMapSettings & ) const
+{
+ return 0;
+}
void QgsMeshTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) const
{
@@ -77,7 +80,7 @@ void QgsMeshTerrainGenerator::rootChunkHeightRange( float &hMin, float &hMax ) c
for ( int i = 0; i < triangularMesh->vertices().count(); ++i )
{
- float zValue = triangularMesh->vertices().at( i ).z();
+ float zValue = static_cast< float >( triangularMesh->vertices().at( i ).z() );
if ( min > zValue )
min = zValue;
if ( max < zValue )
@@ -131,7 +134,7 @@ QgsRectangle QgsMeshTerrainGenerator::extent() const
{
extentInMap = terrainToMapTransform.transform( mLayer->extent() );
}
- catch ( QgsCsException &e )
+ catch ( QgsCsException & )
{
extentInMap = mLayer->extent();
}
diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp
index 52c7d7f12551..3869730a3d30 100644
--- a/src/3d/qgs3dmapscene.cpp
+++ b/src/3d/qgs3dmapscene.cpp
@@ -538,6 +538,18 @@ void Qgs3DMapScene::onLayersChanged()
}
}
+void Qgs3DMapScene::updateTemporal()
+{
+ for ( auto layer : mLayerEntities.keys() )
+ {
+ if ( layer->temporalProperties()->isActive() )
+ {
+ removeLayerEntity( layer );
+ addLayerEntity( layer );
+ }
+ }
+}
+
void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
{
bool needsSceneUpdate = false;
diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h
index e08bbcd2e80f..4edfc3de5cff 100644
--- a/src/3d/qgs3dmapscene.h
+++ b/src/3d/qgs3dmapscene.h
@@ -114,6 +114,10 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
//! Emitted when the scene's state has changed
void sceneStateChanged();
+ public slots:
+ //! Updates the temporale entities
+ void updateTemporal();
+
private slots:
void onCameraChanged();
void onFrameTriggered( float dt );
diff --git a/src/3d/qgs3dmapsettings.cpp b/src/3d/qgs3dmapsettings.cpp
index 74f597903854..299f16ef34fc 100644
--- a/src/3d/qgs3dmapsettings.cpp
+++ b/src/3d/qgs3dmapsettings.cpp
@@ -31,6 +31,7 @@
Qgs3DMapSettings::Qgs3DMapSettings( const Qgs3DMapSettings &other )
: QObject( nullptr )
+ , QgsTemporalRangeObject( other )
, mOrigin( other.mOrigin )
, mCrs( other.mCrs )
, mBackgroundColor( other.mBackgroundColor )
@@ -197,6 +198,11 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
mShowTerrainBoundingBoxes = elemDebug.attribute( QStringLiteral( "bounding-boxes" ), QStringLiteral( "0" ) ).toInt();
mShowTerrainTileInfo = elemDebug.attribute( QStringLiteral( "terrain-tile-info" ), QStringLiteral( "0" ) ).toInt();
mShowCameraViewCenter = elemDebug.attribute( QStringLiteral( "camera-view-center" ), QStringLiteral( "0" ) ).toInt();
+
+ QDomElement elemTemporalRange = elem.firstChildElement( QStringLiteral( "temporal-range" ) );
+ QDateTime start = QDateTime::fromString( elemTemporalRange.attribute( QStringLiteral( "start" ) ), Qt::ISODate );
+ QDateTime end = QDateTime::fromString( elemTemporalRange.attribute( QStringLiteral( "end" ) ), Qt::ISODate );
+ setTemporalRange( QgsDateTimeRange( start, end ) );
}
QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
@@ -279,6 +285,10 @@ QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteCon
elemDebug.setAttribute( QStringLiteral( "camera-view-center" ), mShowCameraViewCenter ? 1 : 0 );
elem.appendChild( elemDebug );
+ QDomElement elemTemporalRange = doc.createElement( QStringLiteral( "temporal-range" ) );
+ elemTemporalRange.setAttribute( QStringLiteral( "start" ), temporalRange().begin().toString( Qt::ISODate ) );
+ elemTemporalRange.setAttribute( QStringLiteral( "end" ), temporalRange().end().toString( Qt::ISODate ) );
+
return elem;
}
diff --git a/src/3d/qgs3dmapsettings.h b/src/3d/qgs3dmapsettings.h
index f16b0a201fb7..955a5210dc77 100644
--- a/src/3d/qgs3dmapsettings.h
+++ b/src/3d/qgs3dmapsettings.h
@@ -48,7 +48,7 @@ class QDomElement;
*
* \since QGIS 3.0
*/
-class _3D_EXPORT Qgs3DMapSettings : public QObject
+class _3D_EXPORT Qgs3DMapSettings : public QObject, public QgsTemporalRangeObject
{
Q_OBJECT
public:
diff --git a/src/3d/qgsmeshlayer3drenderer.cpp b/src/3d/qgsmeshlayer3drenderer.cpp
index f5216002b082..3984296ec0f4 100644
--- a/src/3d/qgsmeshlayer3drenderer.cpp
+++ b/src/3d/qgsmeshlayer3drenderer.cpp
@@ -77,7 +77,8 @@ Qt3DCore::QEntity *QgsMeshLayer3DRenderer::createEntity( const Qgs3DMapSettings
if ( !meshLayer || !meshLayer->dataProvider() )
return nullptr;
- if ( meshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) )
+ if ( meshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) ||
+ !mSymbol->isEnabled() )
{
// 3D not implemented for 1D meshes
return nullptr;
diff --git a/src/3d/symbols/qgsmesh3dsymbol.cpp b/src/3d/symbols/qgsmesh3dsymbol.cpp
index c053c3bf4fe1..c793dac86272 100644
--- a/src/3d/symbols/qgsmesh3dsymbol.cpp
+++ b/src/3d/symbols/qgsmesh3dsymbol.cpp
@@ -41,6 +41,7 @@ void QgsMesh3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext &co
//Advanced symbol
QDomElement elemAdvancedSettings = doc.createElement( QStringLiteral( "advanced-settings" ) );
+ elemAdvancedSettings.setAttribute( QStringLiteral( "renderer-3d-enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
elemAdvancedSettings.setAttribute( QStringLiteral( "smoothed-triangle" ), mSmoothedTriangles ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
elemAdvancedSettings.setAttribute( QStringLiteral( "wireframe-enabled" ), mWireframeEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
elemAdvancedSettings.setAttribute( QStringLiteral( "wireframe-line-width" ), mWireframeLineWidth );
@@ -78,6 +79,7 @@ void QgsMesh3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteContex
//Advanced symbol
QDomElement elemAdvancedSettings = elem.firstChildElement( QStringLiteral( "advanced-settings" ) );
+ mEnabled = elemAdvancedSettings.attribute( QStringLiteral( "renderer-3d-enabled" ) ).toInt();
mSmoothedTriangles = elemAdvancedSettings.attribute( QStringLiteral( "smoothed-triangle" ) ).toInt();
mWireframeEnabled = elemAdvancedSettings.attribute( QStringLiteral( "wireframe-enabled" ) ).toInt();
mWireframeLineWidth = elemAdvancedSettings.attribute( QStringLiteral( "wireframe-line-width" ) ).toDouble();
@@ -237,3 +239,13 @@ void QgsMesh3DSymbol::setArrowsFixedSize( bool arrowsFixeSize )
{
mArrowsFixedSize = arrowsFixeSize;
}
+
+bool QgsMesh3DSymbol::isEnabled() const
+{
+ return mEnabled;
+}
+
+void QgsMesh3DSymbol::setEnabled( bool enabled )
+{
+ mEnabled = enabled;
+}
diff --git a/src/3d/symbols/qgsmesh3dsymbol.h b/src/3d/symbols/qgsmesh3dsymbol.h
index 6364bd7e1d37..06c5da715c28 100644
--- a/src/3d/symbols/qgsmesh3dsymbol.h
+++ b/src/3d/symbols/qgsmesh3dsymbol.h
@@ -76,6 +76,20 @@ class _3D_EXPORT QgsMesh3DSymbol : public QgsAbstract3DSymbol
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const override;
void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override;
+ /**
+ * Returns if the 3d rendering is enabled
+ *
+ * \since QGIS 3.14
+ */
+ bool isEnabled() const;
+
+ /**
+ * Sets if the 3d rendering is enabled
+ *
+ * \since QGIS 3.14
+ */
+ void setEnabled( bool enabled );
+
//! Returns method that determines altitude (whether to clamp to feature to terrain)
Qgs3DTypes::AltitudeClamping altitudeClamping() const { return mAltClamping; }
//! Sets method that determines altitude (whether to clamp to feature to terrain)
@@ -309,6 +323,8 @@ class _3D_EXPORT QgsMesh3DSymbol : public QgsAbstract3DSymbol
QgsPhongMaterialSettings mMaterial; //!< Defines appearance of objects
bool mAddBackFaces = false;
+ bool mEnabled = true;
+
//! Triangles settings
bool mSmoothedTriangles = false;
bool mWireframeEnabled = false;
diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt
index 0e781d198c18..6aab06b8da4c 100644
--- a/src/analysis/CMakeLists.txt
+++ b/src/analysis/CMakeLists.txt
@@ -66,6 +66,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmfiledownloader.cpp
processing/qgsalgorithmfillnodata.cpp
processing/qgsalgorithmfilter.cpp
+ processing/qgsalgorithmfilterbygeometry.cpp
processing/qgsalgorithmfiltervertices.cpp
processing/qgsalgorithmfixgeometries.cpp
processing/qgsalgorithmforcerhr.cpp
@@ -111,6 +112,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmraiseexception.cpp
processing/qgsalgorithmrandomextract.cpp
processing/qgsalgorithmrandompointsextent.cpp
+ processing/qgsalgorithmrandompointsonlines.cpp
processing/qgsalgorithmrasterlayeruniquevalues.cpp
processing/qgsalgorithmrasterlogicalop.cpp
processing/qgsalgorithmrasterize.cpp
@@ -128,7 +130,9 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmrepairshapefile.cpp
processing/qgsalgorithmreverselinedirection.cpp
processing/qgsalgorithmrotate.cpp
+ processing/qgsalgorithmroundrastervalues.cpp
processing/qgsalgorithmruggedness.cpp
+ processing/qgsalgorithmsavelog.cpp
processing/qgsalgorithmsaveselectedfeatures.cpp
processing/qgsalgorithmsegmentize.cpp
processing/qgsalgorithmserviceareafromlayer.cpp
diff --git a/src/analysis/mesh/qgsmeshcalcutils.cpp b/src/analysis/mesh/qgsmeshcalcutils.cpp
index 2a2f6e244ca5..87783de4dd9c 100644
--- a/src/analysis/mesh/qgsmeshcalcutils.cpp
+++ b/src/analysis/mesh/qgsmeshcalcutils.cpp
@@ -107,7 +107,7 @@ std::shared_ptr QgsMeshCalcUtils::create( const QStri
mMeshLayer->nativeMesh(),
mMeshLayer->triangularMesh(),
nullptr,
- mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataInterpolationMethod()
+ mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataResamplingMethod()
);
Q_ASSERT( dataX.size() == resultCount );
QVector dataY =
@@ -116,7 +116,7 @@ std::shared_ptr QgsMeshCalcUtils::create( const QStri
mMeshLayer->nativeMesh(),
mMeshLayer->triangularMesh(),
nullptr,
- mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataInterpolationMethod()
+ mMeshLayer->rendererSettings().scalarSettings( groupIndex ).dataResamplingMethod()
);
Q_ASSERT( dataY.size() == resultCount );
diff --git a/src/analysis/mesh/qgsmeshcontours.cpp b/src/analysis/mesh/qgsmeshcontours.cpp
index d71408d499f2..ad4129602a03 100644
--- a/src/analysis/mesh/qgsmeshcontours.cpp
+++ b/src/analysis/mesh/qgsmeshcontours.cpp
@@ -58,7 +58,7 @@ QgsGeometry QgsMeshContours::exportPolygons(
const QgsMeshDatasetIndex &index,
double min_value,
double max_value,
- QgsMeshRendererScalarSettings::DataInterpolationMethod method,
+ QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback
)
{
@@ -254,7 +254,7 @@ QgsGeometry QgsMeshContours::exportPolygons(
QgsGeometry QgsMeshContours::exportLines( const QgsMeshDatasetIndex &index,
double value,
- QgsMeshRendererScalarSettings::DataInterpolationMethod method,
+ QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback )
{
// Check if the layer/mesh is valid
@@ -380,7 +380,7 @@ QgsGeometry QgsMeshContours::exportLines( const QgsMeshDatasetIndex &index,
}
}
-void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataInterpolationMethod method )
+void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataResamplingMethod method )
{
if ( mCachedIndex != index )
{
diff --git a/src/analysis/mesh/qgsmeshcontours.h b/src/analysis/mesh/qgsmeshcontours.h
index c231104f3c36..e1d9031c1a63 100644
--- a/src/analysis/mesh/qgsmeshcontours.h
+++ b/src/analysis/mesh/qgsmeshcontours.h
@@ -69,7 +69,7 @@ class ANALYSIS_EXPORT QgsMeshContours
*/
QgsGeometry exportLines( const QgsMeshDatasetIndex &index,
double value,
- QgsMeshRendererScalarSettings::DataInterpolationMethod method,
+ QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = nullptr );
/**
@@ -84,13 +84,13 @@ class ANALYSIS_EXPORT QgsMeshContours
QgsGeometry exportPolygons( const QgsMeshDatasetIndex &index,
double min_value,
double max_value,
- QgsMeshRendererScalarSettings::DataInterpolationMethod method,
+ QgsMeshRendererScalarSettings::DataResamplingMethod method,
QgsFeedback *feedback = nullptr );
private:
void populateCache(
const QgsMeshDatasetIndex &index,
- QgsMeshRendererScalarSettings::DataInterpolationMethod method );
+ QgsMeshRendererScalarSettings::DataResamplingMethod method );
QgsMeshLayer *mMeshLayer = nullptr;
diff --git a/src/analysis/processing/qgsalgorithmbuffer.cpp b/src/analysis/processing/qgsalgorithmbuffer.cpp
index 079146bfb089..abed3531b2ae 100644
--- a/src/analysis/processing/qgsalgorithmbuffer.cpp
+++ b/src/analysis/processing/qgsalgorithmbuffer.cpp
@@ -62,7 +62,7 @@ void QgsBufferAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MITER_LIMIT" ), QObject::tr( "Miter limit" ), QgsProcessingParameterNumber::Double, 2, false, 1 ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISSOLVE" ), QObject::tr( "Dissolve result" ), false ) );
- addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Buffered" ), QgsProcessing::TypeVectorPolygon ) );
+ addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Buffered" ), QgsProcessing::TypeVectorPolygon, QVariant(), false, true, true ) );
}
QString QgsBufferAlgorithm::shortHelpString() const
@@ -179,6 +179,36 @@ QgsProcessingAlgorithm::Flags QgsBufferAlgorithm::flags() const
return f;
}
+QgsProcessingAlgorithm::VectorProperties QgsBufferAlgorithm::sinkProperties( const QString &sink, const QVariantMap ¶meters, QgsProcessingContext &context, const QMap &sourceProperties ) const
+{
+ QgsProcessingAlgorithm::VectorProperties result;
+ if ( sink == QStringLiteral( "OUTPUT" ) )
+ {
+ if ( sourceProperties.value( QStringLiteral( "INPUT" ) ).availability == QgsProcessingAlgorithm::Available )
+ {
+ const VectorProperties inputProps = sourceProperties.value( QStringLiteral( "INPUT" ) );
+ result.fields = inputProps.fields;
+ result.crs = inputProps.crs;
+ result.wkbType = QgsWkbTypes::MultiPolygon;
+ result.availability = Available;
+ return result;
+ }
+ else
+ {
+ std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
+ if ( source )
+ {
+ result.fields = source->fields();
+ result.crs = source->sourceCrs();
+ result.wkbType = QgsWkbTypes::MultiPolygon;
+ result.availability = Available;
+ return result;
+ }
+ }
+ }
+ return result;
+}
+
bool QgsBufferAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
{
const QgsVectorLayer *vlayer = qobject_cast< const QgsVectorLayer * >( layer );
diff --git a/src/analysis/processing/qgsalgorithmbuffer.h b/src/analysis/processing/qgsalgorithmbuffer.h
index a68f641b3d5f..eeead26c0518 100644
--- a/src/analysis/processing/qgsalgorithmbuffer.h
+++ b/src/analysis/processing/qgsalgorithmbuffer.h
@@ -48,6 +48,10 @@ class QgsBufferAlgorithm : public QgsProcessingAlgorithm
bool supportInPlaceEdit( const QgsMapLayer *layer ) const override;
QgsProcessingAlgorithm::Flags flags() const override;
+ QgsProcessingAlgorithm::VectorProperties sinkProperties( const QString &sink,
+ const QVariantMap ¶meters,
+ QgsProcessingContext &context,
+ const QMap< QString, QgsProcessingAlgorithm::VectorProperties > &sourceProperties ) const override;
protected:
diff --git a/src/analysis/processing/qgsalgorithmdetectdatasetchanges.cpp b/src/analysis/processing/qgsalgorithmdetectdatasetchanges.cpp
index 6706acf75ed2..a3092a028005 100644
--- a/src/analysis/processing/qgsalgorithmdetectdatasetchanges.cpp
+++ b/src/analysis/processing/qgsalgorithmdetectdatasetchanges.cpp
@@ -69,7 +69,7 @@ void QgsDetectVectorChangesAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "ADDED" ), QObject::tr( "Added features" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, true ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "DELETED" ), QObject::tr( "Deleted features" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, true ) );
- addOutput( new QgsProcessingOutputNumber( QStringLiteral( "UNCHAGED_COUNT" ), QObject::tr( "Count of unchanged features" ) ) );
+ addOutput( new QgsProcessingOutputNumber( QStringLiteral( "UNCHANGED_COUNT" ), QObject::tr( "Count of unchanged features" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "ADDED_COUNT" ), QObject::tr( "Count of features added in revised layer" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "DELETED_COUNT" ), QObject::tr( "Count of features deleted from original layer" ) ) );
}
diff --git a/src/analysis/processing/qgsalgorithmfilterbygeometry.cpp b/src/analysis/processing/qgsalgorithmfilterbygeometry.cpp
new file mode 100644
index 000000000000..52e8f101ea30
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmfilterbygeometry.cpp
@@ -0,0 +1,314 @@
+/***************************************************************************
+ qgsalgorithmfilterbygeometry.cpp
+ ---------------------
+ begin : March 2020
+ copyright : (C) 2020 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 "qgsalgorithmfilterbygeometry.h"
+
+///@cond PRIVATE
+
+QString QgsFilterByGeometryAlgorithm::name() const
+{
+ return QStringLiteral( "filterbygeometry" );
+}
+
+QString QgsFilterByGeometryAlgorithm::displayName() const
+{
+ return QObject::tr( "Filter by geometry type" );
+}
+
+QStringList QgsFilterByGeometryAlgorithm::tags() const
+{
+ return QObject::tr( "extract,filter,geometry,linestring,point,polygon" ).split( ',' );
+}
+
+QString QgsFilterByGeometryAlgorithm::group() const
+{
+ return QObject::tr( "Vector selection" );
+}
+
+QString QgsFilterByGeometryAlgorithm::groupId() const
+{
+ return QStringLiteral( "vectorselection" );
+}
+
+QgsProcessingAlgorithm::Flags QgsFilterByGeometryAlgorithm::flags() const
+{
+ Flags f = QgsProcessingAlgorithm::flags();
+ f |= QgsProcessingAlgorithm::FlagHideFromToolbox;
+ return f;
+}
+
+void QgsFilterByGeometryAlgorithm::initAlgorithm( const QVariantMap & )
+{
+ addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ),
+ QList< int >() << QgsProcessing::TypeVector ) );
+
+ addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "POINTS" ), QObject::tr( "Point features" ),
+ QgsProcessing::TypeVectorPoint, QVariant(), true, true ) );
+
+ addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "LINES" ), QObject::tr( "Line features" ),
+ QgsProcessing::TypeVectorLine, QVariant(), true, true ) );
+
+ addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "POLYGONS" ), QObject::tr( "Polygon features" ),
+ QgsProcessing::TypeVectorPolygon, QVariant(), true, true ) );
+
+ addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "NO_GEOMETRY" ), QObject::tr( "Features with no geometry" ),
+ QgsProcessing::TypeVector, QVariant(), true, true ) );
+
+ addOutput( new QgsProcessingOutputNumber( QStringLiteral( "POINT_COUNT" ), QObject::tr( "Total count of point features" ) ) );
+ addOutput( new QgsProcessingOutputNumber( QStringLiteral( "LINE_COUNT" ), QObject::tr( "Total count of line features" ) ) );
+ addOutput( new QgsProcessingOutputNumber( QStringLiteral( "POLYGON_COUNT" ), QObject::tr( "Total count of polygon features" ) ) );
+ addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NO_GEOMETRY_COUNT" ), QObject::tr( "Total count of features without geometry" ) ) );
+}
+
+QString QgsFilterByGeometryAlgorithm::shortHelpString() const
+{
+ return QObject::tr( "This algorithm filters features by their geometry type. Incoming features will be directed to different "
+ "outputs based on whether they have a point, line or polygon geometry." );
+}
+
+QString QgsFilterByGeometryAlgorithm::shortDescription() const
+{
+ return QObject::tr( "Filters features by geometry type" );
+}
+
+QgsFilterByGeometryAlgorithm *QgsFilterByGeometryAlgorithm::createInstance() const
+{
+ return new QgsFilterByGeometryAlgorithm();
+}
+
+QVariantMap QgsFilterByGeometryAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
+{
+ std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
+ if ( !source )
+ throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
+
+ const bool hasM = QgsWkbTypes::hasM( source->wkbType() );
+ const bool hasZ = QgsWkbTypes::hasZ( source->wkbType() );
+
+ QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
+ QgsWkbTypes::Type lineType = QgsWkbTypes::LineString;
+ QgsWkbTypes::Type polygonType = QgsWkbTypes::Polygon;
+ if ( hasM )
+ {
+ pointType = QgsWkbTypes::addM( pointType );
+ lineType = QgsWkbTypes::addM( lineType );
+ polygonType = QgsWkbTypes::addM( polygonType );
+ }
+ if ( hasZ )
+ {
+ pointType = QgsWkbTypes::addZ( pointType );
+ lineType = QgsWkbTypes::addZ( lineType );
+ polygonType = QgsWkbTypes::addZ( polygonType );
+ }
+
+ QString pointSinkId;
+ std::unique_ptr< QgsFeatureSink > pointSink( parameterAsSink( parameters, QStringLiteral( "POINTS" ), context, pointSinkId, source->fields(),
+ pointType, source->sourceCrs() ) );
+ if ( parameters.value( QStringLiteral( "POINTS" ), QVariant() ).isValid() && !pointSink )
+ throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "POINTS" ) ) );
+
+ QString lineSinkId;
+ std::unique_ptr< QgsFeatureSink > lineSink( parameterAsSink( parameters, QStringLiteral( "LINES" ), context, lineSinkId, source->fields(),
+ lineType, source->sourceCrs() ) );
+ if ( parameters.value( QStringLiteral( "LINES" ), QVariant() ).isValid() && !lineSink )
+ throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "LINES" ) ) );
+
+ QString polygonSinkId;
+ std::unique_ptr< QgsFeatureSink > polygonSink( parameterAsSink( parameters, QStringLiteral( "POLYGONS" ), context, polygonSinkId, source->fields(),
+ polygonType, source->sourceCrs() ) );
+ if ( parameters.value( QStringLiteral( "POLYGONS" ), QVariant() ).isValid() && !polygonSink )
+ throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "POLYGONS" ) ) );
+
+ QString noGeomSinkId;
+ std::unique_ptr< QgsFeatureSink > noGeomSink( parameterAsSink( parameters, QStringLiteral( "NO_GEOMETRY" ), context, noGeomSinkId, source->fields(),
+ QgsWkbTypes::NoGeometry ) );
+ if ( parameters.value( QStringLiteral( "NO_GEOMETRY" ), QVariant() ).isValid() && !noGeomSink )
+ throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NO_GEOMETRY" ) ) );
+
+ long count = source->featureCount();
+ long long pointCount = 0;
+ long long lineCount = 0;
+ long long polygonCount = 0;
+ long long nullCount = 0;
+
+ double step = count > 0 ? 100.0 / count : 1;
+ int current = 0;
+
+ QgsFeatureIterator it = source->getFeatures();
+ QgsFeature f;
+ while ( it.nextFeature( f ) )
+ {
+ if ( feedback->isCanceled() )
+ {
+ break;
+ }
+
+ if ( f.hasGeometry() )
+ {
+ switch ( f.geometry().type() )
+ {
+ case QgsWkbTypes::PointGeometry:
+ if ( pointSink )
+ {
+ pointSink->addFeature( f, QgsFeatureSink::FastInsert );
+ }
+ pointCount++;
+ break;
+ case QgsWkbTypes::LineGeometry:
+ if ( lineSink )
+ {
+ lineSink->addFeature( f, QgsFeatureSink::FastInsert );
+ }
+ lineCount++;
+ break;
+ case QgsWkbTypes::PolygonGeometry:
+ if ( polygonSink )
+ {
+ polygonSink->addFeature( f, QgsFeatureSink::FastInsert );
+ }
+ polygonCount++;
+ break;
+ case QgsWkbTypes::NullGeometry:
+ case QgsWkbTypes::UnknownGeometry:
+ break;
+ }
+ }
+ else
+ {
+ if ( noGeomSink )
+ {
+ noGeomSink->addFeature( f, QgsFeatureSink::FastInsert );
+ }
+ nullCount++;
+ }
+
+ feedback->setProgress( current * step );
+ current++;
+ }
+
+ QVariantMap outputs;
+
+ if ( pointSink )
+ outputs.insert( QStringLiteral( "POINTS" ), pointSinkId );
+ if ( lineSink )
+ outputs.insert( QStringLiteral( "LINES" ), lineSinkId );
+ if ( polygonSink )
+ outputs.insert( QStringLiteral( "POLYGONS" ), polygonSinkId );
+ if ( noGeomSink )
+ outputs.insert( QStringLiteral( "NO_GEOMETRY" ), noGeomSinkId );
+
+ outputs.insert( QStringLiteral( "POINT_COUNT" ), pointCount );
+ outputs.insert( QStringLiteral( "LINE_COUNT" ), lineCount );
+ outputs.insert( QStringLiteral( "POLYGON_COUNT" ), polygonCount );
+ outputs.insert( QStringLiteral( "NO_GEOMETRY_COUNT" ), nullCount );
+
+ return outputs;
+}
+
+
+
+//
+// QgsFilterByLayerTypeAlgorithm
+//
+
+QString QgsFilterByLayerTypeAlgorithm::name() const
+{
+ return QStringLiteral( "filterlayersbytype" );
+}
+
+QString QgsFilterByLayerTypeAlgorithm::displayName() const
+{
+ return QObject::tr( "Filter layers by type" );
+}
+
+QStringList QgsFilterByLayerTypeAlgorithm::tags() const
+{
+ return QObject::tr( "filter,vector,raster,select" ).split( ',' );
+}
+
+QString QgsFilterByLayerTypeAlgorithm::group() const
+{
+ return QObject::tr( "Layer tools" );
+}
+
+QString QgsFilterByLayerTypeAlgorithm::groupId() const
+{
+ return QStringLiteral( "layertools" );
+}
+
+QgsProcessingAlgorithm::Flags QgsFilterByLayerTypeAlgorithm::flags() const
+{
+ Flags f = QgsProcessingAlgorithm::flags();
+ f |= FlagHideFromToolbox | FlagPruneModelBranchesBasedOnAlgorithmResults;
+ return f;
+}
+
+void QgsFilterByLayerTypeAlgorithm::initAlgorithm( const QVariantMap & )
+{
+ addParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
+
+ addParameter( new QgsProcessingParameterVectorDestination( QStringLiteral( "VECTOR" ), QObject::tr( "Vector features" ),
+ QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, false ) );
+
+ addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "RASTER" ), QObject::tr( "Raster layer" ), QVariant(), true, false ) );
+}
+
+QString QgsFilterByLayerTypeAlgorithm::shortHelpString() const
+{
+ return QObject::tr( "This algorithm filters layer by their type. Incoming layers will be directed to different "
+ "outputs based on whether they are a vector or raster layer." );
+}
+
+QString QgsFilterByLayerTypeAlgorithm::shortDescription() const
+{
+ return QObject::tr( "Filters layers by type" );
+}
+
+QgsFilterByLayerTypeAlgorithm *QgsFilterByLayerTypeAlgorithm::createInstance() const
+{
+ return new QgsFilterByLayerTypeAlgorithm();
+}
+
+QVariantMap QgsFilterByLayerTypeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
+{
+ const QgsMapLayer *layer = parameterAsLayer( parameters, QStringLiteral( "INPUT" ), context );
+ if ( !layer )
+ throw QgsProcessingException( QObject::tr( "Could not load input layer" ) );
+
+ QVariantMap outputs;
+
+ switch ( layer->type() )
+ {
+ case QgsMapLayerType::VectorLayer:
+ outputs.insert( QStringLiteral( "VECTOR" ), parameters.value( QStringLiteral( "INPUT" ) ) );
+ break;
+
+ case QgsMapLayerType::RasterLayer:
+ outputs.insert( QStringLiteral( "RASTER" ), parameters.value( QStringLiteral( "INPUT" ) ) );
+ break;
+
+ case QgsMapLayerType::PluginLayer:
+ case QgsMapLayerType::MeshLayer:
+ case QgsMapLayerType::VectorTileLayer:
+ break;
+ }
+
+ return outputs;
+}
+
+///@endcond
+
diff --git a/src/analysis/processing/qgsalgorithmfilterbygeometry.h b/src/analysis/processing/qgsalgorithmfilterbygeometry.h
new file mode 100644
index 000000000000..7c45c36be94f
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmfilterbygeometry.h
@@ -0,0 +1,87 @@
+/***************************************************************************
+ qgsalgorithmfilterbygeometry.h
+ ---------------------
+ begin : March 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGSALGORITHMFILTERBYGEOMETRY_H
+#define QGSALGORITHMFILTERBYGEOMETRY_H
+
+#define SIP_NO_FILE
+
+#include "qgis_sip.h"
+#include "qgsprocessingalgorithm.h"
+
+///@cond PRIVATE
+
+/**
+ * Native filter by geometry type algorithm.
+ */
+class QgsFilterByGeometryAlgorithm : public QgsProcessingAlgorithm
+{
+
+ public:
+
+ QgsFilterByGeometryAlgorithm() = default;
+ Flags flags() const override;
+ void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
+ QString name() const override;
+ QString displayName() const override;
+ QStringList tags() const override;
+ QString group() const override;
+ QString groupId() const override;
+ QString shortHelpString() const override;
+ QString shortDescription() const override;
+ QgsFilterByGeometryAlgorithm *createInstance() const override SIP_FACTORY;
+
+ protected:
+
+ QVariantMap processAlgorithm( const QVariantMap ¶meters,
+ QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
+
+};
+
+
+/**
+ * Native filter by layer type algorithm.
+ */
+class QgsFilterByLayerTypeAlgorithm : public QgsProcessingAlgorithm
+{
+
+ public:
+
+ QgsFilterByLayerTypeAlgorithm() = default;
+ Flags flags() const override;
+ void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
+ QString name() const override;
+ QString displayName() const override;
+ QStringList tags() const override;
+ QString group() const override;
+ QString groupId() const override;
+ QString shortHelpString() const override;
+ QString shortDescription() const override;
+ QgsFilterByLayerTypeAlgorithm *createInstance() const override SIP_FACTORY;
+
+ protected:
+
+ QVariantMap processAlgorithm( const QVariantMap ¶meters,
+ QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
+
+};
+
+///@endcond PRIVATE
+
+#endif // QGSALGORITHMFILTERBYGEOMETRY_H
+
+
diff --git a/src/analysis/processing/qgsalgorithmpackage.cpp b/src/analysis/processing/qgsalgorithmpackage.cpp
index f7d214385b4a..1461a494dde8 100644
--- a/src/analysis/processing/qgsalgorithmpackage.cpp
+++ b/src/analysis/processing/qgsalgorithmpackage.cpp
@@ -177,6 +177,12 @@ QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap ¶meters
feedback->pushDebugInfo( QObject::tr( "Packaging mesh layers is not supported." ) );
errored = true;
break;
+
+ case QgsMapLayerType::VectorTileLayer:
+ //not supported
+ feedback->pushDebugInfo( QObject::tr( "Packaging vector tile layers is not supported." ) );
+ errored = true;
+ break;
}
}
diff --git a/src/analysis/processing/qgsalgorithmrandompointsonlines.cpp b/src/analysis/processing/qgsalgorithmrandompointsonlines.cpp
new file mode 100644
index 000000000000..a8fdad83cbe5
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmrandompointsonlines.cpp
@@ -0,0 +1,337 @@
+/***************************************************************************
+ qgsalgorithmrandompointsonlines.cpp
+ ---------------------
+ begin : March 2020
+ copyright : (C) 2020 by Håvard Tveite
+ email : havard dot tveite at nmbu dot no
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "qgsalgorithmrandompointsonlines.h"
+#include "random"
+
+// The algorithm parameter names:
+static const QString INPUT = QStringLiteral( "INPUT" );
+static const QString POINTS_NUMBER = QStringLiteral( "POINTS_NUMBER" );
+static const QString MIN_DISTANCE = QStringLiteral( "MIN_DISTANCE" );
+static const QString MAX_TRIES_PER_POINT = QStringLiteral( "MAX_TRIES_PER_POINT" );
+static const QString SEED = QStringLiteral( "SEED" );
+static const QString INCLUDE_LINE_ATTRIBUTES = QStringLiteral( "INCLUDE_LINE_ATTRIBUTES" );
+static const QString OUTPUT = QStringLiteral( "OUTPUT" );
+static const QString OUTPUT_POINTS = QStringLiteral( "OUTPUT_POINTS" );
+static const QString POINTS_MISSED = QStringLiteral( "POINTS_MISSED" );
+static const QString LINES_WITH_MISSED_POINTS = QStringLiteral( "LINES_WITH_MISSED_POINTS" );
+static const QString FEATURES_WITH_EMPTY_OR_NO_GEOMETRY = QStringLiteral( "FEATURES_WITH_EMPTY_OR_NO_GEOMETRY" );
+
+///@cond PRIVATE
+
+QString QgsRandomPointsOnLinesAlgorithm::name() const
+{
+ return QStringLiteral( "randompointsonlines" );
+}
+
+QString QgsRandomPointsOnLinesAlgorithm::displayName() const
+{
+ return QObject::tr( "Random points on lines" );
+}
+
+QStringList QgsRandomPointsOnLinesAlgorithm::tags() const
+{
+ return QObject::tr( "seed,attributes,create" ).split( ',' );
+}
+
+QString QgsRandomPointsOnLinesAlgorithm::group() const
+{
+ return QObject::tr( "Vector creation" );
+}
+
+QString QgsRandomPointsOnLinesAlgorithm::groupId() const
+{
+ return QStringLiteral( "vectorcreation" );
+}
+
+void QgsRandomPointsOnLinesAlgorithm::initAlgorithm( const QVariantMap & )
+{
+ addParameter( new QgsProcessingParameterFeatureSource( INPUT, QObject::tr( "Input line layer" ), QList< int >() << QgsProcessing::TypeVectorLine ) );
+ std::unique_ptr< QgsProcessingParameterNumber > numberPointsParam = qgis::make_unique< QgsProcessingParameterNumber >( POINTS_NUMBER, QObject::tr( "Number of points for each feature" ), QgsProcessingParameterNumber::Integer, 1, false, 1 );
+ numberPointsParam->setIsDynamic( true );
+ numberPointsParam->setDynamicPropertyDefinition( QgsPropertyDefinition( POINTS_NUMBER, QObject::tr( "Number of points for each feature" ), QgsPropertyDefinition::IntegerPositive ) );
+ numberPointsParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
+ addParameter( numberPointsParam.release() );
+
+ std::unique_ptr< QgsProcessingParameterDistance > minDistParam = qgis::make_unique< QgsProcessingParameterDistance >( MIN_DISTANCE, QObject::tr( "Minimum distance between points" ), 0, INPUT, true, 0 );
+ minDistParam->setIsDynamic( true );
+ minDistParam->setDynamicPropertyDefinition( QgsPropertyDefinition( MIN_DISTANCE, QObject::tr( "Minimum distance between points" ), QgsPropertyDefinition::DoublePositive ) );
+ minDistParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
+ addParameter( minDistParam.release() );
+
+ std::unique_ptr< QgsProcessingParameterNumber > maxAttemptsParam = qgis::make_unique< QgsProcessingParameterNumber >( MAX_TRIES_PER_POINT, QObject::tr( "Maximum number of search attempts (for Min. dist. > 0)" ), QgsProcessingParameterNumber::Integer, 10, true, 1 );
+ maxAttemptsParam->setFlags( maxAttemptsParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
+ maxAttemptsParam->setIsDynamic( true );
+ maxAttemptsParam->setDynamicPropertyDefinition( QgsPropertyDefinition( MAX_TRIES_PER_POINT, QObject::tr( "Maximum number of search attempts (for Min. dist. > 0)" ), QgsPropertyDefinition::IntegerPositiveGreaterZero ) );
+ maxAttemptsParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
+ addParameter( maxAttemptsParam.release() );
+
+ std::unique_ptr< QgsProcessingParameterNumber > randomSeedParam = qgis::make_unique< QgsProcessingParameterNumber >( SEED, QObject::tr( "Random seed" ), QgsProcessingParameterNumber::Integer, QVariant(), true, 1 );
+ randomSeedParam->setFlags( randomSeedParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
+ addParameter( randomSeedParam.release() );
+
+ std::unique_ptr< QgsProcessingParameterBoolean > includeLineAttrParam = qgis::make_unique< QgsProcessingParameterBoolean >( INCLUDE_LINE_ATTRIBUTES, QObject::tr( "Include line attributes" ), true );
+ includeLineAttrParam->setFlags( includeLineAttrParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
+ addParameter( includeLineAttrParam.release() );
+
+ addParameter( new
+ QgsProcessingParameterFeatureSink( OUTPUT, QObject::tr( "Random points on lines" ), QgsProcessing::TypeVectorPoint ) );
+
+ addOutput( new QgsProcessingOutputNumber( OUTPUT_POINTS, QObject::tr( "Total number of points generated" ) ) );
+ addOutput( new QgsProcessingOutputNumber( POINTS_MISSED, QObject::tr( "Number of missed points" ) ) );
+ addOutput( new QgsProcessingOutputNumber( LINES_WITH_MISSED_POINTS, QObject::tr( "Number of lines with missed points" ) ) );
+ addOutput( new QgsProcessingOutputNumber( FEATURES_WITH_EMPTY_OR_NO_GEOMETRY, QObject::tr( "Number of features with empty or no geometry" ) ) );
+}
+
+QString QgsRandomPointsOnLinesAlgorithm::shortHelpString() const
+{
+ return QObject::tr( "This algorithm creates a point layer, with points placed randomly "
+ "on the lines of the Input line layer .
"
+ "For each feature in the Input line layer , the algorithm attempts to add "
+ "the specified Number of points for each feature to the output layer. "
+ "A Minimum distance between points can be specified. "
+ "A point will not be generated if there is an already generated point "
+ "(on any line feature) within this (Euclidean) distance from "
+ "the generated location. "
+ "If the Minimum distance between points is too large, it may not be possible to generate "
+ "the specified Number of points for each feature . "
+ "The Maximum number of attempts per point "
+ "is only relevant if the Minimum distance between points is greater than 0. "
+ "The total number of points will be 'number of input features' * "
+ "Number of points for each feature if there are no misses. "
+ "The seed for the random generator can be provided (Random seed "
+ "- integer, greater than 0). "
+ "The user can choose not to Include line feature attributes in "
+ "the attributes of the generated point features. "
+ " "
+ "Output from the algorithm:
"
+ " "
+ " A point layer containing the random points (OUTPUT
). "
+ " The number of generated features (POINTS_GENERATED
). "
+ " The number of missed points (POINTS_MISSED
). "
+ " The number of features with non-empty geometry and missing points (LINES_WITH_MISSED_POINTS
). "
+ " The number of features with an empty or no geometry (LINES_WITH_EMPTY_OR_NO_GEOMETRY
). "
+ " "
+ );
+}
+
+
+QgsRandomPointsOnLinesAlgorithm *QgsRandomPointsOnLinesAlgorithm::createInstance() const
+{
+ return new QgsRandomPointsOnLinesAlgorithm();
+}
+
+bool QgsRandomPointsOnLinesAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
+{
+ mNumPoints = parameterAsInt( parameters, POINTS_NUMBER, context );
+ mDynamicNumPoints = QgsProcessingParameters::isDynamic( parameters, POINTS_NUMBER );
+ if ( mDynamicNumPoints )
+ mNumPointsProperty = parameters.value( POINTS_NUMBER ).value< QgsProperty >();
+
+ mMinDistance = parameterAsDouble( parameters, MIN_DISTANCE, context );
+ mDynamicMinDistance = QgsProcessingParameters::isDynamic( parameters, MIN_DISTANCE );
+ if ( mDynamicMinDistance )
+ mMinDistanceProperty = parameters.value( MIN_DISTANCE ).value< QgsProperty >();
+
+ mMaxAttempts = parameterAsInt( parameters, MAX_TRIES_PER_POINT, context );
+ mDynamicMaxAttempts = QgsProcessingParameters::isDynamic( parameters, MAX_TRIES_PER_POINT );
+ if ( mDynamicMaxAttempts )
+ mMaxAttemptsProperty = parameters.value( MAX_TRIES_PER_POINT ).value< QgsProperty >();
+
+ mUseRandomSeed = parameters.value( SEED ).isValid();
+ mRandSeed = parameterAsInt( parameters, SEED, context );
+ mIncludeLineAttr = parameterAsBoolean( parameters, INCLUDE_LINE_ATTRIBUTES, context );
+ return true;
+}
+
+QVariantMap QgsRandomPointsOnLinesAlgorithm::processAlgorithm( const QVariantMap ¶meters,
+ QgsProcessingContext &context, QgsProcessingFeedback *feedback )
+{
+ std::unique_ptr< QgsProcessingFeatureSource > lineSource( parameterAsSource( parameters, INPUT, context ) );
+ if ( !lineSource )
+ throw QgsProcessingException( invalidSourceError( parameters, INPUT ) );
+
+ QgsFields fields;
+ fields.append( QgsField( QStringLiteral( "rand_point_id" ), QVariant::LongLong ) );
+ if ( mIncludeLineAttr )
+ fields.extend( lineSource->fields() );
+
+ QString ldest;
+ std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, OUTPUT,
+ context, ldest, fields, QgsWkbTypes::Point, lineSource->sourceCrs() ) );
+ if ( !sink )
+ throw QgsProcessingException( invalidSinkError( parameters, OUTPUT ) );
+
+ QgsExpressionContext expressionContext = createExpressionContext( parameters, context, lineSource.get() );
+
+ // Initialize random engine
+ std::random_device rd;
+ std::mt19937 mt( !mUseRandomSeed ? rd() : mRandSeed );
+ std::uniform_real_distribution<> uniformDist( 0, 1 );
+
+ // Index for finding close points (mMinDistance > 0)
+ QgsSpatialIndex index;
+
+ int totNPoints = 0;
+ int missedPoints = 0;
+ int missedLines = 0;
+ int emptyOrNullGeom = 0;
+
+ long featureCount = 0;
+ long numberOfFeatures = lineSource->featureCount();
+ long long desiredNumberOfPoints = 0;
+ const double featureProgressStep = 100.0 / ( numberOfFeatures > 0 ? numberOfFeatures : 1 );
+ double baseFeatureProgress = 0.0;
+ QgsFeature lFeat;
+ QgsFeatureIterator fitL = mIncludeLineAttr || mDynamicNumPoints || mDynamicMinDistance || mDynamicMaxAttempts ? lineSource->getFeatures()
+ : lineSource->getFeatures( QgsFeatureRequest().setNoAttributes() );
+ while ( fitL.nextFeature( lFeat ) )
+ {
+ if ( feedback->isCanceled() )
+ {
+ feedback->setProgress( 0 );
+ break;
+ }
+ if ( !lFeat.hasGeometry() )
+ {
+ // Increment invalid features count
+ emptyOrNullGeom++;
+ featureCount++;
+ baseFeatureProgress += featureProgressStep;
+ feedback->setProgress( baseFeatureProgress );
+ continue;
+ }
+ QgsGeometry lGeom( lFeat.geometry() );
+ if ( lGeom.isEmpty() )
+ {
+ // Increment invalid features count
+ emptyOrNullGeom++;
+ featureCount++;
+ baseFeatureProgress += featureProgressStep;
+ feedback->setProgress( baseFeatureProgress );
+ continue;
+ }
+
+ if ( mDynamicNumPoints || mDynamicMinDistance || mDynamicMaxAttempts )
+ {
+ expressionContext.setFeature( lFeat );
+ }
+
+ double lineLength = lGeom.length();
+ int pointsAddedForThisFeature = 0;
+
+ int numberPointsForThisFeature = mNumPoints;
+ if ( mDynamicNumPoints )
+ numberPointsForThisFeature = mNumPointsProperty.valueAsInt( expressionContext, numberPointsForThisFeature );
+ desiredNumberOfPoints += numberPointsForThisFeature;
+
+ int maxAttemptsForThisFeature = mMaxAttempts;
+ if ( mDynamicMaxAttempts )
+ maxAttemptsForThisFeature = mMaxAttemptsProperty.valueAsInt( expressionContext, maxAttemptsForThisFeature );
+
+ double minDistanceForThisFeature = mMinDistance;
+ if ( mDynamicMinDistance )
+ minDistanceForThisFeature = mMinDistanceProperty.valueAsDouble( expressionContext, minDistanceForThisFeature );
+
+ const double pointProgressIncrement = featureProgressStep / ( numberPointsForThisFeature * maxAttemptsForThisFeature );
+
+ double pointProgress = 0.0;
+ for ( long pointIndex = 0; pointIndex < numberPointsForThisFeature; pointIndex++ )
+ {
+ if ( feedback->isCanceled() )
+ {
+ break;
+ }
+ // Try to add a point (mMaxAttempts attempts)
+ int distCheckIterations = 0;
+ while ( distCheckIterations < maxAttemptsForThisFeature )
+ {
+ if ( feedback->isCanceled() )
+ {
+ break;
+ }
+ // Generate a random point
+ double randPos = lineLength * uniformDist( mt );
+ QgsGeometry rpGeom = QgsGeometry( lGeom.interpolate( randPos ) );
+ distCheckIterations++;
+ pointProgress += pointProgressIncrement;
+
+ if ( !rpGeom.isNull() && !rpGeom.isEmpty() )
+ {
+ if ( minDistanceForThisFeature != 0 && totNPoints > 0 )
+ {
+ // Have to check minimum distance to existing points
+ QList neighbors = index.nearestNeighbor( rpGeom, 1, minDistanceForThisFeature );
+ if ( !neighbors.empty() )
+ {
+ feedback->setProgress( baseFeatureProgress + pointProgress );
+ continue;
+ }
+ }
+ // point OK to add
+ QgsFeature f = QgsFeature( totNPoints );
+ QgsAttributes pAttrs = QgsAttributes();
+ pAttrs.append( totNPoints );
+ if ( mIncludeLineAttr )
+ {
+ pAttrs.append( lFeat.attributes() );
+ }
+ f.setAttributes( pAttrs );
+ f.setGeometry( rpGeom );
+
+ if ( minDistanceForThisFeature != 0 )
+ {
+ index.addFeature( f );
+ }
+ sink->addFeature( f, QgsFeatureSink::FastInsert );
+ totNPoints++;
+ pointsAddedForThisFeature++;
+ pointProgress += pointProgressIncrement * ( maxAttemptsForThisFeature - distCheckIterations );
+ break;
+ }
+ else
+ {
+ feedback->setProgress( baseFeatureProgress + pointProgress );
+ }
+ } // while not maxattempts
+ feedback->setProgress( baseFeatureProgress + pointProgress );
+ } // for points
+ baseFeatureProgress += featureProgressStep;
+ if ( pointsAddedForThisFeature < numberPointsForThisFeature )
+ {
+ missedLines++;
+ }
+ featureCount++;
+ feedback->setProgress( baseFeatureProgress );
+ } // while features
+ missedPoints = desiredNumberOfPoints - totNPoints;
+ feedback->pushInfo( QObject::tr( "Total number of points generated: "
+ " %1\nNumber of missed points: %2\nLines with missing points: "
+ " %3\nFeatures with empty or missing geometries: %4"
+ ).arg( totNPoints ).arg( missedPoints ).arg( missedLines ).arg( emptyOrNullGeom ) );
+ QVariantMap outputs;
+ outputs.insert( OUTPUT, ldest );
+ outputs.insert( OUTPUT_POINTS, totNPoints );
+ outputs.insert( POINTS_MISSED, missedPoints );
+ outputs.insert( LINES_WITH_MISSED_POINTS, missedLines );
+ outputs.insert( FEATURES_WITH_EMPTY_OR_NO_GEOMETRY, emptyOrNullGeom );
+
+ return outputs;
+}
+
+///@endcond
diff --git a/src/analysis/processing/qgsalgorithmrandompointsonlines.h b/src/analysis/processing/qgsalgorithmrandompointsonlines.h
new file mode 100644
index 000000000000..61999a09605a
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmrandompointsonlines.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+ qgsalgorithmrandompointsonlines.h
+ ---------------------
+ begin : March 2020
+ copyright : (C) 2020 by Håvard Tveite
+ email : havard dot tveite at nmbu dot no
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 QGSALGORITHMRANDOMPOINTSONLINES_H
+#define QGSALGORITHMRANDOMPOINTSONLINES_H
+
+#define SIP_NO_FILE
+
+#include "qgis_sip.h"
+#include "qgsprocessingalgorithm.h"
+#include "qgsapplication.h"
+
+///@cond PRIVATE
+
+/**
+ * Native random points on lines creation algorithm.
+ */
+class QgsRandomPointsOnLinesAlgorithm : public QgsProcessingAlgorithm
+{
+ public:
+
+ QgsRandomPointsOnLinesAlgorithm() = default;
+ void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
+ QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmRandomPointsOnLines.svg" ) ); }
+ QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmRandomPointsOnLines.svg" ) ); }
+ QString name() const override;
+ QString displayName() const override;
+ QStringList tags() const override;
+ QString group() const override;
+ QString groupId() const override;
+ QString shortHelpString() const override;
+ QgsRandomPointsOnLinesAlgorithm *createInstance() const override SIP_FACTORY;
+
+ protected:
+ bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) override;
+ QVariantMap processAlgorithm( const QVariantMap ¶meters,
+ QgsProcessingContext &context,
+ QgsProcessingFeedback *feedback ) override;
+
+
+ private:
+ int mNumPoints;
+ bool mDynamicNumPoints = false;
+ QgsProperty mNumPointsProperty;
+
+ double mMinDistance = 0;
+ bool mDynamicMinDistance = false;
+ QgsProperty mMinDistanceProperty;
+
+ int mMaxAttempts = 10;
+ bool mDynamicMaxAttempts = false;
+ QgsProperty mMaxAttemptsProperty;
+
+ bool mUseRandomSeed = false;
+ int mRandSeed = 0;
+ bool mIncludeLineAttr = false;
+};
+
+///@endcond PRIVATE
+
+#endif // QGSALGORITHMRANDOMPOINTSONLINES_H
diff --git a/src/analysis/processing/qgsalgorithmremovenullgeometry.cpp b/src/analysis/processing/qgsalgorithmremovenullgeometry.cpp
index 62eb8038ea12..2590efd5ffe4 100644
--- a/src/analysis/processing/qgsalgorithmremovenullgeometry.cpp
+++ b/src/analysis/processing/qgsalgorithmremovenullgeometry.cpp
@@ -47,6 +47,7 @@ QString QgsRemoveNullGeometryAlgorithm::groupId() const
void QgsRemoveNullGeometryAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
+ addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "REMOVE_EMPTY" ), QObject::tr( "Also remove empty geometries" ), false ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Non null geometries" ),
QgsProcessing::TypeVectorAnyGeometry, QVariant(), true ) );
@@ -60,7 +61,10 @@ QString QgsRemoveNullGeometryAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm removes any features which do not have a geometry from a vector layer. "
"All other features will be copied unchanged.\n\n"
- "Optionally, the features with null geometries can be saved to a separate output." );
+ "Optionally, the features with null geometries can be saved to a separate output.\n\n"
+ "If 'Also remove empty geometries' is checked, the algorithm removes features whose geometries "
+ "have no coordinates, i.e., geometries that are empty. In that case, also the null "
+ "output will reflect this option, containing both null and empty geometries." );
}
QgsRemoveNullGeometryAlgorithm *QgsRemoveNullGeometryAlgorithm::createInstance() const
@@ -74,6 +78,8 @@ QVariantMap QgsRemoveNullGeometryAlgorithm::processAlgorithm( const QVariantMap
if ( !source )
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
+ const bool removeEmpty = parameterAsBoolean( parameters, QStringLiteral( "REMOVE_EMPTY" ), context );
+
QString nonNullSinkId;
std::unique_ptr< QgsFeatureSink > nonNullSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, nonNullSinkId, source->fields(),
source->wkbType(), source->sourceCrs() ) );
@@ -95,11 +101,11 @@ QVariantMap QgsRemoveNullGeometryAlgorithm::processAlgorithm( const QVariantMap
break;
}
- if ( f.hasGeometry() && nonNullSink )
+ if ( ( ( !removeEmpty && f.hasGeometry() ) || ( removeEmpty && !f.geometry().isEmpty() ) ) && nonNullSink )
{
nonNullSink->addFeature( f, QgsFeatureSink::FastInsert );
}
- else if ( !f.hasGeometry() && nullSink )
+ else if ( ( ( !removeEmpty && !f.hasGeometry() ) || ( removeEmpty && f.geometry().isEmpty() ) ) && nullSink )
{
nullSink->addFeature( f, QgsFeatureSink::FastInsert );
}
diff --git a/src/analysis/processing/qgsalgorithmroundrastervalues.cpp b/src/analysis/processing/qgsalgorithmroundrastervalues.cpp
new file mode 100644
index 000000000000..ab8b3e2fb62b
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmroundrastervalues.cpp
@@ -0,0 +1,261 @@
+/***************************************************************************
+ qgsalgorithmroundrastervalues.cpp
+ ---------------------
+ begin : April 2020
+ copyright : (C) 2020 by Clemens Raffler
+ email : clemens dot raffler 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 "qgsalgorithmroundrastervalues.h"
+#include "qgsrasterfilewriter.h"
+
+///@cond PRIVATE
+
+QString QgsRoundRasterValuesAlgorithm::name() const
+{
+ return QStringLiteral( "roundrastervalues" );
+}
+
+QString QgsRoundRasterValuesAlgorithm::displayName() const
+{
+ return QObject::tr( "Round raster" );
+}
+
+QStringList QgsRoundRasterValuesAlgorithm::tags() const
+{
+ return QObject::tr( "data,cells,round,truncate" ).split( ',' );
+}
+
+QString QgsRoundRasterValuesAlgorithm::group() const
+{
+ return QObject::tr( "Raster analysis" );
+}
+
+QString QgsRoundRasterValuesAlgorithm::groupId() const
+{
+ return QStringLiteral( "rasteranalysis" );
+}
+
+void QgsRoundRasterValuesAlgorithm::initAlgorithm( const QVariantMap & )
+{
+ addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QStringLiteral( "Input raster" ) ) );
+ addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ), QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
+ addParameter( new QgsProcessingParameterEnum( QStringLiteral( "ROUNDING_DIRECTION" ), QObject::tr( "Rounding direction" ), QStringList() << QObject::tr( "Round up" ) << QObject::tr( "Round to nearest" ) << QObject::tr( "Round down" ), false, 1 ) );
+ addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DECIMAL_PLACES" ), QObject::tr( "Number of decimals places" ), QgsProcessingParameterNumber::Integer, 2 ) );
+ addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output raster" ) ) );
+ std::unique_ptr< QgsProcessingParameterDefinition > baseParameter = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "BASE_N" ), QObject::tr( "Base n for rounding to multiples of n" ), QgsProcessingParameterNumber::Integer, 10, true, 1 );
+ baseParameter->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
+ addParameter( baseParameter.release() );
+}
+
+QString QgsRoundRasterValuesAlgorithm::shortHelpString() const
+{
+ return QObject::tr( "This algorithm rounds the cell values of a raster dataset according to the specified number of decimals.\n "
+ "Alternatively, a negative number of decimal places may be used to round values to powers of a base n "
+ "(specified in the advanced parameter Base n). For example, with a Base value n of 10 and Decimal places of -1 "
+ "the algorithm rounds cell values to multiples of 10, -2 rounds to multiples of 100, and so on. Arbitrary base values "
+ "may be chosen, the algorithm applies the same multiplicative principle. Rounding cell values to multiples of "
+ "a base n may be used to generalize raster layers.\n"
+ "The algorithm preserves the data type of the input raster. Therefore byte/integer rasters can only be rounded "
+ "to multiples of a base n, otherwise a warning is raised and the raster gets copied as byte/integer raster" );
+}
+
+QgsRoundRasterValuesAlgorithm *QgsRoundRasterValuesAlgorithm::createInstance() const
+{
+ return new QgsRoundRasterValuesAlgorithm();
+}
+
+bool QgsRoundRasterValuesAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
+{
+ Q_UNUSED( feedback );
+ QgsRasterLayer *inputRaster = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
+ mDecimalPrecision = parameterAsInt( parameters, QStringLiteral( "DECIMAL_PLACES" ), context );
+ mBaseN = parameterAsInt( parameters, QStringLiteral( "BASE_N" ), context );
+ mMultipleOfBaseN = pow( mBaseN, abs( mDecimalPrecision ) );
+ mScaleFactor = std::pow( 10.0, mDecimalPrecision );
+
+ if ( !inputRaster )
+ throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
+
+ mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
+ if ( mBand < 1 || mBand > inputRaster->bandCount() )
+ throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( inputRaster->bandCount() ) );
+
+ mRoundingDirection = parameterAsEnum( parameters, QStringLiteral( "ROUNDING_DIRECTION" ), context );
+
+ mInterface.reset( inputRaster->dataProvider()->clone() );
+ mDataType = mInterface->dataType( mBand );
+
+ switch ( mDataType )
+ {
+ case Qgis::Byte:
+ case Qgis::Int16:
+ case Qgis::UInt16:
+ case Qgis::Int32:
+ case Qgis::UInt32:
+ mIsInteger = true;
+ if ( mDecimalPrecision > -1 )
+ feedback->reportError( QObject::tr( "Input raster is of byte or integer type. The cell values cannot be rounded and will be output using the same data type." ), false );
+ break;
+ default:
+ mIsInteger = false;
+ break;
+ }
+
+ mInputNoDataValue = inputRaster->dataProvider()->sourceNoDataValue( mBand );
+ mExtent = inputRaster->extent();
+ mLayerWidth = inputRaster->width();
+ mLayerHeight = inputRaster->height();
+ mCrs = inputRaster->crs();
+ mNbCellsXProvider = mInterface->xSize();
+ mNbCellsYProvider = mInterface->ySize();
+ return true;
+}
+
+QVariantMap QgsRoundRasterValuesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
+{
+ //prepare output dataset
+ const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
+ QFileInfo fi( outputFile );
+ const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
+ std::unique_ptr< QgsRasterFileWriter > writer = qgis::make_unique< QgsRasterFileWriter >( outputFile );
+ writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
+ writer->setOutputFormat( outputFormat );
+ std::unique_ptr provider( writer->createOneBandRaster( mInterface->dataType( mBand ), mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) );
+ if ( !provider )
+ throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
+ if ( !provider->isValid() )
+ throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
+
+ //prepare output provider
+ QgsRasterDataProvider *destinationRasterProvider;
+ destinationRasterProvider = provider.get();
+ destinationRasterProvider->setEditable( true );
+ destinationRasterProvider->setNoDataValue( 1, mInputNoDataValue );
+
+ int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH;
+ int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT;
+ int nbBlocksWidth = static_cast< int >( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
+ int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
+ int nbBlocks = nbBlocksWidth * nbBlocksHeight;
+
+ QgsRasterIterator iter( mInterface.get() );
+ iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
+ int iterLeft = 0;
+ int iterTop = 0;
+ int iterCols = 0;
+ int iterRows = 0;
+ std::unique_ptr< QgsRasterBlock > analysisRasterBlock;
+ while ( iter.readNextRasterPart( mBand, iterCols, iterRows, analysisRasterBlock, iterLeft, iterTop ) )
+ {
+ if ( feedback )
+ feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
+ if ( mIsInteger && mDecimalPrecision > -1 )
+ {
+ //nothing to round, just write raster block
+ analysisRasterBlock->setNoDataValue( mInputNoDataValue );
+ destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop );
+ }
+ else
+ {
+ for ( int row = 0; row < iterRows; row++ )
+ {
+ if ( feedback && feedback->isCanceled() )
+ break;
+ for ( int column = 0; column < iterCols; column++ )
+ {
+ bool isNoData = false;
+ double val = analysisRasterBlock->valueAndNoData( row, column, isNoData );
+ if ( isNoData )
+ {
+ analysisRasterBlock->setValue( row, column, mInputNoDataValue );
+ }
+ else
+ {
+ double roundedVal = mInputNoDataValue;
+ if ( mRoundingDirection == 0 && mDecimalPrecision < 0 )
+ {
+ roundedVal = roundUpBaseN( val );
+ }
+ else if ( mRoundingDirection == 0 && mDecimalPrecision > -1 )
+ {
+ double m = ( val < 0.0 ) ? -1.0 : 1.0;
+ roundedVal = roundUp( val, m );
+ }
+ else if ( mRoundingDirection == 1 && mDecimalPrecision < 0 )
+ {
+ roundedVal = roundNearestBaseN( val );
+ }
+ else if ( mRoundingDirection == 1 && mDecimalPrecision > -1 )
+ {
+ double m = ( val < 0.0 ) ? -1.0 : 1.0;
+ roundedVal = roundNearest( val, m );
+ }
+ else if ( mRoundingDirection == 2 && mDecimalPrecision < 0 )
+ {
+ roundedVal = roundDownBaseN( val );
+ }
+ else
+ {
+ double m = ( val < 0.0 ) ? -1.0 : 1.0;
+ roundedVal = roundDown( val, m );
+ }
+ //integer values get automatically cast to double when reading and back to int when writing
+ analysisRasterBlock->setValue( row, column, roundedVal );
+ }
+ }
+ }
+ destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop );
+ }
+ }
+ destinationRasterProvider->setEditable( false );
+
+ QVariantMap outputs;
+ outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
+ return outputs;
+}
+
+double QgsRoundRasterValuesAlgorithm::roundNearest( double value, double m )
+{
+ return ( std::round( value * m * mScaleFactor ) / mScaleFactor ) * m;
+}
+
+double QgsRoundRasterValuesAlgorithm::roundUp( double value, double m )
+{
+ return ( std::ceil( value * m * mScaleFactor ) / mScaleFactor ) * m;
+}
+
+double QgsRoundRasterValuesAlgorithm::roundDown( double value, double m )
+{
+ return ( std::floor( value * m * mScaleFactor ) / mScaleFactor ) * m;
+}
+
+
+double QgsRoundRasterValuesAlgorithm::roundNearestBaseN( double value )
+{
+ return static_cast( mMultipleOfBaseN * round( value / mMultipleOfBaseN ) );
+}
+
+double QgsRoundRasterValuesAlgorithm::roundUpBaseN( double value )
+{
+ return static_cast( mMultipleOfBaseN * ceil( value / mMultipleOfBaseN ) );
+}
+
+double QgsRoundRasterValuesAlgorithm::roundDownBaseN( double value )
+{
+ return static_cast( mMultipleOfBaseN * floor( value / mMultipleOfBaseN ) );
+}
+
+
+
+
+///@endcond
diff --git a/src/analysis/processing/qgsalgorithmroundrastervalues.h b/src/analysis/processing/qgsalgorithmroundrastervalues.h
new file mode 100644
index 000000000000..8511628eb57e
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmroundrastervalues.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+ qgsalgorithmroundrastervalues.h
+ ---------------------
+ begin : April 2020
+ copyright : (C) 2020 by Clemens Raffler
+ email : clemens dot raffler at gmail dot com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+
+#ifndef QGSALGORITHMROUNDRASTERVALUES_H
+#define QGSALGORITHMROUNDRASTERVALUES_H
+
+#define SIP_NO_FILE
+
+#include "qgis_sip.h"
+#include "qgsprocessingalgorithm.h"
+#include "qgsapplication.h"
+
+///@cond PRIVATE
+
+/**
+ * Round raster values algorithm:
+ * This algorithm rounds the Values of floating point raster datasets
+ * based on a predefined precision value.
+ */
+class QgsRoundRasterValuesAlgorithm : public QgsProcessingAlgorithm
+{
+ public:
+
+ QgsRoundRasterValuesAlgorithm() = default;
+ void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
+ QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmRoundRastervalues.svg" ) ); }
+ QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmRoundRastervalues.svg" ) ); }
+ QString name() const override;
+ QString displayName() const override;
+ QStringList tags() const override;
+ QString group() const override;
+ QString groupId() const override;
+ QString shortHelpString() const override;
+ QgsRoundRasterValuesAlgorithm *createInstance() const override SIP_FACTORY;
+
+ protected:
+ bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
+ QVariantMap processAlgorithm( const QVariantMap ¶meters,
+ QgsProcessingContext &context,
+ QgsProcessingFeedback *feedback ) override;
+
+ private:
+ double roundNearest( double value, double m );
+ double roundUp( double value, double m );
+ double roundDown( double value, double m );
+ double roundNearestBaseN( double value );
+ double roundUpBaseN( double value );
+ double roundDownBaseN( double value );
+
+ int mDecimalPrecision = 2;
+ int mBaseN = 10;
+ double mScaleFactor;
+ int mMultipleOfBaseN;
+ int mBand;
+ int mRoundingDirection;
+ std::unique_ptr< QgsRasterInterface > mInterface;
+ Qgis::DataType mDataType;
+ bool mIsInteger;
+ QgsRectangle mExtent;
+ QgsCoordinateReferenceSystem mCrs;
+ int mLayerWidth;
+ int mLayerHeight;
+ int mNbCellsXProvider = 0;
+ int mNbCellsYProvider = 0;
+ double mInputNoDataValue;
+};
+
+///@endcond PRIVATE
+
+#endif // QGSALGORITHMROUNDRASTERVALUES_H
diff --git a/src/analysis/processing/qgsalgorithmsavelog.cpp b/src/analysis/processing/qgsalgorithmsavelog.cpp
new file mode 100644
index 000000000000..0594f6cddef0
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmsavelog.cpp
@@ -0,0 +1,93 @@
+/***************************************************************************
+ qgsalgorithmsavelog.cpp
+ ---------------------
+ begin : March 2020
+ copyright : (C) 2020 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 "qgsalgorithmsavelog.h"
+
+///@cond PRIVATE
+
+QString QgsSaveLogToFileAlgorithm::name() const
+{
+ return QStringLiteral( "savelog" );
+}
+
+QgsProcessingAlgorithm::Flags QgsSaveLogToFileAlgorithm::flags() const
+{
+ return QgsProcessingAlgorithm::flags() | FlagHideFromToolbox | FlagSkipGenericModelLogging;
+}
+
+QString QgsSaveLogToFileAlgorithm::displayName() const
+{
+ return QObject::tr( "Save log to file" );
+}
+
+QStringList QgsSaveLogToFileAlgorithm::tags() const
+{
+ return QObject::tr( "record,messages,logged" ).split( ',' );
+}
+
+QString QgsSaveLogToFileAlgorithm::group() const
+{
+ return QObject::tr( "Modeler tools" );
+}
+
+QString QgsSaveLogToFileAlgorithm::groupId() const
+{
+ return QStringLiteral( "modelertools" );
+}
+
+QString QgsSaveLogToFileAlgorithm::shortHelpString() const
+{
+ return QObject::tr( "This algorithm saves the model's execution log to a file.\n"
+ "Optionally, the log can be saved in a HTML formatted version." );
+}
+
+QString QgsSaveLogToFileAlgorithm::shortDescription() const
+{
+ return QObject::tr( "Saves the model's log contents to a file." );
+}
+
+QgsSaveLogToFileAlgorithm *QgsSaveLogToFileAlgorithm::createInstance() const
+{
+ return new QgsSaveLogToFileAlgorithm();
+}
+
+void QgsSaveLogToFileAlgorithm::initAlgorithm( const QVariantMap & )
+{
+ addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Log file" ), QObject::tr( "Text files (*.txt);;HTML files (*.html *.HTML)" ) ) );
+ addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_HTML" ), QObject::tr( "Use HTML formatting" ), false ) );
+}
+
+QVariantMap QgsSaveLogToFileAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
+{
+ const QString file = parameterAsFile( parameters, QStringLiteral( "OUTPUT" ), context );
+ const bool useHtml = parameterAsBool( parameters, QStringLiteral( "USE_HTML" ), context );
+ if ( !file.isEmpty() )
+ {
+ QFile exportFile( file );
+ if ( !exportFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
+ {
+ throw QgsProcessingException( QObject::tr( "Could not save log to file %1" ).arg( file ) );
+ }
+ QTextStream fout( &exportFile );
+ fout << ( useHtml ? feedback->htmlLog() : feedback->textLog() );
+ }
+ QVariantMap res;
+ res.insert( QStringLiteral( "OUTPUT" ), file );
+ return res;
+}
+
+///@endcond
diff --git a/src/analysis/processing/qgsalgorithmsavelog.h b/src/analysis/processing/qgsalgorithmsavelog.h
new file mode 100644
index 000000000000..b6f28901d1db
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmsavelog.h
@@ -0,0 +1,57 @@
+/***************************************************************************
+ qgsalgorithmsavelog.h
+ ---------------------
+ begin : March 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGSALGORITHMSAVELOG_H
+#define QGSALGORITHMSAVELOG_H
+
+#define SIP_NO_FILE
+
+#include "qgis_sip.h"
+#include "qgsprocessingalgorithm.h"
+#include "qgsapplication.h"
+
+///@cond PRIVATE
+
+/**
+ * Native save log to file algorithm.
+ */
+class QgsSaveLogToFileAlgorithm : public QgsProcessingAlgorithm
+{
+ public:
+ QgsSaveLogToFileAlgorithm() = default;
+ void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
+ Flags flags() const override;
+ QString name() const override;
+ QString displayName() const override;
+ QStringList tags() const override;
+ QString group() const override;
+ QString groupId() const override;
+ QString shortHelpString() const override;
+ QString shortDescription() const override;
+ QgsSaveLogToFileAlgorithm *createInstance() const override SIP_FACTORY;
+
+ protected:
+
+ QVariantMap processAlgorithm( const QVariantMap ¶meters,
+ QgsProcessingContext &context, QgsProcessingFeedback * ) override;
+
+};
+
+
+///@endcond PRIVATE
+
+#endif // QGSALGORITHMSAVELOG_H
diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp
index 3709ce20fa13..9db2f1ced3c9 100644
--- a/src/analysis/processing/qgsnativealgorithms.cpp
+++ b/src/analysis/processing/qgsnativealgorithms.cpp
@@ -61,6 +61,7 @@
#include "qgsalgorithmfiledownloader.h"
#include "qgsalgorithmfillnodata.h"
#include "qgsalgorithmfilter.h"
+#include "qgsalgorithmfilterbygeometry.h"
#include "qgsalgorithmfiltervertices.h"
#include "qgsalgorithmfixgeometries.h"
#include "qgsalgorithmforcerhr.h"
@@ -105,6 +106,7 @@
#include "qgsalgorithmraiseexception.h"
#include "qgsalgorithmrandomextract.h"
#include "qgsalgorithmrandompointsextent.h"
+#include "qgsalgorithmrandompointsonlines.h"
#include "qgsalgorithmrasterlayeruniquevalues.h"
#include "qgsalgorithmrasterlogicalop.h"
#include "qgsalgorithmrasterize.h"
@@ -122,7 +124,9 @@
#include "qgsalgorithmrepairshapefile.h"
#include "qgsalgorithmreverselinedirection.h"
#include "qgsalgorithmrotate.h"
+#include "qgsalgorithmroundrastervalues.h"
#include "qgsalgorithmruggedness.h"
+#include "qgsalgorithmsavelog.h"
#include "qgsalgorithmsaveselectedfeatures.h"
#include "qgsalgorithmsegmentize.h"
#include "qgsalgorithmserviceareafromlayer.h"
@@ -251,6 +255,8 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsFileDownloaderAlgorithm() );
addAlgorithm( new QgsFillNoDataAlgorithm() );
addAlgorithm( new QgsFilterAlgorithm() );
+ addAlgorithm( new QgsFilterByGeometryAlgorithm() );
+ addAlgorithm( new QgsFilterByLayerTypeAlgorithm() );
addAlgorithm( new QgsFilterVerticesByM() );
addAlgorithm( new QgsFilterVerticesByZ() );
addAlgorithm( new QgsFixGeometriesAlgorithm() );
@@ -304,6 +310,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsRaiseWarningAlgorithm() );
addAlgorithm( new QgsRandomExtractAlgorithm() );
addAlgorithm( new QgsRandomPointsExtentAlgorithm() );
+ addAlgorithm( new QgsRandomPointsOnLinesAlgorithm() );
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
addAlgorithm( new QgsRasterLayerZonalStatsAlgorithm() );
addAlgorithm( new QgsRasterLogicalAndAlgorithm() );
@@ -325,7 +332,9 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsRepairShapefileAlgorithm() );
addAlgorithm( new QgsReverseLineDirectionAlgorithm() );
addAlgorithm( new QgsRotateFeaturesAlgorithm() );
+ addAlgorithm( new QgsRoundRasterValuesAlgorithm() );
addAlgorithm( new QgsRuggednessAlgorithm() );
+ addAlgorithm( new QgsSaveLogToFileAlgorithm() );
addAlgorithm( new QgsSaveSelectedFeatures() );
addAlgorithm( new QgsSegmentizeByMaximumAngleAlgorithm() );
addAlgorithm( new QgsSegmentizeByMaximumDistanceAlgorithm() );
diff --git a/src/app/3d/qgs3dmapcanvas.cpp b/src/app/3d/qgs3dmapcanvas.cpp
index cc329e82a988..49f0d0b6362f 100644
--- a/src/app/3d/qgs3dmapcanvas.cpp
+++ b/src/app/3d/qgs3dmapcanvas.cpp
@@ -28,6 +28,7 @@
#include "qgswindow3dengine.h"
#include "qgs3dnavigationwidget.h"
#include "qgssettings.h"
+#include "qgstemporalcontroller.h"
Qgs3DMapCanvas::Qgs3DMapCanvas( QWidget *parent )
: QWidget( parent )
@@ -106,6 +107,8 @@ void Qgs3DMapCanvas::setMap( Qgs3DMapSettings *map )
mNavigationWidget->updateFromCamera();
}
);
+
+ emit mapSettingsChanged();
}
QgsCameraController *Qgs3DMapCanvas::cameraController()
@@ -189,10 +192,24 @@ bool Qgs3DMapCanvas::eventFilter( QObject *watched, QEvent *event )
return false;
}
-
void Qgs3DMapCanvas::setOnScreenNavigationVisibility( bool visibility )
{
mNavigationWidget->setVisible( visibility );
QgsSettings setting;
setting.setValue( QStringLiteral( "/3D/navigationWidget/visibility" ), visibility, QgsSettings::Gui );
}
+
+void Qgs3DMapCanvas::setTemporalController( QgsTemporalController *temporalController )
+{
+ if ( mTemporalController )
+ disconnect( mTemporalController, &QgsTemporalController::updateTemporalRange, this, &Qgs3DMapCanvas::updateTemporalRange );
+
+ mTemporalController = temporalController;
+ connect( mTemporalController, &QgsTemporalController::updateTemporalRange, this, &Qgs3DMapCanvas::updateTemporalRange );
+}
+
+void Qgs3DMapCanvas::updateTemporalRange( const QgsDateTimeRange &temporalrange )
+{
+ mMap->setTemporalRange( temporalrange );
+ mScene->updateTemporal();
+}
diff --git a/src/app/3d/qgs3dmapcanvas.h b/src/app/3d/qgs3dmapcanvas.h
index 70c6e85a3552..d5405359ea43 100644
--- a/src/app/3d/qgs3dmapcanvas.h
+++ b/src/app/3d/qgs3dmapcanvas.h
@@ -19,6 +19,8 @@
#include
#include
+#include "qgsrange.h"
+
namespace Qt3DExtras
{
class Qt3DWindow;
@@ -31,6 +33,7 @@ class QgsWindow3DEngine;
class QgsCameraController;
class QgsPointXY;
class Qgs3DNavigationWidget;
+class QgsTemporalController;
class Qgs3DMapCanvas : public QWidget
@@ -74,14 +77,25 @@ class Qgs3DMapCanvas : public QWidget
Qgs3DMapTool *mapTool() const { return mMapTool; }
/**
- * Set the visibility of on-screen navigation widget.
+ * Sets the visibility of on-screen navigation widget.
*/
void setOnScreenNavigationVisibility( bool visibility );
+ /**
+ * Sets the temporal controller
+ */
+ void setTemporalController( QgsTemporalController *temporalController );
+
signals:
//! Emitted when the 3D map canvas was successfully saved as image
void savedAsImage( QString fileName );
+ //! Emitted when the the map setting is changed
+ void mapSettingsChanged();
+
+ private slots:
+ void updateTemporalRange( const QgsDateTimeRange &timeRange );
+
protected:
void resizeEvent( QResizeEvent *ev ) override;
bool eventFilter( QObject *watched, QEvent *event ) override;
@@ -104,6 +118,8 @@ class Qgs3DMapCanvas : public QWidget
//! On-Screen Navigation widget.
Qgs3DNavigationWidget *mNavigationWidget = nullptr;
+
+ QgsTemporalController *mTemporalController = nullptr;
};
#endif // QGS3DMAPCANVAS_H
diff --git a/src/app/3d/qgs3dmapcanvasdockwidget.cpp b/src/app/3d/qgs3dmapcanvasdockwidget.cpp
index 121669e569f6..d0c10d07cbd2 100644
--- a/src/app/3d/qgs3dmapcanvasdockwidget.cpp
+++ b/src/app/3d/qgs3dmapcanvasdockwidget.cpp
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include "qgisapp.h"
#include "qgs3dmapcanvas.h"
@@ -33,6 +34,8 @@
#include "qgsapplication.h"
#include "qgssettings.h"
#include "qgsgui.h"
+#include "qgsmapthemecollection.h"
+#include "qgstemporalcontroller.h"
#include "qgs3danimationsettings.h"
#include "qgs3danimationwidget.h"
@@ -98,6 +101,19 @@ Qgs3DMapCanvasDockWidget::Qgs3DMapCanvasDockWidget( QWidget *parent )
toolBar->addSeparator();
+ // Map Theme Menu
+ mMapThemeMenu = new QMenu();
+ connect( mMapThemeMenu, &QMenu::aboutToShow, this, &Qgs3DMapCanvasDockWidget::mapThemeMenuAboutToShow );
+
+ mBtnMapThemes = new QToolButton();
+ mBtnMapThemes->setAutoRaise( true );
+ mBtnMapThemes->setToolTip( tr( "Set View Theme" ) );
+ mBtnMapThemes->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayers.svg" ) ) );
+ mBtnMapThemes->setPopupMode( QToolButton::InstantPopup );
+ mBtnMapThemes->setMenu( mMapThemeMenu );
+
+ toolBar->addWidget( mBtnMapThemes );
+
toolBar->addAction( QgsApplication::getThemeIcon( QStringLiteral( "mActionOptions.svg" ) ),
tr( "Configure…" ), this, &Qgs3DMapCanvasDockWidget::configure );
@@ -210,6 +226,9 @@ void Qgs3DMapCanvasDockWidget::setMapSettings( Qgs3DMapSettings *map )
mAnimationWidget->setCameraController( mCanvas->scene()->cameraController() );
mAnimationWidget->setMap( map );
+
+ // Disable button for switching the map theme if the terrain generator is a mesh
+ mBtnMapThemes->setDisabled( mCanvas->map()->terrainGenerator()->type() == QgsTerrainGenerator::Mesh );
}
void Qgs3DMapCanvasDockWidget::setMainCanvas( QgsMapCanvas *canvas )
@@ -266,6 +285,9 @@ void Qgs3DMapCanvasDockWidget::configure()
newCameraPose.setCenterPoint( p );
mCanvas->cameraController()->setCameraPose( newCameraPose );
}
+
+ // Disable map theme button if the terrain generator is a mesh
+ mBtnMapThemes->setDisabled( map->terrainGenerator()->type() == QgsTerrainGenerator::Mesh );
}
void Qgs3DMapCanvasDockWidget::onMainCanvasLayersChanged()
@@ -286,3 +308,40 @@ void Qgs3DMapCanvasDockWidget::onTotalPendingJobsCountChanged()
if ( count )
mLabelPendingJobs->setText( tr( "Loading %1 tiles" ).arg( count ) );
}
+
+void Qgs3DMapCanvasDockWidget::mapThemeMenuAboutToShow()
+{
+ qDeleteAll( mMapThemeMenuPresetActions );
+ mMapThemeMenuPresetActions.clear();
+
+ QString currentTheme = mCanvas->map()->terrainMapTheme();
+
+ QAction *actionFollowMain = new QAction( tr( "(none)" ), mMapThemeMenu );
+ actionFollowMain->setCheckable( true );
+ if ( currentTheme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( currentTheme ) )
+ {
+ actionFollowMain->setChecked( true );
+ }
+ connect( actionFollowMain, &QAction::triggered, this, [ = ]
+ {
+ mCanvas->map()->setTerrainMapTheme( QString() );
+ } );
+ mMapThemeMenuPresetActions.append( actionFollowMain );
+
+ const auto constMapThemes = QgsProject::instance()->mapThemeCollection()->mapThemes();
+ for ( const QString &grpName : constMapThemes )
+ {
+ QAction *a = new QAction( grpName, mMapThemeMenu );
+ a->setCheckable( true );
+ if ( grpName == currentTheme )
+ {
+ a->setChecked( true );
+ }
+ connect( a, &QAction::triggered, this, [a, this]
+ {
+ mCanvas->map()->setTerrainMapTheme( a->text() );
+ } );
+ mMapThemeMenuPresetActions.append( a );
+ }
+ mMapThemeMenu->addActions( mMapThemeMenuPresetActions );
+}
diff --git a/src/app/3d/qgs3dmapcanvasdockwidget.h b/src/app/3d/qgs3dmapcanvasdockwidget.h
index 22551ebb42f9..457f010dee42 100644
--- a/src/app/3d/qgs3dmapcanvasdockwidget.h
+++ b/src/app/3d/qgs3dmapcanvasdockwidget.h
@@ -16,8 +16,10 @@
#ifndef QGS3DMAPCANVASDOCKWIDGET_H
#define QGS3DMAPCANVASDOCKWIDGET_H
+#include "qmenu.h"
#include "qgsdockwidget.h"
#include "qgis_app.h"
+#include "qtoolbutton.h"
#define SIP_NO_FILE
@@ -62,6 +64,7 @@ class APP_EXPORT Qgs3DMapCanvasDockWidget : public QgsDockWidget
void onMainCanvasLayersChanged();
void onMainCanvasColorChanged();
void onTotalPendingJobsCountChanged();
+ void mapThemeMenuAboutToShow();
private:
Qgs3DMapCanvas *mCanvas = nullptr;
@@ -71,6 +74,9 @@ class APP_EXPORT Qgs3DMapCanvasDockWidget : public QgsDockWidget
QLabel *mLabelPendingJobs = nullptr;
Qgs3DMapToolIdentify *mMapToolIdentify = nullptr;
Qgs3DMapToolMeasureLine *mMapToolMeasureLine = nullptr;
+ QMenu *mMapThemeMenu = nullptr;
+ QList mMapThemeMenuPresetActions;
+ QToolButton *mBtnMapThemes = nullptr;
};
#endif // QGS3DMAPCANVASDOCKWIDGET_H
diff --git a/src/app/3d/qgs3dmapconfigwidget.cpp b/src/app/3d/qgs3dmapconfigwidget.cpp
index 17b7d6c633f4..12d8c8e69843 100644
--- a/src/app/3d/qgs3dmapconfigwidget.cpp
+++ b/src/app/3d/qgs3dmapconfigwidget.cpp
@@ -107,14 +107,6 @@ Qgs3DMapConfigWidget::Qgs3DMapConfigWidget( Qgs3DMapSettings *map, QgsMapCanvas
widgetTerrainMaterial->setDiffuseVisible( false );
widgetTerrainMaterial->setMaterial( mMap->terrainShadingMaterial() );
- // populate combo box with map themes
- const QStringList mapThemeNames = QgsProject::instance()->mapThemeCollection()->mapThemes();
- cboTerrainMapTheme->addItem( tr( "(none)" ) ); // item for no map theme
- for ( QString themeName : mapThemeNames )
- cboTerrainMapTheme->addItem( themeName );
-
- cboTerrainMapTheme->setCurrentText( mMap->terrainMapTheme() );
-
widgetLights->setPointLights( mMap->pointLights() );
connect( cboTerrainType, static_cast( &QComboBox::currentIndexChanged ), this, &Qgs3DMapConfigWidget::onTerrainTypeChanged );
@@ -233,8 +225,6 @@ void Qgs3DMapConfigWidget::apply()
mMap->setTerrainShadingEnabled( groupTerrainShading->isChecked() );
mMap->setTerrainShadingMaterial( widgetTerrainMaterial->material() );
- mMap->setTerrainMapTheme( cboTerrainMapTheme->currentText() );
-
mMap->setPointLights( widgetLights->pointLights() );
}
@@ -249,8 +239,6 @@ void Qgs3DMapConfigWidget::onTerrainTypeChanged()
labelTerrainLayer->setVisible( genType == QgsTerrainGenerator::Dem || genType == QgsTerrainGenerator::Mesh );
cboTerrainLayer->setVisible( genType == QgsTerrainGenerator::Dem || genType == QgsTerrainGenerator::Mesh );
groupMeshTerrainShading->setVisible( genType == QgsTerrainGenerator::Mesh );
- labelTerrainMapTheme->setVisible( !( genType == QgsTerrainGenerator::Mesh ) );
- cboTerrainMapTheme->setVisible( !( genType == QgsTerrainGenerator::Mesh ) );
QgsMapLayer *oldTerrainLayer = cboTerrainLayer->currentLayer();
if ( cboTerrainType->currentData() == QgsTerrainGenerator::Dem )
diff --git a/src/app/3d/qgs3dmaptool.cpp b/src/app/3d/qgs3dmaptool.cpp
index ee57dc901ab0..f37bab0102bb 100644
--- a/src/app/3d/qgs3dmaptool.cpp
+++ b/src/app/3d/qgs3dmaptool.cpp
@@ -51,6 +51,11 @@ QCursor Qgs3DMapTool::cursor() const
return Qt::CrossCursor;
}
+void Qgs3DMapTool::onMapSettingsChanged()
+{
+
+}
+
Qgs3DMapCanvas *Qgs3DMapTool::canvas()
{
return mCanvas;
diff --git a/src/app/3d/qgs3dmaptool.h b/src/app/3d/qgs3dmaptool.h
index 6518c66f8f23..199a0ca62b5f 100644
--- a/src/app/3d/qgs3dmaptool.h
+++ b/src/app/3d/qgs3dmaptool.h
@@ -54,6 +54,10 @@ class Qgs3DMapTool : public QObject
Qgs3DMapCanvas *canvas();
+ private slots:
+ //! Called when canvas's map setting is changed
+ virtual void onMapSettingsChanged();
+
protected:
Qgs3DMapCanvas *mCanvas = nullptr;
};
diff --git a/src/app/3d/qgs3dmaptoolidentify.cpp b/src/app/3d/qgs3dmaptoolidentify.cpp
index 9e97916eebd1..92c602bb2a6b 100644
--- a/src/app/3d/qgs3dmaptoolidentify.cpp
+++ b/src/app/3d/qgs3dmaptoolidentify.cpp
@@ -17,6 +17,7 @@
#include "qgsapplication.h"
#include "qgs3dmapcanvas.h"
+#include "qgs3dmapcanvasdockwidget.h"
#include "qgs3dmapscene.h"
#include "qgs3dutils.h"
#include "qgsterrainentity_p.h"
@@ -63,9 +64,8 @@ void Qgs3DMapToolIdentifyPickHandler::handlePickOnVectorLayer( QgsVectorLayer *v
Qgs3DMapToolIdentify::Qgs3DMapToolIdentify( Qgs3DMapCanvas *canvas )
: Qgs3DMapTool( canvas )
{
- connect( mCanvas->scene(), &Qgs3DMapScene::terrainEntityChanged, this, &Qgs3DMapToolIdentify::onTerrainEntityChanged );
-
mPickHandler.reset( new Qgs3DMapToolIdentifyPickHandler( this ) );
+ connect( canvas, &Qgs3DMapCanvas::mapSettingsChanged, this, &Qgs3DMapToolIdentify::onMapSettingsChanged );
}
Qgs3DMapToolIdentify::~Qgs3DMapToolIdentify() = default;
@@ -87,6 +87,7 @@ void Qgs3DMapToolIdentify::activate()
}
mCanvas->scene()->registerPickHandler( mPickHandler.get() );
+ mIsActive = true;
}
void Qgs3DMapToolIdentify::deactivate()
@@ -97,6 +98,7 @@ void Qgs3DMapToolIdentify::deactivate()
}
mCanvas->scene()->unregisterPickHandler( mPickHandler.get() );
+ mIsActive = false;
}
QCursor Qgs3DMapToolIdentify::cursor() const
@@ -104,6 +106,13 @@ QCursor Qgs3DMapToolIdentify::cursor() const
return QgsApplication::getThemeCursor( QgsApplication::Cursor::Identify );
}
+void Qgs3DMapToolIdentify::onMapSettingsChanged()
+{
+ if ( !mIsActive )
+ return;
+ connect( mCanvas->scene(), &Qgs3DMapScene::terrainEntityChanged, this, &Qgs3DMapToolIdentify::onTerrainEntityChanged );
+}
+
void Qgs3DMapToolIdentify::onTerrainPicked( Qt3DRender::QPickEvent *event )
{
if ( event->button() != Qt3DRender::QPickEvent::LeftButton )
@@ -148,6 +157,8 @@ void Qgs3DMapToolIdentify::onTerrainPicked( Qt3DRender::QPickEvent *event )
void Qgs3DMapToolIdentify::onTerrainEntityChanged()
{
+ if ( !mIsActive )
+ return;
// no need to disconnect from the previous entity: it has been destroyed
// start listening to the new terrain entity
if ( QgsTerrainEntity *terrainEntity = mCanvas->scene()->terrainEntity() )
diff --git a/src/app/3d/qgs3dmaptoolidentify.h b/src/app/3d/qgs3dmaptoolidentify.h
index 2a9098f2815e..58a9b8be0009 100644
--- a/src/app/3d/qgs3dmaptoolidentify.h
+++ b/src/app/3d/qgs3dmaptoolidentify.h
@@ -48,10 +48,13 @@ class Qgs3DMapToolIdentify : public Qgs3DMapTool
private slots:
void onTerrainPicked( Qt3DRender::QPickEvent *event );
void onTerrainEntityChanged();
+ void onMapSettingsChanged() override;
private:
std::unique_ptr mPickHandler;
+ bool mIsActive = false;
+
friend class Qgs3DMapToolIdentifyPickHandler;
};
diff --git a/src/app/3d/qgs3dmaptoolmeasureline.cpp b/src/app/3d/qgs3dmaptoolmeasureline.cpp
index 47fd59352226..5a84dc261594 100644
--- a/src/app/3d/qgs3dmaptoolmeasureline.cpp
+++ b/src/app/3d/qgs3dmaptoolmeasureline.cpp
@@ -50,7 +50,6 @@ void Qgs3DMapToolMeasureLinePickHandler::handlePickOnVectorLayer( QgsVectorLayer
Qgs3DMapToolMeasureLine::Qgs3DMapToolMeasureLine( Qgs3DMapCanvas *canvas )
: Qgs3DMapTool( canvas )
{
- connect( mCanvas->scene(), &Qgs3DMapScene::terrainEntityChanged, this, &Qgs3DMapToolMeasureLine::onTerrainEntityChanged );
mPickHandler.reset( new Qgs3DMapToolMeasureLinePickHandler( this ) );
// Dialog
@@ -58,8 +57,7 @@ Qgs3DMapToolMeasureLine::Qgs3DMapToolMeasureLine( Qgs3DMapCanvas *canvas )
mDialog->setWindowFlags( mDialog->windowFlags() | Qt::Tool );
mDialog->restorePosition();
- // Update scale if the terrain vertical scale changed
- connect( mCanvas->map(), &Qgs3DMapSettings::terrainVerticalScaleChanged, this, &Qgs3DMapToolMeasureLine::updateMeasurementLayer );
+ connect( canvas, &Qgs3DMapCanvas::mapSettingsChanged, this, &Qgs3DMapToolMeasureLine::onMapSettingsChanged );
}
Qgs3DMapToolMeasureLine::~Qgs3DMapToolMeasureLine() = default;
@@ -124,6 +122,16 @@ QCursor Qgs3DMapToolMeasureLine::cursor() const
return Qt::CrossCursor;
}
+void Qgs3DMapToolMeasureLine::onMapSettingsChanged()
+{
+ if ( !mIsAlreadyActivated )
+ return;
+ connect( mCanvas->scene(), &Qgs3DMapScene::terrainEntityChanged, this, &Qgs3DMapToolMeasureLine::onTerrainEntityChanged );
+
+ // Update scale if the terrain vertical scale changed
+ connect( mCanvas->map(), &Qgs3DMapSettings::terrainVerticalScaleChanged, this, &Qgs3DMapToolMeasureLine::updateMeasurementLayer );
+}
+
void Qgs3DMapToolMeasureLine::onTerrainPicked( Qt3DRender::QPickEvent *event )
{
handleClick( event, event->worldIntersection() );
@@ -131,6 +139,8 @@ void Qgs3DMapToolMeasureLine::onTerrainPicked( Qt3DRender::QPickEvent *event )
void Qgs3DMapToolMeasureLine::onTerrainEntityChanged()
{
+ if ( !mIsAlreadyActivated )
+ return;
// no need to disconnect from the previous entity: it has been destroyed
// start listening to the new terrain entity
if ( QgsTerrainEntity *terrainEntity = mCanvas->scene()->terrainEntity() )
@@ -169,6 +179,8 @@ void Qgs3DMapToolMeasureLine::handleClick( Qt3DRender::QPickEvent *event, const
void Qgs3DMapToolMeasureLine::updateMeasurementLayer()
{
+ if ( !mMeasurementLayer )
+ return;
double verticalScale = canvas()->map()->terrainVerticalScale();
QgsLineString *line;
if ( verticalScale != 1.0 )
@@ -199,6 +211,8 @@ void Qgs3DMapToolMeasureLine::updateMeasurementLayer()
void Qgs3DMapToolMeasureLine::updateSettings()
{
+ if ( !mMeasurementLayer )
+ return;
// Line style
QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol;
lineSymbol->setRenderAsSimpleLines( true );
diff --git a/src/app/3d/qgs3dmaptoolmeasureline.h b/src/app/3d/qgs3dmaptoolmeasureline.h
index 27631b9dae95..2be6c3ba4c1d 100644
--- a/src/app/3d/qgs3dmaptoolmeasureline.h
+++ b/src/app/3d/qgs3dmaptoolmeasureline.h
@@ -71,6 +71,7 @@ class Qgs3DMapToolMeasureLine : public Qgs3DMapTool
void onTerrainPicked( Qt3DRender::QPickEvent *event );
void onTerrainEntityChanged();
void handleClick( Qt3DRender::QPickEvent *event, const QgsVector3D &worldIntersection );
+ void onMapSettingsChanged() override;
private:
std::unique_ptr mPickHandler;
diff --git a/src/app/3d/qgsmesh3dsymbolwidget.cpp b/src/app/3d/qgsmesh3dsymbolwidget.cpp
index 477cdd30439c..02414f91a0f0 100644
--- a/src/app/3d/qgsmesh3dsymbolwidget.cpp
+++ b/src/app/3d/qgsmesh3dsymbolwidget.cpp
@@ -71,7 +71,7 @@ QgsMesh3dSymbolWidget::QgsMesh3dSymbolWidget( QgsMeshLayer *meshLayer, QWidget *
void QgsMesh3dSymbolWidget::setSymbol( const QgsMesh3DSymbol &symbol )
{
- // Advanced symbology
+ mSymbol = symbol;
mChkSmoothTriangles->setChecked( symbol.smoothedTriangles() );
mChkWireframe->setChecked( symbol.wireframeEnabled() );
mColorButtonWireframe->setColor( symbol.wireframeLineColor() );
@@ -143,7 +143,7 @@ double QgsMesh3dSymbolWidget::lineEditValue( const QLineEdit *lineEdit ) const
QgsMesh3DSymbol QgsMesh3dSymbolWidget::symbol() const
{
- QgsMesh3DSymbol sym;
+ QgsMesh3DSymbol sym = mSymbol;
sym.setSmoothedTriangles( mChkSmoothTriangles->isChecked() );
sym.setWireframeEnabled( mChkWireframe->isChecked() );
diff --git a/src/app/3d/qgsmesh3dsymbolwidget.h b/src/app/3d/qgsmesh3dsymbolwidget.h
index a28c68d9ac4e..fdb5e352f4da 100644
--- a/src/app/3d/qgsmesh3dsymbolwidget.h
+++ b/src/app/3d/qgsmesh3dsymbolwidget.h
@@ -18,6 +18,7 @@
#include
+#include "qgsmesh3dsymbol.h"
#include "ui_qgsmesh3dpropswidget.h"
class QgsMeshDatasetGroupListModel;
@@ -57,6 +58,7 @@ class QgsMesh3dSymbolWidget : public QWidget, private Ui::QgsMesh3dPropsWidget
void setColorRampMinMax( double min, double max );
QgsMeshLayer *mLayer = nullptr;
QgsMeshDatasetGroupListModel *mDatasetGroupListModel = nullptr;
+ QgsMesh3DSymbol mSymbol;
};
diff --git a/src/app/3d/qgsmeshlayer3drendererwidget.cpp b/src/app/3d/qgsmeshlayer3drendererwidget.cpp
index b60cbd899fa2..4618c4e7a6a3 100644
--- a/src/app/3d/qgsmeshlayer3drendererwidget.cpp
+++ b/src/app/3d/qgsmeshlayer3drendererwidget.cpp
@@ -31,14 +31,14 @@ QgsMeshLayer3DRendererWidget::QgsMeshLayer3DRendererWidget( QgsMeshLayer *layer,
QVBoxLayout *layout = new QVBoxLayout( this );
layout->setContentsMargins( 0, 0, 0, 0 );
- chkEnabled = new QCheckBox( tr( "Enable 3D Renderer" ), this );
- layout->addWidget( chkEnabled );
+ mChkEnabled = new QCheckBox( tr( "Enable 3D Renderer" ), this );
+ layout->addWidget( mChkEnabled );
mWidgetMesh = new QgsMesh3dSymbolWidget( layer, this );
mWidgetMesh->configureForDataset();
layout->addWidget( mWidgetMesh );
- connect( chkEnabled, &QCheckBox::clicked, this, &QgsMeshLayer3DRendererWidget::onEnabledClicked );
+ connect( mChkEnabled, &QCheckBox::clicked, this, &QgsMeshLayer3DRendererWidget::onEnabledClicked );
connect( mWidgetMesh, &QgsMesh3dSymbolWidget::changed, this, &QgsMeshLayer3DRendererWidget::widgetChanged );
}
@@ -52,6 +52,7 @@ void QgsMeshLayer3DRendererWidget::setLayer( QgsMeshLayer *layer )
{
QgsMeshLayer3DRenderer *meshRenderer = static_cast( r );
setRenderer( meshRenderer );
+ mWidgetMesh->setEnabled( meshRenderer->symbol()->isEnabled() );
}
else
{
@@ -62,23 +63,15 @@ void QgsMeshLayer3DRendererWidget::setLayer( QgsMeshLayer *layer )
void QgsMeshLayer3DRendererWidget::setRenderer( const QgsMeshLayer3DRenderer *renderer )
{
mRenderer.reset( renderer ? renderer->clone() : nullptr );
- whileBlocking( chkEnabled )->setChecked( ( bool )mRenderer );
+ whileBlocking( mChkEnabled )->setChecked( renderer ? renderer->symbol()->isEnabled() : false );
}
QgsMeshLayer3DRenderer *QgsMeshLayer3DRendererWidget::renderer()
{
- if ( chkEnabled->isChecked() )
- {
- QgsMesh3DSymbol *sym = new QgsMesh3DSymbol( mWidgetMesh->symbol() );
- QgsMeshLayer3DRenderer *r = new QgsMeshLayer3DRenderer( sym );
- r->setLayer( qobject_cast( mLayer ) );
- mRenderer.reset( r );
- }
- else
- {
- mRenderer.reset();
- }
-
+ QgsMesh3DSymbol *sym = new QgsMesh3DSymbol( mWidgetMesh->symbol() );
+ sym->setEnabled( mChkEnabled->isChecked() );
+ mRenderer.reset( new QgsMeshLayer3DRenderer( sym ) );
+ mRenderer->setLayer( qobject_cast( mLayer ) );
return mRenderer.get();
}
@@ -90,6 +83,6 @@ void QgsMeshLayer3DRendererWidget::apply()
void QgsMeshLayer3DRendererWidget::onEnabledClicked()
{
- mWidgetMesh->setEnabled( chkEnabled->isChecked() );
+ mWidgetMesh->setEnabled( mChkEnabled->isChecked() );
emit widgetChanged();
}
diff --git a/src/app/3d/qgsmeshlayer3drendererwidget.h b/src/app/3d/qgsmeshlayer3drendererwidget.h
index 8662f5f214ef..b456e9d006e6 100644
--- a/src/app/3d/qgsmeshlayer3drendererwidget.h
+++ b/src/app/3d/qgsmeshlayer3drendererwidget.h
@@ -49,7 +49,7 @@ class QgsMeshLayer3DRendererWidget : public QgsMapLayerConfigWidget
void onEnabledClicked();
private:
- QCheckBox *chkEnabled = nullptr;
+ QCheckBox *mChkEnabled = nullptr;
QgsMesh3dSymbolWidget *mWidgetMesh = nullptr;
std::unique_ptr mRenderer;
};
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 84e34c814fee..0fc069c3ebf6 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -27,6 +27,7 @@ SET(QGIS_APP_SRCS
qgscustomization.cpp
qgscustomprojectiondialog.cpp
qgsdatumtransformtablewidget.cpp
+ qgsdevtoolspanelwidget.cpp
qgsdiscoverrelationsdialog.cpp
qgsdxfexportdialog.cpp
qgsformannotationdialog.cpp
@@ -61,6 +62,7 @@ SET(QGIS_APP_SRCS
qgsstatusbarmagnifierwidget.cpp
qgsstatusbarscalewidget.cpp
qgstemplateprojectsmodel.cpp
+ qgstemporalcontrollerdockwidget.cpp
qgsversioninfo.cpp
qgsrecentprojectsitemsmodel.cpp
qgsvectorlayerdigitizingproperties.cpp
@@ -162,6 +164,12 @@ SET(QGIS_APP_SRCS
browser/qgsinbuiltdataitemproviders.cpp
+ devtools/qgsappdevtoolutils.cpp
+ devtools/networklogger/qgsnetworklogger.cpp
+ devtools/networklogger/qgsnetworkloggernode.cpp
+ devtools/networklogger/qgsnetworkloggerpanelwidget.cpp
+ devtools/networklogger/qgsnetworkloggerwidgetfactory.cpp
+
labeling/qgslabelpropertydialog.cpp
labeling/qgsmaptoolchangelabelproperties.cpp
labeling/qgsmaptoolpinlabels.cpp
@@ -211,7 +219,7 @@ SET(QGIS_APP_SRCS
mesh/qgsmeshrendereractivedatasetwidget.cpp
mesh/qgsmeshdatasetgrouptreeview.cpp
mesh/qgsmeshcalculatordialog.cpp
- mesh/qgsmeshtimeformatdialog.cpp
+ mesh/qgsmeshstaticdatasetwidget.cpp
)
IF (WITH_3D)
@@ -371,6 +379,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/app
${CMAKE_SOURCE_DIR}/src/app/decorations
+ ${CMAKE_SOURCE_DIR}/src/app/devtools/networklogger
${CMAKE_SOURCE_DIR}/src/app/labeling
${CMAKE_SOURCE_DIR}/src/app/layout
${CMAKE_SOURCE_DIR}/src/app/pluginmanager
@@ -403,11 +412,13 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/effects
${CMAKE_SOURCE_DIR}/src/core/validity
+ ${CMAKE_SOURCE_DIR}/src/core/vectortile
${CMAKE_SOURCE_DIR}/src/gui
${CMAKE_SOURCE_DIR}/src/gui/attributeformconfig
${CMAKE_SOURCE_DIR}/src/gui/symbology
${CMAKE_SOURCE_DIR}/src/gui/attributetable
${CMAKE_SOURCE_DIR}/src/gui/auth
+ ${CMAKE_SOURCE_DIR}/src/gui/devtools
${CMAKE_SOURCE_DIR}/src/gui/labeling
${CMAKE_SOURCE_DIR}/src/gui/numericformats
${CMAKE_SOURCE_DIR}/src/gui/ogr
@@ -419,6 +430,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/gui/layertree
${CMAKE_SOURCE_DIR}/src/gui/locator
${CMAKE_SOURCE_DIR}/src/gui/vector
+ ${CMAKE_SOURCE_DIR}/src/gui/vectortile
${CMAKE_SOURCE_DIR}/src/plugins
${CMAKE_SOURCE_DIR}/src/python
${CMAKE_SOURCE_DIR}/src/native
diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp
index de79dfaa8d47..6d6898a26f9c 100644
--- a/src/app/browser/qgsinbuiltdataitemproviders.cpp
+++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp
@@ -447,6 +447,7 @@ void QgsLayerItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *men
case QgsMapLayerType::PluginLayer:
case QgsMapLayerType::MeshLayer:
+ case QgsMapLayerType::VectorTileLayer:
break;
}
} );
diff --git a/src/app/decorations/qgsdecorationcopyrightdialog.cpp b/src/app/decorations/qgsdecorationcopyrightdialog.cpp
index b60e31399192..64df48c2da95 100644
--- a/src/app/decorations/qgsdecorationcopyrightdialog.cpp
+++ b/src/app/decorations/qgsdecorationcopyrightdialog.cpp
@@ -130,5 +130,5 @@ void QgsDecorationCopyrightDialog::apply()
void QgsDecorationCopyrightDialog::showHelp()
{
- QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#copyright-and-title-labels" ) );
+ QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#copyright_decoration" ) );
}
diff --git a/src/app/decorations/qgsdecorationscalebar.cpp b/src/app/decorations/qgsdecorationscalebar.cpp
index 5bb26ec8ea4f..9f65da5c58c8 100644
--- a/src/app/decorations/qgsdecorationscalebar.cpp
+++ b/src/app/decorations/qgsdecorationscalebar.cpp
@@ -35,6 +35,7 @@ email : sbr00pwb@users.sourceforge.net
#include "qgsunittypes.h"
#include "qgssettings.h"
#include "qgssymbollayerutils.h"
+#include "qgsfillsymbollayer.h"
#include "qgsdoubleboxscalebarrenderer.h"
#include "qgsnumericscalebarrenderer.h"
@@ -153,21 +154,54 @@ void QgsDecorationScaleBar::setupScaleBar()
std::unique_ptr< QgsTicksScaleBarRenderer > tickStyle = qgis::make_unique< QgsTicksScaleBarRenderer >();
tickStyle->setTickPosition( mStyleIndex == 0 ? QgsTicksScaleBarRenderer::TicksDown : QgsTicksScaleBarRenderer::TicksUp );
mStyle = std::move( tickStyle );
- mSettings.setFillColor( mColor );
- mSettings.setLineColor( mColor ); // Compatibility with pre 3.2 configuration
+
+ std::unique_ptr< QgsFillSymbol > fillSymbol = qgis::make_unique< QgsFillSymbol >();
+ fillSymbol->setColor( mColor ); // Compatibility with pre 3.2 configuration
+ if ( QgsSimpleFillSymbolLayer *fill = dynamic_cast< QgsSimpleFillSymbolLayer * >( fillSymbol->symbolLayer( 0 ) ) )
+ {
+ fill->setStrokeStyle( Qt::NoPen );
+ }
+ mSettings.setFillSymbol( fillSymbol.release() );
+
+ std::unique_ptr< QgsLineSymbol > lineSymbol = qgis::make_unique< QgsLineSymbol >();
+ lineSymbol->setColor( mColor ); // Compatibility with pre 3.2 configuration
+ lineSymbol->setWidth( 0.3 );
+ lineSymbol->setOutputUnit( QgsUnitTypes::RenderMillimeters );
+ mSettings.setLineSymbol( lineSymbol.release() );
mSettings.setHeight( 2.2 );
- mSettings.setLineWidth( 0.3 );
break;
}
case 2:
case 3:
+ {
mStyle = qgis::make_unique< QgsSingleBoxScaleBarRenderer >();
- mSettings.setFillColor( mColor );
- mSettings.setFillColor2( QColor( "transparent" ) );
- mSettings.setLineColor( mOutlineColor );
+
+
+ std::unique_ptr< QgsFillSymbol > fillSymbol = qgis::make_unique< QgsFillSymbol >();
+ fillSymbol->setColor( mColor );
+ if ( QgsSimpleFillSymbolLayer *fill = dynamic_cast< QgsSimpleFillSymbolLayer * >( fillSymbol->symbolLayer( 0 ) ) )
+ {
+ fill->setStrokeStyle( Qt::NoPen );
+ }
+ mSettings.setFillSymbol( fillSymbol.release() );
+
+ std::unique_ptr< QgsFillSymbol > fillSymbol2 = qgis::make_unique< QgsFillSymbol >();
+ fillSymbol2->setColor( QColor( 255, 255, 255, 0 ) );
+ if ( QgsSimpleFillSymbolLayer *fill = dynamic_cast< QgsSimpleFillSymbolLayer * >( fillSymbol2->symbolLayer( 0 ) ) )
+ {
+ fill->setStrokeStyle( Qt::NoPen );
+ }
+ mSettings.setAlternateFillSymbol( fillSymbol2.release() );
+
mSettings.setHeight( mStyleIndex == 2 ? 1 : 3 );
- mSettings.setLineWidth( mStyleIndex == 2 ? 0.2 : 0.3 );
+ std::unique_ptr< QgsLineSymbol > lineSymbol = qgis::make_unique< QgsLineSymbol >();
+ lineSymbol->setColor( mOutlineColor ); // Compatibility with pre 3.2 configuration
+ lineSymbol->setWidth( mStyleIndex == 2 ? 0.2 : 0.3 );
+ lineSymbol->setOutputUnit( QgsUnitTypes::RenderMillimeters );
+ mSettings.setLineSymbol( lineSymbol.release() );
+
break;
+ }
}
mSettings.setLabelBarSpace( 1.8 );
}
@@ -317,7 +351,7 @@ void QgsDecorationScaleBar::render( const QgsMapSettings &mapSettings, QgsRender
scaleContext.scale = mapSettings.scale();
//Calculate total width of scale bar and label
- QSizeF size = mStyle->calculateBoxSize( mSettings, scaleContext );
+ QSizeF size = mStyle->calculateBoxSize( context, mSettings, scaleContext );
size.setWidth( context.convertToPainterUnits( size.width(), QgsUnitTypes::RenderMillimeters ) );
size.setHeight( context.convertToPainterUnits( size.height(), QgsUnitTypes::RenderMillimeters ) );
diff --git a/src/app/decorations/qgsdecorationtitledialog.cpp b/src/app/decorations/qgsdecorationtitledialog.cpp
index bb8ea571a478..eeb524f99d7b 100644
--- a/src/app/decorations/qgsdecorationtitledialog.cpp
+++ b/src/app/decorations/qgsdecorationtitledialog.cpp
@@ -138,5 +138,5 @@ void QgsDecorationTitleDialog::apply()
void QgsDecorationTitleDialog::showHelp()
{
- QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#copyright-and-title-labels" ) );
+ QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#title_label_decoration" ) );
}
diff --git a/src/app/devtools/networklogger/qgsnetworklogger.cpp b/src/app/devtools/networklogger/qgsnetworklogger.cpp
new file mode 100644
index 000000000000..06539218f2fd
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworklogger.cpp
@@ -0,0 +1,333 @@
+/***************************************************************************
+ qgsnetworklogger.cpp
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 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 "qgsnetworklogger.h"
+#include "qgsnetworkloggernode.h"
+#include "qgssettings.h"
+#include "qgis.h"
+#include
+#include
+#include
+
+QgsNetworkLogger::QgsNetworkLogger( QgsNetworkAccessManager *manager, QObject *parent )
+ : QAbstractItemModel( parent )
+ , mNam( manager )
+ , mRootNode( qgis::make_unique< QgsNetworkLoggerRootNode >() )
+{
+ // logger must be created on the main thread
+ Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );
+ Q_ASSERT( mNam->thread() == QApplication::instance()->thread() );
+
+ if ( QgsSettings().value( QStringLiteral( "logNetworkRequests" ), false, QgsSettings::App ).toBool() )
+ enableLogging( true );
+}
+
+bool QgsNetworkLogger::isLogging() const
+{
+ return mIsLogging;
+}
+
+QgsNetworkLogger::~QgsNetworkLogger() = default;
+
+void QgsNetworkLogger::enableLogging( bool enabled )
+{
+ if ( enabled )
+ {
+ connect( mNam, qgis::overload< QgsNetworkRequestParameters >::of( &QgsNetworkAccessManager::requestAboutToBeCreated ), this, &QgsNetworkLogger::requestAboutToBeCreated, Qt::UniqueConnection );
+ connect( mNam, qgis::overload< QgsNetworkReplyContent >::of( &QgsNetworkAccessManager::finished ), this, &QgsNetworkLogger::requestFinished, Qt::UniqueConnection );
+ connect( mNam, qgis::overload< QgsNetworkRequestParameters >::of( &QgsNetworkAccessManager::requestTimedOut ), this, &QgsNetworkLogger::requestTimedOut, Qt::UniqueConnection );
+ connect( mNam, &QgsNetworkAccessManager::downloadProgress, this, &QgsNetworkLogger::downloadProgress, Qt::UniqueConnection );
+ connect( mNam, &QgsNetworkAccessManager::requestEncounteredSslErrors, this, &QgsNetworkLogger::requestEncounteredSslErrors, Qt::UniqueConnection );
+ }
+ else
+ {
+ disconnect( mNam, qgis::overload< QgsNetworkRequestParameters >::of( &QgsNetworkAccessManager::requestAboutToBeCreated ), this, &QgsNetworkLogger::requestAboutToBeCreated );
+ disconnect( mNam, qgis::overload< QgsNetworkReplyContent >::of( &QgsNetworkAccessManager::finished ), this, &QgsNetworkLogger::requestFinished );
+ disconnect( mNam, qgis::overload< QgsNetworkRequestParameters >::of( &QgsNetworkAccessManager::requestTimedOut ), this, &QgsNetworkLogger::requestTimedOut );
+ disconnect( mNam, &QgsNetworkAccessManager::downloadProgress, this, &QgsNetworkLogger::downloadProgress );
+ disconnect( mNam, &QgsNetworkAccessManager::requestEncounteredSslErrors, this, &QgsNetworkLogger::requestEncounteredSslErrors );
+ }
+ mIsLogging = enabled;
+}
+
+void QgsNetworkLogger::clear()
+{
+ beginResetModel();
+ mRequestGroups.clear();
+ mRootNode->clear();
+ endResetModel();
+}
+
+void QgsNetworkLogger::requestAboutToBeCreated( QgsNetworkRequestParameters parameters )
+{
+ const int childCount = mRootNode->childCount();
+
+ beginInsertRows( QModelIndex(), childCount, childCount );
+
+ std::unique_ptr< QgsNetworkLoggerRequestGroup > group = qgis::make_unique< QgsNetworkLoggerRequestGroup >( parameters );
+ mRequestGroups.insert( parameters.requestId(), group.get() );
+ mRootNode->addChild( std::move( group ) );
+ endInsertRows();
+}
+
+void QgsNetworkLogger::requestFinished( QgsNetworkReplyContent content )
+{
+ QgsNetworkLoggerRequestGroup *requestGroup = mRequestGroups.value( content.requestId() );
+ if ( !requestGroup )
+ return;
+
+ // find the row: the position of the request in the rootNode
+ const QModelIndex requestIndex = node2index( requestGroup );
+ if ( !requestIndex.isValid() )
+ return;
+
+ beginInsertRows( requestIndex, requestGroup->childCount(), requestGroup->childCount() );
+ requestGroup->setReply( content );
+ endInsertRows();
+
+ emit dataChanged( requestIndex, requestIndex );
+}
+
+void QgsNetworkLogger::requestTimedOut( QgsNetworkRequestParameters parameters )
+{
+ QgsNetworkLoggerRequestGroup *requestGroup = mRequestGroups.value( parameters.requestId() );
+ if ( !requestGroup )
+ return;
+
+ const QModelIndex requestIndex = node2index( requestGroup );
+ if ( !requestIndex.isValid() )
+ return;
+
+ requestGroup->setTimedOut();
+
+ emit dataChanged( requestIndex, requestIndex );
+}
+
+void QgsNetworkLogger::downloadProgress( int requestId, qint64 bytesReceived, qint64 bytesTotal )
+{
+ QgsNetworkLoggerRequestGroup *requestGroup = mRequestGroups.value( requestId );
+ if ( !requestGroup )
+ return;
+
+ const QModelIndex requestIndex = node2index( requestGroup );
+ if ( !requestIndex.isValid() )
+ return;
+
+ requestGroup->setProgress( bytesReceived, bytesTotal );
+
+ emit dataChanged( requestIndex, requestIndex, QVector() << Qt::ToolTipRole );
+}
+
+void QgsNetworkLogger::requestEncounteredSslErrors( int requestId, const QList &errors )
+{
+ QgsNetworkLoggerRequestGroup *requestGroup = mRequestGroups.value( requestId );
+ if ( !requestGroup )
+ return;
+
+ const QModelIndex requestIndex = node2index( requestGroup );
+ if ( !requestIndex.isValid() )
+ return;
+
+ beginInsertRows( requestIndex, requestGroup->childCount(), requestGroup->childCount() );
+ requestGroup->setSslErrors( errors );
+ endInsertRows();
+
+ emit dataChanged( requestIndex, requestIndex );
+}
+
+QgsNetworkLoggerNode *QgsNetworkLogger::index2node( const QModelIndex &index ) const
+{
+ if ( !index.isValid() )
+ return mRootNode.get();
+
+ return reinterpret_cast( index.internalPointer() );
+}
+
+QList QgsNetworkLogger::actions( const QModelIndex &index, QObject *parent )
+{
+ QgsNetworkLoggerNode *node = index2node( index );
+ if ( !node )
+ return QList< QAction * >();
+
+ return node->actions( parent );
+}
+
+QModelIndex QgsNetworkLogger::node2index( QgsNetworkLoggerNode *node ) const
+{
+ if ( !node || !node->parent() )
+ return QModelIndex(); // this is the only root item -> invalid index
+
+ QModelIndex parentIndex = node2index( node->parent() );
+
+ int row = node->parent()->indexOf( node );
+ Q_ASSERT( row >= 0 );
+ return index( row, 0, parentIndex );
+}
+
+QModelIndex QgsNetworkLogger::indexOfParentLayerTreeNode( QgsNetworkLoggerNode *parentNode ) const
+{
+ Q_ASSERT( parentNode );
+
+ QgsNetworkLoggerGroup *grandParentNode = parentNode->parent();
+ if ( !grandParentNode )
+ return QModelIndex(); // root node -> invalid index
+
+ int row = grandParentNode->indexOf( parentNode );
+ Q_ASSERT( row >= 0 );
+
+ return createIndex( row, 0, parentNode );
+}
+
+void QgsNetworkLogger::removeRows( const QList &rows )
+{
+ QList< int > res = rows;
+ std::sort( res.begin(), res.end(), std::greater< int >() );
+
+ for ( int row : qgis::as_const( res ) )
+ {
+ int popId = data( index( row, 0, QModelIndex() ), QgsNetworkLoggerNode::RoleId ).toInt();
+ mRequestGroups.remove( popId );
+
+ beginRemoveRows( QModelIndex(), row, row );
+ mRootNode->removeRow( row );
+ endRemoveRows();
+ }
+}
+
+QgsNetworkLoggerRootNode *QgsNetworkLogger::rootGroup()
+{
+ return mRootNode.get();
+}
+
+int QgsNetworkLogger::rowCount( const QModelIndex &parent ) const
+{
+ QgsNetworkLoggerNode *n = index2node( parent );
+ if ( !n )
+ return 0;
+
+ return n->childCount();
+}
+
+int QgsNetworkLogger::columnCount( const QModelIndex &parent ) const
+{
+ Q_UNUSED( parent )
+ return 1;
+}
+
+QModelIndex QgsNetworkLogger::index( int row, int column, const QModelIndex &parent ) const
+{
+ if ( column < 0 || column >= columnCount( parent ) ||
+ row < 0 || row >= rowCount( parent ) )
+ return QModelIndex();
+
+ QgsNetworkLoggerGroup *n = dynamic_cast< QgsNetworkLoggerGroup * >( index2node( parent ) );
+ if ( !n )
+ return QModelIndex(); // have no children
+
+ return createIndex( row, column, n->childAt( row ) );
+}
+
+QModelIndex QgsNetworkLogger::parent( const QModelIndex &child ) const
+{
+ if ( !child.isValid() )
+ return QModelIndex();
+
+ if ( QgsNetworkLoggerNode *n = index2node( child ) )
+ {
+ return indexOfParentLayerTreeNode( n->parent() ); // must not be null
+ }
+ else
+ {
+ Q_ASSERT( false );
+ return QModelIndex();
+ }
+}
+
+QVariant QgsNetworkLogger::data( const QModelIndex &index, int role ) const
+{
+ if ( !index.isValid() || index.column() > 1 )
+ return QVariant();
+
+ QgsNetworkLoggerNode *node = index2node( index );
+ if ( !node )
+ return QVariant();
+
+ return node->data( role );
+}
+
+Qt::ItemFlags QgsNetworkLogger::flags( const QModelIndex &index ) const
+{
+ if ( !index.isValid() )
+ {
+ Qt::ItemFlags rootFlags = Qt::ItemFlags();
+ return rootFlags;
+ }
+
+ Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+ return f;
+}
+
+QVariant QgsNetworkLogger::headerData( int section, Qt::Orientation orientation, int role ) const
+{
+ if ( section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole )
+ return tr( "Requests" );
+ return QVariant();
+}
+
+
+//
+// QgsNetworkLoggerProxyModel
+//
+
+QgsNetworkLoggerProxyModel::QgsNetworkLoggerProxyModel( QgsNetworkLogger *logger, QObject *parent )
+ : QSortFilterProxyModel( parent )
+ , mLogger( logger )
+{
+ setSourceModel( mLogger );
+}
+
+void QgsNetworkLoggerProxyModel::setFilterString( const QString &string )
+{
+ mFilterString = string;
+ invalidateFilter();
+}
+
+void QgsNetworkLoggerProxyModel::setShowSuccessful( bool show )
+{
+ mShowSuccessful = show;
+ invalidateFilter();
+}
+
+void QgsNetworkLoggerProxyModel::setShowTimeouts( bool show )
+{
+ mShowTimeouts = show;
+ invalidateFilter();
+}
+
+bool QgsNetworkLoggerProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
+{
+ QgsNetworkLoggerNode *node = mLogger->index2node( mLogger->index( source_row, 0, source_parent ) );
+ if ( QgsNetworkLoggerRequestGroup *request = dynamic_cast< QgsNetworkLoggerRequestGroup * >( node ) )
+ {
+ if ( ( request->status() == QgsNetworkLoggerRequestGroup::Status::Complete || request->status() == QgsNetworkLoggerRequestGroup::Status::Canceled )
+ & !mShowSuccessful )
+ return false;
+ else if ( request->status() == QgsNetworkLoggerRequestGroup::Status::TimeOut && !mShowTimeouts )
+ return false;
+ return mFilterString.isEmpty() || request->url().url().contains( mFilterString, Qt::CaseInsensitive );
+ }
+
+ return true;
+}
diff --git a/src/app/devtools/networklogger/qgsnetworklogger.h b/src/app/devtools/networklogger/qgsnetworklogger.h
new file mode 100644
index 000000000000..eea54102aa5d
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworklogger.h
@@ -0,0 +1,169 @@
+/***************************************************************************
+ qgsnetworklogger.h
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSNETWORKLOGGER_H
+#define QGSNETWORKLOGGER_H
+
+#include
+#include
+#include
+#include "qgsnetworkaccessmanager.h"
+
+class QgsNetworkLoggerNode;
+class QgsNetworkLoggerRequestGroup;
+class QgsNetworkLoggerRootNode;
+class QAction;
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLogger
+ * \brief Logs network requests from a QgsNetworkAccessManager, converting them
+ * to a QAbstractItemModel representing the request and response details.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLogger : public QAbstractItemModel
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor for QgsNetworkLogger, logging requests from the specified \a manager.
+ *
+ * \warning QgsNetworkLogger must be created on the main thread, using the main thread's
+ * QgsNetworkAccessManager instance.
+ */
+ QgsNetworkLogger( QgsNetworkAccessManager *manager, QObject *parent );
+ ~QgsNetworkLogger() override;
+
+ /**
+ * Returns TRUE if the logger is currently logging activity.
+ */
+ bool isLogging() const;
+
+ // Implementation of virtual functions from QAbstractItemModel
+
+ int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
+ int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
+ QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override;
+ QModelIndex parent( const QModelIndex &child ) const override;
+ QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
+ Qt::ItemFlags flags( const QModelIndex &index ) const override;
+ QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
+
+ /**
+ * Returns node for given index. Returns root node for invalid index.
+ */
+ QgsNetworkLoggerNode *index2node( const QModelIndex &index ) const;
+
+ /**
+ * Returns a list of actions corresponding to the item at the specified \a index.
+ *
+ * The actions should be parented to \a parent.
+ */
+ QList< QAction * > actions( const QModelIndex &index, QObject *parent );
+
+
+ /**
+ * Removes a list of request \a rows from the log.
+ */
+ void removeRows( const QList< int > &rows );
+
+ /**
+ * Returns the root node of the log.
+ */
+ QgsNetworkLoggerRootNode *rootGroup();
+
+ static constexpr int MAX_LOGGED_REQUESTS = 1000;
+
+ public slots:
+
+ /**
+ * Enables or disables logging, depending on the value of \a enabled.
+ */
+ void enableLogging( bool enabled );
+
+ /**
+ * Clears all logged entries.
+ */
+ void clear();
+
+ private slots:
+ void requestAboutToBeCreated( QgsNetworkRequestParameters parameters );
+ void requestFinished( QgsNetworkReplyContent content );
+ void requestTimedOut( QgsNetworkRequestParameters parameters );
+ void downloadProgress( int requestId, qint64 bytesReceived, qint64 bytesTotal );
+ void requestEncounteredSslErrors( int requestId, const QList &errors );
+
+ private:
+
+ //! Returns index for a given node
+ QModelIndex node2index( QgsNetworkLoggerNode *node ) const;
+ QModelIndex indexOfParentLayerTreeNode( QgsNetworkLoggerNode *parentNode ) const;
+
+ QgsNetworkAccessManager *mNam = nullptr;
+ bool mIsLogging = false;
+
+ std::unique_ptr< QgsNetworkLoggerRootNode > mRootNode;
+
+ QHash< int, QgsNetworkLoggerRequestGroup * > mRequestGroups;
+
+};
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerProxyModel
+ * \brief A proxy model for filtering QgsNetworkLogger models by url string subsets
+ * or request status.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerProxyModel : public QSortFilterProxyModel
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerProxyModel, filtering the specified network \a logger.
+ */
+ QgsNetworkLoggerProxyModel( QgsNetworkLogger *logger, QObject *parent );
+
+ /**
+ * Sets a filter \a string to apply to request URLs.
+ */
+ void setFilterString( const QString &string );
+
+ /**
+ * Sets whether successful requests should be shown.
+ */
+ void setShowSuccessful( bool show );
+
+ /**
+ * Sets whether timed out requests should be shown.
+ */
+ void setShowTimeouts( bool show );
+
+ protected:
+ bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override;
+
+ private:
+
+ QgsNetworkLogger *mLogger = nullptr;
+
+ QString mFilterString;
+ bool mShowSuccessful = true;
+ bool mShowTimeouts = true;
+};
+
+#endif // QGSNETWORKLOGGER_H
diff --git a/src/app/devtools/networklogger/qgsnetworkloggernode.cpp b/src/app/devtools/networklogger/qgsnetworkloggernode.cpp
new file mode 100644
index 000000000000..2f0d1bbf3186
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworkloggernode.cpp
@@ -0,0 +1,627 @@
+/***************************************************************************
+ qgsnetworkloggernode.cpp
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 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 "qgsnetworkloggernode.h"
+#include "qgis.h"
+#include "qgsjsonutils.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//
+// QgsNetworkLoggerNode
+//
+
+QgsNetworkLoggerNode::QgsNetworkLoggerNode() = default;
+QgsNetworkLoggerNode::~QgsNetworkLoggerNode() = default;
+
+
+//
+// QgsNetworkLoggerGroup
+//
+
+QgsNetworkLoggerGroup::QgsNetworkLoggerGroup( const QString &title )
+ : mGroupTitle( title )
+{
+}
+
+void QgsNetworkLoggerGroup::addChild( std::unique_ptr child )
+{
+ if ( !child )
+ return;
+
+ Q_ASSERT( !child->mParent );
+ child->mParent = this;
+
+ mChildren.emplace_back( std::move( child ) );
+}
+
+int QgsNetworkLoggerGroup::indexOf( QgsNetworkLoggerNode *child ) const
+{
+ Q_ASSERT( child->mParent == this );
+ auto it = std::find_if( mChildren.begin(), mChildren.end(), [&]( const std::unique_ptr &p )
+ {
+ return p.get() == child;
+ } );
+ if ( it != mChildren.end() )
+ return std::distance( mChildren.begin(), it );
+ return -1;
+}
+
+QgsNetworkLoggerNode *QgsNetworkLoggerGroup::childAt( int index )
+{
+ Q_ASSERT( static_cast< std::size_t >( index ) < mChildren.size() );
+ return mChildren[ index ].get();
+}
+
+void QgsNetworkLoggerGroup::clear()
+{
+ mChildren.clear();
+}
+
+QVariant QgsNetworkLoggerGroup::data( int role ) const
+{
+ switch ( role )
+ {
+ case Qt::DisplayRole:
+ return mGroupTitle;
+
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+QVariant QgsNetworkLoggerGroup::toVariant() const
+{
+ QVariantMap res;
+ for ( const std::unique_ptr< QgsNetworkLoggerNode > &child : mChildren )
+ {
+ if ( const QgsNetworkLoggerValueNode *valueNode = dynamic_cast< const QgsNetworkLoggerValueNode *>( child.get() ) )
+ {
+ res.insert( valueNode->key(), valueNode->value() );
+ }
+ }
+ return res;
+}
+
+//
+// QgsNetworkLoggerRootNode
+//
+
+QgsNetworkLoggerRootNode::QgsNetworkLoggerRootNode()
+ : QgsNetworkLoggerGroup( QString() )
+{
+
+}
+
+QVariant QgsNetworkLoggerRootNode::data( int ) const
+{
+ return QVariant();
+}
+
+void QgsNetworkLoggerRootNode::removeRow( int row )
+{
+ mChildren.erase( mChildren.begin() + row );
+}
+
+QVariant QgsNetworkLoggerRootNode::toVariant() const
+{
+ QVariantList res;
+ for ( const std::unique_ptr< QgsNetworkLoggerNode > &child : mChildren )
+ res << child->toVariant();
+ return res;
+}
+
+
+//
+// QgsNetworkLoggerValueNode
+//
+QgsNetworkLoggerValueNode::QgsNetworkLoggerValueNode( const QString &key, const QString &value, const QColor &color )
+ : mKey( key )
+ , mValue( value )
+ , mColor( color )
+{
+
+}
+
+QVariant QgsNetworkLoggerValueNode::data( int role ) const
+{
+ switch ( role )
+ {
+ case Qt::DisplayRole:
+ case Qt::ToolTipRole:
+ {
+ return QStringLiteral( "%1: %2" ).arg( mKey.leftJustified( 30, ' ' ), mValue );
+ }
+
+ case Qt::ForegroundRole:
+ {
+ if ( mColor.isValid() )
+ return QBrush( mColor );
+ break;
+ }
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+//
+// QgsNetworkLoggerGroup
+//
+
+void QgsNetworkLoggerGroup::addKeyValueNode( const QString &key, const QString &value, const QColor &color )
+{
+ addChild( qgis::make_unique< QgsNetworkLoggerValueNode >( key, value, color ) );
+}
+
+
+//
+// QgsNetworkLoggerRequestGroup
+//
+
+QgsNetworkLoggerRequestGroup::QgsNetworkLoggerRequestGroup( const QgsNetworkRequestParameters &request )
+ : QgsNetworkLoggerGroup( QString() )
+ , mUrl( request.request().url() )
+ , mRequestId( request.requestId() )
+ , mOperation( request.operation() )
+ , mData( request.content() )
+{
+ const QList headers = request.request().rawHeaderList();
+ for ( const QByteArray &header : headers )
+ {
+ mHeaders.append( qMakePair( QString( header ), QString( request.request().rawHeader( header ) ) ) );
+ }
+
+ std::unique_ptr< QgsNetworkLoggerRequestDetailsGroup > detailsGroup = qgis::make_unique< QgsNetworkLoggerRequestDetailsGroup >( request );
+ mDetailsGroup = detailsGroup.get();
+ addChild( std::move( detailsGroup ) );
+
+ mTimer.start();
+}
+
+QVariant QgsNetworkLoggerRequestGroup::data( int role ) const
+{
+ switch ( role )
+ {
+ case Qt::DisplayRole:
+ return QStringLiteral( "%1 %2 %3" ).arg( QString::number( mRequestId ),
+ operationToString( mOperation ),
+ mUrl.url() );
+
+ case Qt::ToolTipRole:
+ {
+ QString bytes = QObject::tr( "unknown" );
+ if ( mBytesTotal != 0 )
+ {
+ if ( mBytesReceived > 0 && mBytesReceived < mBytesTotal )
+ bytes = QStringLiteral( "%1/%2" ).arg( QString::number( mBytesReceived ), QString::number( mBytesTotal ) );
+ else if ( mBytesReceived > 0 && mBytesReceived == mBytesTotal )
+ bytes = QString::number( mBytesTotal );
+ }
+ // ?? adding instead of \n after (very long) url seems to break url up
+ // COMPLETE, Status: 200 - text/xml; charset=utf-8 - 2334 bytes - 657 milliseconds
+ return QStringLiteral( "%1 %2 - Status: %3 - %4 - %5 bytes - %6 msec - %7 replies" )
+ .arg( mUrl.url(),
+ statusToString( mStatus ),
+ QString::number( mHttpStatus ),
+ mContentType,
+ bytes,
+ mStatus == Status::Pending ? QString::number( mTimer.elapsed() / 1000 ) : QString::number( mTotalTime ),
+ QString::number( mReplies ) );
+ }
+
+ case RoleStatus:
+ return static_cast< int >( mStatus );
+
+ case RoleId:
+ return mRequestId;
+
+ case Qt::ForegroundRole:
+ {
+ if ( mHasSslErrors )
+ return QBrush( QColor( 180, 65, 210 ) );
+ switch ( mStatus )
+ {
+ case QgsNetworkLoggerRequestGroup::Status::Pending:
+ case QgsNetworkLoggerRequestGroup::Status::Canceled:
+ return QBrush( QColor( 0, 0, 0, 100 ) );
+ case QgsNetworkLoggerRequestGroup::Status::Error:
+ return QBrush( QColor( 235, 10, 10 ) );
+ case QgsNetworkLoggerRequestGroup::Status::TimeOut:
+ return QBrush( QColor( 235, 10, 10 ) );
+ case QgsNetworkLoggerRequestGroup::Status::Complete:
+ break;
+ }
+ break;
+ }
+
+ case Qt::FontRole:
+ {
+ if ( mStatus == Status::Canceled )
+ {
+ QFont f;
+ f.setStrikeOut( true );
+ return f;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+QList QgsNetworkLoggerRequestGroup::actions( QObject *parent )
+{
+ QList< QAction * > res;
+ QAction *openUrlAction = new QAction( QObject::tr( "Open URL" ), parent );
+ QObject::connect( openUrlAction, &QAction::triggered, openUrlAction, [ = ]
+ {
+ QDesktopServices::openUrl( mUrl );
+ } );
+ res << openUrlAction;
+
+ QAction *copyUrlAction = new QAction( QObject::tr( "Copy URL" ), parent );
+ QObject::connect( copyUrlAction, &QAction::triggered, openUrlAction, [ = ]
+ {
+ QApplication::clipboard()->setText( mUrl.url() );
+ } );
+ res << copyUrlAction;
+
+ QAction *copyAsCurlAction = new QAction( QObject::tr( "Copy As cURL" ), parent );
+ QObject::connect( copyAsCurlAction, &QAction::triggered, copyAsCurlAction, [ = ]
+ {
+ QString curlHeaders;
+ for ( const QPair< QString, QString > &header : qgis::as_const( mHeaders ) )
+ curlHeaders += QStringLiteral( "-H '%1: %2' " ).arg( header.first, header.second );
+
+ QString curlData;
+ if ( mOperation == QNetworkAccessManager::PostOperation || mOperation == QNetworkAccessManager::PutOperation )
+ curlData = QStringLiteral( "--data '%1' " ).arg( QString( mData ) );
+
+ QString curlCmd = QStringLiteral( "curl '%1' %2 %3--compressed" ).arg(
+ mUrl.url(),
+ curlHeaders,
+ curlData );
+ QApplication::clipboard()->setText( curlCmd );
+ } );
+ res << copyAsCurlAction;
+
+ QAction *copyJsonAction = new QAction( QObject::tr( "Copy as JSON" ), parent );
+ QObject::connect( copyJsonAction, &QAction::triggered, openUrlAction, [ = ]
+ {
+ const QVariant value = toVariant();
+ const QString json = QString::fromStdString( QgsJsonUtils::jsonFromVariant( value ).dump( 2 ) );
+ QApplication::clipboard()->setText( json );
+
+ } );
+ res << copyJsonAction;
+
+ return res;
+}
+
+QVariant QgsNetworkLoggerRequestGroup::toVariant() const
+{
+ QVariantMap res;
+ res.insert( QStringLiteral( "URL" ), mUrl.url() );
+ res.insert( QStringLiteral( "Total time (ms)" ), mTotalTime );
+ res.insert( QStringLiteral( "Bytes Received" ), mBytesReceived );
+ res.insert( QStringLiteral( "Bytes Total" ), mBytesTotal );
+ res.insert( QStringLiteral( "Replies" ), mReplies );
+ if ( mDetailsGroup )
+ {
+ const QVariantMap detailsMap = mDetailsGroup->toVariant().toMap();
+ for ( auto it = detailsMap.constBegin(); it != detailsMap.constEnd(); ++it )
+ res.insert( it.key(), it.value() );
+ }
+ if ( mReplyGroup )
+ {
+ res.insert( QObject::tr( "Reply" ), mReplyGroup->toVariant() );
+ }
+ if ( mSslErrorsGroup )
+ {
+ res.insert( QObject::tr( "SSL Errors" ), mSslErrorsGroup->toVariant() );
+ }
+ return res;
+}
+
+void QgsNetworkLoggerRequestGroup::setReply( const QgsNetworkReplyContent &reply )
+{
+ switch ( reply.error() )
+ {
+ case QNetworkReply::OperationCanceledError:
+ mStatus = Status::Canceled;
+ break;
+
+ case QNetworkReply::NoError:
+ mStatus = Status::Complete;
+ break;
+
+ default:
+ mStatus = Status::Error;
+ break;
+ }
+
+ mTotalTime = mTimer.elapsed();
+ mHttpStatus = reply.attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
+ mContentType = reply.rawHeader( "Content - Type" );
+
+ std::unique_ptr< QgsNetworkLoggerReplyGroup > replyGroup = qgis::make_unique< QgsNetworkLoggerReplyGroup >( reply ) ;
+ mReplyGroup = replyGroup.get();
+ addChild( std::move( replyGroup ) );
+}
+
+void QgsNetworkLoggerRequestGroup::setTimedOut()
+{
+ mStatus = Status::TimeOut;
+}
+
+void QgsNetworkLoggerRequestGroup::setProgress( qint64 bytesReceived, qint64 bytesTotal )
+{
+ mReplies++;
+ mBytesReceived = bytesReceived;
+ mBytesTotal = bytesTotal;
+}
+
+void QgsNetworkLoggerRequestGroup::setSslErrors( const QList &errors )
+{
+ mHasSslErrors = !errors.empty();
+ if ( mHasSslErrors )
+ {
+ std::unique_ptr< QgsNetworkLoggerSslErrorGroup > errorGroup = qgis::make_unique< QgsNetworkLoggerSslErrorGroup >( errors );
+ mSslErrorsGroup = errorGroup.get();
+ addChild( std::move( errorGroup ) );
+ }
+}
+
+QString QgsNetworkLoggerRequestGroup::operationToString( QNetworkAccessManager::Operation operation )
+{
+ switch ( operation )
+ {
+ case QNetworkAccessManager::HeadOperation:
+ return QStringLiteral( "HEAD" );
+ case QNetworkAccessManager::GetOperation:
+ return QStringLiteral( "GET" );
+ case QNetworkAccessManager::PutOperation:
+ return QStringLiteral( "PUT" );
+ case QNetworkAccessManager::PostOperation:
+ return QStringLiteral( "POST" );
+ case QNetworkAccessManager::DeleteOperation:
+ return QStringLiteral( "DELETE" );
+ case QNetworkAccessManager::UnknownOperation:
+ return QStringLiteral( "UNKNOWN" );
+ case QNetworkAccessManager::CustomOperation:
+ return QStringLiteral( "CUSTOM" );
+ }
+ return QString();
+}
+
+QString QgsNetworkLoggerRequestGroup::statusToString( QgsNetworkLoggerRequestGroup::Status status )
+{
+ switch ( status )
+ {
+ case QgsNetworkLoggerRequestGroup::Status::Pending:
+ return QObject::tr( "Pending" );
+ case QgsNetworkLoggerRequestGroup::Status::Complete:
+ return QObject::tr( "Complete" );
+ case QgsNetworkLoggerRequestGroup::Status::Error:
+ return QObject::tr( "Error" );
+ case QgsNetworkLoggerRequestGroup::Status::TimeOut:
+ return QObject::tr( "Timeout" );
+ case QgsNetworkLoggerRequestGroup::Status::Canceled:
+ return QObject::tr( "Canceled" );
+ }
+ return QString();
+}
+
+QString QgsNetworkLoggerRequestGroup::cacheControlToString( QNetworkRequest::CacheLoadControl control )
+{
+ switch ( control )
+ {
+ case QNetworkRequest::AlwaysNetwork:
+ return QObject::tr( "Always load from network, do not check cache" );
+ case QNetworkRequest::PreferNetwork:
+ return QObject::tr( "Load from the network if the cached entry is older than the network entry" );
+ case QNetworkRequest::PreferCache:
+ return QObject::tr( "Load from cache if available, otherwise load from network" );
+ case QNetworkRequest::AlwaysCache:
+ return QObject::tr( "Only load from cache, error if no cached entry available" );
+ }
+ return QString();
+}
+
+
+//
+// QgsNetworkLoggerRequestDetailsGroup
+//
+
+QgsNetworkLoggerRequestDetailsGroup::QgsNetworkLoggerRequestDetailsGroup( const QgsNetworkRequestParameters &request )
+ : QgsNetworkLoggerGroup( QObject::tr( "Request" ) )
+{
+ addKeyValueNode( QObject::tr( "Operation" ), QgsNetworkLoggerRequestGroup::operationToString( request.operation() ) );
+ addKeyValueNode( QObject::tr( "Thread" ), request.originatingThreadId() );
+ addKeyValueNode( QObject::tr( "Initiator" ), request.initiatorClassName().isEmpty() ? QObject::tr( "unknown" ) : request.initiatorClassName() );
+ if ( request.initiatorRequestId().isValid() )
+ addKeyValueNode( QObject::tr( "ID" ), request.initiatorRequestId().toString() );
+ addKeyValueNode( QObject::tr( "Cache (control)" ), QgsNetworkLoggerRequestGroup::cacheControlToString( static_cast< QNetworkRequest::CacheLoadControl >( request.request().attribute( QNetworkRequest::CacheLoadControlAttribute ).toInt() ) ) );
+ addKeyValueNode( QObject::tr( "Cache (save)" ), request.request().attribute( QNetworkRequest::CacheSaveControlAttribute ).toBool() ? QObject::tr( "Can store result in cache" ) : QObject::tr( "Result cannot be stored in cache" ) );
+
+ if ( !QUrlQuery( request.request().url() ).queryItems().isEmpty() )
+ {
+ std::unique_ptr< QgsNetworkLoggerRequestQueryGroup > queryGroup = qgis::make_unique< QgsNetworkLoggerRequestQueryGroup >( request.request().url() );
+ mQueryGroup = queryGroup.get();
+ addChild( std::move( queryGroup ) );
+ }
+
+ std::unique_ptr< QgsNetworkLoggerRequestHeadersGroup > requestHeadersGroup = qgis::make_unique< QgsNetworkLoggerRequestHeadersGroup >( request );
+ mRequestHeaders = requestHeadersGroup.get();
+ addChild( std::move( requestHeadersGroup ) );
+
+ switch ( request.operation() )
+ {
+ case QNetworkAccessManager::PostOperation:
+ case QNetworkAccessManager::PutOperation:
+ {
+ std::unique_ptr< QgsNetworkLoggerPostContentGroup > postContentGroup = qgis::make_unique< QgsNetworkLoggerPostContentGroup >( request );
+ mPostContent = postContentGroup.get();
+ addChild( std::move( postContentGroup ) );
+ break;
+ }
+
+ case QNetworkAccessManager::GetOperation:
+ case QNetworkAccessManager::HeadOperation:
+ case QNetworkAccessManager::DeleteOperation:
+ case QNetworkAccessManager::UnknownOperation:
+ case QNetworkAccessManager::CustomOperation:
+ break;
+ }
+}
+
+QVariant QgsNetworkLoggerRequestDetailsGroup::toVariant() const
+{
+ QVariantMap res = QgsNetworkLoggerGroup::toVariant().toMap();
+ if ( mQueryGroup )
+ res.insert( QObject::tr( "Query" ), mQueryGroup->toVariant() );
+ if ( mRequestHeaders )
+ res.insert( QObject::tr( "Headers" ), mRequestHeaders->toVariant() );
+ if ( mPostContent )
+ res.insert( QObject::tr( "Content" ), mPostContent->toVariant() );
+ return res;
+}
+
+
+//
+// QgsNetworkLoggerRequestQueryGroup
+//
+
+QgsNetworkLoggerRequestQueryGroup::QgsNetworkLoggerRequestQueryGroup( const QUrl &url )
+ : QgsNetworkLoggerGroup( QObject::tr( "Query" ) )
+{
+ QUrlQuery query( url );
+ const QList > queryItems = query.queryItems();
+
+ for ( const QPair< QString, QString > &query : queryItems )
+ {
+ addKeyValueNode( query.first, query.second );
+ }
+}
+
+
+//
+// QgsNetworkLoggerRequestHeadersGroup
+//
+QgsNetworkLoggerRequestHeadersGroup::QgsNetworkLoggerRequestHeadersGroup( const QgsNetworkRequestParameters &request )
+ : QgsNetworkLoggerGroup( QObject::tr( "Headers" ) )
+{
+ const QList headers = request.request().rawHeaderList();
+ for ( const QByteArray &header : headers )
+ {
+ addKeyValueNode( header, request.request().rawHeader( header ) );
+ }
+}
+
+//
+// QgsNetworkLoggerPostContentGroup
+//
+
+QgsNetworkLoggerPostContentGroup::QgsNetworkLoggerPostContentGroup( const QgsNetworkRequestParameters ¶meters )
+ : QgsNetworkLoggerGroup( QObject::tr( "Content" ) )
+{
+ addKeyValueNode( QObject::tr( "Data" ), parameters.content() );
+}
+
+
+//
+// QgsNetworkLoggerReplyGroup
+//
+
+QgsNetworkLoggerReplyGroup::QgsNetworkLoggerReplyGroup( const QgsNetworkReplyContent &reply )
+ : QgsNetworkLoggerGroup( QObject::tr( "Reply" ) )
+{
+ addKeyValueNode( QObject::tr( "Status" ), reply.attribute( QNetworkRequest::HttpStatusCodeAttribute ).toString() );
+ if ( reply.error() != QNetworkReply::NoError )
+ {
+ addKeyValueNode( QObject::tr( "Error Code" ), QString::number( static_cast< int >( reply.error() ) ) );
+ addKeyValueNode( QObject::tr( "Error" ), reply.errorString() );
+ }
+ addKeyValueNode( QObject::tr( "Cache (result)" ), reply.attribute( QNetworkRequest::SourceIsFromCacheAttribute ).toBool() ? QObject::tr( "Used entry from cache" ) : QObject::tr( "Read from network" ) );
+
+ std::unique_ptr< QgsNetworkLoggerReplyHeadersGroup > headersGroup = qgis::make_unique< QgsNetworkLoggerReplyHeadersGroup >( reply );
+ mReplyHeaders = headersGroup.get();
+ addChild( std::move( headersGroup ) );
+}
+
+QVariant QgsNetworkLoggerReplyGroup::toVariant() const
+{
+ QVariantMap res = QgsNetworkLoggerGroup::toVariant().toMap();
+ if ( mReplyHeaders )
+ {
+ res.insert( QObject::tr( "Headers" ), mReplyHeaders->toVariant() );
+ }
+ return res;
+}
+
+
+//
+// QgsNetworkLoggerReplyHeadersGroup
+//
+QgsNetworkLoggerReplyHeadersGroup::QgsNetworkLoggerReplyHeadersGroup( const QgsNetworkReplyContent &reply )
+ : QgsNetworkLoggerGroup( QObject::tr( "Headers" ) )
+{
+ const QList headers = reply.rawHeaderList();
+ for ( const QByteArray &header : headers )
+ {
+ addKeyValueNode( header, reply.rawHeader( header ) );
+ }
+}
+
+//
+// QgsNetworkLoggerSslErrorGroup
+//
+QgsNetworkLoggerSslErrorGroup::QgsNetworkLoggerSslErrorGroup( const QList &errors )
+ : QgsNetworkLoggerGroup( QObject::tr( "SSL errors" ) )
+{
+ for ( const QSslError &error : errors )
+ {
+ addKeyValueNode( QObject::tr( "Error" ), error.errorString(), QColor( 180, 65, 210 ) );
+ }
+}
+
+QVariant QgsNetworkLoggerSslErrorGroup::data( int role ) const
+{
+ if ( role == Qt::ForegroundRole )
+ return QBrush( QColor( 180, 65, 210 ) );
+
+ return QgsNetworkLoggerGroup::data( role );
+}
+
+QList QgsNetworkLoggerNode::actions( QObject * )
+{
+ return QList< QAction * >();
+}
+
+QVariant QgsNetworkLoggerNode::toVariant() const
+{
+ return QVariant();
+}
diff --git a/src/app/devtools/networklogger/qgsnetworkloggernode.h b/src/app/devtools/networklogger/qgsnetworkloggernode.h
new file mode 100644
index 000000000000..d24035be5977
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworkloggernode.h
@@ -0,0 +1,534 @@
+/***************************************************************************
+ qgsnetworkloggernode.h
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSNETWORKLOGGERNODE_H
+#define QGSNETWORKLOGGERNODE_H
+
+#include "qgsnetworkaccessmanager.h"
+#include
+#include
+#include
+#include
+#include
+
+class QAction;
+class QgsNetworkLoggerGroup;
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerNode
+ * \brief Base class for nodes in the network logger model.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerNode
+{
+ public:
+
+ //! Custom node data roles
+ enum Roles
+ {
+ RoleStatus = Qt::UserRole + 1, //!< Request status role
+ RoleId, //!< Request ID role
+ };
+
+ virtual ~QgsNetworkLoggerNode();
+
+ /**
+ * Returns the node's parent node.
+ *
+ * If parent is NULLPTR, the node is a root node
+ */
+ QgsNetworkLoggerGroup *parent() { return mParent; }
+
+ /**
+ * Returns the node's data for the specified model \a role.
+ */
+ virtual QVariant data( int role = Qt::DisplayRole ) const = 0;
+
+ /**
+ * Returns the number of child nodes owned by this node.
+ */
+ virtual int childCount() const = 0;
+
+ /**
+ * Returns a list of actions relating to the node.
+ *
+ * The actions should be parented to \a parent.
+ */
+ virtual QList< QAction * > actions( QObject *parent );
+
+ /**
+ * Converts the node's contents to a variant.
+ */
+ virtual QVariant toVariant() const;
+
+ protected:
+
+ QgsNetworkLoggerNode();
+
+ private:
+
+ QgsNetworkLoggerGroup *mParent = nullptr;
+ friend class QgsNetworkLoggerGroup;
+};
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerGroup
+ * \brief Base class for network logger model "group" nodes, which contain children of their own.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerGroup : public QgsNetworkLoggerNode
+{
+ public:
+
+ /**
+ * Adds a \a child node to this node.
+ */
+ void addChild( std::unique_ptr< QgsNetworkLoggerNode > child );
+
+ /**
+ * Returns the index of the specified \a child node.
+ *
+ * \warning \a child must be a valid child of this node.
+ */
+ int indexOf( QgsNetworkLoggerNode *child ) const;
+
+ /**
+ * Returns the child at the specified \a index.
+ */
+ QgsNetworkLoggerNode *childAt( int index );
+
+ /**
+ * Clears the group, removing all its children.
+ */
+ void clear();
+
+ int childCount() const override final { return mChildren.size(); }
+ QVariant data( int role = Qt::DisplayRole ) const override;
+ QVariant toVariant() const override;
+
+ protected:
+
+ /**
+ * Constructor for a QgsNetworkLoggerGroup, with the specified \a title.
+ */
+ QgsNetworkLoggerGroup( const QString &title );
+
+ /**
+ * Adds a simple \a key: \a value node to the group.
+ */
+ void addKeyValueNode( const QString &key, const QString &value, const QColor &color = QColor() );
+
+ private:
+
+ std::deque< std::unique_ptr< QgsNetworkLoggerNode > > mChildren;
+ QString mGroupTitle;
+ friend class QgsNetworkLoggerRootNode;
+
+};
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerValueNode
+ * \brief A "key: value" style node for the network logger model.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerValueNode : public QgsNetworkLoggerNode
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerValueNode, with the specified \a key (usually translated) and \a value.
+ */
+ QgsNetworkLoggerValueNode( const QString &key, const QString &value, const QColor &color = QColor() );
+
+ /**
+ * Returns the node's key.
+ */
+ QString key() const { return mKey; }
+
+ /**
+ * Returns the node's value.
+ */
+ QString value() const { return mValue; }
+
+ QVariant data( int role = Qt::DisplayRole ) const override final;
+ int childCount() const override final { return 0; }
+
+ private:
+
+ QString mKey;
+ QString mValue;
+ QColor mColor;
+};
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerRootNode
+ * \brief Root node for the network logger model.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerRootNode final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ QgsNetworkLoggerRootNode();
+ QVariant data( int role = Qt::DisplayRole ) const override final;
+
+ /**
+ * Removes a \a row from the root group.
+ */
+ void removeRow( int row );
+
+ QVariant toVariant() const override;
+};
+
+class QgsNetworkLoggerRequestDetailsGroup;
+class QgsNetworkLoggerReplyGroup;
+class QgsNetworkLoggerSslErrorGroup;
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerRequestGroup
+ * \brief Parent group for all network requests, showing the request id, type, url,
+ * and containing child groups with detailed request and response information.
+ *
+ * Visually, a QgsNetworkLoggerRequestGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerRequestGroup (showing id, type (GET etc) url)
+ * |__ QgsNetworkLoggerRequestDetailsGroup (holding Request details)
+ * |__ QgsNetworkLoggerValueNode (key-value pairs with info)
+ * ...
+ * |__ QgsNetworkLoggerRequestQueryGroup (holding query info)
+ * |__ ...
+ * |__ QgsNetworkLoggerRequestHeadersGroup ('Headers')
+ * |__ ...
+ * |__ QgsNetworkLoggerPostContentGroup (showing Data in case of POST)
+ * |__ ...
+ * |__ QgsNetworkLoggerReplyGroup (holding Reply details)
+ * |__ QgsNetworkLoggerReplyHeadersGroup (Reply 'Headers')
+ * |__ ...
+ * |__ QgsNetworkLoggerSslErrorGroup (holding SSL error details, if encountered)
+ * |__ ...
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerRequestGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ //! Request statu
+ enum class Status
+ {
+ Pending, //!< Request underway
+ Complete, //!< Request was successfully completed
+ Error, //!< Request encountered an error
+ TimeOut, //!< Request timed out
+ Canceled, //!< Request was manually canceled
+ };
+
+ /**
+ * Constructor for QgsNetworkLoggerRequestGroup, populated from the
+ * specified \a request details.
+ */
+ QgsNetworkLoggerRequestGroup( const QgsNetworkRequestParameters &request );
+ QVariant data( int role = Qt::DisplayRole ) const override;
+ QList< QAction * > actions( QObject *parent ) override final;
+ QVariant toVariant() const override;
+
+ /**
+ * Returns the request's status.
+ */
+ Status status() const { return mStatus; }
+
+ /**
+ * Returns the request's URL.
+ */
+ QUrl url() const { return mUrl; }
+
+ /**
+ * Called to set the \a reply associated with the request.
+ *
+ * Will automatically create children encapsulating the reply details.
+ */
+ void setReply( const QgsNetworkReplyContent &reply );
+
+ /**
+ * Flags the reply as having timed out.
+ */
+ void setTimedOut();
+
+ /**
+ * Sets the requests download progress.
+ */
+ void setProgress( qint64 bytesReceived, qint64 bytesTotal );
+
+ /**
+ * Reports any SSL errors encountered while processing the request.
+ */
+ void setSslErrors( const QList &errors );
+
+ /**
+ * Converts a network \a operation to a string value.
+ */
+ static QString operationToString( QNetworkAccessManager::Operation operation );
+
+ /**
+ * Converts a request \a status to a translated string value.
+ */
+ static QString statusToString( Status status );
+
+ /**
+ * Converts a cache load \a control value to a translated string.
+ */
+ static QString cacheControlToString( QNetworkRequest::CacheLoadControl control );
+
+ private:
+
+ QUrl mUrl;
+ int mRequestId = 0;
+ QNetworkAccessManager::Operation mOperation;
+ QElapsedTimer mTimer;
+ qint64 mTotalTime = 0;
+ int mHttpStatus = -1;
+ QString mContentType;
+ qint64 mBytesReceived = 0;
+ qint64 mBytesTotal = 0;
+ int mReplies = 0;
+ QByteArray mData;
+ Status mStatus = Status::Pending;
+ bool mHasSslErrors = false;
+ QList< QPair< QString, QString > > mHeaders;
+ QgsNetworkLoggerRequestDetailsGroup *mDetailsGroup = nullptr;
+ QgsNetworkLoggerReplyGroup *mReplyGroup = nullptr;
+ QgsNetworkLoggerSslErrorGroup *mSslErrorsGroup = nullptr;
+};
+
+class QgsNetworkLoggerRequestQueryGroup;
+class QgsNetworkLoggerRequestHeadersGroup;
+class QgsNetworkLoggerPostContentGroup;
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerRequestGroup
+ * \brief Parent group for all network request details, showing the request parameters
+ * and header information.
+ *
+ * Visually, a QgsNetworkLoggerRequestDetailsGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerRequestDetailsGroup (holding Request details)
+ * |__ QgsNetworkLoggerValueNode (key-value pairs with info)
+ * Operation: ...
+ * Thread: ...
+ * Initiator: ...
+ * ID: ...
+ * Cache (control): ...
+ * Cache (save): ...
+ * |__ QgsNetworkLoggerRequestQueryGroup (holding query info)
+ * |__ ...
+ * |__ QgsNetworkLoggerRequestHeadersGroup ('Headers')
+ * |__ ...
+ * |__ QgsNetworkLoggerPostContentGroup (showing Data in case of POST)
+ * |__ ...
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerRequestDetailsGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerRequestDetailsGroup, populated from the
+ * specified \a request details.
+ */
+ QgsNetworkLoggerRequestDetailsGroup( const QgsNetworkRequestParameters &request );
+ QVariant toVariant() const override;
+
+ private:
+
+ QgsNetworkLoggerRequestQueryGroup *mQueryGroup = nullptr;
+ QgsNetworkLoggerRequestHeadersGroup *mRequestHeaders = nullptr;
+ QgsNetworkLoggerPostContentGroup *mPostContent = nullptr;
+};
+
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerRequestHeadersGroup
+ * \brief Parent group for all network request header information.
+ *
+ * Visually, a QgsNetworkLoggerRequestHeadersGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerRequestHeadersGroup (holding Request details)
+ * User-Agent: ...
+ * ...
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerRequestHeadersGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerRequestHeadersGroup, populated from the
+ * specified \a request details.
+ */
+ QgsNetworkLoggerRequestHeadersGroup( const QgsNetworkRequestParameters &request );
+
+};
+
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerRequestQueryGroup
+ * \brief Parent group for all network request query information.
+ *
+ * Visually, a QgsNetworkLoggerRequestQueryGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerRequestQueryGroup (holding query info)
+ * query param: value
+ * ...
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerRequestQueryGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerRequestQueryGroup, populated from the
+ * specified \a url.
+ */
+ QgsNetworkLoggerRequestQueryGroup( const QUrl &url );
+
+};
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerPostContentGroup
+ * \brief Parent group for all request post data, showing POST data.
+ *
+ * Visually, a QgsNetworkLoggerPostContentGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerPostContentGroup (holding POST data)
+ * |__ Data: POST data
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerPostContentGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerPostContentGroup, populated from the
+ * specified \a request details.
+ */
+ QgsNetworkLoggerPostContentGroup( const QgsNetworkRequestParameters ¶meters );
+};
+
+class QgsNetworkLoggerReplyHeadersGroup;
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerReplyGroup
+ * \brief Parent group for all network replies, showing the reply details.
+ *
+ * Visually, a QgsNetworkLoggerReplyGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerReplyGroup (holding Reply details)
+ * |__ Status: reply status (e.g. 'Canceled')
+ * |__ Error code: code (if applicable)
+ * |__ Cache (result): whether reply was taken from cache or network
+ * |__ QgsNetworkLoggerReplyHeadersGroup (Reply 'Headers')
+ * |__ ...
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerReplyGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerReplyGroup, populated from the
+ * specified \a reply details.
+ */
+ QgsNetworkLoggerReplyGroup( const QgsNetworkReplyContent &reply );
+ QVariant toVariant() const override;
+
+ private:
+
+ QgsNetworkLoggerReplyHeadersGroup *mReplyHeaders = nullptr;
+
+};
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerReplyHeadersGroup
+ * \brief Parent group for network reply headers, showing the reply header details.
+ *
+ * Visually, a QgsNetworkLoggerReplyHeadersGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerReplyHeadersGroup (holding reply Header details)
+ * Content-Type: ...
+ * Content-Length: ...
+ * ...
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerReplyHeadersGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerReplyHeadersGroup, populated from the
+ * specified \a reply details.
+ */
+ QgsNetworkLoggerReplyHeadersGroup( const QgsNetworkReplyContent &reply );
+
+};
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerSslErrorNode
+ * \brief Parent group for SSQL errors, showing the error details.
+ *
+ * Visually, a QgsNetworkLoggerSslErrorGroup is structured by:
+ *
+ * |__ QgsNetworkLoggerSslErrorGroup (holding error details)
+ * Error: ...
+ * Error: ...
+ * ...
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerSslErrorGroup final : public QgsNetworkLoggerGroup
+{
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerSslErrorGroup, populated from the
+ * specified \a errors.
+ */
+ QgsNetworkLoggerSslErrorGroup( const QList &errors );
+ QVariant data( int role = Qt::DisplayRole ) const override;
+};
+
+
+
+#endif // QGSNETWORKLOGGERNODE_H
diff --git a/src/app/devtools/networklogger/qgsnetworkloggerpanelwidget.cpp b/src/app/devtools/networklogger/qgsnetworkloggerpanelwidget.cpp
new file mode 100644
index 000000000000..ae89bff79c63
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworkloggerpanelwidget.cpp
@@ -0,0 +1,203 @@
+/***************************************************************************
+ qgsnetworkloggerpanelwidget.cpp
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 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 "qgsnetworkloggerpanelwidget.h"
+#include "qgsguiutils.h"
+#include "qgsnetworklogger.h"
+#include "qgssettings.h"
+#include "qgsnetworkloggernode.h"
+#include "qgsjsonutils.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+//
+// QgsNetworkLoggerTreeView
+//
+
+QgsNetworkLoggerTreeView::QgsNetworkLoggerTreeView( QgsNetworkLogger *logger, QWidget *parent )
+ : QTreeView( parent )
+ , mLogger( logger )
+{
+ connect( this, &QTreeView::expanded, this, &QgsNetworkLoggerTreeView::itemExpanded );
+
+ setFont( QFontDatabase::systemFont( QFontDatabase::FixedFont ) );
+
+ mProxyModel = new QgsNetworkLoggerProxyModel( mLogger, this );
+ setModel( mProxyModel );
+
+ setContextMenuPolicy( Qt::CustomContextMenu );
+ connect( this, &QgsNetworkLoggerTreeView::customContextMenuRequested, this, &QgsNetworkLoggerTreeView::contextMenu );
+
+ connect( verticalScrollBar(), &QAbstractSlider::sliderMoved, this, [this]( int value )
+ {
+ if ( value == verticalScrollBar()->maximum() )
+ mAutoScroll = true;
+ else
+ mAutoScroll = false;
+ } );
+
+ connect( mLogger, &QAbstractItemModel::rowsInserted, this, [ = ]
+ {
+ if ( mLogger->rowCount() > ( QgsNetworkLogger::MAX_LOGGED_REQUESTS * 1.2 ) ) // 20 % more as buffer
+ {
+ // never trim expanded nodes
+ const int toTrim = mLogger->rowCount() - QgsNetworkLogger::MAX_LOGGED_REQUESTS;
+ int trimmed = 0;
+ QList< int > rowsToTrim;
+ rowsToTrim.reserve( toTrim );
+ for ( int i = 0; i < mLogger->rowCount(); ++i )
+ {
+ const QModelIndex proxyIndex = mProxyModel->mapFromSource( mLogger->index( i, 0 ) );
+ if ( !proxyIndex.isValid() || !isExpanded( proxyIndex ) )
+ {
+ rowsToTrim << i;
+ trimmed++;
+ }
+ if ( trimmed == toTrim )
+ break;
+ }
+
+ mLogger->removeRows( rowsToTrim );
+ }
+
+ if ( mAutoScroll )
+ scrollToBottom();
+ } );
+
+ mMenu = new QMenu( this );
+}
+
+void QgsNetworkLoggerTreeView::setFilterString( const QString &string )
+{
+ mProxyModel->setFilterString( string );
+}
+
+void QgsNetworkLoggerTreeView::setShowSuccessful( bool show )
+{
+ mProxyModel->setShowSuccessful( show );
+}
+
+void QgsNetworkLoggerTreeView::setShowTimeouts( bool show )
+{
+ mProxyModel->setShowTimeouts( show );
+}
+
+void QgsNetworkLoggerTreeView::itemExpanded( const QModelIndex &index )
+{
+ // if the item is a QgsNetworkLoggerRequestGroup item, open all children (show ALL info of it)
+ // we want to scroll to last request
+
+ // only expand all children on QgsNetworkLoggerRequestGroup nodes (which don't have a valid parent!)
+ if ( !index.parent().isValid() )
+ expandChildren( index );
+
+ // make ALL request information visible by scrolling view to it
+ scrollTo( index );
+}
+
+void QgsNetworkLoggerTreeView::contextMenu( QPoint point )
+{
+ const QModelIndex viewModelIndex = indexAt( point );
+ const QModelIndex modelIndex = mProxyModel->mapToSource( viewModelIndex );
+
+ if ( modelIndex.isValid() )
+ {
+ mMenu->clear();
+
+ const QList< QAction * > actions = mLogger->actions( modelIndex, mMenu );
+ mMenu->addActions( actions );
+ if ( !mMenu->actions().empty() )
+ {
+ mMenu->exec( viewport()->mapToGlobal( point ) );
+ }
+ }
+}
+
+void QgsNetworkLoggerTreeView::expandChildren( const QModelIndex &index )
+{
+ if ( !index.isValid() )
+ return;
+
+ const int count = model()->rowCount( index );
+ for ( int i = 0; i < count; ++i )
+ {
+ const QModelIndex childIndex = model()->index( i, 0, index );
+ expandChildren( childIndex );
+ }
+ if ( !isExpanded( index ) )
+ expand( index );
+}
+
+
+//
+// QgsNetworkLoggerPanelWidget
+//
+
+QgsNetworkLoggerPanelWidget::QgsNetworkLoggerPanelWidget( QgsNetworkLogger *logger, QWidget *parent )
+ : QgsDevToolWidget( parent )
+ , mLogger( logger )
+{
+ setupUi( this );
+
+ mTreeView = new QgsNetworkLoggerTreeView( mLogger );
+ verticalLayout->addWidget( mTreeView );
+ mToolbar->setIconSize( QgsGuiUtils::iconSize( true ) );
+
+ mFilterLineEdit->setShowClearButton( true );
+ mFilterLineEdit->setShowSearchIcon( true );
+ mFilterLineEdit->setPlaceholderText( tr( "Filter requests" ) );
+
+ mActionShowTimeouts->setChecked( true );
+ mActionShowSuccessful->setChecked( true );
+ mActionRecord->setChecked( mLogger->isLogging() );
+
+ connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged, mTreeView, &QgsNetworkLoggerTreeView::setFilterString );
+ connect( mActionShowTimeouts, &QAction::toggled, mTreeView, &QgsNetworkLoggerTreeView::setShowTimeouts );
+ connect( mActionShowSuccessful, &QAction::toggled, mTreeView, &QgsNetworkLoggerTreeView::setShowSuccessful );
+ connect( mActionClear, &QAction::triggered, mLogger, &QgsNetworkLogger::clear );
+ connect( mActionRecord, &QAction::toggled, this, [ = ]( bool enabled )
+ {
+ QgsSettings().setValue( QStringLiteral( "logNetworkRequests" ), enabled, QgsSettings::App );
+ mLogger->enableLogging( enabled );
+ } );
+ connect( mActionSaveLog, &QAction::triggered, this, [ = ]()
+ {
+ if ( QMessageBox::warning( this, tr( "Save Network Log" ),
+ tr( "Security warning: network logs may contain sensitive data including usernames or passwords. Treat this log as confidential and be careful who you share it with. Continue?" ), QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No )
+ return;
+
+ QString saveFilePath = QFileDialog::getSaveFileName( this, tr( "Save Network Log" ), QDir::homePath(), tr( "Log files" ) + " (*.json)" );
+ if ( saveFilePath.isEmpty() )
+ {
+ return;
+ }
+
+ QFile exportFile( saveFilePath );
+ if ( !exportFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
+ {
+ return;
+ }
+ QTextStream fout( &exportFile );
+
+ const QVariant value = mLogger->rootGroup()->toVariant();
+ const QString json = QString::fromStdString( QgsJsonUtils::jsonFromVariant( value ).dump( 2 ) );
+
+ fout << json;
+ } );
+}
diff --git a/src/app/devtools/networklogger/qgsnetworkloggerpanelwidget.h b/src/app/devtools/networklogger/qgsnetworkloggerpanelwidget.h
new file mode 100644
index 000000000000..2c8bc280b206
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworkloggerpanelwidget.h
@@ -0,0 +1,98 @@
+/***************************************************************************
+ qgsnetworkloggerpanelwidget.h
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSNETWORKLOGGERPANELWIDGET_H
+#define QGSNETWORKLOGGERPANELWIDGET_H
+
+#include "qgsdevtoolwidget.h"
+#include "ui_qgsnetworkloggerpanelbase.h"
+#include
+
+class QgsNetworkLogger;
+class QgsNetworkLoggerProxyModel;
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerTreeView
+ * \brief A custom QTreeView subclass for showing logged network requests.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerTreeView: public QTreeView
+{
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerTreeView, attached to the specified \a logger.
+ */
+ QgsNetworkLoggerTreeView( QgsNetworkLogger *logger, QWidget *parent = nullptr );
+
+ public slots:
+
+ /**
+ * Sets a filter \a string to apply to request URLs.
+ */
+ void setFilterString( const QString &string );
+
+ /**
+ * Sets whether successful requests should be shown.
+ */
+ void setShowSuccessful( bool show );
+
+ /**
+ * Sets whether timed out requests should be shown.
+ */
+ void setShowTimeouts( bool show );
+
+ private slots:
+ void itemExpanded( const QModelIndex &index );
+ void contextMenu( QPoint point );
+
+ private:
+
+ void expandChildren( const QModelIndex &index );
+ QMenu *mMenu = nullptr;
+ QgsNetworkLogger *mLogger = nullptr;
+ QgsNetworkLoggerProxyModel *mProxyModel = nullptr;
+ bool mAutoScroll = true;
+};
+
+
+/**
+ * \ingroup app
+ * \class QgsNetworkLoggerPanelWidget
+ * \brief A panel widget showing logged network requests.
+ *
+ * \since QGIS 3.14
+ */
+class QgsNetworkLoggerPanelWidget : public QgsDevToolWidget, private Ui::QgsNetworkLoggerPanelBase
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor for QgsNetworkLoggerPanelWidget, linked with the specified \a logger.
+ */
+ QgsNetworkLoggerPanelWidget( QgsNetworkLogger *logger, QWidget *parent );
+
+ private:
+
+ QgsNetworkLoggerTreeView *mTreeView = nullptr;
+ QgsNetworkLogger *mLogger = nullptr;
+};
+
+
+#endif // QGSNETWORKLOGGERPANELWIDGET_H
diff --git a/src/app/devtools/networklogger/qgsnetworkloggerwidgetfactory.cpp b/src/app/devtools/networklogger/qgsnetworkloggerwidgetfactory.cpp
new file mode 100644
index 000000000000..acddd2819990
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworkloggerwidgetfactory.cpp
@@ -0,0 +1,29 @@
+/***************************************************************************
+ qgsnetworkloggerwidgetfactory.cpp
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 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 "qgsnetworkloggerwidgetfactory.h"
+#include "qgsnetworkloggerpanelwidget.h"
+#include "qgsapplication.h"
+
+QgsNetworkLoggerWidgetFactory::QgsNetworkLoggerWidgetFactory( QgsNetworkLogger *logger )
+ : QgsDevToolWidgetFactory( QObject::tr( "Network Logger" ), QgsApplication::getThemeIcon( QStringLiteral( "mIconNetworkLogger.svg" ) ) )
+ , mLogger( logger )
+{
+}
+
+QgsDevToolWidget *QgsNetworkLoggerWidgetFactory::createWidget( QWidget *parent ) const
+{
+ return new QgsNetworkLoggerPanelWidget( mLogger, parent );
+}
diff --git a/src/app/devtools/networklogger/qgsnetworkloggerwidgetfactory.h b/src/app/devtools/networklogger/qgsnetworkloggerwidgetfactory.h
new file mode 100644
index 000000000000..ebb916c1fbc1
--- /dev/null
+++ b/src/app/devtools/networklogger/qgsnetworkloggerwidgetfactory.h
@@ -0,0 +1,35 @@
+/***************************************************************************
+ qgsnetworkloggerwidgetfactory.h
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSNETWORKLOGGERWIDGETFACTORY_H
+#define QGSNETWORKLOGGERWIDGETFACTORY_H
+
+#include "qgsdevtoolwidgetfactory.h"
+
+class QgsNetworkLogger;
+
+class QgsNetworkLoggerWidgetFactory: public QgsDevToolWidgetFactory
+{
+ public:
+
+ QgsNetworkLoggerWidgetFactory( QgsNetworkLogger *logger );
+ QgsDevToolWidget *createWidget( QWidget *parent = nullptr ) const override;
+
+ private:
+
+ QgsNetworkLogger *mLogger = nullptr;
+};
+
+
+#endif // QGSNETWORKLOGGERWIDGETFACTORY_H
diff --git a/src/app/devtools/qgsappdevtoolutils.cpp b/src/app/devtools/qgsappdevtoolutils.cpp
new file mode 100644
index 000000000000..b61744d6230d
--- /dev/null
+++ b/src/app/devtools/qgsappdevtoolutils.cpp
@@ -0,0 +1,36 @@
+/***************************************************************************
+ qgsappdevtoolutils.cpp
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 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 "qgsappdevtoolutils.h"
+
+#include "qgisapp.h"
+#include "qgis.h"
+#include "qgsdevtoolwidgetfactory.h"
+
+QgsScopedDevToolWidgetFactory::QgsScopedDevToolWidgetFactory() = default;
+
+QgsScopedDevToolWidgetFactory::~QgsScopedDevToolWidgetFactory()
+{
+ if ( mFactory )
+ QgisApp::instance()->unregisterDevToolFactory( mFactory.get() );
+}
+
+void QgsScopedDevToolWidgetFactory::reset( std::unique_ptr factory )
+{
+ if ( mFactory )
+ QgisApp::instance()->unregisterDevToolFactory( mFactory.get() );
+ mFactory = std::move( factory );
+ if ( mFactory )
+ QgisApp::instance()->registerDevToolFactory( mFactory.get() );
+}
diff --git a/src/app/devtools/qgsappdevtoolutils.h b/src/app/devtools/qgsappdevtoolutils.h
new file mode 100644
index 000000000000..cc66b07f38c1
--- /dev/null
+++ b/src/app/devtools/qgsappdevtoolutils.h
@@ -0,0 +1,43 @@
+
+/***************************************************************************
+ qgsappdevtoolutils.h
+ -------------------------
+ begin : March 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSAPPDEVTOOLUTILS_H
+#define QGSAPPDEVTOOLUTILS_H
+
+class QgsDevToolWidgetFactory;
+#include
+
+/**
+ * \ingroup core
+ *
+ * Manages lifetime of a QgsScopedDevToolWidgetFactory, automatically
+ * registering and unregistering it as required.
+ *
+ * \since QGIS 3.14
+ */
+class QgsScopedDevToolWidgetFactory
+{
+ public:
+ QgsScopedDevToolWidgetFactory();
+ ~QgsScopedDevToolWidgetFactory();
+
+ void reset( std::unique_ptr< QgsDevToolWidgetFactory > factory = nullptr );
+
+ private:
+ std::unique_ptr< QgsDevToolWidgetFactory > mFactory;
+};
+
+
+#endif // QGSAPPDEVTOOLUTILS_H
diff --git a/src/app/gps/qgsgpsinformationwidget.cpp b/src/app/gps/qgsgpsinformationwidget.cpp
index fd2fc87d3d90..ddb9f84e3896 100644
--- a/src/app/gps/qgsgpsinformationwidget.cpp
+++ b/src/app/gps/qgsgpsinformationwidget.cpp
@@ -943,7 +943,7 @@ void QgsGpsInformationWidget::displayGPSInformation( const QgsGpsInformation &in
if ( radRecenterMap->isChecked() ||
( radRecenterWhenNeeded->isChecked() && !myExtentLimit.contains( myPoint ) ) )
{
- mMapCanvas->setExtent( myRect );
+ mMapCanvas->setExtent( myRect, true );
mMapCanvas->refresh();
}
}
diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp
index c2f9ed638a56..2addf0b8b173 100644
--- a/src/app/layout/qgslayoutdesignerdialog.cpp
+++ b/src/app/layout/qgslayoutdesignerdialog.cpp
@@ -2651,6 +2651,15 @@ void QgsLayoutDesignerDialog::exportAtlasToRaster()
if ( !printAtlas || !printAtlas->enabled() )
return;
+ if ( !printAtlas->coverageLayer() )
+ {
+ QMessageBox::warning( this, tr( "Export Atlas" ),
+ tr( "Error: No coverage layer is set." ),
+ QMessageBox::Ok,
+ QMessageBox::Ok );
+ return;
+ }
+
// else, it has an atlas to render, so a directory must first be selected
if ( printAtlas->filenameExpression().isEmpty() )
{
@@ -2823,6 +2832,15 @@ void QgsLayoutDesignerDialog::exportAtlasToSvg()
if ( !printAtlas || !printAtlas->enabled() )
return;
+ if ( !printAtlas->coverageLayer() )
+ {
+ QMessageBox::warning( this, tr( "Export Atlas" ),
+ tr( "Error: No coverage layer is set." ),
+ QMessageBox::Ok,
+ QMessageBox::Ok );
+ return;
+ }
+
if ( containsWmsLayers() )
{
showWmsPrintingWarning();
@@ -2991,6 +3009,15 @@ void QgsLayoutDesignerDialog::exportAtlasToPdf()
if ( !printAtlas || !printAtlas->enabled() )
return;
+ if ( !printAtlas->coverageLayer() )
+ {
+ QMessageBox::warning( this, tr( "Export Atlas" ),
+ tr( "Error: No coverage layer is set." ),
+ QMessageBox::Ok,
+ QMessageBox::Ok );
+ return;
+ }
+
if ( containsWmsLayers() )
{
showWmsPrintingWarning();
diff --git a/src/app/locator/qgsinbuiltlocatorfilters.cpp b/src/app/locator/qgsinbuiltlocatorfilters.cpp
index 299544866177..f4de8668ef50 100644
--- a/src/app/locator/qgsinbuiltlocatorfilters.cpp
+++ b/src/app/locator/qgsinbuiltlocatorfilters.cpp
@@ -24,7 +24,6 @@
#include "qgslayertree.h"
#include "qgsfeedback.h"
#include "qgisapp.h"
-#include "qgsstringutils.h"
#include "qgsmaplayermodel.h"
#include "qgslayoutmanager.h"
#include "qgsmapcanvas.h"
@@ -48,15 +47,26 @@ void QgsLayerTreeLocatorFilter::fetchResults( const QString &string, const QgsLo
const QList layers = tree->findLayers();
for ( QgsLayerTreeLayer *layer : layers )
{
- if ( layer->layer() && ( stringMatches( layer->layer()->name(), string ) || ( context.usingPrefix && string.isEmpty() ) ) )
+ // if the layer is broken, don't include it in the results
+ if ( ! layer->layer() )
+ continue;
+
+ QgsLocatorResult result;
+ result.displayString = layer->layer()->name();
+ result.userData = layer->layerId();
+ result.icon = QgsMapLayerModel::iconForLayer( layer->layer() );
+
+ // return all the layers in case the string query is empty using an equal default score
+ if ( context.usingPrefix && string.isEmpty() )
{
- QgsLocatorResult result;
- result.displayString = layer->layer()->name();
- result.userData = layer->layerId();
- result.icon = QgsMapLayerModel::iconForLayer( layer->layer() );
- result.score = static_cast< double >( string.length() ) / layer->layer()->name().length();
emit resultFetched( result );
+ continue;
}
+
+ result.score = fuzzyScore( result.displayString, string );
+
+ if ( result.score > 0 )
+ emit resultFetched( result );
}
}
@@ -85,15 +95,24 @@ void QgsLayoutLocatorFilter::fetchResults( const QString &string, const QgsLocat
const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts();
for ( QgsMasterLayoutInterface *layout : layouts )
{
- if ( layout && ( stringMatches( layout->name(), string ) || ( context.usingPrefix && string.isEmpty() ) ) )
+ // if the layout is broken, don't include it in the results
+ if ( ! layout )
+ continue;
+
+ QgsLocatorResult result;
+ result.displayString = layout->name();
+ result.userData = layout->name();
+
+ if ( context.usingPrefix && string.isEmpty() )
{
- QgsLocatorResult result;
- result.displayString = layout->name();
- result.userData = layout->name();
- //result.icon = QgsMapLayerModel::iconForLayer( layer->layer() );
- result.score = static_cast< double >( string.length() ) / layout->name().length();
emit resultFetched( result );
+ continue;
}
+
+ result.score = fuzzyScore( result.displayString, string );
+
+ if ( result.score > 0 )
+ emit resultFetched( result );
}
}
@@ -107,9 +126,6 @@ void QgsLayoutLocatorFilter::triggerResult( const QgsLocatorResult &result )
QgisApp::instance()->openLayoutDesignerDialog( layout );
}
-
-
-
QgsActionLocatorFilter::QgsActionLocatorFilter( const QList &parentObjectsForActions, QObject *parent )
: QgsLocatorFilter( parent )
, mActionParents( parentObjectsForActions )
@@ -131,7 +147,7 @@ void QgsActionLocatorFilter::fetchResults( const QString &string, const QgsLocat
for ( QWidget *object : qgis::as_const( mActionParents ) )
{
- searchActions( string, object, found );
+ searchActions( string, object, found );
}
}
@@ -191,15 +207,16 @@ void QgsActionLocatorFilter::searchActions( const QString &string, QWidget *pare
searchText += QStringLiteral( " (%1)" ).arg( tooltip.trimmed() );
}
- if ( stringMatches( searchText, string ) )
+ QgsLocatorResult result;
+ result.displayString = searchText;
+ result.userData = QVariant::fromValue( action );
+ result.icon = action->icon();
+ result.score = fuzzyScore( result.displayString, string );
+
+ if ( result.score > 0 )
{
- QgsLocatorResult result;
- result.displayString = searchText;
- result.userData = QVariant::fromValue( action );
- result.icon = action->icon();
- result.score = static_cast< double >( string.length() ) / searchText.length();
- emit resultFetched( result );
found << action;
+ emit resultFetched( result );
}
}
}
@@ -369,7 +386,12 @@ void QgsAllLayersFeaturesLocatorFilter::prepare( const QString &string, const Qg
enhancedSearch.replace( ' ', '%' );
req.setFilterExpression( QStringLiteral( "%1 ILIKE '%%2%'" )
.arg( layer->displayExpression(), enhancedSearch ) );
- req.setLimit( 30 );
+ req.setLimit( 6 );
+
+ QgsFeatureRequest exactMatchRequest = req;
+ exactMatchRequest.setFilterExpression( QStringLiteral( "%1 ILIKE '%2'" )
+ .arg( layer->displayExpression(), enhancedSearch ) );
+ exactMatchRequest.setLimit( 10 );
std::shared_ptr preparedLayer( new PreparedLayer() );
preparedLayer->expression = expression;
@@ -378,6 +400,7 @@ void QgsAllLayersFeaturesLocatorFilter::prepare( const QString &string, const Qg
preparedLayer->layerName = layer->name();
preparedLayer->featureSource.reset( new QgsVectorLayerFeatureSource( layer ) );
preparedLayer->request = req;
+ preparedLayer->exactMatchRequest = exactMatchRequest;
preparedLayer->layerIcon = QgsMapLayerModel::iconForLayer( layer );
mPreparedLayers.append( preparedLayer );
@@ -394,12 +417,47 @@ void QgsAllLayersFeaturesLocatorFilter::fetchResults( const QString &string, con
for ( auto preparedLayer : qgis::as_const( mPreparedLayers ) )
{
foundInCurrentLayer = 0;
+
+ QgsFeatureIds foundFeatureIds;
+
+ QgsFeatureIterator exactMatchIt = preparedLayer->featureSource->getFeatures( preparedLayer->exactMatchRequest );
+ while ( exactMatchIt.nextFeature( f ) )
+ {
+ if ( feedback->isCanceled() )
+ return;
+
+ QgsLocatorResult result;
+ result.group = preparedLayer->layerName;
+
+ preparedLayer->context.setFeature( f );
+
+ result.displayString = preparedLayer->expression.evaluate( &( preparedLayer->context ) ).toString();
+
+ result.userData = QVariantList() << f.id() << preparedLayer->layerId;
+ foundFeatureIds << f.id();
+ result.icon = preparedLayer->layerIcon;
+ result.score = static_cast< double >( string.length() ) / result.displayString.size();
+
+ result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form…" ) );
+ emit resultFetched( result );
+
+ foundInCurrentLayer++;
+ foundInTotal++;
+ if ( foundInCurrentLayer >= mMaxResultsPerLayer )
+ break;
+ }
+ if ( foundInTotal >= mMaxTotalResults )
+ break;
+
QgsFeatureIterator it = preparedLayer->featureSource->getFeatures( preparedLayer->request );
while ( it.nextFeature( f ) )
{
if ( feedback->isCanceled() )
return;
+ if ( foundFeatureIds.contains( f.id() ) )
+ continue;
+
QgsLocatorResult result;
result.group = preparedLayer->layerName;
@@ -524,30 +582,21 @@ void QgsSettingsLocatorFilter::fetchResults( const QString &string, const QgsLoc
for ( auto optionsPagesIterator = optionsPagesMap.constBegin(); optionsPagesIterator != optionsPagesMap.constEnd(); ++optionsPagesIterator )
{
QString title = optionsPagesIterator.key();
- if ( stringMatches( title, string ) || ( context.usingPrefix && string.isEmpty() ) )
- {
- matchingSettingsPagesMap.insert( title + " (" + tr( "Options" ) + ")", settingsPage( QStringLiteral( "optionpage" ), QString::number( optionsPagesIterator.value() ) ) );
- }
+ matchingSettingsPagesMap.insert( title + " (" + tr( "Options" ) + ")", settingsPage( QStringLiteral( "optionpage" ), QString::number( optionsPagesIterator.value() ) ) );
}
QMap projectPropertyPagesMap = QgisApp::instance()->projectPropertiesPagesMap();
for ( auto projectPropertyPagesIterator = projectPropertyPagesMap.constBegin(); projectPropertyPagesIterator != projectPropertyPagesMap.constEnd(); ++projectPropertyPagesIterator )
{
QString title = projectPropertyPagesIterator.key();
- if ( stringMatches( title, string ) || ( context.usingPrefix && string.isEmpty() ) )
- {
- matchingSettingsPagesMap.insert( title + " (" + tr( "Project Properties" ) + ")", settingsPage( QStringLiteral( "projectpropertypage" ), projectPropertyPagesIterator.value() ) );
- }
+ matchingSettingsPagesMap.insert( title + " (" + tr( "Project Properties" ) + ")", settingsPage( QStringLiteral( "projectpropertypage" ), projectPropertyPagesIterator.value() ) );
}
QMap settingPagesMap = QgisApp::instance()->settingPagesMap();
for ( auto settingPagesIterator = settingPagesMap.constBegin(); settingPagesIterator != settingPagesMap.constEnd(); ++settingPagesIterator )
{
QString title = settingPagesIterator.key();
- if ( stringMatches( title, string ) || ( context.usingPrefix && string.isEmpty() ) )
- {
- matchingSettingsPagesMap.insert( title, settingsPage( QStringLiteral( "settingspage" ), settingPagesIterator.value() ) );
- }
+ matchingSettingsPagesMap.insert( title, settingsPage( QStringLiteral( "settingspage" ), settingPagesIterator.value() ) );
}
for ( auto matchingSettingsPagesIterator = matchingSettingsPagesMap.constBegin(); matchingSettingsPagesIterator != matchingSettingsPagesMap.constEnd(); ++matchingSettingsPagesIterator )
@@ -558,8 +607,17 @@ void QgsSettingsLocatorFilter::fetchResults( const QString &string, const QgsLoc
result.filter = this;
result.displayString = title;
result.userData.setValue( settingsPage );
- result.score = static_cast< double >( string.length() ) / title.length();
- emit resultFetched( result );
+
+ if ( context.usingPrefix && string.isEmpty() )
+ {
+ emit resultFetched( result );
+ continue;
+ }
+
+ result.score = fuzzyScore( result.displayString, string );;
+
+ if ( result.score > 0 )
+ emit resultFetched( result );
}
}
@@ -614,22 +672,28 @@ void QgsBookmarkLocatorFilter::fetchResults( const QString &string, const QgsLoc
while ( i.hasNext() )
{
i.next();
+
if ( feedback->isCanceled() )
return;
QString name = i.key();
+ QModelIndex index = i.value();
+ QgsLocatorResult result;
+ result.filter = this;
+ result.displayString = name;
+ result.userData = index;
+ result.icon = QgsApplication::getThemeIcon( QStringLiteral( "/mItemBookmark.svg" ) );
- if ( stringMatches( name, string ) || ( context.usingPrefix && string.isEmpty() ) )
+ if ( context.usingPrefix && string.isEmpty() )
{
- QModelIndex index = i.value();
- QgsLocatorResult result;
- result.filter = this;
- result.displayString = name;
- result.userData = index;
- result.icon = QgsApplication::getThemeIcon( QStringLiteral( "/mItemBookmark.svg" ) );
- result.score = static_cast< double >( string.length() ) / name.length();
emit resultFetched( result );
+ continue;
}
+
+ result.score = fuzzyScore( result.displayString, string );
+
+ if ( result.score > 0 )
+ emit resultFetched( result );
}
}
diff --git a/src/app/locator/qgsinbuiltlocatorfilters.h b/src/app/locator/qgsinbuiltlocatorfilters.h
index 25bbc36e3ec9..173862cd050c 100644
--- a/src/app/locator/qgsinbuiltlocatorfilters.h
+++ b/src/app/locator/qgsinbuiltlocatorfilters.h
@@ -134,6 +134,7 @@ class APP_EXPORT QgsAllLayersFeaturesLocatorFilter : public QgsLocatorFilter
QgsExpressionContext context;
std::unique_ptr featureSource;
QgsFeatureRequest request;
+ QgsFeatureRequest exactMatchRequest;
QString layerName;
QString layerId;
QIcon layerIcon;
diff --git a/src/app/main.cpp b/src/app/main.cpp
index 5797b28e31f1..94134d9af948 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -1110,8 +1110,7 @@ int main( int argc, char *argv[] )
// An app bundled with QGIS_MACAPP_BUNDLE > 0 is considered a release bundle.
QString relLibPath( QDir::cleanPath( QCoreApplication::applicationDirPath().append( "/../PlugIns" ) ) );
// Note: relLibPath becomes the defacto QT_PLUGINS_DIR of a release app bundle
- if ( QFile::exists( relLibPath + QStringLiteral( "/imageformats" ) )
- && QFile::exists( relLibPath + QStringLiteral( "/codecs" ) ) )
+ if ( QFile::exists( relLibPath + QStringLiteral( "/imageformats" ) ) )
{
// We are in a release app bundle.
// Strip QT_PLUGINS_DIR because it will crash a launched release app bundle, since
diff --git a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp
index c258d59ea61a..af89ff84ab67 100644
--- a/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp
+++ b/src/app/mesh/qgsmeshdatasetgrouptreeview.cpp
@@ -470,8 +470,8 @@ void QgsMeshDatasetGroupTreeView::setActiveGroupFromActiveDataset()
if ( mMeshLayer )
{
const QgsMeshRendererSettings rendererSettings = mMeshLayer->rendererSettings();
- scalarGroup = rendererSettings.activeScalarDataset().group();
- vectorGroup = rendererSettings.activeVectorDataset().group();
+ scalarGroup = rendererSettings.activeScalarDatasetGroup();
+ vectorGroup = rendererSettings.activeVectorDatasetGroup();
}
setActiveScalarGroup( scalarGroup );
diff --git a/src/app/mesh/qgsmeshlayerproperties.cpp b/src/app/mesh/qgsmeshlayerproperties.cpp
index fbdc011a453f..2edcbeb72e7e 100644
--- a/src/app/mesh/qgsmeshlayerproperties.cpp
+++ b/src/app/mesh/qgsmeshlayerproperties.cpp
@@ -21,12 +21,15 @@
#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgscoordinatetransform.h"
+#include "qgsfileutils.h"
#include "qgshelp.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayerstylemanager.h"
+#include "qgsmaplayerstyleguiutils.h"
#include "qgsmeshlayer.h"
#include "qgsmeshlayerproperties.h"
+#include "qgsmeshstaticdatasetwidget.h"
#include "qgsproject.h"
#include "qgsprojectionselectiondialog.h"
#include "qgsrenderermeshpropertieswidget.h"
@@ -49,6 +52,13 @@ QgsMeshLayerProperties::QgsMeshLayerProperties( QgsMapLayer *lyr, QgsMapCanvas *
mRendererMeshPropertiesWidget = new QgsRendererMeshPropertiesWidget( mMeshLayer, canvas, this );
mOptsPage_StyleContent->layout()->addWidget( mRendererMeshPropertiesWidget );
+ mStaticScalarWidget->setLayer( mMeshLayer );
+
+ mTemporalProviderTimeUnitComboBox->addItem( tr( "Seconds" ), QgsUnitTypes::TemporalSeconds );
+ mTemporalProviderTimeUnitComboBox->addItem( tr( "Minutes" ), QgsUnitTypes::TemporalMinutes );
+ mTemporalProviderTimeUnitComboBox->addItem( tr( "Hours" ), QgsUnitTypes::TemporalHours );
+ mTemporalProviderTimeUnitComboBox->addItem( tr( "Days" ), QgsUnitTypes::TemporalDays );
+
connect( mLayerOrigNameLineEd, &QLineEdit::textEdited, this, &QgsMeshLayerProperties::updateLayerName );
connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged, this, &QgsMeshLayerProperties::changeCrs );
connect( mAddDatasetButton, &QPushButton::clicked, this, &QgsMeshLayerProperties::addDataset );
@@ -64,12 +74,20 @@ QgsMeshLayerProperties::QgsMeshLayerProperties( QgsMapLayer *lyr, QgsMapCanvas *
connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsMeshLayerProperties::apply );
connect( mMeshLayer, &QgsMeshLayer::dataChanged, this, &QgsMeshLayerProperties::syncAndRepaint );
+ connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsMeshLayerProperties::showHelp );
+
+ connect( mTemporalReloadButton, &QPushButton::clicked, this, &QgsMeshLayerProperties::reloadTemporalProperties );
+ connect( mTemporalDateTimeReference, &QDateTimeEdit::dateTimeChanged, this, &QgsMeshLayerProperties::onTimeReferenceChange );
+ connect( mTemporalStaticDatasetCheckBox, &QCheckBox::toggled, this, &QgsMeshLayerProperties::onStaticDatasetCheckBoxChanged );
+ connect( mMeshLayer, &QgsMeshLayer::activeScalarDatasetGroupChanged, mStaticScalarWidget, &QgsMeshStaticDatasetWidget::setScalarDatasetGroup );
+ connect( mMeshLayer, &QgsMeshLayer::activeVectorDatasetGroupChanged, mStaticScalarWidget, &QgsMeshStaticDatasetWidget::setVectorDatasetGroup );
#ifdef HAVE_3D
mMesh3DWidget = new QgsMeshLayer3DRendererWidget( mMeshLayer, canvas, mOptsPage_3DView );
mOptsPage_3DView->setLayout( new QVBoxLayout( mOptsPage_3DView ) );
mOptsPage_3DView->layout()->addWidget( mMesh3DWidget );
+ mOptsPage_3DView->setProperty( "helpPage", QStringLiteral( "working_with_mesh/mesh_properties.html#d-view-properties" ) );
#else
delete mOptsPage_3DView; // removes both the "3d view" list item and its page
#endif
@@ -91,6 +109,25 @@ QgsMeshLayerProperties::QgsMeshLayerProperties( QgsMapLayer *lyr, QgsMapCanvas *
if ( !mMeshLayer->styleManager()->isDefault( mMeshLayer->styleManager()->currentStyle() ) )
title += QStringLiteral( " (%1)" ).arg( mMeshLayer->styleManager()->currentStyle() );
restoreOptionsBaseUi( title );
+
+ //Add help page references
+ mOptsPage_Information->setProperty( "helpPage", QStringLiteral( "working_with_mesh/mesh_properties.html#information-properties" ) );
+ mOptsPage_Source->setProperty( "helpPage", QStringLiteral( "working_with_mesh/mesh_properties.html#source-properties" ) );
+ mOptsPage_Style->setProperty( "helpPage", QStringLiteral( "working_with_mesh/mesh_properties.html#symbology-properties" ) );
+ mOptsPage_Rendering->setProperty( "helpPage", QStringLiteral( "working_with_mesh/mesh_properties.html#rendering-properties" ) );
+
+
+ QPushButton *btnStyle = new QPushButton( tr( "Style" ) );
+ QMenu *menuStyle = new QMenu( this );
+ menuStyle->addAction( tr( "Load Style…" ), this, &QgsMeshLayerProperties::loadStyle );
+ menuStyle->addAction( tr( "Save Style…" ), this, &QgsMeshLayerProperties::saveStyleAs );
+ menuStyle->addSeparator();
+ menuStyle->addAction( tr( "Save as Default" ), this, &QgsMeshLayerProperties::saveDefaultStyle );
+ menuStyle->addAction( tr( "Restore Default" ), this, &QgsMeshLayerProperties::loadDefaultStyle );
+ btnStyle->setMenu( menuStyle );
+ connect( menuStyle, &QMenu::aboutToShow, this, &QgsMeshLayerProperties::aboutToShowStyleMenu );
+
+ buttonBox->addButton( btnStyle, QDialogButtonBox::ResetRole );
}
void QgsMeshLayerProperties::syncToLayer()
@@ -154,6 +191,21 @@ void QgsMeshLayerProperties::syncToLayer()
mSimplifyMeshGroupBox->setChecked( simplifySettings.isEnabled() );
mSimplifyReductionFactorSpinBox->setValue( simplifySettings.reductionFactor() );
mSimplifyMeshResolutionSpinBox->setValue( simplifySettings.meshResolution() );
+
+ QgsDebugMsgLevel( QStringLiteral( "populate temporal tab" ), 4 );
+ whileBlocking( mTemporalDateTimeReference )->setDateTime( mMeshLayer->temporalProperties()->referenceTime() );
+ const QgsDateTimeRange timeRange = mMeshLayer->temporalProperties()->timeExtent();
+ mTemporalDateTimeStart->setDateTime( timeRange.begin() );
+ mTemporalDateTimeEnd->setDateTime( timeRange.end() );
+ if ( mMeshLayer->dataProvider() )
+ {
+ mTemporalProviderTimeUnitComboBox->setCurrentIndex(
+ mTemporalProviderTimeUnitComboBox->findData( mMeshLayer->dataProvider()->temporalCapabilities()->temporalUnit() ) );
+ }
+
+ mStaticScalarWidget->syncToLayer();
+ mStaticScalarWidget->setVisible( !mMeshLayer->temporalProperties()->isActive() );
+ mTemporalStaticDatasetCheckBox->setChecked( !mMeshLayer->temporalProperties()->isActive() );
}
void QgsMeshLayerProperties::addDataset()
@@ -170,7 +222,7 @@ void QgsMeshLayerProperties::addDataset()
if ( openFileString.isEmpty() )
{
- return; //canceled by the user
+ return; // canceled by the user
}
QFileInfo openFileInfo( openFileString );
@@ -189,6 +241,107 @@ void QgsMeshLayerProperties::addDataset()
}
}
+void QgsMeshLayerProperties::loadDefaultStyle()
+{
+ bool defaultLoadedFlag = false;
+ QString myMessage = mMeshLayer->loadDefaultStyle( defaultLoadedFlag );
+ // reset if the default style was loaded OK only
+ if ( defaultLoadedFlag )
+ {
+ syncToLayer();
+ }
+ else
+ {
+ // otherwise let the user know what went wrong
+ QMessageBox::information( this,
+ tr( "Default Style" ),
+ myMessage
+ );
+ }
+}
+
+void QgsMeshLayerProperties::saveDefaultStyle()
+{
+ apply(); // make sure the style to save is up-to-date
+
+ // a flag passed by reference
+ bool defaultSavedFlag = false;
+ // after calling this the above flag will be set true for success
+ // or false if the save operation failed
+ QString myMessage = mMeshLayer->saveDefaultStyle( defaultSavedFlag );
+ if ( !defaultSavedFlag )
+ {
+ // let the user know what went wrong
+ QMessageBox::information( this,
+ tr( "Default Style" ),
+ myMessage
+ );
+ }
+}
+
+void QgsMeshLayerProperties::loadStyle()
+{
+ QgsSettings settings;
+ QString lastUsedDir = settings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
+
+ QString fileName = QFileDialog::getOpenFileName(
+ this,
+ tr( "Load rendering setting from style file" ),
+ lastUsedDir,
+ tr( "QGIS Layer Style File" ) + " (*.qml)" );
+ if ( fileName.isEmpty() )
+ return;
+
+ // ensure the user never omits the extension from the file name
+ if ( !fileName.endsWith( QLatin1String( ".qml" ), Qt::CaseInsensitive ) )
+ fileName += QLatin1String( ".qml" );
+
+ mOldStyle = mMeshLayer->styleManager()->style( mMeshLayer->styleManager()->currentStyle() );
+
+ bool defaultLoadedFlag = false;
+ QString message = mMeshLayer->loadNamedStyle( fileName, defaultLoadedFlag );
+ if ( defaultLoadedFlag )
+ {
+ settings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( fileName ).absolutePath() );
+ syncToLayer();
+ }
+ else
+ {
+ QMessageBox::information( this, tr( "Load Style" ), message );
+ }
+}
+
+void QgsMeshLayerProperties::saveStyleAs()
+{
+ QgsSettings settings;
+ QString lastUsedDir = settings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
+
+ QString outputFileName = QFileDialog::getSaveFileName(
+ this,
+ tr( "Save layer properties as style file" ),
+ lastUsedDir,
+ tr( "QGIS Layer Style File" ) + " (*.qml)" );
+ if ( outputFileName.isEmpty() )
+ return;
+
+ // ensure the user never omits the extension from the file name
+ outputFileName = QgsFileUtils::ensureFileNameHasExtension( outputFileName, QStringList() << QStringLiteral( "qml" ) );
+
+ apply(); // make sure the style to save is up-to-date
+
+ // then export style
+ bool defaultLoadedFlag = false;
+ QString message;
+ message = mMeshLayer->saveNamedStyle( outputFileName, defaultLoadedFlag );
+
+ if ( defaultLoadedFlag )
+ {
+ settings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( outputFileName ).absolutePath() );
+ }
+ else
+ QMessageBox::information( this, tr( "Save Style" ), message );
+}
+
void QgsMeshLayerProperties::apply()
{
Q_ASSERT( mRendererMeshPropertiesWidget );
@@ -225,9 +378,26 @@ void QgsMeshLayerProperties::apply()
mMeshLayer->setMeshSimplificationSettings( simplifySettings );
+ QgsDebugMsgLevel( QStringLiteral( "processing temporal tab" ), 4 );
+ /*
+ * Temporal Tab
+ */
+
+ mMeshLayer->setReferenceTime( mTemporalDateTimeReference->dateTime() );
+ if ( mMeshLayer->dataProvider() )
+ mMeshLayer->dataProvider()->setTemporalUnit(
+ static_cast( mTemporalProviderTimeUnitComboBox->currentData().toInt() ) );
+
+ mStaticScalarWidget->apply();
+ bool needEmitRendererChanged = mMeshLayer->temporalProperties()->isActive() == mTemporalStaticDatasetCheckBox->isChecked();
+ mMeshLayer->temporalProperties()->setIsActive( !mTemporalStaticDatasetCheckBox->isChecked() );
+
if ( needMeshUpdating )
mMeshLayer->reload();
+ if ( needEmitRendererChanged )
+ emit mMeshLayer->rendererChanged();
+
//make sure the layer is redrawn
mMeshLayer->triggerRepaint();
@@ -251,3 +421,57 @@ void QgsMeshLayerProperties::syncAndRepaint()
syncToLayer();
mMeshLayer->triggerRepaint();
}
+
+void QgsMeshLayerProperties::showHelp()
+{
+ const QVariant helpPage = mOptionsStackedWidget->currentWidget()->property( "helpPage" );
+
+ if ( helpPage.isValid() )
+ {
+ QgsHelp::openHelp( helpPage.toString() );
+ }
+ else
+ {
+ QgsHelp::openHelp( QStringLiteral( "working_with_mesh/mesh_properties.html" ) );
+ }
+}
+
+void QgsMeshLayerProperties::aboutToShowStyleMenu()
+{
+ QMenu *m = qobject_cast( sender() );
+
+ QgsMapLayerStyleGuiUtils::instance()->removesExtraMenuSeparators( m );
+ // re-add style manager actions!
+ m->addSeparator();
+ QgsMapLayerStyleGuiUtils::instance()->addStyleManagerActions( m, mMeshLayer );
+}
+
+void QgsMeshLayerProperties::reloadTemporalProperties()
+{
+ QgsMeshDataProviderTemporalCapabilities *temporalCapabalities = mMeshLayer->dataProvider()->temporalCapabilities();
+ QgsDateTimeRange timeExtent;
+ QDateTime referenceTime = temporalCapabalities->referenceTime();
+ if ( referenceTime.isValid() )
+ {
+ timeExtent = temporalCapabalities->timeExtent();
+ whileBlocking( mTemporalDateTimeReference )->setDateTime( referenceTime );
+ }
+ else
+ // The reference time already here is used again to define the time extent
+ timeExtent = temporalCapabalities->timeExtent( mTemporalDateTimeReference->dateTime() );
+
+ mTemporalDateTimeStart->setDateTime( timeExtent.begin() );
+ mTemporalDateTimeEnd->setDateTime( timeExtent.end() );
+}
+
+void QgsMeshLayerProperties::onTimeReferenceChange()
+{
+ const QgsDateTimeRange &timeExtent = mMeshLayer->dataProvider()->temporalCapabilities()->timeExtent( mTemporalDateTimeReference->dateTime() );
+ mTemporalDateTimeStart->setDateTime( timeExtent.begin() );
+ mTemporalDateTimeEnd->setDateTime( timeExtent.end() );
+}
+
+void QgsMeshLayerProperties::onStaticDatasetCheckBoxChanged()
+{
+ mStaticScalarWidget->setVisible( mTemporalStaticDatasetCheckBox->isChecked() );
+}
diff --git a/src/app/mesh/qgsmeshlayerproperties.h b/src/app/mesh/qgsmeshlayerproperties.h
index b6fcbe80bdeb..68b2dfadab17 100644
--- a/src/app/mesh/qgsmeshlayerproperties.h
+++ b/src/app/mesh/qgsmeshlayerproperties.h
@@ -19,6 +19,7 @@
#include "ui_qgsmeshlayerpropertiesbase.h"
+#include "qgsmaplayerstylemanager.h"
#include "qgsoptionsdialogbase.h"
#include "qgsguiutils.h"
#include "qgis_app.h"
@@ -28,6 +29,7 @@ class QgsMapCanvas;
class QgsMeshLayer;
class QgsRendererMeshPropertiesWidget;
class QgsMeshLayer3DRendererWidget;
+class QgsMeshStaticDatasetWidget;
/**
* Property sheet for a mesh map layer.
@@ -46,20 +48,35 @@ class APP_EXPORT QgsMeshLayerProperties : public QgsOptionsDialogBase, private U
QgsMeshLayerProperties( QgsMapLayer *lyr, QgsMapCanvas *canvas, QWidget *parent = nullptr, Qt::WindowFlags = QgsGuiUtils::ModalDialogFlags );
private slots:
- //! Synchronize widgets state with associated mesh layer
+ //! Synchronizes widgets state with associated mesh layer
void syncToLayer();
//!Applies the settings made in the dialog without closing the box
void apply();
//! \brief Slot to update layer display name as original is edited.
void updateLayerName( const QString &text );
- //! Synchronize GUI state with associated mesh layer and trigger repaint
+ //! Synchronizes GUI state with associated mesh layer and trigger repaint
void syncAndRepaint();
- //! Change layer coordinate reference system
+ //! Changes layer coordinate reference system
void changeCrs( const QgsCoordinateReferenceSystem &crs );
- //! Associate dataset to the mesh layer
+ //! Associates dataset to the mesh layer
void addDataset();
+ //! Loads the default style when appropriate button is pressed
+ void loadDefaultStyle();
+ //! Saves the default style when appropriate button is pressed
+ void saveDefaultStyle();
+ //! Loads a saved style when appropriate button is pressed
+ void loadStyle();
+ //! Saves a style when appriate button is pressed
+ void saveStyleAs();
+ //! Prepares style menu
+ void aboutToShowStyleMenu();
+ //! Reloads temporal properties from the provider
+ void reloadTemporalProperties();
+ void onTimeReferenceChange();
+
+ void onStaticDatasetCheckBoxChanged();
private:
//! Pointer to the mesh styling widget
QgsRendererMeshPropertiesWidget *mRendererMeshPropertiesWidget = nullptr;
@@ -70,7 +87,14 @@ class APP_EXPORT QgsMeshLayerProperties : public QgsOptionsDialogBase, private U
//! Pointer to mesh 3d styling widget
QgsMeshLayer3DRendererWidget *mMesh3DWidget = nullptr;
+ /**
+ * Previous layer style. Used to reset style to previous state if new style
+ * was loaded but dialog is canceled */
+ QgsMapLayerStyle mOldStyle;
+
friend class TestQgsMeshLayerPropertiesDialog;
+
+ void showHelp();
};
diff --git a/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp b/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp
index 32345a812386..777f448c2628 100644
--- a/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp
+++ b/src/app/mesh/qgsmeshrendereractivedatasetwidget.cpp
@@ -23,54 +23,25 @@
#include "qgsmeshlayer.h"
#include "qgsmessagelog.h"
#include "qgsmeshrenderersettings.h"
-#include "qgsmeshtimeformatdialog.h"
QgsMeshRendererActiveDatasetWidget::QgsMeshRendererActiveDatasetWidget( QWidget *parent )
: QWidget( parent )
{
setupUi( this );
- connect( mTimeComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshRendererActiveDatasetWidget::onActiveTimeChanged );
- connect( mDatasetSlider, &QSlider::valueChanged, mTimeComboBox, &QComboBox::setCurrentIndex );
-
- connect( mTimeFormatButton, &QToolButton::clicked, this, &QgsMeshRendererActiveDatasetWidget::onTimeSettingsClicked );
- connect( mFirstDatasetButton, &QToolButton::clicked, this, &QgsMeshRendererActiveDatasetWidget::onFirstTimeClicked );
- connect( mPreviousDatasetButton, &QToolButton::clicked, this, &QgsMeshRendererActiveDatasetWidget::onPreviousTimeClicked );
- connect( mNextDatasetButton, &QToolButton::clicked, this, &QgsMeshRendererActiveDatasetWidget::onNextTimeClicked );
- connect( mLastDatasetButton, &QToolButton::clicked, this, &QgsMeshRendererActiveDatasetWidget::onLastTimeClicked );
- connect( mDatasetPlaybackButton, &QToolButton::clicked, this, &QgsMeshRendererActiveDatasetWidget::onDatasetPlaybackClicked );
-
connect( mDatasetGroupTreeView, &QgsMeshDatasetGroupTreeView::activeScalarGroupChanged,
this, &QgsMeshRendererActiveDatasetWidget::onActiveScalarGroupChanged );
connect( mDatasetGroupTreeView, &QgsMeshDatasetGroupTreeView::activeVectorGroupChanged,
this, &QgsMeshRendererActiveDatasetWidget::onActiveVectorGroupChanged );
-
- mDatasetPlaybackTimer = new QTimer( this );
- connect( mDatasetPlaybackTimer, &QTimer::timeout,
- this, qgis::overload<>::of( &QgsMeshRendererActiveDatasetWidget::datasetPlaybackTick ) );
-
- mDatasetPlaybackButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionPlay.svg" ) ) );
- mFirstDatasetButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionFirst.svg" ) ) );
- mPreviousDatasetButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionPrevious.svg" ) ) );
- mNextDatasetButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionNext.svg" ) ) );
- mLastDatasetButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionLast.svg" ) ) );
}
-QgsMeshRendererActiveDatasetWidget::~QgsMeshRendererActiveDatasetWidget()
-{
- mDatasetPlaybackTimer->stop();
-}
+QgsMeshRendererActiveDatasetWidget::~QgsMeshRendererActiveDatasetWidget() = default;
+
void QgsMeshRendererActiveDatasetWidget::setLayer( QgsMeshLayer *layer )
{
mMeshLayer = layer;
mDatasetGroupTreeView->setLayer( layer );
- setTimeRange();
-
- if ( layer )
- {
- connect( mMeshLayer, &QgsMeshLayer::timeSettingsChanged, this, &QgsMeshRendererActiveDatasetWidget::setTimeRange );
- }
}
int QgsMeshRendererActiveDatasetWidget::activeScalarDatasetGroup() const
@@ -83,79 +54,6 @@ int QgsMeshRendererActiveDatasetWidget::activeVectorDatasetGroup() const
return mActiveVectorDatasetGroup;
}
-QgsMeshDatasetIndex QgsMeshRendererActiveDatasetWidget::activeScalarDataset() const
-{
- return mActiveScalarDataset;
-}
-
-QgsMeshDatasetIndex QgsMeshRendererActiveDatasetWidget::activeVectorDataset() const
-{
- return mActiveVectorDataset;
-}
-
-void QgsMeshRendererActiveDatasetWidget::setTimeRange()
-{
- // figure out which dataset group contains the greatest number of datasets.
- // this group will be used to initialize the time combo box.
- int datasetCount = 0;
- int groupWithMaximumDatasets = -1;
- if ( mMeshLayer && mMeshLayer->dataProvider() )
- {
- for ( int i = 0; i < mMeshLayer->dataProvider()->datasetGroupCount(); ++i )
- {
- int currentCount = mMeshLayer->dataProvider()->datasetCount( i );
- if ( currentCount > datasetCount )
- {
- datasetCount = currentCount;
- groupWithMaximumDatasets = i;
- }
- }
- }
-
- // update slider
- mDatasetSlider->blockSignals( true );
- mDatasetSlider->setMinimum( 0 );
- mDatasetSlider->setMaximum( datasetCount - 1 );
- mDatasetSlider->blockSignals( false );
-
- // update combobox
- mTimeComboBox->blockSignals( true );
- int currentIndex = mTimeComboBox->currentIndex();
- mTimeComboBox->clear();
- if ( groupWithMaximumDatasets > -1 )
- {
- for ( int i = 0; i < datasetCount; ++i )
- {
- QgsMeshDatasetIndex index( groupWithMaximumDatasets, i );
- QgsMeshDatasetMetadata meta = mMeshLayer->dataProvider()->datasetMetadata( index );
- double time = meta.time();
- mTimeComboBox->addItem( mMeshLayer->formatTime( time ), time );
- }
- }
- mTimeComboBox->setCurrentIndex( currentIndex );
- mTimeComboBox->blockSignals( false );
- updateMetadata();
- // enable/disable time controls depending on whether the data set is time varying
- enableTimeControls();
-}
-
-void QgsMeshRendererActiveDatasetWidget::enableTimeControls()
-{
- const QgsMeshDataProvider *provider = mMeshLayer->dataProvider();
- if ( !provider )
- return;
- const int scalarDatesets = provider->datasetCount( mActiveScalarDatasetGroup );
- const int vectorDatesets = provider->datasetCount( mActiveVectorDatasetGroup );
- const bool isTimeVarying = ( scalarDatesets > 1 ) || ( vectorDatesets > 1 );
- mTimeComboBox->setEnabled( isTimeVarying );
- mDatasetSlider->setEnabled( isTimeVarying );
- mTimeFormatButton->setEnabled( isTimeVarying );
- mFirstDatasetButton->setEnabled( isTimeVarying );
- mPreviousDatasetButton->setEnabled( isTimeVarying );
- mNextDatasetButton->setEnabled( isTimeVarying );
- mLastDatasetButton->setEnabled( isTimeVarying );
- mDatasetPlaybackButton->setEnabled( isTimeVarying );
-}
void QgsMeshRendererActiveDatasetWidget::onActiveScalarGroupChanged( int groupIndex )
{
@@ -163,13 +61,9 @@ void QgsMeshRendererActiveDatasetWidget::onActiveScalarGroupChanged( int groupIn
return;
mActiveScalarDatasetGroup = groupIndex;
-
- // enable/disable time slider controls
- enableTimeControls();
-
- // keep the same timestep if possible
- onActiveTimeChanged( mTimeComboBox->currentIndex() );
+ updateMetadata();
emit activeScalarGroupChanged( mActiveScalarDatasetGroup );
+ emit widgetChanged();
}
void QgsMeshRendererActiveDatasetWidget::onActiveVectorGroupChanged( int groupIndex )
@@ -178,127 +72,9 @@ void QgsMeshRendererActiveDatasetWidget::onActiveVectorGroupChanged( int groupIn
return;
mActiveVectorDatasetGroup = groupIndex;
- // enable/disable time slider controls
- enableTimeControls();
-
- // keep the same timestep if possible
- onActiveTimeChanged( mTimeComboBox->currentIndex() );
+ updateMetadata();
emit activeVectorGroupChanged( mActiveVectorDatasetGroup );
-}
-
-void QgsMeshRendererActiveDatasetWidget::onActiveTimeChanged( int value )
-{
- if ( !mMeshLayer || !mMeshLayer->dataProvider() )
- return;
-
- bool changed = false;
-
- QgsMeshDatasetIndex activeScalarDataset(
- mActiveScalarDatasetGroup,
- std::min( value, mMeshLayer->dataProvider()->datasetCount( mActiveScalarDatasetGroup ) - 1 )
- );
- if ( activeScalarDataset != mActiveScalarDataset )
- {
- mActiveScalarDataset = activeScalarDataset;
- changed = true;
- }
-
- QgsMeshDatasetIndex activeVectorDataset(
- mActiveVectorDatasetGroup,
- std::min( value, mMeshLayer->dataProvider()->datasetCount( mActiveVectorDatasetGroup ) - 1 )
- );
- if ( activeVectorDataset != mActiveVectorDataset )
- {
- mActiveVectorDataset = activeVectorDataset;
- changed = true;
- }
-
- if ( changed )
- {
- whileBlocking( mDatasetSlider )->setValue( value );
- updateMetadata();
- emit widgetChanged();
- }
-}
-
-void QgsMeshRendererActiveDatasetWidget::onTimeSettingsClicked()
-{
- if ( !mMeshLayer )
- return;
- QgsMeshTimeFormatDialog dlg( mMeshLayer );
- dlg.setModal( true );
- dlg.exec();
-}
-
-void QgsMeshRendererActiveDatasetWidget::onFirstTimeClicked()
-{
- mTimeComboBox->setCurrentIndex( 0 );
-}
-
-void QgsMeshRendererActiveDatasetWidget::onPreviousTimeClicked()
-{
- int idx = mTimeComboBox->currentIndex() - 1;
- if ( idx >= 0 )
- mTimeComboBox->setCurrentIndex( idx );
-}
-
-void QgsMeshRendererActiveDatasetWidget::onNextTimeClicked()
-{
- int idx = mTimeComboBox->currentIndex() + 1;
- if ( idx < mTimeComboBox->count() )
- mTimeComboBox->setCurrentIndex( idx );
-}
-
-void QgsMeshRendererActiveDatasetWidget::onLastTimeClicked()
-{
- mTimeComboBox->setCurrentIndex( mTimeComboBox->count() - 1 );
-}
-
-void QgsMeshRendererActiveDatasetWidget::onDatasetPlaybackClicked()
-{
- if ( mDatasetIsPlaying )
- {
- // stop playing
- mDatasetIsPlaying = false;
- mTimeComboBox->setEnabled( true );
- mTimeFormatButton->setEnabled( true );
- mDatasetSlider->setEnabled( true );
- mFirstDatasetButton->setEnabled( true );
- mPreviousDatasetButton->setEnabled( true );
- mNextDatasetButton->setEnabled( true );
- mLastDatasetButton->setEnabled( true );
- mDatasetPlaybackTimer->stop();
- mDatasetPlaybackButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionPlay.svg" ) ) );
- }
- else
- {
- // start
- mDatasetIsPlaying = true;
- mTimeComboBox->setEnabled( false );
- mTimeFormatButton->setEnabled( false );
- mDatasetSlider->setEnabled( false );
- mFirstDatasetButton->setEnabled( false );
- mPreviousDatasetButton->setEnabled( false );
- mNextDatasetButton->setEnabled( false );
- mLastDatasetButton->setEnabled( false );
- int intervalMs = 3000;
- if ( mMeshLayer )
- {
- intervalMs = static_cast( mMeshLayer->timeSettings().datasetPlaybackInterval() * 1000 );
- }
- datasetPlaybackTick();
- mDatasetPlaybackTimer->start( intervalMs );
- mDatasetPlaybackButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStop.svg" ) ) );
- }
-}
-
-void QgsMeshRendererActiveDatasetWidget::datasetPlaybackTick()
-{
- int nextIdx = mTimeComboBox->currentIndex() + 1;
- if ( nextIdx >= mTimeComboBox->count() )
- nextIdx = 0;
-
- mTimeComboBox->setCurrentIndex( nextIdx );
+ emit widgetChanged();
}
void QgsMeshRendererActiveDatasetWidget::updateMetadata()
@@ -312,33 +88,33 @@ void QgsMeshRendererActiveDatasetWidget::updateMetadata()
}
else
{
- if ( mActiveScalarDataset.isValid() )
+ if ( mActiveScalarDatasetGroup > -1 )
{
- if ( mActiveVectorDataset.isValid() )
+ if ( mActiveVectorDatasetGroup > -1 )
{
- if ( mActiveScalarDataset == mActiveVectorDataset )
+ if ( mActiveScalarDatasetGroup == mActiveVectorDatasetGroup )
{
- msg += metadata( mActiveScalarDataset );
+ msg += metadata( mActiveScalarDatasetGroup );
}
else
{
msg += QStringLiteral( "
%1 " ).arg( tr( "Scalar dataset" ) );
- msg += metadata( mActiveScalarDataset );
+ msg += metadata( mActiveScalarDatasetGroup );
msg += QStringLiteral( "
%1 " ).arg( tr( "Vector dataset" ) );
- msg += metadata( mActiveVectorDataset );
+ msg += metadata( mActiveVectorDatasetGroup );
msg += QStringLiteral( "" );
}
}
else
{
- msg += metadata( mActiveScalarDataset );
+ msg += metadata( mActiveScalarDatasetGroup );
}
}
else
{
- if ( mActiveVectorDataset.isValid() )
+ if ( mActiveVectorDatasetGroup > -1 )
{
- msg += metadata( mActiveVectorDataset );
+ msg += metadata( mActiveVectorDatasetGroup );
}
else
{
@@ -357,17 +133,6 @@ QString QgsMeshRendererActiveDatasetWidget::metadata( QgsMeshDatasetIndex datase
QString msg;
msg += QStringLiteral( "" );
- const QgsMeshDatasetMetadata meta = mMeshLayer->dataProvider()->datasetMetadata( datasetIndex );
- msg += QStringLiteral( "%1 %2 " )
- .arg( tr( "Is valid" ) )
- .arg( meta.isValid() ? tr( "Yes" ) : tr( "No" ) );
-
- const double time = meta.time();
- msg += QStringLiteral( "%1 %2 (%3) " )
- .arg( tr( "Time" ) )
- .arg( mMeshLayer->formatTime( time ) )
- .arg( time );
-
QString definedOnMesh;
if ( mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Face ) )
{
@@ -439,26 +204,12 @@ void QgsMeshRendererActiveDatasetWidget::syncToLayer()
const QgsMeshRendererSettings rendererSettings = mMeshLayer->rendererSettings();
mActiveScalarDatasetGroup = mDatasetGroupTreeView->activeScalarGroup();
mActiveVectorDatasetGroup = mDatasetGroupTreeView->activeVectorGroup();
- mActiveScalarDataset = rendererSettings.activeScalarDataset();
- mActiveVectorDataset = rendererSettings.activeVectorDataset();
}
else
{
mActiveScalarDatasetGroup = -1;
mActiveVectorDatasetGroup = -1;
- mActiveScalarDataset = QgsMeshDatasetIndex();
- mActiveVectorDataset = QgsMeshDatasetIndex();
}
- setTimeRange();
-
- int val = 0;
- if ( mActiveScalarDataset.isValid() )
- val = mActiveScalarDataset.dataset();
- else if ( mActiveVectorDataset.isValid() )
- val = mActiveVectorDataset.dataset();
-
- whileBlocking( mTimeComboBox )->setCurrentIndex( val );
- whileBlocking( mDatasetSlider )->setValue( val );
updateMetadata();
}
diff --git a/src/app/mesh/qgsmeshrendereractivedatasetwidget.h b/src/app/mesh/qgsmeshrendereractivedatasetwidget.h
index bca868f9e568..545dd12c8a56 100644
--- a/src/app/mesh/qgsmeshrendereractivedatasetwidget.h
+++ b/src/app/mesh/qgsmeshrendereractivedatasetwidget.h
@@ -55,12 +55,6 @@ class APP_EXPORT QgsMeshRendererActiveDatasetWidget : public QWidget, private Ui
//! Returns index of the active vector dataset group
int activeVectorDatasetGroup() const;
- //! Gets index of the selected/active scalar dataset
- QgsMeshDatasetIndex activeScalarDataset() const;
-
- //! Gets index of the selected/active vector dataset
- QgsMeshDatasetIndex activeVectorDataset() const;
-
//! Synchronizes widgets state with associated mesh layer
void syncToLayer();
@@ -78,32 +72,14 @@ class APP_EXPORT QgsMeshRendererActiveDatasetWidget : public QWidget, private Ui
private slots:
void onActiveScalarGroupChanged( int groupIndex );
void onActiveVectorGroupChanged( int groupIndex );
- void onActiveTimeChanged( int value );
- void onTimeSettingsClicked();
- void onFirstTimeClicked();
- void onPreviousTimeClicked();
- void onNextTimeClicked();
- void onLastTimeClicked();
- void onDatasetPlaybackClicked();
- void datasetPlaybackTick();
QString metadata( QgsMeshDatasetIndex datasetIndex );
private:
- //! Loops through all dataset groups and finds the maximum number of datasets
- void setTimeRange();
-
- //! Enables/Disables time controls depending on whether the selected datasets are time varying
- void enableTimeControls();
-
void updateMetadata();
QgsMeshLayer *mMeshLayer = nullptr; // not owned
int mActiveScalarDatasetGroup = -1;
int mActiveVectorDatasetGroup = -1;
- QgsMeshDatasetIndex mActiveScalarDataset;
- QgsMeshDatasetIndex mActiveVectorDataset;
- bool mDatasetIsPlaying = false;
- QTimer *mDatasetPlaybackTimer = nullptr;
};
#endif // QGSMESHRENDERERSCALARSETTINGSWIDGET_H
diff --git a/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp b/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp
index 0dbe304b6a39..83abc60e709e 100644
--- a/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp
+++ b/src/app/mesh/qgsmeshrendererscalarsettingswidget.cpp
@@ -55,13 +55,13 @@ QgsMeshRendererScalarSettingsWidget::QgsMeshRendererScalarSettingsWidget( QWidge
void QgsMeshRendererScalarSettingsWidget::setLayer( QgsMeshLayer *layer )
{
mMeshLayer = layer;
- mScalarInterpolationTypeComboBox->setEnabled( dataIsDefinedOnFaces() );
+ mScalarInterpolationTypeComboBox->setEnabled( !dataIsDefinedOnEdges() );
}
void QgsMeshRendererScalarSettingsWidget::setActiveDatasetGroup( int groupIndex )
{
mActiveDatasetGroup = groupIndex;
- mScalarInterpolationTypeComboBox->setEnabled( dataIsDefinedOnFaces() );
+ mScalarInterpolationTypeComboBox->setEnabled( !dataIsDefinedOnEdges() );
}
QgsMeshRendererScalarSettings QgsMeshRendererScalarSettingsWidget::settings() const
@@ -70,7 +70,7 @@ QgsMeshRendererScalarSettings QgsMeshRendererScalarSettingsWidget::settings() co
settings.setColorRampShader( mScalarColorRampShaderWidget->shader() );
settings.setClassificationMinimumMaximum( lineEditValue( mScalarMinLineEdit ), lineEditValue( mScalarMaxLineEdit ) );
settings.setOpacity( mOpacityWidget->opacity() );
- settings.setDataInterpolationMethod( dataIntepolationMethod() );
+ settings.setDataResamplingMethod( dataIntepolationMethod() );
settings.setEdgeWidth( mScalarEdgeWidthSpinBox->value() );
settings.setEdgeWidthUnit( mScalarEdgeWidthUnitSelectionWidget->unit() );
return settings;
@@ -98,7 +98,7 @@ void QgsMeshRendererScalarSettingsWidget::syncToLayer( )
whileBlocking( mScalarColorRampShaderWidget )->setFromShader( shader );
whileBlocking( mScalarColorRampShaderWidget )->setMinimumMaximum( min, max );
whileBlocking( mOpacityWidget )->setOpacity( settings.opacity() );
- int index = mScalarInterpolationTypeComboBox->findData( settings.dataInterpolationMethod() );
+ int index = mScalarInterpolationTypeComboBox->findData( settings.dataResamplingMethod() );
whileBlocking( mScalarInterpolationTypeComboBox )->setCurrentIndex( index );
bool hasEdges = ( mMeshLayer->dataProvider() &&
@@ -140,18 +140,11 @@ void QgsMeshRendererScalarSettingsWidget::recalculateMinMaxButtonClicked()
mScalarColorRampShaderWidget->setMinimumMaximumAndClassify( min, max );
}
-QgsMeshRendererScalarSettings::DataInterpolationMethod QgsMeshRendererScalarSettingsWidget::dataIntepolationMethod() const
+QgsMeshRendererScalarSettings::DataResamplingMethod QgsMeshRendererScalarSettingsWidget::dataIntepolationMethod() const
{
- if ( dataIsDefinedOnFaces() )
- {
- const int data = mScalarInterpolationTypeComboBox->currentData().toInt();
- const QgsMeshRendererScalarSettings::DataInterpolationMethod method = static_cast( data );
- return method;
- }
- else
- {
- return QgsMeshRendererScalarSettings::None;
- }
+ const int data = mScalarInterpolationTypeComboBox->currentData().toInt();
+ const QgsMeshRendererScalarSettings::DataResamplingMethod method = static_cast( data );
+ return method;
}
bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnFaces() const
@@ -167,4 +160,17 @@ bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnFaces() const
return onFaces;
}
+bool QgsMeshRendererScalarSettingsWidget::dataIsDefinedOnEdges() const
+{
+ if ( !mMeshLayer || !mMeshLayer->dataProvider() || !mMeshLayer->dataProvider()->isValid() )
+ return false;
+
+ if ( mActiveDatasetGroup < 0 )
+ return false;
+
+ QgsMeshDatasetGroupMetadata meta = mMeshLayer->dataProvider()->datasetGroupMetadata( mActiveDatasetGroup );
+ const bool onEdges = ( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnEdges );
+ return onEdges;
+}
+
diff --git a/src/app/mesh/qgsmeshrendererscalarsettingswidget.h b/src/app/mesh/qgsmeshrendererscalarsettingswidget.h
index 3b081d8e18b5..dbae76d8c7f4 100644
--- a/src/app/mesh/qgsmeshrendererscalarsettingswidget.h
+++ b/src/app/mesh/qgsmeshrendererscalarsettingswidget.h
@@ -65,9 +65,10 @@ class APP_EXPORT QgsMeshRendererScalarSettingsWidget : public QWidget, private U
private:
double lineEditValue( const QLineEdit *lineEdit ) const;
- QgsMeshRendererScalarSettings::DataInterpolationMethod dataIntepolationMethod() const;
+ QgsMeshRendererScalarSettings::DataResamplingMethod dataIntepolationMethod() const;
bool dataIsDefinedOnFaces() const;
+ bool dataIsDefinedOnEdges() const;
QgsMeshLayer *mMeshLayer = nullptr; // not owned
int mActiveDatasetGroup = -1;
diff --git a/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp b/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp
index 0fca69057d7b..c7f2315eea8d 100644
--- a/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp
+++ b/src/app/mesh/qgsmeshrenderervectorsettingswidget.cpp
@@ -26,7 +26,19 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge
mShaftLengthComboBox->setCurrentIndex( -1 );
+ mColoringMethodComboBox->addItem( tr( "Single Color" ), QgsMeshRendererVectorSettings::SingleColor );
+ mColoringMethodComboBox->addItem( tr( "Color Ramp Shader" ), QgsMeshRendererVectorSettings::ColorRamp );
+
connect( mColorWidget, &QgsColorButton::colorChanged, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
+ connect( mColoringMethodComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ),
+ this, &QgsMeshRendererVectorSettingsWidget::onColoringMethodChanged );
+ connect( mColorRampShaderWidget, &QgsColorRampShaderWidget::widgetChanged,
+ this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
+ connect( mColorRampShaderMinimumEditLine, &QLineEdit::textEdited,
+ this, &QgsMeshRendererVectorSettingsWidget::onColorRampMinMaxChanged );
+ connect( mColorRampShaderMaximumEditLine, &QLineEdit::textEdited,
+ this, &QgsMeshRendererVectorSettingsWidget::onColorRampMinMaxChanged );
+
connect( mLineWidthSpinBox, qgis::overload::of( &QgsDoubleSpinBox::valueChanged ),
this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
@@ -38,6 +50,10 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge
connect( mDisplayVectorsOnGridGroupBox, &QGroupBox::toggled, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged );
+ connect( mColorRampShaderLoadButton, &QPushButton::clicked, this, &QgsMeshRendererVectorSettingsWidget::loadColorRampShader );
+
+ onColoringMethodChanged();
+
QVector widgets;
widgets << mMinMagLineEdit << mMaxMagLineEdit
<< mHeadWidthLineEdit << mHeadLengthLineEdit
@@ -102,6 +118,9 @@ QgsMeshRendererVectorSettings QgsMeshRendererVectorSettingsWidget::settings() co
// basic
settings.setColor( mColorWidget->color() );
settings.setLineWidth( mLineWidthSpinBox->value() );
+ settings.setColoringMethod( static_cast
+ ( mColoringMethodComboBox->currentData().toInt() ) );
+ settings.setColorRampShader( mColorRampShaderWidget->shader() );
// filter by magnitude
double val = filterValue( mMinMagLineEdit->text(), -1 );
@@ -179,6 +198,10 @@ void QgsMeshRendererVectorSettingsWidget::syncToLayer( )
// basic
mColorWidget->setColor( settings.color() );
mLineWidthSpinBox->setValue( settings.lineWidth() );
+ mColoringMethodComboBox->setCurrentIndex( mColoringMethodComboBox->findData( settings.coloringMethod() ) );
+ mColorRampShaderWidget->setFromShader( settings.colorRampShader() );
+ mColorRampShaderMinimumEditLine->setText( QString::number( settings.colorRampShader().minimumValue() ) );
+ mColorRampShaderMaximumEditLine->setText( QString::number( settings.colorRampShader().maximumValue() ) );
// filter by magnitude
if ( settings.filterMin() > 0 )
@@ -246,6 +269,46 @@ void QgsMeshRendererVectorSettingsWidget::onStreamLineSeedingMethodChanged( int
mDisplayVectorsOnGridGroupBox->setEnabled( !enabled );
}
+void QgsMeshRendererVectorSettingsWidget::onColoringMethodChanged()
+{
+ mColorRampShaderGroupBox->setVisible( mColoringMethodComboBox->currentData() == QgsMeshRendererVectorSettings::ColorRamp );
+ mColorWidget->setVisible( mColoringMethodComboBox->currentData() == QgsMeshRendererVectorSettings::SingleColor );
+ mSimgleColorLabel->setVisible( mColoringMethodComboBox->currentData() == QgsMeshRendererVectorSettings::SingleColor );
+
+ if ( mColorRampShaderWidget->shader().colorRampItemList().isEmpty() )
+ loadColorRampShader();
+
+ emit widgetChanged();
+}
+
+void QgsMeshRendererVectorSettingsWidget::onColorRampMinMaxChanged()
+{
+ mColorRampShaderWidget->setMinimumMaximumAndClassify(
+ filterValue( mColorRampShaderMinimumEditLine->text(), 0 ),
+ filterValue( mColorRampShaderMaximumEditLine->text(), 0 ) );
+}
+
+void QgsMeshRendererVectorSettingsWidget::loadColorRampShader()
+{
+ if ( !mMeshLayer )
+ return;
+
+ QgsMeshDataProvider *provider = mMeshLayer->dataProvider();
+ int currentVectorDataSetGroupIndex = mMeshLayer->rendererSettings().activeVectorDatasetGroup();
+ if ( !provider ||
+ currentVectorDataSetGroupIndex < 0 ||
+ !provider->datasetGroupMetadata( currentVectorDataSetGroupIndex ).isVector() )
+ return;
+
+ const QgsMeshDatasetGroupMetadata meta = provider->datasetGroupMetadata( currentVectorDataSetGroupIndex );
+ double min = meta.minimum();
+ double max = meta.maximum();
+
+ mColorRampShaderWidget->setMinimumMaximumAndClassify( min, max );
+ whileBlocking( mColorRampShaderMinimumEditLine )->setText( QString::number( min ) );
+ whileBlocking( mColorRampShaderMaximumEditLine )->setText( QString::number( max ) );
+}
+
double QgsMeshRendererVectorSettingsWidget::filterValue( const QString &text, double errVal ) const
{
if ( text.isEmpty() )
diff --git a/src/app/mesh/qgsmeshrenderervectorsettingswidget.h b/src/app/mesh/qgsmeshrenderervectorsettingswidget.h
index f5433a25b88e..b60f52b8f9bb 100644
--- a/src/app/mesh/qgsmeshrenderervectorsettingswidget.h
+++ b/src/app/mesh/qgsmeshrenderervectorsettingswidget.h
@@ -63,6 +63,9 @@ class APP_EXPORT QgsMeshRendererVectorSettingsWidget : public QWidget, private U
private slots:
void onSymbologyChanged( int currentIndex );
void onStreamLineSeedingMethodChanged( int currentIndex );
+ void onColoringMethodChanged();
+ void onColorRampMinMaxChanged();
+ void loadColorRampShader();
private:
diff --git a/src/app/mesh/qgsmeshstaticdatasetwidget.cpp b/src/app/mesh/qgsmeshstaticdatasetwidget.cpp
new file mode 100644
index 000000000000..f5318dbf4f67
--- /dev/null
+++ b/src/app/mesh/qgsmeshstaticdatasetwidget.cpp
@@ -0,0 +1,118 @@
+/***************************************************************************
+ qgsmeshstaticdatasetwidget.cpp
+ -------------------------------------
+ begin : March 2020
+ copyright : (C) 2020 by Vincent Cloarec
+ email : vcloarec 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 "qgsmeshstaticdatasetwidget.h"
+
+#include "qgsmeshlayer.h"
+
+QgsMeshStaticDatasetWidget::QgsMeshStaticDatasetWidget( QWidget *parent ): QWidget( parent )
+{
+ setupUi( this );
+
+ mDatasetScalarModel = new QgsMeshDatasetListModel( this );
+ mScalarDatasetComboBox->setModel( mDatasetScalarModel );
+ mDatasetVectorModel = new QgsMeshDatasetListModel( this );
+ mVectorDatasetComboBox->setModel( mDatasetVectorModel );
+}
+
+void QgsMeshStaticDatasetWidget::setLayer( QgsMeshLayer *layer )
+{
+ mLayer = layer;
+}
+
+void QgsMeshStaticDatasetWidget::syncToLayer()
+{
+ if ( !mLayer )
+ return;
+
+ mScalarDatasetGroup = mLayer->rendererSettings().activeScalarDatasetGroup();
+ mVectorDatasetGroup = mLayer->rendererSettings().activeVectorDatasetGroup();
+ mDatasetScalarModel->setMeshLayer( mLayer );
+ mDatasetScalarModel->setDatasetGroup( mScalarDatasetGroup );
+ mDatasetVectorModel->setMeshLayer( mLayer );
+ mDatasetVectorModel->setDatasetGroup( mVectorDatasetGroup );
+
+ mScalarDatasetComboBox->setCurrentIndex( mLayer->staticScalarDatasetIndex().dataset() + 1 );
+ mVectorDatasetComboBox->setCurrentIndex( mLayer->staticVectorDatasetIndex().dataset() + 1 );
+}
+
+void QgsMeshStaticDatasetWidget::apply()
+{
+ if ( !mLayer )
+ return;
+
+ mLayer->setStaticScalarDatasetIndex( QgsMeshDatasetIndex( mScalarDatasetGroup, mScalarDatasetComboBox->currentIndex() - 1 ) );
+ mLayer->setStaticVectorDatasetIndex( QgsMeshDatasetIndex( mVectorDatasetGroup, mVectorDatasetComboBox->currentIndex() - 1 ) );
+}
+
+void QgsMeshStaticDatasetWidget::setScalarDatasetGroup( int index )
+{
+ mScalarDatasetGroup = index;
+ mDatasetScalarModel->setDatasetGroup( index );
+ mScalarName->setText( mLayer->dataProvider()->datasetGroupMetadata( index ).name() );
+}
+
+void QgsMeshStaticDatasetWidget::setVectorDatasetGroup( int index )
+{
+ mVectorDatasetGroup = index;
+ mDatasetVectorModel->setDatasetGroup( index );
+ mVectorName->setText( mLayer->dataProvider()->datasetGroupMetadata( index ).name() );
+}
+
+QgsMeshDatasetListModel::QgsMeshDatasetListModel( QObject *parent ): QAbstractListModel( parent )
+{}
+
+void QgsMeshDatasetListModel::setMeshLayer( QgsMeshLayer *layer )
+{
+ beginResetModel();
+ mLayer = layer;
+ endResetModel();
+}
+
+void QgsMeshDatasetListModel::setDatasetGroup( int group )
+{
+ beginResetModel();
+ mDatasetGroup = group;
+ endResetModel();
+}
+
+int QgsMeshDatasetListModel::rowCount( const QModelIndex &parent ) const
+{
+ Q_UNUSED( parent )
+
+ if ( mLayer && mLayer->dataProvider() )
+ return mLayer->dataProvider()->datasetCount( mDatasetGroup ) + 1;
+ else
+ return 0;
+}
+
+QVariant QgsMeshDatasetListModel::data( const QModelIndex &index, int role ) const
+{
+ if ( !index.isValid() || !mLayer || !mLayer->dataProvider() || mDatasetGroup < 0 )
+ return QVariant();
+
+ if ( role == Qt::DisplayRole )
+ {
+ if ( index.row() == 0 )
+ return tr( "none" );
+ else
+ {
+ qint64 time = mLayer->dataProvider()->temporalCapabilities()->datasetTime( QgsMeshDatasetIndex( mDatasetGroup, index.row() - 1 ) );
+ return mLayer->formatTime( time / 3600.0 / 1000.0 );
+ }
+ }
+
+ return QVariant();
+}
diff --git a/src/app/mesh/qgsmeshstaticdatasetwidget.h b/src/app/mesh/qgsmeshstaticdatasetwidget.h
new file mode 100644
index 000000000000..9cf70384c432
--- /dev/null
+++ b/src/app/mesh/qgsmeshstaticdatasetwidget.h
@@ -0,0 +1,86 @@
+/***************************************************************************
+ qgsmeshstaticdatasetwidget.h
+ -------------------------------------
+ begin : March 2020
+ copyright : (C) 2020 by Vincent Cloarec
+ email : vcloarec at gmail dot com
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSMESHSTATICDATASETWIDGET_H
+#define QGSMESHSTATICDATASETWIDGET_H
+
+#include
+
+#include "qgis_app.h"
+#include "ui_qgsmeshstaticdatasetwidgetbase.h"
+#include "qgsmeshdataset.h"
+
+class QgsMeshLayer;
+class QgsMeshDataProvider;
+
+
+/**
+ * List mdel for dataset contained in dataset group,
+ * used to display by time dataset in widget
+ */
+class APP_NO_EXPORT QgsMeshDatasetListModel: public QAbstractListModel
+{
+ public:
+ //! Constructor
+ QgsMeshDatasetListModel( QObject *parent );
+
+ //! Sets the layer
+ void setMeshLayer( QgsMeshLayer *layer );
+ //! Sets the dataset group
+ void setDatasetGroup( int group );
+
+ int rowCount( const QModelIndex &parent ) const override;
+ QVariant data( const QModelIndex &index, int role ) const override;
+
+ private:
+ QgsMeshLayer *mLayer = nullptr;
+ int mDatasetGroup = -1;
+};
+
+/**
+ * A widget for setup of the static dataset of a mesh layer.
+ */
+class APP_EXPORT QgsMeshStaticDatasetWidget : public QWidget, private Ui::QgsMeshStaticDatasetWidget
+{
+ Q_OBJECT
+ public:
+ //! Constructor
+ QgsMeshStaticDatasetWidget( QWidget *parent = nullptr );
+
+ //! Sets the layer
+ void setLayer( QgsMeshLayer *layer );
+
+ //! Synchronizes widgets state with associated mesh layer
+ void syncToLayer();
+
+ //! Applies the settings made in the widget
+ void apply();
+
+ public slots:
+ //! Sets the scalar dataset group
+ void setScalarDatasetGroup( int index );
+ //! Sets the vector dataset group
+ void setVectorDatasetGroup( int index );
+
+ private:
+ int mScalarDatasetGroup = -1;
+ int mVectorDatasetGroup = -1;
+
+ QgsMeshDatasetListModel *mDatasetScalarModel = nullptr;
+ QgsMeshDatasetListModel *mDatasetVectorModel = nullptr;
+
+ QgsMeshLayer *mLayer;
+};
+
+#endif // QGSMESHSTATICDATASETWIDGET_H
diff --git a/src/app/mesh/qgsmeshtimeformatdialog.cpp b/src/app/mesh/qgsmeshtimeformatdialog.cpp
deleted file mode 100644
index ee51f58edb40..000000000000
--- a/src/app/mesh/qgsmeshtimeformatdialog.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/***************************************************************************
- qgsmeshtimeformatdialog.cpp
- ---------------------------
- begin : March 2019
- copyright : (C) 2019 by Peter Petrik
- email : zilolv 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 "qgsmeshtimeformatdialog.h"
-#include "qgsgui.h"
-#include "qgsmeshtimesettings.h"
-#include "qgsmeshlayerutils.h"
-
-QgsMeshTimeFormatDialog::QgsMeshTimeFormatDialog( QgsMeshLayer *meshLayer, QWidget *parent, Qt::WindowFlags f )
- : QDialog( parent, f ),
- mLayer( meshLayer )
-{
- setupUi( this );
- QgsGui::enableAutoGeometryRestore( this );
-
- if ( !meshLayer )
- return;
-
- loadSettings();
-
- mReloadReferenceTimeButton->setEnabled( layerHasReferenceTime() );
-
- connect( mUseTimeComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshTimeFormatDialog::saveSettings );
- connect( mReferenceDateTimeEdit, &QDateTimeEdit::dateTimeChanged, this, &QgsMeshTimeFormatDialog::saveSettings );
- connect( mAbsoluteTimeFormatComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshTimeFormatDialog::saveSettings );
- connect( mUseTimeComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshTimeFormatDialog::saveSettings );
- connect( mRelativeTimeFormatComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshTimeFormatDialog::saveSettings );
- connect( mOffsetHoursSpinBox, qgis::overload::of( &QDoubleSpinBox::valueChanged ), this, &QgsMeshTimeFormatDialog::saveSettings );
- connect( mPlaybackIntervalSpinBox, qgis::overload::of( &QDoubleSpinBox::valueChanged ), this, &QgsMeshTimeFormatDialog::saveSettings );
- connect( mProviderTimeUnitComboBox, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &QgsMeshTimeFormatDialog::saveSettings );
-
- connect( mReloadReferenceTimeButton, &QPushButton::clicked, this, &QgsMeshTimeFormatDialog::loadProviderReferenceTime );
-
-}
-
-void QgsMeshTimeFormatDialog::loadProviderReferenceTime()
-{
- mReferenceDateTimeEdit->setDateTime( QgsMeshLayerUtils::firstReferenceTime( mLayer ) );
-}
-
-void QgsMeshTimeFormatDialog::loadSettings()
-{
- const QgsMeshTimeSettings settings = mLayer->timeSettings();
-
- enableGroups( settings.useAbsoluteTime() ) ;
- if ( settings.useAbsoluteTime() )
- {
- mUseTimeComboBox->setCurrentIndex( 0 );
- }
- else
- {
- mUseTimeComboBox->setCurrentIndex( 1 );
- }
-
- // Sets the reference time, if not valid, sets the current date + time 00:00:00
- if ( settings.absoluteTimeReferenceTime().isValid() )
- mReferenceDateTimeEdit->setDateTime( settings.absoluteTimeReferenceTime() );
- else
- mReferenceDateTimeEdit->setDateTime( QDateTime( QDate::currentDate(), QTime( 00, 00, 00 ) ) );
-
- mReferenceDateTimeEdit->setDisplayFormat( settings.absoluteTimeFormat() );
-
- int index = mAbsoluteTimeFormatComboBox->findText( settings.absoluteTimeFormat() );
- if ( index < 0 )
- {
- index = mAbsoluteTimeFormatComboBox->count();
- mAbsoluteTimeFormatComboBox->addItem( settings.absoluteTimeFormat() );
- }
- mAbsoluteTimeFormatComboBox->setCurrentIndex( index );
-
- index = mRelativeTimeFormatComboBox->findText( settings.relativeTimeFormat() );
- if ( index < 0 )
- {
- index = mRelativeTimeFormatComboBox->count();
- mRelativeTimeFormatComboBox->addItem( settings.relativeTimeFormat() );
- }
- mRelativeTimeFormatComboBox->setCurrentIndex( index );
-
- mOffsetHoursSpinBox->setValue( settings.relativeTimeOffsetHours() );
- mPlaybackIntervalSpinBox->setValue( settings.datasetPlaybackInterval() );
-
- mProviderTimeUnitComboBox->setCurrentIndex( settings.providerTimeUnit() );
-}
-
-void QgsMeshTimeFormatDialog::saveSettings()
-{
- QgsMeshTimeSettings settings;
- settings.setUseAbsoluteTime( mUseTimeComboBox->currentIndex() == 0 );
- settings.setAbsoluteTimeReferenceTime( mReferenceDateTimeEdit->dateTime() );
- settings.setAbsoluteTimeFormat( mAbsoluteTimeFormatComboBox->currentText() );
- settings.setRelativeTimeOffsetHours( mOffsetHoursSpinBox->value() );
- settings.setRelativeTimeFormat( mRelativeTimeFormatComboBox->currentText() );
- settings.setDatasetPlaybackInterval( mPlaybackIntervalSpinBox->value() );
- settings.setProviderTimeUnit( static_cast( mProviderTimeUnitComboBox->currentIndex() ) );
- enableGroups( settings.useAbsoluteTime() ) ;
- mLayer->setTimeSettings( settings );
-}
-
-void QgsMeshTimeFormatDialog::enableGroups( bool useAbsoluteTime )
-{
- mAbsoluteTimeGroupBox->setEnabled( useAbsoluteTime );
- mRelativeTimeGroupBox->setEnabled( ! useAbsoluteTime );
-}
-
-bool QgsMeshTimeFormatDialog::layerHasReferenceTime() const
-{
- return QgsMeshLayerUtils::firstReferenceTime( mLayer ).isValid();
-}
-
-QgsMeshTimeFormatDialog::~QgsMeshTimeFormatDialog() = default;
diff --git a/src/app/mesh/qgsmeshtimeformatdialog.h b/src/app/mesh/qgsmeshtimeformatdialog.h
deleted file mode 100644
index 91ec6002e823..000000000000
--- a/src/app/mesh/qgsmeshtimeformatdialog.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/***************************************************************************
- qgsmeshtimeformatdialog.h
- -------------------------
- begin : March 2019
- copyright : (C) 2019 by Peter Petrik
- email : zilolv at gmail dot com
- ***************************************************************************/
-
-/***************************************************************************
- * *
- * This program is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation; either version 2 of the License, or *
- * (at your option) any later version. *
- * *
- ***************************************************************************/
-
-#ifndef QGSMESHTIMEFORMATDIALOG_H
-#define QGSMESHTIMEFORMATDIALOG_H
-
-#include "ui_qgsmeshtimeformatdialog.h"
-#include "qgsmeshcalculator.h"
-#include "qgshelp.h"
-#include "qgis_app.h"
-
-//! A dialog to enter a mesh calculation expression
-class APP_EXPORT QgsMeshTimeFormatDialog: public QDialog, private Ui::QgsMeshTimeFormatDialog
-{
- Q_OBJECT
- public:
-
- /**
- * Constructor for raster calculator dialog
- * \param meshLayer main mesh layer, will be used for default extent and projection
- * \param parent widget
- * \param f window flags
- */
- QgsMeshTimeFormatDialog( QgsMeshLayer *meshLayer = nullptr, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr );
- ~QgsMeshTimeFormatDialog();
-
- private slots:
- void loadProviderReferenceTime();
- private:
- void loadSettings();
- void saveSettings();
- void enableGroups( bool useAbsoluteTime );
-
- bool layerHasReferenceTime() const;
-
- QgsMeshLayer *mLayer;
-};
-
-#endif // QGSMESHTIMEFORMATDIALOG_H
diff --git a/src/app/mesh/qgsrenderermeshpropertieswidget.cpp b/src/app/mesh/qgsrenderermeshpropertieswidget.cpp
index 7c5691c674c0..3c7b9d46502d 100644
--- a/src/app/mesh/qgsrenderermeshpropertieswidget.cpp
+++ b/src/app/mesh/qgsrenderermeshpropertieswidget.cpp
@@ -94,27 +94,27 @@ void QgsRendererMeshPropertiesWidget::apply()
triangularMeshSettings.setEnabled( triangularMeshRenderingIsEnabled );
// SCALAR
- QgsMeshDatasetIndex activeScalarDatasetIndex = mMeshRendererActiveDatasetWidget->activeScalarDataset();
+ int activeScalarDatasetGroupIndex = mMeshRendererActiveDatasetWidget->activeScalarDatasetGroup();
if ( !mContoursGroupBox->isChecked() )
- activeScalarDatasetIndex = QgsMeshDatasetIndex();
+ activeScalarDatasetGroupIndex = -1;
// VECTOR
- QgsMeshDatasetIndex activeVectorDatasetIndex = mMeshRendererActiveDatasetWidget->activeVectorDataset();
+ int activeVectorDatasetGroupIndex = mMeshRendererActiveDatasetWidget->activeVectorDatasetGroup();
if ( !mVectorsGroupBox->isChecked() )
- activeVectorDatasetIndex = QgsMeshDatasetIndex();
+ activeVectorDatasetGroupIndex = -1;
QgsMeshRendererSettings settings = mMeshLayer->rendererSettings();
settings.setEdgeMeshSettings( edgeMeshSettings );
settings.setNativeMeshSettings( nativeMeshSettings );
settings.setTriangularMeshSettings( triangularMeshSettings );
- settings.setActiveScalarDataset( activeScalarDatasetIndex );
- if ( activeScalarDatasetIndex.isValid() )
- settings.setScalarSettings( activeScalarDatasetIndex.group(), mMeshRendererScalarSettingsWidget->settings() );
+ settings.setActiveScalarDatasetGroup( activeScalarDatasetGroupIndex );
+ if ( activeScalarDatasetGroupIndex > -1 )
+ settings.setScalarSettings( activeScalarDatasetGroupIndex, mMeshRendererScalarSettingsWidget->settings() );
- settings.setActiveVectorDataset( activeVectorDatasetIndex );
- if ( activeVectorDatasetIndex.isValid() )
- settings.setVectorSettings( activeVectorDatasetIndex.group(), mMeshRendererVectorSettingsWidget->settings() );
+ settings.setActiveVectorDatasetGroup( activeVectorDatasetGroupIndex );
+ if ( activeVectorDatasetGroupIndex > -1 )
+ settings.setVectorSettings( activeVectorDatasetGroupIndex, mMeshRendererVectorSettingsWidget->settings() );
//set the blend mode for the layer
mMeshLayer->setBlendMode( mBlendModeComboBox->blendMode() );
@@ -123,6 +123,9 @@ void QgsRendererMeshPropertiesWidget::apply()
settings.setAveragingMethod( averagingMethod.get() );
mMeshLayer->setRendererSettings( settings );
mMeshLayer->triggerRepaint();
+
+ QgsSettings windowsSettings;
+ windowsSettings.setValue( QStringLiteral( "/Windows/RendererMeshProperties/tab" ), mStyleOptionsTab->currentIndex() );
}
void QgsRendererMeshPropertiesWidget::syncToLayer()
@@ -137,8 +140,8 @@ void QgsRendererMeshPropertiesWidget::syncToLayer()
mTriangularMeshGroup->setChecked( mMeshLayer ? mMeshLayer->rendererSettings().triangularMeshSettings().isEnabled() : false );
mEdgeMeshGroup->setChecked( mMeshLayer ? mMeshLayer->rendererSettings().edgeMeshSettings().isEnabled() : false );
- onActiveScalarGroupChanged( mMeshRendererActiveDatasetWidget->activeScalarDatasetGroup() );
- onActiveVectorGroupChanged( mMeshRendererActiveDatasetWidget->activeVectorDatasetGroup() );
+ onActiveScalarGroupChanged( mMeshLayer->rendererSettings().activeScalarDatasetGroup() );
+ onActiveVectorGroupChanged( mMeshLayer->rendererSettings().activeVectorDatasetGroup() );
bool hasFaces = ( mMeshLayer->dataProvider() &&
mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Face ) );
@@ -147,6 +150,12 @@ void QgsRendererMeshPropertiesWidget::syncToLayer()
bool hasEdges = ( mMeshLayer->dataProvider() &&
mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) );
mEdgeMeshGroupBox->setVisible( hasEdges );
+
+ QgsSettings settings;
+ if ( !settings.contains( QStringLiteral( "/Windows/RendererMeshProperties/tab" ) ) )
+ settings.setValue( QStringLiteral( "/Windows/RendererMeshProperties/tab" ), 0 );
+ else
+ mStyleOptionsTab->setCurrentIndex( settings.value( QStringLiteral( "/Windows/RendererMeshProperties/tab" ) ).toInt() );
}
void QgsRendererMeshPropertiesWidget::onActiveScalarGroupChanged( int groupIndex )
@@ -155,6 +164,7 @@ void QgsRendererMeshPropertiesWidget::onActiveScalarGroupChanged( int groupIndex
mMeshRendererScalarSettingsWidget->syncToLayer();
mContoursGroupBox->setChecked( groupIndex >= 0 );
mContoursGroupBox->setEnabled( groupIndex >= 0 );
+ emit mMeshLayer->activeScalarDatasetGroupChanged( groupIndex );
}
void QgsRendererMeshPropertiesWidget::onActiveVectorGroupChanged( int groupIndex )
@@ -165,4 +175,5 @@ void QgsRendererMeshPropertiesWidget::onActiveVectorGroupChanged( int groupIndex
mMeshRendererVectorSettingsWidget->syncToLayer();
mVectorsGroupBox->setChecked( groupIndex >= 0 );
mVectorsGroupBox->setEnabled( groupIndex >= 0 );
+ emit mMeshLayer->activeVectorDatasetGroupChanged( groupIndex );
}
diff --git a/src/app/pluginmanager/qgspluginmanager.cpp b/src/app/pluginmanager/qgspluginmanager.cpp
index d65e73467eaa..510b797caafb 100644
--- a/src/app/pluginmanager/qgspluginmanager.cpp
+++ b/src/app/pluginmanager/qgspluginmanager.cpp
@@ -31,6 +31,8 @@
#include
#include
+#include "qgsmessagelog.h"
+
#include "qgis.h"
#include "qgisapp.h"
#include "qgsapplication.h"
@@ -73,6 +75,7 @@ QgsPluginManager::QgsPluginManager( QWidget *parent, bool pluginsAreEnabled, Qt:
connect( leFilter, &QgsFilterLineEdit::textChanged, this, &QgsPluginManager::leFilter_textChanged );
connect( buttonUpgradeAll, &QPushButton::clicked, this, &QgsPluginManager::buttonUpgradeAll_clicked );
connect( buttonInstall, &QPushButton::clicked, this, &QgsPluginManager::buttonInstall_clicked );
+ connect( buttonInstallExperimental, &QPushButton::clicked, this, &QgsPluginManager::buttonInstallExperimental_clicked );
connect( buttonUninstall, &QPushButton::clicked, this, &QgsPluginManager::buttonUninstall_clicked );
connect( treeRepositories, &QTreeWidget::itemSelectionChanged, this, &QgsPluginManager::treeRepositories_itemSelectionChanged );
connect( treeRepositories, &QTreeWidget::doubleClicked, this, &QgsPluginManager::treeRepositories_doubleClicked );
@@ -138,6 +141,7 @@ QgsPluginManager::QgsPluginManager( QWidget *parent, bool pluginsAreEnabled, Qt:
// Hide widgets only suitable with Python support enabled (they will be uncovered back in setPythonUtils)
buttonUpgradeAll->hide();
buttonInstall->hide();
+ buttonInstallExperimental->hide();
buttonUninstall->hide();
frameSettings->setHidden( true );
mOptionsListWidget->item( PLUGMAN_TAB_INSTALL_FROM_ZIP )->setHidden( true );
@@ -179,6 +183,7 @@ void QgsPluginManager::setPythonUtils( QgsPythonUtils *pythonUtils )
mOptionsListWidget->item( PLUGMAN_TAB_INSTALL_FROM_ZIP )->setHidden( false );
buttonUpgradeAll->show();
buttonInstall->show();
+ buttonInstallExperimental->show();
buttonUninstall->show();
frameSettings->setHidden( false );
labelNoPython->setHidden( true );
@@ -191,25 +196,36 @@ void QgsPluginManager::setPythonUtils( QgsPythonUtils *pythonUtils )
QAction *actionSortByDownloads = new QAction( tr( "Sort by Downloads" ), vwPlugins );
QAction *actionSortByVote = new QAction( tr( "Sort by Vote" ), vwPlugins );
QAction *actionSortByStatus = new QAction( tr( "Sort by Status" ), vwPlugins );
+ QAction *actionSortByDateCreated = new QAction( tr( "Sort by Date Created" ), vwPlugins );
+ QAction *actionSortByDateUpdated = new QAction( tr( "Sort by Date Updated" ), vwPlugins );
actionSortByName->setCheckable( true );
actionSortByDownloads->setCheckable( true );
actionSortByVote->setCheckable( true );
actionSortByStatus->setCheckable( true );
+ actionSortByDateCreated->setCheckable( true );
+ actionSortByDateUpdated->setCheckable( true );
QActionGroup *group = new QActionGroup( vwPlugins );
actionSortByName->setActionGroup( group );
actionSortByDownloads->setActionGroup( group );
actionSortByVote->setActionGroup( group );
actionSortByStatus->setActionGroup( group );
+ actionSortByDateCreated->setActionGroup( group );
+ actionSortByDateUpdated->setActionGroup( group );
actionSortByName->setChecked( true );
vwPlugins->addAction( actionSortByName );
vwPlugins->addAction( actionSortByDownloads );
vwPlugins->addAction( actionSortByVote );
vwPlugins->addAction( actionSortByStatus );
+ // TODO "create_date" and "update_date" are actually both representing "update_date" from the server side. Blocked by https://github.com/qgis/QGIS-Django/issues/69
+ // vwPlugins->addAction( actionSortByDateCreated );
+ vwPlugins->addAction( actionSortByDateUpdated );
vwPlugins->setContextMenuPolicy( Qt::ActionsContextMenu );
connect( actionSortByName, &QAction::triggered, mModelProxy, &QgsPluginSortFilterProxyModel::sortPluginsByName );
connect( actionSortByDownloads, &QAction::triggered, mModelProxy, &QgsPluginSortFilterProxyModel::sortPluginsByDownloads );
connect( actionSortByVote, &QAction::triggered, mModelProxy, &QgsPluginSortFilterProxyModel::sortPluginsByVote );
connect( actionSortByStatus, &QAction::triggered, mModelProxy, &QgsPluginSortFilterProxyModel::sortPluginsByStatus );
+ connect( actionSortByDateCreated, &QAction::triggered, mModelProxy, &QgsPluginSortFilterProxyModel::sortPluginsByDateCreated );
+ connect( actionSortByDateUpdated, &QAction::triggered, mModelProxy, &QgsPluginSortFilterProxyModel::sortPluginsByDateUpdated );
// get the QgsSettings group from the installer
QString settingsGroup;
@@ -417,6 +433,8 @@ void QgsPluginManager::getCppPluginsMetadata()
version_t *pVersion = ( version_t * ) cast_to_fptr( myLib->resolve( "version" ) );
icon_t *pIcon = ( icon_t * ) cast_to_fptr( myLib->resolve( "icon" ) );
experimental_t *pExperimental = ( experimental_t * ) cast_to_fptr( myLib->resolve( "experimental" ) );
+ create_date_t *pCreateDate = ( create_date_t * ) cast_to_fptr( myLib->resolve( "create_date" ) );
+ update_date_t *pUpdateDate = ( update_date_t * ) cast_to_fptr( myLib->resolve( "update_date" ) );
// show the values (or lack of) for each function
if ( pName )
@@ -455,6 +473,26 @@ void QgsPluginManager::getCppPluginsMetadata()
{
QgsDebugMsg( "Plugin icon: " + pIcon() );
}
+ else
+ {
+ QgsDebugMsg( QStringLiteral( "Plugin icon not returned when queried" ) );
+ }
+ if ( pCreateDate )
+ {
+ QgsDebugMsg( "Plugin create date: " + pCreateDate() );
+ }
+ else
+ {
+ QgsDebugMsg( QStringLiteral( "Plugin create date not returned when queried" ) );
+ }
+ if ( pUpdateDate )
+ {
+ QgsDebugMsg( "Plugin update date: " + pUpdateDate() );
+ }
+ else
+ {
+ QgsDebugMsg( QStringLiteral( "Plugin update date not returned when queried" ) );
+ }
if ( !pName || !pDesc || !pVersion )
{
@@ -479,6 +517,8 @@ void QgsPluginManager::getCppPluginsMetadata()
metadata[QStringLiteral( "readonly" )] = QStringLiteral( "true" );
metadata[QStringLiteral( "status" )] = QStringLiteral( "orphan" );
metadata[QStringLiteral( "experimental" )] = ( pExperimental ? pExperimental() : QString() );
+ metadata[QStringLiteral( "create_date" )] = ( pCreateDate ? pCreateDate() : QString() );
+ metadata[QStringLiteral( "update_date" )] = ( pUpdateDate ? pUpdateDate() : QString() );
mPlugins.insert( baseName, metadata );
delete myLib;
@@ -525,17 +565,23 @@ void QgsPluginManager::reloadModelData()
QString pluginName = it->value( QStringLiteral( "name" ) );
QString description = it->value( QStringLiteral( "description" ) );
QString author = it->value( QStringLiteral( "author_name" ) );
+ QString createDate = it->value( QStringLiteral( "create_date" ) );
+ QString updateDate = it->value( QStringLiteral( "update_date" ) );
QString iconPath = it->value( QStringLiteral( "icon" ) );
QString status = it->value( QStringLiteral( "status" ) );
+ QString status_exp = it->value( QStringLiteral( "status_exp" ) );
QString error = it->value( QStringLiteral( "error" ) );
QStandardItem *mypDetailItem = new QStandardItem( pluginName );
mypDetailItem->setData( baseName, PLUGIN_BASE_NAME_ROLE );
mypDetailItem->setData( status, PLUGIN_STATUS_ROLE );
+ mypDetailItem->setData( status_exp, PLUGIN_STATUSEXP_ROLE );
mypDetailItem->setData( error, PLUGIN_ERROR_ROLE );
mypDetailItem->setData( description, PLUGIN_DESCRIPTION_ROLE );
mypDetailItem->setData( author, PLUGIN_AUTHOR_ROLE );
+ mypDetailItem->setData( createDate, PLUGIN_CREATE_DATE );
+ mypDetailItem->setData( updateDate, PLUGIN_UPDATE_DATE );
mypDetailItem->setData( it->value( QStringLiteral( "tags" ) ), PLUGIN_TAGS_ROLE );
mypDetailItem->setData( it->value( QStringLiteral( "downloads" ) ).rightJustified( 10, '0' ), PLUGIN_DOWNLOADS_ROLE );
mypDetailItem->setData( it->value( QStringLiteral( "average_vote" ) ), PLUGIN_VOTE_ROLE );
@@ -787,21 +833,21 @@ void QgsPluginManager::showPluginDetails( QStandardItem *item )
"
" ).arg( errorMsg );
}
- if ( metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "upgradeable" ) )
+ if ( metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "upgradeable" ) || metadata->value( QStringLiteral( "status_exp" ) ) == QLatin1String( "upgradeable" ) )
{
html += QString( "" ).arg( tr( "There is a new version available" ) );
}
- if ( metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "new" ) )
+ if ( metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "new" ) || metadata->value( QStringLiteral( "status_exp" ) ) == QLatin1String( "new" ) )
{
html += QString( "" ).arg( tr( "This is a new plugin" ) );
}
- if ( metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "newer" ) )
+ if ( metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "newer" ) && metadata->value( QStringLiteral( "status_exp" ) ) == QLatin1String( "newer" ) )
{
html += QString( ""
" %1 "
@@ -814,7 +860,7 @@ void QgsPluginManager::showPluginDetails( QStandardItem *item )
" "
" %1 "
" "
- "
" ).arg( tr( "This plugin is experimental" ) );
+ "" ).arg( tr( "This plugin has an experimental version available" ) );
}
if ( metadata->value( QStringLiteral( "deprecated" ) ) == QLatin1String( "true" ) )
@@ -968,19 +1014,54 @@ void QgsPluginManager::showPluginDetails( QStandardItem *item )
QUrl::fromLocalFile( localDir ).toString(),
ver );
}
- if ( ! metadata->value( QStringLiteral( "version_available" ) ).isEmpty() )
+
+ // if we allow experimental, we show both stable and experimental versions
+ if ( ! metadata->value( QStringLiteral( "version_available_stable" ) ).isEmpty() )
+ {
+ QString downloadUrl = metadata->value( QStringLiteral( "download_url_stable" ) );
+ if ( downloadUrl.contains( QStringLiteral( "plugins.qgis.org" ) ) )
+ {
+ // For the main repo, open the plugin version page instead of the download link. For other repositories the download link is the only known endpoint.
+ downloadUrl = downloadUrl.replace( QStringLiteral( "download/" ), QString() );
+ }
+
+ QString dateUpdatedStr;
+ if ( ! metadata->value( QStringLiteral( "update_date" ) ).isEmpty() )
+ {
+ const QDateTime dateUpdated = QDateTime::fromString( metadata->value( QStringLiteral( "update_date_stable" ) ).trimmed(), Qt::ISODate );
+ if ( dateUpdated.isValid() )
+ dateUpdatedStr += QStringLiteral( "%1 %2" ).arg( tr( "updated at" ), dateUpdated.toString() );
+ }
+
+ html += QStringLiteral( "%1 %3 %4 "
+ ).arg( tr( "Available version (stable)" ),
+ downloadUrl,
+ metadata->value( QStringLiteral( "version_available_stable" ) ),
+ dateUpdatedStr );
+ }
+
+ if ( ! metadata->value( QStringLiteral( "version_available_experimental" ) ).isEmpty() )
{
- QString downloadUrl = metadata->value( QStringLiteral( "download_url" ) );
+ QString downloadUrl = metadata->value( QStringLiteral( "download_url_experimental" ) );
if ( downloadUrl.contains( QStringLiteral( "plugins.qgis.org" ) ) )
{
// For the main repo, open the plugin version page instead of the download link. For other repositories the download link is the only known endpoint.
downloadUrl = downloadUrl.replace( QStringLiteral( "download/" ), QString() );
}
- html += QStringLiteral( "%1 %3 "
- ).arg( tr( "Available version" ),
+ QString dateUpdatedStr;
+ if ( !metadata->value( QStringLiteral( "update_date_experimental" ) ).isEmpty() )
+ {
+ const QDateTime dateUpdated = QDateTime::fromString( metadata->value( QStringLiteral( "update_date_experimental" ) ).trimmed(), Qt::ISODate );
+ if ( dateUpdated.isValid() )
+ dateUpdatedStr += QStringLiteral( "%1 %2" ).arg( tr( "updated at" ), dateUpdated.toString() );
+ }
+
+ html += QStringLiteral( "%1 %3 %4 "
+ ).arg( tr( "Available version (experimental)" ),
downloadUrl,
- metadata->value( QStringLiteral( "version_available" ) ) );
+ metadata->value( QStringLiteral( "version_available_experimental" ) ),
+ dateUpdatedStr );
}
if ( ! metadata->value( QStringLiteral( "changelog" ) ).isEmpty() )
@@ -1024,10 +1105,44 @@ void QgsPluginManager::showPluginDetails( QStandardItem *item )
buttonInstall->setText( tr( "Reinstall Plugin" ) );
}
+ // Set buttonInstall text (and sometimes focus)
+ buttonInstallExperimental->setDefault( false );
+ if ( metadata->value( QStringLiteral( "status_exp" ) ) == QLatin1String( "upgradeable" ) )
+ {
+ buttonInstallExperimental->setText( tr( "Upgrade Experimental Plugin" ) );
+ }
+ else if ( metadata->value( QStringLiteral( "status_exp" ) ) == QLatin1String( "newer" ) )
+ {
+ buttonInstallExperimental->setText( tr( "Downgrade Experimental Plugin" ) );
+ }
+ else if ( metadata->value( QStringLiteral( "status_exp" ) ) == QLatin1String( "not installed" ) || metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "new" ) )
+ {
+ buttonInstallExperimental->setText( tr( "Install Experimental Plugin" ) );
+ }
+ else
+ {
+ // Default (will be grayed out if not available for reinstallation)
+ buttonInstallExperimental->setText( tr( "Reinstall Experimental Plugin" ) );
+ }
+
+ // DEBUG TODO REMOVE
+ // buttonInstall->setText( buttonInstall->text() + QStringLiteral(" | ") + metadata->value( QStringLiteral( "status" ) ) );
+ // buttonInstallExperimental->setText( buttonInstallExperimental->text() + QStringLiteral(" | ") + metadata->value( QStringLiteral( "status_exp" ) ) );
+
// Enable/disable buttons
- buttonInstall->setEnabled( metadata->value( QStringLiteral( "pythonic" ) ).toUpper() == QLatin1String( "TRUE" ) && metadata->value( QStringLiteral( "status" ) ) != QLatin1String( "orphan" ) );
- buttonUninstall->setEnabled( metadata->value( QStringLiteral( "pythonic" ) ).toUpper() == QLatin1String( "TRUE" ) && metadata->value( QStringLiteral( "readonly" ) ) != QLatin1String( "true" ) && metadata->value( QStringLiteral( "status" ) ) != QLatin1String( "not installed" ) && metadata->value( QStringLiteral( "status" ) ) != QLatin1String( "new" ) );
- buttonUninstall->setHidden( metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "not installed" ) || metadata->value( QStringLiteral( "status" ) ) == QLatin1String( "new" ) );
+
+ bool installEnabled = metadata->value( QStringLiteral( "pythonic" ) ).toUpper() == QLatin1String( "TRUE" ) && metadata->value( QStringLiteral( "status" ) ) != QLatin1String( "orphan" ) && metadata->value( QStringLiteral( "status" ) ) != QLatin1String( "none available" );
+ bool installExpEnabled = metadata->value( QStringLiteral( "pythonic" ) ).toUpper() == QLatin1String( "TRUE" ) && metadata->value( QStringLiteral( "status_exp" ) ) != QLatin1String( "orphan" ) && metadata->value( QStringLiteral( "status_exp" ) ) != QLatin1String( "none available" );
+ buttonInstall->setEnabled( installEnabled );
+ buttonInstall->setVisible( installEnabled || !installExpEnabled );
+ buttonInstallExperimental->setEnabled( installExpEnabled );
+ buttonInstallExperimental->setVisible( installExpEnabled || !installEnabled );
+
+ buttonUninstall->setEnabled( metadata->value( QStringLiteral( "pythonic" ) ).toUpper() == QLatin1String( "TRUE" ) && metadata->value( QStringLiteral( "readonly" ) ) != QLatin1String( "true" ) && ! metadata->value( QStringLiteral( "version_installed" ) ).isEmpty() );
+
+ buttonUninstall->setHidden(
+ metadata->value( QStringLiteral( "version_installed" ) ).isEmpty()
+ );
// Store the id of the currently displayed plugin
mCurrentlyDisplayedPlugin = metadata->value( QStringLiteral( "id" ) );
@@ -1199,7 +1314,7 @@ void QgsPluginManager::setCurrentTab( int idx )
{
case PLUGMAN_TAB_ALL:
// all (statuses ends with Z are for spacers to always sort properly)
- acceptedStatuses << QStringLiteral( "installed" ) << QStringLiteral( "not installed" ) << QStringLiteral( "new" ) << QStringLiteral( "orphan" ) << QStringLiteral( "newer" ) << QStringLiteral( "upgradeable" ) << QStringLiteral( "not installedZ" ) << QStringLiteral( "installedZ" ) << QStringLiteral( "upgradeableZ" ) << QStringLiteral( "orphanZ" ) << QStringLiteral( "newerZZ" ) << QString();
+ acceptedStatuses << QStringLiteral( "installed" ) << QStringLiteral( "not installed" ) << QStringLiteral( "new" ) << QStringLiteral( "orphan" ) << QStringLiteral( "none available" ) << QStringLiteral( "newer" ) << QStringLiteral( "upgradeable" ) << QStringLiteral( "not installedZ" ) << QStringLiteral( "installedZ" ) << QStringLiteral( "upgradeableZ" ) << QStringLiteral( "orphanZ" ) << QStringLiteral( "newerZZ" ) << QString();
tabTitle = QStringLiteral( "all_plugins" );
break;
case PLUGMAN_TAB_INSTALLED:
@@ -1372,7 +1487,13 @@ void QgsPluginManager::buttonUpgradeAll_clicked()
void QgsPluginManager::buttonInstall_clicked()
{
- QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.instance().installPlugin('%1')" ).arg( mCurrentlyDisplayedPlugin ) );
+ QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.instance().installPlugin('%1', stable=True)" ).arg( mCurrentlyDisplayedPlugin ) );
+}
+
+
+void QgsPluginManager::buttonInstallExperimental_clicked()
+{
+ QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.instance().installPlugin('%1', stable=False)" ).arg( mCurrentlyDisplayedPlugin ) );
}
diff --git a/src/app/pluginmanager/qgspluginmanager.h b/src/app/pluginmanager/qgspluginmanager.h
index 24868275a930..d6d7f4ec3781 100644
--- a/src/app/pluginmanager/qgspluginmanager.h
+++ b/src/app/pluginmanager/qgspluginmanager.h
@@ -127,6 +127,9 @@ class QgsPluginManager : public QgsOptionsDialogBase, private Ui::QgsPluginManag
//! Install selected plugin
void buttonInstall_clicked();
+ //! Install selected plugin
+ void buttonInstallExperimental_clicked();
+
//! Uninstall selected plugin
void buttonUninstall_clicked();
diff --git a/src/app/pluginmanager/qgspluginsortfilterproxymodel.cpp b/src/app/pluginmanager/qgspluginsortfilterproxymodel.cpp
index 491f5d6617a0..0a4f48bad2fe 100644
--- a/src/app/pluginmanager/qgspluginsortfilterproxymodel.cpp
+++ b/src/app/pluginmanager/qgspluginsortfilterproxymodel.cpp
@@ -66,10 +66,11 @@ bool QgsPluginSortFilterProxyModel::filterByStatus( QModelIndex &index ) const
}
QString status = sourceModel()->data( index, PLUGIN_STATUS_ROLE ).toString();
+ QString statusexp = sourceModel()->data( index, PLUGIN_STATUSEXP_ROLE ).toString();
if ( status.endsWith( 'Z' ) ) status.chop( 1 );
if ( ! mAcceptedStatuses.isEmpty()
&& ! mAcceptedStatuses.contains( QStringLiteral( "invalid" ) )
- && ! mAcceptedStatuses.contains( status ) )
+ && !( mAcceptedStatuses.contains( status ) || mAcceptedStatuses.contains( statusexp ) ) )
{
// Don't accept if the status doesn't match
return false;
@@ -154,6 +155,22 @@ void QgsPluginSortFilterProxyModel::sortPluginsByStatus()
+void QgsPluginSortFilterProxyModel::sortPluginsByDateCreated()
+{
+ setAcceptedSpacers();
+ sort( 0, Qt::DescendingOrder );
+ setSortRole( PLUGIN_CREATE_DATE );
+}
+
+
+void QgsPluginSortFilterProxyModel::sortPluginsByDateUpdated()
+{
+ setAcceptedSpacers();
+ sort( 0, Qt::DescendingOrder );
+ setSortRole( PLUGIN_UPDATE_DATE );
+}
+
+
bool QgsPluginSortFilterProxyModel::lessThan( const QModelIndex &source_left, const QModelIndex &source_right ) const
{
// Always move deprecated plugins to bottom, regardless of the sort order.
diff --git a/src/app/pluginmanager/qgspluginsortfilterproxymodel.h b/src/app/pluginmanager/qgspluginsortfilterproxymodel.h
index cb25c4d35d4a..791b25b2c047 100644
--- a/src/app/pluginmanager/qgspluginsortfilterproxymodel.h
+++ b/src/app/pluginmanager/qgspluginsortfilterproxymodel.h
@@ -30,7 +30,10 @@ const int PLUGIN_STATUS_ROLE = Qt::UserRole + 6; // for filtering and sort
const int PLUGIN_DOWNLOADS_ROLE = Qt::UserRole + 7; // for sorting
const int PLUGIN_VOTE_ROLE = Qt::UserRole + 8; // for sorting
const int PLUGIN_ISDEPRECATED_ROLE = Qt::UserRole + 9; // for styling
-const int SPACER_ROLE = Qt::UserRole + 20; // for sorting
+const int PLUGIN_STATUSEXP_ROLE = Qt::UserRole + 10; // for filtering and sorting
+const int PLUGIN_CREATE_DATE = Qt::UserRole + 11; // for sorting
+const int PLUGIN_UPDATE_DATE = Qt::UserRole + 12; // for sorting
+const int SPACER_ROLE = Qt::UserRole + 20; // for sorting
@@ -58,6 +61,8 @@ class QgsPluginSortFilterProxyModel : public QSortFilterProxyModel
void sortPluginsByDownloads();
void sortPluginsByVote();
void sortPluginsByStatus();
+ void sortPluginsByDateCreated();
+ void sortPluginsByDateUpdated();
protected:
//! Filter by status: this method is used in both filterAcceptsRow and countWithCurrentStatus.
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 7adc82bce26c..50396102d7ad 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -78,6 +78,7 @@
#include "qgsrelationmanager.h"
#include "qgsapplication.h"
#include "qgslayerstylingwidget.h"
+#include "qgsdevtoolspanelwidget.h"
#include "qgstaskmanager.h"
#include "qgsweakrelation.h"
#include "qgsziputils.h"
@@ -88,6 +89,7 @@
#include "qgssourceselectprovider.h"
#include "qgsprovidermetadata.h"
#include "qgsfixattributedialog.h"
+#include "qgsprojecttimesettings.h"
#include "qgsanalysis.h"
#include "qgsgeometrycheckregistry.h"
@@ -181,6 +183,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgscoordinateutils.h"
#include "qgscredentialdialog.h"
#include "qgscustomdrophandler.h"
+#include "qgscustomprojectopenhandler.h"
#include "qgscustomization.h"
#include "qgscustomlayerorderwidget.h"
#include "qgscustomprojectiondialog.h"
@@ -258,6 +261,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsmapoverviewcanvas.h"
#include "qgsmapsettings.h"
#include "qgsmaptip.h"
+#include "qgsmbtilesreader.h"
#include "qgsmenuheader.h"
#include "qgsmergeattributesdialog.h"
#include "qgsmessageviewer.h"
@@ -335,6 +339,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsvectorlayer.h"
#include "qgsvectorlayerproperties.h"
#include "qgsvectorlayerdigitizingproperties.h"
+#include "qgsvectortilelayer.h"
#include "qgsmapthemes.h"
#include "qgsmessagelogviewer.h"
#include "qgsdataitem.h"
@@ -365,9 +370,10 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsbearingnumericformat.h"
#include "qgsprojectdisplaysettings.h"
#include "qgstemporalcontrollerdockwidget.h"
-
+#include "qgsnetworklogger.h"
#include "qgsuserprofilemanager.h"
#include "qgsuserprofile.h"
+#include "qgsnetworkloggerwidgetfactory.h"
#include "browser/qgsinbuiltdataitemproviders.h"
@@ -831,6 +837,11 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
connect( mUserProfileManager, &QgsUserProfileManager::profilesChanged, this, &QgisApp::refreshProfileMenu );
endProfile();
+ // start the network logger early, we want all requests logged!
+ startProfile( QStringLiteral( "Network logger" ) );
+ mNetworkLogger = new QgsNetworkLogger( QgsNetworkAccessManager::instance(), this );
+ endProfile();
+
// load GUI: actions, menus, toolbars
profiler->beginGroup( QStringLiteral( "qgisapp" ) );
profiler->beginGroup( QStringLiteral( "startup" ) );
@@ -1121,6 +1132,23 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
mMapStylingDock->hide();
endProfile();
+ startProfile( QStringLiteral( "Dev Tools dock" ) );
+ mDevToolsDock = new QgsDockWidget( this );
+ mDevToolsDock->setWindowTitle( tr( "Debugging/Development Tools" ) );
+ mDevToolsDock->setObjectName( QStringLiteral( "DevTools" ) );
+ QShortcut *showDevToolsDock = new QShortcut( QKeySequence( tr( "F12" ) ), this );
+ connect( showDevToolsDock, &QShortcut::activated, mDevToolsDock, &QgsDockWidget::toggleUserVisible );
+ showDevToolsDock->setObjectName( QStringLiteral( "ShowDevToolsPanel" ) );
+ showDevToolsDock->setWhatsThis( tr( "Show Debugging/Development Tools" ) );
+
+ mDevToolsWidget = new QgsDevToolsPanelWidget( mDevToolFactories );
+ mDevToolsDock->setWidget( mDevToolsWidget );
+// connect( mDevToolsDock, &QDockWidget::visibilityChanged, mActionStyleDock, &QAction::setChecked );
+
+ addDockWidget( Qt::RightDockWidgetArea, mDevToolsDock );
+ mDevToolsDock->hide();
+ endProfile();
+
startProfile( QStringLiteral( "Snapping dialog" ) );
mSnappingDialog = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, this );
connect( mSnappingDialog, &QgsSnappingWidget::snappingConfigChanged, QgsProject::instance(), [ = ] { QgsProject::instance()->setSnappingConfig( mSnappingDialog->config() ); } );
@@ -1580,6 +1608,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
mBearingNumericFormat.reset( QgsLocalDefaultSettings::bearingFormat() );
+ mNetworkLoggerWidgetFactory.reset( qgis::make_unique< QgsNetworkLoggerWidgetFactory >( mNetworkLogger ) );
+
// update windows
qApp->processEvents();
@@ -1672,6 +1702,8 @@ QgisApp::~QgisApp()
// shouldn't be needed, but from this stage on, we don't want/need ANY map canvas refreshes to take place
mFreezeCount = 1000000;
+ mNetworkLoggerWidgetFactory.reset();
+
delete mInternalClipboard;
delete mQgisInterface;
delete mStyleSheetBuilder;
@@ -1934,6 +1966,17 @@ void QgisApp::unregisterCustomDropHandler( QgsCustomDropHandler *handler )
}
}
+void QgisApp::registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
+{
+ if ( !mCustomProjectOpenHandlers.contains( handler ) )
+ mCustomProjectOpenHandlers << handler;
+}
+
+void QgisApp::unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
+{
+ mCustomProjectOpenHandlers.removeOne( handler );
+}
+
QVector > QgisApp::customDropHandlers() const
{
return mCustomDropHandlers;
@@ -1984,6 +2027,11 @@ void QgisApp::handleDropUriList( const QgsMimeDataUtils::UriList &lst )
QgsMeshLayer *layer = new QgsMeshLayer( uri, u.name, u.providerKey );
addMapLayer( layer );
}
+ else if ( u.layerType == QLatin1String( "vector-tile" ) )
+ {
+ QgsVectorTileLayer *layer = new QgsVectorTileLayer( uri, u.name );
+ addMapLayer( layer );
+ }
else if ( u.layerType == QLatin1String( "plugin" ) )
{
addPluginLayer( uri, u.name, u.providerKey );
@@ -2549,6 +2597,7 @@ void QgisApp::createActions()
connect( mActionSelectFreehand, &QAction::triggered, this, &QgisApp::selectByFreehand );
connect( mActionSelectRadius, &QAction::triggered, this, &QgisApp::selectByRadius );
connect( mActionDeselectAll, &QAction::triggered, this, &QgisApp::deselectAll );
+ connect( mActionDeselectActiveLayer, &QAction::triggered, this, &QgisApp::deselectActiveLayer );
connect( mActionSelectAll, &QAction::triggered, this, &QgisApp::selectAll );
connect( mActionReselect, &QAction::triggered, this, [ = ]
{
@@ -2649,6 +2698,8 @@ void QgisApp::createActions()
connect( mActionHideAllLayers, &QAction::triggered, this, &QgisApp::hideAllLayers );
connect( mActionShowSelectedLayers, &QAction::triggered, this, &QgisApp::showSelectedLayers );
connect( mActionHideSelectedLayers, &QAction::triggered, this, &QgisApp::hideSelectedLayers );
+ connect( mActionToggleSelectedLayers, &QAction::triggered, this, &QgisApp::toggleSelectedLayers );
+ connect( mActionToggleSelectedLayersIndependently, &QAction::triggered, this, &QgisApp::toggleSelectedLayersIndependently );
connect( mActionHideDeselectedLayers, &QAction::triggered, this, &QgisApp::hideDeselectedLayers );
// Plugin Menu Items
@@ -2806,6 +2857,7 @@ void QgisApp::createActionGroups()
mMapToolGroup->addAction( mActionSelectFreehand );
mMapToolGroup->addAction( mActionSelectRadius );
mMapToolGroup->addAction( mActionDeselectAll );
+ mMapToolGroup->addAction( mActionDeselectActiveLayer );
mMapToolGroup->addAction( mActionSelectAll );
mMapToolGroup->addAction( mActionReselect );
mMapToolGroup->addAction( mActionInvertSelection );
@@ -3108,66 +3160,88 @@ void QgisApp::createToolBars()
mToolbarMenu->addActions( toolbarMenuActions );
- // selection tool button
-
+ // advanced selection tool button
QToolButton *bt = new QToolButton( mAttributesToolBar );
bt->setPopupMode( QToolButton::MenuButtonPopup );
- QList selectionActions;
- selectionActions << mActionSelectByForm << mActionSelectByExpression << mActionSelectAll
- << mActionInvertSelection;
- bt->addActions( selectionActions );
+ bt->addAction( mActionSelectByForm );
+ bt->addAction( mActionSelectByExpression );
+ bt->addAction( mActionSelectAll );
+ bt->addAction( mActionInvertSelection );
- QAction *defSelectionAction = mActionSelectByForm;
+ QAction *defAdvancedSelectionAction = mActionSelectByForm;
switch ( settings.value( QStringLiteral( "UI/selectionTool" ), 0 ).toInt() )
{
case 0:
- defSelectionAction = mActionSelectByForm;
+ defAdvancedSelectionAction = mActionSelectByForm;
break;
case 1:
- defSelectionAction = mActionSelectByExpression;
+ defAdvancedSelectionAction = mActionSelectByExpression;
break;
case 2:
- defSelectionAction = mActionSelectAll;
+ defAdvancedSelectionAction = mActionSelectAll;
break;
case 3:
- defSelectionAction = mActionInvertSelection;
+ defAdvancedSelectionAction = mActionInvertSelection;
break;
}
- bt->setDefaultAction( defSelectionAction );
- QAction *selectionAction = mAttributesToolBar->insertWidget( mActionDeselectAll, bt );
- selectionAction->setObjectName( QStringLiteral( "ActionSelection" ) );
+ bt->setDefaultAction( defAdvancedSelectionAction );
+ QAction *advancedSelectionAction = mAttributesToolBar->insertWidget( mActionOpenTable, bt );
+ advancedSelectionAction->setObjectName( QStringLiteral( "ActionSelection" ) );
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
- // select tool button
+ // mouse select tool button
bt = new QToolButton( mAttributesToolBar );
bt->setPopupMode( QToolButton::MenuButtonPopup );
- QList selectActions;
- selectActions << mActionSelectFeatures << mActionSelectPolygon
- << mActionSelectFreehand << mActionSelectRadius;
- bt->addActions( selectActions );
+ bt->addAction( mActionSelectFeatures );
+ bt->addAction( mActionSelectPolygon );
+ bt->addAction( mActionSelectFreehand );
+ bt->addAction( mActionSelectRadius );
- QAction *defSelectAction = mActionSelectFeatures;
+ QAction *defMouseSelectAction = mActionSelectFeatures;
switch ( settings.value( QStringLiteral( "UI/selectTool" ), 1 ).toInt() )
{
case 1:
- defSelectAction = mActionSelectFeatures;
+ defMouseSelectAction = mActionSelectFeatures;
break;
case 2:
- defSelectAction = mActionSelectRadius;
+ defMouseSelectAction = mActionSelectRadius;
break;
case 3:
- defSelectAction = mActionSelectPolygon;
+ defMouseSelectAction = mActionSelectPolygon;
break;
case 4:
- defSelectAction = mActionSelectFreehand;
+ defMouseSelectAction = mActionSelectFreehand;
break;
}
- bt->setDefaultAction( defSelectAction );
- QAction *selectAction = mAttributesToolBar->insertWidget( selectionAction, bt );
- selectAction->setObjectName( QStringLiteral( "ActionSelect" ) );
+ bt->setDefaultAction( defMouseSelectAction );
+ QAction *mouseSelectionAction = mAttributesToolBar->insertWidget( advancedSelectionAction, bt );
+ mouseSelectionAction->setObjectName( QStringLiteral( "ActionSelect" ) );
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
+
+ // deselection tool button
+ bt = new QToolButton( mAttributesToolBar );
+ bt->setPopupMode( QToolButton::MenuButtonPopup );
+ bt->addAction( mActionDeselectAll );
+ bt->addAction( mActionDeselectActiveLayer );
+
+ QAction *defDeselectionAction = mActionDeselectAll;
+ switch ( settings.value( QStringLiteral( "UI/deselectionTool" ), 0 ).toInt() )
+ {
+ case 0:
+ defDeselectionAction = mActionDeselectAll;
+ break;
+ case 1:
+ defDeselectionAction = mActionDeselectActiveLayer;
+ break;
+ }
+ bt->setDefaultAction( defDeselectionAction );
+ QAction *deselectionAction = mAttributesToolBar->insertWidget( mActionOpenTable, bt );
+ deselectionAction->setObjectName( QStringLiteral( "ActionDeselection" ) );
+ connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
+
+
// feature action tool button
bt = new QToolButton( mAttributesToolBar );
@@ -3178,7 +3252,7 @@ void QgisApp::createToolBars()
connect( mFeatureActionMenu, &QMenu::triggered, this, &QgisApp::doFeatureAction );
connect( mFeatureActionMenu, &QMenu::aboutToShow, this, &QgisApp::refreshFeatureActions );
bt->setMenu( mFeatureActionMenu );
- QAction *featureActionAction = mAttributesToolBar->insertWidget( selectAction, bt );
+ QAction *featureActionAction = mAttributesToolBar->insertWidget( mouseSelectionAction, bt );
featureActionAction->setObjectName( QStringLiteral( "ActionFeatureAction" ) );
// measure tool button
@@ -3879,6 +3953,7 @@ void QgisApp::setTheme( const QString &themeName )
mActionSelectFreehand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectFreehand.svg" ) ) );
mActionSelectRadius->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectRadius.svg" ) ) );
mActionDeselectAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeselectAll.svg" ) ) );
+ mActionDeselectActiveLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeselectActiveLayer.svg" ) ) );
mActionSelectAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectAll.svg" ) ) );
mActionInvertSelection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInvertSelection.svg" ) ) );
mActionSelectByExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionSelect.svg" ) ) );
@@ -4459,7 +4534,7 @@ void QgisApp::freezeCanvases( bool frozen )
QgsMessageBar *QgisApp::messageBar()
{
- Q_ASSERT( mInfoBar );
+ // Q_ASSERT( mInfoBar );
return mInfoBar;
}
@@ -4822,7 +4897,7 @@ void QgisApp::updateRecentProjectPaths()
}
// add this file to the recently opened/saved projects list
-void QgisApp::saveRecentProjectPath( bool savePreviewImage )
+void QgisApp::saveRecentProjectPath( bool savePreviewImage, const QIcon &iconOverlay )
{
// first, re-read the recent project paths. This prevents loss of recent
// projects when multiple QGIS sessions are open
@@ -4858,7 +4933,7 @@ void QgisApp::saveRecentProjectPath( bool savePreviewImage )
projectData.previewImagePath = QStringLiteral( "%1/%2.png" ).arg( previewDir, fileName );
QDir().mkdir( previewDir );
- createPreviewImage( projectData.previewImagePath );
+ createPreviewImage( projectData.previewImagePath, iconOverlay );
}
else
{
@@ -5417,6 +5492,15 @@ QgsMeshLayer *QgisApp::addMeshLayerPrivate( const QString &url, const QString &b
return nullptr;
}
+ // Manage default reference time, if not reference time is present by default in the layer -> assign one
+ if ( ! layer->temporalProperties()->referenceTime().isValid() )
+ {
+ QDateTime referenceTime = QgsProject::instance()->timeSettings()->temporalRange().begin();
+ if ( !referenceTime.isValid() ) // If project reference time is invalid, use current date
+ referenceTime = QDateTime( QDate::currentDate(), QTime( 0, 0, 0, Qt::UTC ) );
+ layer->temporalProperties()->setReferenceTime( referenceTime, layer->dataProvider()->temporalCapabilities() );
+ }
+
QgsProject::instance()->addMapLayer( layer.get() );
askUserForDatumTransform( layer->crs(), QgsProject::instance()->crs(), layer.get() );
@@ -5983,20 +6067,31 @@ void QgisApp::fileExit()
if ( QgsApplication::taskManager()->countActiveTasks() > 0 )
{
QStringList tasks;
- const auto constActiveTasks = QgsApplication::taskManager()->activeTasks();
- for ( QgsTask *task : constActiveTasks )
+ const QList< QgsTask * > activeTasks = QgsApplication::taskManager()->activeTasks();
+ for ( QgsTask *task : activeTasks )
{
+ if ( task->flags() & QgsTask::CancelWithoutPrompt )
+ continue;
+
tasks << tr( " • %1" ).arg( task->description() );
}
- // active tasks
- if ( QMessageBox::question( this, tr( "Active Tasks" ),
- tr( "The following tasks are currently running in the background:\n\n%1\n\nDo you want to try canceling these active tasks?" ).arg( tasks.join( QStringLiteral( "\n" ) ) ),
- QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
+ // prompt if any tasks which require user confirmation remain, otherwise just cancel them directly and continue with shutdown.
+ if ( tasks.empty() )
{
+ // all tasks can be silently terminated without warning
QgsApplication::taskManager()->cancelAll();
}
- return;
+ else
+ {
+ if ( QMessageBox::question( this, tr( "Active Tasks" ),
+ tr( "The following tasks are currently running in the background:\n\n%1\n\nDo you want to try canceling these active tasks?" ).arg( tasks.join( QStringLiteral( "\n" ) ) ),
+ QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
+ {
+ QgsApplication::taskManager()->cancelAll();
+ }
+ return;
+ }
}
QgsCanvasRefreshBlocker refreshBlocker;
@@ -6509,18 +6604,40 @@ void QgisApp::fileOpen()
// Retrieve last used project dir from persistent settings
QgsSettings settings;
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();
+
+
+ QStringList fileFilters;
+ QStringList extensions;
+ fileFilters << tr( "QGIS files" ) + QStringLiteral( " (*.qgs *.qgz *.QGS *.QGZ)" );
+ extensions << QStringLiteral( "qgs" ) << QStringLiteral( "qgz" );
+ for ( QgsCustomProjectOpenHandler *handler : qgis::as_const( mCustomProjectOpenHandlers ) )
+ {
+ if ( handler )
+ {
+ const QStringList filters = handler->filters();
+ fileFilters.append( filters );
+ for ( const QString &filter : filters )
+ extensions.append( QgsFileUtils::extensionsFromFilter( filter ) );
+ }
+ }
+
+ // generate master "all projects" extension list
+ QString allEntry = tr( "All Project Files" ) + QStringLiteral( " (" );
+ for ( const QString &extension : extensions )
+ allEntry += QStringLiteral( "*.%1 *.%2 " ).arg( extension.toLower(), extension.toUpper() );
+ allEntry.chop( 1 ); // remove trailing ' '
+ allEntry += ')';
+ fileFilters.insert( 0, allEntry );
+
QString fullPath = QFileDialog::getOpenFileName( this,
- tr( "Choose a QGIS Project File to Open" ),
+ tr( "Open Project" ),
lastUsedDir,
- tr( "QGIS files" ) + " (*.qgs *.qgz *.QGS)" );
+ fileFilters.join( QStringLiteral( ";;" ) ) );
if ( fullPath.isNull() )
{
return;
}
- // Fix by Tim - getting the dirPath from the dialog
- // directly truncates the last node in the dir path.
- // This is a workaround for that
QFileInfo myFI( fullPath );
QString myPath = myFI.path();
// Persist last used project dir
@@ -6575,7 +6692,22 @@ bool QgisApp::addProject( const QString &projectFile )
bool autoSetupOnFirstLayer = mLayerTreeCanvasBridge->autoSetupOnFirstLayer();
mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( false );
- if ( !QgsProject::instance()->read( projectFile ) && !QgsZipUtils::isZipFile( projectFile ) )
+ // give custom handlers a chance first
+ bool usedCustomHandler = false;
+ bool customHandlerWantsThumbnail = false;
+ QIcon customHandlerIcon;
+ for ( QgsCustomProjectOpenHandler *handler : qgis::as_const( mCustomProjectOpenHandlers ) )
+ {
+ if ( handler && handler->handleProjectOpen( projectFile ) )
+ {
+ usedCustomHandler = true;
+ customHandlerWantsThumbnail = handler->createDocumentThumbnailAfterOpen();
+ customHandlerIcon = handler->icon();
+ break;
+ }
+ }
+
+ if ( !usedCustomHandler && !QgsProject::instance()->read( projectFile ) && !QgsZipUtils::isZipFile( projectFile ) )
{
QString backupFile = projectFile + "~";
QString loadBackupPrompt;
@@ -6668,7 +6800,20 @@ bool QgisApp::addProject( const QString &projectFile )
// specific plug-in state
// add this to the list of recently used project files
- saveRecentProjectPath( false );
+ // if a custom handler was used, then we generate a thumbnail
+ if ( !usedCustomHandler || !customHandlerWantsThumbnail )
+ saveRecentProjectPath( false );
+ else if ( !QgsProject::instance()->fileName().isEmpty() )
+ {
+ // we have to delay the thumbnail creation until after the canvas has refreshed for the first time
+ QMetaObject::Connection *connection = new QMetaObject::Connection();
+ *connection = connect( mMapCanvas, &QgsMapCanvas::mapCanvasRefreshed, [ = ]()
+ {
+ QObject::disconnect( *connection );
+ delete connection;
+ saveRecentProjectPath( true, customHandlerIcon );
+ } );
+ }
QApplication::restoreOverrideCursor();
@@ -7058,12 +7203,32 @@ bool QgisApp::openLayer( const QString &fileName, bool allowInteractive )
if ( fileName.endsWith( QStringLiteral( ".mbtiles" ), Qt::CaseInsensitive ) )
{
- // prefer to use WMS provider's implementation to open MBTiles rasters
- QUrlQuery uq;
- uq.addQueryItem( "type", "mbtiles" );
- uq.addQueryItem( "url", QUrl::fromLocalFile( fileName ).toString() );
- if ( addRasterLayer( uq.toString(), fileInfo.completeBaseName(), QStringLiteral( "wms" ) ) )
- return true;
+ QgsMBTilesReader reader( fileName );
+ if ( reader.open() )
+ {
+ if ( reader.metadataValue( "format" ) == QStringLiteral( "pbf" ) )
+ {
+ // these are vector tiles
+ QUrlQuery uq;
+ uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
+ uq.addQueryItem( QStringLiteral( "url" ), fileName );
+ std::unique_ptr vtLayer( new QgsVectorTileLayer( uq.toString(), fileInfo.completeBaseName() ) );
+ if ( vtLayer->isValid() )
+ {
+ QgsProject::instance()->addMapLayer( vtLayer.release() );
+ return true;
+ }
+ }
+ else // raster tiles
+ {
+ // prefer to use WMS provider's implementation to open MBTiles rasters
+ QUrlQuery uq;
+ uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
+ uq.addQueryItem( QStringLiteral( "url" ), QUrl::fromLocalFile( fileName ).toString() );
+ if ( addRasterLayer( uq.toString(), fileInfo.completeBaseName(), QStringLiteral( "wms" ) ) )
+ return true;
+ }
+ }
}
// try to load it as raster
@@ -7529,6 +7694,35 @@ void QgisApp::hideSelectedLayers()
}
}
+void QgisApp::toggleSelectedLayers()
+{
+ QgsDebugMsg( QStringLiteral( "toggling selected layers!" ) );
+
+ const auto constSelectedNodes = mLayerTreeView->selectedNodes();
+ if ( ! constSelectedNodes.isEmpty() )
+ {
+ bool isFirstNodeChecked = constSelectedNodes[0]->itemVisibilityChecked();
+ for ( QgsLayerTreeNode *node : constSelectedNodes )
+ {
+ node->setItemVisibilityChecked( ! isFirstNodeChecked );
+ }
+ }
+}
+
+void QgisApp::toggleSelectedLayersIndependently()
+{
+ QgsDebugMsg( QStringLiteral( "toggling selected layers independently!" ) );
+
+ const auto constSelectedNodes = mLayerTreeView->selectedNodes();
+ if ( ! constSelectedNodes.isEmpty() )
+ {
+ for ( QgsLayerTreeNode *node : constSelectedNodes )
+ {
+ node->setItemVisibilityChecked( ! node->itemVisibilityChecked() );
+ }
+ }
+}
+
void QgisApp::hideDeselectedLayers()
{
QList selectedLayerNodes = mLayerTreeView->selectedLayerNodes();
@@ -7798,6 +7992,9 @@ void QgisApp::changeDataSource( QgsMapLayer *layer )
vlayer->setSubsetString( subsetString );
}
+ if ( vlayer )
+ vlayer->updateExtents();
+
// All the following code is necessary to refresh the layer
QgsLayerTreeModel *model = qobject_cast( mLayerTreeView->model() );
if ( model )
@@ -7821,20 +8018,45 @@ void QgisApp::changeDataSource( QgsMapLayer *layer )
const QVariantMap fixedUriParts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
// next, we loop through to see if we can auto-fix any other layers with the same source
- const QMap< QString, QgsMapLayer * > layers = QgsProject::instance()->mapLayers( false );
- for ( auto it = layers.begin(); it != layers.end(); ++it )
+ if ( originalSourceParts.contains( QStringLiteral( "path" ) ) )
{
- if ( it.value()->isValid() )
- continue;
+ const QString originalPath = originalSourceParts.value( QStringLiteral( "path" ) ).toString();
+ const QFileInfo originalPathFi( originalPath );
- QVariantMap thisParts = QgsProviderRegistry::instance()->decodeUri( it.value()->providerType(), it.value()->source() );
- if ( thisParts.contains( QStringLiteral( "path" ) ) && thisParts.value( QStringLiteral( "path" ) ) == originalSourceParts.value( QStringLiteral( "path" ) ) )
+ const QMap< QString, QgsMapLayer * > layers = QgsProject::instance()->mapLayers( false );
+ for ( auto it = layers.begin(); it != layers.end(); ++it )
{
- // found a broken layer with the same original path, fix this one too
- uri.uri = it.value()->source().replace( thisParts.value( QStringLiteral( "path" ) ).toString(),
- fixedUriParts.value( QStringLiteral( "path" ) ).toString() );
- uri.providerKey = it.value()->providerType();
- fixLayer( it.value(), uri );
+ if ( it.value()->isValid() )
+ continue;
+
+ QVariantMap thisParts = QgsProviderRegistry::instance()->decodeUri( it.value()->providerType(), it.value()->source() );
+ if ( thisParts.contains( QStringLiteral( "path" ) ) )
+ {
+ const QString thisBrokenPath = thisParts.value( QStringLiteral( "path" ) ).toString();
+ QString fixedPath;
+
+ const QFileInfo thisBrokenPathFi( thisBrokenPath );
+ if ( thisBrokenPath == originalPath )
+ {
+ // found a broken layer with the same original path, fix this one too
+ fixedPath = fixedUriParts.value( QStringLiteral( "path" ) ).toString();
+ }
+ else if ( thisBrokenPathFi.path() == originalPathFi.path() )
+ {
+ // file from same original directory
+ QDir fixedDir = QFileInfo( fixedUriParts.value( QStringLiteral( "path" ) ).toString() ).dir();
+ const QString newCandidatePath = fixedDir.filePath( thisBrokenPathFi.fileName() );
+ if ( QFileInfo::exists( newCandidatePath ) )
+ fixedPath = newCandidatePath;
+ }
+
+ if ( !fixedPath.isEmpty() )
+ {
+ uri.uri = it.value()->source().replace( thisBrokenPath, fixedPath );
+ uri.providerKey = it.value()->providerType();
+ fixLayer( it.value(), uri );
+ }
+ }
}
}
}
@@ -8275,6 +8497,7 @@ QString QgisApp::saveAsFile( QgsMapLayer *layer, const bool onlySelected, const
return saveAsVectorFileGeneral( qobject_cast( layer ), true, onlySelected, defaultToAddToMap );
case QgsMapLayerType::MeshLayer:
+ case QgsMapLayerType::VectorTileLayer:
case QgsMapLayerType::PluginLayer:
return QString();
}
@@ -9286,7 +9509,7 @@ void QgisApp::modifyAttributesOfSelectedFeatures()
//dummy feature
QgsFeature f;
- QgsAttributeEditorContext context;
+ QgsAttributeEditorContext context( createAttributeEditorContext() );
context.setAllowCustomUi( false );
context.setVectorLayerTools( mVectorLayerTools );
context.setCadDockWidget( mAdvancedDigitizingDockWidget );
@@ -9519,6 +9742,23 @@ void QgisApp::deselectAll()
}
}
+void QgisApp::deselectActiveLayer()
+{
+ QgsVectorLayer *vlayer = qobject_cast( mMapCanvas->currentLayer() );
+
+ if ( !vlayer )
+ {
+ visibleMessageBar()->pushMessage(
+ tr( "No active vector layer" ),
+ tr( "To deselect all features, choose a vector layer in the legend" ),
+ Qgis::Info,
+ messageTimeout() );
+ return;
+ }
+
+ vlayer->removeSelection();
+}
+
void QgisApp::invertSelection()
{
QgsVectorLayer *vlayer = qobject_cast( mMapCanvas->currentLayer() );
@@ -9673,8 +9913,24 @@ void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer )
if ( !( geom.isEmpty() || geom.isNull( ) ) )
{
// avoid intersection if enabled in digitize settings
- geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers() );
- // Count collapsed geometries
+ QList avoidIntersectionsLayers;
+ switch ( QgsProject::instance()->avoidIntersectionsMode() )
+ {
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
+ avoidIntersectionsLayers.append( pasteVectorLayer );
+ break;
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers:
+ avoidIntersectionsLayers = QgsProject::instance()->avoidIntersectionsLayers();
+ break;
+ case QgsProject::AvoidIntersectionsMode::AllowIntersections:
+ break;
+ }
+ if ( avoidIntersectionsLayers.size() > 0 )
+ {
+ geom.avoidIntersections( avoidIntersectionsLayers );
+ }
+
+ // count collapsed geometries
if ( geom.isEmpty() || geom.isNull( ) )
invalidGeometriesCount++;
}
@@ -10894,7 +11150,7 @@ void QgisApp::duplicateLayers( const QList &lyrList )
Qgis::Critical, messageTimeout() );
else if ( qobject_cast( dupLayer ) )
visibleMessageBar()->pushMessage( tr( "Layer duplication complete" ),
- tr( "Note that it's using the same data source." ),
+ dupLayer->providerType() != QLatin1String( "memory" ) ? tr( "Note that it's using the same data source." ) : QString(),
Qgis::Info, messageTimeout() );
if ( !newSelection )
@@ -11462,9 +11718,7 @@ QMap< QString, int > QgisApp::optionsPagesMap()
sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Network" ), 13 );
sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Locator" ), 14 );
sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Advanced" ), 15 );
-#ifdef HAVE_OPENCL
sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Acceleration" ), 16 );
-#endif
} );
QMap< QString, int > map = sOptionsPagesMap;
@@ -11790,6 +12044,22 @@ void QgisApp::unregisterOptionsWidgetFactory( QgsOptionsWidgetFactory *factory )
mOptionsWidgetFactories.removeAll( factory );
}
+void QgisApp::registerDevToolFactory( QgsDevToolWidgetFactory *factory )
+{
+ mDevToolFactories << factory;
+ if ( mDevToolsWidget )
+ {
+ // widget was already created, so we manually need to push this factory to the widget
+ mDevToolsWidget->addToolFactory( factory );
+ }
+}
+
+void QgisApp::unregisterDevToolFactory( QgsDevToolWidgetFactory *factory )
+{
+ mDevToolsWidget->removeToolFactory( factory );
+ mDevToolFactories.removeAll( factory );
+}
+
QgsMapLayer *QgisApp::activeLayer()
{
return mLayerTreeView ? mLayerTreeView->currentLayer() : nullptr;
@@ -12120,6 +12390,7 @@ void QgisApp::new3DMapCanvas()
map->setSelectionColor( mMapCanvas->selectionColor() );
map->setBackgroundColor( mMapCanvas->canvasColor() );
map->setLayers( mMapCanvas->layers() );
+ map->setTemporalRange( mMapCanvas->temporalRange() );
map->setTransformContext( QgsProject::instance()->transformContext() );
map->setPathResolver( QgsProject::instance()->pathResolver() );
@@ -12169,6 +12440,7 @@ Qgs3DMapCanvasDockWidget *QgisApp::createNew3DMapCanvasDock( const QString &name
map3DWidget->setWindowTitle( name );
map3DWidget->mapCanvas3D()->setObjectName( name );
map3DWidget->setMainCanvas( mMapCanvas );
+ map3DWidget->mapCanvas3D()->setTemporalController( mTemporalControllerWidget->temporalController() );
return map3DWidget;
#else
Q_UNUSED( name )
@@ -13939,6 +14211,10 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
mActionIdentify->setEnabled( true );
break;
+ case QgsMapLayerType::VectorTileLayer:
+ // TODO
+ break;
+
case QgsMapLayerType::PluginLayer:
break;
@@ -14404,7 +14680,7 @@ void QgisApp::generateProjectAttachedFiles( QgsStringMap &files )
previewImage->deleteLater();
}
-void QgisApp::createPreviewImage( const QString &path )
+void QgisApp::createPreviewImage( const QString &path, const QIcon &icon )
{
// Render the map canvas
QSize previewSize( 250, 177 ); // h = w / std::sqrt(2)
@@ -14416,6 +14692,13 @@ void QgisApp::createPreviewImage( const QString &path )
QPainter previewPainter( &previewImage );
mMapCanvas->render( &previewPainter, QRect( QPoint(), previewSize ), previewRect );
+ if ( !icon.isNull() )
+ {
+ QPixmap pixmap = icon.pixmap( QSize( 24, 24 ) );
+ previewPainter.drawPixmap( QPointF( 250 - 24 - 5, 177 - 24 - 5 ), pixmap );
+ }
+ previewPainter.end();
+
// Save
previewImage.save( path );
}
@@ -14797,7 +15080,7 @@ void QgisApp::readProject( const QDomDocument &doc )
}
}
-void QgisApp::showLayerProperties( QgsMapLayer *mapLayer )
+void QgisApp::showLayerProperties( QgsMapLayer *mapLayer, const QString &page )
{
/*
TODO: Consider reusing the property dialogs again.
@@ -14821,6 +15104,9 @@ void QgisApp::showLayerProperties( QgsMapLayer *mapLayer )
case QgsMapLayerType::RasterLayer:
{
QgsRasterLayerProperties *rasterLayerPropertiesDialog = new QgsRasterLayerProperties( mapLayer, mMapCanvas, this );
+ if ( !page.isEmpty() )
+ rasterLayerPropertiesDialog->setCurrentPage( page );
+
// Cannot use exec here due to raster transparency map tool:
// in order to pass focus to the canvas, the dialog needs to
// be hidden and shown in non-modal mode.
@@ -14888,6 +15174,12 @@ void QgisApp::showLayerProperties( QgsMapLayer *mapLayer )
break;
}
+ case QgsMapLayerType::VectorTileLayer:
+ {
+ // TODO
+ break;
+ }
+
case QgsMapLayerType::PluginLayer:
{
QgsPluginLayer *pl = qobject_cast( mapLayer );
@@ -15064,6 +15356,10 @@ void QgisApp::toolButtonActionTriggered( QAction *action )
settings.setValue( QStringLiteral( "UI/selectionTool" ), 2 );
else if ( action == mActionInvertSelection )
settings.setValue( QStringLiteral( "UI/selectionTool" ), 3 );
+ else if ( action == mActionDeselectAll )
+ settings.setValue( QStringLiteral( "UI/deselectionTool" ), 0 );
+ else if ( action == mActionDeselectActiveLayer )
+ settings.setValue( QStringLiteral( "UI/deselectionTool" ), 1 );
else if ( action == mActionMeasure )
settings.setValue( QStringLiteral( "UI/measureTool" ), 0 );
else if ( action == mActionMeasureArea )
@@ -15569,3 +15865,13 @@ void QgisApp::triggerCrashHandler()
RaiseException( 0x12345678, 0, 0, nullptr );
#endif
}
+
+QgsAttributeEditorContext QgisApp::createAttributeEditorContext()
+{
+ QgsAttributeEditorContext context;
+ context.setVectorLayerTools( vectorLayerTools() );
+ context.setMapCanvas( mapCanvas() );
+ context.setCadDockWidget( cadDockWidget() );
+ context.setMainMessageBar( messageBar() );
+ return context;
+}
diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h
index 9c4144a9dbe8..2b71718dbfb6 100644
--- a/src/app/qgisapp.h
+++ b/src/app/qgisapp.h
@@ -50,6 +50,7 @@ class QgsComposerManager;
class QgsContrastEnhancement;
class QgsCoordinateReferenceSystem;
class QgsCustomDropHandler;
+class QgsCustomProjectOpenHandler;
class QgsCustomLayerOrderWidget;
class QgsDockWidget;
class QgsDoubleSpinBox;
@@ -139,6 +140,10 @@ class QgsLayoutCustomDropHandler;
class QgsProxyProgressTask;
class QgsNetworkRequestParameters;
class QgsBearingNumericFormat;
+class QgsDevToolsPanelWidget;
+class QgsDevToolWidgetFactory;
+class QgsNetworkLogger;
+class QgsNetworkLoggerWidgetFactory;
#include
#include
@@ -165,6 +170,7 @@ class QgsBearingNumericFormat;
#include "ui_qgisapp.h"
#include "qgis_app.h"
#include "qgsvectorlayerref.h"
+#include "devtools/qgsappdevtoolutils.h"
#include
#include
@@ -544,6 +550,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QAction *actionHideAllLayers() { return mActionHideAllLayers; }
QAction *actionShowAllLayers() { return mActionShowAllLayers; }
QAction *actionHideSelectedLayers() { return mActionHideSelectedLayers; }
+ QAction *actionToggleSelectedLayers() { return mActionToggleSelectedLayers; }
+ QAction *actionToggleSelectedLayersIndependently() { return mActionToggleSelectedLayersIndependently; }
QAction *actionHideDeselectedLayers() { return mActionHideDeselectedLayers; }
QAction *actionShowSelectedLayers() { return mActionShowSelectedLayers; }
@@ -633,7 +641,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsLocatorWidget *locatorWidget() { return mLocatorWidget; }
//! show layer properties
- void showLayerProperties( QgsMapLayer *mapLayer );
+ void showLayerProperties( QgsMapLayer *mapLayer, const QString &page = QString() );
//! returns pointer to map legend
QgsLayerTreeView *layerTreeView();
@@ -682,12 +690,34 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Unregister a previously registered tab in the options dialog
void unregisterOptionsWidgetFactory( QgsOptionsWidgetFactory *factory );
+ //! Register a dev tool factory
+ void registerDevToolFactory( QgsDevToolWidgetFactory *factory );
+
+ //! Unregister a previously registered dev tool factory
+ void unregisterDevToolFactory( QgsDevToolWidgetFactory *factory );
+
//! Register a new custom drop handler.
void registerCustomDropHandler( QgsCustomDropHandler *handler );
//! Unregister a previously registered custom drop handler.
void unregisterCustomDropHandler( QgsCustomDropHandler *handler );
+ /**
+ * Register a new custom project open \a handler.
+ * \note Ownership of \a handler is not transferred, and the handler must
+ * be unregistered when plugin is unloaded.
+ * \see QgsCustomProjectOpenHandler
+ * \see unregisterCustomProjectOpenHandler()
+ */
+ void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler );
+
+ /**
+ * Unregister a previously registered custom project open \a handler.
+ * \see QgsCustomDropHandler
+ * \see registerCustomProjectOpenHandler()
+ */
+ void unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler );
+
//! Returns a list of registered custom drop handlers.
QVector> customDropHandlers() const;
@@ -1109,6 +1139,12 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Create a new spatial bookmark
void newBookmark( bool inProject = false );
+ /**
+ * Creates a default attribute editor context using the main map canvas and the main edit tools and message bar
+ * \since QGIS 3.12
+ */
+ QgsAttributeEditorContext createAttributeEditorContext();
+
protected:
//! Handle state changes (WindowTitleChange)
@@ -1406,6 +1442,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void showAllLayers();
//reimplements method from base (gui) class
void hideSelectedLayers();
+ //! Toggles the visibility of the selected layers depending on the state of the first layer in selection (first clicked)
+ void toggleSelectedLayers();
+ //! Toggles the visibility of the selected layers independently
+ void toggleSelectedLayersIndependently();
//! Hides any layers which are not selected
void hideDeselectedLayers();
//reimplements method from base (gui) class
@@ -1497,6 +1537,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! deselect features from all layers
void deselectAll();
+ //! deselect features from the current active layer
+ void deselectActiveLayer();
+
//! select all features
void selectAll();
@@ -1820,7 +1863,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void activeLayerChanged( QgsMapLayer *layer );
private:
- void createPreviewImage( const QString &path );
+ void createPreviewImage( const QString &path, const QIcon &overlayIcon = QIcon() );
void startProfile( const QString &name );
void endProfile();
void functionProfile( void ( QgisApp::*fnc )(), QgisApp *instance, const QString &name );
@@ -1875,8 +1918,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
* instance simultaneously results in data loss.
*
* \param savePreviewImage Set to false when the preview image should not be saved. E.g. project load.
+ * \param iconOverlay optional icon to overlay when saving a preview image
*/
- void saveRecentProjectPath( bool savePreviewImage = true );
+ void saveRecentProjectPath( bool savePreviewImage = true, const QIcon &iconOverlay = QIcon() );
//! Save recent projects list to settings
void saveRecentProjects();
//! Update project menu with the current list of recently accessed projects
@@ -2325,6 +2369,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsUserProfileManager *mUserProfileManager = nullptr;
QgsDockWidget *mMapStylingDock = nullptr;
QgsLayerStylingWidget *mMapStyleWidget = nullptr;
+ QgsDockWidget *mDevToolsDock = nullptr;
+ QgsDevToolsPanelWidget *mDevToolsWidget = nullptr;
//! Persistent tile scale slider
QgsTileScaleWidget *mpTileScaleWidget = nullptr;
@@ -2368,7 +2414,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QList mMapLayerPanelFactories;
QList> mOptionsWidgetFactories;
+ QList mDevToolFactories;
+
QVector> mCustomDropHandlers;
+ QVector> mCustomProjectOpenHandlers;
QVector> mCustomLayoutDropHandlers;
QgsLayoutCustomDropHandler *mLayoutQptDropHandler = nullptr;
@@ -2419,6 +2468,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
std::unique_ptr< QgsBearingNumericFormat > mBearingNumericFormat;
+ QgsNetworkLogger *mNetworkLogger = nullptr;
+ QgsScopedDevToolWidgetFactory mNetworkLoggerWidgetFactory;
+
class QgsCanvasRefreshBlocker
{
public:
diff --git a/src/app/qgisappinterface.cpp b/src/app/qgisappinterface.cpp
index 51867c98de2c..bb75ca01030d 100644
--- a/src/app/qgisappinterface.cpp
+++ b/src/app/qgisappinterface.cpp
@@ -536,6 +536,16 @@ void QgisAppInterface::unregisterOptionsWidgetFactory( QgsOptionsWidgetFactory *
qgis->unregisterOptionsWidgetFactory( factory );
}
+void QgisAppInterface::registerDevToolWidgetFactory( QgsDevToolWidgetFactory *factory )
+{
+ qgis->registerDevToolFactory( factory );
+}
+
+void QgisAppInterface::unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory )
+{
+ qgis->unregisterDevToolFactory( factory );
+}
+
void QgisAppInterface::registerCustomDropHandler( QgsCustomDropHandler *handler )
{
qgis->registerCustomDropHandler( handler );
@@ -556,6 +566,16 @@ void QgisAppInterface::unregisterCustomDropHandler( QgsCustomDropHandler *handle
qgis->unregisterCustomDropHandler( handler );
}
+void QgisAppInterface::registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
+{
+ qgis->registerCustomProjectOpenHandler( handler );
+}
+
+void QgisAppInterface::unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
+{
+ qgis->unregisterCustomProjectOpenHandler( handler );
+}
+
QMenu *QgisAppInterface::projectMenu() { return qgis->projectMenu(); }
QMenu *QgisAppInterface::editMenu() { return qgis->editMenu(); }
QMenu *QgisAppInterface::viewMenu() { return qgis->viewMenu(); }
@@ -684,6 +704,8 @@ QAction *QgisAppInterface::actionRemoveAllFromOverview() { return qgis->actionRe
QAction *QgisAppInterface::actionHideAllLayers() { return qgis->actionHideAllLayers(); }
QAction *QgisAppInterface::actionShowAllLayers() { return qgis->actionShowAllLayers(); }
QAction *QgisAppInterface::actionHideSelectedLayers() { return qgis->actionHideSelectedLayers(); }
+QAction *QgisAppInterface::actionToggleSelectedLayers() { return qgis->actionToggleSelectedLayers(); }
+QAction *QgisAppInterface::actionToggleSelectedLayersIndependently() { return qgis->actionToggleSelectedLayersIndependently(); }
QAction *QgisAppInterface::actionHideDeselectedLayers() { return qgis->actionHideDeselectedLayers(); }
QAction *QgisAppInterface::actionShowSelectedLayers() { return qgis->actionShowSelectedLayers(); }
@@ -755,12 +777,8 @@ QgsAttributeDialog *QgisAppInterface::getFeatureForm( QgsVectorLayer *l, QgsFeat
myDa.setSourceCrs( l->crs(), QgsProject::instance()->transformContext() );
myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
- QgsAttributeEditorContext context;
+ QgsAttributeEditorContext context( QgisApp::instance()->createAttributeEditorContext() );
context.setDistanceArea( myDa );
- context.setVectorLayerTools( qgis->vectorLayerTools() );
- context.setMapCanvas( qgis->mapCanvas() );
- context.setCadDockWidget( qgis->cadDockWidget() );
- context.setMainMessageBar( qgis->messageBar() );
QgsAttributeDialog *dialog = new QgsAttributeDialog( l, &feature, false, qgis, true, context );
if ( !feature.isValid() )
{
diff --git a/src/app/qgisappinterface.h b/src/app/qgisappinterface.h
index 1468d0fe0172..60ab70e75adf 100644
--- a/src/app/qgisappinterface.h
+++ b/src/app/qgisappinterface.h
@@ -143,8 +143,12 @@ class APP_EXPORT QgisAppInterface : public QgisInterface
void unregisterMapLayerConfigWidgetFactory( QgsMapLayerConfigWidgetFactory *factory ) override;
void registerOptionsWidgetFactory( QgsOptionsWidgetFactory *factory ) override;
void unregisterOptionsWidgetFactory( QgsOptionsWidgetFactory *factory ) override;
+ void registerDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) override;
+ void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) override;
void registerCustomDropHandler( QgsCustomDropHandler *handler ) override;
void unregisterCustomDropHandler( QgsCustomDropHandler *handler ) override;
+ void registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) override;
+ void unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler ) override;
void registerCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler ) override;
void unregisterCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler ) override;
QMenu *projectMenu() override;
@@ -254,6 +258,8 @@ class APP_EXPORT QgisAppInterface : public QgisInterface
QAction *actionHideAllLayers() override;
QAction *actionShowAllLayers() override;
QAction *actionHideSelectedLayers() override;
+ QAction *actionToggleSelectedLayers() override;
+ QAction *actionToggleSelectedLayersIndependently() override;
QAction *actionHideDeselectedLayers() override;
QAction *actionShowSelectedLayers() override;
QAction *actionManagePlugins() override;
diff --git a/src/app/qgisappstylesheet.cpp b/src/app/qgisappstylesheet.cpp
index c4dde545df4e..c824378e7eb6 100644
--- a/src/app/qgisappstylesheet.cpp
+++ b/src/app/qgisappstylesheet.cpp
@@ -16,14 +16,16 @@
* *
***************************************************************************/
+#include
+#include
+
#include "qgisappstylesheet.h"
#include "qgsapplication.h"
#include "qgisapp.h"
+#include "qgsproxystyle.h"
#include "qgslogger.h"
#include "qgssettings.h"
-#include
-#include
QgisAppStyleSheet::QgisAppStyleSheet( QObject *parent )
: QObject( parent )
@@ -106,18 +108,24 @@ void QgisAppStyleSheet::buildStyleSheet( const QMap &opts )
if ( fontSize != defaultSize || fontFamily != defaultFamily )
ss += QStringLiteral( "* { font: %1pt \"%2\"} " ).arg( fontSize, fontFamily );
+#if QT_VERSION < QT_VERSION_CHECK(5, 12, 2)
// Fix for macOS Qt 5.9+, where close boxes do not show on document mode tab bar tabs
- // See: https://bugreports.qt.io/browse/QTBUG-61092
- // https://bugreports.qt.io/browse/QTBUG-61742
+ // See: https://bugreports.qt.io/browse/QTBUG-61092 => fixed in 5.12.2 / 5.14
+ // https://bugreports.qt.io/browse/QTBUG-61742 => fixed in 5.9.2
// Setting any stylesheet makes the default close button disappear.
// Specifically setting a custom close button temporarily works around issue.
- // TODO: Remove when regression is fixed (Qt 5.9.3 or 5.10?); though hard to tell,
- // since we are overriding the default close button image now.
if ( mMacStyle )
{
ss += QLatin1String( "QTabBar::close-button{ image: url(:/images/themes/default/mIconCloseTab.svg); }" );
ss += QLatin1String( "QTabBar::close-button:hover{ image: url(:/images/themes/default/mIconCloseTabHover.svg); }" );
}
+#endif
+ if ( mMacStyle )
+ {
+ ss += QLatin1String( "QWidget#QgsTextFormatWidgetBase QTabWidget#mOptionsTab QTabBar::tab," );
+ ss += QLatin1String( "QWidget#QgsRendererMeshPropsWidgetBase QTabWidget#mStyleOptionsTab" );
+ ss += QLatin1String( "QTabBar::tab { width: 1.2em; }" );
+ }
ss += QLatin1String( "QGroupBox{ font-weight: 600; }" );
@@ -191,7 +199,8 @@ void QgisAppStyleSheet::saveToSettings( const QMap &opts )
void QgisAppStyleSheet::setActiveValues()
{
- mStyle = qApp->style()->objectName(); // active style name (lowercase)
+ QgsAppStyle *style = dynamic_cast( qApp->style() );
+ mStyle = style ? style->baseStyle() : qApp->style()->objectName(); // active style name (lowercase)
QgsDebugMsgLevel( QStringLiteral( "Style name: %1" ).arg( mStyle ), 2 );
mMacStyle = mStyle.contains( QLatin1String( "macintosh" ) ); // macintosh (aqua)
diff --git a/src/app/qgsapplayertreeviewmenuprovider.cpp b/src/app/qgsapplayertreeviewmenuprovider.cpp
index 83b08fe6923d..bf852fd8a0aa 100644
--- a/src/app/qgsapplayertreeviewmenuprovider.cpp
+++ b/src/app/qgsapplayertreeviewmenuprovider.cpp
@@ -117,6 +117,11 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
menu->addAction( actions->actionMoveToTop( menu ) );
}
+ if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == idx.model()->rowCount() - 1 ) )
+ {
+ menu->addAction( actions->actionMoveToBottom( menu ) );
+ }
+
menu->addSeparator();
if ( mView->selectedNodes( true ).count() >= 2 )
@@ -204,6 +209,11 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
menu->addAction( actions->actionMoveToTop( menu ) );
}
+ if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == idx.model()->rowCount() - 1 ) )
+ {
+ menu->addAction( actions->actionMoveToBottom( menu ) );
+ }
+
QAction *checkAll = actions->actionCheckAndAllParents( menu );
if ( checkAll )
menu->addAction( checkAll );
diff --git a/src/app/qgsattributetabledialog.cpp b/src/app/qgsattributetabledialog.cpp
index 3f53b06b6b50..3ba326f4818a 100644
--- a/src/app/qgsattributetabledialog.cpp
+++ b/src/app/qgsattributetabledialog.cpp
@@ -152,12 +152,9 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *layer, QgsAttr
QgsDistanceArea da;
da.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
da.setEllipsoid( QgsProject::instance()->ellipsoid() );
- mEditorContext.setDistanceArea( da );
- mEditorContext.setVectorLayerTools( QgisApp::instance()->vectorLayerTools() );
- mEditorContext.setMapCanvas( QgisApp::instance()->mapCanvas() );
- mEditorContext.setMainMessageBar( QgisApp::instance()->messageBar() );
- mEditorContext.setCadDockWidget( QgisApp::instance()->cadDockWidget() );
+ QgsAttributeEditorContext editorContext = QgisApp::instance()->createAttributeEditorContext();
+ editorContext.setDistanceArea( da );
QgsFeatureRequest r;
bool needsGeom = false;
@@ -177,12 +174,12 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *layer, QgsAttr
r.setFlags( QgsFeatureRequest::NoGeometry );
// Initialize dual view
- mMainView->init( mLayer, QgisApp::instance()->mapCanvas(), r, mEditorContext, false );
+ mMainView->init( mLayer, QgisApp::instance()->mapCanvas(), r, editorContext, false );
QgsAttributeTableConfig config = mLayer->attributeTableConfig();
mMainView->setAttributeTableConfig( config );
- mFeatureFilterWidget->init( mLayer, mEditorContext, mMainView, QgisApp::instance()->messageBar(), QgisApp::instance()->messageTimeout() );
+ mFeatureFilterWidget->init( mLayer, editorContext, mMainView, QgisApp::instance()->messageBar(), QgisApp::instance()->messageTimeout() );
mActionFeatureActions = new QToolButton();
mActionFeatureActions->setAutoRaise( false );
diff --git a/src/app/qgsattributetabledialog.h b/src/app/qgsattributetabledialog.h
index 92df0e463884..ec3923892329 100644
--- a/src/app/qgsattributetabledialog.h
+++ b/src/app/qgsattributetabledialog.h
@@ -221,7 +221,6 @@ class APP_EXPORT QgsAttributeTableDialog : public QDialog, private Ui::QgsAttrib
QPointer< QgsVectorLayer > mLayer = nullptr;
QStringList mVisibleFields;
- QgsAttributeEditorContext mEditorContext;
void updateMultiEditButtonState();
void deleteFeature( QgsFeatureId fid );
diff --git a/src/app/qgsdevtoolspanelwidget.cpp b/src/app/qgsdevtoolspanelwidget.cpp
new file mode 100644
index 000000000000..03756c9500f2
--- /dev/null
+++ b/src/app/qgsdevtoolspanelwidget.cpp
@@ -0,0 +1,77 @@
+/***************************************************************************
+ qgsdevtoolspanelwidget.cpp
+ ---------------------
+ Date : March 2020
+ Copyright : (C) 2020 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 "qgsdevtoolspanelwidget.h"
+#include "qgisapp.h"
+#include "qgsdevtoolwidgetfactory.h"
+#include "qgsdevtoolwidget.h"
+#include "qgspanelwidgetstack.h"
+
+
+QgsDevToolsPanelWidget::QgsDevToolsPanelWidget( const QList &factories, QWidget *parent )
+ : QWidget( parent )
+{
+ setupUi( this );
+
+ mOptionsListWidget->setIconSize( QgisApp::instance()->iconSize( false ) );
+ mOptionsListWidget->setMaximumWidth( static_cast< int >( mOptionsListWidget->iconSize().width() * 1.18 ) );
+
+ for ( QgsDevToolWidgetFactory *factory : factories )
+ addToolFactory( factory );
+
+ connect( mOptionsListWidget, &QListWidget::currentRowChanged, this, &QgsDevToolsPanelWidget::setCurrentTool );
+}
+
+QgsDevToolsPanelWidget::~QgsDevToolsPanelWidget() = default;
+
+void QgsDevToolsPanelWidget::addToolFactory( QgsDevToolWidgetFactory *factory )
+{
+ if ( QgsDevToolWidget *toolWidget = factory->createWidget( this ) )
+ {
+ QgsPanelWidgetStack *toolStack = new QgsPanelWidgetStack();
+ toolStack->setMainPanel( toolWidget );
+ mStackedWidget->addWidget( toolStack );
+
+ QListWidgetItem *item = new QListWidgetItem( factory->icon(), QString() );
+ item->setToolTip( factory->title() );
+ mOptionsListWidget->addItem( item );
+ int row = mOptionsListWidget->row( item );
+ mFactoryPages[factory] = row;
+
+ if ( mOptionsListWidget->count() == 1 )
+ {
+ setCurrentTool( 0 );
+ }
+ }
+}
+
+void QgsDevToolsPanelWidget::removeToolFactory( QgsDevToolWidgetFactory *factory )
+{
+ if ( mFactoryPages.contains( factory ) )
+ {
+ int currentRow = mStackedWidget->currentIndex();
+ int row = mFactoryPages.value( factory );
+ mStackedWidget->removeWidget( mStackedWidget->widget( row ) );
+ mOptionsListWidget->removeItemWidget( mOptionsListWidget->item( row ) );
+ mFactoryPages.remove( factory );
+ if ( currentRow == row )
+ setCurrentTool( 0 );
+ }
+}
+
+void QgsDevToolsPanelWidget::setCurrentTool( int row )
+{
+ whileBlocking( mOptionsListWidget )->setCurrentRow( row );
+ mStackedWidget->setCurrentIndex( row );
+}
diff --git a/src/app/qgsdevtoolspanelwidget.h b/src/app/qgsdevtoolspanelwidget.h
new file mode 100644
index 000000000000..100db97f5a62
--- /dev/null
+++ b/src/app/qgsdevtoolspanelwidget.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ qgsdevtoolspanelwidget.h
+ ---------------------
+ Date : March 2020
+ Copyright : (C) 2020 Nyall Dawson
+ Email : nyall dot dawson at gmail dot com
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSDEVTOOLSPANELWIDGET_H
+#define QGSDEVTOOLSPANELWIDGET_H
+
+#include "ui_qgsdevtoolswidgetbase.h"
+#include "qgis_app.h"
+
+class QgsDevToolWidgetFactory;
+
+class APP_EXPORT QgsDevToolsPanelWidget : public QWidget, private Ui::QgsDevToolsWidgetBase
+{
+ Q_OBJECT
+ public:
+
+ QgsDevToolsPanelWidget( const QList &factories, QWidget *parent = nullptr );
+ ~QgsDevToolsPanelWidget() override;
+
+ void addToolFactory( QgsDevToolWidgetFactory *factory );
+
+ void removeToolFactory( QgsDevToolWidgetFactory *factory );
+
+ private slots:
+
+ void setCurrentTool( int row );
+
+ private:
+
+ QMap< QgsDevToolWidgetFactory *, int> mFactoryPages;
+};
+
+#endif // QGSDEVTOOLSPANELWIDGET_H
diff --git a/src/app/qgsfeatureaction.cpp b/src/app/qgsfeatureaction.cpp
index 3ce0b7a1d0fd..b3c46a55ed39 100644
--- a/src/app/qgsfeatureaction.cpp
+++ b/src/app/qgsfeatureaction.cpp
@@ -53,7 +53,7 @@ QgsAttributeDialog *QgsFeatureAction::newDialog( bool cloneFeature )
{
QgsFeature *f = cloneFeature ? new QgsFeature( *mFeature ) : mFeature;
- QgsAttributeEditorContext context;
+ QgsAttributeEditorContext context( QgisApp::instance()->createAttributeEditorContext() );
QgsDistanceArea myDa;
@@ -61,14 +61,11 @@ QgsAttributeDialog *QgsFeatureAction::newDialog( bool cloneFeature )
myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
context.setDistanceArea( myDa );
- context.setVectorLayerTools( QgisApp::instance()->vectorLayerTools() );
- context.setMapCanvas( QgisApp::instance()->mapCanvas() );
- context.setCadDockWidget( QgisApp::instance()->cadDockWidget() );
- context.setMainMessageBar( QgisApp::instance()->messageBar() );
context.setFormMode( QgsAttributeEditorContext::StandaloneDialog );
QgsAttributeDialog *dialog = new QgsAttributeDialog( mLayer, f, cloneFeature, parentWidget(), true, context );
dialog->setWindowFlags( dialog->windowFlags() | Qt::Tool );
+
dialog->setObjectName( QStringLiteral( "featureactiondlg:%1:%2" ).arg( mLayer->id() ).arg( f->id() ) );
QList actions = mLayer->actions()->actions( QStringLiteral( "Feature" ) );
diff --git a/src/app/qgsfixattributedialog.cpp b/src/app/qgsfixattributedialog.cpp
index 57a83bc8ff5e..c4e81e2bffac 100644
--- a/src/app/qgsfixattributedialog.cpp
+++ b/src/app/qgsfixattributedialog.cpp
@@ -16,6 +16,7 @@
#include "qgsattributeform.h"
#include "qgsapplication.h"
+#include "qgisapp.h"
#include
@@ -33,6 +34,7 @@ void QgsFixAttributeDialog::init( QgsVectorLayer *layer )
setLayout( new QGridLayout() );
layout()->setMargin( 0 );
context.setFormMode( QgsAttributeEditorContext::StandaloneDialog );
+ context.setVectorLayerTools( QgisApp::instance()->vectorLayerTools() );
mUnfixedFeatures = mFeatures;
mCurrentFeature = mFeatures.begin();
diff --git a/src/app/qgshandlebadlayers.cpp b/src/app/qgshandlebadlayers.cpp
index 491ee9de7ae5..9fa17ce11fb3 100644
--- a/src/app/qgshandlebadlayers.cpp
+++ b/src/app/qgshandlebadlayers.cpp
@@ -44,7 +44,8 @@
void QgsHandleBadLayersHandler::handleBadLayers( const QList &layers )
{
- QApplication::setOverrideCursor( Qt::ArrowCursor );
+ QgsTemporaryCursorRestoreOverride cursorOverride;
+
QgsHandleBadLayers *dialog = new QgsHandleBadLayers( layers );
dialog->buttonBox->button( QDialogButtonBox::Ignore )->setToolTip( tr( "Import all unavailable layers unmodified (you can fix them later)." ) );
@@ -70,7 +71,6 @@ void QgsHandleBadLayersHandler::handleBadLayers( const QList &layers )
}
delete dialog;
- QApplication::restoreOverrideCursor();
}
QgsHandleBadLayers::QgsHandleBadLayers( const QList &layers )
@@ -113,6 +113,9 @@ QgsHandleBadLayers::QgsHandleBadLayers( const QList &layers )
<< tr( "Datasource" )
);
+ mLayerList->horizontalHeader()->setSectionsMovable( true );
+ mLayerList->horizontalHeader()->setSectionResizeMode( QHeaderView::Interactive );
+
int j = 0;
for ( int i = 0; i < mLayers.size(); i++ )
{
diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp
index 617219d98f7f..6f38ea368484 100644
--- a/src/app/qgsidentifyresultsdialog.cpp
+++ b/src/app/qgsidentifyresultsdialog.cpp
@@ -38,6 +38,7 @@
#include
#include
#include
+#include
//graph
#include
@@ -77,6 +78,7 @@
#include "qgsfiledownloaderdialog.h"
#include "qgsfieldformatterregistry.h"
#include "qgsfieldformatter.h"
+#include "qgsfieldmodel.h"
#include "qgssettings.h"
#include "qgsgui.h"
#include "qgsexpressioncontextutils.h"
@@ -497,6 +499,10 @@ void QgsIdentifyResultsDialog::addFeature( const QgsMapToolIdentify::IdentifyRes
addFeature( qobject_cast( result.mLayer ), result.mLabel, result.mAttributes, result.mDerivedAttributes );
break;
+ case QgsMapLayerType::VectorTileLayer:
+ // TODO
+ break;
+
case QgsMapLayerType::PluginLayer:
break;
}
@@ -511,6 +517,9 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
layItem = new QTreeWidgetItem( QStringList() << vlayer->name() );
layItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast( vlayer ) ) );
lstResults->addTopLevelItem( layItem );
+ QFont boldFont;
+ boldFont.setBold( true );
+ layItem->setFont( 0, boldFont );
connect( vlayer, &QObject::destroyed, this, &QgsIdentifyResultsDialog::layerDestroyed );
connect( vlayer, &QgsMapLayer::crsChanged, this, &QgsIdentifyResultsDialog::layerDestroyed );
@@ -526,6 +535,12 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
featItem->setData( 0, Qt::UserRole + 1, mFeatures.size() );
mFeatures << f;
layItem->addChild( featItem );
+ layItem->setFirstColumnSpanned( true );
+
+ QString countSuffix = layItem->childCount() > 1
+ ? QStringLiteral( " [%1]" ).arg( layItem->childCount() )
+ : QString();
+ layItem->setText( 0, QStringLiteral( "%1 %2" ).arg( vlayer->name(), countSuffix ) );
if ( derivedAttributes.size() >= 0 )
{
@@ -616,7 +631,7 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
featItem->addChild( attrItem );
attrItem->setData( 0, Qt::DisplayRole, vlayer->attributeDisplayName( i ) );
- attrItem->setToolTip( 0, vlayer->attributeDisplayName( i ) );
+ attrItem->setToolTip( 0, QgsFieldModel::fieldToolTipExtended( fields.at( i ), vlayer ) );
attrItem->setData( 0, Qt::UserRole, fields.at( i ).name() );
attrItem->setData( 0, Qt::UserRole + 1, i );
@@ -1873,7 +1888,7 @@ void QgsIdentifyResultsDialog::zoomToFeature()
rect.scale( 0.5, &c );
}
- mCanvas->setExtent( rect );
+ mCanvas->setExtent( rect, true );
mCanvas->refresh();
}
diff --git a/src/app/qgslayerstylingwidget.cpp b/src/app/qgslayerstylingwidget.cpp
index 1ab0b863bbb1..c98d4e9e51e2 100644
--- a/src/app/qgslayerstylingwidget.cpp
+++ b/src/app/qgslayerstylingwidget.cpp
@@ -35,6 +35,8 @@
#include "qgsmaplayer.h"
#include "qgsstyle.h"
#include "qgsvectorlayer.h"
+#include "qgsvectortilelayer.h"
+#include "qgsvectortilebasicrendererwidget.h"
#include "qgsmeshlayer.h"
#include "qgsproject.h"
#include "qgsundowidget.h"
@@ -225,6 +227,16 @@ void QgsLayerStylingWidget::setLayer( QgsMapLayer *layer )
break;
}
+ case QgsMapLayerType::VectorTileLayer:
+ {
+ QListWidgetItem *symbolItem = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/symbology.svg" ) ), QString() );
+ symbolItem->setData( Qt::UserRole, Symbology );
+ symbolItem->setToolTip( tr( "Symbology" ) );
+ mOptionsListWidget->addItem( symbolItem );
+
+ break;
+ }
+
case QgsMapLayerType::PluginLayer:
break;
}
@@ -600,6 +612,25 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer()
break;
}
+ case QgsMapLayerType::VectorTileLayer:
+ {
+ QgsVectorTileLayer *vtLayer = qobject_cast( mCurrentLayer );
+ switch ( row )
+ {
+ case 0: // Style
+ {
+ mVectorTileStyleWidget = new QgsVectorTileBasicRendererWidget( vtLayer, mMapCanvas, mMessageBar, mWidgetStack );
+ mVectorTileStyleWidget->setDockMode( true );
+ connect( mVectorTileStyleWidget, &QgsPanelWidget::widgetChanged, this, &QgsLayerStylingWidget::autoApply );
+ mWidgetStack->setMainPanel( mVectorTileStyleWidget );
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+
case QgsMapLayerType::PluginLayer:
{
mStackedWidget->setCurrentIndex( mNotSupportedPage );
@@ -724,6 +755,9 @@ bool QgsLayerStyleManagerWidgetFactory::supportsLayer( QgsMapLayer *layer ) cons
case QgsMapLayerType::MeshLayer:
return true;
+ case QgsMapLayerType::VectorTileLayer:
+ return false; // TODO
+
case QgsMapLayerType::PluginLayer:
return false;
}
diff --git a/src/app/qgslayerstylingwidget.h b/src/app/qgslayerstylingwidget.h
index c1ca3f5bcf35..52bef55b003a 100644
--- a/src/app/qgslayerstylingwidget.h
+++ b/src/app/qgslayerstylingwidget.h
@@ -44,6 +44,7 @@ class QgsMapLayerStyleManagerWidget;
class QgsVectorLayer3DRendererWidget;
class QgsMeshLayer3DRendererWidget;
class QgsMessageBar;
+class QgsVectorTileBasicRendererWidget;
class APP_EXPORT QgsLayerStyleManagerWidgetFactory : public QgsMapLayerConfigWidgetFactory
{
@@ -149,6 +150,7 @@ class APP_EXPORT QgsLayerStylingWidget : public QWidget, private Ui::QgsLayerSty
#endif
QgsRendererRasterPropertiesWidget *mRasterStyleWidget = nullptr;
QgsRendererMeshPropertiesWidget *mMeshStyleWidget = nullptr;
+ QgsVectorTileBasicRendererWidget *mVectorTileStyleWidget = nullptr;
QList mPageFactories;
QMap mUserPages;
QgsLayerStyleManagerWidgetFactory *mStyleManagerFactory = nullptr;
diff --git a/src/app/qgslayertreeviewindicatorprovider.cpp b/src/app/qgslayertreeviewindicatorprovider.cpp
index daa382d75900..66f680a79ce9 100644
--- a/src/app/qgslayertreeviewindicatorprovider.cpp
+++ b/src/app/qgslayertreeviewindicatorprovider.cpp
@@ -21,6 +21,7 @@
#include "qgslayertreeview.h"
#include "qgsvectorlayer.h"
#include "qgsrasterlayer.h"
+#include "qgsmeshlayer.h"
#include "qgisapp.h"
#include "qgsapplication.h"
@@ -96,7 +97,9 @@ void QgsLayerTreeViewIndicatorProvider::onLayerLoaded()
if ( !layerNode )
return;
- if ( !( qobject_cast( layerNode->layer() ) || qobject_cast( layerNode->layer() ) ) )
+ if ( !( qobject_cast( layerNode->layer() ) ||
+ qobject_cast( layerNode->layer() ) ||
+ qobject_cast( layerNode->layer() ) ) )
return;
if ( QgsMapLayer *mapLayer = layerNode->layer() )
diff --git a/src/app/qgslayertreeviewtemporalindicator.cpp b/src/app/qgslayertreeviewtemporalindicator.cpp
index 133bd40523cc..c99b563fcf07 100644
--- a/src/app/qgslayertreeviewtemporalindicator.cpp
+++ b/src/app/qgslayertreeviewtemporalindicator.cpp
@@ -20,7 +20,9 @@
#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgslayertreeutils.h"
+#include "qgsmeshlayer.h"
#include "qgsrasterlayer.h"
+#include "qgsrasterlayerproperties.h"
#include "qgisapp.h"
QgsLayerTreeViewTemporalIndicatorProvider::QgsLayerTreeViewTemporalIndicatorProvider( QgsLayerTreeView *view )
@@ -30,7 +32,7 @@ QgsLayerTreeViewTemporalIndicatorProvider::QgsLayerTreeViewTemporalIndicatorProv
void QgsLayerTreeViewTemporalIndicatorProvider::connectSignals( QgsMapLayer *layer )
{
- if ( !layer->temporalProperties() )
+ if ( !layer || !layer->temporalProperties() )
return;
connect( layer->temporalProperties(), &QgsMapLayerTemporalProperties::changed, this, [ this, layer ]( ) { this->onLayerChanged( layer ); } );
@@ -49,12 +51,14 @@ void QgsLayerTreeViewTemporalIndicatorProvider::onIndicatorClicked( const QModel
switch ( layer->type() )
{
case QgsMapLayerType::RasterLayer:
- QgisApp::instance()->showLayerProperties( qobject_cast( layer ) );
+ QgisApp::instance()->showLayerProperties( qobject_cast( layer ), QStringLiteral( "mOptsPage_Temporal" ) );
break;
-
- case QgsMapLayerType::VectorLayer:
case QgsMapLayerType::MeshLayer:
+ QgisApp::instance()->showLayerProperties( qobject_cast( layer ), QStringLiteral( "mOptsPage_Temporal" ) );
+ break;
+ case QgsMapLayerType::VectorLayer:
case QgsMapLayerType::PluginLayer:
+ case QgsMapLayerType::VectorTileLayer:
break;
}
}
@@ -69,31 +73,14 @@ bool QgsLayerTreeViewTemporalIndicatorProvider::acceptLayer( QgsMapLayer *layer
return false;
}
-QString QgsLayerTreeViewTemporalIndicatorProvider::iconName( QgsMapLayer *layer )
+QString QgsLayerTreeViewTemporalIndicatorProvider::iconName( QgsMapLayer * )
{
- switch ( layer->temporalProperties()->temporalSource() )
- {
- case QgsMapLayerTemporalProperties::TemporalSource::Project:
- return QStringLiteral( "/mIndicatorTimeFromProject.svg" );
-
- case QgsMapLayerTemporalProperties::TemporalSource::Layer:
- return QStringLiteral( "/mIndicatorTemporal.svg" );
- }
-
- return QString();
+ return QStringLiteral( "/mIndicatorTemporal.svg" );
}
-QString QgsLayerTreeViewTemporalIndicatorProvider::tooltipText( QgsMapLayer *layer )
+QString QgsLayerTreeViewTemporalIndicatorProvider::tooltipText( QgsMapLayer * )
{
- switch ( layer->temporalProperties()->temporalSource() )
- {
- case QgsMapLayerTemporalProperties::TemporalSource::Project:
- return tr( "Temporal layer, currently using project's time range " );
-
- case QgsMapLayerTemporalProperties::TemporalSource::Layer:
- return tr( "Temporal layer " );
- }
- return QString();
+ return tr( "Temporal layer " );
}
void QgsLayerTreeViewTemporalIndicatorProvider::onLayerChanged( QgsMapLayer *layer )
diff --git a/src/app/qgsmapthemes.cpp b/src/app/qgsmapthemes.cpp
index 687cb3bd97e1..1151e4ee4448 100644
--- a/src/app/qgsmapthemes.cpp
+++ b/src/app/qgsmapthemes.cpp
@@ -42,6 +42,8 @@ QgsMapThemes::QgsMapThemes()
mMenu->addAction( QgisApp::instance()->actionHideAllLayers() );
mMenu->addAction( QgisApp::instance()->actionShowSelectedLayers() );
mMenu->addAction( QgisApp::instance()->actionHideSelectedLayers() );
+ mMenu->addAction( QgisApp::instance()->actionToggleSelectedLayers() );
+ mMenu->addAction( QgisApp::instance()->actionToggleSelectedLayersIndependently() );
mMenu->addAction( QgisApp::instance()->actionHideDeselectedLayers() );
mMenu->addSeparator();
diff --git a/src/app/qgsmaptooladdfeature.cpp b/src/app/qgsmaptooladdfeature.cpp
index c2e60b674c51..1669f6b0a9fc 100644
--- a/src/app/qgsmaptooladdfeature.cpp
+++ b/src/app/qgsmaptooladdfeature.cpp
@@ -33,6 +33,7 @@
#include "qgsfeatureaction.h"
#include "qgisapp.h"
#include "qgsexpressioncontextutils.h"
+#include "qgsrubberband.h"
#include
@@ -69,7 +70,9 @@ void QgsMapToolAddFeature::digitized( const QgsFeature &f )
{
//add points to other features to keep topology up-to-date
bool topologicalEditing = QgsProject::instance()->topologicalEditing();
- if ( mode() == CaptureLine || mode() == CapturePolygon )
+ QgsProject::AvoidIntersectionsMode avoidIntersectionsMode = QgsProject::instance()->avoidIntersectionsMode();
+ if ( topologicalEditing && avoidIntersectionsMode == QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers &&
+ ( mode() == CaptureLine || mode() == CapturePolygon ) )
{
//use always topological editing for avoidIntersection.
@@ -91,7 +94,6 @@ void QgsMapToolAddFeature::digitized( const QgsFeature &f )
if ( topologicalEditing )
{
QList sm = snappingMatches();
- Q_ASSERT( f.geometry().constGet()->vertexCount() == sm.size() );
for ( int i = 0; i < sm.size() ; ++i )
{
if ( sm.at( i ).layer() )
diff --git a/src/app/qgsmaptooladdpart.cpp b/src/app/qgsmaptooladdpart.cpp
index e931289e0693..22d4ac4d8be5 100644
--- a/src/app/qgsmaptooladdpart.cpp
+++ b/src/app/qgsmaptooladdpart.cpp
@@ -159,7 +159,23 @@ void QgsMapToolAddPart::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
QgsCurvePolygon *cp = new QgsCurvePolygon();
cp->setExteriorRing( curveToAdd );
QgsGeometry *geom = new QgsGeometry( cp );
- geom->avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers() );
+
+ QList avoidIntersectionsLayers;
+ switch ( QgsProject::instance()->avoidIntersectionsMode() )
+ {
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
+ avoidIntersectionsLayers.append( vlayer );
+ break;
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers:
+ avoidIntersectionsLayers = QgsProject::instance()->avoidIntersectionsLayers();
+ break;
+ case QgsProject::AvoidIntersectionsMode::AllowIntersections:
+ break;
+ }
+ if ( avoidIntersectionsLayers.size() > 0 )
+ {
+ geom->avoidIntersections( avoidIntersectionsLayers );
+ }
const QgsCurvePolygon *cpGeom = qgsgeometry_cast( geom->constGet() );
if ( !cpGeom )
diff --git a/src/app/qgsmaptoolfillring.cpp b/src/app/qgsmaptoolfillring.cpp
index 8b5ccfc186f5..0a1cca0d2f0b 100644
--- a/src/app/qgsmaptoolfillring.cpp
+++ b/src/app/qgsmaptoolfillring.cpp
@@ -164,7 +164,11 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
}
else
{
- QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, &ft, false, nullptr, true );
+ QgsAttributeEditorContext context;
+ // don't set cadDockwidget in context because we don't want to be able to create geometries from this dialog
+ // there is one modified and one created feature, so it's a mess of we start to digitize a relation feature geometry
+ context.setVectorLayerTools( QgisApp::instance()->vectorLayerTools() );
+ QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, &ft, false, nullptr, true, context );
dialog->setMode( QgsAttributeEditorContext::AddFeatureMode );
res = dialog->exec(); // will also add the feature
}
diff --git a/src/app/qgsmaptoolreshape.cpp b/src/app/qgsmaptoolreshape.cpp
index e7a2f9bb95fe..a2beff5d2508 100644
--- a/src/app/qgsmaptoolreshape.cpp
+++ b/src/app/qgsmaptoolreshape.cpp
@@ -157,12 +157,27 @@ void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
QHash > ignoreFeatures;
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );
- if ( geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ) != 0 )
+ QList avoidIntersectionsLayers;
+ switch ( QgsProject::instance()->avoidIntersectionsMode() )
{
- emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::Critical );
- vlayer->destroyEditCommand();
- stopCapturing();
- return;
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
+ avoidIntersectionsLayers.append( vlayer );
+ break;
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers:
+ avoidIntersectionsLayers = QgsProject::instance()->avoidIntersectionsLayers();
+ break;
+ case QgsProject::AvoidIntersectionsMode::AllowIntersections:
+ break;
+ }
+ if ( avoidIntersectionsLayers.size() > 0 )
+ {
+ if ( geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ) != 0 )
+ {
+ emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::Critical );
+ vlayer->destroyEditCommand();
+ stopCapturing();
+ return;
+ }
}
if ( geom.isEmpty() ) //intersection removal might have removed the whole geometry
diff --git a/src/app/qgsmaptoolsplitparts.h b/src/app/qgsmaptoolsplitparts.h
index 0f5ca3065e4f..b8926db564a2 100644
--- a/src/app/qgsmaptoolsplitparts.h
+++ b/src/app/qgsmaptoolsplitparts.h
@@ -19,11 +19,11 @@
#include "qgsmaptoolcapture.h"
//! A map tool that draws a line and splits the parts cut by the line
-class QgsMapToolSplitParts: public QgsMapToolCapture
+class APP_EXPORT QgsMapToolSplitParts: public QgsMapToolCapture
{
Q_OBJECT
public:
- explicit QgsMapToolSplitParts( QgsMapCanvas *canvas );
+ QgsMapToolSplitParts( QgsMapCanvas *canvas );
void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) override;
};
diff --git a/src/app/qgsnewspatialitelayerdialog.cpp b/src/app/qgsnewspatialitelayerdialog.cpp
index 47f661f7d949..09f75a2288a2 100644
--- a/src/app/qgsnewspatialitelayerdialog.cpp
+++ b/src/app/qgsnewspatialitelayerdialog.cpp
@@ -22,12 +22,16 @@
#include "qgis.h"
#include "qgsapplication.h"
-#include "qgsproviderregistry.h"
+#include "qgsabstractdatabaseproviderconnection.h"
#include "qgisapp.h" // <- for theme icons
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include "qgscoordinatereferencesystem.h"
+#include "qgsfileutils.h"
#include "qgsprojectionselectiondialog.h"
+#include "qgsproviderconnectionmodel.h"
+#include "qgsprovidermetadata.h"
+#include "qgsproviderregistry.h"
#include "qgsspatialiteutils.h"
#include "qgslogger.h"
#include "qgssettings.h"
@@ -51,7 +55,7 @@ QgsNewSpatialiteLayerDialog::QgsNewSpatialiteLayerDialog( QWidget *parent, Qt::W
connect( mGeometryTypeBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsNewSpatialiteLayerDialog::mGeometryTypeBox_currentIndexChanged );
connect( mTypeBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsNewSpatialiteLayerDialog::mTypeBox_currentIndexChanged );
connect( pbnFindSRID, &QPushButton::clicked, this, &QgsNewSpatialiteLayerDialog::pbnFindSRID_clicked );
- connect( toolButtonNewDatabase, &QToolButton::clicked, this, &QgsNewSpatialiteLayerDialog::toolButtonNewDatabase_clicked );
+ connect( toolButtonNewDatabase, &QToolButton::clicked, this, &QgsNewSpatialiteLayerDialog::createDb );
connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsNewSpatialiteLayerDialog::buttonBox_accepted );
connect( buttonBox, &QDialogButtonBox::rejected, this, &QgsNewSpatialiteLayerDialog::buttonBox_rejected );
@@ -75,20 +79,7 @@ QgsNewSpatialiteLayerDialog::QgsNewSpatialiteLayerDialog( QWidget *parent, Qt::W
mTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFieldInteger.svg" ) ), tr( "Whole number" ), "integer" );
mTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFieldFloat.svg" ) ), tr( "Decimal number" ), "real" );
- // Populate the database list from the stored connections
- QgsSettings settings;
- settings.beginGroup( QStringLiteral( "SpatiaLite/connections" ) );
- QStringList keys = settings.childGroups();
- QStringList::Iterator it = keys.begin();
- mDatabaseComboBox->clear();
- while ( it != keys.end() )
- {
- // retrieving the SQLite DB name and full path
- QString text = settings.value( *it + "/sqlitepath", "###unknown###" ).toString();
- mDatabaseComboBox->addItem( text );
- ++it;
- }
- settings.endGroup();
+ mDatabaseComboBox->setProvider( QStringLiteral( "spatialite" ) );
mOkButton = buttonBox->button( QDialogButtonBox::Ok );
mOkButton->setEnabled( false );
@@ -132,26 +123,6 @@ void QgsNewSpatialiteLayerDialog::mTypeBox_currentIndexChanged( int index )
}
}
-void QgsNewSpatialiteLayerDialog::toolButtonNewDatabase_clicked()
-{
- QString fileName = QFileDialog::getSaveFileName( this, tr( "New SpatiaLite Database File" ),
- QDir::homePath(),
- tr( "SpatiaLite" ) + " (*.sqlite *.db *.sqlite3 *.db3 *.s3db)", nullptr, QFileDialog::DontConfirmOverwrite );
-
- if ( fileName.isEmpty() )
- return;
-
- if ( !fileName.endsWith( QLatin1String( ".sqlite" ), Qt::CaseInsensitive ) && !fileName.endsWith( QLatin1String( ".db" ), Qt::CaseInsensitive ) )
- {
- fileName += QLatin1String( ".sqlite" );
- }
-
- mDatabaseComboBox->insertItem( 0, fileName );
- mDatabaseComboBox->setCurrentIndex( 0 );
-
- createDb();
-}
-
QString QgsNewSpatialiteLayerDialog::selectedType() const
{
return mGeometryTypeBox->currentData( Qt::UserRole ).toString();
@@ -206,11 +177,14 @@ void QgsNewSpatialiteLayerDialog::mRemoveAttributeButton_clicked()
void QgsNewSpatialiteLayerDialog::pbnFindSRID_clicked()
{
+ const QgsDataSourceUri dbUri = mDatabaseComboBox->currentConnectionUri();
+ const QString dbPath = dbUri.database();
+
// first get list of supported SRID from the selected SpatiaLite database
// to build filter for projection selector
sqlite3_database_unique_ptr database;
bool status = true;
- int rc = database.open_v2( mDatabaseComboBox->currentText(), SQLITE_OPEN_READONLY, nullptr );
+ int rc = database.open_v2( dbPath, SQLITE_OPEN_READONLY, nullptr );
if ( rc != SQLITE_OK )
{
QMessageBox::warning( this, tr( "SpatiaLite Database" ), tr( "Unable to open the database" ) );
@@ -274,10 +248,15 @@ void QgsNewSpatialiteLayerDialog::selectionChanged()
bool QgsNewSpatialiteLayerDialog::createDb()
{
- QString dbPath = mDatabaseComboBox->currentText();
+ QString dbPath = QFileDialog::getSaveFileName( this, tr( "New SpatiaLite Database File" ),
+ QDir::homePath(),
+ tr( "SpatiaLite" ) + " (*.sqlite *.db *.sqlite3 *.db3 *.s3db)", nullptr, QFileDialog::DontConfirmOverwrite );
+
if ( dbPath.isEmpty() )
return false;
+ QgsFileUtils::ensureFileNameHasExtension( dbPath, QStringList() << QStringLiteral( ".sqlite" ) << QLatin1String( ".db" ) << QLatin1String( ".sqlite3" )
+ << QLatin1String( ".db3" ) << QLatin1String( ".s3db" ) );
QFile newDb( dbPath );
if ( newDb.exists() )
{
@@ -316,24 +295,21 @@ bool QgsNewSpatialiteLayerDialog::createDb()
if ( !fi.exists() )
{
pbnFindSRID->setEnabled( false );
- return false;
}
-
- QString key = "/SpatiaLite/connections/" + fi.fileName() + "/sqlitepath";
-
- QgsSettings settings;
- if ( !settings.contains( key ) )
+ else
{
- settings.setValue( QStringLiteral( "SpatiaLite/connections/selected" ), fi.fileName() + tr( "@" ) + fi.canonicalFilePath() );
- settings.setValue( key, fi.canonicalFilePath() );
-
- // Reload connections to refresh browser panel
- QgisApp::instance()->reloadConnections();
+ QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "spatialite" ) ) };
+ std::unique_ptr conn( static_cast( md->createConnection( QStringLiteral( "dbname='%1'" ).arg( dbPath ), QVariantMap() ) ) );
+ if ( conn )
+ {
+ md->saveConnection( conn.get(), fi.fileName() );
+ mDatabaseComboBox->setConnection( fi.fileName() );
+ pbnFindSRID->setEnabled( true );
+ return true;
+ }
}
- pbnFindSRID->setEnabled( true );
-
- return true;
+ return false;
}
void QgsNewSpatialiteLayerDialog::buttonBox_accepted()
@@ -349,6 +325,9 @@ void QgsNewSpatialiteLayerDialog::buttonBox_rejected()
bool QgsNewSpatialiteLayerDialog::apply()
{
+ const QgsDataSourceUri dbUri = mDatabaseComboBox->currentConnectionUri();
+ const QString dbPath = dbUri.database();
+
// Build up the sql statement for creating the table
QString sql = QStringLiteral( "create table %1(" ).arg( quotedIdentifier( leLayerName->text() ) );
QString delim;
@@ -369,16 +348,16 @@ bool QgsNewSpatialiteLayerDialog::apply()
// complete the create table statement
sql += ')';
- QgsDebugMsg( QStringLiteral( "Creating table in database %1" ).arg( mDatabaseComboBox->currentText() ) );
+ QgsDebugMsg( QStringLiteral( "Creating table in database %1" ).arg( dbPath ) );
QgsDebugMsg( sql );
spatialite_database_unique_ptr database;
- int rc = database.open( mDatabaseComboBox->currentText() );
+ int rc = database.open( dbPath );
if ( rc != SQLITE_OK )
{
QMessageBox::warning( this,
tr( "SpatiaLite Database" ),
- tr( "Unable to open the database: %1" ).arg( mDatabaseComboBox->currentText() ) );
+ tr( "Unable to open the database: %1" ).arg( dbPath ) );
return false;
}
@@ -433,8 +412,10 @@ bool QgsNewSpatialiteLayerDialog::apply()
}
const QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
- QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "dbname='%1' table='%2'%3 sql=" )
- .arg( mDatabaseComboBox->currentText(),
+ const QString uri = QStringLiteral( "dbname='%1' table='%2'%3 sql=" ).arg( dbPath, leLayerName->text(),
+ mGeometryTypeBox->currentIndex() != 0 ? QStringLiteral( "(%1)" ).arg( leGeometryColumn->text() ) : QString() );
+ QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "%1 table='%2'%3 sql=" )
+ .arg( mDatabaseComboBox->currentConnectionUri(),
leLayerName->text(),
mGeometryTypeBox->currentIndex() != 0 ? QStringLiteral( "(%1)" ).arg( leGeometryColumn->text() ) : QString() ),
leLayerName->text(), QStringLiteral( "spatialite" ), options );
diff --git a/src/app/qgsnewspatialitelayerdialog.h b/src/app/qgsnewspatialitelayerdialog.h
index 4b63cbfbf72f..5594fded4b44 100644
--- a/src/app/qgsnewspatialitelayerdialog.h
+++ b/src/app/qgsnewspatialitelayerdialog.h
@@ -44,7 +44,6 @@ class APP_EXPORT QgsNewSpatialiteLayerDialog: public QDialog, private Ui::QgsNew
void mGeometryTypeBox_currentIndexChanged( int index );
void mTypeBox_currentIndexChanged( int index );
void pbnFindSRID_clicked();
- void toolButtonNewDatabase_clicked();
void nameChanged( const QString & );
void selectionChanged();
void checkOk();
diff --git a/src/app/qgsoptions.cpp b/src/app/qgsoptions.cpp
index c27f18e7ec3e..f80a4b1d4a59 100644
--- a/src/app/qgsoptions.cpp
+++ b/src/app/qgsoptions.cpp
@@ -1196,9 +1196,18 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QListremoveItemWidget( mOptionsListWidget->findItems( tr( "Acceleration" ), Qt::MatchExactly ).first() );
- mOptionsStackedWidget->removeWidget( mOptionsPageAcceleration );
-
+ mGPUEnableCheckBox->setChecked( false );
+ for ( int idx = 0; idx < mOptionsPageAccelerationLayout->count(); ++idx )
+ {
+ QWidget *item = mOptionsPageAccelerationLayout->itemAt( idx )->widget();
+ if ( item )
+ {
+ item->setEnabled( false );
+ }
+ }
+ QLabel *noOpenCL = new QLabel( tr( "QGIS is compiled without OpenCL support. "
+ "GPU acceleration is not available." ), this );
+ mOptionsPageAccelerationLayout->insertWidget( 0, noOpenCL );
#endif
diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp
index fbaf5621e86f..0aa3158c58ad 100644
--- a/src/app/qgsprojectproperties.cpp
+++ b/src/app/qgsprojectproperties.cpp
@@ -70,6 +70,7 @@
#include "qgsbearingnumericformat.h"
#include "qgsprojectdisplaysettings.h"
#include "qgsprojecttimesettings.h"
+#include "qgstemporalutils.h"
//qt includes
#include
@@ -235,26 +236,12 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa
// Set time settings input
QgsDateTimeRange range = QgsProject::instance()->timeSettings()->temporalRange();
- QLocale locale;
- mStartDateTimeEdit->setDisplayFormat(
- locale.dateTimeFormat( QLocale::ShortFormat ) );
- mEndDateTimeEdit->setDisplayFormat(
- locale.dateTimeFormat( QLocale::ShortFormat ) );
+ mStartDateTimeEdit->setDisplayFormat( "yyyy-MM-dd HH:mm:ss" );
+ mEndDateTimeEdit->setDisplayFormat( "yyyy-MM-dd HH:mm:ss" );
- if ( range.begin().isValid() && range.end().isValid() )
- {
- mStartDateTimeEdit->setDateTime( range.begin() );
- mEndDateTimeEdit->setDateTime( range.end() );
-
- mCurrentRangeLabel->setText( tr( "Current selected range: %1 to %2" ).arg(
- mStartDateTimeEdit->dateTime().toString( locale.dateTimeFormat() ),
- mEndDateTimeEdit->dateTime().toString( locale.dateTimeFormat() ) ) );
- }
- else
- {
- mCurrentRangeLabel->setText( tr( "Project range is not set" ) );
- }
+ mStartDateTimeEdit->setDateTime( range.begin() );
+ mEndDateTimeEdit->setDateTime( range.end() );
mAutoTransaction->setChecked( QgsProject::instance()->autoTransaction() );
title( QgsProject::instance()->title() );
@@ -1045,7 +1032,7 @@ void QgsProjectProperties::apply()
QgsProject::instance()->setTrustLayerMetadata( mTrustProjectCheckBox->isChecked() );
// Time settings
- QDateTime start = mStartDateTimeEdit->dateTime();
+ QDateTime start = mStartDateTimeEdit->dateTime();
QDateTime end = mEndDateTimeEdit->dateTime();
QgsProject::instance()->timeSettings()->setTemporalRange( QgsDateTimeRange( start, end ) );
@@ -2510,54 +2497,9 @@ void QgsProjectProperties::mButtonAddColor_clicked()
void QgsProjectProperties::calculateFromLayersButton_clicked()
{
- const QMap &mapLayers = QgsProject::instance()->mapLayers();
- QgsMapLayer *currentLayer = nullptr;
-
- QDateTime minDate;
- QDateTime maxDate;
-
- for ( QMap::const_iterator it = mapLayers.constBegin(); it != mapLayers.constEnd(); ++it )
- {
- currentLayer = it.value();
-
- if ( !currentLayer->temporalProperties() || !currentLayer->temporalProperties()->isActive() )
- continue;
-
- if ( currentLayer->type() == QgsMapLayerType::RasterLayer )
- {
- QgsRasterLayer *rasterLayer = qobject_cast( currentLayer );
-
- QgsDateTimeRange layerRange;
- switch ( rasterLayer->temporalProperties()->mode() )
- {
- case QgsRasterLayerTemporalProperties::ModeFixedTemporalRange:
- layerRange = rasterLayer->temporalProperties()->fixedTemporalRange();
- break;
-
- case QgsRasterLayerTemporalProperties::ModeTemporalRangeFromDataProvider:
- layerRange = rasterLayer->dataProvider()->temporalCapabilities()->availableTemporalRange();
- break;
- }
-
- if ( !minDate.isValid() || layerRange.begin() < minDate )
- minDate = layerRange.begin();
- if ( !maxDate.isValid() || layerRange.end() > maxDate )
- maxDate = layerRange.end();
- }
- }
-
- if ( !minDate.isValid() || !maxDate.isValid() )
- return;
-
- mStartDateTimeEdit->setDateTime( minDate );
- mEndDateTimeEdit->setDateTime( maxDate );
-
- QLocale locale;
- mCurrentRangeLabel->setText( tr( "Current selected range: %1 to %2" ).arg(
- mStartDateTimeEdit->dateTime().toString(
- locale.dateTimeFormat( QLocale::ShortFormat ) ),
- mEndDateTimeEdit->dateTime().toString(
- locale.dateTimeFormat( QLocale::ShortFormat ) ) ) );
+ const QgsDateTimeRange range = QgsTemporalUtils::calculateTemporalRangeForProject( QgsProject::instance() );
+ mStartDateTimeEdit->setDateTime( range.begin() );
+ mEndDateTimeEdit->setDateTime( range.end() );
}
QListWidgetItem *QgsProjectProperties::addScaleToScaleList( const QString &newScale )
diff --git a/src/app/qgssnappinglayertreemodel.cpp b/src/app/qgssnappinglayertreemodel.cpp
index 1fb0e9732e37..a5a4af3d9cb7 100644
--- a/src/app/qgssnappinglayertreemodel.cpp
+++ b/src/app/qgssnappinglayertreemodel.cpp
@@ -27,6 +27,7 @@
#include "qgssnappingconfig.h"
#include "qgsvectorlayer.h"
#include "qgsapplication.h"
+#include "qgsscalewidget.h"
QgsSnappingLayerDelegate::QgsSnappingLayerDelegate( QgsMapCanvas *canvas, QObject *parent )
: QItemDelegate( parent )
@@ -100,6 +101,20 @@ QWidget *QgsSnappingLayerDelegate::createEditor( QWidget *parent, const QStyleOp
return w;
}
+ if ( index.column() == QgsSnappingLayerTreeModel::MinScaleColumn )
+ {
+ QgsScaleWidget *minLimitSp = new QgsScaleWidget( parent );
+ minLimitSp->setToolTip( tr( "Min Scale" ) );
+ return minLimitSp;
+ }
+
+ if ( index.column() == QgsSnappingLayerTreeModel::MaxScaleColumn )
+ {
+ QgsScaleWidget *maxLimitSp = new QgsScaleWidget( parent );
+ maxLimitSp->setToolTip( tr( "Max Scale" ) );
+ return maxLimitSp;
+ }
+
return nullptr;
}
@@ -141,6 +156,22 @@ void QgsSnappingLayerDelegate::setEditorData( QWidget *editor, const QModelIndex
w->setCurrentIndex( w->findData( units ) );
}
}
+ else if ( index.column() == QgsSnappingLayerTreeModel::MinScaleColumn )
+ {
+ QgsScaleWidget *w = qobject_cast( editor );
+ if ( w )
+ {
+ w->setScale( val.toDouble() );
+ }
+ }
+ else if ( index.column() == QgsSnappingLayerTreeModel::MaxScaleColumn )
+ {
+ QgsScaleWidget *w = qobject_cast( editor );
+ if ( w )
+ {
+ w->setScale( val.toDouble() );
+ }
+ }
}
void QgsSnappingLayerDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
@@ -183,6 +214,22 @@ void QgsSnappingLayerDelegate::setModelData( QWidget *editor, QAbstractItemModel
model->setData( index, w->value(), Qt::EditRole );
}
}
+ else if ( index.column() == QgsSnappingLayerTreeModel::MinScaleColumn )
+ {
+ QgsScaleWidget *w = qobject_cast( editor );
+ if ( w )
+ {
+ model->setData( index, w->scale(), Qt::EditRole );
+ }
+ }
+ else if ( index.column() == QgsSnappingLayerTreeModel::MaxScaleColumn )
+ {
+ QgsScaleWidget *w = qobject_cast( editor );
+ if ( w )
+ {
+ model->setData( index, w->scale(), Qt::EditRole );
+ }
+ }
}
@@ -191,6 +238,7 @@ QgsSnappingLayerTreeModel::QgsSnappingLayerTreeModel( QgsProject *project, QgsMa
, mProject( project )
, mCanvas( canvas )
, mIndividualLayerSettings( project->snappingConfig().individualLayerSettings() )
+ , mEnableMinMaxColumn( project->snappingConfig().scaleDependencyMode() == QgsSnappingConfig::PerLayer )
{
connect( project, &QgsProject::snappingConfigChanged, this, &QgsSnappingLayerTreeModel::onSnappingSettingsChanged );
@@ -200,7 +248,7 @@ QgsSnappingLayerTreeModel::QgsSnappingLayerTreeModel( QgsProject *project, QgsMa
int QgsSnappingLayerTreeModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent )
- return 5;
+ return 7;
}
Qt::ItemFlags QgsSnappingLayerTreeModel::flags( const QModelIndex &idx ) const
@@ -231,6 +279,13 @@ Qt::ItemFlags QgsSnappingLayerTreeModel::flags( const QModelIndex &idx ) const
return Qt::NoItemFlags;
}
}
+ else if ( idx.column() == MaxScaleColumn || idx.column() == MinScaleColumn )
+ {
+ if ( mEnableMinMaxColumn )
+ {
+ return Qt::ItemIsEnabled | Qt::ItemIsEditable;
+ }
+ }
else
{
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
@@ -290,6 +345,7 @@ void QgsSnappingLayerTreeModel::setFilterText( const QString &filterText )
void QgsSnappingLayerTreeModel::onSnappingSettingsChanged()
{
const QHash oldSettings = mIndividualLayerSettings;
+ bool wasMinMaxEnabled = mEnableMinMaxColumn;
for ( auto it = oldSettings.constBegin(); it != oldSettings.constEnd(); ++it )
{
@@ -313,17 +369,18 @@ void QgsSnappingLayerTreeModel::onSnappingSettingsChanged()
}
}
- hasRowchanged( mLayerTreeModel->rootGroup(), oldSettings );
+ mEnableMinMaxColumn = ( mProject->snappingConfig().scaleDependencyMode() == QgsSnappingConfig::PerLayer );
+ hasRowchanged( mLayerTreeModel->rootGroup(), oldSettings, wasMinMaxEnabled != mEnableMinMaxColumn );
}
-void QgsSnappingLayerTreeModel::hasRowchanged( QgsLayerTreeNode *node, const QHash &oldSettings )
+void QgsSnappingLayerTreeModel::hasRowchanged( QgsLayerTreeNode *node, const QHash &oldSettings, bool forceRefresh )
{
if ( node->nodeType() == QgsLayerTreeNode::NodeGroup )
{
const auto constChildren = node->children();
for ( QgsLayerTreeNode *child : constChildren )
{
- hasRowchanged( child, oldSettings );
+ hasRowchanged( child, oldSettings, forceRefresh );
}
}
else
@@ -334,7 +391,7 @@ void QgsSnappingLayerTreeModel::hasRowchanged( QgsLayerTreeNode *node, const QHa
{
emit dataChanged( QModelIndex(), idx );
}
- if ( oldSettings.value( vl ) != mProject->snappingConfig().individualLayerSettings().value( vl ) )
+ if ( oldSettings.value( vl ) != mProject->snappingConfig().individualLayerSettings().value( vl ) || forceRefresh )
{
mIndividualLayerSettings.insert( vl, mProject->snappingConfig().individualLayerSettings().value( vl ) );
emit dataChanged( idx, index( idx.row(), columnCount( idx ) - 1 ) );
@@ -400,6 +457,10 @@ QVariant QgsSnappingLayerTreeModel::headerData( int section, Qt::Orientation ori
return tr( "Units" );
case 4:
return tr( "Avoid overlap" );
+ case 5:
+ return tr( "Min Scale" );
+ case 6:
+ return tr( "Max Scale" );
default:
return QVariant();
}
@@ -579,6 +640,46 @@ QVariant QgsSnappingLayerTreeModel::data( const QModelIndex &idx, int role ) con
}
}
}
+
+ if ( idx.column() == MinScaleColumn )
+ {
+ if ( role == Qt::DisplayRole )
+ {
+ if ( ls.minimumScale() <= 0.0 )
+ {
+ return QString( tr( "not set" ) );
+ }
+ else
+ {
+ return QString::number( ls.minimumScale() );
+ }
+ }
+
+ if ( role == Qt::UserRole )
+ {
+ return ls.minimumScale();
+ }
+ }
+
+ if ( idx.column() == MaxScaleColumn )
+ {
+ if ( role == Qt::DisplayRole )
+ {
+ if ( ls.maximumScale() <= 0.0 )
+ {
+ return QString( tr( "not set" ) );
+ }
+ else
+ {
+ return QString::number( ls.maximumScale() );
+ }
+ }
+
+ if ( role == Qt::UserRole )
+ {
+ return ls.maximumScale();
+ }
+ }
}
return QVariant();
@@ -712,5 +813,47 @@ bool QgsSnappingLayerTreeModel::setData( const QModelIndex &index, const QVarian
}
}
+ if ( index.column() == MinScaleColumn && role == Qt::EditRole )
+ {
+ QgsVectorLayer *vl = vectorLayer( index );
+ if ( vl )
+ {
+ if ( !mIndividualLayerSettings.contains( vl ) )
+ return false;
+
+ QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl );
+ if ( !ls.valid() )
+ return false;
+
+ ls.setMinimumScale( value.toDouble() );
+ QgsSnappingConfig config = mProject->snappingConfig();
+ config.setIndividualLayerSettings( vl, ls );
+ mProject->setSnappingConfig( config );
+ emit dataChanged( index, index );
+ return true;
+ }
+ }
+
+ if ( index.column() == MaxScaleColumn && role == Qt::EditRole )
+ {
+ QgsVectorLayer *vl = vectorLayer( index );
+ if ( vl )
+ {
+ if ( !mIndividualLayerSettings.contains( vl ) )
+ return false;
+
+ QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl );
+ if ( !ls.valid() )
+ return false;
+
+ ls.setMaximumScale( value.toDouble() );
+ QgsSnappingConfig config = mProject->snappingConfig();
+ config.setIndividualLayerSettings( vl, ls );
+ mProject->setSnappingConfig( config );
+ emit dataChanged( index, index );
+ return true;
+ }
+ }
+
return false;
}
diff --git a/src/app/qgssnappinglayertreemodel.h b/src/app/qgssnappinglayertreemodel.h
index 0c38e6abd219..9be2c3711416 100644
--- a/src/app/qgssnappinglayertreemodel.h
+++ b/src/app/qgssnappinglayertreemodel.h
@@ -56,7 +56,9 @@ class APP_EXPORT QgsSnappingLayerTreeModel : public QSortFilterProxyModel
TypeColumn,
ToleranceColumn,
UnitsColumn,
- AvoidIntersectionColumn
+ AvoidIntersectionColumn,
+ MinScaleColumn,
+ MaxScaleColumn
};
QgsSnappingLayerTreeModel( QgsProject *project, QgsMapCanvas *canvas, QObject *parent = nullptr );
@@ -93,8 +95,9 @@ class APP_EXPORT QgsSnappingLayerTreeModel : public QSortFilterProxyModel
QString mFilterText;
QHash mIndividualLayerSettings;
QgsLayerTreeModel *mLayerTreeModel = nullptr;
+ bool mEnableMinMaxColumn = true;
- void hasRowchanged( QgsLayerTreeNode *node, const QHash &oldSettings );
+ void hasRowchanged( QgsLayerTreeNode *node, const QHash &oldSettings, bool forceRefresh );
};
#endif // QGSSNAPPINGLAYERTREEVIEW_H
diff --git a/src/app/qgssnappingwidget.cpp b/src/app/qgssnappingwidget.cpp
index 0ec635399719..8775fa530f07 100644
--- a/src/app/qgssnappingwidget.cpp
+++ b/src/app/qgssnappingwidget.cpp
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include "qgisapp.h"
#include "qgsapplication.h"
@@ -40,6 +41,7 @@
#include "qgssnappingwidget.h"
#include "qgsunittypes.h"
#include "qgssettings.h"
+#include "qgsscalewidget.h"
QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas, QWidget *parent )
@@ -111,6 +113,25 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
mEnabledAction->setObjectName( QStringLiteral( "EnableSnappingAction" ) );
connect( mEnabledAction, &QAction::toggled, this, &QgsSnappingWidget::enableSnapping );
+ // avoid intersection mode button
+ mAvoidIntersectionsModeButton = new QToolButton();
+ mAvoidIntersectionsModeButton->setToolTip( tr( "When avoid overlap is enabled, digitized features will be clipped to not overlapped existing ones." ) );
+ mAvoidIntersectionsModeButton->setPopupMode( QToolButton::InstantPopup );
+ QMenu *avoidIntersectionsModeMenu = new QMenu( tr( "Set Avoid Overlap Mode" ), this );
+ mAllowIntersectionsAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mActionAllowIntersections.svg" ) ), tr( "Allow Overlap" ), avoidIntersectionsModeMenu );
+ mAvoidIntersectionsCurrentLayerAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mActionAvoidIntersectionsCurrentLayer.svg" ) ), tr( "Avoid Overlap on Active Layer" ), avoidIntersectionsModeMenu );
+ mAvoidIntersectionsLayersAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mActionAvoidIntersectionsLayers.svg" ) ), tr( "Follow Advanced Configuration" ), avoidIntersectionsModeMenu );
+ avoidIntersectionsModeMenu->addAction( mAllowIntersectionsAction );
+ avoidIntersectionsModeMenu->addAction( mAvoidIntersectionsCurrentLayerAction );
+ avoidIntersectionsModeMenu->addAction( mAvoidIntersectionsLayersAction );
+ mAvoidIntersectionsModeButton->setMenu( avoidIntersectionsModeMenu );
+ mAvoidIntersectionsModeButton->setObjectName( QStringLiteral( "AvoidIntersectionsModeButton" ) );
+ if ( mDisplayMode == Widget )
+ {
+ mAvoidIntersectionsModeButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
+ }
+ connect( mAvoidIntersectionsModeButton, &QToolButton::triggered, this, &QgsSnappingWidget::avoidIntersectionsModeButtonTriggered );
+
// mode button
mModeButton = new QToolButton();
mModeButton->setToolTip( tr( "Snapping Mode" ) );
@@ -173,6 +194,35 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
mToleranceSpinBox->setObjectName( QStringLiteral( "SnappingToleranceSpinBox" ) );
connect( mToleranceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsSnappingWidget::changeTolerance );
+ mMinScaleWidget = new QgsScaleWidget();
+ mMinScaleWidget->setToolTip( tr( "Minimum scale from which snapping is enabled" ) );
+ mMinScaleWidget->setObjectName( QStringLiteral( "SnappingMinScaleSpinBox" ) );
+ connect( mMinScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsSnappingWidget::changeMinScale );
+
+ mMaxScaleWidget = new QgsScaleWidget();
+ mMaxScaleWidget->setToolTip( tr( "Maximum scale up to which snapping is enabled" ) );
+ mMaxScaleWidget->setObjectName( QStringLiteral( "SnappingMaxScaleSpinBox" ) );
+ connect( mMaxScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsSnappingWidget::changeMaxScale );
+
+
+ mSnappingScaleModeButton = new QToolButton();
+ mSnappingScaleModeButton->setToolTip( tr( "Snapping scale mode" ) );
+ mSnappingScaleModeButton->setPopupMode( QToolButton::InstantPopup );
+ QMenu *scaleModeMenu = new QMenu( tr( "Set snapping scale mode" ), this );
+ mDefaultSnappingScaleAct = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingOnScale.svg" ) ), tr( "Disabled" ), scaleModeMenu );
+ mDefaultSnappingScaleAct->setToolTip( tr( "Scale dependency disabled" ) );
+ mGlobalSnappingScaleAct = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingOnScale.svg" ) ), tr( "Global" ), scaleModeMenu );
+ mGlobalSnappingScaleAct->setToolTip( tr( "Scale dependency global" ) );
+ mPerLayerSnappingScaleAct = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingOnScale.svg" ) ), tr( "Per layer" ), scaleModeMenu );
+ mPerLayerSnappingScaleAct->setToolTip( tr( "Scale dependency per layer" ) );
+ scaleModeMenu->addAction( mDefaultSnappingScaleAct );
+ scaleModeMenu->addAction( mGlobalSnappingScaleAct );
+ scaleModeMenu->addAction( mPerLayerSnappingScaleAct );
+ mSnappingScaleModeButton->setMenu( scaleModeMenu );
+ mSnappingScaleModeButton->setObjectName( QStringLiteral( "SnappingScaleModeButton" ) );
+ mSnappingScaleModeButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
+ connect( mSnappingScaleModeButton, &QToolButton::triggered, this, &QgsSnappingWidget::snappingScaleModeTriggered );
+
// units
mUnitsComboBox = new QComboBox();
mUnitsComboBox->addItem( tr( "px" ), QgsTolerance::Pixels );
@@ -261,7 +311,9 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
mTypeAction = tb->addWidget( mTypeButton );
mToleranceAction = tb->addWidget( mToleranceSpinBox );
mUnitAction = tb->addWidget( mUnitsComboBox );
+
tb->addAction( mTopologicalEditingAction );
+ mAvoidIntersectionsModeAction = tb->addWidget( mAvoidIntersectionsModeButton );
tb->addAction( mIntersectionSnappingAction );
tb->addAction( mEnableTracingAction );
}
@@ -279,6 +331,10 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
layout->addWidget( mTypeButton );
layout->addWidget( mToleranceSpinBox );
layout->addWidget( mUnitsComboBox );
+ mSnappingScaleModeButton->setDefaultAction( mDefaultSnappingScaleAct );
+ layout->addWidget( mSnappingScaleModeButton );
+ layout->addWidget( mMinScaleWidget );
+ layout->addWidget( mMaxScaleWidget );
QToolButton *topoButton = new QToolButton();
topoButton->addAction( mTopologicalEditingAction );
@@ -286,6 +342,8 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
topoButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
layout->addWidget( topoButton );
+ layout->addWidget( mAvoidIntersectionsModeButton );
+
QToolButton *interButton = new QToolButton();
interButton->addAction( mIntersectionSnappingAction );
interButton->setDefaultAction( mIntersectionSnappingAction );
@@ -305,6 +363,7 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
// connect settings changed and map units changed to properly update the widget
connect( project, &QgsProject::snappingConfigChanged, this, &QgsSnappingWidget::projectSnapSettingsChanged );
connect( project, &QgsProject::topologicalEditingChanged, this, &QgsSnappingWidget::projectTopologicalEditingChanged );
+ connect( project, &QgsProject::avoidIntersectionsModeChanged, this, &QgsSnappingWidget::projectAvoidIntersectionModeChanged );
connect( mCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsSnappingWidget::updateToleranceDecimals );
// Slightly modify the config so the settings changed code doesn't early exit
@@ -401,12 +460,61 @@ void QgsSnappingWidget::projectSnapSettingsChanged()
mToleranceSpinBox->setValue( config.tolerance() );
}
+ if ( mMinScaleWidget->scale() != config.minimumScale() )
+ {
+ mMinScaleWidget->setScale( config.minimumScale() );
+ }
+
+ if ( mMaxScaleWidget->scale() != config.maximumScale() )
+ {
+ mMaxScaleWidget->setScale( config.maximumScale() );
+ }
+
+ if ( config.scaleDependencyMode() == QgsSnappingConfig::Disabled )
+ {
+ mSnappingScaleModeButton->setDefaultAction( mDefaultSnappingScaleAct );
+ }
+ else if ( config.scaleDependencyMode() == QgsSnappingConfig::Global )
+ {
+ mSnappingScaleModeButton->setDefaultAction( mGlobalSnappingScaleAct );
+ }
+ else if ( config.scaleDependencyMode() == QgsSnappingConfig::PerLayer )
+ {
+ mSnappingScaleModeButton->setDefaultAction( mPerLayerSnappingScaleAct );
+ }
+
if ( config.intersectionSnapping() != mIntersectionSnappingAction->isChecked() )
{
mIntersectionSnappingAction->setChecked( config.intersectionSnapping() );
}
toggleSnappingWidgets( config.enabled() );
+
+}
+
+void QgsSnappingWidget::projectAvoidIntersectionModeChanged()
+{
+ switch ( mProject->avoidIntersectionsMode() )
+ {
+ case QgsProject::AvoidIntersectionsMode::AllowIntersections:
+ mAvoidIntersectionsModeButton->setDefaultAction( mAllowIntersectionsAction );
+ mAllowIntersectionsAction->setChecked( true );
+ mAvoidIntersectionsCurrentLayerAction->setChecked( false );
+ mAvoidIntersectionsLayersAction->setChecked( false );
+ break;
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
+ mAvoidIntersectionsModeButton->setDefaultAction( mAvoidIntersectionsCurrentLayerAction );
+ mAllowIntersectionsAction->setChecked( false );
+ mAvoidIntersectionsCurrentLayerAction->setChecked( true );
+ mAvoidIntersectionsLayersAction->setChecked( false );
+ break;
+ case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers:
+ mAvoidIntersectionsModeButton->setDefaultAction( mAvoidIntersectionsLayersAction );
+ mAllowIntersectionsAction->setChecked( false );
+ mAvoidIntersectionsCurrentLayerAction->setChecked( false );
+ mAvoidIntersectionsLayersAction->setChecked( true );
+ break;
+ }
}
void QgsSnappingWidget::projectTopologicalEditingChanged()
@@ -429,7 +537,16 @@ void QgsSnappingWidget::toggleSnappingWidgets( bool enabled )
mModeButton->setEnabled( enabled );
mTypeButton->setEnabled( enabled );
mToleranceSpinBox->setEnabled( enabled );
+ mSnappingScaleModeButton->setEnabled( enabled );
+ mMinScaleWidget->setEnabled( enabled && mConfig.scaleDependencyMode() == QgsSnappingConfig::Global );
+ mMaxScaleWidget->setEnabled( enabled && mConfig.scaleDependencyMode() == QgsSnappingConfig::Global );
mUnitsComboBox->setEnabled( enabled );
+
+ if ( mEditAdvancedConfigAction )
+ {
+ mEditAdvancedConfigAction->setEnabled( enabled );
+ }
+
if ( mAdvancedConfigWidget )
{
mAdvancedConfigWidget->setEnabled( enabled );
@@ -444,6 +561,18 @@ void QgsSnappingWidget::changeTolerance( double tolerance )
mProject->setSnappingConfig( mConfig );
}
+void QgsSnappingWidget::changeMinScale( double minScale )
+{
+ mConfig.setMinimumScale( minScale );
+ mProject->setSnappingConfig( mConfig );
+}
+
+void QgsSnappingWidget::changeMaxScale( double maxScale )
+{
+ mConfig.setMaximumScale( maxScale );
+ mProject->setSnappingConfig( mConfig );
+}
+
void QgsSnappingWidget::changeUnit( int idx )
{
QgsTolerance::UnitType unit = static_cast( mUnitsComboBox->itemData( idx ).toInt() );
@@ -470,6 +599,33 @@ void QgsSnappingWidget::onSnappingTreeLayersChanged()
mLayerTreeView->resizeColumnToContents( 0 );
}
+void QgsSnappingWidget::avoidIntersectionsModeButtonTriggered( QAction *action )
+{
+ if ( action != mAllowIntersectionsAction &&
+ action != mAvoidIntersectionsCurrentLayerAction &&
+ action != mAvoidIntersectionsLayersAction )
+ {
+ return;
+ }
+
+ if ( action != mAvoidIntersectionsModeButton->defaultAction() )
+ {
+ mAvoidIntersectionsModeButton->setDefaultAction( action );
+ if ( action == mAllowIntersectionsAction )
+ {
+ mProject->setAvoidIntersectionsMode( QgsProject::AvoidIntersectionsMode::AllowIntersections );
+ }
+ else if ( action == mAvoidIntersectionsCurrentLayerAction )
+ {
+ mProject->setAvoidIntersectionsMode( QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer );
+ }
+ else if ( action == mAvoidIntersectionsLayersAction )
+ {
+ mProject->setAvoidIntersectionsMode( QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers );
+ }
+ }
+}
+
void QgsSnappingWidget::modeButtonTriggered( QAction *action )
{
if ( action != mAllLayersAction &&
@@ -529,6 +685,30 @@ void QgsSnappingWidget::typeButtonTriggered( QAction *action )
mProject->setSnappingConfig( mConfig );
}
+void QgsSnappingWidget::snappingScaleModeTriggered( QAction *action )
+{
+ mSnappingScaleModeButton->setDefaultAction( action );
+ QgsSnappingConfig::ScaleDependencyMode mode = mConfig.scaleDependencyMode();
+
+ if ( action == mDefaultSnappingScaleAct )
+ {
+ mode = QgsSnappingConfig::Disabled;
+ }
+ else if ( action == mGlobalSnappingScaleAct )
+ {
+ mode = QgsSnappingConfig::Global;
+ }
+ else if ( action == mPerLayerSnappingScaleAct )
+ {
+ mode = QgsSnappingConfig::PerLayer;
+ }
+
+ mMinScaleWidget->setEnabled( mode == QgsSnappingConfig::Global );
+ mMaxScaleWidget->setEnabled( mode == QgsSnappingConfig::Global );
+ mConfig.setScaleDependencyMode( mode );
+ mProject->setSnappingConfig( mConfig );
+}
+
void QgsSnappingWidget::updateToleranceDecimals()
{
if ( mConfig.units() == QgsTolerance::Pixels )
@@ -570,6 +750,9 @@ void QgsSnappingWidget::modeChanged()
{
mAdvancedConfigWidget->setVisible( advanced );
}
+ mSnappingScaleModeButton->setVisible( advanced );
+ mMinScaleWidget->setVisible( advanced );
+ mMaxScaleWidget->setVisible( advanced );
}
}
diff --git a/src/app/qgssnappingwidget.h b/src/app/qgssnappingwidget.h
index d2e1ea0ef1c4..42081e6edf6e 100644
--- a/src/app/qgssnappingwidget.h
+++ b/src/app/qgssnappingwidget.h
@@ -23,6 +23,7 @@ class QDoubleSpinBox;
class QFont;
class QToolButton;
class QTreeView;
+class QCheckBox;
class QgsDoubleSpinBox;
class QgsFloatingWidget;
@@ -31,6 +32,7 @@ class QgsLayerTreeNode;
class QgsLayerTreeView;
class QgsMapCanvas;
class QgsProject;
+class QgsScaleWidget;
#include "qgssnappingconfig.h"
@@ -91,6 +93,8 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
private slots:
void projectSnapSettingsChanged();
+ void projectAvoidIntersectionModeChanged();
+
void projectTopologicalEditingChanged();
void enableSnapping( bool checked );
@@ -100,6 +104,10 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
void changeTolerance( double tolerance );
+ void changeMinScale( double minScale );
+
+ void changeMaxScale( double maxScale );
+
void changeUnit( int idx );
void enableTopologicalEditing( bool enabled );
@@ -107,7 +115,9 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
void enableIntersectionSnapping( bool enabled );
void modeButtonTriggered( QAction *action );
+ void avoidIntersectionsModeButtonTriggered( QAction *action );
void typeButtonTriggered( QAction *action );
+ void snappingScaleModeTriggered( QAction *action );
//! number of decimals of the tolerance spin box depends on map units
void updateToleranceDecimals();
@@ -130,6 +140,11 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
QgsMapCanvas *mCanvas = nullptr;
QAction *mEnabledAction = nullptr;
+ QToolButton *mAvoidIntersectionsModeButton = nullptr;
+ QAction *mAvoidIntersectionsModeAction = nullptr; // hide widget does not work on toolbar, action needed
+ QAction *mAllowIntersectionsAction = nullptr;
+ QAction *mAvoidIntersectionsCurrentLayerAction = nullptr;
+ QAction *mAvoidIntersectionsLayersAction = nullptr;
QToolButton *mModeButton = nullptr;
QAction *mModeAction = nullptr; // hide widget does not work on toolbar, action needed
QAction *mAllLayersAction = nullptr;
@@ -144,7 +159,13 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
QAction *mCentroidAction = nullptr;
QAction *mMiddleAction = nullptr;
QDoubleSpinBox *mToleranceSpinBox = nullptr;
+ QgsScaleWidget *mMinScaleWidget = nullptr;
+ QgsScaleWidget *mMaxScaleWidget = nullptr;
QAction *mToleranceAction = nullptr; // hide widget does not work on toolbar, action needed
+ QToolButton *mSnappingScaleModeButton = nullptr;
+ QAction *mDefaultSnappingScaleAct = nullptr;
+ QAction *mGlobalSnappingScaleAct = nullptr;
+ QAction *mPerLayerSnappingScaleAct = nullptr;
QComboBox *mUnitsComboBox = nullptr;
QAction *mUnitAction = nullptr; // hide widget does not work on toolbar, action needed
QAction *mTopologicalEditingAction = nullptr;
diff --git a/src/app/qgsstatisticalsummarydockwidget.cpp b/src/app/qgsstatisticalsummarydockwidget.cpp
index 2afdace646ba..70e990848afc 100644
--- a/src/app/qgsstatisticalsummarydockwidget.cpp
+++ b/src/app/qgsstatisticalsummarydockwidget.cpp
@@ -34,7 +34,7 @@ typedef QList< QgsStatisticalSummary::Statistic > StatsList;
typedef QList< QgsStringStatisticalSummary::Statistic > StringStatsList;
typedef QList< QgsDateTimeStatisticalSummary::Statistic > DateTimeStatsList;
Q_GLOBAL_STATIC_WITH_ARGS( StatsList, sDisplayStats, ( {QgsStatisticalSummary::Count, QgsStatisticalSummary::Sum, QgsStatisticalSummary::Mean, QgsStatisticalSummary::Median, QgsStatisticalSummary::StDev, QgsStatisticalSummary::StDevSample, QgsStatisticalSummary::Min, QgsStatisticalSummary::Max, QgsStatisticalSummary::Range, QgsStatisticalSummary::Minority, QgsStatisticalSummary::Majority, QgsStatisticalSummary::Variety, QgsStatisticalSummary::FirstQuartile, QgsStatisticalSummary::ThirdQuartile, QgsStatisticalSummary::InterQuartileRange} ) )
-Q_GLOBAL_STATIC_WITH_ARGS( StringStatsList, sDisplayStringStats, ( {QgsStringStatisticalSummary::Count, QgsStringStatisticalSummary::CountDistinct, QgsStringStatisticalSummary::CountMissing, QgsStringStatisticalSummary::Min, QgsStringStatisticalSummary::Max, QgsStringStatisticalSummary::MinimumLength, QgsStringStatisticalSummary::MaximumLength} ) )
+Q_GLOBAL_STATIC_WITH_ARGS( StringStatsList, sDisplayStringStats, ( {QgsStringStatisticalSummary::Count, QgsStringStatisticalSummary::CountDistinct, QgsStringStatisticalSummary::CountMissing, QgsStringStatisticalSummary::Min, QgsStringStatisticalSummary::Max, QgsStringStatisticalSummary::Minority, QgsStringStatisticalSummary::Majority, QgsStringStatisticalSummary::MinimumLength, QgsStringStatisticalSummary::MaximumLength, QgsStringStatisticalSummary::MeanLength} ) )
Q_GLOBAL_STATIC_WITH_ARGS( DateTimeStatsList, sDisplayDateTimeStats, ( {QgsDateTimeStatisticalSummary::Count, QgsDateTimeStatisticalSummary::CountDistinct, QgsDateTimeStatisticalSummary::CountMissing, QgsDateTimeStatisticalSummary::Min, QgsDateTimeStatisticalSummary::Max, QgsDateTimeStatisticalSummary::Range} ) )
#define MISSING_VALUES -1
diff --git a/src/app/qgstemporalcontrollerdockwidget.cpp b/src/app/qgstemporalcontrollerdockwidget.cpp
new file mode 100644
index 000000000000..faceeb25e135
--- /dev/null
+++ b/src/app/qgstemporalcontrollerdockwidget.cpp
@@ -0,0 +1,37 @@
+/***************************************************************************
+ qgstemporalcontrollerdockwidget.cpp
+ ------------------------------
+ begin : February 2020
+ copyright : (C) 2020 by Samweli Mwakisambwe
+ email : samweli at kartoza 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 "qgstemporalcontrollerdockwidget.h"
+#include "qgstemporalcontrollerwidget.h"
+#include "qgspanelwidgetstack.h"
+
+QgsTemporalControllerDockWidget::QgsTemporalControllerDockWidget( const QString &name, QWidget *parent )
+ : QgsDockWidget( parent )
+{
+ setWindowTitle( name );
+ mControllerWidget = new QgsTemporalControllerWidget();
+ mControllerWidget->setDockMode( true );
+
+ QgsPanelWidgetStack *stack = new QgsPanelWidgetStack();
+ stack->setMainPanel( mControllerWidget );
+ setWidget( stack );
+}
+
+QgsTemporalController *QgsTemporalControllerDockWidget::temporalController()
+{
+ return mControllerWidget->temporalController();
+}
diff --git a/src/gui/qgstemporalmapsettingsdialog.h b/src/app/qgstemporalcontrollerdockwidget.h
similarity index 51%
rename from src/gui/qgstemporalmapsettingsdialog.h
rename to src/app/qgstemporalcontrollerdockwidget.h
index 220ed75783e9..df692e0e3bb8 100644
--- a/src/gui/qgstemporalmapsettingsdialog.h
+++ b/src/app/qgstemporalcontrollerdockwidget.h
@@ -1,7 +1,7 @@
/***************************************************************************
- qgstemporalmapsettingsdialog.h
+ qgstemporalcontrollerdockwidget.h
---------------
- begin : March 2020
+ begin : February 2020
copyright : (C) 2020 by Samweli Mwakisambwe
email : samweli at kartoza dot com
***************************************************************************/
@@ -15,39 +15,43 @@
* *
***************************************************************************/
-#ifndef QGSTEMPORALMAPSETTINGSDIALOG_H
-#define QGSTEMPORALMAPSETTINGSDIALOG_H
+#ifndef QGSTEMPORALCONTROLLERDOCKWIDGET_H
+#define QGSTEMPORALCONTROLLERDOCKWIDGET_H
-#include "ui_qgstemporalmapsettingsdialogbase.h"
-#include "qgis_gui.h"
+#include "qgsdockwidget.h"
+#include "qgis_app.h"
-class QgsTemporalMapSettingsWidget;
+class QgsTemporalControllerWidget;
+class QgsTemporalController;
-#define SIP_NO_FILE
-
-///@cond PRIVATE
-
-class GUI_EXPORT QgsTemporalMapSettingsDialog : public QDialog, private Ui::QgsTemporalMapSettingsDialogBase
+/**
+ * \ingroup app
+ * The QgsTemporalControllerDockWidget class
+ *
+ * \since QGIS 3.14
+ */
+class APP_EXPORT QgsTemporalControllerDockWidget : public QgsDockWidget
{
Q_OBJECT
public:
/**
- * Constructor for QgsTemporalMapSettingsDialog, with the specified \a parent widget.
+ * Constructor for QgsTemporalControllerDockWidget, with the specified \a parent widget.
*/
- QgsTemporalMapSettingsDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags() );
+ QgsTemporalControllerDockWidget( const QString &name, QWidget *parent SIP_TRANSFERTHIS = nullptr );
/**
- * Returns the widget used in getting settings input from user.
+ * Returns the temporal controller object used by this object in navigation.
+ *
+ * The dock widget retains ownership of the returned object.
*/
- QgsTemporalMapSettingsWidget *mapSettingsWidget();
+ QgsTemporalController *temporalController();
private:
- //! Widget for handling temporal map settings
- QgsTemporalMapSettingsWidget *mTemporalMapSettingsWidget = nullptr;
-};
+ QgsTemporalControllerWidget *mControllerWidget = nullptr;
-///@endcond
-#endif // QGSTEMPORALMAPSETTINGSDIALOG_H
+};
+
+#endif // QGSTEMPORALCONTROLLERDOCKWIDGET_H
diff --git a/src/app/vertextool/qgsvertexeditor.cpp b/src/app/vertextool/qgsvertexeditor.cpp
index be3fcc11164b..6e15f25a4a21 100644
--- a/src/app/vertextool/qgsvertexeditor.cpp
+++ b/src/app/vertextool/qgsvertexeditor.cpp
@@ -433,9 +433,10 @@ void QgsVertexEditor::updateVertexSelection( const QItemSelection &, const QItem
try
{
QgsRectangle transformedBbox = t.transform( *bbox );
- QgsRectangle canvasExtent = mCanvas->mapSettings().extent();
+ const QgsRectangle canvasExtent = mCanvas->mapSettings().visibleExtent();
transformedBbox.combineExtentWith( canvasExtent );
- mCanvas->setExtent( transformedBbox );
+ mCanvas->setExtent( transformedBbox, true );
+ mCanvas->refresh();
}
catch ( QgsCsException &cse )
{
diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp
index 52169dcbeefa..407cf6bb97af 100644
--- a/src/app/vertextool/qgsvertextool.cpp
+++ b/src/app/vertextool/qgsvertextool.cpp
@@ -769,7 +769,7 @@ QgsPointLocator::Match QgsVertexTool::snapToEditableLayer( QgsMapMouseEvent *e )
continue;
config.setIndividualLayerSettings( vlayer, QgsSnappingConfig::IndividualLayerSettings(
- vlayer == currentVlayer, static_cast( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ), tol, QgsTolerance::ProjectUnits ) );
+ vlayer == currentVlayer, static_cast( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ), tol, QgsTolerance::ProjectUnits, 0.0, 0.0 ) );
}
snapUtils->setConfig( config );
@@ -796,7 +796,7 @@ QgsPointLocator::Match QgsVertexTool::snapToEditableLayer( QgsMapMouseEvent *e )
continue;
config.setIndividualLayerSettings( vlayer, QgsSnappingConfig::IndividualLayerSettings(
- vlayer->isEditable(), static_cast( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ), tol, QgsTolerance::ProjectUnits ) );
+ vlayer->isEditable(), static_cast( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ), tol, QgsTolerance::ProjectUnits, 0.0, 0.0 ) );
}
snapUtils->setConfig( config );
diff --git a/src/auth/oauth2/qgsauthoauth2edit.cpp b/src/auth/oauth2/qgsauthoauth2edit.cpp
index ec5a7cdf6a9b..d2a6eacedce5 100644
--- a/src/auth/oauth2/qgsauthoauth2edit.cpp
+++ b/src/auth/oauth2/qgsauthoauth2edit.cpp
@@ -993,7 +993,7 @@ void QgsAuthOAuth2Edit::parseSoftwareStatement( const QString &path )
if ( !grantTypes.isEmpty( ) )
{
QString grantType = grantTypes[0];
- if ( grantType == QLatin1Literal( "authorization_code" ) )
+ if ( grantType == QLatin1String( "authorization_code" ) )
{
updateGrantFlow( static_cast( QgsAuthOAuth2Config::AuthCode ) );
}
@@ -1120,10 +1120,10 @@ void QgsAuthOAuth2Edit::registerSoftStatement( const QString ®istrationUrl )
QByteArray json = QJsonWrapper::toJson( QVariant( mSoftwareStatement ), &res, &errStr );
QNetworkRequest registerRequest( regUrl );
QgsSetRequestInitiatorClass( registerRequest, QStringLiteral( "QgsAuthOAuth2Edit" ) );
- registerRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1Literal( "application/json" ) );
+ registerRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1String( "application/json" ) );
QNetworkReply *registerReply;
// For testability: use GET if protocol is file://
- if ( regUrl.scheme() == QLatin1Literal( "file" ) )
+ if ( regUrl.scheme() == QLatin1String( "file" ) )
registerReply = QgsNetworkAccessManager::instance()->get( registerRequest );
else
registerReply = QgsNetworkAccessManager::instance()->post( registerRequest, json );
diff --git a/src/auth/oauth2/qgso2.cpp b/src/auth/oauth2/qgso2.cpp
index cc7131a7cdb8..3b01419add01 100644
--- a/src/auth/oauth2/qgso2.cpp
+++ b/src/auth/oauth2/qgso2.cpp
@@ -233,7 +233,7 @@ void QgsO2::link()
QUrl url( tokenUrl_ );
QNetworkRequest tokenRequest( url );
QgsSetRequestInitiatorClass( tokenRequest, QStringLiteral( "QgsO2" ) );
- tokenRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1Literal( "application/x-www-form-urlencoded" ) );
+ tokenRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1String( "application/x-www-form-urlencoded" ) );
QNetworkReply *tokenReply = getManager()->post( tokenRequest, payload );
connect( tokenReply, SIGNAL( finished() ), this, SLOT( onTokenReplyFinished() ), Qt::QueuedConnection );
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index d11d2b650d97..987144aa8dda 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -97,6 +97,7 @@ SET(QGIS_CORE_SRCS
layertree/qgslayertreeregistrybridge.cpp
layertree/qgslayertreeutils.cpp
layertree/qgslayertree.cpp
+ layertree/qgslegendpatchshape.cpp
metadata/qgsabstractmetadatabase.cpp
metadata/qgslayermetadata.cpp
@@ -108,6 +109,7 @@ SET(QGIS_CORE_SRCS
numericformats/qgsbearingnumericformat.cpp
numericformats/qgscurrencynumericformat.cpp
numericformats/qgsfallbacknumericformat.cpp
+ numericformats/qgsfractionnumericformat.cpp
numericformats/qgsnumericformat.cpp
numericformats/qgsnumericformatregistry.cpp
numericformats/qgspercentagenumericformat.cpp
@@ -154,6 +156,7 @@ SET(QGIS_CORE_SRCS
processing/models/qgsprocessingmodelchildparametersource.cpp
processing/models/qgsprocessingmodelcomment.cpp
processing/models/qgsprocessingmodelcomponent.cpp
+ processing/models/qgsprocessingmodelgroupbox.cpp
processing/models/qgsprocessingmodelparameter.cpp
processing/models/qgsprocessingmodeloutput.cpp
@@ -181,10 +184,13 @@ SET(QGIS_CORE_SRCS
providers/ogr/qgsogrtransaction.cpp
scalebar/qgsdoubleboxscalebarrenderer.cpp
+ scalebar/qgshollowscalebarrenderer.cpp
scalebar/qgsnumericscalebarrenderer.cpp
scalebar/qgsscalebarrenderer.cpp
+ scalebar/qgsscalebarrendererregistry.cpp
scalebar/qgsscalebarsettings.cpp
scalebar/qgssingleboxscalebarrenderer.cpp
+ scalebar/qgssteppedlinescalebarrenderer.cpp
scalebar/qgsticksscalebarrenderer.cpp
qgis.cpp
@@ -219,6 +225,7 @@ SET(QGIS_CORE_SRCS
qgscolorscheme.cpp
qgscolorschemeregistry.cpp
qgsconditionalstyle.cpp
+ qgsconnectionregistry.cpp
qgscoordinateformatter.cpp
qgscoordinatereferencesystem.cpp
qgscoordinatetransform.cpp
@@ -308,6 +315,7 @@ SET(QGIS_CORE_SRCS
qgsmapunitscale.cpp
qgsmargins.cpp
qgsmaskidprovider.cpp
+ qgsmbtilesreader.cpp
qgsmessagelog.cpp
qgsmessageoutput.cpp
qgsmimedatautils.cpp
@@ -353,6 +361,7 @@ SET(QGIS_CORE_SRCS
qgsproviderconnectionmodel.cpp
qgsprovidermetadata.cpp
qgsproviderregistry.cpp
+ qgsproxyfeaturesink.cpp
qgsproxyprogresstask.cpp
qgspythonrunner.cpp
qgsreadwritecontext.cpp
@@ -361,6 +370,7 @@ SET(QGIS_CORE_SRCS
qgsrelationcontext.cpp
qgsweakrelation.cpp
qgsrelationmanager.cpp
+ qgsremappingproxyfeaturesink.cpp
qgsrenderchecker.cpp
qgsrendercontext.cpp
qgsrunprocess.cpp
@@ -387,9 +397,11 @@ SET(QGIS_CORE_SRCS
qgstemporalnavigationobject.cpp
qgstemporalproperty.cpp
qgstemporalrangeobject.cpp
+ qgstemporalutils.cpp
qgstessellator.cpp
qgstextrenderer.cpp
qgstilecache.cpp
+ qgstiles.cpp
qgstolerance.cpp
qgstracer.cpp
qgstranslationcontext.cpp
@@ -458,6 +470,7 @@ SET(QGIS_CORE_SRCS
layout/qgslayoutitemmapgrid.cpp
layout/qgslayoutitemmapitem.cpp
layout/qgslayoutitemmapoverview.cpp
+ layout/qgslayoutitemmarker.cpp
layout/qgslayoutitemnodeitem.cpp
layout/qgslayoutitempage.cpp
layout/qgslayoutitempicture.cpp
@@ -474,6 +487,7 @@ SET(QGIS_CORE_SRCS
layout/qgslayoutmodel.cpp
layout/qgslayoutmultiframe.cpp
layout/qgslayoutmultiframeundocommand.cpp
+ layout/qgslayoutnortharrowhandler.cpp
layout/qgslayoutobject.cpp
layout/qgslayoutpagecollection.cpp
layout/qgslayoutrendercontext.cpp
@@ -516,6 +530,7 @@ SET(QGIS_CORE_SRCS
raster/qgsraster.cpp
raster/qgsrasterblock.cpp
raster/qgsrasterchecker.cpp
+ raster/qgsrastercontourrenderer.cpp
raster/qgsrasterdataprovider.cpp
raster/qgsrasterdataprovidertemporalcapabilities.cpp
raster/qgsrasterfilewritertask.cpp
@@ -552,6 +567,7 @@ SET(QGIS_CORE_SRCS
mesh/qgsmesh3daveraging.cpp
mesh/qgsmeshdataprovider.cpp
+ mesh/qgsmeshdataset.cpp
mesh/qgsmeshlayer.cpp
mesh/qgsmeshlayerinterpolator.cpp
mesh/qgsmeshlayerrenderer.cpp
@@ -563,6 +579,8 @@ SET(QGIS_CORE_SRCS
mesh/qgsmeshtimesettings.cpp
mesh/qgsmeshtracerenderer.cpp
mesh/qgsmeshsimplificationsettings.cpp
+ mesh/qgsmeshlayertemporalproperties.cpp
+ mesh/qgsmeshdataprovidertemporalcapabilities.cpp
labeling/qgslabelfeature.cpp
labeling/qgslabelingengine.cpp
@@ -630,6 +648,16 @@ SET(QGIS_CORE_SRCS
validity/qgsvaliditycheckcontext.cpp
validity/qgsvaliditycheckregistry.cpp
+ vectortile/qgsvectortilebasicrenderer.cpp
+ vectortile/qgsvectortileconnection.cpp
+ vectortile/qgsvectortiledataitems.cpp
+ vectortile/qgsvectortilelayer.cpp
+ vectortile/qgsvectortilelayerrenderer.cpp
+ vectortile/qgsvectortileloader.cpp
+ vectortile/qgsvectortilemvtdecoder.cpp
+ vectortile/qgsvectortileprovidermetadata.cpp
+ vectortile/qgsvectortileutils.cpp
+
${CMAKE_CURRENT_BINARY_DIR}/qgsexpression_texts.cpp
qgsuserprofile.cpp
@@ -725,6 +753,7 @@ SET(QGIS_CORE_HDRS
qgscolorschemeregistry.h
qgsconditionalstyle.h
qgsconnectionpool.h
+ qgsconnectionregistry.h
qgscoordinateformatter.h
qgscoordinatereferencesystem.h
qgscoordinatetransform.h
@@ -775,6 +804,7 @@ SET(QGIS_CORE_HDRS
qgsfieldproxymodel.h
qgsfields.h
qgsfiledownloader.h
+ qgsfilefiltergenerator.h
qgsfileutils.h
qgsfontutils.h
qgsgdalutils.h
@@ -826,6 +856,7 @@ SET(QGIS_CORE_HDRS
qgsmapunitscale.h
qgsmargins.h
qgsmaskidprovider.h
+ qgsmbtilesreader.h
qgsmessagelog.h
qgsmessageoutput.h
qgsmimedatautils.h
@@ -873,6 +904,7 @@ SET(QGIS_CORE_HDRS
qgsproviderconnectionmodel.h
qgsprovidermetadata.h
qgsproviderregistry.h
+ qgsproxyfeaturesink.h
qgsproxyprogresstask.h
qgspythonrunner.h
qgsrange.h
@@ -880,6 +912,7 @@ SET(QGIS_CORE_HDRS
qgsreadwritelocker.h
qgsrelation.h
qgsrelationcontext.h
+ qgsremappingproxyfeaturesink.h
qgsweakrelation.h
qgsrelationmanager.h
qgsrenderchecker.h
@@ -911,11 +944,13 @@ SET(QGIS_CORE_HDRS
qgstemporalnavigationobject.h
qgstemporalproperty.h
qgstemporalrangeobject.h
+ qgstemporalutils.h
qgstessellator.h
qgstestutils.h
qgstextrenderer.h
qgsthreadingutils.h
qgstilecache.h
+ qgstiles.h
qgstolerance.h
qgstracer.h
qgstrackedvectorlayertools.h
@@ -1086,6 +1121,8 @@ SET(QGIS_CORE_HDRS
layertree/qgslayertreenode.h
layertree/qgslayertreeregistrybridge.h
layertree/qgslayertreeutils.h
+ layertree/qgslegendpatchshape.h
+
layout/qgsabstractlayoutiterator.h
layout/qgsabstractreportsection.h
layout/qgscompositionconverter.h
@@ -1110,6 +1147,7 @@ SET(QGIS_CORE_HDRS
layout/qgslayoutitemmapgrid.h
layout/qgslayoutitemmapitem.h
layout/qgslayoutitemmapoverview.h
+ layout/qgslayoutitemmarker.h
layout/qgslayoutitemnodeitem.h
layout/qgslayoutitempage.h
layout/qgslayoutitempicture.h
@@ -1126,6 +1164,7 @@ SET(QGIS_CORE_HDRS
layout/qgslayoutmodel.h
layout/qgslayoutmultiframe.h
layout/qgslayoutmultiframeundocommand.h
+ layout/qgslayoutnortharrowhandler.h
layout/qgslayoutobject.h
layout/qgslayoutpagecollection.h
layout/qgslayoutpoint.h
@@ -1154,6 +1193,7 @@ SET(QGIS_CORE_HDRS
mesh/qgsmesh3daveraging.h
mesh/qgsmeshdataprovider.h
+ mesh/qgsmeshdataset.h
mesh/qgsmeshlayer.h
mesh/qgsmeshlayerinterpolator.h
mesh/qgsmeshlayerrenderer.h
@@ -1165,6 +1205,8 @@ SET(QGIS_CORE_HDRS
mesh/qgstriangularmesh.h
mesh/qgsmeshtracerenderer.h
mesh/qgsmeshsimplificationsettings.h
+ mesh/qgsmeshlayertemporalproperties.h
+ mesh/qgsmeshdataprovidertemporalcapabilities.h
metadata/qgsabstractmetadatabase.h
metadata/qgslayermetadata.h
@@ -1176,6 +1218,7 @@ SET(QGIS_CORE_HDRS
numericformats/qgsbearingnumericformat.h
numericformats/qgscurrencynumericformat.h
numericformats/qgsfallbacknumericformat.h
+ numericformats/qgsfractionnumericformat.h
numericformats/qgsnumericformat.h
numericformats/qgsnumericformatregistry.h
numericformats/qgspercentagenumericformat.h
@@ -1186,6 +1229,7 @@ SET(QGIS_CORE_HDRS
processing/models/qgsprocessingmodelchildparametersource.h
processing/models/qgsprocessingmodelcomment.h
processing/models/qgsprocessingmodelcomponent.h
+ processing/models/qgsprocessingmodelgroupbox.h
processing/models/qgsprocessingmodeloutput.h
processing/models/qgsprocessingmodelparameter.h
processing/qgsprocessing.h
@@ -1234,6 +1278,7 @@ SET(QGIS_CORE_HDRS
raster/qgsrasterbandstats.h
raster/qgsrasterblock.h
raster/qgsrasterchecker.h
+ raster/qgsrastercontourrenderer.h
raster/qgsrasterdataprovider.h
raster/qgsrasterdataprovidertemporalcapabilities.h
raster/qgsrasterdrawer.h
@@ -1265,10 +1310,13 @@ SET(QGIS_CORE_HDRS
raster/qgssinglebandpseudocolorrenderer.h
scalebar/qgsdoubleboxscalebarrenderer.h
+ scalebar/qgshollowscalebarrenderer.h
scalebar/qgsnumericscalebarrenderer.h
scalebar/qgsscalebarrenderer.h
+ scalebar/qgsscalebarrendererregistry.h
scalebar/qgsscalebarsettings.h
scalebar/qgssingleboxscalebarrenderer.h
+ scalebar/qgssteppedlinescalebarrenderer.h
scalebar/qgsticksscalebarrenderer.h
symbology/qgs25drenderer.h
@@ -1308,6 +1356,17 @@ SET(QGIS_CORE_HDRS
validity/qgsabstractvaliditycheck.h
validity/qgsvaliditycheckcontext.h
validity/qgsvaliditycheckregistry.h
+
+ vectortile/qgsvectortilebasicrenderer.h
+ vectortile/qgsvectortileconnection.h
+ vectortile/qgsvectortiledataitems.h
+ vectortile/qgsvectortilelayer.h
+ vectortile/qgsvectortilelayerrenderer.h
+ vectortile/qgsvectortileloader.h
+ vectortile/qgsvectortilemvtdecoder.h
+ vectortile/qgsvectortileprovidermetadata.h
+ vectortile/qgsvectortilerenderer.h
+ vectortile/qgsvectortileutils.h
)
SET(QGIS_CORE_PRIVATE_HDRS
@@ -1351,6 +1410,18 @@ IF(NOT MSVC)
ENDIF ()
ENDIF(NOT MSVC)
+# Generate cpp+header file from .proto file using "protoc" tool (to support MVT encoding of vector tiles)
+protobuf_generate_cpp(VECTOR_TILE_PROTO_SRCS VECTOR_TILE_PROTO_HDRS vectortile/vector_tile.proto)
+SET(QGIS_CORE_SRCS ${QGIS_CORE_SRCS} ${VECTOR_TILE_PROTO_SRCS})
+SET(QGIS_CORE_HDRS ${QGIS_CORE_HDRS} ${VECTOR_TILE_PROTO_HDRS})
+IF (MSVC)
+ SET_SOURCE_FILES_PROPERTIES(${VECTOR_TILE_PROTO_SRCS} vectortile/qgsvectortilemvtdecoder.cpp PROPERTIES COMPILE_DEFINITIONS PROTOBUF_USE_DLLS)
+ELSE (MSVC)
+ # automatically generated file produces warnings (unused-parameter, unused-variable, misleading-indentation)
+ SET_SOURCE_FILES_PROPERTIES(${VECTOR_TILE_PROTO_SRCS} PROPERTIES COMPILE_FLAGS -w)
+ENDIF (MSVC)
+
+
# install headers
# install qgsconfig.h and plugin.h here so they can get into
# the OS X framework target
@@ -1368,10 +1439,11 @@ INCLUDE_DIRECTORIES(
3d
annotations
auth
+ callouts
classification
dxf
effects
- ${CMAKE_SOURCE_DIR}/src/core/expression
+ expression
fieldformatter
geometry
geocms
@@ -1396,6 +1468,7 @@ INCLUDE_DIRECTORIES(
symbology
mesh
validity
+ vectortile
${CMAKE_SOURCE_DIR}/external
${CMAKE_SOURCE_DIR}/external/nlohmann
${CMAKE_SOURCE_DIR}/external/kdbush/include
@@ -1403,6 +1476,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/external/poly2tri
${CMAKE_SOURCE_DIR}/external/rtree/include
${CMAKE_SOURCE_DIR}/external/meshOptimizer
+ ${CMAKE_SOURCE_DIR}/external/mapbox-vector-tile
)
INCLUDE_DIRECTORIES(SYSTEM
@@ -1417,6 +1491,8 @@ INCLUDE_DIRECTORIES(SYSTEM
${QCA_INCLUDE_DIR}
${QTKEYCHAIN_INCLUDE_DIR}
${Qt5SerialPort_INCLUDE_DIRS}
+ ${Protobuf_INCLUDE_DIRS}
+ ${ZLIB_INCLUDE_DIRS}
)
@@ -1551,6 +1627,8 @@ TARGET_LINK_LIBRARIES(qgis_core
${SQLITE3_LIBRARY}
${SPATIALITE_LIBRARY}
${LIBZIP_LIBRARY}
+ ${Protobuf_LITE_LIBRARY}
+ ${ZLIB_LIBRARIES}
)
IF (FORCE_STATIC_PROVIDERS)
diff --git a/src/core/expression/qgsexpression.cpp b/src/core/expression/qgsexpression.cpp
index c76efdea3034..14002a1b8141 100644
--- a/src/core/expression/qgsexpression.cpp
+++ b/src/core/expression/qgsexpression.cpp
@@ -700,6 +700,12 @@ void QgsExpression::initVariableHelp()
sVariableHelpTexts()->insert( QStringLiteral( "project_home" ), QCoreApplication::translate( "variable_help", "Home path of current project." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_crs" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of project (e.g., 'EPSG:4326')." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of project (full definition)." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "project_units" ), QCoreApplication::translate( "variable_help", "Unit of the project's CRS." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "project_crs_description" ), QCoreApplication::translate( "variable_help", "Name of the coordinate reference system of the project." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "project_crs_acronym" ), QCoreApplication::translate( "variable_help", "Acronym of the coordinate reference system of the project." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "project_crs_ellipsoid" ), QCoreApplication::translate( "variable_help", "Acronym of the ellipsoid of the coordinate reference system of the project." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "project_crs_proj4" ), QCoreApplication::translate( "variable_help", "Proj4 definition of the coordinate reference system of the project." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "project_crs_wkt" ), QCoreApplication::translate( "variable_help", "WKT definition of the coordinate reference system of the project." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_author" ), QCoreApplication::translate( "variable_help", "Project author, taken from project metadata." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_abstract" ), QCoreApplication::translate( "variable_help", "Project abstract, taken from project metadata." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_creation_date" ), QCoreApplication::translate( "variable_help", "Project creation date, taken from project metadata." ) );
@@ -708,6 +714,8 @@ void QgsExpression::initVariableHelp()
sVariableHelpTexts()->insert( QStringLiteral( "project_area_units" ), QCoreApplication::translate( "variable_help", "Area unit for current project, used when calculating areas of geometries." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_distance_units" ), QCoreApplication::translate( "variable_help", "Distance unit for current project, used when calculating lengths of geometries." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_ellipsoid" ), QCoreApplication::translate( "variable_help", "Name of ellipsoid of current project, used when calculating geodetic areas and lengths of geometries." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "layer_ids" ), QCoreApplication::translate( "variable_help", "List of all map layer IDs from the current project." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "layers" ), QCoreApplication::translate( "variable_help", "List of all map layers from the current project." ) );
//layer variables
sVariableHelpTexts()->insert( QStringLiteral( "layer_name" ), QCoreApplication::translate( "variable_help", "Name of current layer." ) );
@@ -752,11 +760,11 @@ void QgsExpression::initVariableHelp()
sVariableHelpTexts()->insert( QStringLiteral( "map_crs" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of map (e.g., 'EPSG:4326')." ) );
sVariableHelpTexts()->insert( QStringLiteral( "map_crs_description" ), QCoreApplication::translate( "variable_help", "Name of the coordinate reference system of the map." ) );
sVariableHelpTexts()->insert( QStringLiteral( "map_units" ), QCoreApplication::translate( "variable_help", "Units for map measurements." ) );
- sVariableHelpTexts()->insert( QStringLiteral( "map_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of map (full definition)." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "map_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of the map (full definition)." ) );
sVariableHelpTexts()->insert( QStringLiteral( "map_crs_acronym" ), QCoreApplication::translate( "variable_help", "Acronym of the coordinate reference system of the map." ) );
sVariableHelpTexts()->insert( QStringLiteral( "map_crs_ellipsoid" ), QCoreApplication::translate( "variable_help", "Acronym of the ellipsoid of the coordinate reference system of the map." ) );
- sVariableHelpTexts()->insert( QStringLiteral( "map_crs_proj4" ), QCoreApplication::translate( "variable_help", "Proj4 definition of the coordinate reference system." ) );
- sVariableHelpTexts()->insert( QStringLiteral( "map_crs_wkt" ), QCoreApplication::translate( "variable_help", "WKT definition of the coordinate reference system." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "map_crs_proj4" ), QCoreApplication::translate( "variable_help", "Proj4 definition of the coordinate reference system of the map." ) );
+ sVariableHelpTexts()->insert( QStringLiteral( "map_crs_wkt" ), QCoreApplication::translate( "variable_help", "WKT definition of the coordinate reference system of the map." ) );
sVariableHelpTexts()->insert( QStringLiteral( "map_layer_ids" ), QCoreApplication::translate( "variable_help", "List of map layer IDs visible in the map." ) );
sVariableHelpTexts()->insert( QStringLiteral( "map_layers" ), QCoreApplication::translate( "variable_help", "List of map layers visible in the map." ) );
diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp
index 49f044d9237a..760a5ee58f68 100644
--- a/src/core/expression/qgsexpressionfunction.cpp
+++ b/src/core/expression/qgsexpressionfunction.cpp
@@ -57,6 +57,7 @@
#include "qgsapplication.h"
#include "qgis.h"
#include "qgsexpressioncontextutils.h"
+#include "qgsunittypes.h"
typedef QList ExpressionFunctionList;
@@ -272,6 +273,12 @@ static QVariant fcnGetVariable( const QVariantList &values, const QgsExpressionC
return context->variable( name );
}
+static QVariant fcnEvalTemplate( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
+{
+ QString templateString = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
+ return QgsExpression::replaceExpressionText( templateString, context );
+}
+
static QVariant fcnEval( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
if ( !context )
@@ -1513,6 +1520,96 @@ static QVariant fcnAttributes( const QVariantList &values, const QgsExpressionCo
return result;
}
+static QVariant fcnCoreFeatureMaptipDisplay( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const bool isMaptip )
+{
+ QgsVectorLayer *layer = nullptr;
+ QgsFeature feature;
+ bool evaluate = true;
+
+ if ( values.isEmpty() )
+ {
+ feature = context->feature();
+ layer = QgsExpressionUtils::getVectorLayer( context->variable( QStringLiteral( "layer" ) ), parent );
+ }
+ else if ( values.size() == 1 )
+ {
+ layer = QgsExpressionUtils::getVectorLayer( context->variable( QStringLiteral( "layer" ) ), parent );
+ feature = QgsExpressionUtils::getFeature( values.at( 0 ), parent );
+ }
+ else if ( values.size() == 2 )
+ {
+ layer = QgsExpressionUtils::getVectorLayer( values.at( 0 ), parent );
+ feature = QgsExpressionUtils::getFeature( values.at( 1 ), parent );
+ }
+ else if ( values.size() == 3 )
+ {
+ layer = QgsExpressionUtils::getVectorLayer( values.at( 0 ), parent );
+ feature = QgsExpressionUtils::getFeature( values.at( 1 ), parent );
+ evaluate = values.value( 2 ).toBool();
+ }
+ else
+ {
+ if ( isMaptip )
+ {
+ parent->setEvalErrorString( QObject::tr( "Function `maptip` requires no more than three parameters. %1 given." ).arg( values.length() ) );
+ }
+ else
+ {
+ parent->setEvalErrorString( QObject::tr( "Function `display` requires no more than three parameters. %1 given." ).arg( values.length() ) );
+ }
+ return QVariant();
+ }
+
+ if ( !layer )
+ {
+ parent->setEvalErrorString( QObject::tr( "The layer is not valid." ) );
+ return QVariant( );
+ }
+
+ if ( !feature.isValid() )
+ {
+ parent->setEvalErrorString( QObject::tr( "The feature is not valid." ) );
+ return QVariant( );
+ }
+
+ if ( ! evaluate )
+ {
+ if ( isMaptip )
+ {
+ return layer->mapTipTemplate();
+ }
+ else
+ {
+ return layer->displayExpression();
+ }
+ }
+
+ QgsExpressionContext subContext( *context );
+ subContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
+ subContext.setFeature( feature );
+
+ if ( isMaptip )
+ {
+ return QgsExpression::replaceExpressionText( layer->mapTipTemplate(), &subContext );
+ }
+ else
+ {
+ QgsExpression exp( layer->displayExpression() );
+ exp.prepare( &subContext );
+ return exp.evaluate( &subContext ).toString();
+ }
+}
+
+static QVariant fcnFeatureDisplayExpression( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
+{
+ return fcnCoreFeatureMaptipDisplay( values, context, parent, false );
+}
+
+static QVariant fcnFeatureMaptip( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
+{
+ return fcnCoreFeatureMaptipDisplay( values, context, parent, true );
+}
+
static QVariant fcnIsSelected( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsVectorLayer *layer = nullptr;
@@ -4675,6 +4772,8 @@ static QVariant fcnGetLayerProperty( const QVariantList &values, const QgsExpres
return layer->minimumScale();
else if ( QString::compare( layerProperty, QStringLiteral( "max_scale" ), Qt::CaseInsensitive ) == 0 )
return layer->maximumScale();
+ else if ( QString::compare( layerProperty, QStringLiteral( "is_editable" ), Qt::CaseInsensitive ) == 0 )
+ return layer->isEditable();
else if ( QString::compare( layerProperty, QStringLiteral( "crs" ), Qt::CaseInsensitive ) == 0 )
return layer->crs().authid();
else if ( QString::compare( layerProperty, QStringLiteral( "crs_definition" ), Qt::CaseInsensitive ) == 0 )
@@ -4687,6 +4786,8 @@ static QVariant fcnGetLayerProperty( const QVariantList &values, const QgsExpres
QVariant result = QVariant::fromValue( extentGeom );
return result;
}
+ else if ( QString::compare( layerProperty, QStringLiteral( "distance_units" ), Qt::CaseInsensitive ) == 0 )
+ return QgsUnitTypes::encodeUnit( layer->crs().mapUnits() );
else if ( QString::compare( layerProperty, QStringLiteral( "type" ), Qt::CaseInsensitive ) == 0 )
{
switch ( layer->type() )
@@ -4697,6 +4798,8 @@ static QVariant fcnGetLayerProperty( const QVariantList &values, const QgsExpres
return QCoreApplication::translate( "expressions", "Raster" );
case QgsMapLayerType::MeshLayer:
return QCoreApplication::translate( "expressions", "Mesh" );
+ case QgsMapLayerType::VectorTileLayer:
+ return QCoreApplication::translate( "expressions", "Vector Tile" );
case QgsMapLayerType::PluginLayer:
return QCoreApplication::translate( "expressions", "Plugin" );
}
@@ -5899,9 +6002,36 @@ const QList &QgsExpression::Functions()
fcnGetFeature, QStringLiteral( "Record and Attributes" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "QgsExpressionUtils::getFeature" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "get_feature_by_id" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "layer" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "feature_id" ) ),
- fcnGetFeatureById, QStringLiteral( "Record and Attributes" ), QString(), false, QSet(), false )
- << new QgsStaticExpressionFunction( QStringLiteral( "attributes" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "feature" ), true ),
- fcnAttributes, QStringLiteral( "Record and Attributes" ), QString(), false, QSet() << QgsFeatureRequest::ALL_ATTRIBUTES );
+ fcnGetFeatureById, QStringLiteral( "Record and Attributes" ), QString(), false, QSet(), false );
+
+ QgsStaticExpressionFunction *attributesFunc = new QgsStaticExpressionFunction( QStringLiteral( "attributes" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "feature" ), true ),
+ fcnAttributes, QStringLiteral( "Record and Attributes" ), QString(), false, QSet() << QgsFeatureRequest::ALL_ATTRIBUTES );
+ attributesFunc->setIsStatic( false );
+ functions << attributesFunc;
+
+ QgsStaticExpressionFunction *maptipFunc = new QgsStaticExpressionFunction(
+ QStringLiteral( "maptip" ),
+ -1,
+ fcnFeatureMaptip,
+ QStringLiteral( "Record and Attributes" ),
+ QString(),
+ false,
+ QSet()
+ );
+ maptipFunc->setIsStatic( false );
+ functions << maptipFunc;
+
+ QgsStaticExpressionFunction *displayFunc = new QgsStaticExpressionFunction(
+ QStringLiteral( "display_expression" ),
+ -1,
+ fcnFeatureDisplayExpression,
+ QStringLiteral( "Record and Attributes" ),
+ QString(),
+ false,
+ QSet()
+ );
+ displayFunc->setIsStatic( false );
+ functions << displayFunc;
QgsStaticExpressionFunction *isSelectedFunc = new QgsStaticExpressionFunction(
QStringLiteral( "is_selected" ),
@@ -6015,6 +6145,9 @@ const QList &QgsExpression::Functions()
functions
<< varFunction;
+
+ functions << new QgsStaticExpressionFunction( QStringLiteral( "eval_template" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "template" ) ), fcnEvalTemplate, QStringLiteral( "General" ), QString(), true );
+
QgsStaticExpressionFunction *evalFunc = new QgsStaticExpressionFunction( QStringLiteral( "eval" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "expression" ) ), fcnEval, QStringLiteral( "General" ), QString(), true, QSet() << QgsFeatureRequest::ALL_ATTRIBUTES );
evalFunc->setIsStaticFunction(
[]( const QgsExpressionNodeFunction * node, QgsExpression * parent, const QgsExpressionContext * context )
diff --git a/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp b/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp
index 8070d720e3c6..1bfea625d322 100644
--- a/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp
+++ b/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp
@@ -140,25 +140,32 @@ QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatte
QgsFeatureRequest request;
request.setFlags( QgsFeatureRequest::NoGeometry );
- request.setSubsetOfAttributes( QgsAttributeList() << ki << vi );
+ QgsAttributeIds subsetOfAttributes { ki, vi };
- const QString expression = config.value( QStringLiteral( "FilterExpression" ) ).toString();
+ const QString descriptionExpressionString = config.value( "Description" ).toString();
+ QgsExpression descriptionExpression( descriptionExpressionString );
+ QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
+ descriptionExpression.prepare( &context );
+ subsetOfAttributes += descriptionExpression.referencedAttributeIndexes( layer->fields() );
+ request.setSubsetOfAttributes( subsetOfAttributes.toList() );
+
+ const QString filterExpression = config.value( QStringLiteral( "FilterExpression" ) ).toString();
// Skip the filter and build a full cache if the form scope is required and the feature
// is not valid or the attributes required for the filter have no valid value
// Note: parent form scope is not checked for usability because it's supposed to
// be used into a coalesce that retrieve the current value of the parent
// from the parent layer when used outside of an embedded form
- if ( ! expression.isEmpty() && ( !( expressionRequiresFormScope( expression ) )
- || expressionIsUsable( expression, formFeature ) ) )
+ if ( ! filterExpression.isEmpty() && ( !( expressionRequiresFormScope( filterExpression ) )
+ || expressionIsUsable( filterExpression, formFeature ) ) )
{
- QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
- if ( formFeature.isValid( ) && QgsValueRelationFieldFormatter::expressionRequiresFormScope( expression ) )
- context.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
- if ( parentFormFeature.isValid() && QgsValueRelationFieldFormatter::expressionRequiresParentFormScope( expression ) )
- context.appendScope( QgsExpressionContextUtils::parentFormScope( parentFormFeature ) );
- request.setExpressionContext( context );
- request.setFilterExpression( expression );
+ QgsExpressionContext filterContext = context;
+ if ( formFeature.isValid( ) && QgsValueRelationFieldFormatter::expressionRequiresFormScope( filterExpression ) )
+ filterContext.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
+ if ( parentFormFeature.isValid() && QgsValueRelationFieldFormatter::expressionRequiresParentFormScope( filterExpression ) )
+ filterContext.appendScope( QgsExpressionContextUtils::parentFormScope( parentFormFeature ) );
+ request.setExpressionContext( filterContext );
+ request.setFilterExpression( filterExpression );
}
QgsFeatureIterator fit = layer->getFeatures( request );
@@ -166,7 +173,13 @@ QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatte
QgsFeature f;
while ( fit.nextFeature( f ) )
{
- cache.append( ValueRelationItem( f.attribute( ki ), f.attribute( vi ).toString() ) );
+ QString description;
+ if ( descriptionExpression.isValid() )
+ {
+ context.setFeature( f );
+ description = descriptionExpression.evaluate( &context ).toString();
+ }
+ cache.append( ValueRelationItem( f.attribute( ki ), f.attribute( vi ).toString(), description ) );
}
if ( config.value( QStringLiteral( "OrderByValue" ) ).toBool() )
diff --git a/src/core/fieldformatter/qgsvaluerelationfieldformatter.h b/src/core/fieldformatter/qgsvaluerelationfieldformatter.h
index 05852e5fb877..14ca2426b1c2 100644
--- a/src/core/fieldformatter/qgsvaluerelationfieldformatter.h
+++ b/src/core/fieldformatter/qgsvaluerelationfieldformatter.h
@@ -39,9 +39,10 @@ class CORE_EXPORT QgsValueRelationFieldFormatter : public QgsFieldFormatter
struct ValueRelationItem
{
//! Constructor for ValueRelationItem
- ValueRelationItem( const QVariant &key, const QString &value )
+ ValueRelationItem( const QVariant &key, const QString &value, const QString &description = QString() )
: key( key )
, value( value )
+ , description( description )
{}
//! Constructor for ValueRelationItem
@@ -49,6 +50,7 @@ class CORE_EXPORT QgsValueRelationFieldFormatter : public QgsFieldFormatter
QVariant key;
QString value;
+ QString description;
};
typedef QVector < QgsValueRelationFieldFormatter::ValueRelationItem > ValueRelationCache;
diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp
index 38f0b4653a50..ffa0fb02b13b 100644
--- a/src/core/geometry/qgsgeometry.cpp
+++ b/src/core/geometry/qgsgeometry.cpp
@@ -813,14 +813,14 @@ QgsGeometry::OperationResult QgsGeometry::rotate( double rotation, const QgsPoin
return QgsGeometry::Success;
}
-QgsGeometry::OperationResult QgsGeometry::splitGeometry( const QVector &splitLine, QVector &newGeometries, bool topological, QVector &topologyTestPoints )
+QgsGeometry::OperationResult QgsGeometry::splitGeometry( const QVector &splitLine, QVector &newGeometries, bool topological, QVector &topologyTestPoints, bool splitFeature )
{
QgsPointSequence split, topology;
convertPointList( splitLine, split );
convertPointList( topologyTestPoints, topology );
- return splitGeometry( split, newGeometries, topological, topology );
+ return splitGeometry( split, newGeometries, topological, topology, splitFeature );
}
-QgsGeometry::OperationResult QgsGeometry::splitGeometry( const QgsPointSequence &splitLine, QVector &newGeometries, bool topological, QgsPointSequence &topologyTestPoints )
+QgsGeometry::OperationResult QgsGeometry::splitGeometry( const QgsPointSequence &splitLine, QVector &newGeometries, bool topological, QgsPointSequence &topologyTestPoints, bool splitFeature )
{
if ( !d->geometry )
{
@@ -836,8 +836,8 @@ QgsGeometry::OperationResult QgsGeometry::splitGeometry( const QgsPointSequence
if ( result == QgsGeometryEngine::Success )
{
- *this = newGeoms.takeAt( 0 );
-
+ if ( splitFeature )
+ *this = newGeoms.takeAt( 0 );
newGeometries = newGeoms;
}
@@ -1310,6 +1310,129 @@ json QgsGeometry::asJsonObject( int precision ) const
}
+QVector QgsGeometry::coerceToType( const QgsWkbTypes::Type type ) const
+{
+ QVector< QgsGeometry > res;
+ if ( isNull() )
+ return res;
+
+ if ( wkbType() == type || type == QgsWkbTypes::Unknown )
+ {
+ res << *this;
+ return res;
+ }
+
+ if ( type == QgsWkbTypes::NoGeometry )
+ {
+ return res;
+ }
+
+ QgsGeometry newGeom = *this;
+
+ // Curved -> straight
+ if ( !QgsWkbTypes::isCurvedType( type ) && QgsWkbTypes::isCurvedType( newGeom.wkbType() ) )
+ {
+ newGeom = QgsGeometry( d->geometry.get()->segmentize() );
+ }
+
+ // polygon -> line
+ if ( QgsWkbTypes::geometryType( type ) == QgsWkbTypes::LineGeometry &&
+ newGeom.type() == QgsWkbTypes::PolygonGeometry )
+ {
+ // boundary gives us a (multi)line string of exterior + interior rings
+ newGeom = QgsGeometry( newGeom.constGet()->boundary() );
+ }
+ // line -> polygon
+ if ( QgsWkbTypes::geometryType( type ) == QgsWkbTypes::PolygonGeometry &&
+ newGeom.type() == QgsWkbTypes::LineGeometry )
+ {
+ std::unique_ptr< QgsGeometryCollection > gc( QgsGeometryFactory::createCollectionOfType( type ) );
+ const QgsGeometry source = newGeom;
+ for ( auto part = source.const_parts_begin(); part != source.const_parts_end(); ++part )
+ {
+ std::unique_ptr< QgsAbstractGeometry > exterior( ( *part )->clone() );
+ if ( QgsCurve *curve = qgsgeometry_cast< QgsCurve * >( exterior.get() ) )
+ {
+ if ( QgsWkbTypes::isCurvedType( type ) )
+ {
+ std::unique_ptr< QgsCurvePolygon > cp = qgis::make_unique< QgsCurvePolygon >();
+ cp->setExteriorRing( curve );
+ exterior.release();
+ gc->addGeometry( cp.release() );
+ }
+ else
+ {
+ std::unique_ptr< QgsPolygon > p = qgis::make_unique< QgsPolygon >();
+ p->setExteriorRing( qgsgeometry_cast< QgsLineString * >( curve ) );
+ exterior.release();
+ gc->addGeometry( p.release() );
+ }
+ }
+ }
+ newGeom = QgsGeometry( std::move( gc ) );
+ }
+
+ // line/polygon -> points
+ if ( QgsWkbTypes::geometryType( type ) == QgsWkbTypes::PointGeometry &&
+ ( newGeom.type() == QgsWkbTypes::LineGeometry ||
+ newGeom.type() == QgsWkbTypes::PolygonGeometry ) )
+ {
+ // lines/polygons to a point layer, extract all vertices
+ std::unique_ptr< QgsMultiPoint > mp = qgis::make_unique< QgsMultiPoint >();
+ const QgsGeometry source = newGeom;
+ QSet< QgsPoint > added;
+ for ( auto vertex = source.vertices_begin(); vertex != source.vertices_end(); ++vertex )
+ {
+ if ( added.contains( *vertex ) )
+ continue; // avoid duplicate points, e.g. start/end of rings
+ mp->addGeometry( ( *vertex ).clone() );
+ added.insert( *vertex );
+ }
+ newGeom = QgsGeometry( std::move( mp ) );
+ }
+
+ // Single -> multi
+ if ( QgsWkbTypes::isMultiType( type ) && ! newGeom.isMultipart( ) )
+ {
+ newGeom.convertToMultiType();
+ }
+ // Drop Z/M
+ if ( newGeom.constGet()->is3D() && ! QgsWkbTypes::hasZ( type ) )
+ {
+ newGeom.get()->dropZValue();
+ }
+ if ( newGeom.constGet()->isMeasure() && ! QgsWkbTypes::hasM( type ) )
+ {
+ newGeom.get()->dropMValue();
+ }
+ // Add Z/M back, set to 0
+ if ( ! newGeom.constGet()->is3D() && QgsWkbTypes::hasZ( type ) )
+ {
+ newGeom.get()->addZValue( 0.0 );
+ }
+ if ( ! newGeom.constGet()->isMeasure() && QgsWkbTypes::hasM( type ) )
+ {
+ newGeom.get()->addMValue( 0.0 );
+ }
+
+ // Multi -> single
+ if ( ! QgsWkbTypes::isMultiType( type ) && newGeom.isMultipart( ) )
+ {
+ const QgsGeometryCollection *parts( static_cast< const QgsGeometryCollection * >( newGeom.constGet() ) );
+ QgsAttributeMap attrMap;
+ res.reserve( parts->partCount() );
+ for ( int i = 0; i < parts->partCount( ); i++ )
+ {
+ res << QgsGeometry( parts->geometryN( i )->clone() );
+ }
+ }
+ else
+ {
+ res << newGeom;
+ }
+ return res;
+}
+
QgsGeometry QgsGeometry::convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart ) const
{
switch ( destType )
diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h
index 2396635a2131..0c555ace8678 100644
--- a/src/core/geometry/qgsgeometry.h
+++ b/src/core/geometry/qgsgeometry.h
@@ -881,10 +881,11 @@ class CORE_EXPORT QgsGeometry
* \param[out] newGeometries list of new geometries that have been created with the split
* \param topological TRUE if topological editing is enabled
* \param[out] topologyTestPoints points that need to be tested for topological completeness in the dataset
+ * \param splitFeature Set to True if you want to split a feature, otherwise set to False to split parts
* \returns OperationResult a result code: success or reason of failure
* \deprecated since QGIS 3.12 - will be removed in QGIS 4.0. Use the variant which accepts QgsPoint objects instead of QgsPointXY.
*/
- Q_DECL_DEPRECATED OperationResult splitGeometry( const QVector &splitLine, QVector &newGeometries SIP_OUT, bool topological, QVector &topologyTestPoints SIP_OUT ) SIP_DEPRECATED;
+ Q_DECL_DEPRECATED OperationResult splitGeometry( const QVector &splitLine, QVector &newGeometries SIP_OUT, bool topological, QVector &topologyTestPoints SIP_OUT, bool splitFeature = true ) SIP_DEPRECATED;
/**
* Splits this geometry according to a given line.
@@ -892,9 +893,11 @@ class CORE_EXPORT QgsGeometry
* \param[out] newGeometries list of new geometries that have been created with the split
* \param topological TRUE if topological editing is enabled
* \param[out] topologyTestPoints points that need to be tested for topological completeness in the dataset
+ * \param splitFeature Set to True if you want to split a feature, otherwise set to False to split parts
+ * fix this bug?
* \returns OperationResult a result code: success or reason of failure
*/
- OperationResult splitGeometry( const QgsPointSequence &splitLine, QVector &newGeometries SIP_OUT, bool topological, QgsPointSequence &topologyTestPoints SIP_OUT );
+ OperationResult splitGeometry( const QgsPointSequence &splitLine, QVector &newGeometries SIP_OUT, bool topological, QgsPointSequence &topologyTestPoints SIP_OUT, bool splitFeature = true );
/**
* Replaces a part of this geometry with another line
@@ -1564,14 +1567,42 @@ class CORE_EXPORT QgsGeometry
*/
virtual json asJsonObject( int precision = 17 ) const SIP_SKIP;
+ /**
+ * Attempts to coerce this geometry into the specified destination \a type.
+ *
+ * This method will do anything possible to force the current geometry into the specified type. E.g.
+ * - lines or polygons will be converted to points by return either a single multipoint geometry or multiple
+ * single point geometries.
+ * - polygons will be converted to lines by extracting their exterior and interior rings, returning
+ * either a multilinestring or multiple single line strings as dictated by \a type.
+ * - lines will be converted to polygon rings if \a type is a polygon type
+ * - curved geometries will be segmented if \a type is non-curved.
+ * - multi geometries will be converted to a list of single geometries
+ * - single geometries will be upgraded to multi geometries
+ * - z or m values will be added or dropped as required.
+ *
+ * \note This method is much stricter than convertToType(), as it considers the exact WKB type
+ * of geometries instead of the geometry family (point/line/polygon), and tries more exhaustively
+ * to coerce geometries to the desired \a type. It also correctly maintains curves and z/m values
+ * wherever appropriate.
+ *
+ * \since QGIS 3.14
+ */
+ QVector< QgsGeometry > coerceToType( QgsWkbTypes::Type type ) const;
+
/**
* Try to convert the geometry to the requested type
* \param destType the geometry type to be converted to
* \param destMultipart determines if the output geometry will be multipart or not
* \returns the converted geometry or NULLPTR if the conversion fails.
+ *
+ * \note The coerceToType() method applies much stricter and more exhaustive attempts to convert
+ * between geometry types, and is recommended instead of this method. This method force drops
+ * curves and any z or m values present in the geometry.
+ *
* \since QGIS 2.2
*/
- QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const SIP_FACTORY;
+ QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const;
/* Accessor functions for getting geometry data */
diff --git a/src/core/geometry/qgsgeometrycollection.cpp b/src/core/geometry/qgsgeometrycollection.cpp
index e7cd9c25ca9c..f86a611290a4 100644
--- a/src/core/geometry/qgsgeometrycollection.cpp
+++ b/src/core/geometry/qgsgeometrycollection.cpp
@@ -477,8 +477,28 @@ QgsRectangle QgsGeometryCollection::calculateBoundingBox() const
QgsRectangle bbox = mGeometries.at( 0 )->boundingBox();
for ( int i = 1; i < mGeometries.size(); ++i )
{
+ if ( mGeometries.at( i )->isEmpty() )
+ continue;
+
QgsRectangle geomBox = mGeometries.at( i )->boundingBox();
- bbox.combineExtentWith( geomBox );
+ if ( bbox.isNull() )
+ {
+ // workaround treatment of a QgsRectangle(0,0,0,0) as a "null"/invalid rectangle
+ // if bbox is null, then the first geometry must have returned a bounding box of (0,0,0,0)
+ // so just manually include that as a point... ew.
+ geomBox.combineExtentWith( QPointF( 0, 0 ) );
+ bbox = geomBox;
+ }
+ else if ( geomBox.isNull() )
+ {
+ // ...as above... this part must have a bounding box of (0,0,0,0).
+ // if we try to combine the extent with this "null" box it will just be ignored.
+ bbox.combineExtentWith( QPointF( 0, 0 ) );
+ }
+ else
+ {
+ bbox.combineExtentWith( geomBox );
+ }
}
return bbox;
}
diff --git a/src/core/layertree/qgslayertreelayer.cpp b/src/core/layertree/qgslayertreelayer.cpp
index 659a3c41a07a..6e2759be3e07 100644
--- a/src/core/layertree/qgslayertreelayer.cpp
+++ b/src/core/layertree/qgslayertreelayer.cpp
@@ -39,6 +39,7 @@ QgsLayerTreeLayer::QgsLayerTreeLayer( const QgsLayerTreeLayer &other )
: QgsLayerTreeNode( other )
, mRef( other.mRef )
, mLayerName( other.mLayerName )
+ , mPatchShape( other.mPatchShape )
{
attachToLayer();
}
@@ -110,6 +111,7 @@ QgsLayerTreeLayer *QgsLayerTreeLayer::readXml( QDomElement &element, const QgsRe
Qt::CheckState checked = QgsLayerTreeUtils::checkStateFromXml( element.attribute( QStringLiteral( "checked" ) ) );
bool isExpanded = ( element.attribute( QStringLiteral( "expanded" ), QStringLiteral( "1" ) ) == QLatin1String( "1" ) );
+ QString labelExpression = element.attribute( QStringLiteral( "legend_exp" ) );
// needs to have the layer reference resolved later
QgsLayerTreeLayer *nodeLayer = new QgsLayerTreeLayer( layerID, layerName, source, providerKey );
@@ -118,6 +120,16 @@ QgsLayerTreeLayer *QgsLayerTreeLayer::readXml( QDomElement &element, const QgsRe
nodeLayer->setItemVisibilityChecked( checked != Qt::Unchecked );
nodeLayer->setExpanded( isExpanded );
+ nodeLayer->setLabelExpression( labelExpression );
+
+ const QDomElement patchElem = element.firstChildElement( QStringLiteral( "patch" ) );
+ if ( !patchElem.isNull() )
+ {
+ QgsLegendPatchShape patch;
+ patch.readXml( patchElem, context );
+ nodeLayer->setPatchShape( patch );
+ }
+
return nodeLayer;
}
@@ -144,6 +156,14 @@ void QgsLayerTreeLayer::writeXml( QDomElement &parentElement, const QgsReadWrite
elem.setAttribute( QStringLiteral( "checked" ), mChecked ? QStringLiteral( "Qt::Checked" ) : QStringLiteral( "Qt::Unchecked" ) );
elem.setAttribute( QStringLiteral( "expanded" ), mExpanded ? "1" : "0" );
+ elem.setAttribute( QStringLiteral( "legend_exp" ), mLabelExpression );
+
+ if ( !mPatchShape.isNull() )
+ {
+ QDomElement patchElem = doc.createElement( QStringLiteral( "patch" ) );
+ mPatchShape.writeXml( patchElem, doc, context );
+ elem.appendChild( patchElem );
+ }
writeCommonXml( elem );
@@ -194,3 +214,13 @@ void QgsLayerTreeLayer::setLabelExpression( const QString &expression )
mLabelExpression = expression;
}
+QgsLegendPatchShape QgsLayerTreeLayer::patchShape() const
+{
+ return mPatchShape;
+}
+
+void QgsLayerTreeLayer::setPatchShape( const QgsLegendPatchShape &shape )
+{
+ mPatchShape = shape;
+}
+
diff --git a/src/core/layertree/qgslayertreelayer.h b/src/core/layertree/qgslayertreelayer.h
index d4186228269f..a723ad808b8b 100644
--- a/src/core/layertree/qgslayertreelayer.h
+++ b/src/core/layertree/qgslayertreelayer.h
@@ -21,6 +21,7 @@
#include "qgslayertreenode.h"
#include "qgsmaplayerref.h"
#include "qgsreadwritecontext.h"
+#include "qgslegendpatchshape.h"
class QgsMapLayer;
@@ -142,6 +143,22 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
*/
QString labelExpression() const { return mLabelExpression; }
+ /**
+ * Returns the symbol patch shape to use when rendering the legend node symbol.
+ *
+ * \see setPatchShape()
+ * \since QGIS 3.14
+ */
+ QgsLegendPatchShape patchShape() const;
+
+ /**
+ * Sets the symbol patch \a shape to use when rendering the legend node symbol.
+ *
+ * \see patchShape()
+ * \since QGIS 3.14
+ */
+ void setPatchShape( const QgsLegendPatchShape &shape );
+
signals:
/**
@@ -191,6 +208,8 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
*/
QgsLayerTreeLayer( const QgsLayerTreeLayer &other );
#endif
+
+ QgsLegendPatchShape mPatchShape;
};
diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp
index 84dfaeb122af..909078f1f116 100644
--- a/src/core/layertree/qgslayertreemodel.cpp
+++ b/src/core/layertree/qgslayertreemodel.cpp
@@ -203,6 +203,9 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const
case QgsMapLayerType::MeshLayer:
return QgsLayerItem::iconMesh();
+ case QgsMapLayerType::VectorTileLayer:
+ return QgsLayerItem::iconVectorTile();
+
case QgsMapLayerType::VectorLayer:
case QgsMapLayerType::PluginLayer:
break;
diff --git a/src/core/layertree/qgslayertreemodellegendnode.cpp b/src/core/layertree/qgslayertreemodellegendnode.cpp
index 2153765031db..3bdecaa534bb 100644
--- a/src/core/layertree/qgslayertreemodellegendnode.cpp
+++ b/src/core/layertree/qgslayertreemodellegendnode.cpp
@@ -303,6 +303,23 @@ QString QgsSymbolLegendNode::symbolLabel() const
return label;
}
+QgsLegendPatchShape QgsSymbolLegendNode::patchShape() const
+{
+ if ( mEmbeddedInParent )
+ {
+ return mLayerNode->patchShape();
+ }
+ else
+ {
+ return mPatchShape;
+ }
+}
+
+void QgsSymbolLegendNode::setPatchShape( const QgsLegendPatchShape &shape )
+{
+ mPatchShape = shape;
+}
+
void QgsSymbolLegendNode::setSymbol( QgsSymbol *symbol )
{
if ( !symbol )
@@ -493,6 +510,7 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
// setup temporary render context
QgsRenderContext *context = nullptr;
std::unique_ptr< QgsRenderContext > tempRenderContext;
+ QgsLegendPatchShape patchShape = ctx ? ctx->patchShape : QgsLegendPatchShape();
if ( ctx && ctx->context )
context = ctx->context;
else
@@ -577,7 +595,7 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
imagePainter.setRenderHint( QPainter::Antialiasing );
context->setPainter( &imagePainter );
imagePainter.translate( maxBleed, maxBleed );
- s->drawPreviewIcon( &imagePainter, symbolSize, context );
+ s->drawPreviewIcon( &imagePainter, symbolSize, context, false, nullptr, &patchShape );
imagePainter.translate( -maxBleed, -maxBleed );
context->setPainter( ctx->painter );
//reduce opacity of image
@@ -589,7 +607,7 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
}
else
{
- s->drawPreviewIcon( p, QSize( static_cast< int >( std::round( width * dotsPerMM ) ), static_cast< int >( std::round( height * dotsPerMM ) ) ), context );
+ s->drawPreviewIcon( p, QSize( static_cast< int >( std::round( width * dotsPerMM ) ), static_cast< int >( std::round( height * dotsPerMM ) ) ), context, false, nullptr, &patchShape );
}
if ( !mTextOnSymbolLabel.isEmpty() )
diff --git a/src/core/layertree/qgslayertreemodellegendnode.h b/src/core/layertree/qgslayertreemodellegendnode.h
index 32a05931e7bc..589956200c02 100644
--- a/src/core/layertree/qgslayertreemodellegendnode.h
+++ b/src/core/layertree/qgslayertreemodellegendnode.h
@@ -28,6 +28,7 @@
#include "qgsrasterdataprovider.h" // for QgsImageFetcher dtor visibility
#include "qgsexpressioncontext.h"
+#include "qgslegendpatchshape.h"
class QgsLayerTreeLayer;
class QgsLayerTreeModel;
@@ -145,6 +146,12 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
*/
double maxSiblingSymbolWidth = 0.0;
+ /**
+ * The patch shape to render for the node.
+ *
+ * \since QGIS 3.14
+ */
+ QgsLegendPatchShape patchShape;
};
struct ItemMetrics
@@ -221,6 +228,7 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
QgsLayerTreeLayer *mLayerNode = nullptr;
bool mEmbeddedInParent;
QString mUserLabel;
+ QgsLegendPatchShape mPatchShape;
};
#include "qgslegendsymbolitem.h"
@@ -335,6 +343,22 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
*/
QString symbolLabel() const;
+ /**
+ * Returns the symbol patch shape to use when rendering the legend node symbol.
+ *
+ * \see setPatchShape()
+ * \since QGIS 3.14
+ */
+ QgsLegendPatchShape patchShape() const;
+
+ /**
+ * Sets the symbol patch \a shape to use when rendering the legend node symbol.
+ *
+ * \see patchShape()
+ * \since QGIS 3.14
+ */
+ void setPatchShape( const QgsLegendPatchShape &shape );
+
/**
* Evaluates and returns the text label of the current node
* \param context extra QgsExpressionContext to use for evaluating the expression
diff --git a/src/core/layertree/qgslayertreenode.cpp b/src/core/layertree/qgslayertreenode.cpp
index 4a79eeb08028..b473b62b3a7f 100644
--- a/src/core/layertree/qgslayertreenode.cpp
+++ b/src/core/layertree/qgslayertreenode.cpp
@@ -155,6 +155,18 @@ QList QgsLayerTreeNode::checkedLayers() const
return layers;
}
+int QgsLayerTreeNode::depth() const
+{
+ int depth = 0;
+ QgsLayerTreeNode *node = mParent;
+ while ( node )
+ {
+ node = node->parent();
+ ++depth;
+ }
+ return depth;
+}
+
void QgsLayerTreeNode::setExpanded( bool expanded )
{
if ( mExpanded == expanded )
diff --git a/src/core/layertree/qgslayertreenode.h b/src/core/layertree/qgslayertreenode.h
index c0e0c5703fbd..d39853429aef 100644
--- a/src/core/layertree/qgslayertreenode.h
+++ b/src/core/layertree/qgslayertreenode.h
@@ -208,6 +208,12 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
*/
QList< QgsMapLayer * > checkedLayers() const;
+ /**
+ * Returns the depth of this node, i.e. the number of its ancestors
+ * \since QGIS 3.14
+ */
+ int depth() const;
+
//! Returns whether the node should be shown as expanded or collapsed in GUI
bool isExpanded() const;
//! Sets whether the node should be shown as expanded or collapsed in GUI
diff --git a/src/core/layertree/qgslegendpatchshape.cpp b/src/core/layertree/qgslegendpatchshape.cpp
new file mode 100644
index 000000000000..89918b7720b8
--- /dev/null
+++ b/src/core/layertree/qgslegendpatchshape.cpp
@@ -0,0 +1,221 @@
+/***************************************************************************
+ qgslegendpatchshape.cpp
+ -------------------
+begin : April 2020
+copyright : (C) 2020 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 "qgslegendpatchshape.h"
+#include "qgsmultilinestring.h"
+#include "qgslinestring.h"
+#include "qgspolygon.h"
+
+QgsLegendPatchShape::QgsLegendPatchShape( QgsSymbol::SymbolType type, const QgsGeometry &geometry, bool preserveAspectRatio )
+ : mSymbolType( type )
+ , mGeometry( geometry )
+ , mPreserveAspectRatio( preserveAspectRatio )
+{
+
+}
+
+bool QgsLegendPatchShape::isNull() const
+{
+ return mGeometry.isNull() || mGeometry.isEmpty();
+}
+
+QgsGeometry QgsLegendPatchShape::geometry() const
+{
+ return mGeometry;
+}
+
+void QgsLegendPatchShape::setGeometry( const QgsGeometry &geometry )
+{
+ mGeometry = geometry;
+}
+
+bool QgsLegendPatchShape::preserveAspectRatio() const
+{
+ return mPreserveAspectRatio;
+}
+
+void QgsLegendPatchShape::setPreserveAspectRatio( bool preserveAspectRatio )
+{
+ mPreserveAspectRatio = preserveAspectRatio;
+}
+
+QPolygonF lineStringToQPolygonF( const QgsLineString *line )
+{
+ const double *srcX = line->xData();
+ const double *srcY = line->yData();
+ const int count = line->numPoints();
+ QPolygonF thisRes( count );
+ QPointF *dest = thisRes.data();
+ for ( int i = 0; i < count; ++i )
+ {
+ *dest++ = QPointF( *srcX++, *srcY++ );
+ }
+ return thisRes;
+}
+
+QList > QgsLegendPatchShape::toQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const
+{
+ if ( isNull() || type != mSymbolType )
+ return defaultPatch( type, size );
+
+ // scale and translate to desired size
+
+ const QRectF bounds = mGeometry.boundingBox().toRectF();
+
+ double dx = 0;
+ double dy = 0;
+ if ( mPreserveAspectRatio && bounds.height() > 0 && bounds.width() > 0 )
+ {
+ const double scaling = std::min( size.width() / bounds.width(), size.height() / bounds.height() );
+ const QSizeF scaledSize = bounds.size() * scaling;
+ dx = ( size.width() - scaledSize.width() ) / 2.0;
+ dy = ( size.height() - scaledSize.height() ) / 2.0;
+ size = scaledSize;
+ }
+
+ // important -- the transform needs to flip from north-up to painter style "increasing y down" coordinates
+ QPolygonF targetRectPoly = QPolygonF() << QPointF( dx, dy + size.height() )
+ << QPointF( dx + size.width(), dy + size.height() )
+ << QPointF( dx + size.width(), dy )
+ << QPointF( dx, dy );
+ QPolygonF patchRectPoly = QPolygonF( bounds );
+ //workaround QT Bug #21329
+ patchRectPoly.pop_back();
+ QTransform t;
+ QTransform::quadToQuad( patchRectPoly, targetRectPoly, t );
+
+ QgsGeometry geom = mGeometry;
+ geom.transform( t );
+
+ geom.convertToStraightSegment();
+
+ switch ( mSymbolType )
+ {
+ case QgsSymbol::Marker:
+ {
+ QPolygonF points;
+
+ if ( QgsWkbTypes::flatType( mGeometry.wkbType() ) == QgsWkbTypes::MultiPoint )
+ {
+ const QgsGeometry patch = geom;
+ for ( auto it = patch.vertices_begin(); it != patch.vertices_end(); ++it )
+ points << QPointF( ( *it ).x(), ( *it ).y() );
+ }
+ else
+ {
+ points << QPointF( size.width() / 2, size.height() / 2 );
+ }
+ return QList< QList >() << ( QList< QPolygonF >() << points );
+ }
+
+ case QgsSymbol::Line:
+ {
+ if ( !geom.isMultipart() )
+ return QList< QList >() << ( QList< QPolygonF >() << geom.asQPolygonF() );
+ else
+ {
+ QList< QList > res;
+ const QgsGeometry patch = geom;
+ for ( auto it = patch.const_parts_begin(); it != patch.const_parts_end(); ++it )
+ {
+ const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( *it );
+ if ( !line )
+ continue;
+
+ res << ( QList< QPolygonF >() << lineStringToQPolygonF( line ) );
+ }
+ return res;
+ }
+ }
+
+ case QgsSymbol::Fill:
+ {
+ QList< QList > res;
+
+ const QgsGeometry patch = geom;
+ for ( auto it = patch.const_parts_begin(); it != patch.const_parts_end(); ++it )
+ {
+ QList thisPart;
+ const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( *it );
+ if ( !polygon )
+ continue;
+
+ if ( !polygon->exteriorRing() )
+ continue;
+
+ thisPart << lineStringToQPolygonF( qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ) );
+ for ( int i = 0; i < polygon->numInteriorRings(); ++i )
+ thisPart << lineStringToQPolygonF( qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) ) );
+ res << thisPart;
+ }
+
+ return res;
+ }
+
+ case QgsSymbol::Hybrid:
+ return QList< QList >();
+ }
+
+ return QList< QList >();
+}
+
+QList > QgsLegendPatchShape::defaultPatch( QgsSymbol::SymbolType type, QSizeF size )
+{
+ switch ( type )
+ {
+ case QgsSymbol::Marker:
+ return QList< QList< QPolygonF > >() << ( QList< QPolygonF >() << ( QPolygonF() << QPointF( static_cast< int >( size.width() ) / 2,
+ static_cast< int >( size.height() ) / 2 ) ) );
+
+ case QgsSymbol::Line:
+ // we're adding 0.5 to get rid of blurred preview:
+ // drawing antialiased lines of width 1 at (x,0)-(x,100) creates 2px line
+ return QList< QList >() << ( QList< QPolygonF >() << ( QPolygonF() << QPointF( 0, static_cast< int >( size.height() ) / 2 + 0.5 ) << QPointF( size.width(), static_cast< int >( size.height() ) / 2 + 0.5 ) ) );
+
+ case QgsSymbol::Fill:
+ return QList< QList >() << ( QList< QPolygonF> () << ( QRectF( QPointF( 0, 0 ), QPointF( static_cast< int >( size.width() ), static_cast< int >( size.height() ) ) ) ) );
+
+ case QgsSymbol::Hybrid:
+ return QList< QList >();
+ }
+
+ return QList< QList >();
+}
+
+void QgsLegendPatchShape::readXml( const QDomElement &element, const QgsReadWriteContext & )
+{
+ mGeometry = QgsGeometry::fromWkt( element.attribute( QStringLiteral( "wkt" ) ) );
+ mPreserveAspectRatio = element.attribute( QStringLiteral( "preserveAspect" ) ).toInt();
+ mSymbolType = static_cast< QgsSymbol::SymbolType >( element.attribute( QStringLiteral( "type" ) ).toInt() );
+}
+
+void QgsLegendPatchShape::writeXml( QDomElement &element, QDomDocument &, const QgsReadWriteContext & ) const
+{
+ element.setAttribute( QStringLiteral( "wkt" ), mGeometry.isNull() ? QString() : mGeometry.asWkt( ) );
+ element.setAttribute( QStringLiteral( "preserveAspect" ), mPreserveAspectRatio ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
+ element.setAttribute( QStringLiteral( "type" ), QString::number( mSymbolType ) );
+}
+
+QgsSymbol::SymbolType QgsLegendPatchShape::symbolType() const
+{
+ return mSymbolType;
+}
+
+void QgsLegendPatchShape::setSymbolType( QgsSymbol::SymbolType type )
+{
+ mSymbolType = type;
+}
diff --git a/src/core/layertree/qgslegendpatchshape.h b/src/core/layertree/qgslegendpatchshape.h
new file mode 100644
index 000000000000..2ca21e533b51
--- /dev/null
+++ b/src/core/layertree/qgslegendpatchshape.h
@@ -0,0 +1,153 @@
+/***************************************************************************
+ qgslegendpatchshape.h
+ -------------------
+begin : April 2020
+copyright : (C) 2020 by Nyall Dawson
+email : nyall dot dawson at gmail dot com
+***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSLEGENDPATCHSHAPE_H
+#define QGSLEGENDPATCHSHAPE_H
+
+#include "qgis_core.h"
+#include "qgis_sip.h"
+#include "qgssymbol.h"
+
+/**
+ * \ingroup core
+ * Represents a patch shape for use in map legends.
+ *
+ * \since QGIS 3.14
+ */
+class CORE_EXPORT QgsLegendPatchShape
+{
+ public:
+
+ /**
+ * Constructor for a null QgsLegendPatchShape.
+ *
+ * A null QgsLegendPatchShape indicates that the default legend patch shape
+ * should be used instead.
+ */
+ QgsLegendPatchShape() = default;
+
+ /**
+ * Constructor for QgsLegendPatchShape.
+ *
+ * The \a type argument specifies the symbol type associated with this patch.
+ *
+ * The \a geometry argument gives the shape of the patch to render. See setGeometry()
+ * for further details on the geometry requirements.
+ *
+ * If \a preserveAspectRatio is TRUE, then the patch shape should preserve its aspect ratio when
+ * it is resized to fit a desired legend patch size.
+ */
+ QgsLegendPatchShape( QgsSymbol::SymbolType type,
+ const QgsGeometry &geometry,
+ bool preserveAspectRatio = true );
+
+ /**
+ * Returns TRUE if the patch shape is a null QgsLegendPatchShape,
+ * which indicates that the default legend patch shape should be used instead.
+ */
+ bool isNull() const;
+
+ /**
+ * Returns the symbol type associated with this patch.
+ *
+ * \see setSymbolType()
+ */
+ QgsSymbol::SymbolType symbolType() const;
+
+ /**
+ * Sets the symbol \a type associated with this patch.
+ *
+ * \see symbolType()
+ */
+ void setSymbolType( QgsSymbol::SymbolType type );
+
+ /**
+ * Returns the geometry for the patch shape.
+ *
+ * \see setGeometry()
+ */
+ QgsGeometry geometry() const;
+
+ /**
+ * Sets the \a geometry for the patch shape.
+ *
+ * The origin and size of the \a geometry is not important, as the legend
+ * renderer will automatically scale and transform the geometry to match
+ * the desired overall patch bounds.
+ *
+ * Geometries for legend patches are rendered respecting the traditional
+ * "y values increase toward the top of the map" convention, as opposed
+ * to the standard computer graphics convention of "y values increase toward
+ * the bottom of the display".
+ *
+ * \warning The geometry type should match the patch shape's symbolType(),
+ * e.g. a fill symbol type should only have Polygon or MultiPolygon geometries
+ * set, while a line symbol type must have LineString or MultiLineString geometries.
+ *
+ * \see geometry()
+ */
+ void setGeometry( const QgsGeometry &geometry );
+
+ /**
+ * Returns TRUE if the patch shape should preserve its aspect ratio when
+ * it is resized to fit a desired legend patch size.
+ *
+ * \see setPreserveAspectRatio()
+ */
+ bool preserveAspectRatio() const;
+
+ /**
+ * Sets whether the patch shape should \a preserve its aspect ratio when
+ * it is resized to fit a desired legend patch size.
+ *
+ * The default behavior is to respect the geometry()'s aspect ratio.
+ *
+ * \see setPreserveAspectRatio()
+ */
+ void setPreserveAspectRatio( bool preserve );
+
+ /**
+ * Converts the patch shape to a set of QPolygonF objects representing
+ * how the patch should be drawn for a symbol of the given \a type at the specified \a size (as
+ * geometry parts and rings).
+ */
+ QList< QList< QPolygonF > > toQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const;
+
+ /**
+ * Returns the default patch geometry for the given symbol \a type and \a size as a set of QPolygonF objects (parts and rings).
+ */
+ static QList< QList< QPolygonF > > defaultPatch( QgsSymbol::SymbolType type, QSizeF size );
+
+ /**
+ * Read settings from a DOM \a element.
+ * \see writeXml()
+ */
+ void readXml( const QDomElement &element, const QgsReadWriteContext &context );
+
+ /**
+ * Write settings into a DOM \a element.
+ * \see readXml()
+ */
+ void writeXml( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) const;
+
+ private:
+ QgsSymbol::SymbolType mSymbolType = QgsSymbol::Fill;
+ QgsGeometry mGeometry;
+ bool mPreserveAspectRatio = true;
+
+};
+
+#endif // QGSLEGENDPATCHSHAPE_H
diff --git a/src/core/layout/qgscompositionconverter.cpp b/src/core/layout/qgscompositionconverter.cpp
index 3363656699bb..3899d2fffae9 100644
--- a/src/core/layout/qgscompositionconverter.cpp
+++ b/src/core/layout/qgscompositionconverter.cpp
@@ -30,6 +30,7 @@
#include "qgsproject.h"
#include "qgsmaplayerstylemanager.h"
#include "qgsvectorlayer.h"
+#include "qgslinesymbollayer.h"
#include "qgsprintlayout.h"
#include "qgslayoutatlas.h"
@@ -53,6 +54,7 @@
#include "qgslayoutmultiframe.h"
#include "qgslayoutframe.h"
#include "qgslayoutguidecollection.h"
+#include "qgslayoutnortharrowhandler.h"
QgsPropertiesDefinition QgsCompositionConverter::sPropertyDefinitions;
@@ -688,8 +690,8 @@ bool QgsCompositionConverter::readPictureXml( QgsLayoutItemPicture *layoutItem,
}
//rotation map
- layoutItem->mNorthMode = static_cast< QgsLayoutItemPicture::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
- layoutItem->mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
+ layoutItem->mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
+ layoutItem->mNorthArrowHandler->setNorthOffset( itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
QString rotationMapId = itemElem.attribute( QStringLiteral( "mapId" ), QStringLiteral( "-1" ) );
if ( rotationMapId != QStringLiteral( "-1" ) )
@@ -1029,10 +1031,8 @@ bool QgsCompositionConverter::readScaleBarXml( QgsLayoutItemScaleBar *layoutItem
layoutItem->setMaximumBarWidth( itemElem.attribute( QStringLiteral( "maxBarWidth" ), QStringLiteral( "150" ) ).toDouble() );
layoutItem->mSegmentMillimeters = itemElem.attribute( QStringLiteral( "segmentMillimeters" ), QStringLiteral( "0.0" ) ).toDouble();
layoutItem->setMapUnitsPerScaleBarUnit( itemElem.attribute( QStringLiteral( "numMapUnitsPerScaleBarUnit" ), QStringLiteral( "1.0" ) ).toDouble() );
- layoutItem->setLineWidth( itemElem.attribute( QStringLiteral( "outlineWidth" ), QStringLiteral( "0.3" ) ).toDouble() );
layoutItem->setUnitLabel( itemElem.attribute( QStringLiteral( "unitLabel" ) ) );
- layoutItem->setLineJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( itemElem.attribute( QStringLiteral( "lineJoinStyle" ), QStringLiteral( "miter" ) ) ) );
- layoutItem->setLineCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( itemElem.attribute( QStringLiteral( "lineCapStyle" ), QStringLiteral( "square" ) ) ) );
+
QFont f;
if ( !QgsFontUtils::setFromXmlChildNode( f, itemElem, QStringLiteral( "scaleBarFont" ) ) )
{
@@ -1058,12 +1058,12 @@ bool QgsCompositionConverter::readScaleBarXml( QgsLayoutItemScaleBar *layoutItem
if ( redOk && greenOk && blueOk && alphaOk )
{
- layoutItem->setFillColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
+ layoutItem->fillSymbol()->setColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
}
}
else
{
- layoutItem->setFillColor( QColor( itemElem.attribute( QStringLiteral( "brushColor" ), QStringLiteral( "#000000" ) ) ) );
+ layoutItem->fillSymbol()->setColor( QColor( itemElem.attribute( QStringLiteral( "brushColor" ), QStringLiteral( "#000000" ) ) ) );
}
//fill color 2
@@ -1081,14 +1081,20 @@ bool QgsCompositionConverter::readScaleBarXml( QgsLayoutItemScaleBar *layoutItem
if ( redOk && greenOk && blueOk && alphaOk )
{
- layoutItem->setFillColor2( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
+ layoutItem->alternateFillSymbol()->setColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
}
}
else
{
- layoutItem->setFillColor2( QColor( itemElem.attribute( QStringLiteral( "brush2Color" ), QStringLiteral( "#ffffff" ) ) ) );
+ layoutItem->alternateFillSymbol()->setColor( QColor( itemElem.attribute( QStringLiteral( "brush2Color" ), QStringLiteral( "#ffffff" ) ) ) );
}
+ std::unique_ptr< QgsLineSymbol > lineSymbol = qgis::make_unique< QgsLineSymbol >();
+ std::unique_ptr< QgsSimpleLineSymbolLayer > lineSymbolLayer = qgis::make_unique< QgsSimpleLineSymbolLayer >();
+ lineSymbolLayer->setWidth( itemElem.attribute( QStringLiteral( "outlineWidth" ), QStringLiteral( "0.3" ) ).toDouble() );
+ lineSymbolLayer->setWidthUnit( QgsUnitTypes::RenderMillimeters );
+ lineSymbolLayer->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( itemElem.attribute( QStringLiteral( "lineJoinStyle" ), QStringLiteral( "miter" ) ) ) );
+ lineSymbolLayer->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( itemElem.attribute( QStringLiteral( "lineCapStyle" ), QStringLiteral( "square" ) ) ) );
//stroke color
QDomNodeList strokeColorList = itemElem.elementsByTagName( QStringLiteral( "strokeColor" ) );
if ( !strokeColorList.isEmpty() )
@@ -1104,19 +1110,15 @@ bool QgsCompositionConverter::readScaleBarXml( QgsLayoutItemScaleBar *layoutItem
if ( redOk && greenOk && blueOk && alphaOk )
{
- layoutItem->setLineColor( QColor( strokeRed, strokeGreen, strokeBlue, strokeAlpha ) );
- QPen p = layoutItem->mSettings.pen();
- p.setColor( layoutItem->mSettings.lineColor() );
- layoutItem->setPen( p );
+ lineSymbolLayer->setColor( QColor( strokeRed, strokeGreen, strokeBlue, strokeAlpha ) );
}
}
else
{
- layoutItem->setLineColor( QColor( itemElem.attribute( QStringLiteral( "penColor" ), QStringLiteral( "#000000" ) ) ) );
- QPen p = layoutItem->mSettings.pen();
- p.setColor( layoutItem->mSettings.lineColor() );
- layoutItem->setPen( p );
+ lineSymbolLayer->setColor( QColor( itemElem.attribute( QStringLiteral( "penColor" ), QStringLiteral( "#000000" ) ) ) );
}
+ lineSymbol->changeSymbolLayer( 0, lineSymbolLayer.release() );
+ layoutItem->setLineSymbol( lineSymbol.release() );
//font color
QDomNodeList textColorList = itemElem.elementsByTagName( QStringLiteral( "textColor" ) );
diff --git a/src/core/layout/qgslayoutitemattributetable.cpp b/src/core/layout/qgslayoutitemattributetable.cpp
index dbd208984bfb..43c71978f391 100644
--- a/src/core/layout/qgslayoutitemattributetable.cpp
+++ b/src/core/layout/qgslayoutitemattributetable.cpp
@@ -32,45 +32,6 @@
#include "qgsgeometryengine.h"
#include "qgsconditionalstyle.h"
-//QgsLayoutAttributeTableCompare
-
-///@cond PRIVATE
-
-/**
- * Helper class for sorting tables, takes into account sorting column and ascending / descending
-*/
-class CORE_EXPORT QgsLayoutAttributeTableCompare
-{
- public:
-
- /**
- * Constructor for QgsLayoutAttributeTableCompare.
- */
- QgsLayoutAttributeTableCompare() = default;
- bool operator()( const QVector< QPair< QVariant, QgsConditionalStyle > > &m1, const QVector< QPair< QVariant, QgsConditionalStyle > > &m2 )
- {
- return ( mAscending ? qgsVariantLessThan( m1[mCurrentSortColumn].first, m2[mCurrentSortColumn].first )
- : qgsVariantGreaterThan( m1[mCurrentSortColumn].first, m2[mCurrentSortColumn].first ) );
- }
-
- /**
- * Sets \a column number to sort by.
- */
- void setSortColumn( int column ) { mCurrentSortColumn = column; }
-
- /**
- * Sets sort order for column sorting
- * Set \a ascending to true to sort in ascending order, false to sort in descending order
- */
- void setAscending( bool ascending ) { mAscending = ascending; }
-
- private:
- int mCurrentSortColumn = 0;
- bool mAscending = true;
-};
-
-///@endcond
-
//
// QgsLayoutItemAttributeTable
//
@@ -519,6 +480,12 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont
req.setFilterFid( atlasFeature.id() );
}
+ QVector< QPair > sortColumns = sortAttributes();
+ for ( int i = sortColumns.size() - 1; i >= 0; --i )
+ {
+ req = req.addOrderBy( mColumns.at( sortColumns.at( i ).first )->attribute(), sortColumns.at( i ).second );
+ }
+
QgsFeature f;
int counter = 0;
QgsFeatureIterator fit = layer->getFeatures( req );
@@ -628,16 +595,6 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont
++counter;
}
- //sort the list, starting with the last attribute
- QgsLayoutAttributeTableCompare c;
- QVector< QPair > sortColumns = sortAttributes();
- for ( int i = sortColumns.size() - 1; i >= 0; --i )
- {
- c.setSortColumn( sortColumns.at( i ).first );
- c.setAscending( sortColumns.at( i ).second );
- std::stable_sort( tempContents.begin(), tempContents.end(), c );
- }
-
// build final table contents
contents.reserve( tempContents.size() );
mConditionalStyles.reserve( tempContents.size() );
diff --git a/src/core/layout/qgslayoutitemlegend.cpp b/src/core/layout/qgslayoutitemlegend.cpp
index e6cac9940aba..001bf6f16cd9 100644
--- a/src/core/layout/qgslayoutitemlegend.cpp
+++ b/src/core/layout/qgslayoutitemlegend.cpp
@@ -31,6 +31,7 @@
#include "qgssymbollayerutils.h"
#include "qgslayertreeutils.h"
#include "qgslayoututils.h"
+#include "qgsmapthemecollection.h"
#include
#include
#include
@@ -678,6 +679,7 @@ void QgsLayoutItemLegend::setupMapConnections( QgsLayoutItemMap *map, bool conne
disconnect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
disconnect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
disconnect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
+ disconnect( map, &QgsLayoutItemMap::themeChanged, this, &QgsLayoutItemLegend::mapThemeChanged );
}
else
{
@@ -685,6 +687,7 @@ void QgsLayoutItemLegend::setupMapConnections( QgsLayoutItemMap *map, bool conne
connect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
connect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
+ connect( map, &QgsLayoutItemMap::themeChanged, this, &QgsLayoutItemLegend::mapThemeChanged );
}
}
@@ -700,10 +703,10 @@ void QgsLayoutItemLegend::setLinkedMap( QgsLayoutItemMap *map )
if ( mMap )
{
setupMapConnections( mMap, true );
+ mapThemeChanged( mMap->themeToRender( mMap->createExpressionContext() ) );
}
updateFilterByMap();
-
}
void QgsLayoutItemLegend::invalidateCurrentMap()
@@ -752,6 +755,15 @@ void QgsLayoutItemLegend::updateFilterByMapAndRedraw()
updateFilterByMap( true );
}
+void QgsLayoutItemLegend::setModelStyleOverrides( const QMap &overrides )
+{
+ mLegendModel->setLayerStyleOverrides( overrides );
+ const QList< QgsLayerTreeLayer * > layers = mLegendModel->rootGroup()->findLayers();
+ for ( QgsLayerTreeLayer *nodeLayer : layers )
+ mLegendModel->refreshLayerLegend( nodeLayer );
+
+}
+
void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
{
if ( !mMap )
@@ -766,16 +778,47 @@ void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
}
else
{
- mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
- const QList< QgsLayerTreeLayer * > layers = mLegendModel->rootGroup()->findLayers();
- for ( QgsLayerTreeLayer *nodeLayer : layers )
- mLegendModel->refreshLayerLegend( nodeLayer );
+ setModelStyleOverrides( mMap->layerStyleOverrides() );
}
adjustBoxSize();
+
updateFilterByMap( false );
}
+void QgsLayoutItemLegend::mapThemeChanged( const QString &theme )
+{
+ if ( mThemeName == theme )
+ return;
+
+ mThemeName = theme;
+
+ // map's theme has been changed, so make sure to update the legend here
+ if ( mLegendFilterByMap )
+ {
+ // 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( false );
+ }
+ else
+ {
+ if ( mThemeName.isEmpty() )
+ {
+ setModelStyleOverrides( QMap() );
+ }
+ else
+ {
+ // get style overrides for theme
+ const QMap overrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( mThemeName );
+ setModelStyleOverrides( overrides );
+ }
+ }
+
+ adjustBoxSize();
+
+ updateFilterByMap();
+}
+
void QgsLayoutItemLegend::updateFilterByMap( bool redraw )
{
// ask for update
@@ -790,7 +833,18 @@ void QgsLayoutItemLegend::updateFilterByMap( bool redraw )
void QgsLayoutItemLegend::doUpdateFilterByMap()
{
if ( mMap )
- mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
+ {
+ if ( !mThemeName.isEmpty() )
+ {
+ // get style overrides for theme
+ const QMap overrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( mThemeName );
+ mLegendModel->setLayerStyleOverrides( overrides );
+ }
+ else
+ {
+ mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
+ }
+ }
else
mLegendModel->setLayerStyleOverrides( QMap() );
@@ -821,6 +875,11 @@ void QgsLayoutItemLegend::doUpdateFilterByMap()
mForceResize = true;
}
+QString QgsLayoutItemLegend::themeName() const
+{
+ return mThemeName;
+}
+
void QgsLayoutItemLegend::setLegendFilterOutAtlas( bool doFilter )
{
mFilterOutAtlas = doFilter;
diff --git a/src/core/layout/qgslayoutitemlegend.h b/src/core/layout/qgslayoutitemlegend.h
index eb3d3b7f87a8..291ee3aa9191 100644
--- a/src/core/layout/qgslayoutitemlegend.h
+++ b/src/core/layout/qgslayoutitemlegend.h
@@ -476,6 +476,15 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
*/
QgsLayoutItemMap *linkedMap() const { return mMap; }
+ /**
+ * Returns the name of the theme currently linked to the legend.
+ *
+ * This usually equates to the theme rendered in the linkedMap().
+ *
+ * \since QGIS 3.14
+ */
+ QString themeName() const;
+
/**
* Updates the model and all legend entries.
*/
@@ -518,6 +527,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
//! update legend in case style of associated map has changed
void mapLayerStyleOverridesChanged();
+ //! update legend in case theme of associated map has changed
+ void mapThemeChanged( const QString &theme );
//! react to atlas
void onAtlasEnded();
@@ -534,6 +545,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
void setupMapConnections( QgsLayoutItemMap *map, bool connect = true );
+ void setModelStyleOverrides( const QMap &overrides );
+
std::unique_ptr< QgsLegendModel > mLegendModel;
std::unique_ptr< QgsLayerTreeGroup > mCustomLayerTree;
@@ -567,6 +580,9 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
//! Will be TRUE if the legend should be resized automatically to fit contents
bool mSizeToContents = true;
+ //! Name of theme for legend -- usually the theme associated with the linked map.
+ QString mThemeName;
+
friend class QgsCompositionConverter;
};
diff --git a/src/core/layout/qgslayoutitemmap.cpp b/src/core/layout/qgslayoutitemmap.cpp
index 166f91792a09..5959fe19c6e6 100644
--- a/src/core/layout/qgslayoutitemmap.cpp
+++ b/src/core/layout/qgslayoutitemmap.cpp
@@ -309,6 +309,26 @@ void QgsLayoutItemMap::storeCurrentLayerStyles()
}
}
+void QgsLayoutItemMap::setFollowVisibilityPreset( bool follow )
+{
+ if ( mFollowVisibilityPreset == follow )
+ return;
+
+ mFollowVisibilityPreset = follow;
+ if ( !mFollowVisibilityPresetName.isEmpty() )
+ emit themeChanged( mFollowVisibilityPreset ? mFollowVisibilityPresetName : QString() );
+}
+
+void QgsLayoutItemMap::setFollowVisibilityPresetName( const QString &name )
+{
+ if ( name == mFollowVisibilityPresetName )
+ return;
+
+ mFollowVisibilityPresetName = name;
+ if ( mFollowVisibilityPreset )
+ emit themeChanged( mFollowVisibilityPresetName );
+}
+
void QgsLayoutItemMap::moveContent( double dx, double dy )
{
mLastRenderedImageOffsetX -= dx;
@@ -1718,6 +1738,13 @@ void QgsLayoutItemMap::refreshDataDefinedProperty( const QgsLayoutObject::DataDe
{
refreshLabelMargin( false );
}
+ if ( property == QgsLayoutObject::MapStylePreset || property == QgsLayoutObject::AllProperties )
+ {
+ const QString previousTheme = mLastEvaluatedThemeName.isEmpty() ? mFollowVisibilityPresetName : mLastEvaluatedThemeName;
+ mLastEvaluatedThemeName = mDataDefinedProperties.valueAsString( QgsLayoutObject::MapStylePreset, context, mFollowVisibilityPresetName );
+ if ( mLastEvaluatedThemeName != previousTheme )
+ emit themeChanged( mLastEvaluatedThemeName );
+ }
//force redraw
mCacheInvalidated = true;
diff --git a/src/core/layout/qgslayoutitemmap.h b/src/core/layout/qgslayoutitemmap.h
index 8ccdcebb1800..21049775ed49 100644
--- a/src/core/layout/qgslayoutitemmap.h
+++ b/src/core/layout/qgslayoutitemmap.h
@@ -285,20 +285,24 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
/**
* Sets whether the map should follow a map theme. See followVisibilityPreset() for more details.
*/
- void setFollowVisibilityPreset( bool follow ) { mFollowVisibilityPreset = follow; }
+ void setFollowVisibilityPreset( bool follow );
/**
* Preset name that decides which layers and layer styles are used for map rendering. It is only
* used when followVisibilityPreset() returns TRUE.
* \see setFollowVisibilityPresetName()
+ *
+ * \see themeChanged()
*/
QString followVisibilityPresetName() const { return mFollowVisibilityPresetName; }
/**
* Sets preset name for map rendering. See followVisibilityPresetName() for more details.
* \see followVisibilityPresetName()
+ *
+ * \see themeChanged()
*/
- void setFollowVisibilityPresetName( const QString &name ) { mFollowVisibilityPresetName = name; }
+ void setFollowVisibilityPresetName( const QString &name );
void moveContent( double dx, double dy ) override;
void setMoveContentPreviewOffset( double dx, double dy ) override;
@@ -602,6 +606,16 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
*/
void layerStyleOverridesChanged();
+ /**
+ * Emitted when the map's associated \a theme is changed.
+ *
+ * \note This signal is not emitted when the definition of the theme changes, only the map
+ * is linked to a different theme then it previously was.
+ *
+ * \since QGIS 3.14
+ */
+ void themeChanged( const QString &theme );
+
public slots:
void refresh() override;
@@ -716,6 +730,9 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
* is TRUE. May be overridden by data-defined expression. */
QString mFollowVisibilityPresetName;
+ //! Name of the last data-defined evaluated theme name
+ QString mLastEvaluatedThemeName;
+
/**
* \brief Draw to paint device
* \param painter painter
diff --git a/src/core/layout/qgslayoutitemmarker.cpp b/src/core/layout/qgslayoutitemmarker.cpp
new file mode 100644
index 000000000000..011fbba021fa
--- /dev/null
+++ b/src/core/layout/qgslayoutitemmarker.cpp
@@ -0,0 +1,254 @@
+/***************************************************************************
+ qgslayoutitemmarker.cpp
+ ---------------------
+ begin : April 2020
+ copyright : (C) 2020 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 "qgslayoutitemmarker.h"
+#include "qgslayout.h"
+#include "qgslayoututils.h"
+#include "qgssymbollayerutils.h"
+#include "qgslayoutmodel.h"
+#include "qgsstyleentityvisitor.h"
+#include "qgslayoutitemmap.h"
+
+#include
+
+QgsLayoutItemMarker::QgsLayoutItemMarker( QgsLayout *layout )
+ : QgsLayoutItem( layout )
+ , mNorthArrowHandler( new QgsLayoutNorthArrowHandler( this ) )
+{
+ setBackgroundEnabled( false );
+ setFrameEnabled( false );
+ setReferencePoint( QgsLayoutItem::Middle );
+ QgsStringMap properties;
+ properties.insert( QStringLiteral( "size" ), QStringLiteral( "4" ) );
+ mShapeStyleSymbol.reset( QgsMarkerSymbol::createSimple( properties ) );
+ refreshSymbol();
+
+ connect( this, &QgsLayoutItemMarker::sizePositionChanged, this, [ = ]
+ {
+ updateBoundingRect();
+ update();
+ } );
+
+ connect( mNorthArrowHandler, &QgsLayoutNorthArrowHandler::arrowRotationChanged, this, &QgsLayoutItemMarker::northArrowRotationChanged );
+}
+
+QgsLayoutItemMarker::~QgsLayoutItemMarker() = default;
+
+QgsLayoutItemMarker *QgsLayoutItemMarker::create( QgsLayout *layout )
+{
+ return new QgsLayoutItemMarker( layout );
+}
+
+int QgsLayoutItemMarker::type() const
+{
+ return QgsLayoutItemRegistry::LayoutMarker;
+}
+
+QIcon QgsLayoutItemMarker::icon() const
+{
+ return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemMarker.svg" ) );
+}
+
+void QgsLayoutItemMarker::refreshSymbol()
+{
+ if ( layout() )
+ {
+ QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( layout(), nullptr, layout()->renderContext().dpi() );
+
+ std::unique_ptr< QgsMarkerSymbol > sym( mShapeStyleSymbol->clone() );
+ sym->setAngle( sym->angle() + mNorthArrowRotation );
+ sym->startRender( rc );
+ QRectF bounds = sym->bounds( QPointF( 0, 0 ), rc );
+ sym->stopRender( rc );
+ mPoint = QPointF( -bounds.left() * 25.4 / layout()->renderContext().dpi(),
+ -bounds.top() * 25.4 / layout()->renderContext().dpi() );
+ bounds.translate( mPoint );
+
+ QgsLayoutSize newSizeMm = QgsLayoutSize( bounds.size() * 25.4 / layout()->renderContext().dpi(), QgsUnitTypes::LayoutMillimeters );
+ mFixedSize = mLayout->renderContext().measurementConverter().convert( newSizeMm, sizeWithUnits().units() );
+
+ attemptResize( mFixedSize );
+ }
+
+ updateBoundingRect();
+
+ update();
+ emit frameChanged();
+}
+
+void QgsLayoutItemMarker::updateBoundingRect()
+{
+ QRectF rectangle = rect();
+
+ // add a bit, to account for antialiasing on stroke and miter effects on stroke
+ rectangle.adjust( -5, -5, 5, 5 );
+ if ( rectangle != mCurrentRectangle )
+ {
+ prepareGeometryChange();
+ mCurrentRectangle = rectangle;
+ }
+}
+
+void QgsLayoutItemMarker::northArrowRotationChanged( double rotation )
+{
+ mNorthArrowRotation = rotation;
+ refreshSymbol();
+}
+
+void QgsLayoutItemMarker::setSymbol( QgsMarkerSymbol *symbol )
+{
+ if ( !symbol )
+ return;
+
+ mShapeStyleSymbol.reset( symbol );
+ refreshSymbol();
+}
+
+QgsMarkerSymbol *QgsLayoutItemMarker::symbol()
+{
+ return mShapeStyleSymbol.get();
+}
+
+void QgsLayoutItemMarker::setLinkedMap( QgsLayoutItemMap *map )
+{
+ mNorthArrowHandler->setLinkedMap( map );
+}
+
+QgsLayoutItemMap *QgsLayoutItemMarker::linkedMap() const
+{
+ return mNorthArrowHandler->linkedMap();
+}
+
+QgsLayoutNorthArrowHandler::NorthMode QgsLayoutItemMarker::northMode() const
+{
+ return mNorthArrowHandler->northMode();
+
+}
+
+void QgsLayoutItemMarker::setNorthMode( QgsLayoutNorthArrowHandler::NorthMode mode )
+{
+ mNorthArrowHandler->setNorthMode( mode );
+
+}
+
+double QgsLayoutItemMarker::northOffset() const
+{
+ return mNorthArrowHandler->northOffset();
+}
+
+void QgsLayoutItemMarker::setNorthOffset( double offset )
+{
+ mNorthArrowHandler->setNorthOffset( offset );
+}
+
+QRectF QgsLayoutItemMarker::boundingRect() const
+{
+ return mCurrentRectangle;
+}
+
+QgsLayoutSize QgsLayoutItemMarker::fixedSize() const
+{
+ return mFixedSize;
+}
+
+bool QgsLayoutItemMarker::accept( QgsStyleEntityVisitorInterface *visitor ) const
+{
+ if ( mShapeStyleSymbol )
+ {
+ QgsStyleSymbolEntity entity( mShapeStyleSymbol.get() );
+ if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, uuid(), displayName() ) ) )
+ return false;
+ }
+
+ return true;
+}
+
+void QgsLayoutItemMarker::draw( QgsLayoutItemRenderContext &context )
+{
+ QPainter *painter = context.renderContext().painter();
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( Qt::NoBrush );
+
+ double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
+
+ QPointF shapePoint = mPoint * scale;
+
+ std::unique_ptr< QgsMarkerSymbol > sym( mShapeStyleSymbol->clone() );
+ sym->setAngle( sym->angle() + mNorthArrowRotation );
+ sym->startRender( context.renderContext() );
+ sym->renderPoint( shapePoint, nullptr, context.renderContext() );
+ sym->stopRender( context.renderContext() );
+}
+
+bool QgsLayoutItemMarker::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
+{
+ QDomElement shapeStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mShapeStyleSymbol.get(), document, context );
+ element.appendChild( shapeStyleElem );
+
+ //rotation
+ element.setAttribute( QStringLiteral( "arrowRotation" ), QString::number( mNorthArrowRotation ) );
+ if ( !mNorthArrowHandler->linkedMap() )
+ {
+ element.setAttribute( QStringLiteral( "mapUuid" ), QString() );
+ }
+ else
+ {
+ element.setAttribute( QStringLiteral( "mapUuid" ), mNorthArrowHandler->linkedMap()->uuid() );
+ }
+ element.setAttribute( QStringLiteral( "northMode" ), mNorthArrowHandler->northMode() );
+ element.setAttribute( QStringLiteral( "northOffset" ), mNorthArrowHandler->northOffset() );
+
+ return true;
+}
+
+bool QgsLayoutItemMarker::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
+{
+ QDomElement shapeStyleSymbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
+ if ( !shapeStyleSymbolElem.isNull() )
+ {
+ mShapeStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol( shapeStyleSymbolElem, context ) );
+ }
+
+ //picture rotation
+ if ( !qgsDoubleNear( element.attribute( QStringLiteral( "arrowRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
+ {
+ mNorthArrowRotation = element.attribute( QStringLiteral( "arrowRotation" ), QStringLiteral( "0" ) ).toDouble();
+ }
+
+ //rotation map
+ mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( element.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
+ mNorthArrowHandler->setNorthOffset( element.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
+
+ mNorthArrowHandler->setLinkedMap( nullptr );
+ mRotationMapUuid = element.attribute( QStringLiteral( "mapUuid" ) );
+
+ refreshSymbol();
+
+ return true;
+}
+
+void QgsLayoutItemMarker::finalizeRestoreFromXml()
+{
+ if ( !mLayout || mRotationMapUuid.isEmpty() )
+ {
+ mNorthArrowHandler->setLinkedMap( nullptr );
+ }
+ else
+ {
+ mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
+ }
+ emit changed();
+}
diff --git a/src/core/layout/qgslayoutitemmarker.h b/src/core/layout/qgslayoutitemmarker.h
new file mode 100644
index 000000000000..be07d397467b
--- /dev/null
+++ b/src/core/layout/qgslayoutitemmarker.h
@@ -0,0 +1,165 @@
+/***************************************************************************
+ qgslayoutitemmarker.h
+ ---------------------
+ begin : April 2020
+ copyright : (C) 2020 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************/
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGSLAYOUTITEMMARKER_H
+#define QGSLAYOUTITEMMARKER_H
+
+#include "qgis_core.h"
+#include "qgslayoutitem.h"
+#include "qgslayoutitemregistry.h"
+#include "qgslayoutnortharrowhandler.h"
+
+class QgsMarkerSymbol;
+
+/**
+ * \ingroup core
+ * \class QgsLayoutItemMarker
+ * \brief A layout item for showing marker symbols.
+ * \since QGIS 3.14
+ */
+class CORE_EXPORT QgsLayoutItemMarker : public QgsLayoutItem
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor for QgsLayoutItemMarker, with the specified parent \a layout.
+ */
+ explicit QgsLayoutItemMarker( QgsLayout *layout );
+ ~QgsLayoutItemMarker() override;
+
+ /**
+ * Returns a new marker item for the specified \a layout.
+ *
+ * The caller takes responsibility for deleting the returned object.
+ */
+ static QgsLayoutItemMarker *create( QgsLayout *layout ) SIP_FACTORY;
+
+
+ int type() const override;
+ QIcon icon() const override;
+
+ /**
+ * Sets the marker \a symbol used to draw the shape. Ownership is transferred.
+ * \see symbol()
+ */
+ void setSymbol( QgsMarkerSymbol *symbol SIP_TRANSFER );
+
+ /**
+ * Returns the marker symbol used to draw the shape.
+ * \see setSymbol()
+ */
+ QgsMarkerSymbol *symbol();
+
+ /**
+ * Sets the \a map object for rotation.
+ *
+ * If this is set then the marker will be rotated by the same
+ * amount as the specified map object. This is useful especially for
+ * syncing north arrows with a map item.
+ *
+ * \see linkedMap()
+ */
+ void setLinkedMap( QgsLayoutItemMap *map );
+
+ /**
+ * Returns the linked rotation map, if set. An NULLPTR means map rotation is
+ * disabled. If this is set then the marker is rotated by the same amount
+ * as the specified map object.
+ * \see setLinkedMap()
+ */
+ QgsLayoutItemMap *linkedMap() const;
+
+ /**
+ * When the marker is linked to a map in north arrow rotation mode,
+ * returns the current north arrow rotation for the marker.
+ *
+ * \see setLinkedMap()
+ */
+ double northArrowRotation() const { return mNorthArrowRotation; }
+
+ /**
+ * Returns the mode used to align the marker to a map's North.
+ * \see setNorthMode()
+ * \see northOffset()
+ */
+ QgsLayoutNorthArrowHandler::NorthMode northMode() const;
+
+ /**
+ * Sets the \a mode used to align the marker to a map's North.
+ * \see northMode()
+ * \see setNorthOffset()
+ */
+ void setNorthMode( QgsLayoutNorthArrowHandler::NorthMode mode );
+
+ /**
+ * Returns the offset added to the marker's rotation from a map's North.
+ * \see setNorthOffset()
+ * \see northMode()
+ */
+ double northOffset() const;
+
+ /**
+ * Sets the \a offset added to the marker's rotation from a map's North.
+ * \see northOffset()
+ * \see setNorthMode()
+ */
+ void setNorthOffset( double offset );
+
+ // Depending on the symbol style, the bounding rectangle can be larger than the shape
+ QRectF boundingRect() const override;
+
+ QgsLayoutSize fixedSize() const override;
+
+ bool accept( QgsStyleEntityVisitorInterface *visitor ) const override;
+
+ protected:
+
+ void draw( QgsLayoutItemRenderContext &context ) override;
+
+ bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override;
+ bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) override;
+
+ void finalizeRestoreFromXml() override;
+ private slots:
+
+ /**
+ * Should be called after the shape's symbol is changed. Redraws the shape and recalculates
+ * its selection bounds.
+ */
+ void refreshSymbol();
+
+ //! Updates the bounding rect of this item
+ void updateBoundingRect();
+
+ void northArrowRotationChanged( double rotation );
+
+ private:
+
+ std::unique_ptr< QgsMarkerSymbol > mShapeStyleSymbol;
+
+ QPointF mPoint;
+ QRectF mCurrentRectangle;
+ QgsLayoutSize mFixedSize;
+
+ QString mRotationMapUuid;
+ QgsLayoutNorthArrowHandler *mNorthArrowHandler = nullptr;
+ double mNorthArrowRotation = 0;
+};
+
+
+#endif //QGSLAYOUTITEMMARKER_H
diff --git a/src/core/layout/qgslayoutitempicture.cpp b/src/core/layout/qgslayoutitempicture.cpp
index 182a8d3a814a..244ba1ec57d2 100644
--- a/src/core/layout/qgslayoutitempicture.cpp
+++ b/src/core/layout/qgslayoutitempicture.cpp
@@ -35,6 +35,8 @@
#include "qgsbearingutils.h"
#include "qgsmapsettings.h"
#include "qgsreadwritecontext.h"
+#include "qgsimagecache.h"
+#include "qgslayoutnortharrowhandler.h"
#include
#include
@@ -49,6 +51,7 @@
QgsLayoutItemPicture::QgsLayoutItemPicture( QgsLayout *layout )
: QgsLayoutItem( layout )
+ , mNorthArrowHandler( new QgsLayoutNorthArrowHandler( this ) )
{
//default to no background
setBackgroundEnabled( false );
@@ -63,6 +66,7 @@ QgsLayoutItemPicture::QgsLayoutItemPicture( QgsLayout *layout )
connect( &layout->renderContext(), &QgsLayoutRenderContext::dpiChanged, this, &QgsLayoutItemPicture::recalculateSize );
connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemPicture::shapeChanged );
+ connect( mNorthArrowHandler, &QgsLayoutNorthArrowHandler::arrowRotationChanged, this, &QgsLayoutItemPicture::updateNorthArrowRotation );
}
int QgsLayoutItemPicture::type() const
@@ -344,6 +348,7 @@ void QgsLayoutItemPicture::refreshPicture( const QgsExpressionContext *context )
mHasExpressionError = false;
if ( mDataDefinedProperties.isActive( QgsLayoutObject::PictureSource ) )
{
+ mMode = FormatUnknown;
bool ok = false;
const QgsProperty &sourceProperty = mDataDefinedProperties.property( QgsLayoutObject::PictureSource );
source = sourceProperty.value( *evalContext, source, &ok );
@@ -439,52 +444,49 @@ void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
}
}
-void QgsLayoutItemPicture::disconnectMap( QgsLayoutItemMap *map )
+void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
{
- if ( map )
+ switch ( mMode )
{
- disconnect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- disconnect( map, &QgsLayoutItemMap::rotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- disconnect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- }
-}
-
-void QgsLayoutItemPicture::updateMapRotation()
-{
- if ( !mRotationMap )
- return;
-
- // take map rotation
- double rotation = mRotationMap->mapRotation() + mRotationMap->rotation();
-
- // handle true north
- switch ( mNorthMode )
- {
- case GridNorth:
- break; // nothing to do
+ case FormatUnknown:
+ break;
- case TrueNorth:
+ case FormatRaster:
{
- QgsPointXY center = mRotationMap->extent().center();
- QgsCoordinateReferenceSystem crs = mRotationMap->crs();
- QgsCoordinateTransformContext transformContext = mLayout->project()->transformContext();
+ bool fitsInCache = false;
+ mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true );
+ break;
+ }
- try
+ case FormatSVG:
+ {
+ QgsExpressionContext context = createExpressionContext();
+ QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
+ QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
+ double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
+ const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
+ 1.0 );
+ mSVG.load( svgContent );
+ if ( mSVG.isValid() )
{
- double bearing = QgsBearingUtils::bearingTrueNorth( crs, transformContext, center );
- rotation += bearing;
+ mMode = FormatSVG;
+ QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
+ mDefaultSvgSize.setWidth( viewBox.width() );
+ mDefaultSvgSize.setHeight( viewBox.height() );
}
- catch ( QgsException &e )
+ else
{
- Q_UNUSED( e )
- QgsDebugMsg( QStringLiteral( "Caught exception %1" ).arg( e.what() ) );
+ mMode = FormatUnknown;
}
break;
}
}
+}
- rotation += mNorthOffset;
- setPictureRotation( ( rotation > 360.0 ) ? rotation - 360.0 : rotation );
+void QgsLayoutItemPicture::updateNorthArrowRotation( double rotation )
+{
+ setPictureRotation( rotation );
+ emit pictureRotationChanged( rotation );
}
void QgsLayoutItemPicture::loadPicture( const QVariant &data )
@@ -493,7 +495,7 @@ void QgsLayoutItemPicture::loadPicture( const QVariant &data )
QVariant imageData( data );
mEvaluatedPath = data.toString();
- if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) )
+ if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) && mMode == FormatUnknown )
{
QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
@@ -506,16 +508,21 @@ void QgsLayoutItemPicture::loadPicture( const QVariant &data )
mMode = FormatRaster;
}
}
- else if ( mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
+ else if ( mMode == FormatUnknown && mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
{
- //remote location
+ //remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
loadRemotePicture( mEvaluatedPath );
}
- else
+ else if ( mMode == FormatUnknown )
{
- //local location
+ //local location - for old API/project compatibility only!!
loadLocalPicture( mEvaluatedPath );
}
+ else
+ {
+ loadPictureUsingCache( mEvaluatedPath );
+ }
+
if ( mMode != FormatUnknown ) //make sure we start with a new QImage
{
recalculateSize();
@@ -649,25 +656,7 @@ void QgsLayoutItemPicture::setPictureRotation( double rotation )
void QgsLayoutItemPicture::setLinkedMap( QgsLayoutItemMap *map )
{
- if ( mRotationMap )
- {
- disconnectMap( mRotationMap );
- }
-
- if ( !map ) //disable rotation from map
- {
- mRotationMap = nullptr;
- }
- else
- {
- mPictureRotation = map->mapRotation();
- connect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- connect( map, &QgsLayoutItemMap::rotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- mRotationMap = map;
- updateMapRotation();
- emit pictureRotationChanged( mPictureRotation );
- }
+ mNorthArrowHandler->setLinkedMap( map );
}
void QgsLayoutItemPicture::setResizeMode( QgsLayoutItemPicture::ResizeMode mode )
@@ -702,8 +691,9 @@ void QgsLayoutItemPicture::refreshDataDefinedProperty( const QgsLayoutObject::Da
QgsLayoutItem::refreshDataDefinedProperty( property );
}
-void QgsLayoutItemPicture::setPicturePath( const QString &path )
+void QgsLayoutItemPicture::setPicturePath( const QString &path, Format format )
{
+ mMode = format;
mSourcePath = path;
refreshPicture();
}
@@ -732,19 +722,20 @@ bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocu
elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
+ elem.setAttribute( QStringLiteral( "mode" ), mMode );
//rotation
elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
- if ( !mRotationMap )
+ if ( !mNorthArrowHandler->linkedMap() )
{
elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
}
else
{
- elem.setAttribute( QStringLiteral( "mapUuid" ), mRotationMap->uuid() );
+ elem.setAttribute( QStringLiteral( "mapUuid" ), mNorthArrowHandler->linkedMap()->uuid() );
}
- elem.setAttribute( QStringLiteral( "northMode" ), mNorthMode );
- elem.setAttribute( QStringLiteral( "northOffset" ), mNorthOffset );
+ elem.setAttribute( QStringLiteral( "northMode" ), mNorthArrowHandler->northMode() );
+ elem.setAttribute( QStringLiteral( "northOffset" ), mNorthArrowHandler->northOffset() );
return true;
}
@@ -759,6 +750,7 @@ bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemEle
mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
+ mMode = static_cast< Format >( itemElem.attribute( QStringLiteral( "mode" ), QString::number( FormatUnknown ) ).toInt() );
QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
if ( !composerItemList.isEmpty() )
@@ -803,11 +795,10 @@ bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemEle
}
//rotation map
- mNorthMode = static_cast< NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
- mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
+ mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
+ mNorthArrowHandler->setNorthOffset( itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
- disconnectMap( mRotationMap );
- mRotationMap = nullptr;
+ mNorthArrowHandler->setLinkedMap( nullptr );
mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
return true;
@@ -815,19 +806,27 @@ bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemEle
QgsLayoutItemMap *QgsLayoutItemPicture::linkedMap() const
{
- return mRotationMap;
+ return mNorthArrowHandler->linkedMap();
+}
+
+QgsLayoutItemPicture::NorthMode QgsLayoutItemPicture::northMode() const
+{
+ return static_cast< QgsLayoutItemPicture::NorthMode >( mNorthArrowHandler->northMode() );
}
void QgsLayoutItemPicture::setNorthMode( QgsLayoutItemPicture::NorthMode mode )
{
- mNorthMode = mode;
- updateMapRotation();
+ mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( mode ) );
+}
+
+double QgsLayoutItemPicture::northOffset() const
+{
+ return mNorthArrowHandler->northOffset();
}
void QgsLayoutItemPicture::setNorthOffset( double offset )
{
- mNorthOffset = offset;
- updateMapRotation();
+ mNorthArrowHandler->setNorthOffset( offset );
}
void QgsLayoutItemPicture::setPictureAnchor( ReferencePoint anchor )
@@ -854,24 +853,24 @@ void QgsLayoutItemPicture::setSvgStrokeWidth( double width )
refreshPicture();
}
+void QgsLayoutItemPicture::setMode( QgsLayoutItemPicture::Format mode )
+{
+ if ( mMode == mode )
+ return;
+
+ mMode = mode;
+ refreshPicture();
+}
+
void QgsLayoutItemPicture::finalizeRestoreFromXml()
{
if ( !mLayout || mRotationMapUuid.isEmpty() )
{
- mRotationMap = nullptr;
+ mNorthArrowHandler->setLinkedMap( nullptr );
}
else
{
- if ( mRotationMap )
- {
- disconnectMap( mRotationMap );
- }
- if ( ( mRotationMap = qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) ) )
- {
- connect( mRotationMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- connect( mRotationMap, &QgsLayoutItemMap::rotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- connect( mRotationMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemPicture::updateMapRotation );
- }
+ mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
}
refreshPicture();
diff --git a/src/core/layout/qgslayoutitempicture.h b/src/core/layout/qgslayoutitempicture.h
index ae9a7e7b64e3..1f231fc33cb8 100644
--- a/src/core/layout/qgslayoutitempicture.h
+++ b/src/core/layout/qgslayoutitempicture.h
@@ -25,6 +25,7 @@ email : nyall dot dawson at gmail dot com
class QgsLayoutItemMap;
class QgsExpression;
+class QgsLayoutNorthArrowHandler;
/**
* \ingroup core
@@ -85,9 +86,12 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
* Sets the source \a path of the image (may be svg or a raster format). Data defined
* picture source may override this value. The path can either be a local path
* or a remote (http) path.
+ *
+ * Ideally, the \a format argument should specify the image format.
+ *
* \see picturePath()
*/
- void setPicturePath( const QString &path );
+ void setPicturePath( const QString &path, Format format = FormatUnknown );
/**
* Returns the path of the source image. Data defined picture source may override
@@ -131,7 +135,7 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
* \see setNorthMode()
* \see northOffset()
*/
- NorthMode northMode() const { return mNorthMode; }
+ NorthMode northMode() const;
/**
* Sets the \a mode used to align the picture to a map's North.
@@ -145,7 +149,7 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
* \see setNorthOffset()
* \see northMode()
*/
- double northOffset() const { return mNorthOffset; }
+ double northOffset() const;
/**
* Sets the \a offset added to the picture's rotation from a map's North.
@@ -226,9 +230,17 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
/**
* Returns the current picture mode (image format).
+ * \see setMode()
*/
Format mode() const { return mMode; }
+ /**
+ * Sets the current picture \a mode (image format).
+ * \see mode()
+ * \since QGIS 3.14
+ */
+ void setMode( Format mode );
+
void finalizeRestoreFromXml() override;
/**
@@ -313,13 +325,6 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
double mPictureRotation = 0;
QString mRotationMapUuid;
- //! Map that sets the rotation (or NULLPTR if this picture uses map independent rotation)
- QPointer< QgsLayoutItemMap > mRotationMap;
-
- //! Mode used to align to North
- NorthMode mNorthMode = GridNorth;
- //! Offset for north arrow
- double mNorthOffset = 0.0;
//! Width of the picture (in mm)
double mPictureWidth = 0.0;
@@ -339,6 +344,8 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
bool mIsMissingImage = false;
QString mEvaluatedPath;
+ QgsLayoutNorthArrowHandler *mNorthArrowHandler = nullptr;
+
//! Loads an image file into the picture item and redraws the item
void loadPicture( const QVariant &data );
@@ -358,11 +365,11 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
*/
void loadLocalPicture( const QString &path );
- void disconnectMap( QgsLayoutItemMap *map );
+ void loadPictureUsingCache( const QString &path );
private slots:
- void updateMapRotation();
+ void updateNorthArrowRotation( double rotation );
void shapeChanged();
diff --git a/src/core/layout/qgslayoutitemregistry.cpp b/src/core/layout/qgslayoutitemregistry.cpp
index 5a9af00d7824..486facc236e8 100644
--- a/src/core/layout/qgslayoutitemregistry.cpp
+++ b/src/core/layout/qgslayoutitemregistry.cpp
@@ -30,6 +30,7 @@
#include "qgslayoutitemmanualtable.h"
#include "qgslayoutitemtexttable.h"
#include "qgslayoutframe.h"
+#include "qgslayoutitemmarker.h"
#include "qgsgloweffect.h"
#include "qgseffectstack.h"
#include "qgsvectorlayer.h"
@@ -76,6 +77,7 @@ bool QgsLayoutItemRegistry::populate()
shape->setShapeType( QgsLayoutItemShape::Rectangle );
return shape;
} ) );
+ addLayoutItemType( new QgsLayoutItemMetadata( LayoutMarker, QObject::tr( "Marker" ), QObject::tr( "Markers" ), QgsLayoutItemMarker::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPolygon, QObject::tr( "Polygon" ), QObject::tr( "Polygons" ), QgsLayoutItemPolygon::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPolyline, QObject::tr( "Polyline" ), QObject::tr( "Polylines" ), QgsLayoutItemPolyline::create ) );
diff --git a/src/core/layout/qgslayoutitemregistry.h b/src/core/layout/qgslayoutitemregistry.h
index 8a4779d52606..168b1e2f13e7 100644
--- a/src/core/layout/qgslayoutitemregistry.h
+++ b/src/core/layout/qgslayoutitemregistry.h
@@ -336,6 +336,7 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
Layout3DMap, //!< 3D map item
LayoutManualTable, //!< Manual (fixed) table
+ LayoutMarker, //!< Marker item
// item types provided by plugins
PluginItem = LayoutTextTable + 10000, //!< Starting point for plugin item types
diff --git a/src/core/layout/qgslayoutitemscalebar.cpp b/src/core/layout/qgslayoutitemscalebar.cpp
index 1bf0bb655279..7203b9610348 100644
--- a/src/core/layout/qgslayoutitemscalebar.cpp
+++ b/src/core/layout/qgslayoutitemscalebar.cpp
@@ -16,16 +16,14 @@
#include "qgslayoutitemscalebar.h"
#include "qgslayoutitemregistry.h"
+#include "qgsscalebarrendererregistry.h"
#include "qgslayoutitemmap.h"
#include "qgslayout.h"
#include "qgslayoututils.h"
#include "qgsdistancearea.h"
+#include "qgssingleboxscalebarrenderer.h"
#include "qgsscalebarrenderer.h"
-#include "qgsdoubleboxscalebarrenderer.h"
#include "qgsmapsettings.h"
-#include "qgsnumericscalebarrenderer.h"
-#include "qgssingleboxscalebarrenderer.h"
-#include "qgsticksscalebarrenderer.h"
#include "qgsrectangle.h"
#include "qgsproject.h"
#include "qgssymbollayerutils.h"
@@ -35,6 +33,8 @@
#include "qgsstyleentityvisitor.h"
#include "qgsnumericformat.h"
#include "qgsnumericformatregistry.h"
+#include "qgslinesymbollayer.h"
+#include "qgsfillsymbollayer.h"
#include
#include
@@ -67,7 +67,8 @@ QgsLayoutItemScaleBar *QgsLayoutItemScaleBar::create( QgsLayout *layout )
QgsLayoutSize QgsLayoutItemScaleBar::minimumSize() const
{
- return QgsLayoutSize( mStyle->calculateBoxSize( mSettings, createScaleContext() ), QgsUnitTypes::LayoutMillimeters );
+ QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, nullptr );
+ return QgsLayoutSize( mStyle->calculateBoxSize( context, mSettings, createScaleContext() ), QgsUnitTypes::LayoutMillimeters );
}
void QgsLayoutItemScaleBar::draw( QgsLayoutItemRenderContext &context )
@@ -75,6 +76,40 @@ void QgsLayoutItemScaleBar::draw( QgsLayoutItemRenderContext &context )
if ( !mStyle )
return;
+ if ( dataDefinedProperties().isActive( QgsLayoutObject::ScalebarLineColor ) || dataDefinedProperties().isActive( QgsLayoutObject::ScalebarLineWidth ) )
+ {
+ // compatibility code - ScalebarLineColor and ScalebarLineWidth are deprecated
+ QgsExpressionContext expContext = createExpressionContext();
+ std::unique_ptr< QgsLineSymbol > sym( mSettings.lineSymbol()->clone() );
+ Q_NOWARN_DEPRECATED_PUSH
+ if ( dataDefinedProperties().isActive( QgsLayoutObject::ScalebarLineWidth ) )
+ sym->setWidth( mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ScalebarLineWidth, expContext, mSettings.lineWidth() ) );
+ if ( dataDefinedProperties().isActive( QgsLayoutObject::ScalebarLineColor ) )
+ sym->setColor( mDataDefinedProperties.valueAsColor( QgsLayoutObject::ScalebarLineColor, expContext, mSettings.lineColor() ) );
+ Q_NOWARN_DEPRECATED_POP
+ mSettings.setLineSymbol( sym.release() );
+ }
+ if ( dataDefinedProperties().isActive( QgsLayoutObject::ScalebarFillColor ) )
+ {
+ // compatibility code - ScalebarLineColor and ScalebarLineWidth are deprecated
+ QgsExpressionContext expContext = createExpressionContext();
+ std::unique_ptr< QgsFillSymbol > sym( mSettings.fillSymbol()->clone() );
+ Q_NOWARN_DEPRECATED_PUSH
+ sym->setColor( mDataDefinedProperties.valueAsColor( QgsLayoutObject::ScalebarFillColor, expContext, mSettings.fillColor() ) );
+ Q_NOWARN_DEPRECATED_POP
+ mSettings.setFillSymbol( sym.release() );
+ }
+ if ( dataDefinedProperties().isActive( QgsLayoutObject::ScalebarFillColor2 ) )
+ {
+ // compatibility code - ScalebarLineColor and ScalebarLineWidth are deprecated
+ QgsExpressionContext expContext = createExpressionContext();
+ std::unique_ptr< QgsFillSymbol > sym( mSettings.alternateFillSymbol()->clone() );
+ Q_NOWARN_DEPRECATED_PUSH
+ sym->setColor( mDataDefinedProperties.valueAsColor( QgsLayoutObject::ScalebarFillColor2, expContext, mSettings.fillColor2() ) );
+ Q_NOWARN_DEPRECATED_POP
+ mSettings.setAlternateFillSymbol( sym.release() );
+ }
+
mStyle->draw( context.renderContext(), mSettings, createScaleContext() );
}
@@ -149,6 +184,36 @@ void QgsLayoutItemScaleBar::setTextFormat( const QgsTextFormat &format )
emit changed();
}
+QgsLineSymbol *QgsLayoutItemScaleBar::lineSymbol() const
+{
+ return mSettings.lineSymbol();
+}
+
+void QgsLayoutItemScaleBar::setLineSymbol( QgsLineSymbol *symbol )
+{
+ mSettings.setLineSymbol( symbol );
+}
+
+QgsFillSymbol *QgsLayoutItemScaleBar::fillSymbol() const
+{
+ return mSettings.fillSymbol();
+}
+
+void QgsLayoutItemScaleBar::setFillSymbol( QgsFillSymbol *symbol )
+{
+ mSettings.setFillSymbol( symbol );
+}
+
+QgsFillSymbol *QgsLayoutItemScaleBar::alternateFillSymbol() const
+{
+ return mSettings.alternateFillSymbol();
+}
+
+void QgsLayoutItemScaleBar::setAlternateFillSymbol( QgsFillSymbol *symbol )
+{
+ mSettings.setAlternateFillSymbol( symbol );
+}
+
void QgsLayoutItemScaleBar::setNumberOfSegmentsLeft( int nSegmentsLeft )
{
if ( !mStyle )
@@ -209,30 +274,18 @@ void QgsLayoutItemScaleBar::refreshDataDefinedProperty( const QgsLayoutObject::D
//updates data defined properties and redraws item to match
if ( property == QgsLayoutObject::ScalebarFillColor || property == QgsLayoutObject::AllProperties )
{
- QBrush b = mSettings.brush();
- b.setColor( mDataDefinedProperties.valueAsColor( QgsLayoutObject::ScalebarFillColor, context, mSettings.fillColor() ) );
- mSettings.setBrush( b );
forceUpdate = true;
}
if ( property == QgsLayoutObject::ScalebarFillColor2 || property == QgsLayoutObject::AllProperties )
{
- QBrush b = mSettings.brush2();
- b.setColor( mDataDefinedProperties.valueAsColor( QgsLayoutObject::ScalebarFillColor2, context, mSettings.fillColor2() ) );
- mSettings.setBrush2( b );
forceUpdate = true;
}
if ( property == QgsLayoutObject::ScalebarLineColor || property == QgsLayoutObject::AllProperties )
{
- QPen p = mSettings.pen();
- p.setColor( mDataDefinedProperties.valueAsColor( QgsLayoutObject::ScalebarLineColor, context, mSettings.lineColor() ) );
- mSettings.setPen( p );
forceUpdate = true;
}
if ( property == QgsLayoutObject::ScalebarLineWidth || property == QgsLayoutObject::AllProperties )
{
- QPen p = mSettings.pen();
- p.setWidthF( mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ScalebarLineWidth, context, mSettings.lineWidth() ) );
- mSettings.setPen( p );
forceUpdate = true;
}
if ( forceUpdate )
@@ -345,26 +398,44 @@ void QgsLayoutItemScaleBar::setUnits( QgsUnitTypes::DistanceUnit u )
emit changed();
}
+Qt::PenJoinStyle QgsLayoutItemScaleBar::lineJoinStyle() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.lineJoinStyle();
+ Q_NOWARN_DEPRECATED_POP
+}
+
void QgsLayoutItemScaleBar::setLineJoinStyle( Qt::PenJoinStyle style )
{
+ Q_NOWARN_DEPRECATED_PUSH
if ( mSettings.lineJoinStyle() == style )
{
//no change
return;
}
mSettings.setLineJoinStyle( style );
+ Q_NOWARN_DEPRECATED_POP
update();
emit changed();
}
+Qt::PenCapStyle QgsLayoutItemScaleBar::lineCapStyle() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.lineCapStyle();
+ Q_NOWARN_DEPRECATED_POP
+}
+
void QgsLayoutItemScaleBar::setLineCapStyle( Qt::PenCapStyle style )
{
+ Q_NOWARN_DEPRECATED_PUSH
if ( mSettings.lineCapStyle() == style )
{
//no change
return;
}
mSettings.setLineCapStyle( style );
+ Q_NOWARN_DEPRECATED_POP
update();
emit changed();
}
@@ -398,6 +469,11 @@ void QgsLayoutItemScaleBar::applyDefaultSettings()
emit changed();
}
+bool QgsLayoutItemScaleBar::applyDefaultRendererSettings( QgsScaleBarRenderer *renderer )
+{
+ return renderer->applyDefaultSettings( mSettings );
+}
+
QgsUnitTypes::DistanceUnit QgsLayoutItemScaleBar::guessUnits() const
{
if ( !mMap )
@@ -479,7 +555,8 @@ void QgsLayoutItemScaleBar::resizeToMinimumWidth()
if ( !mStyle )
return;
- double widthMM = mStyle->calculateBoxSize( mSettings, createScaleContext() ).width();
+ QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, nullptr );
+ double widthMM = mStyle->calculateBoxSize( context, mSettings, createScaleContext() ).width();
QgsLayoutSize currentSize = sizeWithUnits();
currentSize.setWidth( mLayout->renderContext().measurementConverter().convert( QgsLayoutMeasurement( widthMM, QgsUnitTypes::LayoutMillimeters ), currentSize.units() ).length() );
attemptResize( currentSize );
@@ -490,7 +567,7 @@ void QgsLayoutItemScaleBar::resizeToMinimumWidth()
void QgsLayoutItemScaleBar::update()
{
//Don't adjust box size for numeric scale bars:
- if ( mStyle && mStyle->name() != QLatin1String( "Numeric" ) )
+ if ( mStyle && mStyle->id() != QLatin1String( "Numeric" ) )
{
refreshItemSize();
}
@@ -507,34 +584,10 @@ void QgsLayoutItemScaleBar::updateScale()
void QgsLayoutItemScaleBar::setStyle( const QString &styleName )
{
//switch depending on style name
- if ( styleName == QLatin1String( "Single Box" ) )
- {
- mStyle = qgis::make_unique< QgsSingleBoxScaleBarRenderer >();
- }
- else if ( styleName == QLatin1String( "Double Box" ) )
- {
- mStyle = qgis::make_unique< QgsDoubleBoxScaleBarRenderer >();
- }
- else if ( styleName == QLatin1String( "Line Ticks Middle" ) || styleName == QLatin1String( "Line Ticks Down" ) || styleName == QLatin1String( "Line Ticks Up" ) )
+ std::unique_ptr< QgsScaleBarRenderer> renderer( QgsApplication::scaleBarRendererRegistry()->renderer( styleName ) );
+ if ( renderer )
{
- std::unique_ptr< QgsTicksScaleBarRenderer > tickStyle = qgis::make_unique< QgsTicksScaleBarRenderer >();
- if ( styleName == QLatin1String( "Line Ticks Middle" ) )
- {
- tickStyle->setTickPosition( QgsTicksScaleBarRenderer::TicksMiddle );
- }
- else if ( styleName == QLatin1String( "Line Ticks Down" ) )
- {
- tickStyle->setTickPosition( QgsTicksScaleBarRenderer::TicksDown );
- }
- else if ( styleName == QLatin1String( "Line Ticks Up" ) )
- {
- tickStyle->setTickPosition( QgsTicksScaleBarRenderer::TicksUp );
- }
- mStyle = std::move( tickStyle );
- }
- else if ( styleName == QLatin1String( "Numeric" ) )
- {
- mStyle = qgis::make_unique< QgsNumericScaleBarRenderer >();
+ mStyle = std::move( renderer );
}
refreshItemSize();
emit changed();
@@ -544,7 +597,7 @@ QString QgsLayoutItemScaleBar::style() const
{
if ( mStyle )
{
- return mStyle->name();
+ return mStyle->id();
}
else
{
@@ -589,6 +642,83 @@ void QgsLayoutItemScaleBar::setFontColor( const QColor &color )
mSettings.textFormat().setOpacity( color.alphaF() );
}
+QColor QgsLayoutItemScaleBar::fillColor() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.fillColor();
+ Q_NOWARN_DEPRECATED_POP
+}
+
+void QgsLayoutItemScaleBar::setFillColor( const QColor &color )
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ mSettings.setFillColor( color );
+ Q_NOWARN_DEPRECATED_POP
+}
+
+QColor QgsLayoutItemScaleBar::fillColor2() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.fillColor2();
+ Q_NOWARN_DEPRECATED_POP
+}
+
+void QgsLayoutItemScaleBar::setFillColor2( const QColor &color )
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ mSettings.setFillColor2( color );
+ Q_NOWARN_DEPRECATED_POP
+}
+
+QColor QgsLayoutItemScaleBar::lineColor() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.lineColor();
+ Q_NOWARN_DEPRECATED_POP
+}
+
+void QgsLayoutItemScaleBar::setLineColor( const QColor &color )
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ mSettings.setLineColor( color );
+ Q_NOWARN_DEPRECATED_POP
+}
+
+double QgsLayoutItemScaleBar::lineWidth() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.lineWidth();
+ Q_NOWARN_DEPRECATED_POP
+}
+
+void QgsLayoutItemScaleBar::setLineWidth( double width )
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ mSettings.setLineWidth( width );
+ Q_NOWARN_DEPRECATED_POP
+}
+
+QPen QgsLayoutItemScaleBar::pen() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.pen();
+ Q_NOWARN_DEPRECATED_POP
+}
+
+QBrush QgsLayoutItemScaleBar::brush() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.brush();
+ Q_NOWARN_DEPRECATED_POP
+}
+
+QBrush QgsLayoutItemScaleBar::brush2() const
+{
+ Q_NOWARN_DEPRECATED_PUSH
+ return mSettings.brush2();
+ Q_NOWARN_DEPRECATED_POP
+}
+
bool QgsLayoutItemScaleBar::writePropertiesToElement( QDomElement &composerScaleBarElem, QDomDocument &doc, const QgsReadWriteContext &rwContext ) const
{
composerScaleBarElem.setAttribute( QStringLiteral( "height" ), QString::number( mSettings.height() ) );
@@ -606,11 +736,22 @@ bool QgsLayoutItemScaleBar::writePropertiesToElement( QDomElement &composerScale
QDomElement textElem = mSettings.textFormat().writeXml( doc, rwContext );
composerScaleBarElem.appendChild( textElem );
+ Q_NOWARN_DEPRECATED_PUSH
+ // kept just for allowing projects to open in QGIS < 3.14, remove for 4.0
composerScaleBarElem.setAttribute( QStringLiteral( "outlineWidth" ), QString::number( mSettings.lineWidth() ) );
- composerScaleBarElem.setAttribute( QStringLiteral( "unitLabel" ), mSettings.unitLabel() );
- composerScaleBarElem.setAttribute( QStringLiteral( "unitType" ), QgsUnitTypes::encodeUnit( mSettings.units() ) );
composerScaleBarElem.setAttribute( QStringLiteral( "lineJoinStyle" ), QgsSymbolLayerUtils::encodePenJoinStyle( mSettings.lineJoinStyle() ) );
composerScaleBarElem.setAttribute( QStringLiteral( "lineCapStyle" ), QgsSymbolLayerUtils::encodePenCapStyle( mSettings.lineCapStyle() ) );
+ //pen color
+ QDomElement strokeColorElem = doc.createElement( QStringLiteral( "strokeColor" ) );
+ strokeColorElem.setAttribute( QStringLiteral( "red" ), QString::number( mSettings.lineColor().red() ) );
+ strokeColorElem.setAttribute( QStringLiteral( "green" ), QString::number( mSettings.lineColor().green() ) );
+ strokeColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( mSettings.lineColor().blue() ) );
+ strokeColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( mSettings.lineColor().alpha() ) );
+ composerScaleBarElem.appendChild( strokeColorElem );
+ Q_NOWARN_DEPRECATED_POP
+
+ composerScaleBarElem.setAttribute( QStringLiteral( "unitLabel" ), mSettings.unitLabel() );
+ composerScaleBarElem.setAttribute( QStringLiteral( "unitType" ), QgsUnitTypes::encodeUnit( mSettings.units() ) );
QDomElement numericFormatElem = doc.createElement( QStringLiteral( "numericFormat" ) );
mSettings.numericFormat()->writeXml( numericFormatElem, doc, rwContext );
@@ -619,7 +760,7 @@ bool QgsLayoutItemScaleBar::writePropertiesToElement( QDomElement &composerScale
//style
if ( mStyle )
{
- composerScaleBarElem.setAttribute( QStringLiteral( "style" ), mStyle->name() );
+ composerScaleBarElem.setAttribute( QStringLiteral( "style" ), mStyle->id() );
}
//map id
@@ -630,6 +771,9 @@ bool QgsLayoutItemScaleBar::writePropertiesToElement( QDomElement &composerScale
//colors
+ Q_NOWARN_DEPRECATED_PUSH
+ // kept just for allowing projects to open in QGIS < 3.14, remove for 4.0
+
//fill color
QDomElement fillColorElem = doc.createElement( QStringLiteral( "fillColor" ) );
fillColorElem.setAttribute( QStringLiteral( "red" ), QString::number( mSettings.fillColor().red() ) );
@@ -646,13 +790,7 @@ bool QgsLayoutItemScaleBar::writePropertiesToElement( QDomElement &composerScale
fillColor2Elem.setAttribute( QStringLiteral( "alpha" ), QString::number( mSettings.fillColor2().alpha() ) );
composerScaleBarElem.appendChild( fillColor2Elem );
- //pen color
- QDomElement strokeColorElem = doc.createElement( QStringLiteral( "strokeColor" ) );
- strokeColorElem.setAttribute( QStringLiteral( "red" ), QString::number( mSettings.lineColor().red() ) );
- strokeColorElem.setAttribute( QStringLiteral( "green" ), QString::number( mSettings.lineColor().green() ) );
- strokeColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( mSettings.lineColor().blue() ) );
- strokeColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( mSettings.lineColor().alpha() ) );
- composerScaleBarElem.appendChild( strokeColorElem );
+ Q_NOWARN_DEPRECATED_POP
//label vertical/horizontal placement
composerScaleBarElem.setAttribute( QStringLiteral( "labelVerticalPlacement" ), QString::number( static_cast< int >( mSettings.labelVerticalPlacement() ) ) );
@@ -661,6 +799,30 @@ bool QgsLayoutItemScaleBar::writePropertiesToElement( QDomElement &composerScale
//alignment
composerScaleBarElem.setAttribute( QStringLiteral( "alignment" ), QString::number( static_cast< int >( mSettings.alignment() ) ) );
+ QDomElement lineSymbol = doc.createElement( QStringLiteral( "lineSymbol" ) );
+ const QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( QString(),
+ mSettings.lineSymbol(),
+ doc,
+ rwContext );
+ lineSymbol.appendChild( symbolElem );
+ composerScaleBarElem.appendChild( lineSymbol );
+
+ QDomElement fillSymbol1Elem = doc.createElement( QStringLiteral( "fillSymbol1" ) );
+ const QDomElement symbol1Elem = QgsSymbolLayerUtils::saveSymbol( QString(),
+ mSettings.fillSymbol(),
+ doc,
+ rwContext );
+ fillSymbol1Elem.appendChild( symbol1Elem );
+ composerScaleBarElem.appendChild( fillSymbol1Elem );
+
+ QDomElement fillSymbol2Elem = doc.createElement( QStringLiteral( "fillSymbol2" ) );
+ const QDomElement symbol2Elem = QgsSymbolLayerUtils::saveSymbol( QString(),
+ mSettings.alternateFillSymbol(),
+ doc,
+ rwContext );
+ fillSymbol2Elem.appendChild( symbol2Elem );
+ composerScaleBarElem.appendChild( fillSymbol2Elem );
+
return true;
}
@@ -677,10 +839,64 @@ bool QgsLayoutItemScaleBar::readPropertiesFromElement( const QDomElement &itemEl
mSettings.setMaximumBarWidth( itemElem.attribute( QStringLiteral( "maxBarWidth" ), QStringLiteral( "150" ) ).toDouble() );
mSegmentMillimeters = itemElem.attribute( QStringLiteral( "segmentMillimeters" ), QStringLiteral( "0.0" ) ).toDouble();
mSettings.setMapUnitsPerScaleBarUnit( itemElem.attribute( QStringLiteral( "numMapUnitsPerScaleBarUnit" ), QStringLiteral( "1.0" ) ).toDouble() );
- mSettings.setLineWidth( itemElem.attribute( QStringLiteral( "outlineWidth" ), QStringLiteral( "0.3" ) ).toDouble() );
+
+ QDomElement lineSymbolElem = itemElem.firstChildElement( QStringLiteral( "lineSymbol" ) );
+ bool foundLineSymbol = false;
+ if ( !lineSymbolElem.isNull() )
+ {
+ QDomElement symbolElem = lineSymbolElem.firstChildElement( QStringLiteral( "symbol" ) );
+ std::unique_ptr< QgsLineSymbol > lineSymbol( QgsSymbolLayerUtils::loadSymbol( symbolElem, context ) );
+ if ( lineSymbol )
+ {
+ mSettings.setLineSymbol( lineSymbol.release() );
+ foundLineSymbol = true;
+ }
+ }
+ if ( !foundLineSymbol )
+ {
+ // old project compatibility
+ std::unique_ptr< QgsLineSymbol > lineSymbol = qgis::make_unique< QgsLineSymbol >();
+ std::unique_ptr< QgsSimpleLineSymbolLayer > lineSymbolLayer = qgis::make_unique< QgsSimpleLineSymbolLayer >();
+ lineSymbolLayer->setWidth( itemElem.attribute( QStringLiteral( "outlineWidth" ), QStringLiteral( "0.3" ) ).toDouble() );
+ lineSymbolLayer->setWidthUnit( QgsUnitTypes::RenderMillimeters );
+ lineSymbolLayer->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( itemElem.attribute( QStringLiteral( "lineJoinStyle" ), QStringLiteral( "miter" ) ) ) );
+ lineSymbolLayer->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( itemElem.attribute( QStringLiteral( "lineCapStyle" ), QStringLiteral( "square" ) ) ) );
+
+ //stroke color
+ QDomNodeList strokeColorList = itemElem.elementsByTagName( QStringLiteral( "strokeColor" ) );
+ if ( !strokeColorList.isEmpty() )
+ {
+ QDomElement strokeColorElem = strokeColorList.at( 0 ).toElement();
+ bool redOk, greenOk, blueOk, alphaOk;
+ int strokeRed, strokeGreen, strokeBlue, strokeAlpha;
+
+ strokeRed = strokeColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
+ strokeGreen = strokeColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
+ strokeBlue = strokeColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
+ strokeAlpha = strokeColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
+
+ if ( redOk && greenOk && blueOk && alphaOk )
+ {
+ lineSymbolLayer->setColor( QColor( strokeRed, strokeGreen, strokeBlue, strokeAlpha ) );
+ }
+ }
+ else
+ {
+ lineSymbolLayer->setColor( QColor( itemElem.attribute( QStringLiteral( "penColor" ), QStringLiteral( "#000000" ) ) ) );
+ }
+
+ // need to translate the deprecated ScalebarLineWidth and ScalebarLineColor properties to symbol properties,
+ // and then remove them from the scalebar so they don't interfere and apply to other compatibility workarounds
+ lineSymbolLayer->setDataDefinedProperty( QgsSymbolLayer::PropertyStrokeWidth, dataDefinedProperties().property( QgsLayoutObject::ScalebarLineWidth ) );
+ dataDefinedProperties().setProperty( QgsLayoutObject::ScalebarLineWidth, QgsProperty() );
+ lineSymbolLayer->setDataDefinedProperty( QgsSymbolLayer::PropertyStrokeColor, dataDefinedProperties().property( QgsLayoutObject::ScalebarLineColor ) );
+ dataDefinedProperties().setProperty( QgsLayoutObject::ScalebarLineColor, QgsProperty() );
+
+ lineSymbol->changeSymbolLayer( 0, lineSymbolLayer.release() );
+ mSettings.setLineSymbol( lineSymbol.release() );
+ }
+
mSettings.setUnitLabel( itemElem.attribute( QStringLiteral( "unitLabel" ) ) );
- mSettings.setLineJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( itemElem.attribute( QStringLiteral( "lineJoinStyle" ), QStringLiteral( "miter" ) ) ) );
- mSettings.setLineCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( itemElem.attribute( QStringLiteral( "lineCapStyle" ), QStringLiteral( "square" ) ) ) );
QDomNodeList textFormatNodeList = itemElem.elementsByTagName( QStringLiteral( "text-style" ) );
if ( !textFormatNodeList.isEmpty() )
@@ -715,80 +931,108 @@ bool QgsLayoutItemScaleBar::readPropertiesFromElement( const QDomElement &itemEl
mSettings.setNumericFormat( QgsApplication::numericFormatRegistry()->createFromXml( numericFormatElem, context ) );
}
- //colors
- //fill color
- QDomNodeList fillColorList = itemElem.elementsByTagName( QStringLiteral( "fillColor" ) );
- if ( !fillColorList.isEmpty() )
+ QDomElement fillSymbol1Elem = itemElem.firstChildElement( QStringLiteral( "fillSymbol1" ) );
+ bool foundFillSymbol1 = false;
+ if ( !fillSymbol1Elem.isNull() )
{
- QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
- bool redOk, greenOk, blueOk, alphaOk;
- int fillRed, fillGreen, fillBlue, fillAlpha;
-
- fillRed = fillColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
- fillGreen = fillColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
- fillBlue = fillColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
- fillAlpha = fillColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
-
- if ( redOk && greenOk && blueOk && alphaOk )
+ QDomElement symbolElem = fillSymbol1Elem.firstChildElement( QStringLiteral( "symbol" ) );
+ std::unique_ptr< QgsFillSymbol > fillSymbol( QgsSymbolLayerUtils::loadSymbol( symbolElem, context ) );
+ if ( fillSymbol )
{
- mSettings.setFillColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
+ mSettings.setFillSymbol( fillSymbol.release() );
+ foundFillSymbol1 = true;
}
}
- else
- {
- mSettings.setFillColor( QColor( itemElem.attribute( QStringLiteral( "brushColor" ), QStringLiteral( "#000000" ) ) ) );
- }
-
- //fill color 2
- QDomNodeList fillColor2List = itemElem.elementsByTagName( QStringLiteral( "fillColor2" ) );
- if ( !fillColor2List.isEmpty() )
+ if ( !foundFillSymbol1 )
{
- QDomElement fillColor2Elem = fillColor2List.at( 0 ).toElement();
- bool redOk, greenOk, blueOk, alphaOk;
- int fillRed, fillGreen, fillBlue, fillAlpha;
+ // old project compatibility
+ std::unique_ptr< QgsFillSymbol > fillSymbol = qgis::make_unique< QgsFillSymbol >();
+ std::unique_ptr< QgsSimpleFillSymbolLayer > fillSymbolLayer = qgis::make_unique< QgsSimpleFillSymbolLayer >();
+ fillSymbolLayer->setStrokeStyle( Qt::NoPen );
+
+ //fill color
+ QDomNodeList fillColorList = itemElem.elementsByTagName( QStringLiteral( "fillColor" ) );
+ if ( !fillColorList.isEmpty() )
+ {
+ QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
+ bool redOk, greenOk, blueOk, alphaOk;
+ int fillRed, fillGreen, fillBlue, fillAlpha;
- fillRed = fillColor2Elem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
- fillGreen = fillColor2Elem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
- fillBlue = fillColor2Elem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
- fillAlpha = fillColor2Elem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
+ fillRed = fillColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
+ fillGreen = fillColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
+ fillBlue = fillColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
+ fillAlpha = fillColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
- if ( redOk && greenOk && blueOk && alphaOk )
+ if ( redOk && greenOk && blueOk && alphaOk )
+ {
+ fillSymbolLayer->setColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
+ }
+ }
+ else
{
- mSettings.setFillColor2( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
+ fillSymbolLayer->setColor( QColor( itemElem.attribute( QStringLiteral( "brushColor" ), QStringLiteral( "#000000" ) ) ) );
}
+
+ // need to translate the deprecated ScalebarFillColor property to symbol properties,
+ // and then remove them from the scalebar so they don't interfere and apply to other compatibility workarounds
+ fillSymbolLayer->setDataDefinedProperty( QgsSymbolLayer::PropertyFillColor, dataDefinedProperties().property( QgsLayoutObject::ScalebarFillColor ) );
+ dataDefinedProperties().setProperty( QgsLayoutObject::ScalebarFillColor, QgsProperty() );
+
+ fillSymbol->changeSymbolLayer( 0, fillSymbolLayer.release() );
+ mSettings.setFillSymbol( fillSymbol.release() );
}
- else
+
+ QDomElement fillSymbol2Elem = itemElem.firstChildElement( QStringLiteral( "fillSymbol2" ) );
+ bool foundFillSymbol2 = false;
+ if ( !fillSymbol2Elem.isNull() )
{
- mSettings.setFillColor2( QColor( itemElem.attribute( QStringLiteral( "brush2Color" ), QStringLiteral( "#ffffff" ) ) ) );
+ QDomElement symbolElem = fillSymbol2Elem.firstChildElement( QStringLiteral( "symbol" ) );
+ std::unique_ptr< QgsFillSymbol > fillSymbol( QgsSymbolLayerUtils::loadSymbol( symbolElem, context ) );
+ if ( fillSymbol )
+ {
+ mSettings.setAlternateFillSymbol( fillSymbol.release() );
+ foundFillSymbol2 = true;
+ }
}
-
- //stroke color
- QDomNodeList strokeColorList = itemElem.elementsByTagName( QStringLiteral( "strokeColor" ) );
- if ( !strokeColorList.isEmpty() )
+ if ( !foundFillSymbol2 )
{
- QDomElement strokeColorElem = strokeColorList.at( 0 ).toElement();
- bool redOk, greenOk, blueOk, alphaOk;
- int strokeRed, strokeGreen, strokeBlue, strokeAlpha;
+ // old project compatibility
+ std::unique_ptr< QgsFillSymbol > fillSymbol = qgis::make_unique< QgsFillSymbol >();
+ std::unique_ptr< QgsSimpleFillSymbolLayer > fillSymbolLayer = qgis::make_unique< QgsSimpleFillSymbolLayer >();
+ fillSymbolLayer->setStrokeStyle( Qt::NoPen );
- strokeRed = strokeColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
- strokeGreen = strokeColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
- strokeBlue = strokeColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
- strokeAlpha = strokeColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
+ //fill color 2
- if ( redOk && greenOk && blueOk && alphaOk )
+ QDomNodeList fillColor2List = itemElem.elementsByTagName( QStringLiteral( "fillColor2" ) );
+ if ( !fillColor2List.isEmpty() )
{
- mSettings.setLineColor( QColor( strokeRed, strokeGreen, strokeBlue, strokeAlpha ) );
- QPen p = mSettings.pen();
- p.setColor( mSettings.lineColor() );
- mSettings.setPen( p );
+ QDomElement fillColor2Elem = fillColor2List.at( 0 ).toElement();
+ bool redOk, greenOk, blueOk, alphaOk;
+ int fillRed, fillGreen, fillBlue, fillAlpha;
+
+ fillRed = fillColor2Elem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
+ fillGreen = fillColor2Elem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
+ fillBlue = fillColor2Elem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
+ fillAlpha = fillColor2Elem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
+
+ if ( redOk && greenOk && blueOk && alphaOk )
+ {
+ fillSymbolLayer->setColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) );
+ }
}
- }
- else
- {
- mSettings.setLineColor( QColor( itemElem.attribute( QStringLiteral( "penColor" ), QStringLiteral( "#000000" ) ) ) );
- QPen p = mSettings.pen();
- p.setColor( mSettings.lineColor() );
- mSettings.setPen( p );
+ else
+ {
+ fillSymbolLayer->setColor( QColor( itemElem.attribute( QStringLiteral( "brush2Color" ), QStringLiteral( "#ffffff" ) ) ) );
+ }
+
+ // need to translate the deprecated ScalebarFillColor2 property to symbol properties,
+ // and then remove them from the scalebar so they don't interfere and apply to other compatibility workarounds
+ fillSymbolLayer->setDataDefinedProperty( QgsSymbolLayer::PropertyFillColor, dataDefinedProperties().property( QgsLayoutObject::ScalebarFillColor2 ) );
+ dataDefinedProperties().setProperty( QgsLayoutObject::ScalebarFillColor2, QgsProperty() );
+
+ fillSymbol->changeSymbolLayer( 0, fillSymbolLayer.release() );
+ mSettings.setAlternateFillSymbol( fillSymbol.release() );
+
}
//font color
@@ -817,8 +1061,7 @@ bool QgsLayoutItemScaleBar::readPropertiesFromElement( const QDomElement &itemEl
}
//style
- QString styleString = itemElem.attribute( QStringLiteral( "style" ), QString() );
- setStyle( styleString.toLocal8Bit().data() );
+ setStyle( itemElem.attribute( QStringLiteral( "style" ), QString() ) );
if ( itemElem.attribute( QStringLiteral( "unitType" ) ).isEmpty() )
{
diff --git a/src/core/layout/qgslayoutitemscalebar.h b/src/core/layout/qgslayoutitemscalebar.h
index 354bc040c12e..bc4c7fd01f02 100644
--- a/src/core/layout/qgslayoutitemscalebar.h
+++ b/src/core/layout/qgslayoutitemscalebar.h
@@ -188,6 +188,67 @@ class CORE_EXPORT QgsLayoutItemScaleBar: public QgsLayoutItem
*/
void setTextFormat( const QgsTextFormat &format );
+ /**
+ * Returns the line symbol used to render the scalebar (only used for some scalebar types).
+ *
+ * Ownership is not transferred.
+ *
+ * \see setLineSymbol()
+ * \since QGIS 3.14
+ */
+ QgsLineSymbol *lineSymbol() const;
+
+ /**
+ * Sets the line \a symbol used to render the scalebar (only used for some scalebar types). Ownership of \a symbol is
+ * transferred to the scalebar.
+ *
+ * \see lineSymbol()
+ * \since QGIS 3.14
+ */
+ void setLineSymbol( QgsLineSymbol *symbol SIP_TRANSFER );
+
+ /**
+ * Returns the primary fill symbol used to render the scalebar (only used for some scalebar types).
+ *
+ * Ownership is not transferred.
+ *
+ * \see setFillSymbol()
+ * \see alternateFillSymbol()
+ * \since QGIS 3.14
+ */
+ QgsFillSymbol *fillSymbol() const;
+
+ /**
+ * Sets the primary fill \a symbol used to render the scalebar (only used for some scalebar types). Ownership of \a symbol is
+ * transferred to the scalebar.
+ *
+ * \see fillSymbol()
+ * \see setAlternateFillSymbol()
+ * \since QGIS 3.14
+ */
+ void setFillSymbol( QgsFillSymbol *symbol SIP_TRANSFER );
+
+ /**
+ * Returns the secondary fill symbol used to render the scalebar (only used for some scalebar types).
+ *
+ * Ownership is not transferred.
+ *
+ * \see setAlternateFillSymbol()
+ * \see fillSymbol()
+ * \since QGIS 3.14
+ */
+ QgsFillSymbol *alternateFillSymbol() const;
+
+ /**
+ * Sets the secondary fill \a symbol used to render the scalebar (only used for some scalebar types). Ownership of \a symbol is
+ * transferred to the scalebar.
+ *
+ * \see alternateFillSymbol()
+ * \see setFillSymbol()
+ * \since QGIS 3.14
+ */
+ void setAlternateFillSymbol( QgsFillSymbol *symbol SIP_TRANSFER );
+
/**
* Returns the font used for drawing text in the scalebar.
* \see setFont()
@@ -222,75 +283,86 @@ class CORE_EXPORT QgsLayoutItemScaleBar: public QgsLayoutItem
* Returns the color used for fills in the scalebar.
* \see setFillColor()
* \see fillColor2()
+ * \deprecated use fillSymbol() instead
*/
- QColor fillColor() const { return mSettings.fillColor(); }
+ Q_DECL_DEPRECATED QColor fillColor() const SIP_DEPRECATED;
/**
* Sets the \a color used for fills in the scalebar.
* \see fillColor()
* \see setFillColor2()
+ * \deprecated use setFillSymbol() instead
*/
- void setFillColor( const QColor &color ) { mSettings.setFillColor( color ); }
+ Q_DECL_DEPRECATED void setFillColor( const QColor &color ) SIP_DEPRECATED;
/**
* Returns the secondary color used for fills in the scalebar.
* \see setFillColor2()
* \see fillColor()
+ * \deprecated use alternateFillSymbol() instead
*/
- QColor fillColor2() const { return mSettings.fillColor2(); }
+ Q_DECL_DEPRECATED QColor fillColor2() const SIP_DEPRECATED;
/**
* Sets the secondary \a color used for fills in the scalebar.
* \see fillColor2()
* \see setFillColor2()
+ * \deprecated use setAlternateFillSymbol() instead
*/
- void setFillColor2( const QColor &color ) { mSettings.setFillColor2( color ); }
+ Q_DECL_DEPRECATED void setFillColor2( const QColor &color ) SIP_DEPRECATED;
/**
* Returns the color used for lines in the scalebar.
* \see setLineColor()
+ * \deprecated use lineSymbol() instead
*/
- QColor lineColor() const { return mSettings.lineColor(); }
+ Q_DECL_DEPRECATED QColor lineColor() const SIP_DEPRECATED;
/**
* Sets the \a color used for lines in the scalebar.
* \see lineColor()
+ * \deprecated use setLineSymbol() instead
*/
- void setLineColor( const QColor &color ) { mSettings.setLineColor( color ); }
+ Q_DECL_DEPRECATED void setLineColor( const QColor &color ) SIP_DEPRECATED;
/**
* Returns the line width in millimeters for lines in the scalebar.
* \see setLineWidth()
+ * \deprecated use lineSymbol() instead
*/
- double lineWidth() const { return mSettings.lineWidth(); }
+ Q_DECL_DEPRECATED double lineWidth() const SIP_DEPRECATED;
/**
* Sets the line \a width in millimeters for lines in the scalebar.
* \see lineWidth()
+ * \deprecated use setLineSymbol() instead
*/
- void setLineWidth( double width ) { mSettings.setLineWidth( width ); }
+ Q_DECL_DEPRECATED void setLineWidth( double width ) SIP_DEPRECATED;
/**
* Returns the pen used for drawing outlines in the scalebar.
* \see brush()
+ * \deprecated use lineSymbol() instead
*/
- QPen pen() const { return mSettings.pen(); }
+ Q_DECL_DEPRECATED QPen pen() const SIP_DEPRECATED;
/**
* Returns the primary brush for the scalebar.
* \returns QBrush used for filling the scalebar
* \see brush2
* \see pen
+ * \deprecated use fillSymbol() instead
*/
- QBrush brush() const {return mSettings.brush();}
+ Q_DECL_DEPRECATED QBrush brush() const SIP_DEPRECATED;
/**
* Returns the secondary brush for the scalebar. This is used for alternating color style scalebars, such
* as single and double box styles.
* \returns QBrush used for secondary color areas
* \see brush
+ * \deprecated use alternateFillSymbol() instead
*/
- QBrush brush2() const {return mSettings.brush2(); }
+ Q_DECL_DEPRECATED QBrush brush2() const SIP_DEPRECATED;
/**
* Returns the scalebar height (in millimeters).
@@ -395,26 +467,30 @@ class CORE_EXPORT QgsLayoutItemScaleBar: public QgsLayoutItem
/**
* Returns the join style used for drawing lines in the scalebar.
* \see setLineJoinStyle()
+ * \deprecated use lineSymbol() instead
*/
- Qt::PenJoinStyle lineJoinStyle() const { return mSettings.lineJoinStyle(); }
+ Q_DECL_DEPRECATED Qt::PenJoinStyle lineJoinStyle() const SIP_DEPRECATED;
/**
* Sets the join \a style used when drawing the lines in the scalebar
* \see lineJoinStyle()
+ * \deprecated use setLineSymbol() instead
*/
- void setLineJoinStyle( Qt::PenJoinStyle style );
+ Q_DECL_DEPRECATED void setLineJoinStyle( Qt::PenJoinStyle style ) SIP_DEPRECATED;
/**
* Returns the cap style used for drawing lines in the scalebar.
* \see setLineCapStyle()
+ * \deprecated use lineSymbol() instead
*/
- Qt::PenCapStyle lineCapStyle() const { return mSettings.lineCapStyle(); }
+ Q_DECL_DEPRECATED Qt::PenCapStyle lineCapStyle() const SIP_DEPRECATED;
/**
* Sets the cap \a style used when drawing the lines in the scalebar.
* \see lineCapStyle()
+ * \deprecated use setLineSymbol() instead
*/
- void setLineCapStyle( Qt::PenCapStyle style );
+ Q_DECL_DEPRECATED void setLineCapStyle( Qt::PenCapStyle style ) SIP_DEPRECATED;
/**
* Applies the default scalebar settings to the scale bar.
@@ -422,6 +498,15 @@ class CORE_EXPORT QgsLayoutItemScaleBar: public QgsLayoutItem
*/
void applyDefaultSettings();
+ /**
+ * Applies any default settings relating to the specified \a renderer to the item.
+ *
+ * Returns TRUE if settings were applied.
+ *
+ * \since QGIS 3.14
+ */
+ bool applyDefaultRendererSettings( QgsScaleBarRenderer *renderer );
+
/**
* Attempts to guess the most reasonable unit choice for the scalebar, given
* the current linked map's scale.
diff --git a/src/core/layout/qgslayoutnortharrowhandler.cpp b/src/core/layout/qgslayoutnortharrowhandler.cpp
new file mode 100644
index 000000000000..4c4305197a65
--- /dev/null
+++ b/src/core/layout/qgslayoutnortharrowhandler.cpp
@@ -0,0 +1,122 @@
+/***************************************************************************
+ qgslayoutnortharrowhandler.cpp
+ -------------------
+begin : April 2020
+copyright : (C) 2020 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 "qgslayoutnortharrowhandler.h"
+#include "qgslayoutitemmap.h"
+#include "qgslayout.h"
+#include "qgsbearingutils.h"
+#include "qgslogger.h"
+
+QgsLayoutNorthArrowHandler::QgsLayoutNorthArrowHandler( QObject *parent )
+ : QObject( parent )
+{
+
+}
+
+void QgsLayoutNorthArrowHandler::disconnectMap( QgsLayoutItemMap *map )
+{
+ if ( map )
+ {
+ disconnect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutNorthArrowHandler::updateMapRotation );
+ disconnect( map, &QgsLayoutItemMap::rotationChanged, this, &QgsLayoutNorthArrowHandler::updateMapRotation );
+ disconnect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutNorthArrowHandler::updateMapRotation );
+ }
+}
+
+void QgsLayoutNorthArrowHandler::updateMapRotation()
+{
+ if ( !mRotationMap )
+ return;
+
+ // take map rotation
+ double rotation = mRotationMap->mapRotation() + mRotationMap->rotation();
+
+ // handle true north
+ switch ( mNorthMode )
+ {
+ case GridNorth:
+ break; // nothing to do
+
+ case TrueNorth:
+ {
+ QgsPointXY center = mRotationMap->extent().center();
+ QgsCoordinateReferenceSystem crs = mRotationMap->crs();
+ QgsCoordinateTransformContext transformContext = mRotationMap->layout()->project()->transformContext();
+
+ try
+ {
+ double bearing = QgsBearingUtils::bearingTrueNorth( crs, transformContext, center );
+ rotation += bearing;
+ }
+ catch ( QgsException &e )
+ {
+ Q_UNUSED( e )
+ QgsDebugMsg( QStringLiteral( "Caught exception %1" ).arg( e.what() ) );
+ }
+ break;
+ }
+ }
+
+ rotation += mNorthOffset;
+ const double oldRotation = mArrowRotation;
+ mArrowRotation = ( rotation > 360.0 ) ? rotation - 360.0 : rotation ;
+ if ( mArrowRotation != oldRotation )
+ emit arrowRotationChanged( mArrowRotation );
+}
+
+void QgsLayoutNorthArrowHandler::setLinkedMap( QgsLayoutItemMap *map )
+{
+ if ( mRotationMap )
+ {
+ disconnectMap( mRotationMap );
+ }
+
+ if ( !map ) //disable rotation from map
+ {
+ mRotationMap = nullptr;
+ if ( mArrowRotation != 0 )
+ {
+ mArrowRotation = 0;
+ emit arrowRotationChanged( mArrowRotation );
+ }
+ }
+ else
+ {
+ connect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutNorthArrowHandler::updateMapRotation );
+ connect( map, &QgsLayoutItemMap::rotationChanged, this, &QgsLayoutNorthArrowHandler::updateMapRotation );
+ connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutNorthArrowHandler::updateMapRotation );
+ mRotationMap = map;
+ updateMapRotation();
+ }
+}
+
+QgsLayoutItemMap *QgsLayoutNorthArrowHandler::linkedMap() const
+{
+ return mRotationMap;
+}
+
+void QgsLayoutNorthArrowHandler::setNorthMode( QgsLayoutNorthArrowHandler::NorthMode mode )
+{
+ mNorthMode = mode;
+ updateMapRotation();
+}
+
+void QgsLayoutNorthArrowHandler::setNorthOffset( double offset )
+{
+ mNorthOffset = offset;
+ updateMapRotation();
+}
diff --git a/src/core/layout/qgslayoutnortharrowhandler.h b/src/core/layout/qgslayoutnortharrowhandler.h
new file mode 100644
index 000000000000..556e3144442b
--- /dev/null
+++ b/src/core/layout/qgslayoutnortharrowhandler.h
@@ -0,0 +1,124 @@
+/***************************************************************************
+ qgslayoutnortharrowhandler.h
+ -------------------
+begin : April 2020
+copyright : (C) 2020 by Nyall Dawson
+email : nyall dot dawson at gmail dot com
+***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+#ifndef QGSLAYOUTNORTHARROWHANDLER_H
+#define QGSLAYOUTNORTHARROWHANDLER_H
+
+#include "qgis_core.h"
+#include "qgis_sip.h"
+#include
+#include
+
+class QgsLayoutItemMap;
+
+/**
+ * \ingroup core
+ * An object which handles north-arrow type behavior for layout items.
+ * \since QGIS 3.14
+ */
+class CORE_EXPORT QgsLayoutNorthArrowHandler: public QObject
+{
+ Q_OBJECT
+ public:
+
+ //! Method for syncing rotation to a map's North direction
+ enum NorthMode
+ {
+ GridNorth = 0, //!< Align to grid north
+ TrueNorth, //!< Align to true north
+ };
+
+ /**
+ * Constructor for QgsLayoutNorthArrowHandler, with the specified parent \a object.
+ */
+ QgsLayoutNorthArrowHandler( QObject *parent SIP_TRANSFERTHIS );
+
+ /**
+ * Returns the rotation to be used for the arrow, in degrees clockwise.
+ */
+ double arrowRotation() const { return mArrowRotation; }
+
+ /**
+ * Sets the linked \a map item.
+ *
+ * \see linkedMap()
+ */
+ void setLinkedMap( QgsLayoutItemMap *map );
+
+ /**
+ * Returns the linked rotation map, if set. An NULLPTR means arrow calculation is
+ * disabled.
+ *
+ * \see setLinkedMap()
+ */
+ QgsLayoutItemMap *linkedMap() const;
+
+ /**
+ * Returns the mode used to calculate the arrow rotation.
+ * \see setNorthMode()
+ * \see northOffset()
+ */
+ NorthMode northMode() const { return mNorthMode; }
+
+ /**
+ * Sets the \a mode used to calculate the arrow rotation.
+ * \see northMode()
+ * \see setNorthOffset()
+ */
+ void setNorthMode( NorthMode mode );
+
+ /**
+ * Returns the offset added to the arrows's rotation from a map's North.
+ * \see setNorthOffset()
+ * \see northMode()
+ */
+ double northOffset() const { return mNorthOffset; }
+
+ /**
+ * Sets the \a offset added to the arrows's rotation from a map's North.
+ * \see northOffset()
+ * \see setNorthMode()
+ */
+ void setNorthOffset( double offset );
+
+ signals:
+ //! Emitted on arrow rotation change
+ void arrowRotationChanged( double newRotation );
+
+ private:
+
+ //! Arrow rotation
+ double mArrowRotation = 0;
+
+ QString mRotationMapUuid;
+ //! Map that sets the rotation (or NULLPTR if this picture uses map independent rotation)
+ QPointer< QgsLayoutItemMap > mRotationMap;
+
+ //! Mode used to align to North
+ NorthMode mNorthMode = GridNorth;
+ //! Offset for north arrow
+ double mNorthOffset = 0.0;
+
+ void disconnectMap( QgsLayoutItemMap *map );
+
+ private slots:
+
+ void updateMapRotation();
+
+
+};
+
+#endif // QGSLAYOUTNORTHARROWHANDLER_H
diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h
index 3896439bd920..0b645111cad9 100644
--- a/src/core/layout/qgslayoutobject.h
+++ b/src/core/layout/qgslayoutobject.h
@@ -185,10 +185,10 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe
LegendTitle, //!< Legend title
LegendColumnCount, //!< Legend column count
//scalebar item
- ScalebarFillColor, //!< Scalebar fill color
- ScalebarFillColor2, //!< Scalebar secondary fill color
- ScalebarLineColor, //!< Scalebar line color
- ScalebarLineWidth, //!< Scalebar line width,
+ ScalebarFillColor, //!< Scalebar fill color (deprecated, use data defined properties on scalebar fill symbol 1 instead)
+ ScalebarFillColor2, //!< Scalebar secondary fill color (deprecated, use data defined properties on scalebar fill symbol 2 instead)
+ ScalebarLineColor, //!< Scalebar line color (deprecated, use data defined properties on scalebar line symbol instead)
+ ScalebarLineWidth, //!< Scalebar line width (deprecated, use data defined properties on scalebar line symbol instead)
//table item
AttributeTableSourceLayer, //!< Attribute table source layer
MapCrs, //!< Map CRS
diff --git a/src/core/layout/qgslayouttable.cpp b/src/core/layout/qgslayouttable.cpp
index 384bbe363e53..2c3219f10920 100644
--- a/src/core/layout/qgslayouttable.cpp
+++ b/src/core/layout/qgslayouttable.cpp
@@ -301,7 +301,7 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0;
double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0;
- double cellHeaderHeight = QgsLayoutUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin;
+ double cellHeaderHeight = mMaxRowHeightMap[0] + 2 * mCellMargin;//QgsLayoutUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin;
double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin;
QRectF cell;
@@ -352,15 +352,6 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
currentX += mCellMargin;
- Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( 0 );
- if ( column->width() <= 0 )
- {
- //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text
- //which may slightly exceed the calculated width
- //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width
- textFlag = Qt::TextDontClip;
- }
-
cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
//calculate alignment of header
@@ -381,7 +372,15 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
break;
}
- QgsLayoutUtils::drawText( p, cell, column->heading(), mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag );
+ // disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
+ // and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
+ QString str = column->heading();
+ Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( Qt::TextDontClip );
+ if ( ( mWrapBehavior != TruncateText || column->width() > 0 ) && textRequiresWrapping( str, column->width(), mHeaderFont ) )
+ {
+ str = wrappedText( str, column->width(), mHeaderFont );
+ }
+ QgsLayoutUtils::drawText( p, cell, str, mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag );
currentX += mMaxColumnWidthMap[ col ];
currentX += mCellMargin;
@@ -869,6 +868,8 @@ bool QgsLayoutTable::calculateMaxColumnWidths()
int cells = cols * ( mTableContents.count() + 1 );
QVector< double > widths( cells );
+ double currentCellTextWidth;
+
//first, go through all the column headers and calculate the sizes
QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
int col = 0;
@@ -881,7 +882,15 @@ bool QgsLayoutTable::calculateMaxColumnWidths()
}
else if ( mHeaderMode != QgsLayoutTable::NoHeaders )
{
- widths[col] = QgsLayoutUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
+ //column width set to automatic, so check content size
+ QStringList multiLineSplit = ( *columnIt )->heading().split( '\n' );
+ currentCellTextWidth = 0;
+ const auto constMultiLineSplit = multiLineSplit;
+ for ( const QString &line : constMultiLineSplit )
+ {
+ currentCellTextWidth = std::max( currentCellTextWidth, QgsLayoutUtils::textWidthMM( mHeaderFont, line ) );
+ }
+ widths[col] = currentCellTextWidth;
}
else
{
@@ -892,7 +901,6 @@ bool QgsLayoutTable::calculateMaxColumnWidths()
//next, go through all the table contents and calculate the sizes
QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
- double currentCellTextWidth;
int row = 1;
for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
{
@@ -951,7 +959,19 @@ bool QgsLayoutTable::calculateMaxRowHeights()
for ( ; columnIt != mColumns.constEnd(); ++columnIt )
{
//height
- heights[col] = mHeaderMode != QgsLayoutTable::NoHeaders ? QgsLayoutUtils::textHeightMM( mHeaderFont, ( *columnIt )->heading() ) : 0;
+ if ( mHeaderMode == QgsLayoutTable::NoHeaders )
+ {
+ heights[col] = 0;
+ }
+ else if ( textRequiresWrapping( ( *columnIt )->heading(), mColumns.at( col )->width(), mHeaderFont ) )
+ {
+ //contents too wide for cell, need to wrap
+ heights[col] = QgsLayoutUtils::textHeightMM( mHeaderFont, wrappedText( ( *columnIt )->heading(), mColumns.at( col )->width(), mHeaderFont ) );
+ }
+ else
+ {
+ heights[col] = QgsLayoutUtils::textHeightMM( mHeaderFont, ( *columnIt )->heading() );
+ }
col++;
}
@@ -1103,7 +1123,7 @@ void QgsLayoutTable::drawHorizontalGridLines( QPainter *painter, int firstRow, i
{
painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
- currentY += ( QgsLayoutUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin );
+ currentY += mMaxRowHeightMap[0] + 2 * mCellMargin;
}
for ( int row = firstRow; row < lastRow; ++row )
{
@@ -1251,7 +1271,7 @@ void QgsLayoutTable::drawVerticalGridLines( QPainter *painter, const QMapisRunning() )
{
// can't do anything while a query is running, and can't block
diff --git a/src/core/mesh/qgsmeshdataprovider.cpp b/src/core/mesh/qgsmeshdataprovider.cpp
index a0f9e672455e..b701856c6815 100644
--- a/src/core/mesh/qgsmeshdataprovider.cpp
+++ b/src/core/mesh/qgsmeshdataprovider.cpp
@@ -15,180 +15,32 @@
* *
***************************************************************************/
+#include "qgis.h"
#include "qgsmeshdataprovider.h"
+#include "qgsmeshdataprovidertemporalcapabilities.h"
#include "qgsrectangle.h"
-#include "qgis.h"
-
-
-
-QgsMeshDatasetIndex::QgsMeshDatasetIndex( int group, int dataset )
- : mGroupIndex( group ), mDatasetIndex( dataset )
-{}
-
-int QgsMeshDatasetIndex::group() const
-{
- return mGroupIndex;
-}
-
-int QgsMeshDatasetIndex::dataset() const
-{
- return mDatasetIndex;
-}
-
-bool QgsMeshDatasetIndex::isValid() const
-{
- return ( group() > -1 ) && ( dataset() > -1 );
-}
-
-bool QgsMeshDatasetIndex::operator ==( QgsMeshDatasetIndex other ) const
-{
- if ( isValid() && other.isValid() )
- return other.group() == group() && other.dataset() == dataset();
- else
- return isValid() == other.isValid();
-}
-
-bool QgsMeshDatasetIndex::operator !=( QgsMeshDatasetIndex other ) const
-{
- return !( operator==( other ) );
-}
QgsMeshDataProvider::QgsMeshDataProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options )
- : QgsDataProvider( uri, options )
-{
-}
-
-QgsMeshDatasetValue::QgsMeshDatasetValue( double x, double y )
- : mX( x ), mY( y )
-{}
-
-QgsMeshDatasetValue::QgsMeshDatasetValue( double scalar )
- : mX( scalar )
-{}
-
-double QgsMeshDatasetValue::scalar() const
-{
- if ( std::isnan( mY ) )
- {
- return mX;
- }
- else if ( std::isnan( mX ) )
- {
- return std::numeric_limits::quiet_NaN();
- }
- else
- {
- return std::sqrt( ( mX ) * ( mX ) + ( mY ) * ( mY ) );
- }
-}
-
-void QgsMeshDatasetValue::set( double scalar )
-{
- setX( scalar );
-}
-
-void QgsMeshDatasetValue::setX( double x )
-{
- mX = x;
-}
-
-void QgsMeshDatasetValue::setY( double y )
-{
- mY = y;
-}
-
-double QgsMeshDatasetValue::x() const
+ : QgsDataProvider( uri, options ), mTemporalCapabilities( qgis::make_unique() )
{
- return mX;
}
-double QgsMeshDatasetValue::y() const
+QgsMeshDataProviderTemporalCapabilities *QgsMeshDataProvider::temporalCapabilities()
{
- return mY;
+ return mTemporalCapabilities.get();
}
-bool QgsMeshDatasetValue::operator==( const QgsMeshDatasetValue other ) const
+const QgsMeshDataProviderTemporalCapabilities *QgsMeshDataProvider::temporalCapabilities() const
{
- bool equal = std::isnan( mX ) == std::isnan( other.x() );
- equal &= std::isnan( mY ) == std::isnan( other.y() );
-
- if ( equal )
- {
- if ( std::isnan( mY ) )
- {
- equal &= qgsDoubleNear( other.x(), mX, 1E-8 );
- }
- else
- {
- equal &= qgsDoubleNear( other.x(), mX, 1E-8 );
- equal &= qgsDoubleNear( other.y(), mY, 1E-8 );
- }
- }
- return equal;
+ return mTemporalCapabilities.get();
}
-QgsMeshDatasetGroupMetadata::QgsMeshDatasetGroupMetadata( const QString &name,
- bool isScalar,
- DataType dataType,
- double minimum,
- double maximum,
- int maximumVerticalLevels,
- const QDateTime &referenceTime,
- const QMap &extraOptions )
- : mName( name )
- , mIsScalar( isScalar )
- , mDataType( dataType )
- , mMinimumValue( minimum )
- , mMaximumValue( maximum )
- , mExtraOptions( extraOptions )
- , mMaximumVerticalLevelsCount( maximumVerticalLevels )
- , mReferenceTime( referenceTime )
+void QgsMeshDataProvider::setTemporalUnit( QgsUnitTypes::TemporalUnit unit )
{
-}
-
-QMap QgsMeshDatasetGroupMetadata::extraOptions() const
-{
- return mExtraOptions;
-}
-
-bool QgsMeshDatasetGroupMetadata::isVector() const
-{
- return !mIsScalar;
-}
-
-bool QgsMeshDatasetGroupMetadata::isScalar() const
-{
- return mIsScalar;
-}
-
-QString QgsMeshDatasetGroupMetadata::name() const
-{
- return mName;
-}
-
-QgsMeshDatasetGroupMetadata::DataType QgsMeshDatasetGroupMetadata::dataType() const
-{
- return mDataType;
-}
-
-double QgsMeshDatasetGroupMetadata::minimum() const
-{
- return mMinimumValue;
-}
-
-double QgsMeshDatasetGroupMetadata::maximum() const
-{
- return mMaximumValue;
-}
-
-int QgsMeshDatasetGroupMetadata::maximumVerticalLevelsCount() const
-{
- return mMaximumVerticalLevelsCount;
-}
-
-QDateTime QgsMeshDatasetGroupMetadata::referenceTime() const
-{
- return mReferenceTime;
+ QgsUnitTypes::TemporalUnit oldUnit = mTemporalCapabilities->temporalUnit();
+ mTemporalCapabilities->setTemporalUnit( unit );
+ if ( oldUnit != unit )
+ reloadData();
}
int QgsMeshDatasetSourceInterface::datasetCount( QgsMeshDatasetIndex index ) const
@@ -201,136 +53,6 @@ QgsMeshDatasetGroupMetadata QgsMeshDatasetSourceInterface::datasetGroupMetadata(
return datasetGroupMetadata( index.group() );
}
-QgsMeshDatasetMetadata::QgsMeshDatasetMetadata(
- double time,
- bool isValid,
- double minimum,
- double maximum,
- int maximumVerticalLevels )
- : mTime( time )
- , mIsValid( isValid )
- , mMinimumValue( minimum )
- , mMaximumValue( maximum )
- , mMaximumVerticalLevelsCount( maximumVerticalLevels )
-{
-}
-
-double QgsMeshDatasetMetadata::time() const
-{
- return mTime;
-}
-
-bool QgsMeshDatasetMetadata::isValid() const
-{
- return mIsValid;
-}
-
-double QgsMeshDatasetMetadata::minimum() const
-{
- return mMinimumValue;
-}
-
-double QgsMeshDatasetMetadata::maximum() const
-{
- return mMaximumValue;
-}
-
-int QgsMeshDatasetMetadata::maximumVerticalLevelsCount() const
-{
- return mMaximumVerticalLevelsCount;
-}
-
-QgsMeshDataBlock::QgsMeshDataBlock()
- : mType( ActiveFlagInteger )
-{
-}
-
-QgsMeshDataBlock::QgsMeshDataBlock( QgsMeshDataBlock::DataType type, int count )
- : mType( type ),
- mSize( count )
-{
-}
-
-QgsMeshDataBlock::DataType QgsMeshDataBlock::type() const
-{
- return mType;
-}
-
-int QgsMeshDataBlock::count() const
-{
- return mSize;
-}
-
-bool QgsMeshDataBlock::isValid() const
-{
- return ( count() > 0 ) && ( mIsValid );
-}
-
-QgsMeshDatasetValue QgsMeshDataBlock::value( int index ) const
-{
- if ( !isValid() )
- return QgsMeshDatasetValue();
-
- Q_ASSERT( mType != ActiveFlagInteger );
-
- if ( mType == ScalarDouble )
- return QgsMeshDatasetValue( mDoubleBuffer[index] );
-
- return QgsMeshDatasetValue(
- mDoubleBuffer[2 * index],
- mDoubleBuffer[2 * index + 1]
- );
-}
-
-bool QgsMeshDataBlock::active( int index ) const
-{
- if ( !isValid() )
- return false;
-
- Q_ASSERT( mType == ActiveFlagInteger );
-
- if ( mIntegerBuffer.empty() )
- return true;
- else
- return bool( mIntegerBuffer[index] );
-}
-
-void QgsMeshDataBlock::setActive( const QVector &vals )
-{
- Q_ASSERT( mType == ActiveFlagInteger );
- Q_ASSERT( vals.size() == count() );
-
- mIntegerBuffer = vals;
- setValid( true );
-}
-
-QVector QgsMeshDataBlock::active() const
-{
- Q_ASSERT( mType == ActiveFlagInteger );
- return mIntegerBuffer;
-}
-
-QVector QgsMeshDataBlock::values() const
-{
- Q_ASSERT( mType != ActiveFlagInteger );
-
- return mDoubleBuffer;
-}
-
-void QgsMeshDataBlock::setValues( const QVector &vals )
-{
- Q_ASSERT( mType != ActiveFlagInteger );
- Q_ASSERT( mType == ScalarDouble ? vals.size() == count() : vals.size() == 2 * count() );
-
- mDoubleBuffer = vals;
- setValid( true );
-}
-
-void QgsMeshDataBlock::setValid( bool valid )
-{
- mIsValid = valid;
-}
-
QgsMeshVertex QgsMesh::vertex( int index ) const
{
if ( index < vertices.size() && index >= 0 )
@@ -388,119 +110,6 @@ int QgsMesh::edgeCount() const
return edges.size();
}
-QgsMesh3dDataBlock::QgsMesh3dDataBlock() = default;
-
-QgsMesh3dDataBlock::~QgsMesh3dDataBlock() {};
-
-QgsMesh3dDataBlock::QgsMesh3dDataBlock( int count, bool isVector )
- : mSize( count )
- , mIsVector( isVector )
-{
-}
-
-bool QgsMesh3dDataBlock::isValid() const
-{
- return mIsValid;
-}
-
-bool QgsMesh3dDataBlock::isVector() const
-{
- return mIsVector;
-}
-
-int QgsMesh3dDataBlock::count() const
-{
- return mSize;
-}
-
-int QgsMesh3dDataBlock::firstVolumeIndex() const
-{
- if ( mFaceToVolumeIndex.empty() )
- return -1;
- return mFaceToVolumeIndex[0];
-}
-
-int QgsMesh3dDataBlock::lastVolumeIndex() const
-{
- if ( mFaceToVolumeIndex.empty() || mVerticalLevelsCount.empty() )
- return -1;
- const int lastVolumeStartIndex = mFaceToVolumeIndex[mFaceToVolumeIndex.size() - 1];
- const int volumesCountInLastRow = mVerticalLevelsCount[mVerticalLevelsCount.size() - 1];
- return lastVolumeStartIndex + volumesCountInLastRow;
-}
-
-int QgsMesh3dDataBlock::volumesCount() const
-{
- return lastVolumeIndex() - firstVolumeIndex();
-}
-
-QVector QgsMesh3dDataBlock::verticalLevelsCount() const
-{
- Q_ASSERT( isValid() );
- return mVerticalLevelsCount;
-}
-
-void QgsMesh3dDataBlock::setFaceToVolumeIndex( const QVector &faceToVolumeIndex )
-{
- Q_ASSERT( faceToVolumeIndex.size() == count() );
- mFaceToVolumeIndex = faceToVolumeIndex;
-}
-
-void QgsMesh3dDataBlock::setVerticalLevelsCount( const QVector &verticalLevelsCount )
-{
- Q_ASSERT( verticalLevelsCount.size() == count() );
- mVerticalLevelsCount = verticalLevelsCount;
-}
-
-QVector QgsMesh3dDataBlock::verticalLevels() const
-{
- Q_ASSERT( isValid() );
- return mVerticalLevels;
-}
-
-void QgsMesh3dDataBlock::setVerticalLevels( const QVector &verticalLevels )
-{
- Q_ASSERT( verticalLevels.size() == volumesCount() + count() );
- mVerticalLevels = verticalLevels;
-}
-
-QVector QgsMesh3dDataBlock::faceToVolumeIndex() const
-{
- Q_ASSERT( isValid() );
- return mFaceToVolumeIndex;
-}
-
-QVector QgsMesh3dDataBlock::values() const
-{
- Q_ASSERT( isValid() );
- return mDoubleBuffer;
-}
-
-QgsMeshDatasetValue QgsMesh3dDataBlock::value( int volumeIndex ) const
-{
- if ( !isValid() )
- return QgsMeshDatasetValue();
-
- if ( !mIsVector )
- return QgsMeshDatasetValue( mDoubleBuffer[volumeIndex] );
-
- return QgsMeshDatasetValue(
- mDoubleBuffer[2 * volumeIndex],
- mDoubleBuffer[2 * volumeIndex + 1]
- );
-}
-
-void QgsMesh3dDataBlock::setValues( const QVector &doubleBuffer )
-{
- Q_ASSERT( doubleBuffer.size() == ( isVector() ? 2 * volumesCount() : volumesCount() ) );
- mDoubleBuffer = doubleBuffer;
-}
-
-void QgsMesh3dDataBlock::setValid( bool valid )
-{
- mIsValid = valid;
-}
-
bool QgsMeshDataSourceInterface::contains( const QgsMesh::ElementType &type ) const
{
switch ( type )
diff --git a/src/core/mesh/qgsmeshdataprovider.h b/src/core/mesh/qgsmeshdataprovider.h
index dd50abf6a98a..028edc4ec1c4 100644
--- a/src/core/mesh/qgsmeshdataprovider.h
+++ b/src/core/mesh/qgsmeshdataprovider.h
@@ -28,38 +28,11 @@
#include "qgis_core.h"
#include "qgspoint.h"
#include "qgsdataprovider.h"
+#include "qgsmeshdataset.h"
+#include "qgsmeshdataprovidertemporalcapabilities.h"
-class QgsRectangle;
-/**
- * \ingroup core
- *
- * QgsMeshDatasetIndex is index that identifies the dataset group (e.g. wind speed)
- * and a dataset in this group (e.g. magnitude of wind speed in particular time)
- *
- * \note The API is considered EXPERIMENTAL and can be changed without a notice
- *
- * \since QGIS 3.4
- */
-class CORE_EXPORT QgsMeshDatasetIndex
-{
- public:
- //! Creates an index. -1 represents invalid group/dataset
- QgsMeshDatasetIndex( int group = -1, int dataset = -1 );
- //! Returns a group index
- int group() const;
- //! Returns a dataset index within group()
- int dataset() const;
- //! Returns whether index is valid, ie at least groups is set
- bool isValid() const;
- //! Equality operator
- bool operator == ( QgsMeshDatasetIndex other ) const;
- //! Inequality operator
- bool operator != ( QgsMeshDatasetIndex other ) const;
- private:
- int mGroupIndex = -1;
- int mDatasetIndex = -1;
-};
+class QgsRectangle;
//! xyz coords of vertex
typedef QgsPoint QgsMeshVertex;
@@ -134,455 +107,6 @@ struct CORE_EXPORT QgsMesh
QVector faces SIP_SKIP;
};
-/**
- * \ingroup core
- *
- * QgsMeshDatasetValue represents single dataset value
- *
- * could be scalar or vector. Nodata values are represented by NaNs.
- *
- * \note The API is considered EXPERIMENTAL and can be changed without a notice
- *
- * \since QGIS 3.2
- */
-class CORE_EXPORT QgsMeshDatasetValue
-{
- public:
- //! Constructor for vector value
- QgsMeshDatasetValue( double x,
- double y );
-
- //! Constructor for scalar value
- QgsMeshDatasetValue( double scalar );
-
- //! Default Ctor, initialize to NaN
- QgsMeshDatasetValue() = default;
-
- //! Dtor
- ~QgsMeshDatasetValue() = default;
-
- //! Sets scalar value
- void set( double scalar );
-
- //! Sets X value
- void setX( double x );
-
- //! Sets Y value
- void setY( double y ) ;
-
- //! Returns magnitude of vector for vector data or scalar value for scalar data
- double scalar() const;
-
- //! Returns x value
- double x() const;
-
- //! Returns y value
- double y() const;
-
- bool operator==( QgsMeshDatasetValue other ) const;
-
- private:
- double mX = std::numeric_limits::quiet_NaN();
- double mY = std::numeric_limits::quiet_NaN();
-};
-
-/**
- * \ingroup core
- *
- * QgsMeshDataBlock is a block of integers/doubles that can be used
- * to retrieve:
- * active flags (e.g. face's active integer flag)
- * scalars (e.g. scalar dataset double values)
- * vectors (e.g. vector dataset doubles x,y values)
- *
- * data are implicitly shared, so the class can be quickly copied
- * std::numeric_limits::quiet_NaN() represents NODATA value
- *
- * Data can be accessed all at once with values() (faster) or
- * value by value (slower) with active() or value()
- *
- * \since QGIS 3.6
- */
-class CORE_EXPORT QgsMeshDataBlock
-{
- public:
- //! Type of data stored in the block
- enum DataType
- {
- ActiveFlagInteger, //!< Integer boolean flag whether face is active
- ScalarDouble, //!< Scalar double values
- Vector2DDouble, //!< Vector double pairs (x1, y1, x2, y2, ... )
- };
-
- //! Constructs an invalid block
- QgsMeshDataBlock();
-
- //! Constructs a new block
- QgsMeshDataBlock( DataType type, int count );
-
- //! Type of data stored in the block
- DataType type() const;
-
- //! Number of items stored in the block
- int count() const;
-
- //! Whether the block is valid
- bool isValid() const;
-
- /**
- * Returns a value represented by the index
- * For active flag the behavior is undefined
- */
- QgsMeshDatasetValue value( int index ) const;
-
- /**
- * Returns a value for active flag by the index
- * For scalar and vector 2d the behavior is undefined
- */
- bool active( int index ) const;
-
- /**
- * Sets active flag values.
- *
- * If the data provider/datasets does not have active
- * flag capability (== all values are valid), just
- * set block validity by setValid( true )
- *
- * \param vals value vector with size count()
- *
- * For scalar and vector 2d the behavior is undefined
- *
- * \since QGIS 3.12
- */
- void setActive( const QVector &vals );
-
- /**
- * Returns active flag array
- *
- * Even for active flag valid dataset, the returned array could be empty.
- * This means that the data provider/dataset does not support active flag
- * capability, so all faces are active by default.
- *
- * For scalar and vector 2d the behavior is undefined
- *
- * \since QGIS 3.12
- */
- QVector active() const;
-
- /**
- * Returns buffer to the array with values
- * For vector it is pairs (x1, y1, x2, y2, ... )
- *
- * \since QGIS 3.12
- */
- QVector values() const;
-
- /**
- * Sets values
- *
- * For scalar datasets, it must have size count()
- * For vector datasets, it must have size 2 * count()
- * For active flag the behavior is undefined
- *
- * \since QGIS 3.12
- */
- void setValues( const QVector &vals );
-
- //! Sets block validity
- void setValid( bool valid );
-
- private:
- QVector mDoubleBuffer;
- QVector mIntegerBuffer;
- DataType mType;
- int mSize = 0;
- bool mIsValid = false;
-};
-
-/**
- * \ingroup core
- *
- * QgsMesh3dDataBlock is a block of 3d stacked mesh data related N
- * faces defined on base mesh frame.
- *
- * data are implicitly shared, so the class can be quickly copied
- * std::numeric_limits::quiet_NaN() represents NODATA value
- *
- * \note The API is considered EXPERIMENTAL and can be changed without a notice
- *
- * \since QGIS 3.12
- */
-class CORE_EXPORT QgsMesh3dDataBlock
-{
- public:
- //! Constructs an invalid block
- QgsMesh3dDataBlock();
-
- //! Dtor
- ~QgsMesh3dDataBlock();
-
- //! Constructs a new block for count faces
- QgsMesh3dDataBlock( int count, bool isVector );
-
- //! Sets block validity
- void setValid( bool valid );
-
- //! Whether the block is valid
- bool isValid() const;
-
- //! Whether we store vector values
- bool isVector() const;
-
- //! Number of 2d faces for which the volume data is stored in the block
- int count() const;
-
- //! Index of the first volume stored in the buffer (absolute)
- int firstVolumeIndex() const;
-
- //! Index of the last volume stored in the buffer (absolute)
- int lastVolumeIndex() const;
-
- //! Returns number of volumes stored in the buffer
- int volumesCount() const;
-
- /**
- * Returns number of vertical level above 2d faces
- */
- QVector verticalLevelsCount() const;
-
- /**
- * Sets the vertical level counts
- */
- void setVerticalLevelsCount( const QVector &verticalLevelsCount );
-
- /**
- * Returns the vertical levels height
- */
- QVector verticalLevels() const;
-
- /**
- * Sets the vertical levels height
- */
- void setVerticalLevels( const QVector &verticalLevels );
-
- /**
- * Returns the indexing between faces and volumes
- */
- QVector faceToVolumeIndex() const;
-
- /**
- * Sets the indexing between faces and volumes
- */
- void setFaceToVolumeIndex( const QVector &faceToVolumeIndex );
-
- /**
- * Returns the values at volume centers
- *
- * For vector datasets the number of values is doubled (x1, y1, x2, y2, ... )
- */
- QVector values() const;
-
- /**
- * Returns the value at volume centers
- *
- * \param volumeIndex volume index relative to firstVolumeIndex()
- * \returns value (scalar or vector)
- */
- QgsMeshDatasetValue value( int volumeIndex ) const;
-
- /**
- * Sets the values at volume centers
- *
- * For vector datasets the number of values is doubled (x1, y1, x2, y2, ... )
- */
- void setValues( const QVector &doubleBuffer );
-
- private:
- int mSize = 0;
- bool mIsValid = false;
- bool mIsVector = false;
- QVector mVerticalLevelsCount;
- QVector mVerticalLevels;
- QVector mFaceToVolumeIndex;
- QVector mDoubleBuffer; // for scalar/vector values
-};
-
-/**
- * \ingroup core
- *
- * QgsMeshDatasetGroupMetadata is a collection of dataset group metadata
- * such as whether the data is vector or scalar, name
- *
- * \note The API is considered EXPERIMENTAL and can be changed without a notice
- *
- * \since QGIS 3.4
- */
-class CORE_EXPORT QgsMeshDatasetGroupMetadata
-{
- public:
-
- //! Location of where data is specified for datasets in the dataset group
- enum DataType
- {
- DataOnFaces = 0, //!< Data is defined on faces
- DataOnVertices, //!< Data is defined on vertices
- DataOnVolumes, //!< Data is defined on volumes \since QGIS 3.12
- DataOnEdges //!< Data is defined on edges \since QGIS 3.14
- };
-
- //! Constructs an empty metadata object
- QgsMeshDatasetGroupMetadata() = default;
-
- /**
- * Constructs a valid metadata object
- *
- * \param name name of the dataset group
- * \param isScalar dataset contains scalar data, specifically the y-value of QgsMeshDatasetValue is NaN
- * \param dataType where the data are defined on (vertices, faces or volumes)
- * \param minimum minimum value (magnitude for vectors) present among all group's dataset values
- * \param maximum maximum value (magnitude for vectors) present among all group's dataset values
- * \param maximumVerticalLevels maximum number of vertical levels for 3d stacked meshes, 0 for 2d meshes
- * \param referenceTime reference time of the dataset group
- * \param extraOptions dataset's extra options stored by the provider. Usually contains the name, time value, time units, data file vendor, ...
- */
- QgsMeshDatasetGroupMetadata( const QString &name,
- bool isScalar,
- DataType dataType,
- double minimum,
- double maximum,
- int maximumVerticalLevels,
- const QDateTime &referenceTime,
- const QMap &extraOptions );
-
- /**
- * Returns name of the dataset group
- */
- QString name() const;
-
- /**
- * Returns extra metadata options, for example description
- */
- QMap extraOptions() const;
-
- /**
- * \brief Returns whether dataset group has vector data
- */
- bool isVector() const;
-
- /**
- * \brief Returns whether dataset group has scalar data
- */
- bool isScalar() const;
-
- /**
- * Returns whether dataset group data is defined on vertices or faces or volumes
- *
- * \since QGIS 3.12
- */
- DataType dataType() const;
-
- /**
- * \brief Returns minimum scalar value/vector magnitude present for whole dataset group
- */
- double minimum() const;
-
- /**
- * \brief Returns maximum scalar value/vector magnitude present for whole dataset group
- */
- double maximum() const;
-
- /**
- * Returns maximum number of vertical levels for 3d stacked meshes
- *
- * \since QGIS 3.12
- */
- int maximumVerticalLevelsCount() const;
-
- /**
- * Returns the reference time
- *
- * \since QGIS 3.12
- */
- QDateTime referenceTime() const;
-
- private:
- QString mName;
- bool mIsScalar = false;
- DataType mDataType = DataType::DataOnFaces;
- double mMinimumValue = std::numeric_limits::quiet_NaN();
- double mMaximumValue = std::numeric_limits::quiet_NaN();
- QMap mExtraOptions;
- int mMaximumVerticalLevelsCount = 0; // for 3d stacked meshes
- QDateTime mReferenceTime;
-};
-
-/**
- * \ingroup core
- *
- * QgsMeshDatasetMetadata is a collection of mesh dataset metadata such
- * as whether the data is valid or associated time for the dataset
- *
- * \note The API is considered EXPERIMENTAL and can be changed without a notice
- *
- * \since QGIS 3.2
- */
-class CORE_EXPORT QgsMeshDatasetMetadata
-{
- public:
- //! Constructs an empty metadata object
- QgsMeshDatasetMetadata() = default;
-
- /**
- * Constructs a valid metadata object
- *
- * \param time a time which this dataset represents in the dataset group
- * \param isValid dataset is loadad and valid for fetching the data
- * \param minimum minimum value (magnitude for vectors) present among dataset values
- * \param maximum maximum value (magnitude for vectors) present among dataset values
- * \param maximumVerticalLevels maximum number of vertical levels for 3d stacked meshes, 0 for 2d meshes
- */
- QgsMeshDatasetMetadata( double time,
- bool isValid,
- double minimum,
- double maximum,
- int maximumVerticalLevels
- );
-
- /**
- * Returns the time value for this dataset
- */
- double time() const;
-
- /**
- * Returns whether dataset is valid
- */
- bool isValid() const;
-
- /**
- * Returns minimum scalar value/vector magnitude present for the dataset
- */
- double minimum() const;
-
- /**
- * Returns maximum scalar value/vector magnitude present for the dataset
- */
- double maximum() const;
-
- /**
- * Returns maximum number of vertical levels for 3d stacked meshes
- *
- * \since QGIS 3.12
- */
- int maximumVerticalLevelsCount() const;
-
- private:
- double mTime = std::numeric_limits::quiet_NaN();
- bool mIsValid = false;
- double mMinimumValue = std::numeric_limits::quiet_NaN();
- double mMaximumValue = std::numeric_limits::quiet_NaN();
- int mMaximumVerticalLevelsCount = 0; // for 3d stacked meshes
-};
-
/**
* \ingroup core
*
@@ -803,9 +327,24 @@ class CORE_EXPORT QgsMeshDataProvider: public QgsDataProvider, public QgsMeshDat
//! Ctor
QgsMeshDataProvider( const QString &uri, const QgsDataProvider::ProviderOptions &providerOptions );
+ QgsMeshDataProviderTemporalCapabilities *temporalCapabilities() override;
+ const QgsMeshDataProviderTemporalCapabilities *temporalCapabilities() const override SIP_SKIP;
+
+ /**
+ * Sets the temporal unit of the provider and reload data if it changes.
+ *
+ * \param unit the temporal unit
+ *
+ * \since QGIS 3.14
+ */
+ void setTemporalUnit( QgsUnitTypes::TemporalUnit unit );
+
signals:
//! Emitted when some new dataset groups have been added
void datasetGroupsAdded( int count );
+
+ private:
+ std::unique_ptr mTemporalCapabilities;
};
#endif // QGSMESHDATAPROVIDER_H
diff --git a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp
new file mode 100644
index 000000000000..28cf516ed804
--- /dev/null
+++ b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.cpp
@@ -0,0 +1,153 @@
+/***************************************************************************
+ qgsmeshdataprovidertemporalcapabilities.cpp
+ -----------------------
+ begin : March 2020
+ copyright : (C) 2020 by Vincent Cloarec
+ email : vcloarec 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 "qgsmeshdataprovidertemporalcapabilities.h"
+
+
+QgsMeshDataProviderTemporalCapabilities::QgsMeshDataProviderTemporalCapabilities(): QgsDataProviderTemporalCapabilities()
+{}
+
+QgsMeshDatasetIndex QgsMeshDataProviderTemporalCapabilities::datasetIndexFromRelativeTimeRange( int group, qint64 startTimeSinceGlobalReference, qint64 endTimeSinceGlobalReference ) const
+{
+ // No time --> non temporal dataset, so return the dataset that has to be the only one
+ const QList &datasetTimes = mDatasetTimeSinceGroupReference[group];
+ if ( datasetTimes.isEmpty() )
+ return QgsMeshDatasetIndex( group, 0 );
+ const QDateTime groupReference = mGroupsReferenceDateTime[group];
+ const qint64 startTimeSinceGroupReference =
+ startTimeSinceGlobalReference - mGlobalReferenceDateTime.msecsTo( groupReference );
+ const qint64 endTimeSinceGroupReference =
+ endTimeSinceGlobalReference - mGlobalReferenceDateTime.msecsTo( groupReference );
+
+ if ( startTimeSinceGroupReference >= datasetTimes.last() )
+ return QgsMeshDatasetIndex();
+
+ for ( int i = 0; i < datasetTimes.count(); ++i )
+ {
+ qint64 time = datasetTimes.at( i );
+ if ( startTimeSinceGroupReference <= time )
+ {
+ if ( endTimeSinceGroupReference <= time )
+ return QgsMeshDatasetIndex( group, i - 1 ); // invalid if i=0
+ else
+ return QgsMeshDatasetIndex( group, i );
+ }
+ }
+
+ // if we are here (normally, this could no happen), return invalid dataset index
+ return QgsMeshDatasetIndex();
+}
+
+void QgsMeshDataProviderTemporalCapabilities::addGroupReferenceDateTime( int group, const QDateTime &reference )
+{
+ if ( ( !mGlobalReferenceDateTime.isValid() && reference.isValid() ) ||
+ ( reference.isValid() && mGlobalReferenceDateTime.isValid() && reference < mGlobalReferenceDateTime ) )
+ mGlobalReferenceDateTime = reference;
+
+ mGroupsReferenceDateTime[group] = reference;
+}
+
+void QgsMeshDataProviderTemporalCapabilities::addDatasetTimeInMilliseconds( int group, qint64 time )
+{
+ QList &datasetTimes = mDatasetTimeSinceGroupReference[group];
+ datasetTimes.append( time );
+}
+
+void QgsMeshDataProviderTemporalCapabilities::addDatasetTime( int group, double time )
+{
+ qint64 unitTimeFactor = QgsUnitTypes::fromUnitToUnitFactor( mTemporalUnit, QgsUnitTypes::TemporalMilliseconds );
+ addDatasetTimeInMilliseconds( group, time * unitTimeFactor );
+}
+
+bool QgsMeshDataProviderTemporalCapabilities::hasReferenceTime() const
+{
+ return mGlobalReferenceDateTime.isValid();
+}
+
+QDateTime QgsMeshDataProviderTemporalCapabilities::referenceTime() const
+{
+ return mGlobalReferenceDateTime;
+}
+
+QgsDateTimeRange QgsMeshDataProviderTemporalCapabilities::timeExtent() const
+{
+
+ return timeExtent( mGlobalReferenceDateTime );
+}
+
+QgsDateTimeRange QgsMeshDataProviderTemporalCapabilities::timeExtent( const QDateTime &reference ) const
+{
+ QDateTime end;
+ QDateTime begin;
+ for ( QHash::const_iterator it = mGroupsReferenceDateTime.constBegin() ;
+ it != mGroupsReferenceDateTime.constEnd(); ++it )
+ {
+ QDateTime groupReference = it.value();
+ if ( !groupReference.isValid() ) //the dataset group has not a valid reference time -->take global reference
+ groupReference = mGlobalReferenceDateTime;
+
+
+ if ( !groupReference.isValid() )
+ groupReference = reference;
+
+ const QList times = mDatasetTimeSinceGroupReference[it.key()];
+ qint64 durationSinceFirst = groupReference.msecsTo( reference );
+ qint64 durationSinceLast = groupReference.msecsTo( reference );
+ if ( !times.isEmpty() )
+ {
+ durationSinceFirst += times.first();
+ durationSinceLast += times.last();
+ }
+
+ if ( !end.isValid() || groupReference.addMSecs( durationSinceLast ) > end )
+ end = groupReference.addMSecs( durationSinceLast );
+
+ if ( !begin.isValid() || groupReference.addMSecs( durationSinceFirst ) > begin )
+ begin = groupReference.addMSecs( durationSinceFirst );
+ }
+
+ return QgsDateTimeRange( begin, end );
+}
+
+void QgsMeshDataProviderTemporalCapabilities::setTemporalUnit( QgsUnitTypes::TemporalUnit timeUnit )
+{
+ mTemporalUnit = timeUnit;
+}
+
+QgsUnitTypes::TemporalUnit QgsMeshDataProviderTemporalCapabilities::temporalUnit() const
+{
+ return mTemporalUnit;
+}
+
+qint64 QgsMeshDataProviderTemporalCapabilities::datasetTime( const QgsMeshDatasetIndex &index ) const
+{
+ if ( !index.isValid() )
+ return -999999;
+
+ const QList ×List = mDatasetTimeSinceGroupReference[index.group()];
+ if ( index.dataset() < timesList.count() )
+ return timesList.at( index.dataset() );
+ else
+ return -999999;
+}
+
+void QgsMeshDataProviderTemporalCapabilities::clear()
+{
+ mGlobalReferenceDateTime = QDateTime();
+ mGroupsReferenceDateTime.clear();
+ mDatasetTimeSinceGroupReference.clear();
+}
diff --git a/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h
new file mode 100644
index 000000000000..e2b3b5a05305
--- /dev/null
+++ b/src/core/mesh/qgsmeshdataprovidertemporalcapabilities.h
@@ -0,0 +1,139 @@
+/***************************************************************************
+ qgsmeshdataprovidertemporalcapabilities.h
+ -----------------------
+ begin : March 2020
+ copyright : (C) 2020 by Vincent Cloarec
+ email : vcloarec at gmail dot com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGSMESHDATAPROVIDERTEMPORALCAPABILITIES_H
+#define QGSMESHDATAPROVIDERTEMPORALCAPABILITIES_H
+
+#include "qgsdataprovidertemporalcapabilities.h"
+#include "qgsrange.h"
+#include "qgsmeshdataset.h"
+
+
+/**
+ * \class QgsMeshDataProviderTemporalCapabilities
+ * \ingroup core
+ * Class for handling properties relating to a mesh data provider's temporal capabilities.
+ *
+ * \since QGIS 3.14
+ */
+class CORE_EXPORT QgsMeshDataProviderTemporalCapabilities: public QgsDataProviderTemporalCapabilities
+{
+ public:
+
+ /**
+ * Constructor for QgsMeshDataProviderTemporalCapabilities
+ */
+ QgsMeshDataProviderTemporalCapabilities();
+
+ /**
+ * Returns the first dataset that are include in the range [\a startTimeSinceGlobalReference,\a endTimeSinceGlobalReference[ (in milliseconds)
+ * from the dataset \a group. If no dataset is present in this range return the last dataset before this range if it not the last one
+ * of whole the dataset group
+ *
+ * Returns invalid dataset index if there is no data set in the range
+ *
+ * \note for non temporal dataset group, the range is not used and the unique dataset is returned
+ */
+ QgsMeshDatasetIndex datasetIndexFromRelativeTimeRange( int group, qint64 startTimeSinceGlobalReference, qint64 endTimeSinceGlobalReference ) const;
+
+ /**
+ * Adds a \a reference date/time from a dataset \a group
+ *
+ * \note must be used only by the mesh data provider
+ */
+ void addGroupReferenceDateTime( int group, const QDateTime &reference ) SIP_SKIP;
+
+ /**
+ * Adds a \a time (in milliseconds) from a dataset contained in \a group
+ *
+ * \note must be used only by the mesh data provider,
+ * all dataset need to be added one after one
+ */
+ void addDatasetTimeInMilliseconds( int group, qint64 time ) SIP_SKIP;
+
+ /**
+ * Adds a \a time (in provider unit) from a dataset contained in \a group
+ *
+ * \note must be used only by the mesh data provider,
+ * all dataset need to be added one after one
+ */
+ void addDatasetTime( int group, double time ) SIP_SKIP;
+
+ /**
+ * Returns whether the reference time is set
+ */
+ bool hasReferenceTime() const;
+
+ /**
+ * Returns the reference time
+ */
+ QDateTime referenceTime() const;
+
+ /**
+ * Returns the time extent using the internal reference time
+ * and the first and last times available from the all the dataset
+ */
+ QgsDateTimeRange timeExtent() const;
+
+ /**
+ * Returns the time extent using an external \a reference date time
+ * and the first and last times available from the all the dataset
+ */
+ QgsDateTimeRange timeExtent( const QDateTime &reference ) const;
+
+ /**
+ * Sets the temporal unit (\a temporalUnit) used to read data by the data provider
+ *
+ * Temporal units supported are milliseconds, seconds, minutes, hors, days and weeks
+ */
+ void setTemporalUnit( QgsUnitTypes::TemporalUnit temporalUnit );
+
+ /**
+ * Returns the temporal unit used to read data by the data provider
+ */
+ QgsUnitTypes::TemporalUnit temporalUnit() const;
+
+ /**
+ * Returns the relative time in milliseconds of the dataset
+ */
+ qint64 datasetTime( const QgsMeshDatasetIndex &index ) const;
+
+ /**
+ * Clears alls stored reference times and dataset times
+ */
+ void clear();
+
+ private:
+
+ //! Holds the global reference date/time value if exists (min of all groups), otherwise it is invalid
+ QDateTime mGlobalReferenceDateTime;
+
+ //! Holds the reference time of each dataset groups
+ QHash mGroupsReferenceDateTime;
+
+ /**
+ * Holds the time of each dataset in milliseconds.
+ * The times are from the dataset groups reference time if any,
+ * otherwise from 0
+ * Non Temporal dataset (static) have empty list
+ */
+ QHash> mDatasetTimeSinceGroupReference;
+
+ QgsUnitTypes::TemporalUnit mTemporalUnit = QgsUnitTypes::TemporalHours;
+};
+
+#endif // QGSMESHDATAPROVIDERTEMPORALCAPABILITIES_H
diff --git a/src/core/mesh/qgsmeshdataset.cpp b/src/core/mesh/qgsmeshdataset.cpp
new file mode 100644
index 000000000000..fce759dc6943
--- /dev/null
+++ b/src/core/mesh/qgsmeshdataset.cpp
@@ -0,0 +1,437 @@
+/***************************************************************************
+ qgsmeshdataset.cpp
+ -----------------------
+ begin : April 2018
+ copyright : (C) 2018 by Peter Petrik
+ email : zilolv 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 "qgsmeshdataset.h"
+#include "qgsrectangle.h"
+#include "qgis.h"
+
+QgsMeshDatasetIndex::QgsMeshDatasetIndex( int group, int dataset )
+ : mGroupIndex( group ), mDatasetIndex( dataset )
+{}
+
+int QgsMeshDatasetIndex::group() const
+{
+ return mGroupIndex;
+}
+
+int QgsMeshDatasetIndex::dataset() const
+{
+ return mDatasetIndex;
+}
+
+bool QgsMeshDatasetIndex::isValid() const
+{
+ return ( group() > -1 ) && ( dataset() > -1 );
+}
+
+bool QgsMeshDatasetIndex::operator ==( QgsMeshDatasetIndex other ) const
+{
+ if ( isValid() && other.isValid() )
+ return other.group() == group() && other.dataset() == dataset();
+ else
+ return isValid() == other.isValid();
+}
+
+bool QgsMeshDatasetIndex::operator !=( QgsMeshDatasetIndex other ) const
+{
+ return !( operator==( other ) );
+}
+
+QgsMeshDatasetValue::QgsMeshDatasetValue( double x, double y )
+ : mX( x ), mY( y )
+{}
+
+QgsMeshDatasetValue::QgsMeshDatasetValue( double scalar )
+ : mX( scalar )
+{}
+
+double QgsMeshDatasetValue::scalar() const
+{
+ if ( std::isnan( mY ) )
+ {
+ return mX;
+ }
+ else if ( std::isnan( mX ) )
+ {
+ return std::numeric_limits::quiet_NaN();
+ }
+ else
+ {
+ return std::sqrt( ( mX ) * ( mX ) + ( mY ) * ( mY ) );
+ }
+}
+
+void QgsMeshDatasetValue::set( double scalar )
+{
+ setX( scalar );
+}
+
+void QgsMeshDatasetValue::setX( double x )
+{
+ mX = x;
+}
+
+void QgsMeshDatasetValue::setY( double y )
+{
+ mY = y;
+}
+
+double QgsMeshDatasetValue::x() const
+{
+ return mX;
+}
+
+double QgsMeshDatasetValue::y() const
+{
+ return mY;
+}
+
+bool QgsMeshDatasetValue::operator==( const QgsMeshDatasetValue other ) const
+{
+ bool equal = std::isnan( mX ) == std::isnan( other.x() );
+ equal &= std::isnan( mY ) == std::isnan( other.y() );
+
+ if ( equal )
+ {
+ if ( std::isnan( mY ) )
+ {
+ equal &= qgsDoubleNear( other.x(), mX, 1E-8 );
+ }
+ else
+ {
+ equal &= qgsDoubleNear( other.x(), mX, 1E-8 );
+ equal &= qgsDoubleNear( other.y(), mY, 1E-8 );
+ }
+ }
+ return equal;
+}
+
+QgsMeshDatasetGroupMetadata::QgsMeshDatasetGroupMetadata( const QString &name,
+ bool isScalar,
+ DataType dataType,
+ double minimum,
+ double maximum,
+ int maximumVerticalLevels,
+ const QDateTime &referenceTime,
+ bool isTemporal,
+ const QMap &extraOptions )
+ : mName( name )
+ , mIsScalar( isScalar )
+ , mDataType( dataType )
+ , mMinimumValue( minimum )
+ , mMaximumValue( maximum )
+ , mExtraOptions( extraOptions )
+ , mMaximumVerticalLevelsCount( maximumVerticalLevels )
+ , mReferenceTime( referenceTime )
+ , mIsTemporal( isTemporal )
+
+{
+}
+
+QMap QgsMeshDatasetGroupMetadata::extraOptions() const
+{
+ return mExtraOptions;
+}
+
+bool QgsMeshDatasetGroupMetadata::isVector() const
+{
+ return !mIsScalar;
+}
+
+bool QgsMeshDatasetGroupMetadata::isScalar() const
+{
+ return mIsScalar;
+}
+
+bool QgsMeshDatasetGroupMetadata::isTemporal() const
+{
+ return mIsTemporal;
+}
+
+QString QgsMeshDatasetGroupMetadata::name() const
+{
+ return mName;
+}
+
+QgsMeshDatasetGroupMetadata::DataType QgsMeshDatasetGroupMetadata::dataType() const
+{
+ return mDataType;
+}
+
+double QgsMeshDatasetGroupMetadata::minimum() const
+{
+ return mMinimumValue;
+}
+
+double QgsMeshDatasetGroupMetadata::maximum() const
+{
+ return mMaximumValue;
+}
+
+int QgsMeshDatasetGroupMetadata::maximumVerticalLevelsCount() const
+{
+ return mMaximumVerticalLevelsCount;
+}
+
+QDateTime QgsMeshDatasetGroupMetadata::referenceTime() const
+{
+ return mReferenceTime;
+}
+
+QgsMeshDatasetMetadata::QgsMeshDatasetMetadata(
+ double time,
+ bool isValid,
+ double minimum,
+ double maximum,
+ int maximumVerticalLevels )
+ : mTime( time )
+ , mIsValid( isValid )
+ , mMinimumValue( minimum )
+ , mMaximumValue( maximum )
+ , mMaximumVerticalLevelsCount( maximumVerticalLevels )
+{
+}
+
+double QgsMeshDatasetMetadata::time() const
+{
+ return mTime;
+}
+
+bool QgsMeshDatasetMetadata::isValid() const
+{
+ return mIsValid;
+}
+
+double QgsMeshDatasetMetadata::minimum() const
+{
+ return mMinimumValue;
+}
+
+double QgsMeshDatasetMetadata::maximum() const
+{
+ return mMaximumValue;
+}
+
+int QgsMeshDatasetMetadata::maximumVerticalLevelsCount() const
+{
+ return mMaximumVerticalLevelsCount;
+}
+
+QgsMeshDataBlock::QgsMeshDataBlock()
+ : mType( ActiveFlagInteger )
+{
+}
+
+QgsMeshDataBlock::QgsMeshDataBlock( QgsMeshDataBlock::DataType type, int count )
+ : mType( type ),
+ mSize( count )
+{
+}
+
+QgsMeshDataBlock::DataType QgsMeshDataBlock::type() const
+{
+ return mType;
+}
+
+int QgsMeshDataBlock::count() const
+{
+ return mSize;
+}
+
+bool QgsMeshDataBlock::isValid() const
+{
+ return ( count() > 0 ) && ( mIsValid );
+}
+
+QgsMeshDatasetValue QgsMeshDataBlock::value( int index ) const
+{
+ if ( !isValid() )
+ return QgsMeshDatasetValue();
+
+ Q_ASSERT( mType != ActiveFlagInteger );
+
+ if ( mType == ScalarDouble )
+ return QgsMeshDatasetValue( mDoubleBuffer[index] );
+
+ return QgsMeshDatasetValue(
+ mDoubleBuffer[2 * index],
+ mDoubleBuffer[2 * index + 1]
+ );
+}
+
+bool QgsMeshDataBlock::active( int index ) const
+{
+ if ( !isValid() )
+ return false;
+
+ Q_ASSERT( mType == ActiveFlagInteger );
+
+ if ( mIntegerBuffer.empty() )
+ return true;
+ else
+ return bool( mIntegerBuffer[index] );
+}
+
+void QgsMeshDataBlock::setActive( const QVector &vals )
+{
+ Q_ASSERT( mType == ActiveFlagInteger );
+ Q_ASSERT( vals.size() == count() );
+
+ mIntegerBuffer = vals;
+ setValid( true );
+}
+
+QVector QgsMeshDataBlock::active() const
+{
+ Q_ASSERT( mType == ActiveFlagInteger );
+ return mIntegerBuffer;
+}
+
+QVector QgsMeshDataBlock::values() const
+{
+ Q_ASSERT( mType != ActiveFlagInteger );
+
+ return mDoubleBuffer;
+}
+
+void QgsMeshDataBlock::setValues( const QVector &vals )
+{
+ Q_ASSERT( mType != ActiveFlagInteger );
+ Q_ASSERT( mType == ScalarDouble ? vals.size() == count() : vals.size() == 2 * count() );
+
+ mDoubleBuffer = vals;
+ setValid( true );
+}
+
+void QgsMeshDataBlock::setValid( bool valid )
+{
+ mIsValid = valid;
+}
+
+QgsMesh3dDataBlock::QgsMesh3dDataBlock() = default;
+
+QgsMesh3dDataBlock::~QgsMesh3dDataBlock() {};
+
+QgsMesh3dDataBlock::QgsMesh3dDataBlock( int count, bool isVector )
+ : mSize( count )
+ , mIsVector( isVector )
+{
+}
+
+bool QgsMesh3dDataBlock::isValid() const
+{
+ return mIsValid;
+}
+
+bool QgsMesh3dDataBlock::isVector() const
+{
+ return mIsVector;
+}
+
+int QgsMesh3dDataBlock::count() const
+{
+ return mSize;
+}
+
+int QgsMesh3dDataBlock::firstVolumeIndex() const
+{
+ if ( mFaceToVolumeIndex.empty() )
+ return -1;
+ return mFaceToVolumeIndex[0];
+}
+
+int QgsMesh3dDataBlock::lastVolumeIndex() const
+{
+ if ( mFaceToVolumeIndex.empty() || mVerticalLevelsCount.empty() )
+ return -1;
+ const int lastVolumeStartIndex = mFaceToVolumeIndex[mFaceToVolumeIndex.size() - 1];
+ const int volumesCountInLastRow = mVerticalLevelsCount[mVerticalLevelsCount.size() - 1];
+ return lastVolumeStartIndex + volumesCountInLastRow;
+}
+
+int QgsMesh3dDataBlock::volumesCount() const
+{
+ return lastVolumeIndex() - firstVolumeIndex();
+}
+
+QVector QgsMesh3dDataBlock::verticalLevelsCount() const
+{
+ Q_ASSERT( isValid() );
+ return mVerticalLevelsCount;
+}
+
+void QgsMesh3dDataBlock::setFaceToVolumeIndex( const QVector &faceToVolumeIndex )
+{
+ Q_ASSERT( faceToVolumeIndex.size() == count() );
+ mFaceToVolumeIndex = faceToVolumeIndex;
+}
+
+void QgsMesh3dDataBlock::setVerticalLevelsCount( const QVector &verticalLevelsCount )
+{
+ Q_ASSERT( verticalLevelsCount.size() == count() );
+ mVerticalLevelsCount = verticalLevelsCount;
+}
+
+QVector QgsMesh3dDataBlock::verticalLevels() const
+{
+ Q_ASSERT( isValid() );
+ return mVerticalLevels;
+}
+
+void QgsMesh3dDataBlock::setVerticalLevels( const QVector &verticalLevels )
+{
+ Q_ASSERT( verticalLevels.size() == volumesCount() + count() );
+ mVerticalLevels = verticalLevels;
+}
+
+QVector QgsMesh3dDataBlock::faceToVolumeIndex() const
+{
+ Q_ASSERT( isValid() );
+ return mFaceToVolumeIndex;
+}
+
+QVector QgsMesh3dDataBlock::values() const
+{
+ Q_ASSERT( isValid() );
+ return mDoubleBuffer;
+}
+
+QgsMeshDatasetValue QgsMesh3dDataBlock::value( int volumeIndex ) const
+{
+ if ( !isValid() )
+ return QgsMeshDatasetValue();
+
+ if ( !mIsVector )
+ return QgsMeshDatasetValue( mDoubleBuffer[volumeIndex] );
+
+ return QgsMeshDatasetValue(
+ mDoubleBuffer[2 * volumeIndex],
+ mDoubleBuffer[2 * volumeIndex + 1]
+ );
+}
+
+void QgsMesh3dDataBlock::setValues( const QVector &doubleBuffer )
+{
+ Q_ASSERT( doubleBuffer.size() == ( isVector() ? 2 * volumesCount() : volumesCount() ) );
+ mDoubleBuffer = doubleBuffer;
+}
+
+void QgsMesh3dDataBlock::setValid( bool valid )
+{
+ mIsValid = valid;
+}
+
diff --git a/src/core/mesh/qgsmeshdataset.h b/src/core/mesh/qgsmeshdataset.h
new file mode 100644
index 000000000000..37db333eae1b
--- /dev/null
+++ b/src/core/mesh/qgsmeshdataset.h
@@ -0,0 +1,524 @@
+/***************************************************************************
+ qgsmeshdataset.h
+ ---------------------
+ begin : April 2018
+ copyright : (C) 2018 by Peter Petrik
+ email : zilolv at gmail dot com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGSMESHDATASET_H
+#define QGSMESHDATASET_H
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "qgis_core.h"
+#include "qgspoint.h"
+#include "qgsdataprovider.h"
+
+
+class QgsRectangle;
+
+/**
+ * \ingroup core
+ *
+ * QgsMeshDatasetIndex is index that identifies the dataset group (e.g. wind speed)
+ * and a dataset in this group (e.g. magnitude of wind speed in particular time)
+ *
+ * \note The API is considered EXPERIMENTAL and can be changed without a notice
+ *
+ * \since QGIS 3.4
+ */
+class CORE_EXPORT QgsMeshDatasetIndex
+{
+ public:
+ //! Creates an index. -1 represents invalid group/dataset
+ QgsMeshDatasetIndex( int group = -1, int dataset = -1 );
+ //! Returns a group index
+ int group() const;
+ //! Returns a dataset index within group()
+ int dataset() const;
+ //! Returns whether index is valid, ie at least groups is set
+ bool isValid() const;
+ //! Equality operator
+ bool operator == ( QgsMeshDatasetIndex other ) const;
+ //! Inequality operator
+ bool operator != ( QgsMeshDatasetIndex other ) const;
+ private:
+ int mGroupIndex = -1;
+ int mDatasetIndex = -1;
+};
+
+/**
+ * \ingroup core
+ *
+ * QgsMeshDatasetValue represents single dataset value
+ *
+ * could be scalar or vector. Nodata values are represented by NaNs.
+ *
+ * \note The API is considered EXPERIMENTAL and can be changed without a notice
+ *
+ * \since QGIS 3.2
+ */
+class CORE_EXPORT QgsMeshDatasetValue
+{
+ public:
+ //! Constructor for vector value
+ QgsMeshDatasetValue( double x,
+ double y );
+
+ //! Constructor for scalar value
+ QgsMeshDatasetValue( double scalar );
+
+ //! Default Ctor, initialize to NaN
+ QgsMeshDatasetValue() = default;
+
+ //! Dtor
+ ~QgsMeshDatasetValue() = default;
+
+ //! Sets scalar value
+ void set( double scalar );
+
+ //! Sets X value
+ void setX( double x );
+
+ //! Sets Y value
+ void setY( double y ) ;
+
+ //! Returns magnitude of vector for vector data or scalar value for scalar data
+ double scalar() const;
+
+ //! Returns x value
+ double x() const;
+
+ //! Returns y value
+ double y() const;
+
+ bool operator==( QgsMeshDatasetValue other ) const;
+
+ private:
+ double mX = std::numeric_limits::quiet_NaN();
+ double mY = std::numeric_limits::quiet_NaN();
+};
+
+/**
+ * \ingroup core
+ *
+ * QgsMeshDataBlock is a block of integers/doubles that can be used
+ * to retrieve:
+ * active flags (e.g. face's active integer flag)
+ * scalars (e.g. scalar dataset double values)
+ * vectors (e.g. vector dataset doubles x,y values)
+ *
+ * data are implicitly shared, so the class can be quickly copied
+ * std::numeric_limits::quiet_NaN() represents NODATA value
+ *
+ * Data can be accessed all at once with values() (faster) or
+ * value by value (slower) with active() or value()
+ *
+ * \since QGIS 3.6
+ */
+class CORE_EXPORT QgsMeshDataBlock
+{
+ public:
+ //! Type of data stored in the block
+ enum DataType
+ {
+ ActiveFlagInteger, //!< Integer boolean flag whether face is active
+ ScalarDouble, //!< Scalar double values
+ Vector2DDouble, //!< Vector double pairs (x1, y1, x2, y2, ... )
+ };
+
+ //! Constructs an invalid block
+ QgsMeshDataBlock();
+
+ //! Constructs a new block
+ QgsMeshDataBlock( DataType type, int count );
+
+ //! Type of data stored in the block
+ DataType type() const;
+
+ //! Number of items stored in the block
+ int count() const;
+
+ //! Whether the block is valid
+ bool isValid() const;
+
+ /**
+ * Returns a value represented by the index
+ * For active flag the behavior is undefined
+ */
+ QgsMeshDatasetValue value( int index ) const;
+
+ /**
+ * Returns a value for active flag by the index
+ * For scalar and vector 2d the behavior is undefined
+ */
+ bool active( int index ) const;
+
+ /**
+ * Sets active flag values.
+ *
+ * If the data provider/datasets does not have active
+ * flag capability (== all values are valid), just
+ * set block validity by setValid( true )
+ *
+ * \param vals value vector with size count()
+ *
+ * For scalar and vector 2d the behavior is undefined
+ *
+ * \since QGIS 3.12
+ */
+ void setActive( const QVector &vals );
+
+ /**
+ * Returns active flag array
+ *
+ * Even for active flag valid dataset, the returned array could be empty.
+ * This means that the data provider/dataset does not support active flag
+ * capability, so all faces are active by default.
+ *
+ * For scalar and vector 2d the behavior is undefined
+ *
+ * \since QGIS 3.12
+ */
+ QVector active() const;
+
+ /**
+ * Returns buffer to the array with values
+ * For vector it is pairs (x1, y1, x2, y2, ... )
+ *
+ * \since QGIS 3.12
+ */
+ QVector values() const;
+
+ /**
+ * Sets values
+ *
+ * For scalar datasets, it must have size count()
+ * For vector datasets, it must have size 2 * count()
+ * For active flag the behavior is undefined
+ *
+ * \since QGIS 3.12
+ */
+ void setValues( const QVector &vals );
+
+ //! Sets block validity
+ void setValid( bool valid );
+
+ private:
+ QVector mDoubleBuffer;
+ QVector mIntegerBuffer;
+ DataType mType;
+ int mSize = 0;
+ bool mIsValid = false;
+};
+
+/**
+ * \ingroup core
+ *
+ * QgsMesh3dDataBlock is a block of 3d stacked mesh data related N
+ * faces defined on base mesh frame.
+ *
+ * data are implicitly shared, so the class can be quickly copied
+ * std::numeric_limits::quiet_NaN() represents NODATA value
+ *
+ * \note The API is considered EXPERIMENTAL and can be changed without a notice
+ *
+ * \since QGIS 3.12
+ */
+class CORE_EXPORT QgsMesh3dDataBlock
+{
+ public:
+ //! Constructs an invalid block
+ QgsMesh3dDataBlock();
+
+ //! Dtor
+ ~QgsMesh3dDataBlock();
+
+ //! Constructs a new block for count faces
+ QgsMesh3dDataBlock( int count, bool isVector );
+
+ //! Sets block validity
+ void setValid( bool valid );
+
+ //! Whether the block is valid
+ bool isValid() const;
+
+ //! Whether we store vector values
+ bool isVector() const;
+
+ //! Number of 2d faces for which the volume data is stored in the block
+ int count() const;
+
+ //! Index of the first volume stored in the buffer (absolute)
+ int firstVolumeIndex() const;
+
+ //! Index of the last volume stored in the buffer (absolute)
+ int lastVolumeIndex() const;
+
+ //! Returns number of volumes stored in the buffer
+ int volumesCount() const;
+
+ /**
+ * Returns number of vertical level above 2d faces
+ */
+ QVector verticalLevelsCount() const;
+
+ /**
+ * Sets the vertical level counts
+ */
+ void setVerticalLevelsCount( const QVector &verticalLevelsCount );
+
+ /**
+ * Returns the vertical levels height
+ */
+ QVector