diff --git a/.ci/azure-pipelines/azure-pipelines.yml b/.ci/azure-pipelines/azure-pipelines.yml deleted file mode 100644 index 73406cb0724cf..0000000000000 --- a/.ci/azure-pipelines/azure-pipelines.yml +++ /dev/null @@ -1,221 +0,0 @@ -variables: - LR: release-3_12 - LTR: release-3_10 - CTEST_CUSTOM_TESTS_IGNORE: "ProcessingGdalAlgorithmsRasterTest;ProcessingGdalAlgorithmsVectorTest;ProcessingGrass7AlgorithmsImageryTest;ProcessingGrass7AlgorithmsRasterTest;ProcessingGrass7AlgorithmsVectorTest;ProcessingGuiTest;ProcessingOtbAlgorithmsTest;ProcessingQgisAlgorithmsTestPt1;ProcessingQgisAlgorithmsTestPt2;ProcessingQgisAlgorithmsTestPt3;ProcessingQgisAlgorithmsTestPt4;ProcessingScriptUtilsTest;PyQgsAnnotation;PyQgsAppStartup;PyQgsAuthManagerOAuth2OWSTest;PyQgsAuthManagerPasswordOWSTest;PyQgsAuthManagerPKIOWSTest;PyQgsAuthManagerProxy;PyQgsAuthSettingsWidget;PyQgsAuxiliaryStorage;PyQgsBlockingNetworkRequest;PyQgsExifTools;PyQgsFileDownloader;PyQgsFileUtils;PyQgsGeometryTest;PyQgsImageCache;PyQgsImportIntoPostGIS;PyQgsLayoutAtlas;PyQgsLayoutLegend;PyQgsLayoutMap;PyQgsLayoutMapGrid;PyQgsMapLayer;PyQgsOfflineEditingWFS;PyQgsOGRProvider;PyQgsOGRProviderGpkg;PyQgsOGRProviderSqlite;PyQgsPalLabelingCanvas;PyQgsPalLabelingLayout;PyQgsPalLabelingPlacement;PyQgsPointDisplacementRenderer;PyQgsProject;PyQgsProviderConnectionGpkg;PyQgsProviderConnectionPostgres;PyQgsPythonProvider;PyQgsRasterFileWriter;PyQgsRasterLayer;PyQgsSelectiveMasking;PyQgsServerAccessControlWMSGetlegendgraphic;PyQgsServerApi;PyQgsServerCacheManager;PyQgsServerLocaleOverride;PyQgsServerSecurity;PyQgsServerSettings;PyQgsServerWMS;PyQgsServerWMSDimension;PyQgsServerWMSGetFeatureInfo;PyQgsServerWMSGetLegendGraphic;PyQgsServerWMSGetMap;PyQgsServerWMSGetPrint;PyQgsServerWMSGetPrintExtra;PyQgsServerWMSGetPrintOutputs;PyQgsServerWMSGetPrintAtlas;PyQgsServerWMTS;PyQgsSettings;PyQgsShapefileProvider;PyQgsSpatialiteProvider;PyQgsSvgCache;PyQgsSymbolLayer;PyQgsTaskManager;PyQgsTextRenderer;PyQgsVectorFileWriter;PyQgsVectorLayer;PyQgsVectorLayerUtils;PyQgsVirtualLayerProvider;PyQgsWFSProviderGUI;PyQgsZipUtils;qgis_3drenderingtest;qgis_alignrastertest;qgis_arcgisrestutilstest;qgis_banned_keywords;qgis_browsermodeltest;qgis_callouttest;qgis_compositionconvertertest;qgis_coordinatereferencesystemtest;qgis_datadefinedsizelegendtest;qgis_datumtransformdialog;qgis_diagramtest;qgis_doxygen_order;qgis_dxfexporttest;qgis_expressiontest;qgis_filedownloader;qgis_geometrycheckstest;qgis_geometrytest;qgis_grassprovidertest7;qgis_imagecachetest;qgis_invertedpolygonrenderertest;qgis_labelingenginetest;qgis_layerdefinitiontest;qgis_layout3dmaptest;qgis_layouthtmltest;qgis_layoutlabeltest;qgis_layoutmapgridtest;qgis_layoutmaptest;qgis_layoutpicturetest;qgis_layoutscalebartest;qgis_layouttabletest;qgis_legendrenderertest;qgis_licenses;qgis_defwindowtitle;qgis_maprendererjobtest;qgis_maprotationtest;qgis_mapsettingsutilstest;qgis_maptooladdfeatureline;qgis_mimedatautilstest;qgis_networkaccessmanagertest;qgis_openclutilstest;qgis_painteffecttest;qgis_pallabelingtest;qgis_processingtest;qgis_projecttest;qgis_qgisappclipboard;qgis_rasterlayersaveasdialog;qgis_shellcheck;qgis_sipify;qgis_sip_include;qgis_sip_uptodate;qgis_spelling;qgis_styletest;qgis_svgcachetest;qgis_taskmanagertest;qgis_transformdialog;qgis_vectorfilewritertest;qgis_wcsprovidertest;qgis_ziplayertest;qgis_meshcalculator;qgis_pointlocatortest;PyQgsExpressionBuilderWidget;PyQgsDatumTransform;qgis_vertextool;PyQgsCoordinateOperationWidget;PyQgsProviderConnectionSpatialite;qgis_maptoolsplitpartstest;qgis_vectortilelayertest;qgis_ogrproviderguitest" - Agent.Source.Git.ShallowFetchDepth: 120 - -trigger: - branches: - include: -# - master - - $(LR) - - $(LTR) - - azure-pipelines - -pr: -#- master -- $(LR) -- $(LTR) - -jobs: -- job: OSGeo4W - pool: - vmImage: vs2017-win2016 - timeoutInMinutes: 360 - strategy: - maxParallel: 4 - matrix: - x86: - OSGEO4W_ROOT: C:\OSGeo4W - OSGEO4W_ARCH: x86 - CLCACHE_DIR: c:\clcache-x86 - PLATFORM: x86 - CC: C:\OSGeo4W\bin\clcache.bat - CXX: C:\OSGeo4W\bin\clcache.bat - - x86_64: - OSGEO4W_ROOT: C:\OSGeo4W64 - OSGEO4W_ARCH: x86_64 - CLCACHE_DIR: c:\clcache-x86_64 - PLATFORM: x64 - CC: C:\OSGeo4W64\bin\clcache.bat - CXX: C:\OSGeo4W64\bin\clcache.bat - - steps: - - bash: | - if [ "$BUILD_REASON" = "PullRequest" ]; then - branch=$SYSTEM_PULLREQUEST_TARGETBRANCH - pr=$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER - else - branch=$BUILD_SOURCEBRANCHNAME - fi - - echo "BRANCH: ${branch}" - echo "PR: ${pr}" - echo "LR: ${LR}" - echo "LTR: ${LTR}" - - case "${branch}" in - "${LTR}") - OSGEO4W_PKG=qgis-ltr-dev - OSGEO4W_DEPS=qgis-ltr-dev-deps - ;; - "${LR}") - OSGEO4W_PKG=qgis-rel-dev - OSGEO4W_DEPS=qgis-rel-dev-deps - ;; - *) - OSGEO4W_PKG=qgis-dev - OSGEO4W_DEPS=qgis-dev-deps - ;; - esac - - target=Experimental - major=$(sed -ne 's/^SET(CPACK_PACKAGE_VERSION_MAJOR "\([0-9]*\)")\s*$/\1/ip' CMakeLists.txt) - minor=$(sed -ne 's/^SET(CPACK_PACKAGE_VERSION_MINOR "\([0-9]*\)")\s*$/\1/ip' CMakeLists.txt) - patch=$(sed -ne 's/^SET(CPACK_PACKAGE_VERSION_PATCH "\([0-9]*\)")\s*$/\1/ip' CMakeLists.txt) - binary=$(curl --location-trusted http://ftp.osuosl.org/pub/osgeo/download/osgeo4w/$OSGEO4W_ARCH/release/qgis/$OSGEO4W_PKG/LATEST.sha | sed -e "s/:.*$//") - (( binary++ )) || true - - version=$major.$minor.$patch - sha="${BUILD_SOURCEVERSION:0:10}" - - if [ "$BUILD_REASON" = "PullRequest" ]; then - buildname="PR $pr / $branch ($BUILD_BUILDID) ($sha) ($OSGEO4W_PKG $target $OSGEO4W_ARCH)" # no colons allowed here - else - buildname="$OSGEO4W_PKG-$version-$sha-$target-VC14-$OSGEO4W_ARCH" - fi - - url=$buildname - url=${url//(/%28} - url=${url//)/%29} - url=${url// /+} - url="https://cdash.orfeo-toolbox.org/index.php?project=QGIS&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercombine=and&filtercount=4&showfilters=0&filtercombine=and&field1=buildname&compare1=61&value1=$url&field2=site&compare2=65&value2=azure-pipelines&field3=buildstarttime&compare3=83&value3=$(date +%Y-%m-%d --date=yesterday)&field4=buildstarttime&compare4=84&value4=$(date +%Y-%m-%d --date=tomorrow)" - - echo "##vso[task.setvariable variable=TARGET]$target" - echo "##vso[task.setvariable variable=OSGEO4W_PKG]$OSGEO4W_PKG" - echo "##vso[task.setvariable variable=OSGEO4W_DEPS]$OSGEO4W_DEPS" - echo "##vso[task.setvariable variable=MAJOR]$major" - echo "##vso[task.setvariable variable=MINOR]$minor" - echo "##vso[task.setvariable variable=PATCH]$patch" - echo "##vso[task.setvariable variable=BINARY]$binary" - echo "##vso[task.setvariable variable=VERSION]$version" - echo "##vso[task.setvariable variable=BUILDNAME]$buildname" - echo "##vso[task.setvariable variable=DASHURL]${url//&/^&}" - - displayName: 'Setup build variables' - - - script: curl --output c:\setup-x86.exe https://cygwin.com/setup-x86.exe - displayName: 'Download cygwin Installer' - - - script: curl --output c:\osgeo4w-setup.exe http://ftp.osuosl.org/pub/osgeo/download/osgeo4w/osgeo4w-setup-%OSGEO4W_ARCH%.exe - displayName: 'Download OSGeo4W Installer' - - - script: curl --location-trusted --output c:\ninja.zip https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip - displayName: 'Download Ninja' - -# - script: curl --location-trusted --output c:\depends.zip http://www.dependencywalker.com/depends22_%PLATFORM%.zip -# displayName: 'Download Dependency walker' - -# Too large… -# - task: Cache@2 -# inputs: -# key: 'cygwin | $(Date:yyyyMMdd)' -# path: 'c:\cygwin' -# restoreKeys: | -# cygwin | $(Date:yyyyMM) -# cygwin | $(Date:yyyy) -# cygwin -# displayName: Cache cygwin - - - powershell: ms-windows/osgeo4w/runasadmin.ps1 c:\setup-x86.exe -qnNdO -R C:/cygwin -s http://cygwin.mirror.constant.com -l C:/temp/cygwin -P "bison,flex,git,poppler,doxygen,unzip" - displayName: 'Installing cygwin' - -# Too large… -# - task: Cache@2 -# inputs: -# key: 'osgeo4w | $(OSGEO4W_ARCH) | $(Date:yyyyMMdd)' -# path: '$(OSGEO4W_ROOT)' -# restoreKeys: | -# osgeo4w | $(OSGEO4W_ARCH) | $(Date:yyyyMMdd) -# osgeo4w | $(OSGEO4W_ARCH) | $(Date:yyyyMM) -# osgeo4w | $(OSGEO4W_ARCH) | $(Date:yyyy) -# osgeo4w | $(OSGEO4W_ARCH) -# displayName: Cache OSGeo4W - - - powershell: ms-windows/osgeo4w/runasadmin.ps1 c:\osgeo4w-setup.exe --autoaccept --advanced --arch $env:OSGEO4W_ARCH --quiet-mode --upgrade-also --root $env:OSGEO4W_ROOT --only-site -s http://ftp.osuosl.org/pub/osgeo/download/osgeo4w -l c:\temp\osgeo4w -P $env:OSGEO4W_DEPS -P python3-clcache - displayName: 'Installing OSGeo4W' - - - script: | - rmdir /s /q c:\temp\cygwin - rmdir /s /q c:\temp\osgeo4w - displayName: 'Clear package caches' - - - script: c:\cygwin\bin\unzip -o c:\ninja.zip -d %OSGEO4W_ROOT%\bin - displayName: 'Extracting Ninja' - -# - script: c:\cygwin\bin\unzip -o c:\depends.zip -d %OSGEO4W_ROOT%\bin -# displayName: 'Extracting Dependency Walker' - - - script: | - PATH %OSGEO4W_ROOT%\bin;%ProgramFiles%\CMake\bin;%PATH% - cmake --version - ctest --version - ninja --version - displayName: 'Display tool versions' - -# Too large… -# - task: Cache@2 -# inputs: -# key: 'clcache | $(OSGEO4W_ARCH) | $(OSGEO4W_PKG) | $(Date:yyyyMMdd) | $(Hours)' -# path: '$(CLCACHE_DIR)' -# restoreKeys: | -# clcache | $(OSGEO4W_ARCH) | $(OSGEO4W_PKG) | $(Date:yyyyMMdd) | $(Hours) -# clcache | $(OSGEO4W_ARCH) | $(OSGEO4W_PKG) | $(Date:yyyyMMdd) -# clcache | $(OSGEO4W_ARCH) | $(OSGEO4W_PKG) | $(Date:yyyyMM) -# clcache | $(OSGEO4W_ARCH) | $(OSGEO4W_PKG) | $(Date:yyyy) -# clcache | $(OSGEO4W_ARCH) | $(OSGEO4W_PKG) -# displayName: Cache clcache - - - script: | - echo on - PATH c:\cygwin\bin;%OSGEO4W_ROOT%\bin;%PATH% - cd ms-windows\osgeo4w - touch skippackage - set OSGEO4W_CXXFLAGS=/MD /MP /Od /D NDEBUG - @echo ##[section]%OSGEO4W_ARCH% results available at %DASHURL% - package-nightly.cmd %VERSION% %BINARY% %OSGEO4W_PKG% %OSGEO4W_ARCH% %BUILD_SOURCEVERSION:~0,10% azure-pipelines - displayName: 'Building QGIS' - -# - script: | -# echo on -# PATH %OSGEO4W_ROOT%\bin;%PATH% -# cd ms-windows\osgeo4w\build-%OSGEO4W_PKG%-%OSGEO4W_ARCH% -# set /P tag=bootstrap.cmd curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/%O4W_REPO%/master/bootstrap.sh>bootstrap.sh - assoc ShellScript=*.sh - ftype ShellScript=%GITHUB_WORKSPACE%\cygwin\bin\bash.exe %1 %* - assoc PythonScript=*.py - ftype PythonScript=%GITHUB_WORKSPACE%\src\%PKG%\osgeo4w\osgeo4w\bin\python3.exe %1 %* - set O4W_GIT_REPO=%GITHUB_SERVER_URL%/%O4W_REPO% call bootstrap.cmd %PKG% - path - ccache -sv + %GITHUB_WORKSPACE%\scripts\ccache -sv - uses: actions/upload-artifact@v4 with: - name: osgeo4w-repo + name: ${{ matrix.pkg }} path: x86_64/ retention-days: 1 @@ -120,5 +114,5 @@ jobs: uses: actions/cache/save@v4 if: ${{ github.event_name == 'push' }} with: - path: src/${{ matrix.pkg }}/osgeo4w/build + path: ccache key: build-ccache-osgeo4w-${{ matrix.pkg }}-${{ github.ref_name }}-${{ github.run_id }} diff --git a/.gitignore b/.gitignore index 375a9e2d6b360..c1d96f30b493e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,20 +44,6 @@ desktop.ini doc/INSTALL.tex i18n/*.qm ms-windows/*.exe* -ms-windows/Installer-Files/postinstall.bat -ms-windows/Installer-Files/preremove.bat -ms-windows/nsis/ -ms-windows/osgeo4w/addons/ -ms-windows/osgeo4w/binary-* -ms-windows/osgeo4w/build-* -ms-windows/osgeo4w/nsis/ -ms-windows/osgeo4w/packages-x86/ -ms-windows/osgeo4w/packages-x86_64/ -ms-windows/osgeo4w/unpacked/ -ms-windows/osgeo4w/untgz/ -ms-windows/packages/ -ms-windows/progs/ -ms-windows/untgz/ python/plugins/grassprovider/description/algorithms.json python/plugins/grassprovider/tests/testdata/directions.tif.aux.xml python/plugins/processing/tests/testdata/*.aux.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index c0c02a97f25dd..96bb4d6df6e62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,13 @@ endif() # don't relink it only the shared object changes set(CMAKE_LINK_DEPENDS_NO_SHARED ON) +set (WITH_BINDINGS TRUE CACHE BOOL "Determines whether Python bindings should be built") +set (WITH_3D TRUE CACHE BOOL "Determines whether QGIS 3D library should be built") +set (WITH_QGIS_PROCESS TRUE CACHE BOOL "Determines whether the standalone \"qgis_process\" tool should be built") +set (WITH_DESKTOP TRUE CACHE BOOL "Determines whether QGIS desktop should be built") +set (WITH_GUI TRUE CACHE BOOL "Determines whether QGIS GUI library should be built") + + ############################################################# # Project and version set(CPACK_PACKAGE_VERSION_MAJOR "3") @@ -85,17 +92,15 @@ if (USE_OPENCL) endif() # Configure CCache if available -if(NOT MSVC) - option(USE_CCACHE "Use ccache" ON) - if (USE_CCACHE) - find_program(CCACHE_FOUND ccache) - if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) - message(STATUS "ccache found") - endif(CCACHE_FOUND) - endif(USE_CCACHE) -endif(NOT MSVC) +option(USE_CCACHE "Use ccache" ON) +if (USE_CCACHE) + find_program(CCACHE_FOUND ccache) + if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + message(STATUS "ccache found") + endif(CCACHE_FOUND) +endif(USE_CCACHE) if (IOS) set (DEFAULT_FORCE_STATIC_LIBS TRUE) @@ -140,8 +145,6 @@ if(WITH_CORE) endif() endforeach (GRASS_SEARCH_VERSION) - set (WITH_GUI TRUE CACHE BOOL "Determines whether QGIS GUI library (and everything built on top of it) should be built") - set (WITH_OAUTH2_PLUGIN TRUE CACHE BOOL "Determines whether OAuth2 authentication method plugin should be built") if(WITH_OAUTH2_PLUGIN) set(HAVE_OAUTH2_PLUGIN TRUE) @@ -151,8 +154,6 @@ if(WITH_CORE) set (WITH_ANALYSIS TRUE CACHE BOOL "Determines whether QGIS analysis library should be built") - set (WITH_DESKTOP TRUE CACHE BOOL "Determines whether QGIS desktop should be built") - if(WITH_DESKTOP) if((WIN32 AND NOT MINGW) OR (UNIX AND NOT APPLE AND NOT ANDROID AND NOT IOS)) set (CRASH_HANDLER_AVAILABLE TRUE) @@ -171,17 +172,13 @@ if(WITH_CORE) endif() endif() - set (WITH_3D TRUE CACHE BOOL "Determines whether QGIS 3D library should be built") set (WITH_QUICK FALSE CACHE BOOL "Determines whether QGIS Quick library should be built") - set (WITH_QGIS_PROCESS TRUE CACHE BOOL "Determines whether the standalone \"qgis_process\" tool should be built") - set (NATIVE_CRSSYNC_BIN "" CACHE PATH "Path to a natively compiled synccrsdb binary. If set, crssync will not build but use provided bin instead.") mark_as_advanced (NATIVE_CRSSYNC_BIN) # try to configure and build python bindings by default - set (WITH_BINDINGS TRUE CACHE BOOL "Determines whether Python bindings should be built") if (WITH_BINDINGS) # By default bindings will be installed only to QGIS directory # Someone might want to install it to python site-packages directory @@ -638,8 +635,8 @@ if (ENABLE_TESTS) add_custom_target(check COMMAND xvfb-run --server-args=-screen\ 0\ 1024x768x24 ctest --output-on-failure) # Define SOURCETREE fixture - add_test(NAME logGitStatus COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/chkgitstatus.sh log) - add_test(NAME checkGitStatus COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/chkgitstatus.sh check) + add_test(NAME logGitStatus COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/scripts/chkgitstatus.sh log) + add_test(NAME checkGitStatus COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/scripts/chkgitstatus.sh check) set_tests_properties(logGitStatus PROPERTIES FIXTURES_SETUP SOURCETREE) set_property(TEST logGitStatus PROPERTY SKIP_RETURN_CODE 2) set_tests_properties(checkGitStatus PROPERTIES FIXTURES_CLEANUP SOURCETREE) diff --git a/external/mdal/frmts/mdal_h2i.cpp b/external/mdal/frmts/mdal_h2i.cpp index 0b2ca0f33dbb5..be5b2b6834ada 100644 --- a/external/mdal/frmts/mdal_h2i.cpp +++ b/external/mdal/frmts/mdal_h2i.cpp @@ -137,7 +137,7 @@ bool MDAL::DriverH2i::parseJsonFile( const std::string filePath, MetadataH2i &me metadata.dirPath = MDAL::dirName( filePath ); } - catch ( Json::exception &e ) + catch ( Json::exception & ) { return false; } diff --git a/external/mdal/mdal_utils.cpp b/external/mdal/mdal_utils.cpp index efcbc1b6489a0..4691f00b99a8b 100644 --- a/external/mdal/mdal_utils.cpp +++ b/external/mdal/mdal_utils.cpp @@ -17,7 +17,9 @@ #include #ifdef _MSC_VER +#ifndef UNICODE #define UNICODE +#endif #include #include #include @@ -1113,12 +1115,12 @@ std::vector MDAL::Library::libraryFilesInDir( const std::string &di { std::vector filesList; #ifdef _WIN32 - WIN32_FIND_DATA data; + WIN32_FIND_DATAA data; HANDLE hFind; std::string pattern = dirPath; pattern.push_back( '*' ); - hFind = FindFirstFile( pattern.c_str(), &data ); + hFind = FindFirstFileA( pattern.c_str(), &data ); if ( hFind == INVALID_HANDLE_VALUE ) return filesList; @@ -1129,7 +1131,7 @@ std::vector MDAL::Library::libraryFilesInDir( const std::string &di if ( !fileName.empty() && fileExtension( fileName ) == ".dll" ) filesList.push_back( fileName ); } - while ( FindNextFile( hFind, &data ) != 0 ); + while ( FindNextFileA( hFind, &data ) != 0 ); FindClose( hFind ); #else @@ -1140,8 +1142,8 @@ std::vector MDAL::Library::libraryFilesInDir( const std::string &di std::string fileName( de->d_name ); if ( !fileName.empty() ) { - std::string extentsion = fileExtension( fileName ); - if ( extentsion == ".so" || extentsion == ".dylib" ) + std::string extension = fileExtension( fileName ); + if ( extension == ".so" || extension == ".dylib" ) filesList.push_back( fileName ); } de = readdir( dir ); @@ -1160,7 +1162,7 @@ bool MDAL::Library::loadLibrary() #ifdef _WIN32 UINT uOldErrorMode = SetErrorMode( SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS ); - d->mLibrary = LoadLibrary( d->mLibraryFile.c_str() ); + d->mLibrary = LoadLibraryA( d->mLibraryFile.c_str() ); SetErrorMode( uOldErrorMode ); #else d->mLibrary = dlopen( d->mLibraryFile.c_str(), RTLD_LAZY ); diff --git a/external/qspatialite/CMakeLists.txt b/external/qspatialite/CMakeLists.txt index 33536dafe913c..527f383303c7c 100644 --- a/external/qspatialite/CMakeLists.txt +++ b/external/qspatialite/CMakeLists.txt @@ -7,18 +7,18 @@ add_definitions(-DQT_SHARED) include_directories(SYSTEM ${SQLITE3_INCLUDE_DIR} - ${Qt5Sql_PRIVATE_INCLUDE_DIRS} + ${${QT_VERSION_BASE}Sql_PRIVATE_INCLUDE_DIRS} ) -set(QSQLSPATIALITE_SRC qsql_spatialite.cpp smain.cpp) -QT5_WRAP_CPP(QSQLSPATIALITE_SRC qsql_spatialite.h smain.h) +set(QSQLSPATIALITE_SRC qsql_spatialite.cpp smain.cpp qsql_spatialite.h smain.h) add_library(qsqlspatialite SHARED ${QSQLSPATIALITE_SRC}) + target_link_libraries(qsqlspatialite - ${Qt5Core_LIBRARIES} - ${Qt5Sql_LIBRARIES} - spatialite::spatialite - qgis_core + ${QT_VERSION_BASE}::Core + ${QT_VERSION_BASE}::Sql + spatialite::spatialite + qgis_core ) install(TARGETS qsqlspatialite diff --git a/external/qspatialite/qsql_spatialite.cpp b/external/qspatialite/qsql_spatialite.cpp index 0e8f15a25f766..5a75a04748b31 100644 --- a/external/qspatialite/qsql_spatialite.cpp +++ b/external/qspatialite/qsql_spatialite.cpp @@ -632,7 +632,7 @@ bool QSpatiaLiteDriver::open( const QString &db, const QString &, const QString bool openReadOnlyOption = false; bool openUriOption = false; - const auto opts = conOpts.splitRef( QLatin1Char( ';' ) ); + const auto opts = conOpts.split( QLatin1Char( ';' ) ); for ( auto option : opts ) { option = option.trimmed(); diff --git a/external/untwine/api/QgisUntwine_win.cpp b/external/untwine/api/QgisUntwine_win.cpp index 7469655df8f81..47d355ce36759 100644 --- a/external/untwine/api/QgisUntwine_win.cpp +++ b/external/untwine/api/QgisUntwine_win.cpp @@ -28,7 +28,7 @@ bool QgisUntwine::start(Options& options) cmdline += "--" + op.first + " \"" + op.second + "\" "; PROCESS_INFORMATION processInfo; - STARTUPINFO startupInfo; + STARTUPINFOA startupInfo; ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); diff --git a/images/images.qrc b/images/images.qrc index ee0d8e3dbd33f..b609842a0d987 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -998,6 +998,11 @@ themes/default/mTemporalNavigationMovie.svg themes/default/mActionAddSensorThingsLayer.svg themes/default/mIconSensorThings.svg + themes/default/mActionRunSelected.svg + themes/default/mIconSearchCaseSensitive.svg + themes/default/mIconSearchWholeWord.svg + themes/default/mIconSearchWrapAround.svg + themes/default/mIconSearchRegex.svg qgis_tips/symbol_levels.png diff --git a/images/themes/default/mActionRunSelected.svg b/images/themes/default/mActionRunSelected.svg new file mode 100644 index 0000000000000..c3883ba26a67f --- /dev/null +++ b/images/themes/default/mActionRunSelected.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/default/mIconSearchCaseSensitive.svg b/images/themes/default/mIconSearchCaseSensitive.svg new file mode 100644 index 0000000000000..ce25357e5ea89 --- /dev/null +++ b/images/themes/default/mIconSearchCaseSensitive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/default/mIconSearchRegex.svg b/images/themes/default/mIconSearchRegex.svg new file mode 100644 index 0000000000000..cec740439ab97 --- /dev/null +++ b/images/themes/default/mIconSearchRegex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/default/mIconSearchWholeWord.svg b/images/themes/default/mIconSearchWholeWord.svg new file mode 100644 index 0000000000000..81a8063f67847 --- /dev/null +++ b/images/themes/default/mIconSearchWholeWord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/default/mIconSearchWrapAround.svg b/images/themes/default/mIconSearchWrapAround.svg new file mode 100644 index 0000000000000..96cfb61b9c411 --- /dev/null +++ b/images/themes/default/mIconSearchWrapAround.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ms-windows/Installer-Files/InstallHeaderImage.bmp b/ms-windows/Installer-Files/InstallHeaderImage.bmp deleted file mode 100644 index 2332341daa04f..0000000000000 Binary files a/ms-windows/Installer-Files/InstallHeaderImage.bmp and /dev/null differ diff --git a/ms-windows/Installer-Files/Install_QGIS.ico b/ms-windows/Installer-Files/Install_QGIS.ico deleted file mode 100644 index 752cb9e25f09d..0000000000000 Binary files a/ms-windows/Installer-Files/Install_QGIS.ico and /dev/null differ diff --git a/ms-windows/Installer-Files/LICENSE.txt b/ms-windows/Installer-Files/LICENSE.txt deleted file mode 100644 index d1327eb937e76..0000000000000 --- a/ms-windows/Installer-Files/LICENSE.txt +++ /dev/null @@ -1,28 +0,0 @@ - QGIS is Copyright (C) QGIS Development Team - and the respective authors, 2004. - - 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. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - - In addition, as a special exception, the QGIS Development Team gives - permission to link the code of this program with the Qt library, - including but not limited to the following versions (both free and - commercial): Qt/Non-commercial Windows, Qt/Windows, Qt/X11, Qt/Mac, - and Qt/Embedded (or with modified versions of Qt that use the same - license as Qt), and distribute linked combinations including the two. - You must obey the GNU General Public License in all respects for all - of the code used other than Qt. If you modify this file, you may - extend this exception to your version of the file, but you are not - obligated to do so. If you do not wish to do so, delete this exception - statement from your version. diff --git a/ms-windows/Installer-Files/QGIS-WebSite.URL b/ms-windows/Installer-Files/QGIS-WebSite.URL deleted file mode 100644 index 79dcf3a244955..0000000000000 --- a/ms-windows/Installer-Files/QGIS-WebSite.URL +++ /dev/null @@ -1,2 +0,0 @@ -[InternetShortcut] -URL=http://qgis.org/ diff --git a/ms-windows/Installer-Files/QGIS.ico b/ms-windows/Installer-Files/QGIS.ico deleted file mode 100644 index da327712e77d6..0000000000000 Binary files a/ms-windows/Installer-Files/QGIS.ico and /dev/null differ diff --git a/ms-windows/Installer-Files/QGIS_Web.ico b/ms-windows/Installer-Files/QGIS_Web.ico deleted file mode 100644 index e07ad44c0b527..0000000000000 Binary files a/ms-windows/Installer-Files/QGIS_Web.ico and /dev/null differ diff --git a/ms-windows/Installer-Files/UnInstallHeaderImage.bmp b/ms-windows/Installer-Files/UnInstallHeaderImage.bmp deleted file mode 100644 index 06567269dc2df..0000000000000 Binary files a/ms-windows/Installer-Files/UnInstallHeaderImage.bmp and /dev/null differ diff --git a/ms-windows/Installer-Files/Uninstall_QGIS.ico b/ms-windows/Installer-Files/Uninstall_QGIS.ico deleted file mode 100644 index 4119d01d3a694..0000000000000 Binary files a/ms-windows/Installer-Files/Uninstall_QGIS.ico and /dev/null differ diff --git a/ms-windows/Installer-Files/WelcomeFinishPage.bmp b/ms-windows/Installer-Files/WelcomeFinishPage.bmp deleted file mode 100644 index 3fb81c4b741b9..0000000000000 Binary files a/ms-windows/Installer-Files/WelcomeFinishPage.bmp and /dev/null differ diff --git a/ms-windows/Installer-Files/WelcomeFinishPage_old.bmp b/ms-windows/Installer-Files/WelcomeFinishPage_old.bmp deleted file mode 100644 index 72e3579bc255d..0000000000000 Binary files a/ms-windows/Installer-Files/WelcomeFinishPage_old.bmp and /dev/null differ diff --git a/ms-windows/Installer-Files/sidelogomaster.svg b/ms-windows/Installer-Files/sidelogomaster.svg deleted file mode 100644 index c6044e4a13bb6..0000000000000 --- a/ms-windows/Installer-Files/sidelogomaster.svg +++ /dev/null @@ -1 +0,0 @@ -QGIS \ No newline at end of file diff --git a/ms-windows/Installer-Files/sidelogomaster.xcf.bz2 b/ms-windows/Installer-Files/sidelogomaster.xcf.bz2 deleted file mode 100644 index 16b6b71e66a8d..0000000000000 Binary files a/ms-windows/Installer-Files/sidelogomaster.xcf.bz2 and /dev/null differ diff --git a/ms-windows/QGIS-Installer.nsi b/ms-windows/QGIS-Installer.nsi deleted file mode 100644 index 892a7083aa932..0000000000000 --- a/ms-windows/QGIS-Installer.nsi +++ /dev/null @@ -1,569 +0,0 @@ -;-------------------------------------------------------------------------- -; QGIS-Installer.nsi - QGIS Installer for Windows -; --------------------- -; Date : September 2008 -; Copyright : (C) 2008 by Marco Pasetti -; Email : marco dot pasetti at alice dot it -;-------------------------------------------------------------------------- -; # -; This program is free software; you can redistribute it and/or modify # -; it under the terms of the GNU General Public License as published by # -; the Free Software Foundation; either version 2 of the License, or # -; (at your option) any later version. # -; # -;-------------------------------------------------------------------------- - -;Extended for creatensis.pl by Jürgen E. Fischer - -;---------------------------------------------------------------------------------------------------------------------------- - -; Added by Tim to get optimal compression -SetCompressor /SOLID lzma - -; Added by Tim to allow privilege elevation in vista -RequestExecutionLevel admin - -;---------------------------------------------------------------------------------------------------------------------------- - -;NSIS Includes - -!include "x64.nsh" -!include "MUI.nsh" -!include "LogicLib.nsh" - -;---------------------------------------------------------------------------------------------------------------------------- - -;Set the installer variables, depending on the selected version to build - -!addplugindir osgeo4w/untgz -!addplugindir osgeo4w/nsis -!addplugindir osgeo4w/inetc - -;---------------------------------------------------------------------------------------------------------------------------- - -;Publisher variables - -!define PUBLISHER "QGIS Development Team" -!define WEB_SITE "https://qgis.org" -!define WIKI_PAGE "https://qgis.org/en/docs/" - -;---------------------------------------------------------------------------------------------------------------------------- - -;General Definitions - -;Name of the application shown during install -Name "${DISPLAYED_NAME}" - -;Name of the output file (installer executable) -OutFile "${INSTALLER_NAME}" - -;Tell the installer to show Install and Uninstall details as default -ShowInstDetails hide -ShowUnInstDetails hide - -;---------------------------------------------------------------------------------------------------------------------------- - -; .onInit Function (called when the installer is nearly finished initializing) - -; Check if QGIS is already installed on the system and, if yes, what version and binary release; -; depending on that, select the install procedure: - -; 1. first installation = if QGIS is not already installed -; install QGIS asking for the install PATH - -; 2. upgrade installation = if an older release of QGIS is already installed -; call the uninstaller of the currently installed QGIS release -; if the uninstall procedure succeeded, call the current installer without asking for the install PATH -; QGIS will be installed in the same PATH of the previous installation - -; 3. downgrade installation = if a newer release of QGIS is already installed -; call the uninstaller of the currently installed QGIS release -; if the uninstall procedure succeeded, call the current installer without asking for the install PATH -; QGIS will be installed in the same PATH of the previous installation - -; 4. repair installation = if the same release of QGIS is already installed -; call the uninstaller of the currently installed QGIS release -; if the uninstall procedure succeeded, call the current installer asking for the install PATH - -Function .onInit -!ifdef INNER - WriteUninstaller "${UNINSTALLERDEST}\uninstall.exe" - Quit -!endif - ${If} ${ARCH} == "x86_64" - ${If} ${RunningX64} - DetailPrint "Installer running on 64-bit host" - ; disable registry redirection (enable access to 64-bit portion of registry) - SetRegView 64 - ; change install dir - ${If} $INSTDIR == "" - StrCpy $INSTDIR "$PROGRAMFILES64\${QGIS_BASE}" - ${EndIf} - ${EndIf} - ${EndIf} - - ${If} $INSTDIR == "" - StrCpy $INSTDIR "$PROGRAMFILES\${QGIS_BASE}" - ${EndIf} - - Var /GLOBAL ASK_FOR_PATH - StrCpy $ASK_FOR_PATH "YES" - - Var /GLOBAL UNINSTALL_STRING - Var /GLOBAL INSTALL_PATH - - Var /GLOBAL INSTALLED_VERSION_NUMBER - Var /GLOBAL INSTALLED_SVN_REVISION - Var /GLOBAL INSTALLED_BINARY_REVISION - - Var /GLOBAL INSTALLED_VERSION_INT - - Var /GLOBAL DISPLAYED_INSTALLED_VERSION - - Var /GLOBAL MESSAGE_0_ - Var /GLOBAL MESSAGE_1_ - Var /GLOBAL MESSAGE_2_ - Var /GLOBAL MESSAGE_3_ - - ReadRegStr $UNINSTALL_STRING HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "UninstallString" - ReadRegStr $INSTALL_PATH HKLM "Software\${QGIS_BASE}" "InstallPath" - ReadRegStr $INSTALLED_VERSION_NUMBER HKLM "Software\${QGIS_BASE}" "VersionNumber" - ReadRegStr $INSTALLED_BINARY_REVISION HKLM "Software\${QGIS_BASE}" "BinaryRevision" - - ReadRegStr $INSTALLED_VERSION_INT HKLM "Software\${QGIS_BASE}" "VersionInt" - ${If} $INSTALLED_VERSION_INT == "" - # First using new scheme: 1080001 - # Previous: SvnRevision 14615 + BinaryRevision 0 - ReadRegStr $INSTALLED_SVN_REVISION HKLM "Software\${QGIS_BASE}" "SvnRevision" - IntOp $INSTALLED_VERSION_INT $INSTALLED_SVN_REVISION + $INSTALLED_BINARY_REVISION - ${EndIf} - - StrCpy $MESSAGE_0_ "${QGIS_BASE} is already installed on your system.$\r$\n" - StrCpy $MESSAGE_0_ "$MESSAGE_0_$\r$\n" - - ${If} $INSTALLED_BINARY_REVISION == "" - StrCpy $DISPLAYED_INSTALLED_VERSION "$INSTALLED_VERSION_NUMBER" - ${Else} - StrCpy $DISPLAYED_INSTALLED_VERSION "$INSTALLED_VERSION_NUMBER-$INSTALLED_BINARY_REVISION" - ${EndIf} - - StrCpy $MESSAGE_0_ "$MESSAGE_0_The installed release is $DISPLAYED_INSTALLED_VERSION$\r$\n" - - StrCpy $MESSAGE_1_ "$MESSAGE_0_$\r$\n" - StrCpy $MESSAGE_1_ "$MESSAGE_1_You are going to install a newer release of ${QGIS_BASE}$\r$\n" - StrCpy $MESSAGE_1_ "$MESSAGE_1_$\r$\n" - StrCpy $MESSAGE_1_ "$MESSAGE_1_Press OK to uninstall QGIS $DISPLAYED_INSTALLED_VERSION" - StrCpy $MESSAGE_1_ "$MESSAGE_1_ and install ${DISPLAYED_NAME} or Cancel to quit." - - StrCpy $MESSAGE_2_ "$MESSAGE_0_$\r$\n" - StrCpy $MESSAGE_2_ "$MESSAGE_2_You are going to install an older release of ${QGIS_BASE}$\r$\n" - StrCpy $MESSAGE_2_ "$MESSAGE_2_$\r$\n" - StrCpy $MESSAGE_2_ "$MESSAGE_2_Press OK to uninstall QGIS $DISPLAYED_INSTALLED_VERSION" - StrCpy $MESSAGE_2_ "$MESSAGE_2_ and install ${DISPLAYED_NAME} or Cancel to quit." - - StrCpy $MESSAGE_3_ "$MESSAGE_0_$\r$\n" - StrCpy $MESSAGE_3_ "$MESSAGE_3_This is the latest release available.$\r$\n" - StrCpy $MESSAGE_3_ "$MESSAGE_3_$\r$\n" - StrCpy $MESSAGE_3_ "$MESSAGE_3_Press OK to reinstall ${DISPLAYED_NAME} or Cancel to quit." - - ${If} $INSTALLED_VERSION_INT = 0 - ${Else} - ${If} $INSTALLED_VERSION_INT < ${VERSION_INT} - MessageBox MB_OKCANCEL "$MESSAGE_1_" IDOK upgrade IDCANCEL quit_upgrade - upgrade: - StrCpy $ASK_FOR_PATH "NO" - ExecWait '"$UNINSTALL_STRING" _?=$INSTALL_PATH' $0 - Goto continue_upgrade - quit_upgrade: - Abort - continue_upgrade: - ${ElseIf} $INSTALLED_VERSION_INT > ${VERSION_INT} - MessageBox MB_OKCANCEL "$MESSAGE_2_" IDOK downgrade IDCANCEL quit_downgrade - downgrade: - StrCpy $ASK_FOR_PATH "NO" - ExecWait '"$UNINSTALL_STRING" _?=$INSTALL_PATH' $0 - Goto continue_downgrade - quit_downgrade: - Abort - continue_downgrade: - ${ElseIf} $INSTALLED_VERSION_INT = ${VERSION_INT} - MessageBox MB_OKCANCEL "$MESSAGE_3_" IDOK reinstall IDCANCEL quit_reinstall - reinstall: - ExecWait '"$UNINSTALL_STRING" _?=$INSTALL_PATH' $0 - Goto continue_reinstall - quit_reinstall: - Abort - continue_reinstall: - ${EndIf} - - ${If} $0 = 0 - ${Else} - Abort - ${EndIf} - ${EndIf} -FunctionEnd - -;---------------------------------------------------------------------------------------------------------------------------- - -;CheckUpdate Function -;Check if to show the MUI_PAGE_DIRECTORY during the installation (to ask for the install PATH) - -Function CheckUpdate - - ${If} $ASK_FOR_PATH == "NO" - Abort - ${EndIf} - -FunctionEnd - -;---------------------------------------------------------------------------------------------------------------------------- - -;Interface Settings - -!define MUI_ABORTWARNING -!define MUI_ICON ".\Installer-Files\Install_QGIS.ico" -!define MUI_UNICON ".\Installer-Files\Uninstall_QGIS.ico" -!define MUI_HEADERIMAGE_BITMAP_NOSTETCH ".\Installer-Files\InstallHeaderImage.bmp" -!define MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH ".\Installer-Files\UnInstallHeaderImage.bmp" -!define MUI_WELCOMEFINISHPAGE_BITMAP ".\Installer-Files\WelcomeFinishPage.bmp" -!define MUI_UNWELCOMEFINISHPAGE_BITMAP ".\Installer-Files\WelcomeFinishPage.bmp" - -;---------------------------------------------------------------------------------------------------------------------------- - -;Installer Pages - -!define MUI_WELCOMEPAGE_TITLE_3LINES -!insertmacro MUI_PAGE_WELCOME -!insertmacro MUI_PAGE_LICENSE ${LICENSE_FILE} - -!define MUI_PAGE_CUSTOMFUNCTION_PRE CheckUpdate -!insertmacro MUI_PAGE_DIRECTORY - -!insertmacro MUI_PAGE_COMPONENTS -!insertmacro MUI_PAGE_INSTFILES -!define MUI_FINISHPAGE_TITLE_3LINES -!insertmacro MUI_PAGE_FINISH - -!insertmacro MUI_UNPAGE_WELCOME -!insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES -!insertmacro MUI_UNPAGE_FINISH - -;---------------------------------------------------------------------------------------------------------------------------- - -; Language files -!insertmacro MUI_LANGUAGE "English" -!insertmacro MUI_LANGUAGE "German" -!insertmacro MUI_LANGUAGE "French" -!insertmacro MUI_LANGUAGE "Russian" -!insertmacro MUI_LANGUAGE "Japanese" -!insertmacro MUI_LANGUAGE "Italian" -!insertmacro MUI_LANGUAGE "Polish" -!insertmacro MUI_LANGUAGE "Spanish" -!insertmacro MUI_LANGUAGE "PortugueseBR" -!insertmacro MUI_LANGUAGE "Portuguese" -!insertmacro MUI_LANGUAGE "Czech" -!insertmacro MUI_LANGUAGE "Croatian" -!insertmacro MUI_LANGUAGE "Thai" -!insertmacro MUI_LANGUAGE "Dutch" - -;---------------------------------------------------------------------------------------------------------------------------- - -;Installer Sections - -;Declares the variables for optional Sample Data Sections -Var /GLOBAL HTTP_PATH -Var /GLOBAL ARCHIVE_NAME -Var /GLOBAL EXTENDED_ARCHIVE_NAME -Var /GLOBAL ORIGINAL_UNTAR_FOLDER -Var /GLOBAL CUSTOM_UNTAR_FOLDER -Var /GLOBAL ARCHIVE_SIZE_KB -Var /GLOBAL ARCHIVE_SIZE_MB -Var /GLOBAL DOWNLOAD_MESSAGE_ - -!ifndef INNER -Section "QGIS" SecQGIS - SectionIn RO - - ;Added by Tim to set the reg key so we get default plugin loading - !include plugins.nsh - ;Added by Tim to set the reg key so we get default python & py plugins - !include python_plugins.nsh - - ;Set the INSTALL_DIR variable - Var /GLOBAL INSTALL_DIR - - ${If} $ASK_FOR_PATH == "NO" - StrCpy $INSTALL_DIR "$INSTALL_PATH" - ${Else} - StrCpy $INSTALL_DIR "$INSTDIR" - ${EndIf} - - ;Set to try to overwrite existing files - SetOverwrite try - - ;Set the GIS_DATABASE directory - SetShellVarContext current - Var /GLOBAL GIS_DATABASE - StrCpy $GIS_DATABASE "$DOCUMENTS\GIS DataBase" - - ;Create the GIS_DATABASE directory - CreateDirectory "$GIS_DATABASE" - - ;add Installer files - SetOutPath "$INSTALL_DIR\icons" - File .\Installer-Files\QGIS.ico - File .\Installer-Files\QGIS_Web.ico - SetOutPath "$INSTALL_DIR" - File .\Installer-Files\postinstall.bat - File .\Installer-Files\preremove.bat - - ;add QGIS files - SetOutPath "$INSTALL_DIR" - File /r ${PACKAGE_FOLDER}\*.* - -!ifndef INNER - SetOutPath $INSTDIR - File uninstall.exe -!endif - - ;Registry Key Entries - - ;HKEY_LOCAL_MACHINE Install entries - ;Set the Name, Version and Revision of QGIS+ PublisherInfo + InstallPath - WriteRegStr HKLM "Software\${QGIS_BASE}" "Name" "${QGIS_BASE}" - WriteRegStr HKLM "Software\${QGIS_BASE}" "VersionNumber" "${VERSION_NUMBER}" - WriteRegStr HKLM "Software\${QGIS_BASE}" "VersionName" "${VERSION_NAME}" - WriteRegStr HKLM "Software\${QGIS_BASE}" "VersionInt" "${VERSION_INT}" - WriteRegStr HKLM "Software\${QGIS_BASE}" "BinaryRevision" "${BINARY_REVISION}" - WriteRegStr HKLM "Software\${QGIS_BASE}" "Publisher" "${PUBLISHER}" - WriteRegStr HKLM "Software\${QGIS_BASE}" "WebSite" "${WEB_SITE}" - WriteRegStr HKLM "Software\${QGIS_BASE}" "InstallPath" "$INSTALL_DIR" - - ;HKEY_LOCAL_MACHINE Uninstall entries - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "DisplayName" "${DISPLAYED_NAME}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "DisplayVersion" "${VERSION_NUMBER}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "UninstallString" "$INSTALL_DIR\uninstall.exe" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "DisplayIcon" "$INSTALL_DIR\icons\QGIS.ico" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "EstimatedSize" 1 - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "HelpLink" "${WIKI_PAGE}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "URLInfoAbout" "${WEB_SITE}" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" "Publisher" "${PUBLISHER}" - - ;Create the Desktop Shortcut - SetShellVarContext current - - ;Create the Windows Start Menu Shortcuts - SetShellVarContext all - - CreateDirectory "$SMPROGRAMS\${QGIS_BASE}" - - GetFullPathName /SHORT $0 $INSTALL_DIR - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_ROOT", "$0").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_STARTMENU", "$SMPROGRAMS\${QGIS_BASE}").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_DESKTOP", "$DESKTOP\${QGIS_BASE}").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_MENU_LINKS", "1").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_DESKTOP_LINKS", "1").r0' - - ReadEnvStr $0 COMSPEC - nsExec::ExecToLog '"$0" /c "$INSTALL_DIR\postinstall.bat"' - - IfFileExists "$INSTALL_DIR\etc\reboot" RebootNecessary NoRebootNecessary - -RebootNecessary: - IfSilent FlagRebootNecessary - SetRebootFlag true - Return - -FlagRebootNecessary: - SetErrorLevel 3010 ; ERROR_SUCCESS_REBOOT_REQUIRED - Return - -NoRebootNecessary: - Return - -SectionEnd -!endif - -Function DownloadDataSet - - IntOp $ARCHIVE_SIZE_MB $ARCHIVE_SIZE_KB / 1024 - - StrCpy $DOWNLOAD_MESSAGE_ "The installer will download the $EXTENDED_ARCHIVE_NAME sample data set.$\r$\n" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_$\r$\n" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_The archive is about $ARCHIVE_SIZE_MB MB and may take" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_ several minutes to be downloaded.$\r$\n" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_$\r$\n" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_The $EXTENDED_ARCHIVE_NAME will be copied to:$\r$\n" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_$GIS_DATABASE\$CUSTOM_UNTAR_FOLDER.$\r$\n" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_$\r$\n" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_Press OK to continue or Cancel to skip the download and complete the ${QGIS_BASE}" - StrCpy $DOWNLOAD_MESSAGE_ "$DOWNLOAD_MESSAGE_ installation without the $EXTENDED_ARCHIVE_NAME data set.$\r$\n" - - MessageBox MB_OKCANCEL "$DOWNLOAD_MESSAGE_" IDOK download IDCANCEL cancel_download - - download: - SetShellVarContext current - InitPluginsDir - inetc::get /caption "$ARCHIVE_NAME" /canceltext "Cancel" "$HTTP_PATH/$ARCHIVE_NAME" "$TEMP\$ARCHIVE_NAME" /end - Pop $0 # return value = exit code, "OK" means OK - StrCmp $0 "OK" download_ok download_failed - - download_ok: - InitPluginsDir - untgz::extract "-d" "$GIS_DATABASE" "$TEMP\$ARCHIVE_NAME" - Pop $0 - StrCmp $0 "success" untar_ok untar_failed - - untar_ok: - Rename "$GIS_DATABASE\$ORIGINAL_UNTAR_FOLDER" "$GIS_DATABASE\$CUSTOM_UNTAR_FOLDER" - Delete "$TEMP\$ARCHIVE_NAME" - Goto end - - download_failed: - DetailPrint "$0" ;print error message to log - MessageBox MB_OK "Download Failed.$\r$\n${QGIS_BASE} will be installed without the $EXTENDED_ARCHIVE_NAME sample data set." - Goto end - - cancel_download: - MessageBox MB_OK "Download Canceled.$\r$\n${QGIS_BASE} will be installed without the $EXTENDED_ARCHIVE_NAME sample data set." - Goto end - - untar_failed: - DetailPrint "$0" ;print error message to log - - end: - -FunctionEnd - -Section /O "North Carolina Data Set" SecNorthCarolinaSDB - - ;Set the size (in KB) of the archive file - StrCpy $ARCHIVE_SIZE_KB 138629 - - ;Set the size (in KB) of the unpacked archive file - AddSize 293314 - - StrCpy $HTTP_PATH "https://grass.osgeo.org/sampledata" - StrCpy $ARCHIVE_NAME "nc_spm_latest.tar.gz" - StrCpy $EXTENDED_ARCHIVE_NAME "North Carolina" - StrCpy $ORIGINAL_UNTAR_FOLDER "nc_spm_08" - StrCpy $CUSTOM_UNTAR_FOLDER "North-Carolina" - - Call DownloadDataSet - -SectionEnd - -Section /O "South Dakota (Spearfish) Data Set" SecSpearfishSDB - - ;Set the size (in KB) of the archive file - StrCpy $ARCHIVE_SIZE_KB 20803 - - ;Set the size (in KB) of the unpacked archive file - AddSize 42171 - - StrCpy $HTTP_PATH "https://grass.osgeo.org/sampledata" - StrCpy $ARCHIVE_NAME "spearfish_grass60data-0.3.tar.gz" - StrCpy $EXTENDED_ARCHIVE_NAME "South Dakota (Spearfish)" - StrCpy $ORIGINAL_UNTAR_FOLDER "spearfish60" - StrCpy $CUSTOM_UNTAR_FOLDER "Spearfish60" - - Call DownloadDataSet - -SectionEnd - -Section /O "Alaska Data Set" SecAlaskaSDB - - ;Set the size (in KB) of the archive file - StrCpy $ARCHIVE_SIZE_KB 10253 - - ;Set the size (in KB) of the unpacked archive file - AddSize 33914 - - StrCpy $HTTP_PATH "https://qgis.org/downloads/data" - StrCpy $ARCHIVE_NAME "qgis_sample_data.tar.gz" - StrCpy $EXTENDED_ARCHIVE_NAME "Alaska" - StrCpy $ORIGINAL_UNTAR_FOLDER "qgis_sample_data" - StrCpy $CUSTOM_UNTAR_FOLDER "Alaska" - - Call DownloadDataSet - -SectionEnd - -;---------------------------------------------------------------------------------------------------------------------------- - -;Uninstaller Section - -!ifdef INNER -Section "Uninstall" - ${If} ${ARCH} == "x86_64" - ${If} ${RunningX64} - DetailPrint "Installer running on 64-bit host" - ; disable registry redirection (enable access to 64-bit portion of registry) - SetRegView 64 - ${EndIf} - ${EndIf} - - GetFullPathName /SHORT $0 $INSTDIR - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_ROOT", "$0").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_STARTMENU", "$SMPROGRAMS\${QGIS_BASE}").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_DESKTOP", "$DESKTOP\${QGIS_BASE}").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_MENU_LINKS", "1").r0' - System::Call 'Kernel32::SetEnvironmentVariableA(t, t) i("OSGEO4W_DESKTOP_LINKS", "1").r0' - - ReadEnvStr $0 COMSPEC - nsExec::ExecToLog '"$0" /c "$INSTDIR\preremove.bat"' - - Delete "$INSTDIR\uninstall.exe" - Delete "$INSTDIR\*.bat.done" - Delete "$INSTDIR\*.log" - Delete "$INSTDIR\*.txt" - Delete "$INSTDIR\*.ico" - Delete "$INSTDIR\*.bat" - Delete "$INSTDIR\*.dll" - - RMDir /r "$INSTDIR\bin" - RMDir /r "$INSTDIR\apps" - RMDir /r "$INSTDIR\etc" - RMDir /r "$INSTDIR\include" - RMDir /r "$INSTDIR\lib" - RMDir /r "$INSTDIR\share" - RMDir /r "$INSTDIR\icons" - RMDir /r "$INSTDIR\src" - RMDir /r "$INSTDIR\contrib" - RMDir /r "$INSTDIR\manifest" - RMDir /r "$INSTDIR\man" - - ;if empty, remove the install folder - RMDir "$INSTDIR" - - ;remove the Desktop ShortCut - SetShellVarContext all - Delete "$DESKTOP\${QGIS_BASE}\QGIS Desktop (${VERSION_NUMBER}).lnk" - Delete "$DESKTOP\${QGIS_BASE}\QGIS Browser (${VERSION_NUMBER}).lnk" - Delete "$DESKTOP\${QGIS_BASE}\OSGeo4W.lnk" - RmDir "$DESKTOP\${QGIS_BASE}" - - ;remove the Programs Start ShortCut - SetShellVarContext all - RMDir /r "$SMPROGRAMS\${QGIS_BASE}" - - ;remove the Registry Entries - DeleteRegKey HKLM "Software\${QGIS_BASE}" - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${QGIS_BASE}" -SectionEnd -!endif - -;---------------------------------------------------------------------------------------------------------------------------- - -!ifndef INNER -;Installer Section Descriptions -!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN - !insertmacro MUI_DESCRIPTION_TEXT ${SecQGIS} "Install ${QGIS_BASE}" - !insertmacro MUI_DESCRIPTION_TEXT ${SecNorthCarolinaSDB} "Download and install the North Carolina sample data set" - !insertmacro MUI_DESCRIPTION_TEXT ${SecSpearfishSDB} "Download and install the South Dakota (Spearfish) sample data set" - !insertmacro MUI_DESCRIPTION_TEXT ${SecAlaskaSDB} "Download and install the Alaska sample database (shapefiles and TIFF data)" -!insertmacro MUI_FUNCTION_DESCRIPTION_END -!endif - -;---------------------------------------------------------------------------------------------------------------------------- diff --git a/ms-windows/osgeo4w/configonly.bat b/ms-windows/osgeo4w/configonly.bat deleted file mode 100644 index ee2aa62541879..0000000000000 --- a/ms-windows/osgeo4w/configonly.bat +++ /dev/null @@ -1,46 +0,0 @@ -@echo off -REM *************************************************************************** -REM configonly.cmd -REM --------------------- -REM begin : June 2018 -REM copyright : (C) 2018 by Juergen E. Fischer -REM email : jef at norbit dot de -REM *************************************************************************** -REM * * -REM * This program is free software; you can redistribute it and/or modify * -REM * it under the terms of the GNU General Public License as published by * -REM * the Free Software Foundation; either version 2 of the License, or * -REM * (at your option) any later version. * -REM * * -REM *************************************************************************** - -set ARCH=%1 -if "%ARCH%"=="x86" ( - set CMAKEGEN=Visual Studio 14 2015 -) else ( - set CMAKEGEN=Visual Studio 14 2015 Win64 - set ARCH=x86_64 -) - -set CONFIGONLY=1 - -setlocal enabledelayedexpansion - -for /f "tokens=*" %%L in (..\..\CMakeLists.txt) do ( - set L=%%L - set V=!L:SET(CPACK_PACKAGE_VERSION_=! - if not !V!==!L! ( - set V=!V:"=! - set V=!V:^)=! - set _major=!V:MAJOR =! - set _minor=!V:MINOR =! - set _patch=!V:PATCH =! - if not !_major!==!V! set MAJOR=!_major! - if not !_minor!==!V! set MINOR=!_minor! - if not !_patch!==!V! set PATCH=!_patch! - ) -) - -package-nightly.cmd %MAJOR%.%MINOR%.%PATCH% 99 qgis-test %ARCH% - -endlocal diff --git a/ms-windows/osgeo4w/creatensis.pl b/ms-windows/osgeo4w/creatensis.pl deleted file mode 100755 index 81d349d7989f3..0000000000000 --- a/ms-windows/osgeo4w/creatensis.pl +++ /dev/null @@ -1,567 +0,0 @@ -#!/usr/bin/env perl -# creates a NSIS installer from OSGeo4W packages -# note: works also on Unix - -# Copyright (C) 2010 Jürgen E. Fischer - -# 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. - -# -# Download OSGeo4W packages -# - -BEGIN { - # ignore requireAdministrator execution level while producing the - # uninstaller - $ENV{"__COMPAT_LAYER"} = 'RUNASINVOKER'; -} - -use strict; -use warnings; -use Getopt::Long; -use Pod::Usage; - -my $keep = 0; -my $verbose = 0; - -my $packagename = "QGIS"; -my $releasename; -my $shortname = "qgis"; -my $version; -my $binary; -my $root = "http://download.osgeo.org/osgeo4w"; -my $ininame = "setup.ini"; -my $arch = "x86_64"; -my $signwith; -my $signpass; -my $help; - -my $result = GetOptions( - "verbose+" => \$verbose, - "keep" => \$keep, - "signwith=s" => \$signwith, - "signpass=s" => \$signpass, - "releasename=s" => \$releasename, - "version=s" => \$version, - "binary=i" => \$binary, - "packagename=s" => \$packagename, - "shortname=s" => \$shortname, - "ininame=s" => \$ininame, - "mirror=s" => \$root, - "arch=s" => \$arch, - "help" => \$help - ); - -die "certificate not found" if defined $signwith && ! -f $signwith; - -pod2usage(1) if $help; - -my $wgetopt = $verbose ? "" : "-nv"; - -unless(-f "nsis/System.dll") { - mkdir "nsis", 0755 unless -d "nsis"; - system "wget $wgetopt -Onsis/System.dll http://qgis.org/downloads/System.dll"; - die "download of System.dll failed" if $?; -} - -my $archpath = $arch eq "" ? "" : "/$arch"; -my $archpostfix = $arch eq "" ? "" : "-$arch"; -my $unpacked = "unpacked" . ($arch eq "" ? "" : "-$arch"); -my $packages = "packages" . ($arch eq "" ? "" : "-$arch"); - -mkdir $packages, 0755 unless -d $packages; -chdir $packages; - -system "wget $wgetopt -c http://qgis.org/downloads/Untgz.zip" unless -f "Untgz.zip"; -die "download of Untgz.zip failed" if $?; - -system "wget $wgetopt -c https://qgis.org/downloads/Inetc.zip" unless -f "Inetc.zip"; -die "download of Inetc.zip failed" if $?; - -my %dep; -my %file; -my %lic; -my %sdesc; -my %md5; -my $package; - -system "wget $wgetopt -O setup.ini $root$archpath/$ininame"; -die "download of setup.ini failed" if $?; -open F, "setup.ini" || die "setup.ini not found"; -while() { - my $file; - my $md5; - - chop; - if(/^@ (\S+)/) { - $package = $1; - } elsif( /^requires: (.*)$/ ) { - @{$dep{$package}} = split / /, $1; - } elsif( ($file,$md5) = /^install:\s+(\S+)\s+.*\s+(\S+)$/) { - $file{$package} = $file unless exists $file{$package}; - $file =~ s/^.*\///; - $md5{$file} = $md5 unless exists $md5{$file}; - } elsif( ($file,$md5) = /^license:\s+(\S+)\s+.*\s+(\S+)$/) { - $lic{$package} = $file unless exists $lic{$package}; - $file =~ s/^.*\///; - $md5{$file} = $md5 unless exists $md5{$file}; - } elsif( /^sdesc:\s*"(.*)"\s*$/) { - $sdesc{$package} = $1 unless exists $sdesc{$package}; - } -} -close F; - -my %pkgs; - -sub getDeps { - my $pkg = shift; - - my $deponly = $pkg =~ /-$/; - $pkg =~ s/-$//; - - unless($deponly) { - return if exists $pkgs{$pkg}; - print " Including package $pkg\n" if $verbose; - $pkgs{$pkg} = 1; - } elsif( exists $pkgs{$pkg} ) { - print " Excluding package $pkg\n" if $verbose; - delete $pkgs{$pkg}; - return; - } else { - print " Including dependencies of package $pkg\n" if $verbose; - } - - foreach my $p ( @{ $dep{$pkg} } ) { - getDeps($p); - } -} - -unless(@ARGV) { - print "Defaulting to qgis-full package...\n" if $verbose; - push @ARGV, "qgis-full"; -} - -getDeps($_) for @ARGV; - -if(-f "../addons/bin/NCSEcw4_RO.dll") { - print "Enabling ECW support...\n" if $verbose; - getDeps("gdal-ecw") -} - -my @lic; -my @desc; -foreach my $p ( keys %pkgs ) { - my @f; - unless( exists $file{$p} ) { - print "No file for package $p found.\n" if $verbose; - next; - } - push @f, "$root/$file{$p}"; - - if( exists $lic{$p} ) { - push @f, "$root/$lic{$p}"; - my($l) = $lic{$p} =~ /([^\/]+)$/; - push @lic, $l; - push @desc, $sdesc{$p}; - } - - for my $f (@f) { - $f =~ s/\/\.\//\//g; - - my($file) = $f =~ /([^\/]+)$/; - - next if -f $file; - - print "Downloading $file [$f]...\n" if $verbose; - system "wget $wgetopt -c $f"; - die "download of $f failed" if $? or ! -f $file; - - if( exists $md5{$file} ) { - my $md5; - open F, "md5sum '$file'|"; - while() { - if( /^(\S+)\s+\*?(.*)$/ && $2 eq $file ) { - $md5 = $1; - } - } - close F; - - die "No md5sum of $p determined [$file]" unless defined $md5; - if( $md5 eq $md5{$file} ) { - print "md5sum of $file verified.\n" if $verbose; - } else { - die "md5sum mismatch for $file [$md5 vs $md5{$file{$p}}]" - } - } - else - { - die "md5sum for $file not found.\n"; - } - } -} - -chdir ".."; - -# -# Unpack them -# Add nircmd -# Add addons -# - -if( -d $unpacked ) { - unless( $keep ) { - print "Removing $unpacked directory\n" if $verbose; - system "rm -rf $unpacked"; - } else { - print "Keeping $unpacked directory\n" if $verbose; - } -} - -my $taropt = "v" x $verbose; - -unless(-d $unpacked ) { - mkdir "$unpacked", 0755; - mkdir "$unpacked/bin", 0755; - mkdir "$unpacked/etc", 0755; - mkdir "$unpacked/etc/setup", 0755; - - # Create package database - open O, ">$unpacked/etc/setup/installed.db"; - print O "INSTALLED.DB 2\n"; - - foreach my $pn ( keys %pkgs ) { - my $p = $file{$pn}; - unless( defined $p ) { - print "No package found for $pn\n" if $verbose; - next; - } - - $p =~ s#^.*/#$packages/#; - - unless( -r $p ) { - print "Package $p not found.\n" if $verbose; - next; - } - - print O "$pn $p 0\n"; - - print "Unpacking $p...\n" if $verbose; - system "bash -c 'tar $taropt -C $unpacked -xjvf $p | gzip -c >$unpacked/etc/setup/$pn.lst.gz && [ \${PIPESTATUS[0]} == 0 -a \${PIPESTATUS[1]} == 0 ]'"; - die "unpacking of $p failed" if $?; - } - - close O; - - chdir $unpacked; - - mkdir "bin", 0755; - - unless( -f "bin/nircmd.exe" ) { - unless( -f "../$packages/nircmd.zip" ) { - system "cd ../$packages; wget $wgetopt -c http://www.nirsoft.net/utils/nircmd.zip"; - die "download of nircmd.zip failed" if $?; - } - - mkdir "apps", 0755; - mkdir "apps/nircmd", 0755; - system "cd apps/nircmd; unzip ../../../$packages/nircmd.zip && mv nircmd.exe nircmdc.exe ../../bin"; - die "unpacking of nircmd failed" if $?; - } - - if( -d "../addons" ) { - print " Including addons...\n" if $verbose; - system "tar -C ../addons -cf - . | tar $taropt -xf -"; - die "copying of addons failed" if $?; - } - - chdir ".."; -} - -my($major, $minor, $patch); - -open F, "../../CMakeLists.txt"; -while() { - if(/SET\(CPACK_PACKAGE_VERSION_MAJOR "(\d+)"\)/i) { - $major = $1; - } elsif(/SET\(CPACK_PACKAGE_VERSION_MINOR "(\d+)"\)/i) { - $minor = $1; - } elsif(/SET\(CPACK_PACKAGE_VERSION_PATCH "(\d+)"\)/i) { - $patch = $1; - } elsif(/SET\(RELEASE_NAME "(.+)"\)/i) { - $releasename = $1 unless defined $releasename; - } -} -close F; - -$version = "$major.$minor.$patch" unless defined $version; - -my($pmajor,$pminor,$ppatch) = $version =~ /^(\d+)\.(\d+)\.(\d+)$/; -die "Invalid version $version" unless defined $ppatch; - -unless( defined $binary ) { - if( -f "binary$archpostfix-$version" ) { - open P, "binary$archpostfix-$version"; - $binary =

; - close P; - $binary++; - } else { - $binary = 1; - } -} - -# -# Create postinstall.bat -# - -open F, ">../Installer-Files/postinstall.bat"; - -my $r = ">>postinstall.log 2>&1\r\n"; - -print F "\@echo off\r\n"; -print F "if exist postinstall.log del postinstall.log\r\n"; -print F "set OSGEO4W_ROOT_MSYS=%OSGEO4W_ROOT:\\=/%$r"; -print F "if \"%OSGEO4W_ROOT_MSYS:~1,1%\"==\":\" set OSGEO4W_ROOT_MSYS=/%OSGEO4W_ROOT_MSYS:~0,1%/%OSGEO4W_ROOT_MSYS:~3%$r"; - -print F "del preremove-conf.bat$r"; -my $c = ">>preremove-conf.bat\r\n"; -print F "echo set OSGEO4W_ROOT=%OSGEO4W_ROOT%$c"; -print F "echo set OSGEO4W_ROOT_MSYS=%OSGEO4W_ROOT_MSYS%$c"; -print F "echo set OSGEO4W_STARTMENU=%OSGEO4W_STARTMENU%$c"; -print F "echo set OSGEO4W_DESKTOP=%OSGEO4W_DESKTOP%$c"; - -print F "echo OSGEO4W_ROOT=%OSGEO4W_ROOT%$r"; -print F "echo OSGEO4W_ROOT_MSYS=%OSGEO4W_ROOT_MSYS%$r"; -print F "echo OSGEO4W_STARTMENU=%OSGEO4W_STARTMENU%$r"; -print F "echo OSGEO4W_DESKTOP=%OSGEO4W_DESKTOP%$r"; -print F "PATH %OSGEO4W_ROOT%\\bin;%PATH%$r"; -print F "cd /d %OSGEO4W_ROOT%$r"; - -chdir $unpacked; -for my $p () { - $p =~ s/\//\\/g; - my($dir,$file) = $p =~ /^(.+)\\([^\\]+)$/; - - print F "echo Running postinstall $file...$r"; - print F "%COMSPEC% /c $p$r"; - print F "ren $p $file.done$r"; -} -chdir ".."; - -print F "ren postinstall.bat postinstall.bat.done$r"; - -close F; - -open F, ">../Installer-Files/preremove.bat"; - -$r = ">>%TEMP%\\$packagename-OSGeo4W-$version-$binary-preremove.log 2>&1\r\n"; - -print F "\@echo off\r\n"; -print F "call \"%~dp0\\preremove-conf.bat\"$r"; -print F "echo OSGEO4W_ROOT=%OSGEO4W_ROOT%$r"; -print F "echo OSGEO4W_STARTMENU=%OSGEO4W_STARTMENU%$r"; -print F "echo OSGEO4W_DESKTOP=%OSGEO4W_DESKTOP%$r"; -print F "set OSGEO4W_ROOT_MSYS=%OSGEO4W_ROOT:\\=/%$r"; -print F "if \"%OSGEO4W_ROOT_MSYS:~1,1%\"==\":\" set OSGEO4W_ROOT_MSYS=/%OSGEO4W_ROOT_MSYS:~0,1%/%OSGEO4W_ROOT_MSYS:~3%$r"; -print F "echo OSGEO4W_ROOT_MSYS=%OSGEO4W_ROOT_MSYS%$r"; -print F "PATH %OSGEO4W_ROOT%\\bin;%PATH%$r"; -print F "cd /d \"%OSGEO4W_ROOT%\"$r"; - -chdir $unpacked; -for my $p () { - $p =~ s/\//\\/g; - my($dir,$file) = $p =~ /^(.+)\\([^\\]+)$/; - - print F "echo Running preremove $file...$r"; - print F "%COMSPEC% /c $p$r"; - print F "ren $p $file.done$r"; -} -chdir ".."; - -print F "ren preremove.bat preremove.bat.done$r"; - -close F; - -unless(-d "untgz") { - system "unzip $packages/Untgz.zip"; - die "unpacking Untgz.zip failed" if $?; -} - -unless(-d "inetc") { - mkdir "inetc", 0755; - system "unzip -p $packages/Inetc.zip Plugins/x86-ansi/INetC.dll >inetc/INetC.dll"; - die "unpacking Inetc.zip failed" if $?; -} - -chdir ".."; - - -print "Creating license file\n" if $verbose; -open O, ">license.tmp"; -my $lic; -for my $l ( ( "osgeo4w/$unpacked/apps/$shortname/doc/LICENSE", "../COPYING", "./Installer-Files/LICENSE.txt" ) ) { - next unless -f $l; - $lic = $l; - last; -} - -die "no license found" unless defined $lic; - -my $i = 0; -if( @lic ) { - print O "License overview:\n"; - print O "1. QGIS\n"; - $i = 1; - for my $l ( @desc ) { - print O ++$i . ". $l\n"; - } - $i = 0; - print O "\n\n----------\n\n" . ++$i . ". License of 'QGIS'\n\n"; -} - -print " Including QGIS license $lic\n" if $verbose; -open I, $lic; -while() { - s/\s*$/\n/; - print O; -} -close I; - -for my $l (@lic) { - print " Including license $l\n" if $verbose; - - open I, "osgeo4w/$packages/$l" or die "License $l not found."; - print O "\n\n----------\n\n" . ++$i . ". License of '" . shift(@desc) . "'\n\n"; - while() { - s/\s*$/\n/; - print O; - } - close I; -} - -close O; - -my $license = "license.tmp"; -if( -f "osgeo4w/$unpacked/apps/$shortname/doc/LICENSE" ) { - open O, ">osgeo4w/$unpacked/apps/$shortname/doc/LICENSE"; - open I, $license; - while() { - print O; - } - close O; - close I; - - $license = "osgeo4w/$unpacked/apps/$shortname/doc/LICENSE"; -} - - -print "Running NSIS\n" if $verbose; - -my $installerbase = "$packagename-OSGeo4W-$version-$binary-Setup$archpostfix"; - -my $run; -my $instdest; - -if($^O eq "cygwin") { - $run = "cygstart "; - $instdest = `cygpath -w \$PWD`; -} else { - $run = "wine "; - $instdest = `winepath -w \$PWD`; -} - -$instdest =~ s/\s+$//; -$instdest =~ s/\\/\\\\/g; - - -my $args = ""; -$args .= " -V$verbose"; -$args .= " -DVERSION_NAME='$releasename'"; -$args .= " -DVERSION_NUMBER='$version'"; -$args .= " -DBINARY_REVISION=$binary"; -$args .= sprintf( " -DVERSION_INT='%d%02d%02d%02d'", $pmajor, $pminor, $ppatch, $binary ); -$args .= sprintf( " -DQGIS_BASE='$packagename %d.%d'", $pmajor, $pminor ); -$args .= " -DDISPLAYED_NAME=\"$packagename $version '$releasename'\""; -$args .= " -DPACKAGE_FOLDER=osgeo4w/$unpacked"; -$args .= " -DLICENSE_FILE='$license'"; -$args .= " -DARCH='$arch'"; -$args .= " QGIS-Installer.nsi"; - -sub sign { - my $base = shift; - - my $cmd = "osslsigncode sign"; - $cmd .= " -pkcs12 \"$signwith\""; - $cmd .= " -pass \"$signpass\"" if defined $signpass; - $cmd .= " -n \"$packagename $version '$releasename'\""; - $cmd .= " -h sha256"; - $cmd .= " -i \"https://qgis.org\""; - $cmd .= " -t \"http://timestamp.digicert.com\""; - $cmd .= " -in \"$base.exe\""; - $cmd .= " $base-signed.exe"; - system $cmd; - die "signing failed [$cmd]" if $?; - - rename("$base-signed.exe", "$base.exe") or die "rename failed: $!"; -} - -my $cmd; -unlink "makeuinst.exe"; -$cmd = "makensis -DINNER=1 -DUNINSTALLERDEST='$instdest' -DINSTALLER_NAME='makeuinst.exe' $args"; -system $cmd; -die "running makensis failed [$cmd]" if $?; -die "makeuinst.exe not created" unless -f "makeuinst.exe"; - -unlink "uninstall.exe"; -chmod 0755, "makeuinst.exe"; -system "${run}makeuinst.exe"; -sleep 5; -die "uninstall.exe not created" unless -f "uninstall.exe"; -unlink "makeuinst.exe"; - -sign "uninstall" if $signwith; - -$cmd = "makensis -DINSTALLER_NAME='$installerbase.exe' $args"; -system $cmd; -die "running makensis failed [$cmd]" if $?; - -sign "$installerbase" if $signwith; - -open P, ">osgeo4w/binary$archpostfix-$version"; -print P $binary; -close P; - -system "md5sum $installerbase.exe >$installerbase.exe.md5sum"; - -__END__ - -=head1 NAME - -creatensis.pl - create NSIS package from OSGeo4W packages - -=head1 SYNOPSIS - -creatensis.pl [options] [packages...] - - Options: - -verbose increase verbosity - -releasename=name name of release (defaults to CMakeLists.txt setting) - -keep don't start with a fresh unpacked directory - -signwith=cert.p12 optionally sign package with certificate (requires osslsigncode) - -signpass=password password of certificate - -version=m.m.p package version (defaults to CMakeLists.txt setting) - -binary=b binary version of package - -ininame=filename name of the setup.ini (defaults to setup.ini) - -packagename=s name of package (defaults to 'QGIS') - -shortname=s shortname used for batch file (defaults to 'qgis') - -mirror=s default mirror (defaults to 'http://download.osgeo.org/osgeo4w') - -arch=s architecture (x86 or x86_64; defaults to 'x86_64') - -help this help - - If no packages are given 'qgis-full' and it's dependencies will be retrieved - and packaged. - - Packages with a appended '-' are excluded, but their dependencies are included. -=cut diff --git a/ms-windows/osgeo4w/designer.bat.tmpl b/ms-windows/osgeo4w/designer.bat.tmpl deleted file mode 100644 index 6778fb445cf05..0000000000000 --- a/ms-windows/osgeo4w/designer.bat.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -call "%~dp0\o4w_env.bat" -call qt5_env.bat -call py3_env.bat -path %OSGEO4W_ROOT%\apps\@package@\bin;%PATH% -set QGIS_PREFIX_PATH=%OSGEO4W_ROOT:\=/%/apps/@package@ -set QT_PLUGIN_PATH=%OSGEO4W_ROOT%\apps\@package@\qtplugins;%OSGEO4W_ROOT%\apps\qt5\plugins -cd %USERPROFILE% -start "Qt Designer with QGIS custom widgets" /B "%OSGEO4W_ROOT%\apps\qt5\bin\designer.exe" %* diff --git a/ms-windows/osgeo4w/httpd.conf.tmpl b/ms-windows/osgeo4w/httpd.conf.tmpl deleted file mode 100644 index 382be280173da..0000000000000 --- a/ms-windows/osgeo4w/httpd.conf.tmpl +++ /dev/null @@ -1,28 +0,0 @@ -LoadModule fcgid_module modules/mod_fcgid.so - -DefaultInitEnv O4W_QT_PREFIX "@osgeo4w@/apps/Qt5" -DefaultInitEnv O4W_QT_BINARIES "@osgeo4w@/apps/Qt5/bin" -DefaultInitEnv O4W_QT_PLUGINS "@osgeo4w@/apps/Qt5/plugins" -DefaultInitEnv O4W_QT_LIBRARIES "@osgeo4w@/apps/Qt5/lib" -DefaultInitEnv O4W_QT_TRANSLATIONS "@osgeo4w@/apps/Qt5/translations" -DefaultInitEnv O4W_QT_HEADERS "@osgeo4w@/apps/Qt5/include" -DefaultInitEnv O4W_QT_DOC "@osgeo4w@/apps/Qt5/doc" - -DefaultInitEnv PATH "@osgeo4w@\apps\qt5\bin;@osgeo4w@\bin;@osgeo4w@\apps\@package@\bin;@osgeo4w@\apps\grass\@grasspath@\bin;@osgeo4w@\apps\grass\@grasspath@\lib;@windir@\system32;@windir@;@windir@\System32\Wbem" -DefaultInitEnv QGIS_PREFIX_PATH "@osgeo4w@\apps\@package@" -DefaultInitEnv QT_PLUGIN_PATH "@osgeo4w@\apps\@package@\qtplugins;@osgeo4w@\apps\qt5\plugins" -DefaultInitEnv TEMP "@temp@" -DefaultInitEnv PYTHONHOME "@osgeo4w@\apps\Python37" -DefaultInitEnv PYTHONPATH "@osgeo4w@\apps\Python37;@osgeo4w@\apps\Python37\Scripts" - -Alias /@package@/ @osgeo4w@/apps/@package@/bin/ - - - SetHandler fcgid-script - Options ExecCGI - # Order/Allow is for Apache 2.2 - #Order allow,deny - #Allow from all - # Require is for Apache 2.4 - Require all granted - diff --git a/ms-windows/osgeo4w/msvc-env.bat b/ms-windows/osgeo4w/msvc-env.bat deleted file mode 100644 index 0dae07f8be412..0000000000000 --- a/ms-windows/osgeo4w/msvc-env.bat +++ /dev/null @@ -1,91 +0,0 @@ -@echo off -REM *************************************************************************** -REM msvc-env.cmd -REM --------------------- -REM begin : June 2018 -REM copyright : (C) 2018 by Juergen E. Fischer -REM email : jef at norbit dot de -REM *************************************************************************** -REM * * -REM * This program is free software; you can redistribute it and/or modify * -REM * it under the terms of the GNU General Public License as published by * -REM * the Free Software Foundation; either version 2 of the License, or * -REM * (at your option) any later version. * -REM * * -REM *************************************************************************** - -if not "%PROGRAMFILES(X86)%"=="" set PF86=%PROGRAMFILES(X86)% -if "%PF86%"=="" set PF86=%PROGRAMFILES% -if "%PF86%"=="" (echo PROGRAMFILES not set & goto error) - -if "%VCSDK%"=="" set VCSDK=10.0.14393.0 - -set ARCH=%1 -if "%ARCH%"=="x86" goto x86 -if "%ARCH%"=="x86_64" goto x86_64 -goto usage - -:x86 -set VCARCH=x86 -set CMAKE_COMPILER_PATH=%PF86%\Microsoft Visual Studio 14.0\VC\bin -set DBGHLP_PATH=%PF86%\Microsoft Visual Studio 14.0\Common7\IDE\Remote Debugger\x86 -set SETUPAPI_LIBRARY=%PF86%\Windows Kits\10\Lib\%VCSDK%\um\x86\SetupAPI.Lib -goto archset - -:x86_64 -set VCARCH=amd64 -set CMAKE_COMPILER_PATH=%PF86%\Microsoft Visual Studio 14.0\VC\bin\amd64 -set DBGHLP_PATH=%PF86%\Microsoft Visual Studio 14.0\Common7\IDE\Remote Debugger\x64 -set SETUPAPI_LIBRARY=%PF86%\Windows Kits\10\Lib\%VCSDK%\um\x64\SetupAPI.Lib - -:archset -if not exist "%SETUPAPI_LIBRARY%" (echo SETUPAPI_LIBRARY not found & goto error) - -if "%CC%"=="" set CC=%CMAKE_COMPILER_PATH:\=/%/cl.exe -if "%CXX%"=="" set CXX=%CMAKE_COMPILER_PATH:\=/%/cl.exe -set CLCACHE_CL=%CMAKE_COMPILER_PATH:\=/%/cl.exe - -if "%OSGEO4W_ROOT%"=="" if "%ARCH%"=="x86" ( - set OSGEO4W_ROOT=C:\OSGeo4W -) else ( - set OSGEO4W_ROOT=C:\OSGeo4W64 -) - -if not exist "%OSGEO4W_ROOT%\bin\o4w_env.bat" (echo o4w_env.bat not found & goto error) -call "%OSGEO4W_ROOT%\bin\o4w_env.bat" -call "%OSGEO4W_ROOT%\bin\py3_env.bat" -call "%OSGEO4W_ROOT%\bin\qt5_env.bat" - -set VS140COMNTOOLS=%PF86%\Microsoft Visual Studio 14.0\Common7\Tools\ -call "%PF86%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %VCARCH% - -path %path%;%PF86%\Microsoft Visual Studio 14.0\VC\bin - -set GRASS7= -if exist %OSGEO4W_ROOT%\bin\grass74.bat set GRASS7=%OSGEO4W_ROOT%\bin\grass74.bat -if exist %OSGEO4W_ROOT%\bin\grass76.bat set GRASS7=%OSGEO4W_ROOT%\bin\grass76.bat -if exist %OSGEO4W_ROOT%\bin\grass78.bat set GRASS7=%OSGEO4W_ROOT%\bin\grass78.bat -if "%GRASS7%"=="" (echo GRASS7 not found & goto error) -for /f "usebackq tokens=1" %%a in (`%GRASS7% --config path`) do set GRASS_PREFIX=%%a - -set PYTHONPATH= -if exist "%PROGRAMFILES%\CMake\bin" path %PATH%;%PROGRAMFILES%\CMake\bin -if exist "%PF86%\CMake\bin" path %PATH%;%PF86%\CMake\bin -if exist c:\cygwin64\bin path %PATH%;c:\cygwin64\bin -if exist c:\cygwin\bin path %PATH%;c:\cygwin\bin - -set LIB=%LIB%;%OSGEO4W_ROOT%\apps\Qt5\lib;%OSGEO4W_ROOT%\lib -set INCLUDE=%INCLUDE%;%OSGEO4W_ROOT%\apps\Qt5\include;%OSGEO4W_ROOT%\include - -goto end - -:usage -echo usage: %0 [x86^|x86_64] -echo sample: %0 x86_64 -exit /b 1 - -:error -echo ENV ERROR %ERRORLEVEL%: %DATE% %TIME% -exit /b 1 - -:end diff --git a/ms-windows/osgeo4w/ninja/ninja.bat b/ms-windows/osgeo4w/ninja/ninja.bat deleted file mode 100644 index e8b08649b1446..0000000000000 --- a/ms-windows/osgeo4w/ninja/ninja.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo off -call %OSGEO4W_ROOT%\bin\o4w_env.bat -call py3_env.bat -call qt5_env.bat -call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 -path %PATH%;c:\cygwin\bin;c:\program files\cmake\bin -%OSGEO4W_ROOT%\bin\ninja -j4 -C ..\build-qgis-dev-x86_64 diff --git a/ms-windows/osgeo4w/ninja/ninja.sln b/ms-windows/osgeo4w/ninja/ninja.sln deleted file mode 100644 index 7a7812b16bd65..0000000000000 --- a/ms-windows/osgeo4w/ninja/ninja.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ninja", "ninja.vcxproj", "{02B448C7-945C-46D6-954C-AEAE0653BA59}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - RelWithDebInfo|Win32 = RelWithDebInfo|Win32 - RelWithDebInfo|x64 = RelWithDebInfo|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {02B448C7-945C-46D6-954C-AEAE0653BA59}.RelWithDebInfo|Win32.ActiveCfg = Release|Win32 - {02B448C7-945C-46D6-954C-AEAE0653BA59}.RelWithDebInfo|Win32.Build.0 = Release|Win32 - {02B448C7-945C-46D6-954C-AEAE0653BA59}.RelWithDebInfo|x64.ActiveCfg = Release|x64 - {02B448C7-945C-46D6-954C-AEAE0653BA59}.RelWithDebInfo|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/ms-windows/osgeo4w/ninja/ninja.vcxproj b/ms-windows/osgeo4w/ninja/ninja.vcxproj deleted file mode 100644 index 347ae90b71769..0000000000000 --- a/ms-windows/osgeo4w/ninja/ninja.vcxproj +++ /dev/null @@ -1,108 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {02B448C7-945C-46D6-954C-AEAE0653BA59} - MakeFileProj - 8.1 - - - - Makefile - true - v140 - - - Makefile - false - v140 - - - Makefile - true - v140 - - - Makefile - false - v140 - - - - - - - - - - - - - - - - - - - - - ninja -C ../build-qgis-dev-x86 all - ..\build-qgis-dev-x86\output\bin\qgis.exe - WIN32;_DEBUG;$(NMakePreprocessorDefinitions) - ..\build-qgis-dev-x86 - - ninja -C ../build-qgis-dev-x86 clean all - ninja -C ../build-qgis-dev-x86 clean - - - ninja -C ../build-qgis-dev-x86 all - ..\build-qgis-dev-x86\output\bin\qgis.exe - WIN32;NDEBUG;$(NMakePreprocessorDefinitions) - ..\build-qgis-dev-x86 - - ninja -C ../build-qgis-dev-x86 clean all - ninja -C ../build-qgis-dev-x86 clean - - - ninja -C ../build-qgis-dev-x86_64 -j4 -k1000 all - ninja -C ../build-qgis-dev-x86_64 -j4 -k1000 clean all - ninja -C ../build-qgis-dev-x86_64 clean - ..\build-qgis-dev-x86_64\output\bin\qgis.exe - - - ninja -C ../build-qgis-dev-x86_64 -j4 -k1000 all - ninja -C ../build-qgis-dev-x86_64 -j4 -k1000 clean all - ninja -C ../build-qgis-dev-x86_64 clean - ..\build-qgis-dev-x86_64\output\bin\qgis.exe - - - - x86.log - - - - - x86.log - - - - - - \ No newline at end of file diff --git a/ms-windows/osgeo4w/package-nightly.cmd b/ms-windows/osgeo4w/package-nightly.cmd deleted file mode 100644 index f1e89aaf83327..0000000000000 --- a/ms-windows/osgeo4w/package-nightly.cmd +++ /dev/null @@ -1,325 +0,0 @@ -@echo off -REM *************************************************************************** -REM package-nightly.cmd -REM --------------------- -REM begin : January 2011 -REM copyright : (C) 2011 by Juergen E. Fischer -REM email : jef at norbit dot de -REM *************************************************************************** -REM * * -REM * This program is free software; you can redistribute it and/or modify * -REM * it under the terms of the GNU General Public License as published by * -REM * the Free Software Foundation; either version 2 of the License, or * -REM * (at your option) any later version. * -REM * * -REM *************************************************************************** - -setlocal enabledelayedexpansion - -set VERSION=%1 -set PACKAGE=%2 -set PACKAGENAME=%3 -set ARCH=%4 -set SHA=%5 -set SITE=%6 -if "%VERSION%"=="" goto usage -if "%PACKAGE%"=="" goto usage -if "%PACKAGENAME%"=="" goto usage -if "%ARCH%"=="" goto usage -if not "%SHA%"=="" set SHA=-%SHA% -if "%SITE%"=="" set SITE=qgis.org -if "%TARGET%"=="" set TARGET=Nightly -if "%BUILDNAME%"=="" set BUILDNAME=%PACKAGENAME%-%VERSION%%SHA%-%TARGET%-VC14-%ARCH% - -if "%BUILDDIR%"=="" set BUILDDIR=%CD%\build-%PACKAGENAME%-%ARCH% -if not exist "%BUILDDIR%" mkdir %BUILDDIR% -if not exist "%BUILDDIR%" (echo could not create build directory %BUILDDIR% & goto error) - -call msvc-env.bat %ARCH% -call gdal-dev-env.bat - -set O4W_ROOT=%OSGEO4W_ROOT:\=/% -set LIB_DIR=%O4W_ROOT% - -if "%ARCH%"=="x86" ( - set CMAKE_OPT=^ - -D SPATIALINDEX_LIBRARY=%O4W_ROOT%/lib/spatialindex-32.lib -) else ( - set CMAKE_OPT=^ - -D SPATIALINDEX_LIBRARY=%O4W_ROOT%/lib/spatialindex-64.lib ^ - -D CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_NO_WARNINGS=TRUE -) - -for %%i in ("%GRASS_PREFIX%") do set GRASS7_VERSION=%%~nxi -set GRASS_VERSIONS=%GRASS7_VERSION% - -set TAR=tar.exe -if exist "c:\cygwin\bin\tar.exe" set TAR=c:\cygwin\bin\tar.exe -if exist "c:\cygwin64\bin\tar.exe" set TAR=c:\cygwin64\bin\tar.exe - -set BUILDCONF=RelWithDebInfo - -cd ..\.. -set SRCDIR=%CD% - -if "%BUILDDIR:~1,1%"==":" %BUILDDIR:~0,2% -cd %BUILDDIR% - -set PKGDIR=%OSGEO4W_ROOT%\apps\%PACKAGENAME% - -if exist repackage goto package - -if not exist build.log goto build - -REM -REM try renaming the logfile to see if it's locked -REM - -if exist build.tmp del build.tmp -if exist build.tmp (echo could not remove build.tmp & goto error) - -ren build.log build.tmp -if exist build.log goto locked -if not exist build.tmp goto locked - -ren build.tmp build.log -if exist build.tmp goto locked -if not exist build.log goto locked - -goto build - -:locked -echo Logfile locked -if exist build.tmp del build.tmp -goto error - -:build -echo BEGIN: %DATE% %TIME% - -set >buildenv.log - -if exist qgsversion.h del qgsversion.h - -if exist CMakeCache.txt if exist skipcmake goto skipcmake - -touch %SRCDIR%\CMakeLists.txt - -echo CMAKE: %DATE% %TIME% - -if "%CMAKEGEN%"=="" set CMAKEGEN=Ninja -if "%OSGEO4W_CXXFLAGS%"=="" set OSGEO4W_CXXFLAGS=/MD /Z7 /MP /Od /D NDEBUG - -for %%i in (%PYTHONHOME%) do set PYVER=%%~ni - -cmake -G "%CMAKEGEN%" ^ - -D CMAKE_CXX_COMPILER="%CXX:\=/%" ^ - -D CMAKE_C_COMPILER="%CC:\=/%" ^ - -D CMAKE_LINKER="%CMAKE_COMPILER_PATH:\=/%/link.exe" ^ - -D CMAKE_CXX_FLAGS_RELWITHDEBINFO="%OSGEO4W_CXXFLAGS%" ^ - -D CMAKE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO=%BUILDDIR%\apps\%PACKAGENAME%\pdb ^ - -D SUBMIT_URL="https://cdash.orfeo-toolbox.org/submit.php?project=QGIS" ^ - -D BUILDNAME="%BUILDNAME%" ^ - -D SITE="%SITE%" ^ - -D PEDANTIC=TRUE ^ - -D WITH_QSPATIALITE=TRUE ^ - -D WITH_SERVER=TRUE ^ - -D SERVER_SKIP_ECW=TRUE ^ - -D WITH_GRASS=TRUE ^ - -D WITH_3D=TRUE ^ - -D WITH_GRASS7=TRUE ^ - -D WITH_HANA=TRUE ^ - -D GRASS_PREFIX7=%GRASS_PREFIX:\=/% ^ - -D WITH_ORACLE=TRUE ^ - -D WITH_CUSTOM_WIDGETS=TRUE ^ - -D CMAKE_BUILD_TYPE=%BUILDCONF% ^ - -D CMAKE_CONFIGURATION_TYPES=%BUILDCONF% ^ - -D SETUPAPI_LIBRARY="%SETUPAPI_LIBRARY%" ^ - -D PROJ_LIBRARY=%O4W_ROOT%/apps/proj-dev/lib/proj.lib ^ - -D PROJ_INCLUDE_DIR=%O4W_ROOT%/apps/proj-dev/include ^ - -D GDAL_LIBRARY=%O4W_ROOT%/apps/gdal-dev/lib/gdal_i.lib ^ - -D GDAL_INCLUDE_DIR=%O4W_ROOT%/apps/gdal-dev/include ^ - -D GEOS_LIBRARY=%O4W_ROOT%/lib/geos_c.lib ^ - -D SQLITE3_LIBRARY=%O4W_ROOT%/lib/sqlite3_i.lib ^ - -D SPATIALITE_LIBRARY=%O4W_ROOT%/lib/spatialite_i.lib ^ - -D PYTHON_EXECUTABLE=%O4W_ROOT%/bin/python3.exe ^ - -D SIP_BINARY_PATH=%PYTHONHOME:\=/%/sip.exe ^ - -D PYTHON_INCLUDE_DIR=%PYTHONHOME:\=/%/include ^ - -D PYTHON_LIBRARY=%PYTHONHOME:\=/%/libs/%PYVER%.lib ^ - -D QT_LIBRARY_DIR=%O4W_ROOT%/lib ^ - -D QT_HEADERS_DIR=%O4W_ROOT%/apps/qt5/include ^ - -D CMAKE_INSTALL_PREFIX=%O4W_ROOT%/apps/%PACKAGENAME% ^ - -D FCGI_INCLUDE_DIR=%O4W_ROOT%/include ^ - -D FCGI_LIBRARY=%O4W_ROOT%/lib/libfcgi.lib ^ - -D QCA_INCLUDE_DIR=%OSGEO4W_ROOT%\apps\Qt5\include\QtCrypto ^ - -D QCA_LIBRARY=%OSGEO4W_ROOT%\apps\Qt5\lib\qca-qt5.lib ^ - -D QSCINTILLA_LIBRARY=%OSGEO4W_ROOT%\apps\Qt5\lib\qscintilla2.lib ^ - -D DART_TESTING_TIMEOUT=60 ^ - -D PUSH_TO_CDASH=TRUE ^ - %CMAKE_OPT% ^ - %SRCDIR:\=/% -if errorlevel 1 (echo cmake failed & goto error) - -if "%CONFIGONLY%"=="1" (echo Exiting after configuring build directory: %CD% & goto end) - -:skipcmake -if exist ..\noclean (echo skip clean & goto skipclean) -echo CLEAN: %DATE% %TIME% -cmake --build %BUILDDIR% --target clean --config %BUILDCONF% -if errorlevel 1 (echo clean failed & goto error) - -:skipclean -if exist ..\skipbuild (echo skip build & goto skipbuild) -echo ALL_BUILD: %DATE% %TIME% -cmake --build %BUILDDIR% --target %TARGET%Build --config %BUILDCONF% -set /P tag=<%BUILDDIR%\Testing\TAG -findstr "" %BUILDDIR%\Testing\%tag%\Build.xml >nul -if not errorlevel 1 ( - cmake --build %BUILDDIR% --target %TARGET%Submit --config %BUILDCONF% - if errorlevel 1 echo SUBMITTING BUILD ERRORS WAS NOT SUCCESSFUL. - echo build failed - goto error -) - -:skipbuild -if exist ..\skiptests goto skiptests - -echo RUN_TESTS: %DATE% %TIME% - -reg add "HKCU\Software\Microsoft\Windows\Windows Error Reporting" /v DontShow /t REG_DWORD /d 1 /f - -set oldtemp=%TEMP% -set oldtmp=%TMP% -set oldpath=%PATH% - -set TEMP=%TEMP%\%PACKAGENAME%-%ARCH% -set TMP=%TEMP% -if exist "%TEMP%" rmdir /s /q "%TEMP%" -mkdir "%TEMP%" - -for %%g IN (%GRASS_VERSIONS%) do ( - set path=!path!;%OSGEO4W_ROOT%\apps\grass\%%g\lib - set GISBASE=%OSGEO4W_ROOT%\apps\grass\%%g -) -PATH %path%;%BUILDDIR%\output\plugins -set QT_PLUGIN_PATH=%BUILDDIR%\output\plugins;%OSGEO4W_ROOT%\apps\qt5\plugins - -cmake --build %BUILDDIR% --target %TARGET%Test --config %BUILDCONF% -if errorlevel 1 echo TESTS WERE NOT SUCCESSFUL. - -set TEMP=%oldtemp% -set TMP=%oldtmp% -PATH %oldpath% - -cmake --build %BUILDDIR% --target %TARGET%Submit --config %BUILDCONF% -if errorlevel 1 echo TEST SUBMISSION WAS NOT SUCCESSFUL. - -:skiptests -if exist ..\skippackage goto end - -if exist "%PKGDIR%" ( - echo REMOVE: %DATE% %TIME% - rmdir /s /q "%PKGDIR%" -) - -echo INSTALL: %DATE% %TIME% -cmake --build %BUILDDIR% --target install --config %BUILDCONF% -if errorlevel 1 (echo INSTALL failed & goto error) - -:package -echo PACKAGE: %DATE% %TIME% - -cd .. - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/@grassversions@/%GRASS_VERSIONS%/g' postinstall-dev.bat >%OSGEO4W_ROOT%\etc\postinstall\%PACKAGENAME%.bat -if errorlevel 1 (echo creation of desktop postinstall failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/@grassversions@/%GRASS_VERSIONS%/g' preremove-dev.bat >%OSGEO4W_ROOT%\etc\preremove\%PACKAGENAME%.bat -if errorlevel 1 (echo creation of desktop preremove failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/^call py3_env.bat/call gdal-dev-py3-env.bat/' designer.bat.tmpl >%OSGEO4W_ROOT%\bin\%PACKAGENAME%-designer.bat.tmpl -if errorlevel 1 (echo creation of designer template failed & goto error) -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' qgis.reg.tmpl >%PKGDIR%\bin\qgis.reg.tmpl -if errorlevel 1 (echo creation of registry template & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/^call py3_env.bat/call gdal-dev-py3-env.bat/' qgis.bat.tmpl >%OSGEO4W_ROOT%\bin\%PACKAGENAME%.bat.tmpl -if errorlevel 1 (echo creation of desktop template failed & goto error) - -set batches=bin/%PACKAGENAME%.bat.tmpl -for %%g IN (%GRASS_VERSIONS%) do ( - for /f "usebackq tokens=1" %%a in (`%%g --config version`) do set gv=%%a - for /F "delims=." %%i in ("!gv!") do set v=%%i - - sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/@grasspath@/%%g/g' -e 's/@grassversion@/!gv!/g' -e 's/^call py3_env.bat/call gdal-dev-py3-env.bat/' qgis-grass.bat.tmpl >%OSGEO4W_ROOT%\bin\%PACKAGENAME%-g!v!.bat.tmpl - if errorlevel 1 (echo creation of desktop template failed & goto error) - set batches=!batches! bin/%PACKAGENAME%-g!v!.bat.tmpl -) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/^call py3_env.bat/call gdal-dev-py3-env.bat/' python.bat.tmpl >%OSGEO4W_ROOT%\bin\python-%PACKAGENAME%.bat.tmpl -if errorlevel 1 (echo creation of python wrapper template failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/^call py3_env.bat/call gdal-dev-py3-env.bat/' process.bat.tmpl >%OSGEO4W_ROOT%\bin\qgis_process-%PACKAGENAME%.bat.tmpl -if errorlevel 1 (echo creation of qgis process wrapper template failed & goto error) - -touch exclude -if exist ..\skipbuild (echo skip build & goto skipbuild) - -move %PKGDIR%\bin\qgis.exe %OSGEO4W_ROOT%\bin\%PACKAGENAME%-bin.exe -if errorlevel 1 (echo move of desktop executable failed & goto error) -copy qgis.vars %OSGEO4W_ROOT%\bin\%PACKAGENAME%-bin.vars -if errorlevel 1 (echo copy of desktop executable vars failed & goto error) - -if not exist %PKGDIR%\qtplugins\sqldrivers mkdir %PKGDIR%\qtplugins\sqldrivers -move %OSGEO4W_ROOT%\apps\qt5\plugins\sqldrivers\qsqlocispatial.dll %PKGDIR%\qtplugins\sqldrivers -if errorlevel 1 (echo move of oci sqldriver failed & goto error) -move %OSGEO4W_ROOT%\apps\qt5\plugins\sqldrivers\qsqlspatialite.dll %PKGDIR%\qtplugins\sqldrivers -if errorlevel 1 (echo move of spatialite sqldriver failed & goto error) - -if not exist %PKGDIR%\qtplugins\designer mkdir %PKGDIR%\qtplugins\designer -move %OSGEO4W_ROOT%\apps\qt5\plugins\designer\qgis_customwidgets.dll %PKGDIR%\qtplugins\designer -if errorlevel 1 (echo move of customwidgets failed & goto error) - -if not exist %PKGDIR%\python\PyQt5\uic\widget-plugins mkdir %PKGDIR%\python\PyQt5\uic\widget-plugins -move %PYTHONHOME%\Lib\site-packages\PyQt5\uic\widget-plugins\qgis_customwidgets.py %PKGDIR%\python\PyQt5\uic\widget-plugins -if errorlevel 1 (echo move of customwidgets binding failed & goto error) - -for %%i in (dbghelp.dll symsrv.dll) do ( - copy "%DBGHLP_PATH%\%%i" %OSGEO4W_ROOT%\apps\%PACKAGENAME% - if errorlevel 1 (echo %%i not found & goto error) -) - -if not exist %ARCH%\release\qgis\%PACKAGENAME% mkdir %ARCH%\release\qgis\%PACKAGENAME% -%TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%/%PACKAGENAME%-%VERSION%-%PACKAGE%.tar.bz2 ^ - --exclude-from exclude ^ - --exclude "*.pyc" ^ - apps/%PACKAGENAME% ^ - bin/%PACKAGENAME%-bin.exe ^ - bin/%PACKAGENAME%-bin.vars ^ - %batches% ^ - bin/%PACKAGENAME%-designer.bat.tmpl ^ - bin/python-%PACKAGENAME%.bat.tmpl ^ - bin/qgis_process-%PACKAGENAME%.bat.tmpl ^ - etc/postinstall/%PACKAGENAME%.bat ^ - etc/preremove/%PACKAGENAME%.bat -if errorlevel 1 (echo tar failed & goto error) - -if not exist %ARCH%\release\qgis\%PACKAGENAME%-pdb mkdir %ARCH%\release\qgis\%PACKAGENAME%-pdb -%TAR% -C %BUILDDIR% -cjf %ARCH%/release/qgis/%PACKAGENAME%-pdb/%PACKAGENAME%-pdb-%VERSION%-%PACKAGE%.tar.bz2 ^ - apps/%PACKAGENAME%/pdb -if errorlevel 1 (echo tar failed & goto error) - -goto end - -:usage -echo usage: %0 version package packagename arch [sha [site]] -echo sample: %0 2.11.0 38 qgis-dev x86_64 339dbf1 qgis.org -exit /b 1 - -:error -echo BUILD ERROR %ERRORLEVEL%: %DATE% %TIME% -if exist %PACKAGENAME%-%VERSION%-%PACKAGE%.tar.bz2 del %PACKAGENAME%-%VERSION%-%PACKAGE%.tar.bz2 -exit /b 1 - -:end -echo FINISHED: %DATE% %TIME% - -endlocal diff --git a/ms-windows/osgeo4w/package.cmd b/ms-windows/osgeo4w/package.cmd deleted file mode 100644 index ee7c45dfd616c..0000000000000 --- a/ms-windows/osgeo4w/package.cmd +++ /dev/null @@ -1,482 +0,0 @@ -@echo off -REM *************************************************************************** -REM package.cmd -REM --------------------- -REM begin : July 2009 -REM copyright : (C) 2009 by Juergen E. Fischer -REM email : jef at norbit dot de -REM *************************************************************************** -REM * * -REM * This program is free software; you can redistribute it and/or modify * -REM * it under the terms of the GNU General Public License as published by * -REM * the Free Software Foundation; either version 2 of the License, or * -REM * (at your option) any later version. * -REM * * -REM *************************************************************************** - -setlocal enabledelayedexpansion - -set VERSION=%1 -set PACKAGE=%2 -set PACKAGENAME=%3 -set ARCH=%4 -set SHA=%5 -set SITE=%6 -if "%VERSION%"=="" goto usage -if "%PACKAGE%"=="" goto usage -if "%PACKAGENAME%"=="" goto usage -if "%ARCH%"=="" goto usage -if not "%SHA%"=="" set SHA=-%SHA% -if "%SITE%"=="" set SITE=qgis.org -if "%BUILDNAME%"=="" set BUILDNAME=%PACKAGENAME%-%VERSION%%SHA%-Release-VC14-%ARCH% - -set BUILDDIR=%CD%\build-%PACKAGENAME%-%ARCH% -if not exist "%BUILDDIR%" mkdir %BUILDDIR% -if not exist "%BUILDDIR%" (echo could not create build directory %BUILDDIR% & goto error) - -call msvc-env.bat %ARCH% - -set O4W_ROOT=%OSGEO4W_ROOT:\=/% -set LIB_DIR=%O4W_ROOT% - -if "%ARCH%"=="x86" ( - set CMAKE_OPT=^ - -D SPATIALINDEX_LIBRARY=%O4W_ROOT%/lib/spatialindex-32.lib -) else ( - set CMAKE_OPT=^ - -D SPATIALINDEX_LIBRARY=%O4W_ROOT%/lib/spatialindex-64.lib ^ - -D CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_NO_WARNINGS=TRUE -) - -for %%i in ("%GRASS_PREFIX%") do set GRASS7_VERSION=%%~nxi -set GRASS_VERSIONS=%GRASS7_VERSION% - -set TAR=tar.exe -if exist "c:\cygwin\bin\tar.exe" set TAR=c:\cygwin\bin\tar.exe -if exist "c:\cygwin64\bin\tar.exe" set TAR=c:\cygwin64\bin\tar.exe - -set BUILDCONF=Release - -cd ..\.. -set SRCDIR=%CD% - -if "%BUILDDIR:~1,1%"==":" %BUILDDIR:~0,2% -cd %BUILDDIR% - -set PKGDIR=%OSGEO4W_ROOT%\apps\%PACKAGENAME% - -if exist repackage goto package - -if not exist build.log goto build - -REM -REM try renaming the logfile to see if it's locked -REM - -if exist build.tmp del build.tmp -if exist build.tmp (echo could not remove build.tmp & goto error) - -ren build.log build.tmp -if exist build.log goto locked -if not exist build.tmp goto locked - -ren build.tmp build.log -if exist build.tmp goto locked -if not exist build.log goto locked - -goto build - -:locked -echo Logfile locked -if exist build.tmp del build.tmp -goto error - -:build -echo BEGIN: %DATE% %TIME% - -set >buildenv.log - -if exist qgsversion.h del qgsversion.h - -if exist CMakeCache.txt if exist skipcmake goto skipcmake - -touch %SRCDIR%\CMakeLists.txt - -echo CMAKE: %DATE% %TIME% -if errorlevel 1 goto error - -if "%CMAKEGEN%"=="" set CMAKEGEN=Ninja -if "%CC%"=="" set CC="%CMAKE_COMPILER_PATH:\=/%/cl.exe" -if "%CXX%"=="" set CXX="%CMAKE_COMPILER_PATH:\=/%/cl.exe" -if "%OSGEO4W_CXXFLAGS%"=="" set OSGEO4W_CXXFLAGS=/MD /Z7 /MP /O2 /Ob2 /D NDEBUG - -for %%i in (%PYTHONHOME%) do set PYVER=%%~ni - -cmake -G "%CMAKEGEN%" ^ - -D CMAKE_CXX_COMPILER="%CXX:\=/%" ^ - -D CMAKE_C_COMPILER="%CC:\=/%" ^ - -D CMAKE_LINKER="%CMAKE_COMPILER_PATH:\=/%/link.exe" ^ - -D CMAKE_CXX_FLAGS_RELEASE="%OSGEO4W_CXXFLAGS%" ^ - -D CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE=%BUILDDIR%\apps\%PACKAGENAME%\pdb ^ - -D CMAKE_SHARED_LINKER_FLAGS_RELEASE="/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF" ^ - -D CMAKE_MODULE_LINKER_FLAGS_RELEASE="/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF" ^ - -D SUBMIT_URL="https://cdash.orfeo-toolbox.org/submit.php?project=QGIS" ^ - -D BUILDNAME="%BUILDNAME%" ^ - -D SITE="%SITE%" ^ - -D PEDANTIC=TRUE ^ - -D WITH_QSPATIALITE=TRUE ^ - -D WITH_SERVER=TRUE ^ - -D WITH_HANA=TRUE ^ - -D SERVER_SKIP_ECW=TRUE ^ - -D WITH_GRASS=TRUE ^ - -D WITH_3D=TRUE ^ - -D WITH_GRASS7=TRUE ^ - -D GRASS_PREFIX7=%GRASS_PREFIX:\=/% ^ - -D WITH_ORACLE=TRUE ^ - -D WITH_CUSTOM_WIDGETS=TRUE ^ - -D CMAKE_BUILD_TYPE=%BUILDCONF% ^ - -D CMAKE_CONFIGURATION_TYPES=%BUILDCONF% ^ - -D SETUPAPI_LIBRARY="%SETUPAPI_LIBRARY%" ^ - -D GEOS_LIBRARY=%O4W_ROOT%/lib/geos_c.lib ^ - -D SQLITE3_LIBRARY=%O4W_ROOT%/lib/sqlite3_i.lib ^ - -D SPATIALITE_LIBRARY=%O4W_ROOT%/lib/spatialite_i.lib ^ - -D PYTHON_EXECUTABLE=%O4W_ROOT%/bin/python3.exe ^ - -D SIP_BINARY_PATH=%PYTHONHOME:\=/%/sip.exe ^ - -D PYTHON_INCLUDE_DIR=%PYTHONHOME:\=/%/include ^ - -D PYTHON_LIBRARY=%PYTHONHOME:\=/%/libs/%PYVER%.lib ^ - -D QT_LIBRARY_DIR=%O4W_ROOT%/lib ^ - -D QT_HEADERS_DIR=%O4W_ROOT%/apps/qt5/include ^ - -D CMAKE_INSTALL_PREFIX=%O4W_ROOT%/apps/%PACKAGENAME% ^ - -D FCGI_INCLUDE_DIR=%O4W_ROOT%/include ^ - -D FCGI_LIBRARY=%O4W_ROOT%/lib/libfcgi.lib ^ - -D QCA_INCLUDE_DIR=%OSGEO4W_ROOT%\apps\Qt5\include\QtCrypto ^ - -D QCA_LIBRARY=%OSGEO4W_ROOT%\apps\Qt5\lib\qca-qt5.lib ^ - -D QSCINTILLA_LIBRARY=%OSGEO4W_ROOT%\apps\Qt5\lib\qscintilla2.lib ^ - -D DART_TESTING_TIMEOUT=60 ^ - %CMAKE_OPT% ^ - %SRCDIR:\=/% -if errorlevel 1 (echo cmake failed & goto error) - -if "%CONFIGONLY%"=="1" (echo Exiting after configuring build directory: %CD% & goto end) - -:skipcmake -if exist ..\noclean (echo skip clean & goto skipclean) -echo CLEAN: %DATE% %TIME% -cmake --build %BUILDDIR% --target clean --config %BUILDCONF% -if errorlevel 1 (echo clean failed & goto error) - -:skipclean -if exist ..\skipbuild (echo skip build & goto skipbuild) -echo ALL_BUILD: %DATE% %TIME% -cmake --build %BUILDDIR% --config %BUILDCONF% -if errorlevel 1 (echo build failed & goto error) - -:skipbuild -if exist ..\skiptests goto skiptests - -echo RUN_TESTS: %DATE% %TIME% - -reg add "HKCU\Software\Microsoft\Windows\Windows Error Reporting" /v DontShow /t REG_DWORD /d 1 /f - -set oldtemp=%TEMP% -set oldtmp=%TMP% -set oldpath=%PATH% - -set TEMP=%TEMP%\%PACKAGENAME%-%ARCH% -set TMP=%TEMP% -if exist "%TEMP%" rmdir /s /q "%TEMP%" -mkdir "%TEMP%" - -for %%g IN (%GRASS_VERSIONS%) do ( - set path=!path!;%OSGEO4W_ROOT%\apps\grass\%%g\lib - set GISBASE=%OSGEO4W_ROOT%\apps\grass\%%g -) -PATH %path%;%BUILDDIR%\output\plugins -set QT_PLUGIN_PATH=%BUILDDIR%\output\plugins;%OSGEO4W_ROOT%\apps\qt5\plugins - -cmake --build %BUILDDIR% --target Experimental --config %BUILDCONF% -if errorlevel 1 echo TESTS WERE NOT SUCCESSFUL. - -set TEMP=%oldtemp% -set TMP=%oldtmp% -PATH %oldpath% - -:skiptests -if exist ..\skippackage goto end - -if exist "%PKGDIR%" ( - echo REMOVE: %DATE% %TIME% - rmdir /s /q "%PKGDIR%" -) - -echo INSTALL: %DATE% %TIME% -cmake --build %BUILDDIR% --target install --config %BUILDCONF% -if errorlevel 1 (echo INSTALL failed & goto error) - -:package -echo PACKAGE: %DATE% %TIME% - -cd .. -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' postinstall-common.bat >%OSGEO4W_ROOT%\etc\postinstall\%PACKAGENAME%-common.bat -if errorlevel 1 (echo creation of common postinstall failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' postinstall-desktop.bat >%OSGEO4W_ROOT%\etc\postinstall\%PACKAGENAME%.bat -if errorlevel 1 (echo creation of desktop postinstall failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' preremove-desktop.bat >%OSGEO4W_ROOT%\etc\preremove\%PACKAGENAME%.bat -if errorlevel 1 (echo creation of desktop preremove failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' qgis.bat.tmpl >%OSGEO4W_ROOT%\bin\%PACKAGENAME%.bat.tmpl -if errorlevel 1 (echo creation of desktop template failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' designer.bat.tmpl >%OSGEO4W_ROOT%\bin\%PACKAGENAME%-designer.bat.tmpl -if errorlevel 1 (echo creation of designer template failed & goto error) -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' qgis.reg.tmpl >%PKGDIR%\bin\qgis.reg.tmpl -if errorlevel 1 (echo creation of registry template & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' postinstall-server.bat >%OSGEO4W_ROOT%\etc\postinstall\%PACKAGENAME%-server.bat -if errorlevel 1 (echo creation of server postinstall failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' preremove-server.bat >%OSGEO4W_ROOT%\etc\preremove\%PACKAGENAME%-server.bat -if errorlevel 1 (echo creation of server preremove failed & goto error) - -if not exist %OSGEO4W_ROOT%\httpd.d mkdir %OSGEO4W_ROOT%\httpd.d -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' httpd.conf.tmpl >%OSGEO4W_ROOT%\httpd.d\httpd_%PACKAGENAME%.conf.tmpl -if errorlevel 1 (echo creation of httpd.conf template failed & goto error) - -set packages="" "-common" "-server" "-devel" "-oracle-provider" "-grass-plugin-common" - -for %%g IN (%GRASS_VERSIONS%) do ( - for /f "usebackq tokens=1" %%a in (`%%g --config version`) do set gv=%%a - for /F "delims=." %%i in ("!gv!") do set v=%%i - set w=!v! - if !v!==6 set w= - - sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/@grasspath@/%%g/g' -e 's/@grassversion@/!gv!/g' -e 's/@grassmajor@/!v!/g' postinstall-grass.bat >%OSGEO4W_ROOT%\etc\postinstall\%PACKAGENAME%-grass-plugin!w!.bat - if errorlevel 1 (echo creation of grass desktop postinstall failed & goto error) - sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/@grasspath@/%%g/g' -e 's/@grassversion@/!gv!/g' -e 's/@grassmajor@/!v!/g' preremove-grass.bat >%OSGEO4W_ROOT%\etc\preremove\%PACKAGENAME%-grass-plugin!w!.bat - if errorlevel 1 (echo creation of grass desktop preremove failed & goto error) - sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' -e 's/@grasspath@/%%g/g' -e 's/@grassversion@/!gv!/g' -e 's/@grassmajor@/!v!/g' qgis-grass.bat.tmpl >%OSGEO4W_ROOT%\bin\%PACKAGENAME%-grass!v!.bat.tmpl - if errorlevel 1 (echo creation of grass desktop template failed & goto error) - - set packages=!packages! "-grass-plugin!w!" -) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' python.bat.tmpl >%OSGEO4W_ROOT%\bin\python-%PACKAGENAME%.bat.tmpl -if errorlevel 1 (echo creation of python wrapper template failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' process.bat.tmpl >%OSGEO4W_ROOT%\bin\qgis_process-%PACKAGENAME%.bat.tmpl -if errorlevel 1 (echo creation of qgis process wrapper template failed & goto error) - -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' preremove-grass-plugin-common.bat >%OSGEO4W_ROOT%\etc\preremove\%PACKAGENAME%-grass-plugin-common.bat -if errorlevel 1 (echo creation of grass common preremove failed & goto error) -sed -e 's/@package@/%PACKAGENAME%/g' -e 's/@version@/%VERSION%/g' postinstall-grass-plugin-common.bat >%OSGEO4W_ROOT%\etc\postinstall\%PACKAGENAME%-grass-plugin-common.bat -if errorlevel 1 (echo creation of grass common postinstall failed & goto error) - -touch exclude -if exist ..\skipbuild (echo skip build & goto skipbuild) - -for %%i in (%packages%) do ( - if not exist %ARCH%\release\qgis\%PACKAGENAME%%%i mkdir %ARCH%\release\qgis\%PACKAGENAME%%%i -) - -%TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-common/%PACKAGENAME%-common-%VERSION%-%PACKAGE%.tar.bz2 ^ - --exclude-from exclude ^ - --exclude "*.pyc" ^ - "apps/%PACKAGENAME%/bin/qgispython.dll" ^ - "apps/%PACKAGENAME%/bin/qgis_analysis.dll" ^ - "apps/%PACKAGENAME%/bin/qgis_3d.dll" ^ - "apps/%PACKAGENAME%/bin/qgis_core.dll" ^ - "apps/%PACKAGENAME%/bin/qgis_gui.dll" ^ - "apps/%PACKAGENAME%/bin/qgis_native.dll" ^ - "apps/%PACKAGENAME%/bin/qgis_process.exe" ^ - "apps/%PACKAGENAME%/doc/" ^ - "apps/%PACKAGENAME%/plugins/authmethod_basic.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_delimitedtext.dll" ^ - "apps/%PACKAGENAME%/plugins/authmethod_esritoken.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_gpx.dll" ^ - "apps/%PACKAGENAME%/plugins/authmethod_identcert.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_mssql.dll" ^ - "apps/%PACKAGENAME%/plugins/authmethod_pkcs12.dll" ^ - "apps/%PACKAGENAME%/plugins/authmethod_pkipaths.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_postgres.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_postgresraster.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_spatialite.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_virtuallayer.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_wcs.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_wfs.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_wms.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_arcgismapserver.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_arcgisfeatureserver.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_mdal.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_hana.dll" ^ - "apps/%PACKAGENAME%/plugins/authmethod_oauth2.dll" ^ - "apps/%PACKAGENAME%/plugins/authmethod_maptilerhmacsha256.dll" ^ - "apps/%PACKAGENAME%/resources/qgis.db" ^ - "apps/%PACKAGENAME%/resources/spatialite.db" ^ - "apps/%PACKAGENAME%/resources/srs.db" ^ - "apps/%PACKAGENAME%/resources/symbology-style.xml" ^ - "apps/%PACKAGENAME%/resources/cpt-city-qgis-min/" ^ - "apps/%PACKAGENAME%/svg/" ^ - "apps/%PACKAGENAME%/crssync.exe" ^ - "etc/postinstall/%PACKAGENAME%-common.bat" -if errorlevel 1 (echo tar common failed & goto error) - -%TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-server/%PACKAGENAME%-server-%VERSION%-%PACKAGE%.tar.bz2 ^ - --exclude-from exclude ^ - --exclude "*.pyc" ^ - "apps/%PACKAGENAME%/bin/qgis_mapserv.fcgi.exe" ^ - "apps/%PACKAGENAME%/bin/qgis_server.dll" ^ - "apps/%PACKAGENAME%/bin/admin.sld" ^ - "apps/%PACKAGENAME%/bin/wms_metadata.xml" ^ - "apps/%PACKAGENAME%/resources/server/" ^ - "apps/%PACKAGENAME%/server/" ^ - "apps/%PACKAGENAME%/python/qgis/_server.pyd" ^ - "apps/%PACKAGENAME%/python/qgis/_server.pyi" ^ - "apps/%PACKAGENAME%/python/qgis/server/" ^ - "httpd.d/httpd_%PACKAGENAME%.conf.tmpl" ^ - "etc/postinstall/%PACKAGENAME%-server.bat" ^ - "etc/preremove/%PACKAGENAME%-server.bat" -if errorlevel 1 (echo tar server failed & goto error) - -move %PKGDIR%\bin\qgis.exe %OSGEO4W_ROOT%\bin\%PACKAGENAME%-bin.exe -if errorlevel 1 (echo move of desktop executable failed & goto error) -copy qgis.vars %OSGEO4W_ROOT%\bin\%PACKAGENAME%-bin.vars -if errorlevel 1 (echo copy of desktop executable vars failed & goto error) - -if not exist %PKGDIR%\qtplugins\sqldrivers mkdir %PKGDIR%\qtplugins\sqldrivers -move %OSGEO4W_ROOT%\apps\qt5\plugins\sqldrivers\qsqlocispatial.dll %PKGDIR%\qtplugins\sqldrivers -if errorlevel 1 (echo move of oci sqldriver failed & goto error) -move %OSGEO4W_ROOT%\apps\qt5\plugins\sqldrivers\qsqlspatialite.dll %PKGDIR%\qtplugins\sqldrivers -if errorlevel 1 (echo move of spatialite sqldriver failed & goto error) - -if not exist %PKGDIR%\qtplugins\designer mkdir %PKGDIR%\qtplugins\designer -move %OSGEO4W_ROOT%\apps\qt5\plugins\designer\qgis_customwidgets.dll %PKGDIR%\qtplugins\designer -if errorlevel 1 (echo move of customwidgets failed & goto error) - -if not exist %PKGDIR%\python\PyQt5\uic\widget-plugins mkdir %PKGDIR%\python\PyQt5\uic\widget-plugins -move %PYTHONHOME%\Lib\site-packages\PyQt5\uic\widget-plugins\qgis_customwidgets.py %PKGDIR%\python\PyQt5\uic\widget-plugins -if errorlevel 1 (echo move of customwidgets binding failed & goto error) - -for %%i in (dbghelp.dll symsrv.dll) do ( - copy "%DBGHLP_PATH%\%%i" %OSGEO4W_ROOT%\apps\%PACKAGENAME% - if errorlevel 1 (echo %%i not found & goto error) -) - -if not exist %ARCH%\release\qgis\%PACKAGENAME% mkdir %ARCH%\release\qgis\%PACKAGENAME% -%TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%/%PACKAGENAME%-%VERSION%-%PACKAGE%.tar.bz2 ^ - --exclude-from exclude ^ - --exclude "*.pyc" ^ - --exclude "apps/%PACKAGENAME%/python/qgis/_server.pyd" ^ - --exclude "apps/%PACKAGENAME%/python/qgis/_server.pyi" ^ - --exclude "apps/%PACKAGENAME%/python/qgis/_server.lib" ^ - --exclude "apps/%PACKAGENAME%/python/qgis/server" ^ - --exclude "apps/%PACKAGENAME%/server/" ^ - "bin/%PACKAGENAME%-bin.exe" ^ - "bin/%PACKAGENAME%-bin.vars" ^ - "bin/python-%PACKAGENAME%.bat.tmpl" ^ - "bin/qgis_process-%PACKAGENAME%.bat.tmpl" ^ - "apps/%PACKAGENAME%/bin/qgis_app.dll" ^ - "apps/%PACKAGENAME%/bin/qgis.reg.tmpl" ^ - "apps/%PACKAGENAME%/i18n/" ^ - "apps/%PACKAGENAME%/icons/" ^ - "apps/%PACKAGENAME%/images/" ^ - "apps/%PACKAGENAME%/plugins/plugin_offlineediting.dll" ^ - "apps/%PACKAGENAME%/plugins/plugin_topology.dll" ^ - "apps/%PACKAGENAME%/plugins/plugin_geometrychecker.dll" ^ - "apps/%PACKAGENAME%/qtplugins/sqldrivers/qsqlspatialite.dll" ^ - "apps/%PACKAGENAME%/qtplugins/designer/" ^ - "apps/%PACKAGENAME%/python/" ^ - "apps/%PACKAGENAME%/resources/customization.xml" ^ - "apps/%PACKAGENAME%/resources/themes/" ^ - "apps/%PACKAGENAME%/resources/data/" ^ - "apps/%PACKAGENAME%/resources/metadata-ISO/" ^ - "apps/%PACKAGENAME%/resources/opencl_programs/" ^ - "apps/%PACKAGENAME%/resources/palettes/" ^ - "apps/%PACKAGENAME%/resources/2to3migration.txt" ^ - "apps/%PACKAGENAME%/resources/qgis_global_settings.ini" ^ - "apps/%PACKAGENAME%/qgiscrashhandler.exe" ^ - "apps/%PACKAGENAME%/dbghelp.dll" ^ - "apps/%PACKAGENAME%/symsrv.dll" ^ - "bin/%PACKAGENAME%.bat.tmpl" ^ - "bin/%PACKAGENAME%-designer.bat.tmpl" ^ - "etc/postinstall/%PACKAGENAME%.bat" ^ - "etc/preremove/%PACKAGENAME%.bat" -if errorlevel 1 (echo tar desktop failed & goto error) - -if not exist %ARCH%\release\qgis\%PACKAGENAME%-pdb mkdir %ARCH%\release\qgis\%PACKAGENAME%-pdb -%TAR% -C %BUILDDIR% -cjf %ARCH%/release/qgis/%PACKAGENAME%-pdb/%PACKAGENAME%-pdb-%VERSION%-%PACKAGE%.tar.bz2 ^ - apps/%PACKAGENAME%/pdb -if errorlevel 1 (echo tar failed & goto error) - -%TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-grass-plugin-common/%PACKAGENAME%-grass-plugin-common-%VERSION%-%PACKAGE%.tar.bz2 ^ - --exclude-from exclude ^ - --exclude "*.pyc" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.d.rast6.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.d.rast7.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.g.info6.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.g.info7.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.r.in6.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.r.in7.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.v.in6.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/modules/qgis.v.in7.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/bin/qgis.g.browser6.exe" ^ - --exclude "apps/%PACKAGENAME%/grass/bin/qgis.g.browser7.exe" ^ - "apps/%PACKAGENAME%/grass" ^ - "etc/postinstall/%PACKAGENAME%-grass-plugin-common.bat" ^ - "etc/preremove/%PACKAGENAME%-grass-plugin-common.bat" -if errorlevel 1 (echo tar grass-plugin failed & goto error) - -for %%g IN (%GRASS_VERSIONS%) do ( - for /f "usebackq tokens=1" %%a in (`%%g --config version`) do set gv=%%a - for /F "delims=." %%i in ("!gv!") do set v=%%i - set w=!v! - if !v!==6 set w= - - %TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-grass-plugin!w!/%PACKAGENAME%-grass-plugin!w!-%VERSION%-%PACKAGE%.tar.bz2 ^ - "apps/%PACKAGENAME%/bin/qgisgrass!v!.dll" ^ - "apps/%PACKAGENAME%/grass/bin/qgis.g.browser!v!.exe" ^ - "apps/%PACKAGENAME%/grass/modules/qgis.d.rast!v!.exe" ^ - "apps/%PACKAGENAME%/grass/modules/qgis.g.info!v!.exe" ^ - "apps/%PACKAGENAME%/grass/modules/qgis.r.in!v!.exe" ^ - "apps/%PACKAGENAME%/grass/modules/qgis.v.in!v!.exe" ^ - "apps/%PACKAGENAME%/plugins/plugin_grass!v!.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_grass!v!.dll" ^ - "apps/%PACKAGENAME%/plugins/provider_grassraster!v!.dll" ^ - "bin/%PACKAGENAME%-grass!v!.bat.tmpl" ^ - "etc/postinstall/%PACKAGENAME%-grass-plugin!w!.bat" ^ - "etc/preremove/%PACKAGENAME%-grass-plugin!w!.bat" - if errorlevel 1 (echo tar grass-plugin!w! failed & goto error) -) - -%TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-oracle-provider/%PACKAGENAME%-oracle-provider-%VERSION%-%PACKAGE%.tar.bz2 ^ - "apps/%PACKAGENAME%/plugins/oracleprovider.dll" ^ - "apps/%PACKAGENAME%/qtplugins/sqldrivers/qsqlocispatial.dll" -if errorlevel 1 (echo tar oracle-provider failed & goto error) - -%TAR% -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-devel/%PACKAGENAME%-devel-%VERSION%-%PACKAGE%.tar.bz2 ^ - --exclude-from exclude ^ - --exclude "*.pyc" ^ - "apps/%PACKAGENAME%/FindQGIS.cmake" ^ - "apps/%PACKAGENAME%/include/" ^ - "apps/%PACKAGENAME%/lib/" -if errorlevel 1 (echo tar devel failed & goto error) - -goto end - -:usage -echo usage: %0 version package packagename arch [sha [site]] -echo sample: %0 2.0.1 3 qgis x86 f802808 -exit /b 1 - -:error -echo BUILD ERROR %ERRORLEVEL%: %DATE% %TIME% -for %%i in (%packages%) do ( - if exist %ARCH%\release\qgis\%PACKAGENAME%%%i\%PACKAGENAME%%%i-%VERSION%-%PACKAGE%.tar.bz2 del %ARCH%\release\qgis\%PACKAGENAME%%%i\%PACKAGENAME%%%i-%VERSION%-%PACKAGE%.tar.bz2 -) -exit /b 1 - -:end -echo FINISHED: %DATE% %TIME% - -endlocal diff --git a/ms-windows/osgeo4w/postinstall-common.bat b/ms-windows/osgeo4w/postinstall-common.bat deleted file mode 100644 index 4cbee4a1bf3e7..0000000000000 --- a/ms-windows/osgeo4w/postinstall-common.bat +++ /dev/null @@ -1,5 +0,0 @@ -call "%OSGEO4W_ROOT%\bin\o4w_env.bat" -call qt5_env.bat -path %PATH%;%OSGEO4W_ROOT%\apps\@package@\bin -set QGIS_PREFIX_PATH=%OSGEO4W_ROOT:\=/%/apps/@package@ -"%OSGEO4W_ROOT%\apps\@package@\crssync" diff --git a/ms-windows/osgeo4w/postinstall-desktop.bat b/ms-windows/osgeo4w/postinstall-desktop.bat deleted file mode 100644 index 392aeccfde0a5..0000000000000 --- a/ms-windows/osgeo4w/postinstall-desktop.bat +++ /dev/null @@ -1,25 +0,0 @@ -textreplace -std -t bin\@package@.bat -textreplace -std -t bin\@package@-designer.bat -textreplace -std -t bin\python-@package@.bat -textreplace -std -t bin\qgis_process-@package@.bat - -REM get short path without blanks -for %%i in ("%OSGEO4W_ROOT%") do set O4W_ROOT=%%~fsi -if "%OSGEO4W_DESKTOP%"=="" set OSGEO4W_DESKTOP=~$folder.common_desktop$ - -call "%OSGEO4W_ROOT%\bin\@package@.bat" --postinstall - -if not %OSGEO4W_MENU_LINKS%==0 mkdir "%OSGEO4W_STARTMENU%" -if not %OSGEO4W_DESKTOP_LINKS%==0 mkdir "%OSGEO4W_DESKTOP%" - -if not %OSGEO4W_MENU_LINKS%==0 nircmd shortcut "%O4W_ROOT%\bin\@package@-bin.exe" "%OSGEO4W_STARTMENU%" "QGIS Desktop @version@" "" "" "" "" "~$folder.mydocuments$" -if not %OSGEO4W_DESKTOP_LINKS%==0 nircmd shortcut "%O4W_ROOT%\bin\@package@-bin.exe" "%OSGEO4W_DESKTOP%" "QGIS Desktop @version@" "" "" "" "" "~$folder.mydocuments$" - -if not %OSGEO4W_MENU_LINKS%==0 nircmd shortcut "%O4W_ROOT%\bin\nircmd.exe" "%OSGEO4W_STARTMENU%" "Qt Designer with QGIS @version@ custom widgets" "exec hide """%OSGEO4W_ROOT%\bin\@package@-designer.bat"" "%O4W_ROOT%\apps\@package@\icons\QGIS.ico" "" "" "~$folder.mydocuments$" -if not %OSGEO4W_DESKTOP_LINKS%==0 nircmd shortcut "%O4W_ROOT%\bin\nircmd.exe" "%OSGEO4W_DESKTOP%" "Qt Designer with QGIS @version@ custom widgets" "exec hide %O4W_ROOT%\bin\@package@-designer.bat" "%O4W_ROOT%\apps\@package@\icons\QGIS.ico" "" "" "~$folder.mydocuments$" - -set OSGEO4W_ROOT=%OSGEO4W_ROOT:\=\\% -textreplace -std -t "%O4W_ROOT%\apps\@package@\bin\qgis.reg" -nircmd elevate "%WINDIR%\regedit" /s "%O4W_ROOT%\apps\@package@\bin\qgis.reg" -del /s /q "%OSGEO4W_ROOT%\apps\@package@\python\*.pyc" -exit /b 0 diff --git a/ms-windows/osgeo4w/postinstall-dev.bat b/ms-windows/osgeo4w/postinstall-dev.bat deleted file mode 100644 index 870873951314e..0000000000000 --- a/ms-windows/osgeo4w/postinstall-dev.bat +++ /dev/null @@ -1,55 +0,0 @@ -setlocal enabledelayedexpansion - -textreplace -std -t bin\@package@.bat -textreplace -std -t bin\@package@-designer.bat -textreplace -std -t bin\python-@package@.bat -textreplace -std -t bin\qgis_process-@package@.bat - -if "%OSGEO4W_DESKTOP%"=="" set OSGEO4W_DESKTOP=~$folder.common_desktop$ - -if not %OSGEO4W_MENU_LINKS%==0 mkdir "%OSGEO4W_STARTMENU%" -if not %OSGEO4W_DESKTOP_LINKS%==0 mkdir "%OSGEO4W_DESKTOP%" - -call "%OSGEO4W_ROOT%\bin\@package@.bat" --postinstall - -if not %OSGEO4W_MENU_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\@package@-bin.exe" "%OSGEO4W_STARTMENU%" "QGIS Desktop @version@" "" "" "" "" "~$folder.mydocuments$" -if not %OSGEO4W_DESKTOP_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\@package@-bin.exe" "%OSGEO4W_DESKTOP%" "QGIS Desktop @version@" "" "" "" "" "~$folder.mydocuments$" - -for %%g in (@grassversions@) do ( - set gv= - for /f "usebackq tokens=1" %%a in (`%%g --config version`) do set gv=%%a - if not "!gv!"=="" ( - for /F "delims=." %%i in ("!gv!") do set v=%%i - - copy "%OSGEO4W_ROOT%\bin\@package@-bin.exe" "%OSGEO4W_ROOT%\bin\@package@-bin-g!v!.exe" - copy "%OSGEO4W_ROOT%\bin\@package@-bin.vars" "%OSGEO4W_ROOT%\bin\@package@-bin-g!v!.vars" - textreplace -std -map @grassmajor@ !v! -t bin\@package@-g!v!.bat - call "%OSGEO4W_ROOT%\bin\@package@-g!v!.bat" --postinstall - - if not %OSGEO4W_MENU_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\@package@-bin-g!v!.exe" "%OSGEO4W_STARTMENU%" "QGIS Desktop @version@ with GRASS !gv! (Nightly)" "" "" "" "" "~$folder.mydocuments$" - if not %OSGEO4W_DESKTOP_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\@package@-bin-g!v!.exe" "%OSGEO4W_DESKTOP%" "QGIS Desktop @version@ with GRASS !gv! (Nightly)" "" "" "" "" "~$folder.mydocuments$" - ) -) - -if not %OSGEO4W_MENU_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\nircmd.exe" "%OSGEO4W_STARTMENU%" "Qt Designer with QGIS @version@ custom widgets (Nightly)" "exec hide """%OSGEO4W_ROOT%\bin\@package@-designer.bat"" "%OSGEO4W_ROOT%\apps\@package@\icons\QGIS.ico" "" "" "~$folder.mydocuments$" -if not %OSGEO4W_DESKTOP_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\nircmd.exe" "%OSGEO4W_DESKTOP%" "Qt Designer with QGIS @version@ custom widgets (Nightly)" "exec hide """%OSGEO4W_ROOT%\bin\@package@-designer.bat"" "%OSGEO4W_ROOT%\apps\@package@\icons\QGIS.ico" "" "" "~$folder.mydocuments$" - -set O4W_ROOT=%OSGEO4W_ROOT% -set OSGEO4W_ROOT=%OSGEO4W_ROOT:\=\\% -textreplace -std -t "%O4W_ROOT%\apps\@package@\bin\qgis.reg" -set OSGEO4W_ROOT=%O4W_ROOT% - -REM Do not register extensions if release is installed -if not exist "%O4W_ROOT%\apps\qgis\bin\qgis.reg" nircmd elevate "%WINDIR%\regedit" /s "%O4W_ROOT%\apps\@package@\bin\qgis.reg" - -call "%OSGEO4W_ROOT%\bin\o4w_env.bat" -call qt5_env.bat -call gdal-dev-env.bat -path %PATH%;%OSGEO4W_ROOT%\apps\@package@\bin -set QGIS_PREFIX_PATH=%OSGEO4W_ROOT:\=/%/apps/@package@ -"%OSGEO4W_ROOT%\apps\@package@\crssync" - -del /s /q "%OSGEO4W_ROOT%\apps\@package@\*.pyc" -exit /b 0 - -endlocal diff --git a/ms-windows/osgeo4w/postinstall-grass-plugin-common.bat b/ms-windows/osgeo4w/postinstall-grass-plugin-common.bat deleted file mode 100644 index 892c9d81cc324..0000000000000 --- a/ms-windows/osgeo4w/postinstall-grass-plugin-common.bat +++ /dev/null @@ -1,2 +0,0 @@ -del /s /q "%OSGEO4W_ROOT%\apps\@package@\grass\*.pyc" -exit /b 0 diff --git a/ms-windows/osgeo4w/postinstall-grass.bat b/ms-windows/osgeo4w/postinstall-grass.bat deleted file mode 100644 index b851c2cde834e..0000000000000 --- a/ms-windows/osgeo4w/postinstall-grass.bat +++ /dev/null @@ -1,13 +0,0 @@ -textreplace -std -t bin\@package@-grass@grassmajor@.bat - -if "%OSGEO4W_DESKTOP%"=="" set OSGEO4W_DESKTOP=~$folder.common_desktop$ - -copy bin\@package@-bin.exe bin\@package@-bin-g@grassmajor@.exe -copy bin\@package@-bin.vars bin\@package@-bin-g@grassmajor@.vars -call "%OSGEO4W_ROOT%\bin\@package@-grass@grassmajor@.bat" --postinstall - -if not %OSGEO4W_MENU_LINKS%==0 mkdir "%OSGEO4W_STARTMENU%" -if not %OSGEO4W_DESKTOP_LINKS%==0 mkdir "%OSGEO4W_DESKTOP%" - -if not %OSGEO4W_MENU_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\@package@-bin-g@grassmajor@.exe" "%OSGEO4W_STARTMENU%" "QGIS Desktop @version@ with GRASS @grassversion@" "" "" "" "" "~$folder.mydocuments$" -if not %OSGEO4W_DESKTOP_LINKS%==0 nircmd shortcut "%OSGEO4W_ROOT%\bin\@package@-bin-g@grassmajor@.exe" "%OSGEO4W_DESKTOP%" "QGIS Desktop @version@ with GRASS @grassversion@" "" "" "" "" "~$folder.mydocuments$" diff --git a/ms-windows/osgeo4w/postinstall-server.bat b/ms-windows/osgeo4w/postinstall-server.bat deleted file mode 100644 index db70852841684..0000000000000 --- a/ms-windows/osgeo4w/postinstall-server.bat +++ /dev/null @@ -1,5 +0,0 @@ -textreplace -std ^ - -map @windir@ "%WINDIR%" ^ - -map @temp@ "%TEMP%" ^ - -t httpd.d\httpd_@package@.conf -del httpd.d\httpd_@package@.conf.tmpl diff --git a/ms-windows/osgeo4w/preremove-desktop.bat b/ms-windows/osgeo4w/preremove-desktop.bat deleted file mode 100644 index 242a205480ca8..0000000000000 --- a/ms-windows/osgeo4w/preremove-desktop.bat +++ /dev/null @@ -1,15 +0,0 @@ -del "%OSGEO4W_STARTMENU%\QGIS Desktop @version@.lnk" -del "%OSGEO4W_STARTMENU%\QGIS Browser @version@.lnk" -del "%OSGEO4W_STARTMENU%\Qt Designer with QGIS @version@ custom widgets.lnk" -rmdir "%OSGEO4W_STARTMENU%" -del "%OSGEO4W_DESKTOP%\QGIS Desktop @version@.lnk" -del "%OSGEO4W_DESKTOP%\QGIS Browser @version@.lnk" -del "%OSGEO4W_DESKTOP%\Qt Designer with QGIS @version@ custom widgets.lnk" -rmdir "%OSGEO4W_DESKTOP%" -del "%OSGEO4W_ROOT%\bin\@package@.bat" -del "%OSGEO4W_ROOT%\bin\@package@-bin.vars" -del "%OSGEO4W_ROOT%\bin\@package@-bin.env" -del "%OSGEO4W_ROOT%\bin\@package@-designer.bat" -del "%OSGEO4W_ROOT%\apps\@package@\python\qgis\qgisconfig.py" -del "%OSGEO4W_ROOT%\apps\@package@\bin\qgis.reg" -del /s /q "%OSGEO4W_ROOT%\apps\@package@\python\*.pyc" diff --git a/ms-windows/osgeo4w/preremove-dev.bat b/ms-windows/osgeo4w/preremove-dev.bat deleted file mode 100644 index ae7532026c20c..0000000000000 --- a/ms-windows/osgeo4w/preremove-dev.bat +++ /dev/null @@ -1,30 +0,0 @@ -setlocal enabledelayedexpansion - -for %%g in (@grassversions@) do ( - for /f "usebackq tokens=1" %%a in (`%%g --config version`) do set gv=%%a - for /F "delims=." %%i in ("!gv!") do set v=%%i - - del "%OSGEO4W_STARTMENU%\QGIS Desktop @version@ with GRASS !gv! (Nightly).lnk" - del "%OSGEO4W_STARTMENU%\QGIS Browser @version@ with GRASS !gv! (Nightly).lnk" - del "%OSGEO4W_DESKTOP%\QGIS Desktop @version@ with GRASS !gv! (Nightly).lnk" - del "%OSGEO4W_DESKTOP%\QGIS Browser @version@ with GRASS !gv! (Nightly).lnk" - del "%OSGEO4W_ROOT%\bin\@package@-g!v!.bat" - del "%OSGEO4W_ROOT%\bin\@package@-bin-g!v!.exe" - del "%OSGEO4W_ROOT%\bin\@package@-bin-g!v!.env" - del "%OSGEO4W_ROOT%\bin\@package@-bin-g!v!.vars" -) - -del "%OSGEO4W_STARTMENU%\Qt Designer with QGIS @version@ custom widgets (Nightly).lnk" -rmdir "%OSGEO4W_STARTMENU%" -del "%OSGEO4W_DESKTOP%\Qt Designer with QGIS @version@ custom widgets (Nightly).lnk" -rmdir "%OSGEO4W_DESKTOP%" - -del "%OSGEO4W_ROOT%\bin\@package@-bin.env" -del "%OSGEO4W_ROOT%\bin\@package@-designer.bat" -del "%OSGEO4W_ROOT%\bin\python-@package@.bat" -del "%OSGEO4W_ROOT%\bin\qgis_process-@package@.bat" -del "%OSGEO4W_ROOT%\apps\@package@\python\qgis\qgisconfig.py" -del "%OSGEO4W_ROOT%\apps\@package@\bin\qgis.reg" -del /s /q "%OSGEO4W_ROOT%\apps\@package@\*.pyc" - -endlocal diff --git a/ms-windows/osgeo4w/preremove-grass-plugin-common.bat b/ms-windows/osgeo4w/preremove-grass-plugin-common.bat deleted file mode 100644 index 59ae472ac9642..0000000000000 --- a/ms-windows/osgeo4w/preremove-grass-plugin-common.bat +++ /dev/null @@ -1 +0,0 @@ -del /s /q "%OSGEO4W_ROOT%\apps\@package@\grass\*.pyc" diff --git a/ms-windows/osgeo4w/preremove-grass.bat b/ms-windows/osgeo4w/preremove-grass.bat deleted file mode 100644 index 1b2c2abc33c40..0000000000000 --- a/ms-windows/osgeo4w/preremove-grass.bat +++ /dev/null @@ -1,10 +0,0 @@ -del "%OSGEO4W_STARTMENU%\QGIS Desktop @version@ with GRASS @grassversion@.lnk" -del "%OSGEO4W_STARTMENU%\QGIS Browser @version@ with GRASS @grassversion@.lnk" -rmdir "%OSGEO4W_STARTMENU%" -del "%OSGEO4W_DESKTOP%\QGIS Desktop @version@ with GRASS @grassversion@.lnk" -del "%OSGEO4W_DESKTOP%\QGIS Browser @version@ with GRASS @grassversion@.lnk" -rmdir "%OSGEO4W_DESKTOP%" -del "%OSGEO4W_ROOT%\bin\@package@-grass@grassmajor@.bat" -del "%OSGEO4W_ROOT%\bin\@package@-bin-g@grassmajor@.exe" -del "%OSGEO4W_ROOT%\bin\@package@-bin-g@grassmajor@.vars" -del "%OSGEO4W_ROOT%\bin\@package@-bin-g@grassmajor@.env" diff --git a/ms-windows/osgeo4w/preremove-server.bat b/ms-windows/osgeo4w/preremove-server.bat deleted file mode 100644 index 91f3f99f79fa8..0000000000000 --- a/ms-windows/osgeo4w/preremove-server.bat +++ /dev/null @@ -1 +0,0 @@ -del "%OSGEO4W_ROOT%\httpd.d\httpd_@package@.conf" diff --git a/ms-windows/osgeo4w/process.bat.tmpl b/ms-windows/osgeo4w/process.bat.tmpl deleted file mode 100644 index 87746d50bdc43..0000000000000 --- a/ms-windows/osgeo4w/process.bat.tmpl +++ /dev/null @@ -1,14 +0,0 @@ -@echo off -call "%~dp0\o4w_env.bat" -call qt5_env.bat -call py3_env.bat -@echo off -path %OSGEO4W_ROOT%\apps\@package@\bin;%PATH% -set QGIS_PREFIX_PATH=%OSGEO4W_ROOT:\=/%/apps/@package@ -set GDAL_FILENAME_IS_UTF8=YES -rem Set VSI cache to be used as buffer, see #6448 -set VSI_CACHE=TRUE -set VSI_CACHE_SIZE=1000000 -set QT_PLUGIN_PATH=%OSGEO4W_ROOT%\apps\@package@\qtplugins;%OSGEO4W_ROOT%\apps\qt5\plugins -set PYTHONPATH=%OSGEO4W_ROOT%\apps\@package@\python;%PYTHONPATH% -"%OSGEO4W_ROOT%\apps\@package@\bin\qgis_process.exe" %* diff --git a/ms-windows/osgeo4w/python.bat.tmpl b/ms-windows/osgeo4w/python.bat.tmpl deleted file mode 100644 index 3282dcd50e5df..0000000000000 --- a/ms-windows/osgeo4w/python.bat.tmpl +++ /dev/null @@ -1,14 +0,0 @@ -@echo off -call "%~dp0\o4w_env.bat" -call qt5_env.bat -call py3_env.bat -@echo off -path %OSGEO4W_ROOT%\apps\@package@\bin;%PATH% -set QGIS_PREFIX_PATH=%OSGEO4W_ROOT:\=/%/apps/@package@ -set GDAL_FILENAME_IS_UTF8=YES -rem Set VSI cache to be used as buffer, see #6448 -set VSI_CACHE=TRUE -set VSI_CACHE_SIZE=1000000 -set QT_PLUGIN_PATH=%OSGEO4W_ROOT%\apps\@package@\qtplugins;%OSGEO4W_ROOT%\apps\qt5\plugins -set PYTHONPATH=%OSGEO4W_ROOT%\apps\@package@\python;%PYTHONPATH% -"%PYTHONHOME%\python" %* diff --git a/ms-windows/osgeo4w/qgis-grass.bat.tmpl b/ms-windows/osgeo4w/qgis-grass.bat.tmpl deleted file mode 100644 index 4d45dca5a881f..0000000000000 --- a/ms-windows/osgeo4w/qgis-grass.bat.tmpl +++ /dev/null @@ -1,15 +0,0 @@ -@echo off -call "%~dp0\o4w_env.bat" -call qt5_env.bat -call py3_env.bat -set savedpath=%PATH% -call "%OSGEO4W_ROOT%\apps\grass\@grasspath@\etc\env.bat" -@echo off -path %OSGEO4W_ROOT%\apps\@package@\bin;%OSGEO4W_ROOT%\apps\grass\@grasspath@\lib;%OSGEO4W_ROOT%\apps\grass\@grasspath@\bin;%savedpath% -set QGIS_PREFIX_PATH=%OSGEO4W_ROOT:\=/%/apps/@package@ -set GDAL_FILENAME_IS_UTF8=YES -rem Set VSI cache to be used as buffer, see #6448 -set VSI_CACHE=TRUE -set VSI_CACHE_SIZE=1000000 -set QT_PLUGIN_PATH=%OSGEO4W_ROOT%\apps\@package@\qtplugins;%OSGEO4W_ROOT%\apps\qt5\plugins -start "QGIS" /B "%OSGEO4W_ROOT%\bin\@package@-bin-g@grassmajor@.exe" %* diff --git a/ms-windows/osgeo4w/qgis.bat.tmpl b/ms-windows/osgeo4w/qgis.bat.tmpl deleted file mode 100644 index e3c09c80e4b3b..0000000000000 --- a/ms-windows/osgeo4w/qgis.bat.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -@echo off -call "%~dp0\o4w_env.bat" -call qt5_env.bat -call py3_env.bat -@echo off -path %OSGEO4W_ROOT%\apps\@package@\bin;%PATH% -set QGIS_PREFIX_PATH=%OSGEO4W_ROOT:\=/%/apps/@package@ -set GDAL_FILENAME_IS_UTF8=YES -rem Set VSI cache to be used as buffer, see #6448 -set VSI_CACHE=TRUE -set VSI_CACHE_SIZE=1000000 -set QT_PLUGIN_PATH=%OSGEO4W_ROOT%\apps\@package@\qtplugins;%OSGEO4W_ROOT%\apps\qt5\plugins -start "QGIS" /B "%OSGEO4W_ROOT%\bin\@package@-bin.exe" %* diff --git a/ms-windows/osgeo4w/qgis.reg.tmpl b/ms-windows/osgeo4w/qgis.reg.tmpl deleted file mode 100644 index 9bbd6b5afc2d6..0000000000000 --- a/ms-windows/osgeo4w/qgis.reg.tmpl +++ /dev/null @@ -1,48 +0,0 @@ -Windows Registry Editor Version 5.00 - -[HKEY_CLASSES_ROOT\QGIS Project] -@="QGIS Project" - -[HKEY_CLASSES_ROOT\QGIS Project\DefaultIcon] -@="@osgeo4w@\\apps\\@package@\\icons\\qgis-qgs.ico" - -[HKEY_CLASSES_ROOT\QGIS Project\Shell] - -[HKEY_CLASSES_ROOT\QGIS Project\Shell\open] -@="" - -[HKEY_CLASSES_ROOT\QGIS Project\Shell\open\command] -@="\"@osgeo4w@\\bin\\@package@.bat\" \"%1\"" - -[HKEY_CLASSES_ROOT\.qgs] -@="QGIS Project" - -[HKEY_CLASSES_ROOT\.qgz] -@="QGIS Project" - -[HKEY_CLASSES_ROOT\QGIS Composer Template] -@="QGIS Composer Template" - -[HKEY_CLASSES_ROOT\QGIS Composer Template\DefaultIcon] -@="@osgeo4w@\\apps\\@package@\\icons\\qgis-qpt.ico" - -[HKEY_CLASSES_ROOT\.qpt] -@="QGIS Composer Template" - -[HKEY_CLASSES_ROOT\QGIS Layer Settings] -@="QGIS Layer Settings" - -[HKEY_CLASSES_ROOT\QGIS Layer Settings\DefaultIcon] -@="@osgeo4w@\\apps\\@package@\\icons\\qgis-qml.ico" - -[HKEY_CLASSES_ROOT\.qml] -@="QGIS Layer Settings" - -[HKEY_CLASSES_ROOT\QGIS Layer Definition] -@="QGIS Layer Definition" - -[HKEY_CLASSES_ROOT\QGIS Layer Definition\DefaultIcon] -@="@osgeo4w@\\apps\\@package@\\icons\\qgis-qlr.ico" - -[HKEY_CLASSES_ROOT\.qlr] -@="QGIS Layer Definition" diff --git a/ms-windows/osgeo4w/qgis.vars b/ms-windows/osgeo4w/qgis.vars deleted file mode 100644 index 2fa37cfef9cb1..0000000000000 --- a/ms-windows/osgeo4w/qgis.vars +++ /dev/null @@ -1,30 +0,0 @@ -PATH -GDAL_DATA -GDAL_DRIVER_PATH -GDAL_FILENAME_IS_UTF8 -GEOTIFF_CSV -GISBASE -GRASS_HTML_BROWSER -GRASS_PROJSHARE -GRASS_PYTHON -GRASS_SH -GRASS_WISH -JPEGMEM -NLS_LANG -OSGEO4W_ROOT -PROJ_LIB -PYTHONHOME -PYTHONPATH -QGIS_PREFIX_PATH -QT_PLUGIN_PATH -QT_RASTER_CLIP_LIMIT -VSI_CACHE -VSI_CACHE_SIZE -O4W_QT_PREFIX -O4W_QT_BINARIES -O4W_QT_PLUGINS -O4W_QT_LIBRARIES -O4W_QT_TRANSLATIONS -O4W_QT_HEADERS -PGEO_DRIVER_TEMPLATE -OGR_SKIP diff --git a/ms-windows/osgeo4w/runasadmin.ps1 b/ms-windows/osgeo4w/runasadmin.ps1 deleted file mode 100644 index ab2b4fca35a08..0000000000000 --- a/ms-windows/osgeo4w/runasadmin.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -#-RunAsAdministrator -Write-Output ($args -join ' ') -$cmd, $args = $args -Start-Process $cmd -Wait -ArgumentList $args -NoNewWindow diff --git a/ms-windows/plugins.nsh b/ms-windows/plugins.nsh deleted file mode 100644 index 335ed71ba0e20..0000000000000 --- a/ms-windows/plugins.nsh +++ /dev/null @@ -1,14 +0,0 @@ -############################### reg2nsis begin ################################# -# This NSIS-script was generated by the Reg2Nsis utility # -# Author : Artem Zankovich # -# URL : http://aarrtteemm.nm.ru # -# Usage : You can freely inserts this into your setup script as inline text # -# or include file with the help of !include directive. # -# Please don't remove this header. # -################################################################################ - -WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "grassplugin" "true" -WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "offlineeditingplugin" "true" -WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "topolplugin" "true" - -############################### reg2nsis end ################################# diff --git a/ms-windows/python_plugins.nsh b/ms-windows/python_plugins.nsh deleted file mode 100644 index 7a86b37ec826d..0000000000000 --- a/ms-windows/python_plugins.nsh +++ /dev/null @@ -1,14 +0,0 @@ -############################### reg2nsis begin ################################# -# This NSIS-script was generated by the Reg2Nsis utility # -# Author : Artem Zankovich # -# URL : http://aarrtteemm.nm.ru # -# Usage : You can freely inserts this into your setup script as inline text # -# or include file with the help of !include directive. # -# Please don't remove this header. # -################################################################################ - -WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\PythonPlugins" "GdalTools" "true" -WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\PythonPlugins" "db_manager" "true" -WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\PythonPlugins" "processing" "true" - -############################### reg2nsis end ################################# diff --git a/ms-windows/quickpackage.sh b/ms-windows/quickpackage.sh deleted file mode 100755 index a146c1fa12ab1..0000000000000 --- a/ms-windows/quickpackage.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -########################################################################### -# quickpackage.sh -# --------------------- -# Date : November 2010 -# Copyright : (C) 2010 by Tim Sutton -# Email : tim 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. # -# # -########################################################################### - - -# This script is just for if you want to run the nsis (under linux) part -# of the package building process. Typically you should use -# -# osgeo4w/creatensis.pl -# -# rather to do the complete package build process. However running this -# script can be useful if you have manually tweaked the package contents -# under osgeo4w/unpacked and want to create a new package based on that. -# -# Tim Sutton November 2010 - -makensis \ --DVERSION_NUMBER='1.7.0' \ --DVERSION_NAME='Wroclaw' \ --DSVN_REVISION='0' \ --DQGIS_BASE='QGIS' \ --DINSTALLER_NAME='QGIS-1-7-0-Setup.exe' \ --DDISPLAYED_NAME='QGIS 1.7.0' \ --DBINARY_REVISION=1 \ --DINSTALLER_TYPE=OSGeo4W \ --DPACKAGE_FOLDER=osgeo4w/unpacked \ --DSHORTNAME=qgis \ -QGIS-Installer.nsi diff --git a/ms-windows/x64.nsh b/ms-windows/x64.nsh deleted file mode 100644 index e694c1e613693..0000000000000 --- a/ms-windows/x64.nsh +++ /dev/null @@ -1,54 +0,0 @@ -; --------------------- -; x64.nsh -; --------------------- -; -; A few simple macros to handle installations on x64 machines. -; -; RunningX64 checks if the installer is running on x64. -; -; ${If} ${RunningX64} -; MessageBox MB_OK "running on x64" -; ${EndIf} -; -; DisableX64FSRedirection disables file system redirection. -; EnableX64FSRedirection enables file system redirection. -; -; SetOutPath $SYSDIR -; ${DisableX64FSRedirection} -; File some.dll # extracts to C:\Windows\System32 -; ${EnableX64FSRedirection} -; File some.dll # extracts to C:\Windows\SysWOW64 -; - -!ifndef ___X64__NSH___ -!define ___X64__NSH___ - -!include LogicLib.nsh - -!macro _RunningX64 _a _b _t _f - !insertmacro _LOGICLIB_TEMP - System::Call kernel32::GetCurrentProcess()i.s - System::Call kernel32::IsWow64Process(is,*i.s) - Pop $_LOGICLIB_TEMP - !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}` -!macroend - -!define RunningX64 `"" RunningX64 ""` - -!macro DisableX64FSRedirection - - System::Call kernel32::Wow64EnableWow64FsRedirection(i0) - -!macroend - -!define DisableX64FSRedirection "!insertmacro DisableX64FSRedirection" - -!macro EnableX64FSRedirection - - System::Call kernel32::Wow64EnableWow64FsRedirection(i1) - -!macroend - -!define EnableX64FSRedirection "!insertmacro EnableX64FSRedirection" - -!endif # !___X64__NSH___ diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index facf62b02b0e5..e7b740ea8c191 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -18,10 +18,6 @@ class Qgs3DMapScene : QObject { %Docstring(signature="appended") Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children. - -.. note:: - - Not available in Python bindings %End %TypeHeaderCode diff --git a/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in b/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in index 435229d610ab0..53540699166bf 100644 --- a/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in @@ -18,10 +18,6 @@ class Qgs3DMapScene : QObject { %Docstring(signature="appended") Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children. - -.. note:: - - Not available in Python bindings %End %TypeHeaderCode diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index 704615038b041..5d10aa2e64fb1 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -1040,7 +1040,10 @@ QgsPalLayerSettings.BottomRight = Qgis.LabelPredefinedPointPosition.BottomRight QgsPalLayerSettings.BottomRight.is_monkey_patched = True QgsPalLayerSettings.BottomRight.__doc__ = "Label on bottom right of point" -Qgis.LabelPredefinedPointPosition.__doc__ = "Positions for labels when using the Qgis.LabelPlacement.OrderedPositionsAroundPoint placement mode.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsPalLayerSettings`.PredefinedPointPosition\n\n.. versionadded:: 3.26\n\n" + '* ``TopLeft``: ' + Qgis.LabelPredefinedPointPosition.TopLeft.__doc__ + '\n' + '* ``TopSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyLeft.__doc__ + '\n' + '* ``TopMiddle``: ' + Qgis.LabelPredefinedPointPosition.TopMiddle.__doc__ + '\n' + '* ``TopSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyRight.__doc__ + '\n' + '* ``TopRight``: ' + Qgis.LabelPredefinedPointPosition.TopRight.__doc__ + '\n' + '* ``MiddleLeft``: ' + Qgis.LabelPredefinedPointPosition.MiddleLeft.__doc__ + '\n' + '* ``MiddleRight``: ' + Qgis.LabelPredefinedPointPosition.MiddleRight.__doc__ + '\n' + '* ``BottomLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomLeft.__doc__ + '\n' + '* ``BottomSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyLeft.__doc__ + '\n' + '* ``BottomMiddle``: ' + Qgis.LabelPredefinedPointPosition.BottomMiddle.__doc__ + '\n' + '* ``BottomSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyRight.__doc__ + '\n' + '* ``BottomRight``: ' + Qgis.LabelPredefinedPointPosition.BottomRight.__doc__ +QgsPalLayerSettings.OverPoint = Qgis.LabelPredefinedPointPosition.OverPoint +QgsPalLayerSettings.OverPoint.is_monkey_patched = True +QgsPalLayerSettings.OverPoint.__doc__ = "Label directly centered over point (since QGIS 3.38)" +Qgis.LabelPredefinedPointPosition.__doc__ = "Positions for labels when using the Qgis.LabelPlacement.OrderedPositionsAroundPoint placement mode.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsPalLayerSettings`.PredefinedPointPosition\n\n.. versionadded:: 3.26\n\n" + '* ``TopLeft``: ' + Qgis.LabelPredefinedPointPosition.TopLeft.__doc__ + '\n' + '* ``TopSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyLeft.__doc__ + '\n' + '* ``TopMiddle``: ' + Qgis.LabelPredefinedPointPosition.TopMiddle.__doc__ + '\n' + '* ``TopSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyRight.__doc__ + '\n' + '* ``TopRight``: ' + Qgis.LabelPredefinedPointPosition.TopRight.__doc__ + '\n' + '* ``MiddleLeft``: ' + Qgis.LabelPredefinedPointPosition.MiddleLeft.__doc__ + '\n' + '* ``MiddleRight``: ' + Qgis.LabelPredefinedPointPosition.MiddleRight.__doc__ + '\n' + '* ``BottomLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomLeft.__doc__ + '\n' + '* ``BottomSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyLeft.__doc__ + '\n' + '* ``BottomMiddle``: ' + Qgis.LabelPredefinedPointPosition.BottomMiddle.__doc__ + '\n' + '* ``BottomSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyRight.__doc__ + '\n' + '* ``BottomRight``: ' + Qgis.LabelPredefinedPointPosition.BottomRight.__doc__ + '\n' + '* ``OverPoint``: ' + Qgis.LabelPredefinedPointPosition.OverPoint.__doc__ # -- Qgis.LabelPredefinedPointPosition.baseClass = Qgis QgsPalLayerSettings.OffsetType = Qgis.LabelOffsetType @@ -1548,6 +1551,14 @@ # -- Qgis.GpsQualityIndicator.baseClass = Qgis # monkey patching scoped based enum +Qgis.GpsNavigationStatus.NotValid.__doc__ = "Navigation status not valid" +Qgis.GpsNavigationStatus.Safe.__doc__ = "Safe" +Qgis.GpsNavigationStatus.Caution.__doc__ = "Caution" +Qgis.GpsNavigationStatus.Unsafe.__doc__ = "Unsafe" +Qgis.GpsNavigationStatus.__doc__ = "GPS navigation status.\n\n.. versionadded:: 3.38\n\n" + '* ``NotValid``: ' + Qgis.GpsNavigationStatus.NotValid.__doc__ + '\n' + '* ``Safe``: ' + Qgis.GpsNavigationStatus.Safe.__doc__ + '\n' + '* ``Caution``: ' + Qgis.GpsNavigationStatus.Caution.__doc__ + '\n' + '* ``Unsafe``: ' + Qgis.GpsNavigationStatus.Unsafe.__doc__ +# -- +Qgis.GpsNavigationStatus.baseClass = Qgis +# monkey patching scoped based enum Qgis.GpsInformationComponent.Location.__doc__ = "2D location (latitude/longitude), as a QgsPointXY value" Qgis.GpsInformationComponent.Altitude.__doc__ = "Altitude/elevation above or below the mean sea level" Qgis.GpsInformationComponent.GroundSpeed.__doc__ = "Ground speed" @@ -3215,6 +3226,13 @@ # -- Qgis.FieldDomainMergePolicy.baseClass = Qgis # monkey patching scoped based enum +Qgis.FieldDuplicatePolicy.DefaultValue.__doc__ = "Use default field value" +Qgis.FieldDuplicatePolicy.Duplicate.__doc__ = "Duplicate original value" +Qgis.FieldDuplicatePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic (since QGIS 3.30)" +Qgis.FieldDuplicatePolicy.__doc__ = "Duplicate policy for fields.\n\nWhen a feature is duplicated, defines how the value of attributes are computed.\n\n.. versionadded:: 3.38\n\n" + '* ``DefaultValue``: ' + Qgis.FieldDuplicatePolicy.DefaultValue.__doc__ + '\n' + '* ``Duplicate``: ' + Qgis.FieldDuplicatePolicy.Duplicate.__doc__ + '\n' + '* ``UnsetField``: ' + Qgis.FieldDuplicatePolicy.UnsetField.__doc__ +# -- +Qgis.FieldDuplicatePolicy.baseClass = Qgis +# monkey patching scoped based enum Qgis.FieldDomainType.Coded.__doc__ = "Coded field domain" Qgis.FieldDomainType.Range.__doc__ = "Numeric range field domain (min/max)" Qgis.FieldDomainType.Glob.__doc__ = "Glob string pattern field domain" @@ -3260,7 +3278,8 @@ # monkey patching scoped based enum Qgis.MeshElevationMode.FixedElevationRange.__doc__ = "Layer has a fixed elevation range" Qgis.MeshElevationMode.FromVertices.__doc__ = "Elevation should be taken from mesh vertices" -Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ +Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ = "Layer has a fixed (manually specified) elevation range per group" +Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ + '\n' + '* ``FixedRangePerGroup``: ' + Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ # -- Qgis.MeshElevationMode.baseClass = Qgis # monkey patching scoped based enum diff --git a/python/PyQt6/core/auto_additions/qgsmeshrenderersettings.py b/python/PyQt6/core/auto_additions/qgsmeshrenderersettings.py index cb060364851cb..2ece71f4d5af7 100644 --- a/python/PyQt6/core/auto_additions/qgsmeshrenderersettings.py +++ b/python/PyQt6/core/auto_additions/qgsmeshrenderersettings.py @@ -6,6 +6,16 @@ QgsMeshRendererVectorArrowSettings.Fixed = QgsMeshRendererVectorArrowSettings.ArrowScalingMethod.Fixed QgsMeshRendererVectorStreamlineSettings.MeshGridded = QgsMeshRendererVectorStreamlineSettings.SeedingStartPointsMethod.MeshGridded QgsMeshRendererVectorStreamlineSettings.Random = QgsMeshRendererVectorStreamlineSettings.SeedingStartPointsMethod.Random +# monkey patching scoped based enum +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MetersPerSecond.__doc__ = "Meters per second" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.KilometersPerHour.__doc__ = "Kilometers per hour" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.Knots.__doc__ = "Knots (Nautical miles per hour)" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MilesPerHour.__doc__ = "Miles per hour" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.FeetPerSecond.__doc__ = "Feet per second" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.OtherUnit.__doc__ = "Other unit" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.__doc__ = "Wind speed units. Wind barbs use knots so we use this enum for preset conversion values\n\n" + '* ``MetersPerSecond``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MetersPerSecond.__doc__ + '\n' + '* ``KilometersPerHour``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.KilometersPerHour.__doc__ + '\n' + '* ``Knots``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.Knots.__doc__ + '\n' + '* ``MilesPerHour``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MilesPerHour.__doc__ + '\n' + '* ``FeetPerSecond``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.FeetPerSecond.__doc__ + '\n' + '* ``OtherUnit``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.OtherUnit.__doc__ +# -- QgsMeshRendererVectorSettings.Arrows = QgsMeshRendererVectorSettings.Symbology.Arrows QgsMeshRendererVectorSettings.Streamlines = QgsMeshRendererVectorSettings.Symbology.Streamlines QgsMeshRendererVectorSettings.Traces = QgsMeshRendererVectorSettings.Symbology.Traces +QgsMeshRendererVectorSettings.WindBarbs = QgsMeshRendererVectorSettings.Symbology.WindBarbs diff --git a/python/PyQt6/core/auto_additions/qgsrenderer.py b/python/PyQt6/core/auto_additions/qgsrenderer.py index 43fd1e3a65e5b..43e3b60be4339 100644 --- a/python/PyQt6/core/auto_additions/qgsrenderer.py +++ b/python/PyQt6/core/auto_additions/qgsrenderer.py @@ -1,4 +1,9 @@ # The following has been generated automatically from src/core/symbology/qgsrenderer.h +# monkey patching scoped based enum +QgsFeatureRenderer.Property.HeatmapRadius.__doc__ = "Heatmap renderer radius" +QgsFeatureRenderer.Property.HeatmapMaximum.__doc__ = "Heatmap maximum value" +QgsFeatureRenderer.Property.__doc__ = "Data definable properties for renderers.\n\n.. versionadded:: 3.38\n\n" + '* ``HeatmapRadius``: ' + QgsFeatureRenderer.Property.HeatmapRadius.__doc__ + '\n' + '* ``HeatmapMaximum``: ' + QgsFeatureRenderer.Property.HeatmapMaximum.__doc__ +# -- QgsFeatureRenderer.SymbolLevels = QgsFeatureRenderer.Capability.SymbolLevels QgsFeatureRenderer.MoreSymbolsPerFeature = QgsFeatureRenderer.Capability.MoreSymbolsPerFeature QgsFeatureRenderer.Filter = QgsFeatureRenderer.Capability.Filter diff --git a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in index 8337f9e16d127..61ce1397087c7 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in @@ -369,7 +369,7 @@ corresponds to the last point in the line. %Docstring Returns the z-coordinate of the specified node in the line string. -If the LineString does not have a z-dimension then ``nan`` will be returned. +If the LineString does not have a z-dimension then ``NaN`` will be returned. Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1 corresponds to the last point in the line. @@ -398,7 +398,7 @@ corresponds to the last point in the line. %Docstring Returns the m-coordinate of the specified node in the line string. -If the LineString does not have a m-dimension then ``nan`` will be returned. +If the LineString does not have a m-dimension then ``NaN`` will be returned. Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1 corresponds to the last point in the line. @@ -863,13 +863,26 @@ Calculates the minimal 3D bounding box for the geometry. .. versionadded:: 3.34 %End - QgsLineString *measuredLine( double start, double end ) const /Factory/; %Docstring Re-write the measure ordinate (or add one, if it isn't already there) interpolating the measure between the supplied ``start`` and ``end`` values. .. versionadded:: 3.36 +%End + + QgsLineString *interpolateM( bool use3DDistance = true ) const /Factory/; +%Docstring +Returns a copy of this line with all missing (NaN) m values interpolated +from m values of surrounding vertices. + +If the line does not contain m values, ``None`` is returned. + +The ``use3DDistance`` controls whether 2D or 3D distances between vertices +should be used during interpolation. This option is only considered for lines +with z values. + +.. versionadded:: 3.38 %End protected: diff --git a/python/PyQt6/core/auto_generated/gps/qgsgpsinformation.sip.in b/python/PyQt6/core/auto_generated/gps/qgsgpsinformation.sip.in index e5f1c55000c5d..65f98b7219662 100644 --- a/python/PyQt6/core/auto_generated/gps/qgsgpsinformation.sip.in +++ b/python/PyQt6/core/auto_generated/gps/qgsgpsinformation.sip.in @@ -88,6 +88,24 @@ Returns the best fix status and corresponding constellation. bool satInfoComplete; + Qgis::GpsNavigationStatus navigationStatus() const; +%Docstring +Returns the navigation status. + +.. seealso:: :py:func:`setNavigationStatus` + +.. versionadded:: 3.38 +%End + + void setNavigationStatus( Qgis::GpsNavigationStatus status ); +%Docstring +Sets the navigation ``status``. + +.. seealso:: :py:func:`navigationStatus` + +.. versionadded:: 3.38 +%End + bool isValid() const; %Docstring Returns whether the connection information is valid diff --git a/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in b/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in index 5fd12928fad70..815c4dbacb86c 100644 --- a/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in +++ b/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in @@ -1115,6 +1115,13 @@ Retrieves the second fill color for the grid frame. virtual void refresh(); + void copyProperties( const QgsLayoutItemMapGrid *other ); +%Docstring +Copies properties from specified map grid. + +.. versionadded:: 3.38 +%End + signals: void crsChanged(); diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in index 5df433842ae5b..a0725f4ddc7dc 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshdataset.sip.in @@ -12,6 +12,7 @@ + class QgsMeshDatasetIndex { %Docstring(signature="appended") @@ -431,6 +432,16 @@ Constructs a valid metadata object QString name() const; %Docstring Returns name of the dataset group +%End + + QString parentQuantityName() const; +%Docstring +Returns the name of the dataset's parent quantity, if available. + +The quantity can be used to collect dataset groups which represent a single quantity +but at different values (e.g. groups which represent different elevations). + +.. versionadded:: 3.38 %End QString uri() const; diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in index ad3f120c534f0..89931877d2c71 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -592,52 +592,54 @@ Dataset index is valid even the temporal properties is inactive. This method is .. versionadded:: 3.22 %End - QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) 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 +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) 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 +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticScalarDatasetIndex() const; + QgsMeshDatasetIndex staticScalarDatasetIndex( int group = -1 ) const; %Docstring -Returns the static scalar dataset index that is rendered if the temporal properties is not active +Returns the static scalar dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticVectorDatasetIndex() const; + QgsMeshDatasetIndex staticVectorDatasetIndex( int group = -1 ) const; %Docstring -Returns the static vector dataset index that is rendered if the temporal properties is not active +Returns the static vector dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in index f15a152d4cf72..28266b6c237c6 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in @@ -100,6 +100,42 @@ Sets the fixed elevation ``range`` for the mesh. .. seealso:: :py:func:`fixedRange` +.. versionadded:: 3.38 +%End + + QMap fixedRangePerGroup() const; +%Docstring +Returns the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`setFixedRangePerGroup` + +.. versionadded:: 3.38 +%End + + void setFixedRangePerGroup( const QMap &ranges ); +%Docstring +Sets the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`fixedRangePerGroup` + .. versionadded:: 3.38 %End diff --git a/python/PyQt6/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in b/python/PyQt6/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in index 4e515a672a987..e679521e1f162 100644 --- a/python/PyQt6/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in +++ b/python/PyQt6/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in @@ -419,6 +419,85 @@ Writes configuration to a new DOM element }; +class QgsMeshRendererVectorWindBarbSettings +{ +%Docstring(signature="appended") + +Represents a mesh renderer settings for vector datasets displayed with wind barbs + +.. note:: + + The API is considered EXPERIMENTAL and can be changed without a notice + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsmeshrenderersettings.h" +%End + public: + enum class WindSpeedUnit + { + MetersPerSecond, + KilometersPerHour, + Knots, + MilesPerHour, + FeetPerSecond, + OtherUnit + }; + + double magnitudeMultiplier() const; +%Docstring +Returns the multiplier for the magnitude to convert it to knots, according to the units set with :py:func:`~QgsMeshRendererVectorWindBarbSettings.setMagnitudeUnits` +A custom multiplier can be set with :py:func:`~QgsMeshRendererVectorWindBarbSettings.setMagnitudeMultiplier` for the case when units are set to OtherUnit +%End + + void setMagnitudeMultiplier( double magnitudeMultiplier ); +%Docstring +Sets a multiplier for the magnitude to convert it to knots +%End + + double shaftLength() const; +%Docstring +Returns the shaft length (in millimeters) +%End + + void setShaftLength( double shaftLength ); +%Docstring +Sets the shaft length (in millimeters) +%End + + Qgis::RenderUnit shaftLengthUnits(); +%Docstring +Sets the units for the shaft length +%End + + void setShaftLengthUnits( Qgis::RenderUnit shaftLengthUnit ); +%Docstring +Returns the units for the shaft length +%End + + WindSpeedUnit magnitudeUnits() const; +%Docstring +Returns the units that the data are in +%End + + void setMagnitudeUnits( WindSpeedUnit units ); +%Docstring +Sets the units that the data are in +%End + + QDomElement writeXml( QDomDocument &doc ) const; +%Docstring +Writes configuration to a new DOM element +%End + void readXml( const QDomElement &elem ); +%Docstring +Reads configuration from the given DOM element +%End + +}; + class QgsMeshRendererVectorSettings { %Docstring(signature="appended") @@ -444,7 +523,9 @@ Represents a renderer settings for vector datasets //! Displaying vector dataset with streamlines Streamlines, //! Displaying vector dataset with particle traces - Traces + Traces, + //! Displaying vector dataset with wind barbs + WindBarbs }; double lineWidth() const; @@ -609,6 +690,20 @@ Returns settings for vector rendered with traces Sets settings for vector rendered with traces .. versionadded:: 3.12 +%End + + QgsMeshRendererVectorWindBarbSettings windBarbSettings() const; +%Docstring +Returns settings for vector rendered with wind barbs + +.. versionadded:: 3.38 +%End + + void setWindBarbSettings( const QgsMeshRendererVectorWindBarbSettings &windBarbSettings ); +%Docstring +Sets settings for vector rendered with wind barbs + +.. versionadded:: 3.38 %End QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context = QgsReadWriteContext() ) const; diff --git a/python/PyQt6/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in b/python/PyQt6/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in index bff0a33a107a5..1fd7f190d237d 100644 --- a/python/PyQt6/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in +++ b/python/PyQt6/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in @@ -100,6 +100,17 @@ Encapsulates the results of running a Processing model QgsProcessingModelResult(); + void clear(); +%Docstring +Clears any existing results. +%End + + void mergeWith( const QgsProcessingModelResult &other ); +%Docstring +Merges this set of results with an ``other`` set of results. + +Conflicting results from ``other`` will replace results in this object. +%End QMap< QString, QgsProcessingModelChildAlgorithmResult > childResults() const; %Docstring @@ -109,6 +120,14 @@ Map keys refer to the child algorithm IDs. %End + + + + QSet< QString > executedChildIds() const; +%Docstring +Returns the set of child algorithm IDs which were executed during the model execution. +%End + }; diff --git a/python/PyQt6/core/auto_generated/processing/qgsprocessingcontext.sip.in b/python/PyQt6/core/auto_generated/processing/qgsprocessingcontext.sip.in index 6ca842b447369..aaa74b652a660 100644 --- a/python/PyQt6/core/auto_generated/processing/qgsprocessingcontext.sip.in +++ b/python/PyQt6/core/auto_generated/processing/qgsprocessingcontext.sip.in @@ -657,6 +657,9 @@ Returns list of the equivalent qgis_process arguments representing the settings .. versionadded:: 3.24 %End + + + QgsProcessingModelResult modelResult() const; %Docstring Returns the model results, populated when the context is used to run a model algorithm. diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index e0e52feb4a716..879b61c981c46 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -577,6 +577,7 @@ The development version BottomMiddle, BottomSlightlyRight, BottomRight, + OverPoint, }; enum class LabelOffsetType /BaseType=IntEnum/ @@ -895,6 +896,14 @@ The development version Simulation, }; + enum class GpsNavigationStatus /BaseType=IntEnum/ + { + NotValid, + Safe, + Caution, + Unsafe, + }; + enum class GpsInformationComponent /BaseType=IntFlag/ { Location, @@ -1826,6 +1835,13 @@ The development version GeometryWeighted, }; + enum class FieldDuplicatePolicy /BaseType=IntEnum/ + { + DefaultValue, + Duplicate, + UnsetField, + }; + enum class FieldDomainType /BaseType=IntEnum/ { Coded, @@ -1872,7 +1888,8 @@ The development version enum class MeshElevationMode /BaseType=IntEnum/ { FixedElevationRange, - FromVertices + FromVertices, + FixedRangePerGroup, }; enum class BetweenLineConstraint /BaseType=IntEnum/ diff --git a/python/PyQt6/core/auto_generated/qgsfield.sip.in b/python/PyQt6/core/auto_generated/qgsfield.sip.in index 655275c4790e3..0193040936b60 100644 --- a/python/PyQt6/core/auto_generated/qgsfield.sip.in +++ b/python/PyQt6/core/auto_generated/qgsfield.sip.in @@ -466,6 +466,26 @@ be handled during a split operation. .. seealso:: :py:func:`splitPolicy` .. versionadded:: 3.30 +%End + + Qgis::FieldDuplicatePolicy duplicatePolicy() const; +%Docstring +Returns the field's duplicate policy, which indicates how field values should +be handled during a duplicate operation. + +.. seealso:: :py:func:`setDuplicatePolicy` + +.. versionadded:: 3.38 +%End + + void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ); +%Docstring +Sets the field's duplicate ``policy``, which indicates how field values should +be handled during a duplicate operation. + +.. seealso:: :py:func:`duplicatePolicy` + +.. versionadded:: 3.38 %End SIP_PYOBJECT __repr__(); diff --git a/python/PyQt6/core/auto_generated/symbology/qgsheatmaprenderer.sip.in b/python/PyQt6/core/auto_generated/symbology/qgsheatmaprenderer.sip.in index a107425c98ea6..73149a0ab87db 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgsheatmaprenderer.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgsheatmaprenderer.sip.in @@ -60,6 +60,8 @@ Creates a new heatmap renderer instance from XML static QgsHeatmapRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) /Factory/; virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const; + virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const /Factory/; + virtual void modifyRequestExtent( QgsRectangle &extent, QgsRenderContext &context ); @@ -81,6 +83,24 @@ Sets the color ramp to use for shading the heatmap. :param ramp: color ramp for heatmap. Ownership of ramp is transferred to the renderer. .. seealso:: :py:func:`colorRamp` +%End + + const QgsColorRampLegendNodeSettings &legendSettings() const; +%Docstring +Returns the color ramp legend settings. + +.. seealso:: :py:func:`setLegendSettings` + +.. versionadded:: 3.38 +%End + + void setLegendSettings( const QgsColorRampLegendNodeSettings &settings ); +%Docstring +Sets the color ramp legend ``settings``. + +.. seealso:: :py:func:`legendSettings` + +.. versionadded:: 3.38 %End double radius() const; diff --git a/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in b/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in index e8bb6baafda7b..8aecfd9e772db 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in @@ -82,6 +82,20 @@ class QgsFeatureRenderer %End public: + enum class Property /BaseType=IntEnum/ + { + HeatmapRadius, + HeatmapMaximum, + }; + + static const QgsPropertiesDefinition &propertyDefinitions(); +%Docstring +Returns the symbol property definitions. + +.. versionadded:: 3.18 +%End + + static QgsFeatureRenderer *defaultRenderer( Qgis::GeometryType geomType ) /Factory/; %Docstring Returns a new renderer - used by default in vector layers @@ -358,7 +372,22 @@ the features displayed using that key. %Docstring Returns a list of symbology items for the legend +.. seealso:: :py:func:`createLegendNodes` + .. seealso:: :py:func:`legendKeys` +%End + + virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const /Factory/; +%Docstring +Returns a list of legend nodes to be used for the legend for the renderer. + +Ownership is transferred to the caller. + +The default implementation creates a legend node for each symbol item returned by :py:func:`~QgsFeatureRenderer.legendSymbolItems` + +.. seealso:: :py:func:`legendSymbolItems` + +.. versionadded:: 3.38 %End virtual QString legendClassificationAttribute() const; @@ -434,6 +463,42 @@ Sets whether the renderer should be rendered to a raster destination. would result in a large, complex vector output. .. seealso:: :py:func:`forceRasterRender` +%End + + void setDataDefinedProperty( Property key, const QgsProperty &property ); +%Docstring +Sets a data defined property for the renderer. Any existing property with the same key +will be overwritten. + +.. seealso:: :py:func:`dataDefinedProperties` + +.. seealso:: Property + +.. versionadded:: 3.38 +%End + + QgsPropertyCollection &dataDefinedProperties(); +%Docstring +Returns a reference to the renderer's property collection, used for data defined overrides. + +.. seealso:: :py:func:`setDataDefinedProperties` + +.. seealso:: Property + +.. versionadded:: 3.38 +%End + + + void setDataDefinedProperties( const QgsPropertyCollection &collection ); +%Docstring +Sets the renderer's property collection, used for data defined overrides. + +:param collection: property collection. Existing properties will be replaced. + +.. seealso:: :py:func:`dataDefinedProperties` + + +.. versionadded:: 3.38 %End double referenceScale() const; @@ -552,6 +617,7 @@ Currently clones - Reference scale - Symbol levels enabled/disabled - Force raster render enabled/disabled +- Data defined properties :param destRenderer: destination renderer for copied effect diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in index f67099d84c9f3..b54fb80e6f104 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1935,6 +1935,27 @@ Sets a split ``policy`` for the field with the specified index. } %End + void setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy ); +%Docstring +Sets a duplicate ``policy`` for the field with the specified index. + +:raises KeyError: if no field with the specified index exists + +.. versionadded:: 3.38 +%End + +%MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldDuplicatePolicy( a0, a1 ); + } +%End + QSet excludeAttributesWms() const /Deprecated/; %Docstring A set of attributes that are not advertised in WMS requests with QGIS server. diff --git a/python/PyQt6/gui/auto_additions/qgscodeeditorcolorscheme.py b/python/PyQt6/gui/auto_additions/qgscodeeditorcolorscheme.py index 3253296bc13a4..173a692ba01b2 100644 --- a/python/PyQt6/gui/auto_additions/qgscodeeditorcolorscheme.py +++ b/python/PyQt6/gui/auto_additions/qgscodeeditorcolorscheme.py @@ -35,5 +35,6 @@ QgsCodeEditorColorScheme.ColorRole.FoldIconForeground.__doc__ = "Fold icon foreground color" QgsCodeEditorColorScheme.ColorRole.FoldIconHalo.__doc__ = "Fold icon halo color" QgsCodeEditorColorScheme.ColorRole.IndentationGuide.__doc__ = "Indentation guide line" -QgsCodeEditorColorScheme.ColorRole.__doc__ = "Color roles.\n\n" + '* ``Default``: ' + QgsCodeEditorColorScheme.ColorRole.Default.__doc__ + '\n' + '* ``Keyword``: ' + QgsCodeEditorColorScheme.ColorRole.Keyword.__doc__ + '\n' + '* ``Class``: ' + QgsCodeEditorColorScheme.ColorRole.Class.__doc__ + '\n' + '* ``Method``: ' + QgsCodeEditorColorScheme.ColorRole.Method.__doc__ + '\n' + '* ``Decoration``: ' + QgsCodeEditorColorScheme.ColorRole.Decoration.__doc__ + '\n' + '* ``Number``: ' + QgsCodeEditorColorScheme.ColorRole.Number.__doc__ + '\n' + '* ``Comment``: ' + QgsCodeEditorColorScheme.ColorRole.Comment.__doc__ + '\n' + '* ``CommentLine``: ' + QgsCodeEditorColorScheme.ColorRole.CommentLine.__doc__ + '\n' + '* ``CommentBlock``: ' + QgsCodeEditorColorScheme.ColorRole.CommentBlock.__doc__ + '\n' + '* ``Background``: ' + QgsCodeEditorColorScheme.ColorRole.Background.__doc__ + '\n' + '* ``Cursor``: ' + QgsCodeEditorColorScheme.ColorRole.Cursor.__doc__ + '\n' + '* ``CaretLine``: ' + QgsCodeEditorColorScheme.ColorRole.CaretLine.__doc__ + '\n' + '* ``SingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.SingleQuote.__doc__ + '\n' + '* ``DoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.DoubleQuote.__doc__ + '\n' + '* ``TripleSingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleSingleQuote.__doc__ + '\n' + '* ``TripleDoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleDoubleQuote.__doc__ + '\n' + '* ``Operator``: ' + QgsCodeEditorColorScheme.ColorRole.Operator.__doc__ + '\n' + '* ``QuotedOperator``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedOperator.__doc__ + '\n' + '* ``Identifier``: ' + QgsCodeEditorColorScheme.ColorRole.Identifier.__doc__ + '\n' + '* ``QuotedIdentifier``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedIdentifier.__doc__ + '\n' + '* ``Tag``: ' + QgsCodeEditorColorScheme.ColorRole.Tag.__doc__ + '\n' + '* ``UnknownTag``: ' + QgsCodeEditorColorScheme.ColorRole.UnknownTag.__doc__ + '\n' + '* ``MarginBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginBackground.__doc__ + '\n' + '* ``MarginForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginForeground.__doc__ + '\n' + '* ``SelectionBackground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionBackground.__doc__ + '\n' + '* ``SelectionForeground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionForeground.__doc__ + '\n' + '* ``MatchedBraceBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceBackground.__doc__ + '\n' + '* ``MatchedBraceForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceForeground.__doc__ + '\n' + '* ``Edge``: ' + QgsCodeEditorColorScheme.ColorRole.Edge.__doc__ + '\n' + '* ``Fold``: ' + QgsCodeEditorColorScheme.ColorRole.Fold.__doc__ + '\n' + '* ``Error``: ' + QgsCodeEditorColorScheme.ColorRole.Error.__doc__ + '\n' + '* ``ErrorBackground``: ' + QgsCodeEditorColorScheme.ColorRole.ErrorBackground.__doc__ + '\n' + '* ``FoldIconForeground``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconForeground.__doc__ + '\n' + '* ``FoldIconHalo``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconHalo.__doc__ + '\n' + '* ``IndentationGuide``: ' + QgsCodeEditorColorScheme.ColorRole.IndentationGuide.__doc__ +QgsCodeEditorColorScheme.ColorRole.SearchMatchBackground.__doc__ = "Background color for search matches (since QGIS 3.38)" +QgsCodeEditorColorScheme.ColorRole.__doc__ = "Color roles.\n\n" + '* ``Default``: ' + QgsCodeEditorColorScheme.ColorRole.Default.__doc__ + '\n' + '* ``Keyword``: ' + QgsCodeEditorColorScheme.ColorRole.Keyword.__doc__ + '\n' + '* ``Class``: ' + QgsCodeEditorColorScheme.ColorRole.Class.__doc__ + '\n' + '* ``Method``: ' + QgsCodeEditorColorScheme.ColorRole.Method.__doc__ + '\n' + '* ``Decoration``: ' + QgsCodeEditorColorScheme.ColorRole.Decoration.__doc__ + '\n' + '* ``Number``: ' + QgsCodeEditorColorScheme.ColorRole.Number.__doc__ + '\n' + '* ``Comment``: ' + QgsCodeEditorColorScheme.ColorRole.Comment.__doc__ + '\n' + '* ``CommentLine``: ' + QgsCodeEditorColorScheme.ColorRole.CommentLine.__doc__ + '\n' + '* ``CommentBlock``: ' + QgsCodeEditorColorScheme.ColorRole.CommentBlock.__doc__ + '\n' + '* ``Background``: ' + QgsCodeEditorColorScheme.ColorRole.Background.__doc__ + '\n' + '* ``Cursor``: ' + QgsCodeEditorColorScheme.ColorRole.Cursor.__doc__ + '\n' + '* ``CaretLine``: ' + QgsCodeEditorColorScheme.ColorRole.CaretLine.__doc__ + '\n' + '* ``SingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.SingleQuote.__doc__ + '\n' + '* ``DoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.DoubleQuote.__doc__ + '\n' + '* ``TripleSingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleSingleQuote.__doc__ + '\n' + '* ``TripleDoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleDoubleQuote.__doc__ + '\n' + '* ``Operator``: ' + QgsCodeEditorColorScheme.ColorRole.Operator.__doc__ + '\n' + '* ``QuotedOperator``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedOperator.__doc__ + '\n' + '* ``Identifier``: ' + QgsCodeEditorColorScheme.ColorRole.Identifier.__doc__ + '\n' + '* ``QuotedIdentifier``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedIdentifier.__doc__ + '\n' + '* ``Tag``: ' + QgsCodeEditorColorScheme.ColorRole.Tag.__doc__ + '\n' + '* ``UnknownTag``: ' + QgsCodeEditorColorScheme.ColorRole.UnknownTag.__doc__ + '\n' + '* ``MarginBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginBackground.__doc__ + '\n' + '* ``MarginForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginForeground.__doc__ + '\n' + '* ``SelectionBackground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionBackground.__doc__ + '\n' + '* ``SelectionForeground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionForeground.__doc__ + '\n' + '* ``MatchedBraceBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceBackground.__doc__ + '\n' + '* ``MatchedBraceForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceForeground.__doc__ + '\n' + '* ``Edge``: ' + QgsCodeEditorColorScheme.ColorRole.Edge.__doc__ + '\n' + '* ``Fold``: ' + QgsCodeEditorColorScheme.ColorRole.Fold.__doc__ + '\n' + '* ``Error``: ' + QgsCodeEditorColorScheme.ColorRole.Error.__doc__ + '\n' + '* ``ErrorBackground``: ' + QgsCodeEditorColorScheme.ColorRole.ErrorBackground.__doc__ + '\n' + '* ``FoldIconForeground``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconForeground.__doc__ + '\n' + '* ``FoldIconHalo``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconHalo.__doc__ + '\n' + '* ``IndentationGuide``: ' + QgsCodeEditorColorScheme.ColorRole.IndentationGuide.__doc__ + '\n' + '* ``SearchMatchBackground``: ' + QgsCodeEditorColorScheme.ColorRole.SearchMatchBackground.__doc__ # -- diff --git a/python/PyQt6/gui/auto_additions/qgscolorramplegendnodewidget.py b/python/PyQt6/gui/auto_additions/qgscolorramplegendnodewidget.py new file mode 100644 index 0000000000000..67618898a0322 --- /dev/null +++ b/python/PyQt6/gui/auto_additions/qgscolorramplegendnodewidget.py @@ -0,0 +1,14 @@ +# The following has been generated automatically from src/gui/qgscolorramplegendnodewidget.h +# monkey patching scoped based enum +QgsColorRampLegendNodeWidget.Capability.Prefix.__doc__ = "Allow editing legend prefix" +QgsColorRampLegendNodeWidget.Capability.Suffix.__doc__ = "Allow editing legend suffix" +QgsColorRampLegendNodeWidget.Capability.NumberFormat.__doc__ = "Allow editing number format" +QgsColorRampLegendNodeWidget.Capability.DefaultMinimum.__doc__ = "Allow resetting minimum label to default" +QgsColorRampLegendNodeWidget.Capability.DefaultMaximum.__doc__ = "Allow resetting maximum label to default" +QgsColorRampLegendNodeWidget.Capability.AllCapabilities.__doc__ = "All capabilities" +QgsColorRampLegendNodeWidget.Capability.__doc__ = "Capabilities to expose in the widget.\n\n.. versionadded:: 3.38\n\n" + '* ``Prefix``: ' + QgsColorRampLegendNodeWidget.Capability.Prefix.__doc__ + '\n' + '* ``Suffix``: ' + QgsColorRampLegendNodeWidget.Capability.Suffix.__doc__ + '\n' + '* ``NumberFormat``: ' + QgsColorRampLegendNodeWidget.Capability.NumberFormat.__doc__ + '\n' + '* ``DefaultMinimum``: ' + QgsColorRampLegendNodeWidget.Capability.DefaultMinimum.__doc__ + '\n' + '* ``DefaultMaximum``: ' + QgsColorRampLegendNodeWidget.Capability.DefaultMaximum.__doc__ + '\n' + '* ``AllCapabilities``: ' + QgsColorRampLegendNodeWidget.Capability.AllCapabilities.__doc__ +# -- +QgsColorRampLegendNodeWidget.Capability.baseClass = QgsColorRampLegendNodeWidget +QgsColorRampLegendNodeWidget.Capabilities = lambda flags=0: QgsColorRampLegendNodeWidget.Capability(flags) +QgsColorRampLegendNodeWidget.Capabilities.baseClass = QgsColorRampLegendNodeWidget +Capabilities = QgsColorRampLegendNodeWidget # dirty hack since SIP seems to introduce the flags in module diff --git a/python/PyQt6/gui/auto_additions/qgsdecoratedscrollbar.py b/python/PyQt6/gui/auto_additions/qgsdecoratedscrollbar.py new file mode 100644 index 0000000000000..a9d54d7d6f82e --- /dev/null +++ b/python/PyQt6/gui/auto_additions/qgsdecoratedscrollbar.py @@ -0,0 +1,9 @@ +# The following has been generated automatically from src/gui/qgsdecoratedscrollbar.h +# monkey patching scoped based enum +QgsScrollBarHighlight.Priority.Invalid.__doc__ = "Invalid" +QgsScrollBarHighlight.Priority.LowPriority.__doc__ = "Low priority, rendered below all other highlights" +QgsScrollBarHighlight.Priority.NormalPriority.__doc__ = "Normal priority" +QgsScrollBarHighlight.Priority.HighPriority.__doc__ = "High priority" +QgsScrollBarHighlight.Priority.HighestPriority.__doc__ = "Highest priority, rendered above all other highlights" +QgsScrollBarHighlight.Priority.__doc__ = "Priority, which dictates how overlapping highlights are rendered\n\n" + '* ``Invalid``: ' + QgsScrollBarHighlight.Priority.Invalid.__doc__ + '\n' + '* ``LowPriority``: ' + QgsScrollBarHighlight.Priority.LowPriority.__doc__ + '\n' + '* ``NormalPriority``: ' + QgsScrollBarHighlight.Priority.NormalPriority.__doc__ + '\n' + '* ``HighPriority``: ' + QgsScrollBarHighlight.Priority.HighPriority.__doc__ + '\n' + '* ``HighestPriority``: ' + QgsScrollBarHighlight.Priority.HighestPriority.__doc__ +# -- diff --git a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in index f8c7c072b5206..8b4bca093bbe5 100644 --- a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in +++ b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in @@ -62,7 +62,6 @@ whenever the public :py:func:`~QgsCodeInterpreter.exec` method is called. - class QgsCodeEditor : QsciScintilla { %Docstring(signature="appended") @@ -103,6 +102,8 @@ A text editor based on QScintilla2. typedef QFlags Flags; + static const int SEARCH_RESULT_INDICATOR; + QgsCodeEditor( QWidget *parent /TransferThis/ = 0, const QString &title = QString(), bool folding = false, bool margin = false, QgsCodeEditor::Flags flags = QgsCodeEditor::Flags(), QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); %Docstring Construct a new code editor. diff --git a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in index 49e317000368c..162ea2db34909 100644 --- a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in +++ b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in @@ -58,6 +58,7 @@ Defines a color scheme for use in :py:class:`QgsCodeEditor` widgets. FoldIconForeground, FoldIconHalo, IndentationGuide, + SearchMatchBackground, }; QgsCodeEditorColorScheme( const QString &id = QString(), const QString &name = QString() ); diff --git a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in new file mode 100644 index 0000000000000..eaeb55ef70fd2 --- /dev/null +++ b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in @@ -0,0 +1,116 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/codeeditors/qgscodeeditorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsCodeEditorWidget : QgsPanelWidget +{ +%Docstring(signature="appended") +A widget which wraps a :py:class:`QgsCodeEditor` in additional functionality. + +This widget wraps an existing :py:class:`QgsCodeEditor` object in a widget which provides +additional standard functionality, such as search/replace tools. The caller +must create an unparented :py:class:`QgsCodeEditor` object (or a subclass of :py:class:`QgsCodeEditor`) +first, and then construct a :py:class:`QgsCodeEditorWidget` passing this object to the +constructor. + +.. note:: + + may not be available in Python bindings, depending on platform support + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgscodeeditorwidget.h" +%End + public: + + QgsCodeEditorWidget( QgsCodeEditor *editor /Transfer/, + QgsMessageBar *messageBar = 0, + QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor for QgsCodeEditorWidget, wrapping the specified ``editor`` widget. + +Ownership of ``editor`` will be transferred to this widget. + +If an explicit ``messageBar`` is specified then it will be used to provide +feedback, otherwise an integrated message bar will be used. +%End + ~QgsCodeEditorWidget(); + + QgsCodeEditor *editor(); +%Docstring +Returns the wrapped code editor. +%End + + bool isSearchBarVisible() const; +%Docstring +Returns ``True`` if the search bar is visible. +%End + + QgsMessageBar *messageBar(); +%Docstring +Returns the message bar associated with the widget, to use for user feedback. +%End + + public slots: + + void showSearchBar(); +%Docstring +Shows the search bar. + +.. seealso:: :py:func:`hideSearchBar` + +.. seealso:: :py:func:`setSearchBarVisible` +%End + + void hideSearchBar(); +%Docstring +Hides the search bar. + +.. seealso:: :py:func:`showSearchBar` + +.. seealso:: :py:func:`setSearchBarVisible` +%End + + void setSearchBarVisible( bool visible ); +%Docstring +Sets whether the search bar is ``visible``. + +.. seealso:: :py:func:`showSearchBar` + +.. seealso:: :py:func:`hideSearchBar` +%End + + void triggerFind(); +%Docstring +Triggers a find operation, using the default behavior. + +This will automatically open the search bar and start a find operation using +the default behavior, e.g. searching for any selected text in the code editor. +%End + + signals: + + void searchBarToggled( bool visible ); +%Docstring +Emitted when the visibility of the search bar is changed. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/codeeditors/qgscodeeditorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/PyQt6/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in b/python/PyQt6/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in index 86f58913e8527..a7e625dab85f3 100644 --- a/python/PyQt6/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in +++ b/python/PyQt6/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in @@ -403,6 +403,20 @@ Sets the ``results`` obtained for this child algorithm for the last model execut signals: + void runFromHere(); +%Docstring +Emitted when the user opts to run the model from this child algorithm. + +.. versionadded:: 3.38 +%End + + void runSelected(); +%Docstring +Emitted when the user opts to run selected steps from the model. + +.. versionadded:: 3.38 +%End + void showPreviousResults(); %Docstring Emitted when the user opts to view previous results from this child algorithm. diff --git a/python/PyQt6/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in b/python/PyQt6/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in index 8ec16930a4eba..94e778a804bfb 100644 --- a/python/PyQt6/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in +++ b/python/PyQt6/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in @@ -172,6 +172,20 @@ Emitted whenever a component of the model is changed. %Docstring Emitted whenever the selected item changes. If ``None``, no item is selected. +%End + + void runSelected(); +%Docstring +Emitted when the user opts to run selected steps from the model. + +.. versionadded:: 3.38 +%End + + void runFromChild( const QString &childId ); +%Docstring +Emitted when the user opts to run the part of the model starting from the specified child algorithm. + +.. versionadded:: 3.38 %End void showChildAlgorithmOutputs( const QString &childId ); diff --git a/python/PyQt6/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in b/python/PyQt6/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in index 6dfc1ac576de0..9b46a74e97e31 100644 --- a/python/PyQt6/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in +++ b/python/PyQt6/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in @@ -359,6 +359,15 @@ This allows the dialog to override default Processing settings for an individual signals: + void algorithmAboutToRun( QgsProcessingContext *context ); +%Docstring +Emitted when the algorithm is about to run in the specified ``context``. + +This signal can be used to tweak the ``context`` prior to the algorithm execution. + +.. versionadded:: 3.38 +%End + void algorithmFinished( bool successful, const QVariantMap &result ); %Docstring Emitted whenever an algorithm has finished executing in the dialog. diff --git a/python/PyQt6/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in b/python/PyQt6/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in index 0e2d50e421c81..6ebd566e919d1 100644 --- a/python/PyQt6/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in +++ b/python/PyQt6/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in @@ -35,6 +35,8 @@ This should only be called once -- calling multiple times will result in duplica virtual QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) /Factory/; + virtual void updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ); + signals: diff --git a/python/PyQt6/gui/auto_generated/qgsbrowsertreeview.sip.in b/python/PyQt6/gui/auto_generated/qgsbrowsertreeview.sip.in index b66a85c928562..b1066e07390d9 100644 --- a/python/PyQt6/gui/auto_generated/qgsbrowsertreeview.sip.in +++ b/python/PyQt6/gui/auto_generated/qgsbrowsertreeview.sip.in @@ -66,12 +66,14 @@ Returns ``True`` if the item was found and could be selected. .. versionadded:: 3.28 %End - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); %Docstring Expands out a file ``path`` in the view. The ``path`` must correspond to a valid directory existing on the file system. +Since QGIS 3.38 the ``selectPath`` argument can be used to automatically select the path too. + .. versionadded:: 3.28 %End diff --git a/python/PyQt6/gui/auto_generated/qgscolorramplegendnodewidget.sip.in b/python/PyQt6/gui/auto_generated/qgscolorramplegendnodewidget.sip.in index bbe8d561270b5..02af80c7d3cb4 100644 --- a/python/PyQt6/gui/auto_generated/qgscolorramplegendnodewidget.sip.in +++ b/python/PyQt6/gui/auto_generated/qgscolorramplegendnodewidget.sip.in @@ -11,7 +11,6 @@ - class QgsColorRampLegendNodeWidget: QgsPanelWidget { %Docstring(signature="appended") @@ -30,9 +29,24 @@ When changes are made the to settings by a user the :py:func:`~widgetChanged` si %End public: - QgsColorRampLegendNodeWidget( QWidget *parent = 0 ); + enum class Capability /BaseType=IntFlag/ + { + Prefix, + Suffix, + NumberFormat, + DefaultMinimum, + DefaultMaximum, + AllCapabilities, + }; + + typedef QFlags Capabilities; + + + QgsColorRampLegendNodeWidget( QWidget *parent = 0, QgsColorRampLegendNodeWidget::Capabilities capabilities = QgsColorRampLegendNodeWidget::Capability::AllCapabilities ); %Docstring Constructor for QgsColorRampLegendNodeWidget, with the specified ``parent`` widget. + +Since QGIS 3.38, the ``capabilities`` argument can be used to fine-tune settings exposed in the widget. %End QgsColorRampLegendNodeSettings settings() const; @@ -59,6 +73,8 @@ when using single band gray renderer). %End }; +QFlags operator|(QgsColorRampLegendNodeWidget::Capability f1, QFlags f2); + class QgsColorRampLegendNodeDialog : QDialog { @@ -73,9 +89,11 @@ A dialog for configuring a :py:class:`QgsColorRampLegendNode` (:py:class:`QgsCol %End public: - QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent /TransferThis/ = 0 ); + QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent /TransferThis/ = 0, QgsColorRampLegendNodeWidget::Capabilities capabilities = QgsColorRampLegendNodeWidget::Capability::AllCapabilities ); %Docstring Constructor for QgsColorRampLegendNodeDialog, initially showing the specified ``settings``. + +Since QGIS 3.38, the ``capabilities`` argument can be used to fine-tune settings exposed in the dialog. %End QgsColorRampLegendNodeSettings settings() const; diff --git a/python/PyQt6/gui/auto_generated/qgsdatasourceselectdialog.sip.in b/python/PyQt6/gui/auto_generated/qgsdatasourceselectdialog.sip.in index 9784db0fb3be2..5e68643d31882 100644 --- a/python/PyQt6/gui/auto_generated/qgsdatasourceselectdialog.sip.in +++ b/python/PyQt6/gui/auto_generated/qgsdatasourceselectdialog.sip.in @@ -65,12 +65,14 @@ Sets a description label .. versionadded:: 3.8 %End - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); %Docstring Expands out a file ``path`` in the view. The ``path`` must correspond to a valid directory existing on the file system. +Since QGIS 3.38 the ``selectPath`` argument can be used to automatically select the path too. + .. versionadded:: 3.28 %End @@ -101,6 +103,11 @@ Apply filter to the model Scroll to last selected index and expand it's children %End + virtual void dragEnterEvent( QDragEnterEvent *event ); + + virtual void dropEvent( QDropEvent *event ); + + signals: void validationChanged( bool isValid ); @@ -176,12 +183,14 @@ Sets a description label .. versionadded:: 3.8 %End - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); %Docstring Expands out a file ``path`` in the view. The ``path`` must correspond to a valid directory existing on the file system. +Since QGIS 3.38 the ``selectPath`` argument can be used to automatically select the path too. + .. versionadded:: 3.28 %End diff --git a/python/PyQt6/gui/auto_generated/qgsdecoratedscrollbar.sip.in b/python/PyQt6/gui/auto_generated/qgsdecoratedscrollbar.sip.in new file mode 100644 index 0000000000000..66663e70b6690 --- /dev/null +++ b/python/PyQt6/gui/auto_generated/qgsdecoratedscrollbar.sip.in @@ -0,0 +1,157 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsdecoratedscrollbar.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsScrollBarHighlight +{ +%Docstring(signature="appended") +Encapsulates the details of a highlight in a scrollbar, used alongside :py:class:`QgsScrollBarHighlightController`. + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsdecoratedscrollbar.h" +%End + public: + + enum class Priority /BaseType=IntEnum/ + { + Invalid, + LowPriority, + NormalPriority, + HighPriority, + HighestPriority + }; + + QgsScrollBarHighlight( int category, int position, const QColor &color, QgsScrollBarHighlight::Priority priority = QgsScrollBarHighlight::Priority::NormalPriority ); +%Docstring +Constructor for QgsScrollBarHighlight. +%End + + QgsScrollBarHighlight(); +%Docstring +Default constructor for QgsScrollBarHighlight. +%End + + int category; + + int position; + + QColor color; + + QgsScrollBarHighlight::Priority priority; +}; + +class QgsScrollBarHighlightController +{ +%Docstring(signature="appended") +Adds highlights (colored markers) to a scrollbar. + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsdecoratedscrollbar.h" +%End + public: + + QgsScrollBarHighlightController(); + ~QgsScrollBarHighlightController(); + + QScrollBar *scrollBar() const; +%Docstring +Returns the associated scroll bar. +%End + + QAbstractScrollArea *scrollArea() const; +%Docstring +Returns the associated scroll area. + +.. seealso:: :py:func:`setScrollArea` +%End + + void setScrollArea( QAbstractScrollArea *scrollArea ); +%Docstring +Sets the associated scroll bar. + +.. seealso:: :py:func:`scrollArea` +%End + + double lineHeight() const; +%Docstring +Returns the line height for text associated with the scroll area. + +.. seealso:: :py:func:`setLineHeight` +%End + + void setLineHeight( double height ); +%Docstring +Sets the line ``height`` for text associated with the scroll area. + +.. seealso:: :py:func:`lineHeight` +%End + + double visibleRange() const; +%Docstring +Returns the visible range of the scroll area (i.e. the viewport's height). + +.. seealso:: :py:func:`setVisibleRange` +%End + + void setVisibleRange( double visibleRange ); +%Docstring +Sets the visible range of the scroll area (i.e. the viewport's height). + +.. seealso:: :py:func:`visibleRange` +%End + + double margin() const; +%Docstring +Returns the document margins for the associated viewport. + +.. seealso:: :py:func:`setMargin` +%End + + void setMargin( double margin ); +%Docstring +Sets the document ``margin`` for the associated viewport. + +.. seealso:: :py:func:`margin` +%End + + + void addHighlight( const QgsScrollBarHighlight &highlight ); +%Docstring +Adds a ``highlight`` to the scrollbar. +%End + + void removeHighlights( int category ); +%Docstring +Removes all highlights with matching ``category`` from the scrollbar. +%End + + void removeAllHighlights(); +%Docstring +Removes all highlights from the scroll bar. +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsdecoratedscrollbar.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/PyQt6/gui/auto_generated/qgsexpressionbuilderwidget.sip.in b/python/PyQt6/gui/auto_generated/qgsexpressionbuilderwidget.sip.in index 6614ea27cec90..fe3674ffdfb42 100644 --- a/python/PyQt6/gui/auto_generated/qgsexpressionbuilderwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/qgsexpressionbuilderwidget.sip.in @@ -12,7 +12,6 @@ - class QgsExpressionBuilderWidget : QWidget { %Docstring(signature="appended") diff --git a/python/PyQt6/gui/auto_generated/qgsqueryresultwidget.sip.in b/python/PyQt6/gui/auto_generated/qgsqueryresultwidget.sip.in index 11fc750e3c673..7e1b24b04dcf0 100644 --- a/python/PyQt6/gui/auto_generated/qgsqueryresultwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/qgsqueryresultwidget.sip.in @@ -11,6 +11,7 @@ + class QgsQueryResultWidget: QWidget { %Docstring(signature="appended") diff --git a/python/PyQt6/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in index 806252ab2ab5b..38ae70ab6b14e 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in @@ -35,6 +35,8 @@ class QgsCategorizedSymbolRendererWidget : QgsRendererWidget virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + int matchToSymbols( QgsStyle *style ); %Docstring diff --git a/python/PyQt6/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in index 74e36c341a671..f50e3778474fc 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in @@ -8,7 +8,7 @@ -class QgsEmbeddedSymbolRendererWidget : QgsRendererWidget, QgsExpressionContextGenerator +class QgsEmbeddedSymbolRendererWidget : QgsRendererWidget { %Docstring(signature="appended") A widget used represent options of a :py:class:`QgsEmbeddedSymbolRenderer` diff --git a/python/PyQt6/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in index 0d033cc2d56f9..699e388177e2b 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in @@ -30,6 +30,8 @@ class QgsGraduatedSymbolRendererWidget : QgsRendererWidget virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + public slots: void graduatedColumnChanged( const QString &field ); diff --git a/python/PyQt6/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in index f5a0cf7aa6173..1a0250db77d6b 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in @@ -39,6 +39,8 @@ Constructor virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + }; diff --git a/python/PyQt6/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in index fa4e7f9df7b57..a35daf3e2d2a8 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in @@ -11,7 +11,7 @@ -class QgsPointClusterRendererWidget: QgsRendererWidget, QgsExpressionContextGenerator +class QgsPointClusterRendererWidget: QgsRendererWidget { %Docstring(signature="appended") A widget which allows configuration of the properties for a :py:class:`QgsPointClusterRenderer`. diff --git a/python/PyQt6/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in index fea5f06266b1f..85f064ef55bae 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in @@ -10,7 +10,7 @@ -class QgsPointDisplacementRendererWidget: QgsRendererWidget, QgsExpressionContextGenerator +class QgsPointDisplacementRendererWidget: QgsRendererWidget { %TypeHeaderCode diff --git a/python/PyQt6/gui/auto_generated/symbology/qgsrendererwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgsrendererwidget.sip.in index 521fc8d006d65..fc6614f1d377f 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgsrendererwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgsrendererwidget.sip.in @@ -8,7 +8,7 @@ -class QgsRendererWidget : QgsPanelWidget +class QgsRendererWidget : QgsPanelWidget, QgsExpressionContextGenerator { %Docstring(signature="appended") Base class for renderer settings widgets. @@ -27,6 +27,8 @@ WORKFLOW: %End public: QgsRendererWidget( QgsVectorLayer *layer, QgsStyle *style ); + virtual QgsExpressionContext createExpressionContext() const; + virtual QgsFeatureRenderer *renderer() = 0; %Docstring @@ -112,6 +114,13 @@ The ``levels`` argument defines the updated list of symbols with rendering passe The ``enabled`` arguments specifies if symbol levels should be enabled for the renderer. .. versionadded:: 3.20 +%End + + void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsFeatureRenderer::Property key ); +%Docstring +Registers a data defined override button. Handles setting up connections +for the button and initializing the button to show the correct descriptions +and help text for the associated property. %End protected slots: diff --git a/python/PyQt6/gui/gui_auto.sip b/python/PyQt6/gui/gui_auto.sip index c668cc6c9a90e..e6bf953cc4333 100644 --- a/python/PyQt6/gui/gui_auto.sip +++ b/python/PyQt6/gui/gui_auto.sip @@ -46,6 +46,7 @@ %Include auto_generated/qgsdataitemguiproviderregistry.sip %Include auto_generated/qgsdatasourceselectdialog.sip %Include auto_generated/qgsdbrelationshipwidget.sip +%Include auto_generated/qgsdecoratedscrollbar.sip %Include auto_generated/qgsnewdatabasetablenamewidget.sip %Include auto_generated/qgsdetaileditemdata.sip %Include auto_generated/qgsdetaileditemdelegate.sip @@ -309,6 +310,9 @@ %If ( HAVE_QSCI_SIP ) %Include auto_generated/codeeditors/qgscodeeditorsql.sip %End +%If ( HAVE_QSCI_SIP ) +%Include auto_generated/codeeditors/qgscodeeditorwidget.sip +%End %Include auto_generated/devtools/qgsdevtoolwidget.sip %Include auto_generated/devtools/qgsdevtoolwidgetfactory.sip %Include auto_generated/editorwidgets/core/qgseditorconfigwidget.sip diff --git a/python/console/console.py b/python/console/console.py index 1653e77200c21..adc5ff55873fa 100644 --- a/python/console/console.py +++ b/python/console/console.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -*- """ /*************************************************************************** Python Console for QGIS @@ -145,10 +144,13 @@ def __init__(self, parent=None): QWidget.__init__(self, parent) self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console")) - self.shell = ShellScintilla(self) + self.shell = ShellScintilla(console_widget=self) self.setFocusProxy(self.shell) - self.shellOut = ShellOutputScintilla(self) - self.tabEditorWidget = EditorTabWidget(self) + self.shell_output = ShellOutputScintilla( + console_widget=self, + shell_editor=self.shell + ) + self.tabEditorWidget = EditorTabWidget(console_widget=self) # ------------ UI ------------------------------- @@ -160,7 +162,7 @@ def __init__(self, parent=None): self.shellOutWidget = QWidget(self) self.shellOutWidget.setLayout(QVBoxLayout()) self.shellOutWidget.layout().setContentsMargins(0, 0, 0, 0) - self.shellOutWidget.layout().addWidget(self.shellOut) + self.shellOutWidget.layout().addWidget(self.shell_output) self.splitter = QSplitter(self.splitterEditor) self.splitter.setOrientation(Qt.Orientation.Vertical) @@ -174,7 +176,6 @@ def __init__(self, parent=None): self.splitterObj.setOrientation(Qt.Orientation.Horizontal) self.widgetEditor = QWidget(self.splitterObj) - self.widgetFind = QWidget(self) self.listClassMethod = QTreeWidget(self.splitterObj) self.listClassMethod.setColumnCount(2) @@ -186,8 +187,6 @@ def __init__(self, parent=None): # Hide side editor on start up self.splitterObj.hide() self.listClassMethod.hide() - # Hide search widget on start up - self.widgetFind.hide() icon_size = iface.iconSize(dockedToolbar=True) if iface else QSize(16, 16) @@ -315,16 +314,24 @@ def __init__(self, parent=None): self.objectListButton.setIconVisibleInMenu(True) self.objectListButton.setToolTip(objList) self.objectListButton.setText(objList) + # Action for Find text findText = QCoreApplication.translate("PythonConsole", "Find Text") - self.findTextButton = QAction(self) - self.findTextButton.setCheckable(True) - self.findTextButton.setEnabled(True) - self.findTextButton.setIcon(QgsApplication.getThemeIcon("console/iconSearchEditorConsole.svg")) - self.findTextButton.setMenuRole(QAction.MenuRole.PreferencesRole) - self.findTextButton.setIconVisibleInMenu(True) - self.findTextButton.setToolTip(findText) - self.findTextButton.setText(findText) + self.find_text_action = QAction(self) + self.find_text_action.setCheckable(True) + self.find_text_action.setEnabled(True) + self.find_text_action.setIcon(QgsApplication.getThemeIcon("console/iconSearchEditorConsole.svg")) + self.find_text_action.setMenuRole(QAction.MenuRole.PreferencesRole) + self.find_text_action.setIconVisibleInMenu(True) + self.find_text_action.setToolTip(findText) + self.find_text_action.setText(findText) + + self.tabEditorWidget.search_bar_toggled.connect( + self.find_text_action.setChecked + ) + self.find_text_action.toggled.connect( + self.tabEditorWidget.toggle_search_bar + ) # ----------------Toolbar Console------------------------------------- @@ -431,7 +438,7 @@ def __init__(self, parent=None): self.toolBarEditor.addAction(self.copyEditorButton) self.toolBarEditor.addAction(self.pasteEditorButton) self.toolBarEditor.addSeparator() - self.toolBarEditor.addAction(self.findTextButton) + self.toolBarEditor.addAction(self.find_text_action) self.toolBarEditor.addSeparator() self.toolBarEditor.addAction(self.toggleCommentEditorButton) self.toolBarEditor.addAction(self.reformatCodeEditorButton) @@ -455,10 +462,10 @@ def __init__(self, parent=None): sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.shellOut.sizePolicy().hasHeightForWidth()) - self.shellOut.setSizePolicy(sizePolicy) + sizePolicy.setHeightForWidth(self.shell_output.sizePolicy().hasHeightForWidth()) + self.shell_output.setSizePolicy(sizePolicy) - self.shellOut.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.shell_output.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.shell.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) # ------------ Layout ------------------------------- @@ -477,47 +484,6 @@ def __init__(self, parent=None): self.layoutEditor.addWidget(self.toolBarEditor, 0, 1, 1, 1) self.layoutEditor.addWidget(self.widgetButtonEditor, 1, 0, 2, 1) self.layoutEditor.addWidget(self.tabEditorWidget, 1, 1, 1, 1) - self.layoutEditor.addWidget(self.widgetFind, 2, 1, 1, 1) - - # Layout for the find widget - self.layoutFind = QGridLayout(self.widgetFind) - self.layoutFind.setContentsMargins(0, 0, 0, 0) - self.lineEditFind = QgsFilterLineEdit() - self.lineEditFind.setShowSearchIcon(True) - placeHolderTxt = QCoreApplication.translate("PythonConsole", "Enter text to find…") - - self.lineEditFind.setPlaceholderText(placeHolderTxt) - self.toolBarFindText = QToolBar() - self.toolBarFindText.setIconSize(icon_size) - self.findNextButton = QAction(self) - self.findNextButton.setEnabled(False) - toolTipfindNext = QCoreApplication.translate("PythonConsole", "Find Next") - self.findNextButton.setToolTip(toolTipfindNext) - self.findNextButton.setIcon(QgsApplication.getThemeIcon("console/iconSearchNextEditorConsole.svg")) - self.findPrevButton = QAction(self) - self.findPrevButton.setEnabled(False) - toolTipfindPrev = QCoreApplication.translate("PythonConsole", "Find Previous") - self.findPrevButton.setToolTip(toolTipfindPrev) - self.findPrevButton.setIcon(QgsApplication.getThemeIcon("console/iconSearchPrevEditorConsole.svg")) - self.caseSensitive = QCheckBox() - caseSensTr = QCoreApplication.translate("PythonConsole", "Case Sensitive") - self.caseSensitive.setText(caseSensTr) - self.wholeWord = QCheckBox() - wholeWordTr = QCoreApplication.translate("PythonConsole", "Whole Word") - self.wholeWord.setText(wholeWordTr) - self.wrapAround = QCheckBox() - self.wrapAround.setChecked(True) - wrapAroundTr = QCoreApplication.translate("PythonConsole", "Wrap Around") - self.wrapAround.setText(wrapAroundTr) - - self.toolBarFindText.addWidget(self.lineEditFind) - self.toolBarFindText.addAction(self.findPrevButton) - self.toolBarFindText.addAction(self.findNextButton) - self.toolBarFindText.addWidget(self.caseSensitive) - self.toolBarFindText.addWidget(self.wholeWord) - self.toolBarFindText.addWidget(self.wrapAround) - - self.layoutFind.addWidget(self.toolBarFindText, 0, 1, 1, 1) # ------------ Add first Tab in Editor ------------------------------- @@ -525,7 +491,6 @@ def __init__(self, parent=None): # ------------ Signal ------------------------------- - self.findTextButton.triggered.connect(self._toggleFind) self.objectListButton.toggled.connect(self.toggleObjectListWidget) self.toggleCommentEditorButton.triggered.connect(self.toggleComment) self.reformatCodeEditorButton.triggered.connect(self.reformatCode) @@ -534,7 +499,7 @@ def __init__(self, parent=None): self.copyEditorButton.triggered.connect(self.copyEditor) self.pasteEditorButton.triggered.connect(self.pasteEditor) self.showEditorButton.toggled.connect(self.toggleEditor) - self.clearButton.triggered.connect(self.shellOut.clearConsole) + self.clearButton.triggered.connect(self.shell_output.clearConsole) self.optionsButton.triggered.connect(self.openSettings) self.runButton.triggered.connect(self.shell.entered) self.openFileButton.triggered.connect(self.openScriptFile) @@ -545,27 +510,6 @@ def __init__(self, parent=None): self.helpAPIAction.triggered.connect(self.openHelpAPI) self.helpCookbookAction.triggered.connect(self.openHelpCookbook) self.listClassMethod.itemClicked.connect(self.onClickGoToLine) - self.lineEditFind.returnPressed.connect(self._findNext) - self.findNextButton.triggered.connect(self._findNext) - self.findPrevButton.triggered.connect(self._findPrev) - self.lineEditFind.textChanged.connect(self._textFindChanged) - - self.findScut = QShortcut(QKeySequence.StandardKey.Find, self.widgetEditor) - self.findScut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) - self.findScut.activated.connect(self._openFind) - - self.findNextScut = QShortcut(QKeySequence.StandardKey.FindNext, self.widgetEditor) - self.findNextScut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) - self.findNextScut.activated.connect(self._findNext) - - self.findPreviousScut = QShortcut(QKeySequence.StandardKey.FindPrevious, self.widgetEditor) - self.findPreviousScut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) - self.findPreviousScut.activated.connect(self._findPrev) - - # Escape on editor hides the find bar - self.findScut = QShortcut(Qt.Key.Key_Escape, self.widgetEditor) - self.findScut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) - self.findScut.activated.connect(self._closeFind) if iface is not None: self.exit_blocker = ConsoleExitBlocker(self) @@ -598,28 +542,6 @@ def allowExit(self): def _toggleFind(self): self.tabEditorWidget.currentWidget().toggleFindWidget() - def _openFind(self): - self.tabEditorWidget.currentWidget().openFindWidget() - - def _closeFind(self): - self.tabEditorWidget.currentWidget().closeFindWidget() - - def _findNext(self): - self.tabEditorWidget.currentWidget().findText(True) - - def _findPrev(self): - self.tabEditorWidget.currentWidget().findText(False) - - def _textFindChanged(self): - if self.lineEditFind.text(): - self.findNextButton.setEnabled(True) - self.findPrevButton.setEnabled(True) - self.tabEditorWidget.currentWidget().findText(True, showMessage=False, findFirst=True) - else: - self.lineEditFind.setStyleSheet('') - self.findNextButton.setEnabled(False) - self.findPrevButton.setEnabled(False) - def onClickGoToLine(self, item, column): tabEditor = self.tabEditorWidget.currentWidget() if item.text(1) == 'syntaxError': @@ -756,14 +678,14 @@ def openSettings(self): def updateSettings(self): self.shell.refreshSettingsShell() - self.shellOut.refreshSettingsOutput() + self.shell_output.refreshSettingsOutput() self.tabEditorWidget.refreshSettingsEditor() def callWidgetMessageBar(self, text): - self.shellOut.widgetMessageBar(iface, text) + self.shell_output.widgetMessageBar(text) - def callWidgetMessageBarEditor(self, text, level, timeout): - self.tabEditorWidget.showMessage(text, level, timeout) + def callWidgetMessageBarEditor(self, text, level): + self.tabEditorWidget.showMessage(text, level) def updateTabListScript(self, script, action=None): if action == 'remove': diff --git a/python/console/console_compile_apis.py b/python/console/console_compile_apis.py index 73634f4d20ffc..32204bb4ebc2d 100644 --- a/python/console/console_compile_apis.py +++ b/python/console/console_compile_apis.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -*- """ /*************************************************************************** Module to generate prepared APIs for calltips and auto-completion. diff --git a/python/console/console_editor.py b/python/console/console_editor.py index 58fc0c3fc296e..9c3921bac3e1e 100644 --- a/python/console/console_editor.py +++ b/python/console/console_editor.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -*- """ /*************************************************************************** Python Console for QGIS @@ -18,6 +17,7 @@ ***************************************************************************/ Some portions of code were taken from https://code.google.com/p/pydee/ """ +from __future__ import annotations import codecs import importlib @@ -26,15 +26,34 @@ import re import sys import tempfile -from typing import Optional +from typing import ( + Optional, + TYPE_CHECKING +) from functools import partial from operator import itemgetter from pathlib import Path from qgis.core import Qgis, QgsApplication, QgsBlockingNetworkRequest, QgsSettings -from qgis.gui import QgsCodeEditorPython, QgsMessageBar +from qgis.gui import ( + QgsCodeEditorPython, + QgsCodeEditorWidget, + QgsMessageBar +) + from qgis.PyQt.Qsci import QsciScintilla -from qgis.PyQt.QtCore import QByteArray, QCoreApplication, QDir, QEvent, QFileInfo, QJsonDocument, QSize, Qt, QUrl +from qgis.PyQt.QtCore import ( + pyqtSignal, + QByteArray, + QCoreApplication, + QDir, + QEvent, + QFileInfo, + QJsonDocument, + QSize, + Qt, + QUrl +) from qgis.PyQt.QtGui import QKeySequence from qgis.PyQt.QtNetwork import QNetworkRequest from qgis.PyQt.QtWidgets import ( @@ -56,12 +75,23 @@ ) from qgis.utils import OverrideCursor, iface +if TYPE_CHECKING: + from .console import PythonConsoleWidget + class Editor(QgsCodeEditorPython): - def __init__(self, parent=None): - super().__init__(parent) - self.parent = parent + trigger_find = pyqtSignal() + + def __init__(self, + editor_tab: EditorTab, + console_widget: PythonConsoleWidget, + tab_widget: EditorTabWidget): + super().__init__(editor_tab) + self.editor_tab: EditorTab = editor_tab + self.console_widget: PythonConsoleWidget = console_widget + self.tab_widget: EditorTabWidget = tab_widget + self.path: Optional[str] = None # recent modification time self.lastModified = 0 @@ -93,7 +123,7 @@ def __init__(self, parent=None): self.syntaxCheckScut = QShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_4), self) self.syntaxCheckScut.setContext(Qt.ShortcutContext.WidgetShortcut) self.syntaxCheckScut.activated.connect(self.syntaxCheck) - self.modificationChanged.connect(self.parent.modified) + self.modificationChanged.connect(self.editor_tab.modified) self.modificationAttempted.connect(self.fileReadOnly) def settingsEditor(self): @@ -154,7 +184,7 @@ def contextMenuEvent(self, e): QgsApplication.getThemeIcon("console/iconSearchEditorConsole.svg"), QCoreApplication.translate("PythonConsole", "Find Text"), menu) - find_action.triggered.connect(self.openFindWidget) + find_action.triggered.connect(self.trigger_find) menu.addAction(find_action) cutAction = QAction( @@ -210,7 +240,7 @@ def contextMenuEvent(self, e): menu.addSeparator() menu.addAction(QgsApplication.getThemeIcon("console/iconSettingsConsole.svg"), QCoreApplication.translate("PythonConsole", "Options…"), - self.pythonconsole.openSettings) + self.console_widget.openSettings) syntaxCheckAction.setEnabled(False) pasteAction.setEnabled(False) pyQGISHelpAction.setEnabled(False) @@ -242,64 +272,27 @@ def contextMenuEvent(self, e): showCodeInspection.setEnabled(True) menu.exec(self.mapToGlobal(e.pos())) - def findText(self, forward, showMessage=True, findFirst=False): - lineFrom, indexFrom, lineTo, indexTo = self.getSelection() - if findFirst: - line = 0 - index = 0 - else: - line, index = self.getCursorPosition() - text = self.pythonconsole.lineEditFind.text() - re = False - wrap = self.pythonconsole.wrapAround.isChecked() - cs = self.pythonconsole.caseSensitive.isChecked() - wo = self.pythonconsole.wholeWord.isChecked() - notFound = False - if text: - if not forward: - line = lineFrom - index = indexFrom - # findFirst(QString(), re bool, cs bool, wo bool, wrap, bool, forward=True) - # re = Regular Expression, cs = Case Sensitive, wo = Whole Word, wrap = Wrap Around - if not self.findFirst(text, re, cs, wo, wrap, forward, line, index): - notFound = True - if notFound: - styleError = 'QLineEdit {background-color: #d65253; \ - color: #ffffff;}' - if showMessage: - msgText = QCoreApplication.translate('PythonConsole', - '"{0}" was not found.').format(text) - self.parent.showMessage(msgText) - else: - styleError = '' - self.pythonconsole.lineEditFind.setStyleSheet(styleError) - - def findNext(self): - self.findText(True) - - def findPrevious(self): - self.findText(False) - def objectListEditor(self): - listObj = self.pythonconsole.listClassMethod + listObj = self.console_widget.listClassMethod if listObj.isVisible(): listObj.hide() - self.pythonconsole.objectListButton.setChecked(False) + self.console_widget.objectListButton.setChecked(False) else: listObj.show() - self.pythonconsole.objectListButton.setChecked(True) + self.console_widget.objectListButton.setChecked(True) def shareOnGist(self, is_public): ACCESS_TOKEN = QgsSettings().value("pythonConsole/accessTokenGithub", '', type=QByteArray) if not ACCESS_TOKEN: msg_text = QCoreApplication.translate( 'PythonConsole', 'GitHub personal access token must be generated (see Console Options)') - self.parent.showMessage(msg_text, Qgis.MessageLevel.Warning, 5) + self.showMessage(msg_text, + level=Qgis.MessageLevel.Warning) return URL = "https://api.github.com/gists" - path = self.tabwidget.currentWidget().path + path = self.tab_widget.currentWidget().path filename = os.path.basename(path) if path else None filename = filename if filename else "pyqgis_snippet.py" @@ -321,34 +314,15 @@ def shareOnGist(self, is_public): link = _json.object()['html_url'].toString() QApplication.clipboard().setText(link) msg = QCoreApplication.translate('PythonConsole', 'URL copied to clipboard.') - self.parent.showMessage(msg) + self.showMessage(msg) else: msg = QCoreApplication.translate('PythonConsole', 'Connection error: ') - self.parent.showMessage(msg + request.erroMessage(), Qgis.MessageLevel.Warning, 5) + self.showMessage(msg + request.erroMessage(), + level=Qgis.MessageLevel.Warning) def hideEditor(self): - self.pythonconsole.splitterObj.hide() - self.pythonconsole.showEditorButton.setChecked(False) - - def openFindWidget(self): - wF = self.pythonconsole.widgetFind - wF.show() - if self.hasSelectedText(): - self.pythonconsole.lineEditFind.setText(self.selectedText().strip()) - self.pythonconsole.lineEditFind.setFocus() - self.pythonconsole.findTextButton.setChecked(True) - - def closeFindWidget(self): - wF = self.pythonconsole.widgetFind - wF.hide() - self.pythonconsole.findTextButton.setChecked(False) - - def toggleFindWidget(self): - wF = self.pythonconsole.widgetFind - if wF.isVisible(): - self.closeFindWidget() - else: - self.openFindWidget() + self.console_widget.splitterObj.hide() + self.console_widget.showEditorButton.setChecked(False) def createTempFile(self): name = tempfile.NamedTemporaryFile(delete=False).name @@ -359,14 +333,14 @@ def createTempFile(self): def runScriptCode(self): autoSave = QgsSettings().value("pythonConsole/autoSaveScript", False, type=bool) - tabWidget = self.tabwidget.currentWidget() + tabWidget = self.tab_widget.currentWidget() filename = tabWidget.path filename_override = None msgEditorBlank = QCoreApplication.translate('PythonConsole', 'Hey, type something to run!') if filename is None: if not self.isModified(): - self.parent.showMessage(msgEditorBlank) + self.showMessage(msgEditorBlank) return deleteTempFile = False @@ -376,20 +350,20 @@ def runScriptCode(self): elif not filename or self.isModified(): # Create a new temp file if the file isn't already saved. filename = self.createTempFile() - filename_override = self.tabwidget.tabText(self.tabwidget.currentIndex()) + filename_override = self.tab_widget.tabText(self.tab_widget.currentIndex()) if filename_override.startswith('*'): filename_override = filename_override[1:] deleteTempFile = True - self.pythonconsole.shell.runFile(filename, filename_override) + self.console_widget.shell.runFile(filename, filename_override) if deleteTempFile: Path(filename).unlink() def runSelectedCode(self): # spellok cmd = self.selectedText() - self.pythonconsole.shell.insertFromDropPaste(cmd) - self.pythonconsole.shell.entered() + self.console_widget.shell.insertFromDropPaste(cmd) + self.console_widget.shell.entered() self.setFocus() def getTextFromEditor(self): @@ -431,7 +405,8 @@ def focusInEvent(self, e): if not QFileInfo(self.path).exists(): msgText = QCoreApplication.translate('PythonConsole', 'The file "{0}" has been deleted or is not accessible').format(self.path) - self.parent.showMessage(msgText, Qgis.MessageLevel.Critical) + self.showMessage(msgText, + level=Qgis.MessageLevel.Critical) return if self.path and self.lastModified != QFileInfo(self.path).lastModified(): self.beginUndoAction() @@ -441,21 +416,21 @@ def focusInEvent(self, e): self.setModified(False) self.endUndoAction() - self.tabwidget.listObject(self.tabwidget.currentWidget()) + self.tab_widget.listObject(self.tab_widget.currentWidget()) self.lastModified = QFileInfo(self.path).lastModified() super().focusInEvent(e) def fileReadOnly(self): - tabWidget = self.tabwidget.currentWidget() + tabWidget = self.tab_widget.currentWidget() msgText = QCoreApplication.translate('PythonConsole', 'The file "{0}" is read only, please save to different file first.').format(tabWidget.path) - self.parent.showMessage(msgText, Qgis.MessageLevel.Warning) + self.showMessage(msgText) - def loadFile(self, filename, readOnly=False): + def loadFile(self, filename: str, read_only: bool = False): self.lastModified = QFileInfo(filename).lastModified() self.path = filename self.setText(Path(filename).read_text(encoding='utf-8')) - self.setReadOnly(readOnly) + self.setReadOnly(read_only) self.setModified(False) self.recolor() @@ -466,7 +441,7 @@ def save(self, filename: Optional[str] = None): if QgsSettings().value("pythonConsole/formatOnSave", False, type=bool): self.reformatCode() - index = self.tabwidget.indexOf(self.parent) + index = self.tab_widget.indexOf(self.editor_tab) if filename: self.path = filename if not self.path: @@ -475,7 +450,7 @@ def save(self, filename: Optional[str] = None): folder = QgsSettings().value("pythonConsole/lastDirPath", QDir.homePath()) self.path, filter = QFileDialog().getSaveFileName(self, saveTr, - os.path.join(folder, self.tabwidget.tabText(index).replace('*', '') + '.py'), + os.path.join(folder, self.tab_widget.tabText(index).replace('*', '') + '.py'), "Script file (*.py)") # If the user didn't select a file, abort the save operation if not self.path: @@ -484,19 +459,19 @@ def save(self, filename: Optional[str] = None): msgText = QCoreApplication.translate('PythonConsole', 'Script was correctly saved.') - self.parent.showMessage(msgText) + self.showMessage(msgText) # Save the new contents # Need to use newline='' to avoid adding extra \r characters on Windows with open(self.path, 'w', encoding='utf-8', newline='') as f: f.write(self.text()) - self.tabwidget.setTabTitle(index, Path(self.path).name) - self.tabwidget.setTabToolTip(index, self.path) + self.tab_widget.setTabTitle(index, Path(self.path).name) + self.tab_widget.setTabToolTip(index, self.path) self.setModified(False) - self.pythonconsole.saveFileButton.setEnabled(False) + self.console_widget.saveFileButton.setEnabled(False) self.lastModified = QFileInfo(self.path).lastModified() - self.pythonconsole.updateTabListScript(self.path, action='append') - self.tabwidget.listObject(self.parent) + self.console_widget.updateTabListScript(self.path, action='append') + self.tab_widget.listObject(self.editor_tab) QgsSettings().setValue("pythonConsole/lastDirPath", Path(self.path).parent.as_posix()) @@ -525,11 +500,11 @@ def keyPressEvent(self, e): # Ctrl+W: close current tab if ctrl and e.key() == Qt.Key.Key_W: - self.parent.close() + self.editor_tab.close() # Ctrl+Shift+W: close all tabs if ctrl_shift and e.key() == Qt.Key.Key_W: - self.tabwidget.closeAll() + self.tab_widget.closeAll() # Ctrl+S: save current tab if ctrl and e.key() == Qt.Key.Key_S: @@ -537,51 +512,79 @@ def keyPressEvent(self, e): # Ctrl+Shift+S: save current tab as if ctrl_shift and e.key() == Qt.Key.Key_S: - self.tabwidget.saveAs() + self.tab_widget.saveAs() # Ctrl+T: open new tab if ctrl and e.key() == Qt.Key.Key_T: - self.tabwidget.newTabEditor() + self.tab_widget.newTabEditor() super().keyPressEvent(e) - def showMessage(self, title, text, level): - self.parent.showMessage(text, level, title=title) + def showMessage(self, + text: str, + title: Optional[str] = None, + level=Qgis.MessageLevel.Info): + self.editor_tab.showMessage(text, level, title=title) class EditorTab(QWidget): - def __init__(self, parent, pythonconsole, filename, readOnly): - super().__init__(parent) - self.tabwidget = parent + search_bar_toggled = pyqtSignal(bool) + + def __init__(self, + tab_widget: EditorTabWidget, + console_widget: PythonConsoleWidget, + filename: Optional[str], + read_only: bool): + super().__init__(tab_widget) + self.tab_widget: EditorTabWidget = tab_widget + + self._editor = Editor(editor_tab=self, + console_widget=console_widget, + tab_widget=tab_widget) + + self._editor_code_widget = QgsCodeEditorWidget( + self._editor + ) + self._editor_code_widget.searchBarToggled.connect( + self.search_bar_toggled + ) + + self._editor.trigger_find.connect( + self._editor_code_widget.triggerFind + ) - self._editor = Editor(self) - self._editor.pythonconsole = pythonconsole - self._editor.tabwidget = parent if filename: if QFileInfo(filename).exists(): - self._editor.loadFile(filename, readOnly) - - # Creates layout for message bar - self.layout = QGridLayout(self._editor) - self.layout.setContentsMargins(0, 0, 0, 0) - spacerItem = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) - self.layout.addItem(spacerItem, 1, 0, 1, 1) - # messageBar instance - self.infoBar = QgsMessageBar() - sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) - self.infoBar.setSizePolicy(sizePolicy) - self.layout.addWidget(self.infoBar, 0, 0, 1, 1) + self._editor.loadFile(filename, read_only) self.tabLayout = QGridLayout(self) self.tabLayout.setContentsMargins(0, 0, 0, 0) - self.tabLayout.addWidget(self._editor) + self.tabLayout.addWidget(self._editor_code_widget) def modified(self, modified): - self.tabwidget.tabModified(self, modified) + self.tab_widget.tabModified(self, modified) + + def search_bar_visible(self) -> bool: + """ + Returns True if the tab's search bar is visible + """ + return self._editor_code_widget.isSearchBarVisible() + + def trigger_find(self): + """ + Triggers a find operation using the default behavior + """ + self._editor_code_widget.triggerFind() + + def hide_search_bar(self): + """ + Hides the search bar + """ + self._editor_code_widget.hideSearchBar() def close(self): - self.tabwidget._removeTab(self, tab2index=True) + self.tab_widget._removeTab(self, tab2index=True) def __getattr__(self, name): """ Forward all missing attribute requests to the editor.""" @@ -598,14 +601,16 @@ def __setattr__(self, name, value): return setattr(self._editor, name, value) def showMessage(self, text, level=Qgis.MessageLevel.Info, timeout=-1, title=""): - self.infoBar.pushMessage(title, text, level, timeout) + self._editor_code_widget.messageBar().pushMessage(title, text, level, timeout) class EditorTabWidget(QTabWidget): - def __init__(self, parent): + search_bar_toggled = pyqtSignal(bool) + + def __init__(self, console_widget: PythonConsoleWidget): super().__init__(parent=None) - self.parent = parent + self.console_widget: PythonConsoleWidget = console_widget self.idx = -1 # Layout for top frame (restore tabs) @@ -699,6 +704,19 @@ def _currentWidgetChanged(self, tab): self.changeLastDirPath(tab) self.enableSaveIfModified(tab) + self.search_bar_toggled.emit( + self.currentWidget().search_bar_visible() + ) + + def toggle_search_bar(self, visible: bool): + """ + Toggles whether the search bar should be visible + """ + if visible and not self.currentWidget().search_bar_visible(): + self.currentWidget().trigger_find() + elif not visible and self.currentWidget().search_bar_visible(): + self.currentWidget().hide_search_bar() + def contextMenuEvent(self, e): tabBar = self.tabBar() self.idx = tabBar.tabAt(e.pos()) @@ -753,22 +771,22 @@ def closeAll(self): self._removeTab(0) def saveAs(self): - self.parent.saveAsScriptFile(self.idx) + self.console_widget.saveAsScriptFile(self.idx) def enableSaveIfModified(self, tab): tabWidget = self.widget(tab) if tabWidget: - self.parent.saveFileButton.setEnabled(tabWidget.isModified()) + self.console_widget.saveFileButton.setEnabled(tabWidget.isModified()) def enableToolBarEditor(self, enable): if self.topFrame.isVisible(): enable = False - self.parent.toolBarEditor.setEnabled(enable) + self.console_widget.toolBarEditor.setEnabled(enable) - def newTabEditor(self, tabName=None, filename=None): - readOnly = False + def newTabEditor(self, tabName=None, filename: Optional[str] = None): + read_only = False if filename: - readOnly = not QFileInfo(filename).isWritable() + read_only = not QFileInfo(filename).isWritable() try: fn = codecs.open(filename, "rb", encoding='utf-8') fn.read() @@ -784,20 +802,31 @@ def newTabEditor(self, tabName=None, filename=None): nr = self.count() if not tabName: tabName = QCoreApplication.translate('PythonConsole', 'Untitled-{0}').format(nr) - tab = EditorTab(self, self.parent, filename, readOnly) + tab = EditorTab(tab_widget=self, + console_widget=self.console_widget, + filename=filename, + read_only=read_only) self.iconTab = QgsApplication.getThemeIcon('console/iconTabEditorConsole.svg') - self.addTab(tab, self.iconTab, tabName + ' (ro)' if readOnly else tabName) + self.addTab(tab, self.iconTab, tabName + ' (ro)' if read_only else tabName) self.setCurrentWidget(tab) if filename: self.setTabToolTip(self.currentIndex(), filename) else: self.setTabToolTip(self.currentIndex(), tabName) + tab.search_bar_toggled.connect(self._tab_search_bar_toggled) + + def _tab_search_bar_toggled(self, visible: bool): + if self.sender() != self.currentWidget(): + return + + self.search_bar_toggled.emit(visible) + def tabModified(self, tab, modified): index = self.indexOf(tab) s = self.tabText(index) self.setTabTitle(index, '*{}'.format(s) if modified else re.sub(r'^(\*)', '', s)) - self.parent.saveFileButton.setEnabled(modified) + self.console_widget.saveFileButton.setEnabled(modified) def setTabTitle(self, tab, title): self.setTabText(tab, title) @@ -819,13 +848,13 @@ def _removeTab(self, tab, tab2index=False): if res == QMessageBox.StandardButton.Save: editorTab.save() if editorTab.path: - self.parent.updateTabListScript(editorTab.path, action='remove') + self.console_widget.updateTabListScript(editorTab.path, action='remove') self.removeTab(tab) if self.count() < 1: self.newTabEditor() else: if editorTab.path: - self.parent.updateTabListScript(editorTab.path, action='remove') + self.console_widget.updateTabListScript(editorTab.path, action='remove') if self.count() <= 1: self.removeTab(tab) self.newTabEditor() @@ -860,7 +889,7 @@ def restoreTabs(self): print('## Error: ') s = errOnRestore sys.stderr.write(s) - self.parent.updateTabListScript(pathFile, action='remove') + self.console_widget.updateTabListScript(pathFile, action='remove') if self.count() < 1: self.newTabEditor(filename=None) self.topFrame.close() @@ -868,7 +897,7 @@ def restoreTabs(self): self.currentWidget()._editor.setFocus(Qt.FocusReason.TabFocusReason) def closeRestore(self): - self.parent.updateTabListScript(None) + self.console_widget.updateTabListScript(None) self.topFrame.close() self.newTabEditor(filename=None) self.enableToolBarEditor(True) @@ -885,7 +914,7 @@ def showFileTabMenuTriggered(self, action): self.setCurrentIndex(index) def listObject(self, tab): - self.parent.listClassMethod.clear() + self.console_widget.listClassMethod.clear() if isinstance(tab, EditorTab): tabWidget = self.widget(self.indexOf(tab)) else: @@ -938,7 +967,7 @@ def listObject(self, tab): methodItem.setSizeHint(0, QSize(18, 18)) classItem.addChild(methodItem) dictObject[meth] = lineno - self.parent.listClassMethod.addTopLevelItem(classItem) + self.console_widget.listClassMethod.addTopLevelItem(classItem) for func_name, data in sorted(list(readModuleFunction.items()), key=lambda x: x[1].lineno): if isinstance(data, pyclbr.Function) and \ os.path.normpath(data.file) == os.path.normpath(tabWidget.path): @@ -951,7 +980,7 @@ def listObject(self, tab): if sys.platform.startswith('win'): funcItem.setSizeHint(0, QSize(18, 18)) dictObject[func_name] = data.lineno - self.parent.listClassMethod.addTopLevelItem(funcItem) + self.console_widget.listClassMethod.addTopLevelItem(funcItem) if found: sys.path.remove(pathFile) except: @@ -960,18 +989,18 @@ def listObject(self, tab): msgItem.setText(1, 'syntaxError') iconWarning = QgsApplication.getThemeIcon("console/iconSyntaxErrorConsole.svg") msgItem.setIcon(0, iconWarning) - self.parent.listClassMethod.addTopLevelItem(msgItem) + self.console_widget.listClassMethod.addTopLevelItem(msgItem) def refreshSettingsEditor(self): objInspectorEnabled = QgsSettings().value("pythonConsole/enableObjectInsp", False, type=bool) - listObj = self.parent.objectListButton - if self.parent.listClassMethod.isVisible(): + listObj = self.console_widget.objectListButton + if self.console_widget.listClassMethod.isVisible(): listObj.setChecked(objInspectorEnabled) listObj.setEnabled(objInspectorEnabled) if objInspectorEnabled: cW = self.currentWidget() - if cW and not self.parent.listClassMethod.isVisible(): + if cW and not self.console_widget.listClassMethod.isVisible(): with OverrideCursor(Qt.CursorShape.WaitCursor): self.listObject(cW) diff --git a/python/console/console_output.py b/python/console/console_output.py index 1dcbe93676cfd..a07c79d3e021c 100644 --- a/python/console/console_output.py +++ b/python/console/console_output.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -*- """ /*************************************************************************** Python Console for QGIS @@ -18,6 +17,10 @@ ***************************************************************************/ Some portions of code were taken from https://code.google.com/p/pydee/ """ +from __future__ import annotations + +import sys +from typing import TYPE_CHECKING from qgis.PyQt import sip from qgis.PyQt.QtCore import Qt, QCoreApplication, QThread, QMetaObject, Q_ARG, QObject, pyqtSlot @@ -26,7 +29,10 @@ from qgis.PyQt.Qsci import QsciScintilla from qgis.core import Qgis, QgsApplication, QgsSettings from qgis.gui import QgsMessageBar, QgsCodeEditorPython -import sys + +if TYPE_CHECKING: + from .console import PythonConsoleWidget + from .console_sci import ShellScintilla class writeOut(QObject): @@ -117,10 +123,12 @@ def isatty(self): class ShellOutputScintilla(QgsCodeEditorPython): - def __init__(self, parent=None): - super().__init__(parent) - self.parent = parent - self.shell = self.parent.shell + def __init__(self, + console_widget: PythonConsoleWidget, + shell_editor: ShellScintilla): + super().__init__(console_widget) + self.console_widget: PythonConsoleWidget = console_widget + self.shell_editor: ShellScintilla = shell_editor # Creates layout for message bar self.layout = QGridLayout(self) @@ -202,7 +210,7 @@ def refreshSettingsOutput(self): def clearConsole(self): self.setText('') self.insertInitText() - self.shell.setFocus() + self.shell_editor.setFocus() def contextMenuEvent(self, e): menu = QMenu(self) @@ -254,7 +262,7 @@ def contextMenuEvent(self, e): settings_action = QAction(QgsApplication.getThemeIcon("console/iconSettingsConsole.svg"), QCoreApplication.translate("PythonConsole", "Options…"), menu) - settings_action.triggered.connect(self.parent.openSettings) + settings_action.triggered.connect(self.console_widget.openSettings) menu.addAction(settings_action) runAction.setEnabled(False) @@ -270,21 +278,21 @@ def contextMenuEvent(self, e): if not self.text(3) == '': selectAllAction.setEnabled(True) clearAction.setEnabled(True) - if self.parent.tabEditorWidget.isVisible(): + if self.console_widget.tabEditorWidget.isVisible(): showEditorAction.setEnabled(False) menu.exec(self.mapToGlobal(e.pos())) def hideToolBar(self): - tB = self.parent.toolBar + tB = self.console_widget.toolBar tB.hide() if tB.isVisible() else tB.show() - self.shell.setFocus() + self.shell_editor.setFocus() def showEditor(self): - Ed = self.parent.splitterObj + Ed = self.console_widget.splitterObj if not Ed.isVisible(): Ed.show() - self.parent.showEditorButton.setChecked(True) - self.shell.setFocus() + self.console_widget.showEditorButton.setChecked(True) + self.shell_editor.setFocus() def copy(self): """Copy text to clipboard... or keyboard interrupt""" @@ -297,21 +305,20 @@ def copy(self): def enteredSelected(self): cmd = self.selectedText() - self.shell.insertFromDropPaste(cmd) - self.shell.entered() + self.shell_editor.insertFromDropPaste(cmd) + self.shell_editor.entered() def keyPressEvent(self, e): # empty text indicates possible shortcut key sequence so stay in output txt = e.text() if len(txt) and txt >= " ": - self.shell.append(txt) - self.shell.moveCursorToEnd() - self.shell.setFocus() + self.shell_editor.append(txt) + self.shell_editor.moveCursorToEnd() + self.shell_editor.setFocus() e.ignore() else: # possible shortcut key sequence, accept it e.accept() - def widgetMessageBar(self, iface, text): - timeout = iface.messageTimeout() - self.infoBar.pushMessage(text, Qgis.MessageLevel.Info, timeout) + def widgetMessageBar(self, text: str): + self.infoBar.pushMessage(text, Qgis.MessageLevel.Info) diff --git a/python/console/console_sci.py b/python/console/console_sci.py index e80e75c06858e..ba0688c8d9df2 100644 --- a/python/console/console_sci.py +++ b/python/console/console_sci.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -*- """ /*************************************************************************** Python Console for QGIS @@ -18,13 +17,17 @@ ***************************************************************************/ Some portions of code were taken from https://code.google.com/p/pydee/ """ +from __future__ import annotations import code import os import re import sys import traceback -from typing import Optional +from typing import ( + Optional, + TYPE_CHECKING +) from pathlib import Path from tempfile import NamedTemporaryFile @@ -34,18 +37,18 @@ from qgis.PyQt.QtWidgets import QShortcut, QApplication from qgis.core import ( QgsApplication, - QgsSettings, Qgis, QgsProcessingUtils ) from qgis.gui import ( QgsCodeEditorPython, - QgsCodeEditorColorScheme, QgsCodeEditor, QgsCodeInterpreter ) from .process_wrapper import ProcessWrapper +if TYPE_CHECKING: + from .console import PythonConsoleWidget _init_statements = [ # Python @@ -150,11 +153,11 @@ def _pyqgis(object=None): class PythonInterpreter(QgsCodeInterpreter, code.InteractiveInterpreter): - def __init__(self, shell): + def __init__(self, shell: ShellScintilla): super(QgsCodeInterpreter, self).__init__() code.InteractiveInterpreter.__init__(self, locals=None) - self.shell = shell + self.shell: ShellScintilla = shell self.sub_process = None self.buffer = [] @@ -219,7 +222,7 @@ def execCommandImpl(self, cmd, show_input=True): re.findall(r'^\d.[0-9]*', Qgis.QGIS_VERSION)[0] if cmd == "?": - self.shell.parent.shellOut.insertHelp() + self.shell.console_widget.shell_output.insertHelp() elif cmd == '_pyqgis': webbrowser.open("https://qgis.org/pyqgis/{}".format(version)) elif cmd == '_api': @@ -280,15 +283,15 @@ def processFinished(self, errorcode): class ShellScintilla(QgsCodeEditorPython): - def __init__(self, parent=None): + def __init__(self, console_widget: PythonConsoleWidget): # We set the ImmediatelyUpdateHistory flag here, as users can easily # crash QGIS by entering a Python command, and we don't want the - # history leading to the crash lost.. - super().__init__(parent, [], QgsCodeEditor.Mode.CommandInput, + # history leading to the crash lost... + super().__init__(console_widget, [], QgsCodeEditor.Mode.CommandInput, flags=QgsCodeEditor.Flags(QgsCodeEditor.Flag.CodeFolding | QgsCodeEditor.Flag.ImmediatelyUpdateHistory)) - self.parent = parent - self._interpreter = PythonInterpreter(self) + self.console_widget: PythonConsoleWidget = console_widget + self._interpreter = PythonInterpreter(shell=self) self.setInterpreter(self._interpreter) self.opening = ['(', '{', '[', "'", '"'] @@ -335,12 +338,12 @@ def refreshSettingsShell(self): def on_session_history_cleared(self): msgText = QCoreApplication.translate('PythonConsole', 'Session history cleared successfully.') - self.parent.callWidgetMessageBar(msgText) + self.console_widget.callWidgetMessageBar(msgText) def on_persistent_history_cleared(self): msgText = QCoreApplication.translate('PythonConsole', 'History cleared successfully.') - self.parent.callWidgetMessageBar(msgText) + self.console_widget.callWidgetMessageBar(msgText) def keyPressEvent(self, e): diff --git a/python/console/console_settings.py b/python/console/console_settings.py index 36ba6e2df4091..5c9e2b08e7eca 100644 --- a/python/console/console_settings.py +++ b/python/console/console_settings.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -*- """ /*************************************************************************** Python Console for QGIS diff --git a/python/console/process_wrapper.py b/python/console/process_wrapper.py index b18f43b4bc26d..b59d8a6ead92c 100644 --- a/python/console/process_wrapper.py +++ b/python/console/process_wrapper.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ *************************************************************************** process_wrapper.py diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 44632c05bce2b..e47789aef6aa6 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -1019,7 +1019,10 @@ QgsPalLayerSettings.BottomRight = Qgis.LabelPredefinedPointPosition.BottomRight QgsPalLayerSettings.BottomRight.is_monkey_patched = True QgsPalLayerSettings.BottomRight.__doc__ = "Label on bottom right of point" -Qgis.LabelPredefinedPointPosition.__doc__ = "Positions for labels when using the Qgis.LabelPlacement.OrderedPositionsAroundPoint placement mode.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsPalLayerSettings`.PredefinedPointPosition\n\n.. versionadded:: 3.26\n\n" + '* ``TopLeft``: ' + Qgis.LabelPredefinedPointPosition.TopLeft.__doc__ + '\n' + '* ``TopSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyLeft.__doc__ + '\n' + '* ``TopMiddle``: ' + Qgis.LabelPredefinedPointPosition.TopMiddle.__doc__ + '\n' + '* ``TopSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyRight.__doc__ + '\n' + '* ``TopRight``: ' + Qgis.LabelPredefinedPointPosition.TopRight.__doc__ + '\n' + '* ``MiddleLeft``: ' + Qgis.LabelPredefinedPointPosition.MiddleLeft.__doc__ + '\n' + '* ``MiddleRight``: ' + Qgis.LabelPredefinedPointPosition.MiddleRight.__doc__ + '\n' + '* ``BottomLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomLeft.__doc__ + '\n' + '* ``BottomSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyLeft.__doc__ + '\n' + '* ``BottomMiddle``: ' + Qgis.LabelPredefinedPointPosition.BottomMiddle.__doc__ + '\n' + '* ``BottomSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyRight.__doc__ + '\n' + '* ``BottomRight``: ' + Qgis.LabelPredefinedPointPosition.BottomRight.__doc__ +QgsPalLayerSettings.OverPoint = Qgis.LabelPredefinedPointPosition.OverPoint +QgsPalLayerSettings.OverPoint.is_monkey_patched = True +QgsPalLayerSettings.OverPoint.__doc__ = "Label directly centered over point (since QGIS 3.38)" +Qgis.LabelPredefinedPointPosition.__doc__ = "Positions for labels when using the Qgis.LabelPlacement.OrderedPositionsAroundPoint placement mode.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsPalLayerSettings`.PredefinedPointPosition\n\n.. versionadded:: 3.26\n\n" + '* ``TopLeft``: ' + Qgis.LabelPredefinedPointPosition.TopLeft.__doc__ + '\n' + '* ``TopSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyLeft.__doc__ + '\n' + '* ``TopMiddle``: ' + Qgis.LabelPredefinedPointPosition.TopMiddle.__doc__ + '\n' + '* ``TopSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.TopSlightlyRight.__doc__ + '\n' + '* ``TopRight``: ' + Qgis.LabelPredefinedPointPosition.TopRight.__doc__ + '\n' + '* ``MiddleLeft``: ' + Qgis.LabelPredefinedPointPosition.MiddleLeft.__doc__ + '\n' + '* ``MiddleRight``: ' + Qgis.LabelPredefinedPointPosition.MiddleRight.__doc__ + '\n' + '* ``BottomLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomLeft.__doc__ + '\n' + '* ``BottomSlightlyLeft``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyLeft.__doc__ + '\n' + '* ``BottomMiddle``: ' + Qgis.LabelPredefinedPointPosition.BottomMiddle.__doc__ + '\n' + '* ``BottomSlightlyRight``: ' + Qgis.LabelPredefinedPointPosition.BottomSlightlyRight.__doc__ + '\n' + '* ``BottomRight``: ' + Qgis.LabelPredefinedPointPosition.BottomRight.__doc__ + '\n' + '* ``OverPoint``: ' + Qgis.LabelPredefinedPointPosition.OverPoint.__doc__ # -- Qgis.LabelPredefinedPointPosition.baseClass = Qgis QgsPalLayerSettings.OffsetType = Qgis.LabelOffsetType @@ -1519,6 +1522,14 @@ # -- Qgis.GpsQualityIndicator.baseClass = Qgis # monkey patching scoped based enum +Qgis.GpsNavigationStatus.NotValid.__doc__ = "Navigation status not valid" +Qgis.GpsNavigationStatus.Safe.__doc__ = "Safe" +Qgis.GpsNavigationStatus.Caution.__doc__ = "Caution" +Qgis.GpsNavigationStatus.Unsafe.__doc__ = "Unsafe" +Qgis.GpsNavigationStatus.__doc__ = "GPS navigation status.\n\n.. versionadded:: 3.38\n\n" + '* ``NotValid``: ' + Qgis.GpsNavigationStatus.NotValid.__doc__ + '\n' + '* ``Safe``: ' + Qgis.GpsNavigationStatus.Safe.__doc__ + '\n' + '* ``Caution``: ' + Qgis.GpsNavigationStatus.Caution.__doc__ + '\n' + '* ``Unsafe``: ' + Qgis.GpsNavigationStatus.Unsafe.__doc__ +# -- +Qgis.GpsNavigationStatus.baseClass = Qgis +# monkey patching scoped based enum Qgis.GpsInformationComponent.Location.__doc__ = "2D location (latitude/longitude), as a QgsPointXY value" Qgis.GpsInformationComponent.Altitude.__doc__ = "Altitude/elevation above or below the mean sea level" Qgis.GpsInformationComponent.GroundSpeed.__doc__ = "Ground speed" @@ -3160,6 +3171,13 @@ # -- Qgis.FieldDomainMergePolicy.baseClass = Qgis # monkey patching scoped based enum +Qgis.FieldDuplicatePolicy.DefaultValue.__doc__ = "Use default field value" +Qgis.FieldDuplicatePolicy.Duplicate.__doc__ = "Duplicate original value" +Qgis.FieldDuplicatePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic (since QGIS 3.30)" +Qgis.FieldDuplicatePolicy.__doc__ = "Duplicate policy for fields.\n\nWhen a feature is duplicated, defines how the value of attributes are computed.\n\n.. versionadded:: 3.38\n\n" + '* ``DefaultValue``: ' + Qgis.FieldDuplicatePolicy.DefaultValue.__doc__ + '\n' + '* ``Duplicate``: ' + Qgis.FieldDuplicatePolicy.Duplicate.__doc__ + '\n' + '* ``UnsetField``: ' + Qgis.FieldDuplicatePolicy.UnsetField.__doc__ +# -- +Qgis.FieldDuplicatePolicy.baseClass = Qgis +# monkey patching scoped based enum Qgis.FieldDomainType.Coded.__doc__ = "Coded field domain" Qgis.FieldDomainType.Range.__doc__ = "Numeric range field domain (min/max)" Qgis.FieldDomainType.Glob.__doc__ = "Glob string pattern field domain" @@ -3205,7 +3223,8 @@ # monkey patching scoped based enum Qgis.MeshElevationMode.FixedElevationRange.__doc__ = "Layer has a fixed elevation range" Qgis.MeshElevationMode.FromVertices.__doc__ = "Elevation should be taken from mesh vertices" -Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ +Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ = "Layer has a fixed (manually specified) elevation range per group" +Qgis.MeshElevationMode.__doc__ = "Mesh layer elevation modes.\n\n.. versionadded:: 3.38\n\n" + '* ``FixedElevationRange``: ' + Qgis.MeshElevationMode.FixedElevationRange.__doc__ + '\n' + '* ``FromVertices``: ' + Qgis.MeshElevationMode.FromVertices.__doc__ + '\n' + '* ``FixedRangePerGroup``: ' + Qgis.MeshElevationMode.FixedRangePerGroup.__doc__ # -- Qgis.MeshElevationMode.baseClass = Qgis # monkey patching scoped based enum diff --git a/python/core/auto_additions/qgsmeshrenderersettings.py b/python/core/auto_additions/qgsmeshrenderersettings.py new file mode 100644 index 0000000000000..61188fe179720 --- /dev/null +++ b/python/core/auto_additions/qgsmeshrenderersettings.py @@ -0,0 +1,10 @@ +# The following has been generated automatically from src/core/mesh/qgsmeshrenderersettings.h +# monkey patching scoped based enum +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MetersPerSecond.__doc__ = "Meters per second" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.KilometersPerHour.__doc__ = "Kilometers per hour" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.Knots.__doc__ = "Knots (Nautical miles per hour)" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MilesPerHour.__doc__ = "Miles per hour" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.FeetPerSecond.__doc__ = "Feet per second" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.OtherUnit.__doc__ = "Other unit" +QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.__doc__ = "Wind speed units. Wind barbs use knots so we use this enum for preset conversion values\n\n" + '* ``MetersPerSecond``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MetersPerSecond.__doc__ + '\n' + '* ``KilometersPerHour``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.KilometersPerHour.__doc__ + '\n' + '* ``Knots``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.Knots.__doc__ + '\n' + '* ``MilesPerHour``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.MilesPerHour.__doc__ + '\n' + '* ``FeetPerSecond``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.FeetPerSecond.__doc__ + '\n' + '* ``OtherUnit``: ' + QgsMeshRendererVectorWindBarbSettings.WindSpeedUnit.OtherUnit.__doc__ +# -- diff --git a/python/core/auto_generated/geometry/qgslinestring.sip.in b/python/core/auto_generated/geometry/qgslinestring.sip.in index 8337f9e16d127..61ce1397087c7 100644 --- a/python/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/core/auto_generated/geometry/qgslinestring.sip.in @@ -369,7 +369,7 @@ corresponds to the last point in the line. %Docstring Returns the z-coordinate of the specified node in the line string. -If the LineString does not have a z-dimension then ``nan`` will be returned. +If the LineString does not have a z-dimension then ``NaN`` will be returned. Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1 corresponds to the last point in the line. @@ -398,7 +398,7 @@ corresponds to the last point in the line. %Docstring Returns the m-coordinate of the specified node in the line string. -If the LineString does not have a m-dimension then ``nan`` will be returned. +If the LineString does not have a m-dimension then ``NaN`` will be returned. Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1 corresponds to the last point in the line. @@ -863,13 +863,26 @@ Calculates the minimal 3D bounding box for the geometry. .. versionadded:: 3.34 %End - QgsLineString *measuredLine( double start, double end ) const /Factory/; %Docstring Re-write the measure ordinate (or add one, if it isn't already there) interpolating the measure between the supplied ``start`` and ``end`` values. .. versionadded:: 3.36 +%End + + QgsLineString *interpolateM( bool use3DDistance = true ) const /Factory/; +%Docstring +Returns a copy of this line with all missing (NaN) m values interpolated +from m values of surrounding vertices. + +If the line does not contain m values, ``None`` is returned. + +The ``use3DDistance`` controls whether 2D or 3D distances between vertices +should be used during interpolation. This option is only considered for lines +with z values. + +.. versionadded:: 3.38 %End protected: diff --git a/python/core/auto_generated/gps/qgsgpsinformation.sip.in b/python/core/auto_generated/gps/qgsgpsinformation.sip.in index e5f1c55000c5d..65f98b7219662 100644 --- a/python/core/auto_generated/gps/qgsgpsinformation.sip.in +++ b/python/core/auto_generated/gps/qgsgpsinformation.sip.in @@ -88,6 +88,24 @@ Returns the best fix status and corresponding constellation. bool satInfoComplete; + Qgis::GpsNavigationStatus navigationStatus() const; +%Docstring +Returns the navigation status. + +.. seealso:: :py:func:`setNavigationStatus` + +.. versionadded:: 3.38 +%End + + void setNavigationStatus( Qgis::GpsNavigationStatus status ); +%Docstring +Sets the navigation ``status``. + +.. seealso:: :py:func:`navigationStatus` + +.. versionadded:: 3.38 +%End + bool isValid() const; %Docstring Returns whether the connection information is valid diff --git a/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in b/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in index 02444b6b5eb1d..5e72cf12c8afe 100644 --- a/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in @@ -1115,6 +1115,13 @@ Retrieves the second fill color for the grid frame. virtual void refresh(); + void copyProperties( const QgsLayoutItemMapGrid *other ); +%Docstring +Copies properties from specified map grid. + +.. versionadded:: 3.38 +%End + signals: void crsChanged(); diff --git a/python/core/auto_generated/mesh/qgsmeshdataset.sip.in b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in index 16f891990df01..fae0e459b1c20 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataset.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataset.sip.in @@ -12,6 +12,7 @@ + class QgsMeshDatasetIndex { %Docstring(signature="appended") @@ -431,6 +432,16 @@ Constructs a valid metadata object QString name() const; %Docstring Returns name of the dataset group +%End + + QString parentQuantityName() const; +%Docstring +Returns the name of the dataset's parent quantity, if available. + +The quantity can be used to collect dataset groups which represent a single quantity +but at different values (e.g. groups which represent different elevations). + +.. versionadded:: 3.38 %End QString uri() const; diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index ad3f120c534f0..89931877d2c71 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -592,52 +592,54 @@ Dataset index is valid even the temporal properties is inactive. This method is .. versionadded:: 3.22 %End - QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) 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 +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) 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 +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. note:: the returned dataset index depends on the matching method, see :py:func:`~QgsMeshLayer.setTemporalMatchingMethod` - .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticScalarDatasetIndex() const; + QgsMeshDatasetIndex staticScalarDatasetIndex( int group = -1 ) const; %Docstring -Returns the static scalar dataset index that is rendered if the temporal properties is not active +Returns the static scalar dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End - QgsMeshDatasetIndex staticVectorDatasetIndex() const; + QgsMeshDatasetIndex staticVectorDatasetIndex( int group = -1 ) const; %Docstring -Returns the static vector dataset index that is rendered if the temporal properties is not active +Returns the static vector dataset index that is rendered if the temporal properties is not active. + +Since QGIS 3.38, the ``group`` argument can be used to specify a fixed group +to use. If this is not specified, then the active group from the layer's renderer will be used. .. versionadded:: 3.14 %End diff --git a/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in b/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in index f15a152d4cf72..28266b6c237c6 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayerelevationproperties.sip.in @@ -100,6 +100,42 @@ Sets the fixed elevation ``range`` for the mesh. .. seealso:: :py:func:`fixedRange` +.. versionadded:: 3.38 +%End + + QMap fixedRangePerGroup() const; +%Docstring +Returns the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`setFixedRangePerGroup` + +.. versionadded:: 3.38 +%End + + void setFixedRangePerGroup( const QMap &ranges ); +%Docstring +Sets the fixed elevation range for each group. + +.. note:: + + This is only considered when :py:func:`~QgsMeshLayerElevationProperties.mode` is :py:class:`Qgis`.MeshElevationMode.FixedRangePerGroup. + +.. note:: + + When a fixed range is set any :py:func:`~QgsMeshLayerElevationProperties.zOffset` and :py:func:`~QgsMeshLayerElevationProperties.zScale` is ignored. + + +.. seealso:: :py:func:`fixedRangePerGroup` + .. versionadded:: 3.38 %End diff --git a/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in b/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in index b8b6545e020ef..085a3e0d10b2d 100644 --- a/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshrenderersettings.sip.in @@ -419,6 +419,85 @@ Writes configuration to a new DOM element }; +class QgsMeshRendererVectorWindBarbSettings +{ +%Docstring(signature="appended") + +Represents a mesh renderer settings for vector datasets displayed with wind barbs + +.. note:: + + The API is considered EXPERIMENTAL and can be changed without a notice + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsmeshrenderersettings.h" +%End + public: + enum class WindSpeedUnit + { + MetersPerSecond, + KilometersPerHour, + Knots, + MilesPerHour, + FeetPerSecond, + OtherUnit + }; + + double magnitudeMultiplier() const; +%Docstring +Returns the multiplier for the magnitude to convert it to knots, according to the units set with :py:func:`~QgsMeshRendererVectorWindBarbSettings.setMagnitudeUnits` +A custom multiplier can be set with :py:func:`~QgsMeshRendererVectorWindBarbSettings.setMagnitudeMultiplier` for the case when units are set to OtherUnit +%End + + void setMagnitudeMultiplier( double magnitudeMultiplier ); +%Docstring +Sets a multiplier for the magnitude to convert it to knots +%End + + double shaftLength() const; +%Docstring +Returns the shaft length (in millimeters) +%End + + void setShaftLength( double shaftLength ); +%Docstring +Sets the shaft length (in millimeters) +%End + + Qgis::RenderUnit shaftLengthUnits(); +%Docstring +Sets the units for the shaft length +%End + + void setShaftLengthUnits( Qgis::RenderUnit shaftLengthUnit ); +%Docstring +Returns the units for the shaft length +%End + + WindSpeedUnit magnitudeUnits() const; +%Docstring +Returns the units that the data are in +%End + + void setMagnitudeUnits( WindSpeedUnit units ); +%Docstring +Sets the units that the data are in +%End + + QDomElement writeXml( QDomDocument &doc ) const; +%Docstring +Writes configuration to a new DOM element +%End + void readXml( const QDomElement &elem ); +%Docstring +Reads configuration from the given DOM element +%End + +}; + class QgsMeshRendererVectorSettings { %Docstring(signature="appended") @@ -444,7 +523,9 @@ Represents a renderer settings for vector datasets //! Displaying vector dataset with streamlines Streamlines, //! Displaying vector dataset with particle traces - Traces + Traces, + //! Displaying vector dataset with wind barbs + WindBarbs }; double lineWidth() const; @@ -609,6 +690,20 @@ Returns settings for vector rendered with traces Sets settings for vector rendered with traces .. versionadded:: 3.12 +%End + + QgsMeshRendererVectorWindBarbSettings windBarbSettings() const; +%Docstring +Returns settings for vector rendered with wind barbs + +.. versionadded:: 3.38 +%End + + void setWindBarbSettings( const QgsMeshRendererVectorWindBarbSettings &windBarbSettings ); +%Docstring +Sets settings for vector rendered with wind barbs + +.. versionadded:: 3.38 %End QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context = QgsReadWriteContext() ) const; diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in index bff0a33a107a5..1fd7f190d237d 100644 --- a/python/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in +++ b/python/core/auto_generated/processing/models/qgsprocessingmodelresult.sip.in @@ -100,6 +100,17 @@ Encapsulates the results of running a Processing model QgsProcessingModelResult(); + void clear(); +%Docstring +Clears any existing results. +%End + + void mergeWith( const QgsProcessingModelResult &other ); +%Docstring +Merges this set of results with an ``other`` set of results. + +Conflicting results from ``other`` will replace results in this object. +%End QMap< QString, QgsProcessingModelChildAlgorithmResult > childResults() const; %Docstring @@ -109,6 +120,14 @@ Map keys refer to the child algorithm IDs. %End + + + + QSet< QString > executedChildIds() const; +%Docstring +Returns the set of child algorithm IDs which were executed during the model execution. +%End + }; diff --git a/python/core/auto_generated/processing/qgsprocessingcontext.sip.in b/python/core/auto_generated/processing/qgsprocessingcontext.sip.in index 38acae6b9dd9a..db83057df622f 100644 --- a/python/core/auto_generated/processing/qgsprocessingcontext.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingcontext.sip.in @@ -657,6 +657,9 @@ Returns list of the equivalent qgis_process arguments representing the settings .. versionadded:: 3.24 %End + + + QgsProcessingModelResult modelResult() const; %Docstring Returns the model results, populated when the context is used to run a model algorithm. diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index d9f0567f5ab78..7eb6600c38a1b 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -577,6 +577,7 @@ The development version BottomMiddle, BottomSlightlyRight, BottomRight, + OverPoint, }; enum class LabelOffsetType @@ -895,6 +896,14 @@ The development version Simulation, }; + enum class GpsNavigationStatus + { + NotValid, + Safe, + Caution, + Unsafe, + }; + enum class GpsInformationComponent { Location, @@ -1826,6 +1835,13 @@ The development version GeometryWeighted, }; + enum class FieldDuplicatePolicy + { + DefaultValue, + Duplicate, + UnsetField, + }; + enum class FieldDomainType { Coded, @@ -1872,7 +1888,8 @@ The development version enum class MeshElevationMode { FixedElevationRange, - FromVertices + FromVertices, + FixedRangePerGroup, }; enum class BetweenLineConstraint diff --git a/python/core/auto_generated/qgsfield.sip.in b/python/core/auto_generated/qgsfield.sip.in index 655275c4790e3..0193040936b60 100644 --- a/python/core/auto_generated/qgsfield.sip.in +++ b/python/core/auto_generated/qgsfield.sip.in @@ -466,6 +466,26 @@ be handled during a split operation. .. seealso:: :py:func:`splitPolicy` .. versionadded:: 3.30 +%End + + Qgis::FieldDuplicatePolicy duplicatePolicy() const; +%Docstring +Returns the field's duplicate policy, which indicates how field values should +be handled during a duplicate operation. + +.. seealso:: :py:func:`setDuplicatePolicy` + +.. versionadded:: 3.38 +%End + + void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ); +%Docstring +Sets the field's duplicate ``policy``, which indicates how field values should +be handled during a duplicate operation. + +.. seealso:: :py:func:`duplicatePolicy` + +.. versionadded:: 3.38 %End SIP_PYOBJECT __repr__(); diff --git a/python/core/auto_generated/symbology/qgsheatmaprenderer.sip.in b/python/core/auto_generated/symbology/qgsheatmaprenderer.sip.in index a107425c98ea6..73149a0ab87db 100644 --- a/python/core/auto_generated/symbology/qgsheatmaprenderer.sip.in +++ b/python/core/auto_generated/symbology/qgsheatmaprenderer.sip.in @@ -60,6 +60,8 @@ Creates a new heatmap renderer instance from XML static QgsHeatmapRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) /Factory/; virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const; + virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const /Factory/; + virtual void modifyRequestExtent( QgsRectangle &extent, QgsRenderContext &context ); @@ -81,6 +83,24 @@ Sets the color ramp to use for shading the heatmap. :param ramp: color ramp for heatmap. Ownership of ramp is transferred to the renderer. .. seealso:: :py:func:`colorRamp` +%End + + const QgsColorRampLegendNodeSettings &legendSettings() const; +%Docstring +Returns the color ramp legend settings. + +.. seealso:: :py:func:`setLegendSettings` + +.. versionadded:: 3.38 +%End + + void setLegendSettings( const QgsColorRampLegendNodeSettings &settings ); +%Docstring +Sets the color ramp legend ``settings``. + +.. seealso:: :py:func:`legendSettings` + +.. versionadded:: 3.38 %End double radius() const; diff --git a/python/core/auto_generated/symbology/qgsrenderer.sip.in b/python/core/auto_generated/symbology/qgsrenderer.sip.in index 0722a00a5fe6f..a7db1eaa021a9 100644 --- a/python/core/auto_generated/symbology/qgsrenderer.sip.in +++ b/python/core/auto_generated/symbology/qgsrenderer.sip.in @@ -82,6 +82,20 @@ class QgsFeatureRenderer %End public: + enum class Property + { + HeatmapRadius, + HeatmapMaximum, + }; + + static const QgsPropertiesDefinition &propertyDefinitions(); +%Docstring +Returns the symbol property definitions. + +.. versionadded:: 3.18 +%End + + static QgsFeatureRenderer *defaultRenderer( Qgis::GeometryType geomType ) /Factory/; %Docstring Returns a new renderer - used by default in vector layers @@ -358,7 +372,22 @@ the features displayed using that key. %Docstring Returns a list of symbology items for the legend +.. seealso:: :py:func:`createLegendNodes` + .. seealso:: :py:func:`legendKeys` +%End + + virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const /Factory/; +%Docstring +Returns a list of legend nodes to be used for the legend for the renderer. + +Ownership is transferred to the caller. + +The default implementation creates a legend node for each symbol item returned by :py:func:`~QgsFeatureRenderer.legendSymbolItems` + +.. seealso:: :py:func:`legendSymbolItems` + +.. versionadded:: 3.38 %End virtual QString legendClassificationAttribute() const; @@ -434,6 +463,42 @@ Sets whether the renderer should be rendered to a raster destination. would result in a large, complex vector output. .. seealso:: :py:func:`forceRasterRender` +%End + + void setDataDefinedProperty( Property key, const QgsProperty &property ); +%Docstring +Sets a data defined property for the renderer. Any existing property with the same key +will be overwritten. + +.. seealso:: :py:func:`dataDefinedProperties` + +.. seealso:: Property + +.. versionadded:: 3.38 +%End + + QgsPropertyCollection &dataDefinedProperties(); +%Docstring +Returns a reference to the renderer's property collection, used for data defined overrides. + +.. seealso:: :py:func:`setDataDefinedProperties` + +.. seealso:: Property + +.. versionadded:: 3.38 +%End + + + void setDataDefinedProperties( const QgsPropertyCollection &collection ); +%Docstring +Sets the renderer's property collection, used for data defined overrides. + +:param collection: property collection. Existing properties will be replaced. + +.. seealso:: :py:func:`dataDefinedProperties` + + +.. versionadded:: 3.38 %End double referenceScale() const; @@ -552,6 +617,7 @@ Currently clones - Reference scale - Symbol levels enabled/disabled - Force raster render enabled/disabled +- Data defined properties :param destRenderer: destination renderer for copied effect diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index f67099d84c9f3..b54fb80e6f104 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1935,6 +1935,27 @@ Sets a split ``policy`` for the field with the specified index. } %End + void setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy ); +%Docstring +Sets a duplicate ``policy`` for the field with the specified index. + +:raises KeyError: if no field with the specified index exists + +.. versionadded:: 3.38 +%End + +%MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldDuplicatePolicy( a0, a1 ); + } +%End + QSet excludeAttributesWms() const /Deprecated/; %Docstring A set of attributes that are not advertised in WMS requests with QGIS server. diff --git a/python/gui/auto_additions/qgscodeeditorcolorscheme.py b/python/gui/auto_additions/qgscodeeditorcolorscheme.py index 3253296bc13a4..173a692ba01b2 100644 --- a/python/gui/auto_additions/qgscodeeditorcolorscheme.py +++ b/python/gui/auto_additions/qgscodeeditorcolorscheme.py @@ -35,5 +35,6 @@ QgsCodeEditorColorScheme.ColorRole.FoldIconForeground.__doc__ = "Fold icon foreground color" QgsCodeEditorColorScheme.ColorRole.FoldIconHalo.__doc__ = "Fold icon halo color" QgsCodeEditorColorScheme.ColorRole.IndentationGuide.__doc__ = "Indentation guide line" -QgsCodeEditorColorScheme.ColorRole.__doc__ = "Color roles.\n\n" + '* ``Default``: ' + QgsCodeEditorColorScheme.ColorRole.Default.__doc__ + '\n' + '* ``Keyword``: ' + QgsCodeEditorColorScheme.ColorRole.Keyword.__doc__ + '\n' + '* ``Class``: ' + QgsCodeEditorColorScheme.ColorRole.Class.__doc__ + '\n' + '* ``Method``: ' + QgsCodeEditorColorScheme.ColorRole.Method.__doc__ + '\n' + '* ``Decoration``: ' + QgsCodeEditorColorScheme.ColorRole.Decoration.__doc__ + '\n' + '* ``Number``: ' + QgsCodeEditorColorScheme.ColorRole.Number.__doc__ + '\n' + '* ``Comment``: ' + QgsCodeEditorColorScheme.ColorRole.Comment.__doc__ + '\n' + '* ``CommentLine``: ' + QgsCodeEditorColorScheme.ColorRole.CommentLine.__doc__ + '\n' + '* ``CommentBlock``: ' + QgsCodeEditorColorScheme.ColorRole.CommentBlock.__doc__ + '\n' + '* ``Background``: ' + QgsCodeEditorColorScheme.ColorRole.Background.__doc__ + '\n' + '* ``Cursor``: ' + QgsCodeEditorColorScheme.ColorRole.Cursor.__doc__ + '\n' + '* ``CaretLine``: ' + QgsCodeEditorColorScheme.ColorRole.CaretLine.__doc__ + '\n' + '* ``SingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.SingleQuote.__doc__ + '\n' + '* ``DoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.DoubleQuote.__doc__ + '\n' + '* ``TripleSingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleSingleQuote.__doc__ + '\n' + '* ``TripleDoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleDoubleQuote.__doc__ + '\n' + '* ``Operator``: ' + QgsCodeEditorColorScheme.ColorRole.Operator.__doc__ + '\n' + '* ``QuotedOperator``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedOperator.__doc__ + '\n' + '* ``Identifier``: ' + QgsCodeEditorColorScheme.ColorRole.Identifier.__doc__ + '\n' + '* ``QuotedIdentifier``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedIdentifier.__doc__ + '\n' + '* ``Tag``: ' + QgsCodeEditorColorScheme.ColorRole.Tag.__doc__ + '\n' + '* ``UnknownTag``: ' + QgsCodeEditorColorScheme.ColorRole.UnknownTag.__doc__ + '\n' + '* ``MarginBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginBackground.__doc__ + '\n' + '* ``MarginForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginForeground.__doc__ + '\n' + '* ``SelectionBackground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionBackground.__doc__ + '\n' + '* ``SelectionForeground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionForeground.__doc__ + '\n' + '* ``MatchedBraceBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceBackground.__doc__ + '\n' + '* ``MatchedBraceForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceForeground.__doc__ + '\n' + '* ``Edge``: ' + QgsCodeEditorColorScheme.ColorRole.Edge.__doc__ + '\n' + '* ``Fold``: ' + QgsCodeEditorColorScheme.ColorRole.Fold.__doc__ + '\n' + '* ``Error``: ' + QgsCodeEditorColorScheme.ColorRole.Error.__doc__ + '\n' + '* ``ErrorBackground``: ' + QgsCodeEditorColorScheme.ColorRole.ErrorBackground.__doc__ + '\n' + '* ``FoldIconForeground``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconForeground.__doc__ + '\n' + '* ``FoldIconHalo``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconHalo.__doc__ + '\n' + '* ``IndentationGuide``: ' + QgsCodeEditorColorScheme.ColorRole.IndentationGuide.__doc__ +QgsCodeEditorColorScheme.ColorRole.SearchMatchBackground.__doc__ = "Background color for search matches (since QGIS 3.38)" +QgsCodeEditorColorScheme.ColorRole.__doc__ = "Color roles.\n\n" + '* ``Default``: ' + QgsCodeEditorColorScheme.ColorRole.Default.__doc__ + '\n' + '* ``Keyword``: ' + QgsCodeEditorColorScheme.ColorRole.Keyword.__doc__ + '\n' + '* ``Class``: ' + QgsCodeEditorColorScheme.ColorRole.Class.__doc__ + '\n' + '* ``Method``: ' + QgsCodeEditorColorScheme.ColorRole.Method.__doc__ + '\n' + '* ``Decoration``: ' + QgsCodeEditorColorScheme.ColorRole.Decoration.__doc__ + '\n' + '* ``Number``: ' + QgsCodeEditorColorScheme.ColorRole.Number.__doc__ + '\n' + '* ``Comment``: ' + QgsCodeEditorColorScheme.ColorRole.Comment.__doc__ + '\n' + '* ``CommentLine``: ' + QgsCodeEditorColorScheme.ColorRole.CommentLine.__doc__ + '\n' + '* ``CommentBlock``: ' + QgsCodeEditorColorScheme.ColorRole.CommentBlock.__doc__ + '\n' + '* ``Background``: ' + QgsCodeEditorColorScheme.ColorRole.Background.__doc__ + '\n' + '* ``Cursor``: ' + QgsCodeEditorColorScheme.ColorRole.Cursor.__doc__ + '\n' + '* ``CaretLine``: ' + QgsCodeEditorColorScheme.ColorRole.CaretLine.__doc__ + '\n' + '* ``SingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.SingleQuote.__doc__ + '\n' + '* ``DoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.DoubleQuote.__doc__ + '\n' + '* ``TripleSingleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleSingleQuote.__doc__ + '\n' + '* ``TripleDoubleQuote``: ' + QgsCodeEditorColorScheme.ColorRole.TripleDoubleQuote.__doc__ + '\n' + '* ``Operator``: ' + QgsCodeEditorColorScheme.ColorRole.Operator.__doc__ + '\n' + '* ``QuotedOperator``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedOperator.__doc__ + '\n' + '* ``Identifier``: ' + QgsCodeEditorColorScheme.ColorRole.Identifier.__doc__ + '\n' + '* ``QuotedIdentifier``: ' + QgsCodeEditorColorScheme.ColorRole.QuotedIdentifier.__doc__ + '\n' + '* ``Tag``: ' + QgsCodeEditorColorScheme.ColorRole.Tag.__doc__ + '\n' + '* ``UnknownTag``: ' + QgsCodeEditorColorScheme.ColorRole.UnknownTag.__doc__ + '\n' + '* ``MarginBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginBackground.__doc__ + '\n' + '* ``MarginForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MarginForeground.__doc__ + '\n' + '* ``SelectionBackground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionBackground.__doc__ + '\n' + '* ``SelectionForeground``: ' + QgsCodeEditorColorScheme.ColorRole.SelectionForeground.__doc__ + '\n' + '* ``MatchedBraceBackground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceBackground.__doc__ + '\n' + '* ``MatchedBraceForeground``: ' + QgsCodeEditorColorScheme.ColorRole.MatchedBraceForeground.__doc__ + '\n' + '* ``Edge``: ' + QgsCodeEditorColorScheme.ColorRole.Edge.__doc__ + '\n' + '* ``Fold``: ' + QgsCodeEditorColorScheme.ColorRole.Fold.__doc__ + '\n' + '* ``Error``: ' + QgsCodeEditorColorScheme.ColorRole.Error.__doc__ + '\n' + '* ``ErrorBackground``: ' + QgsCodeEditorColorScheme.ColorRole.ErrorBackground.__doc__ + '\n' + '* ``FoldIconForeground``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconForeground.__doc__ + '\n' + '* ``FoldIconHalo``: ' + QgsCodeEditorColorScheme.ColorRole.FoldIconHalo.__doc__ + '\n' + '* ``IndentationGuide``: ' + QgsCodeEditorColorScheme.ColorRole.IndentationGuide.__doc__ + '\n' + '* ``SearchMatchBackground``: ' + QgsCodeEditorColorScheme.ColorRole.SearchMatchBackground.__doc__ # -- diff --git a/python/gui/auto_additions/qgscolorramplegendnodewidget.py b/python/gui/auto_additions/qgscolorramplegendnodewidget.py new file mode 100644 index 0000000000000..e4590dcb25e26 --- /dev/null +++ b/python/gui/auto_additions/qgscolorramplegendnodewidget.py @@ -0,0 +1,13 @@ +# The following has been generated automatically from src/gui/qgscolorramplegendnodewidget.h +# monkey patching scoped based enum +QgsColorRampLegendNodeWidget.Capability.Prefix.__doc__ = "Allow editing legend prefix" +QgsColorRampLegendNodeWidget.Capability.Suffix.__doc__ = "Allow editing legend suffix" +QgsColorRampLegendNodeWidget.Capability.NumberFormat.__doc__ = "Allow editing number format" +QgsColorRampLegendNodeWidget.Capability.DefaultMinimum.__doc__ = "Allow resetting minimum label to default" +QgsColorRampLegendNodeWidget.Capability.DefaultMaximum.__doc__ = "Allow resetting maximum label to default" +QgsColorRampLegendNodeWidget.Capability.AllCapabilities.__doc__ = "All capabilities" +QgsColorRampLegendNodeWidget.Capability.__doc__ = "Capabilities to expose in the widget.\n\n.. versionadded:: 3.38\n\n" + '* ``Prefix``: ' + QgsColorRampLegendNodeWidget.Capability.Prefix.__doc__ + '\n' + '* ``Suffix``: ' + QgsColorRampLegendNodeWidget.Capability.Suffix.__doc__ + '\n' + '* ``NumberFormat``: ' + QgsColorRampLegendNodeWidget.Capability.NumberFormat.__doc__ + '\n' + '* ``DefaultMinimum``: ' + QgsColorRampLegendNodeWidget.Capability.DefaultMinimum.__doc__ + '\n' + '* ``DefaultMaximum``: ' + QgsColorRampLegendNodeWidget.Capability.DefaultMaximum.__doc__ + '\n' + '* ``AllCapabilities``: ' + QgsColorRampLegendNodeWidget.Capability.AllCapabilities.__doc__ +# -- +QgsColorRampLegendNodeWidget.Capability.baseClass = QgsColorRampLegendNodeWidget +QgsColorRampLegendNodeWidget.Capabilities.baseClass = QgsColorRampLegendNodeWidget +Capabilities = QgsColorRampLegendNodeWidget # dirty hack since SIP seems to introduce the flags in module diff --git a/python/gui/auto_additions/qgsdecoratedscrollbar.py b/python/gui/auto_additions/qgsdecoratedscrollbar.py new file mode 100644 index 0000000000000..a9d54d7d6f82e --- /dev/null +++ b/python/gui/auto_additions/qgsdecoratedscrollbar.py @@ -0,0 +1,9 @@ +# The following has been generated automatically from src/gui/qgsdecoratedscrollbar.h +# monkey patching scoped based enum +QgsScrollBarHighlight.Priority.Invalid.__doc__ = "Invalid" +QgsScrollBarHighlight.Priority.LowPriority.__doc__ = "Low priority, rendered below all other highlights" +QgsScrollBarHighlight.Priority.NormalPriority.__doc__ = "Normal priority" +QgsScrollBarHighlight.Priority.HighPriority.__doc__ = "High priority" +QgsScrollBarHighlight.Priority.HighestPriority.__doc__ = "Highest priority, rendered above all other highlights" +QgsScrollBarHighlight.Priority.__doc__ = "Priority, which dictates how overlapping highlights are rendered\n\n" + '* ``Invalid``: ' + QgsScrollBarHighlight.Priority.Invalid.__doc__ + '\n' + '* ``LowPriority``: ' + QgsScrollBarHighlight.Priority.LowPriority.__doc__ + '\n' + '* ``NormalPriority``: ' + QgsScrollBarHighlight.Priority.NormalPriority.__doc__ + '\n' + '* ``HighPriority``: ' + QgsScrollBarHighlight.Priority.HighPriority.__doc__ + '\n' + '* ``HighestPriority``: ' + QgsScrollBarHighlight.Priority.HighestPriority.__doc__ +# -- diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in index 2a19853295445..966fefb3861e3 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in @@ -62,7 +62,6 @@ whenever the public :py:func:`~QgsCodeInterpreter.exec` method is called. - class QgsCodeEditor : QsciScintilla { %Docstring(signature="appended") @@ -103,6 +102,8 @@ A text editor based on QScintilla2. typedef QFlags Flags; + static const int SEARCH_RESULT_INDICATOR; + QgsCodeEditor( QWidget *parent /TransferThis/ = 0, const QString &title = QString(), bool folding = false, bool margin = false, QgsCodeEditor::Flags flags = QgsCodeEditor::Flags(), QgsCodeEditor::Mode mode = QgsCodeEditor::Mode::ScriptEditor ); %Docstring Construct a new code editor. diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in index 49e317000368c..162ea2db34909 100644 --- a/python/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorcolorscheme.sip.in @@ -58,6 +58,7 @@ Defines a color scheme for use in :py:class:`QgsCodeEditor` widgets. FoldIconForeground, FoldIconHalo, IndentationGuide, + SearchMatchBackground, }; QgsCodeEditorColorScheme( const QString &id = QString(), const QString &name = QString() ); diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in new file mode 100644 index 0000000000000..eaeb55ef70fd2 --- /dev/null +++ b/python/gui/auto_generated/codeeditors/qgscodeeditorwidget.sip.in @@ -0,0 +1,116 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/codeeditors/qgscodeeditorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsCodeEditorWidget : QgsPanelWidget +{ +%Docstring(signature="appended") +A widget which wraps a :py:class:`QgsCodeEditor` in additional functionality. + +This widget wraps an existing :py:class:`QgsCodeEditor` object in a widget which provides +additional standard functionality, such as search/replace tools. The caller +must create an unparented :py:class:`QgsCodeEditor` object (or a subclass of :py:class:`QgsCodeEditor`) +first, and then construct a :py:class:`QgsCodeEditorWidget` passing this object to the +constructor. + +.. note:: + + may not be available in Python bindings, depending on platform support + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgscodeeditorwidget.h" +%End + public: + + QgsCodeEditorWidget( QgsCodeEditor *editor /Transfer/, + QgsMessageBar *messageBar = 0, + QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor for QgsCodeEditorWidget, wrapping the specified ``editor`` widget. + +Ownership of ``editor`` will be transferred to this widget. + +If an explicit ``messageBar`` is specified then it will be used to provide +feedback, otherwise an integrated message bar will be used. +%End + ~QgsCodeEditorWidget(); + + QgsCodeEditor *editor(); +%Docstring +Returns the wrapped code editor. +%End + + bool isSearchBarVisible() const; +%Docstring +Returns ``True`` if the search bar is visible. +%End + + QgsMessageBar *messageBar(); +%Docstring +Returns the message bar associated with the widget, to use for user feedback. +%End + + public slots: + + void showSearchBar(); +%Docstring +Shows the search bar. + +.. seealso:: :py:func:`hideSearchBar` + +.. seealso:: :py:func:`setSearchBarVisible` +%End + + void hideSearchBar(); +%Docstring +Hides the search bar. + +.. seealso:: :py:func:`showSearchBar` + +.. seealso:: :py:func:`setSearchBarVisible` +%End + + void setSearchBarVisible( bool visible ); +%Docstring +Sets whether the search bar is ``visible``. + +.. seealso:: :py:func:`showSearchBar` + +.. seealso:: :py:func:`hideSearchBar` +%End + + void triggerFind(); +%Docstring +Triggers a find operation, using the default behavior. + +This will automatically open the search bar and start a find operation using +the default behavior, e.g. searching for any selected text in the code editor. +%End + + signals: + + void searchBarToggled( bool visible ); +%Docstring +Emitted when the visibility of the search bar is changed. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/codeeditors/qgscodeeditorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in b/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in index 105283cfb8a11..1c74dcbe86034 100644 --- a/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in +++ b/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in @@ -403,6 +403,20 @@ Sets the ``results`` obtained for this child algorithm for the last model execut signals: + void runFromHere(); +%Docstring +Emitted when the user opts to run the model from this child algorithm. + +.. versionadded:: 3.38 +%End + + void runSelected(); +%Docstring +Emitted when the user opts to run selected steps from the model. + +.. versionadded:: 3.38 +%End + void showPreviousResults(); %Docstring Emitted when the user opts to view previous results from this child algorithm. diff --git a/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in b/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in index 27f5386024aaa..eeba83f625e50 100644 --- a/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in +++ b/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in @@ -172,6 +172,20 @@ Emitted whenever a component of the model is changed. %Docstring Emitted whenever the selected item changes. If ``None``, no item is selected. +%End + + void runSelected(); +%Docstring +Emitted when the user opts to run selected steps from the model. + +.. versionadded:: 3.38 +%End + + void runFromChild( const QString &childId ); +%Docstring +Emitted when the user opts to run the part of the model starting from the specified child algorithm. + +.. versionadded:: 3.38 %End void showChildAlgorithmOutputs( const QString &childId ); diff --git a/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in b/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in index f7e34e24e9687..fccedb6a75c82 100644 --- a/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in +++ b/python/gui/auto_generated/processing/qgsprocessingalgorithmdialogbase.sip.in @@ -359,6 +359,15 @@ This allows the dialog to override default Processing settings for an individual signals: + void algorithmAboutToRun( QgsProcessingContext *context ); +%Docstring +Emitted when the algorithm is about to run in the specified ``context``. + +This signal can be used to tweak the ``context`` prior to the algorithm execution. + +.. versionadded:: 3.38 +%End + void algorithmFinished( bool successful, const QVariantMap &result ); %Docstring Emitted whenever an algorithm has finished executing in the dialog. diff --git a/python/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in b/python/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in index 0e2d50e421c81..6ebd566e919d1 100644 --- a/python/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in +++ b/python/gui/auto_generated/processing/qgsprocessinghistoryprovider.sip.in @@ -35,6 +35,8 @@ This should only be called once -- calling multiple times will result in duplica virtual QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) /Factory/; + virtual void updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ); + signals: diff --git a/python/gui/auto_generated/qgsbrowsertreeview.sip.in b/python/gui/auto_generated/qgsbrowsertreeview.sip.in index b66a85c928562..b1066e07390d9 100644 --- a/python/gui/auto_generated/qgsbrowsertreeview.sip.in +++ b/python/gui/auto_generated/qgsbrowsertreeview.sip.in @@ -66,12 +66,14 @@ Returns ``True`` if the item was found and could be selected. .. versionadded:: 3.28 %End - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); %Docstring Expands out a file ``path`` in the view. The ``path`` must correspond to a valid directory existing on the file system. +Since QGIS 3.38 the ``selectPath`` argument can be used to automatically select the path too. + .. versionadded:: 3.28 %End diff --git a/python/gui/auto_generated/qgscolorramplegendnodewidget.sip.in b/python/gui/auto_generated/qgscolorramplegendnodewidget.sip.in index bbe8d561270b5..d9700eed8c2a5 100644 --- a/python/gui/auto_generated/qgscolorramplegendnodewidget.sip.in +++ b/python/gui/auto_generated/qgscolorramplegendnodewidget.sip.in @@ -11,7 +11,6 @@ - class QgsColorRampLegendNodeWidget: QgsPanelWidget { %Docstring(signature="appended") @@ -30,9 +29,24 @@ When changes are made the to settings by a user the :py:func:`~widgetChanged` si %End public: - QgsColorRampLegendNodeWidget( QWidget *parent = 0 ); + enum class Capability + { + Prefix, + Suffix, + NumberFormat, + DefaultMinimum, + DefaultMaximum, + AllCapabilities, + }; + + typedef QFlags Capabilities; + + + QgsColorRampLegendNodeWidget( QWidget *parent = 0, QgsColorRampLegendNodeWidget::Capabilities capabilities = QgsColorRampLegendNodeWidget::Capability::AllCapabilities ); %Docstring Constructor for QgsColorRampLegendNodeWidget, with the specified ``parent`` widget. + +Since QGIS 3.38, the ``capabilities`` argument can be used to fine-tune settings exposed in the widget. %End QgsColorRampLegendNodeSettings settings() const; @@ -59,6 +73,8 @@ when using single band gray renderer). %End }; +QFlags operator|(QgsColorRampLegendNodeWidget::Capability f1, QFlags f2); + class QgsColorRampLegendNodeDialog : QDialog { @@ -73,9 +89,11 @@ A dialog for configuring a :py:class:`QgsColorRampLegendNode` (:py:class:`QgsCol %End public: - QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent /TransferThis/ = 0 ); + QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent /TransferThis/ = 0, QgsColorRampLegendNodeWidget::Capabilities capabilities = QgsColorRampLegendNodeWidget::Capability::AllCapabilities ); %Docstring Constructor for QgsColorRampLegendNodeDialog, initially showing the specified ``settings``. + +Since QGIS 3.38, the ``capabilities`` argument can be used to fine-tune settings exposed in the dialog. %End QgsColorRampLegendNodeSettings settings() const; diff --git a/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in b/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in index 9784db0fb3be2..5e68643d31882 100644 --- a/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in +++ b/python/gui/auto_generated/qgsdatasourceselectdialog.sip.in @@ -65,12 +65,14 @@ Sets a description label .. versionadded:: 3.8 %End - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); %Docstring Expands out a file ``path`` in the view. The ``path`` must correspond to a valid directory existing on the file system. +Since QGIS 3.38 the ``selectPath`` argument can be used to automatically select the path too. + .. versionadded:: 3.28 %End @@ -101,6 +103,11 @@ Apply filter to the model Scroll to last selected index and expand it's children %End + virtual void dragEnterEvent( QDragEnterEvent *event ); + + virtual void dropEvent( QDropEvent *event ); + + signals: void validationChanged( bool isValid ); @@ -176,12 +183,14 @@ Sets a description label .. versionadded:: 3.8 %End - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); %Docstring Expands out a file ``path`` in the view. The ``path`` must correspond to a valid directory existing on the file system. +Since QGIS 3.38 the ``selectPath`` argument can be used to automatically select the path too. + .. versionadded:: 3.28 %End diff --git a/python/gui/auto_generated/qgsdecoratedscrollbar.sip.in b/python/gui/auto_generated/qgsdecoratedscrollbar.sip.in new file mode 100644 index 0000000000000..d26e35aaf6b7f --- /dev/null +++ b/python/gui/auto_generated/qgsdecoratedscrollbar.sip.in @@ -0,0 +1,157 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsdecoratedscrollbar.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsScrollBarHighlight +{ +%Docstring(signature="appended") +Encapsulates the details of a highlight in a scrollbar, used alongside :py:class:`QgsScrollBarHighlightController`. + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsdecoratedscrollbar.h" +%End + public: + + enum class Priority + { + Invalid, + LowPriority, + NormalPriority, + HighPriority, + HighestPriority + }; + + QgsScrollBarHighlight( int category, int position, const QColor &color, QgsScrollBarHighlight::Priority priority = QgsScrollBarHighlight::Priority::NormalPriority ); +%Docstring +Constructor for QgsScrollBarHighlight. +%End + + QgsScrollBarHighlight(); +%Docstring +Default constructor for QgsScrollBarHighlight. +%End + + int category; + + int position; + + QColor color; + + QgsScrollBarHighlight::Priority priority; +}; + +class QgsScrollBarHighlightController +{ +%Docstring(signature="appended") +Adds highlights (colored markers) to a scrollbar. + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsdecoratedscrollbar.h" +%End + public: + + QgsScrollBarHighlightController(); + ~QgsScrollBarHighlightController(); + + QScrollBar *scrollBar() const; +%Docstring +Returns the associated scroll bar. +%End + + QAbstractScrollArea *scrollArea() const; +%Docstring +Returns the associated scroll area. + +.. seealso:: :py:func:`setScrollArea` +%End + + void setScrollArea( QAbstractScrollArea *scrollArea ); +%Docstring +Sets the associated scroll bar. + +.. seealso:: :py:func:`scrollArea` +%End + + double lineHeight() const; +%Docstring +Returns the line height for text associated with the scroll area. + +.. seealso:: :py:func:`setLineHeight` +%End + + void setLineHeight( double height ); +%Docstring +Sets the line ``height`` for text associated with the scroll area. + +.. seealso:: :py:func:`lineHeight` +%End + + double visibleRange() const; +%Docstring +Returns the visible range of the scroll area (i.e. the viewport's height). + +.. seealso:: :py:func:`setVisibleRange` +%End + + void setVisibleRange( double visibleRange ); +%Docstring +Sets the visible range of the scroll area (i.e. the viewport's height). + +.. seealso:: :py:func:`visibleRange` +%End + + double margin() const; +%Docstring +Returns the document margins for the associated viewport. + +.. seealso:: :py:func:`setMargin` +%End + + void setMargin( double margin ); +%Docstring +Sets the document ``margin`` for the associated viewport. + +.. seealso:: :py:func:`margin` +%End + + + void addHighlight( const QgsScrollBarHighlight &highlight ); +%Docstring +Adds a ``highlight`` to the scrollbar. +%End + + void removeHighlights( int category ); +%Docstring +Removes all highlights with matching ``category`` from the scrollbar. +%End + + void removeAllHighlights(); +%Docstring +Removes all highlights from the scroll bar. +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsdecoratedscrollbar.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in b/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in index b746daf926d1f..9542b1e398d3e 100644 --- a/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in +++ b/python/gui/auto_generated/qgsexpressionbuilderwidget.sip.in @@ -12,7 +12,6 @@ - class QgsExpressionBuilderWidget : QWidget { %Docstring(signature="appended") diff --git a/python/gui/auto_generated/qgsqueryresultwidget.sip.in b/python/gui/auto_generated/qgsqueryresultwidget.sip.in index 6f14e43cc9bc8..be10db1ac9ec7 100644 --- a/python/gui/auto_generated/qgsqueryresultwidget.sip.in +++ b/python/gui/auto_generated/qgsqueryresultwidget.sip.in @@ -11,6 +11,7 @@ + class QgsQueryResultWidget: QWidget { %Docstring(signature="appended") diff --git a/python/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in b/python/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in index b1c10ce22d73d..2e4f5a7278adc 100644 --- a/python/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgscategorizedsymbolrendererwidget.sip.in @@ -35,6 +35,8 @@ class QgsCategorizedSymbolRendererWidget : QgsRendererWidget virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + int matchToSymbols( QgsStyle *style ); %Docstring diff --git a/python/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in b/python/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in index 74e36c341a671..f50e3778474fc 100644 --- a/python/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgsembeddedsymbolrendererwidget.sip.in @@ -8,7 +8,7 @@ -class QgsEmbeddedSymbolRendererWidget : QgsRendererWidget, QgsExpressionContextGenerator +class QgsEmbeddedSymbolRendererWidget : QgsRendererWidget { %Docstring(signature="appended") A widget used represent options of a :py:class:`QgsEmbeddedSymbolRenderer` diff --git a/python/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in b/python/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in index 0d033cc2d56f9..699e388177e2b 100644 --- a/python/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgsgraduatedsymbolrendererwidget.sip.in @@ -30,6 +30,8 @@ class QgsGraduatedSymbolRendererWidget : QgsRendererWidget virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + public slots: void graduatedColumnChanged( const QString &field ); diff --git a/python/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in b/python/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in index f5a0cf7aa6173..1a0250db77d6b 100644 --- a/python/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgsheatmaprendererwidget.sip.in @@ -39,6 +39,8 @@ Constructor virtual void setContext( const QgsSymbolWidgetContext &context ); + virtual QgsExpressionContext createExpressionContext() const; + }; diff --git a/python/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in b/python/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in index fa4e7f9df7b57..a35daf3e2d2a8 100644 --- a/python/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgspointclusterrendererwidget.sip.in @@ -11,7 +11,7 @@ -class QgsPointClusterRendererWidget: QgsRendererWidget, QgsExpressionContextGenerator +class QgsPointClusterRendererWidget: QgsRendererWidget { %Docstring(signature="appended") A widget which allows configuration of the properties for a :py:class:`QgsPointClusterRenderer`. diff --git a/python/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in b/python/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in index fea5f06266b1f..85f064ef55bae 100644 --- a/python/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgspointdisplacementrendererwidget.sip.in @@ -10,7 +10,7 @@ -class QgsPointDisplacementRendererWidget: QgsRendererWidget, QgsExpressionContextGenerator +class QgsPointDisplacementRendererWidget: QgsRendererWidget { %TypeHeaderCode diff --git a/python/gui/auto_generated/symbology/qgsrendererwidget.sip.in b/python/gui/auto_generated/symbology/qgsrendererwidget.sip.in index 521fc8d006d65..fc6614f1d377f 100644 --- a/python/gui/auto_generated/symbology/qgsrendererwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgsrendererwidget.sip.in @@ -8,7 +8,7 @@ -class QgsRendererWidget : QgsPanelWidget +class QgsRendererWidget : QgsPanelWidget, QgsExpressionContextGenerator { %Docstring(signature="appended") Base class for renderer settings widgets. @@ -27,6 +27,8 @@ WORKFLOW: %End public: QgsRendererWidget( QgsVectorLayer *layer, QgsStyle *style ); + virtual QgsExpressionContext createExpressionContext() const; + virtual QgsFeatureRenderer *renderer() = 0; %Docstring @@ -112,6 +114,13 @@ The ``levels`` argument defines the updated list of symbols with rendering passe The ``enabled`` arguments specifies if symbol levels should be enabled for the renderer. .. versionadded:: 3.20 +%End + + void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsFeatureRenderer::Property key ); +%Docstring +Registers a data defined override button. Handles setting up connections +for the button and initializing the button to show the correct descriptions +and help text for the associated property. %End protected slots: diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index c668cc6c9a90e..e6bf953cc4333 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -46,6 +46,7 @@ %Include auto_generated/qgsdataitemguiproviderregistry.sip %Include auto_generated/qgsdatasourceselectdialog.sip %Include auto_generated/qgsdbrelationshipwidget.sip +%Include auto_generated/qgsdecoratedscrollbar.sip %Include auto_generated/qgsnewdatabasetablenamewidget.sip %Include auto_generated/qgsdetaileditemdata.sip %Include auto_generated/qgsdetaileditemdelegate.sip @@ -309,6 +310,9 @@ %If ( HAVE_QSCI_SIP ) %Include auto_generated/codeeditors/qgscodeeditorsql.sip %End +%If ( HAVE_QSCI_SIP ) +%Include auto_generated/codeeditors/qgscodeeditorwidget.sip +%End %Include auto_generated/devtools/qgsdevtoolwidget.sip %Include auto_generated/devtools/qgsdevtoolwidgetfactory.sip %Include auto_generated/editorwidgets/core/qgseditorconfigwidget.sip diff --git a/python/plugins/processing/gui/AlgorithmDialog.py b/python/plugins/processing/gui/AlgorithmDialog.py index 825499d29d872..bac2f1725e9ee 100644 --- a/python/plugins/processing/gui/AlgorithmDialog.py +++ b/python/plugins/processing/gui/AlgorithmDialog.py @@ -166,6 +166,7 @@ def runAlgorithm(self): self.feedback = self.createFeedback() self.context = dataobjects.createContext(self.feedback) self.applyContextOverrides(self.context) + self.algorithmAboutToRun.emit(self.context) checkCRS = ProcessingConfig.getSetting(ProcessingConfig.WARN_UNMATCHING_CRS) try: diff --git a/python/testing/__init__.py b/python/testing/__init__.py index 800a7fda454a9..f7608a27518f4 100644 --- a/python/testing/__init__.py +++ b/python/testing/__init__.py @@ -305,6 +305,19 @@ def render_layout_check( return result + @staticmethod + def get_test_data_path(file_path: str) -> Path: + """ + Returns the full path to a file contained within the test data + directory. + """ + from utilities import unitTestDataPath + + return ( + Path(unitTestDataPath()) / + (file_path[1:] if file_path.startswith('/') else file_path) + ) + def assertLayersEqual(self, layer_expected, layer_result, **kwargs): """ :param layer_expected: The first layer to compare diff --git a/resources/function_help/json/from_json b/resources/function_help/json/from_json index 5cd842c36a904..02c358ebb7bbe 100644 --- a/resources/function_help/json/from_json +++ b/resources/function_help/json/from_json @@ -8,8 +8,8 @@ "description": "JSON string" }], "examples": [{ - "expression": "from_json('{\"qgis\":\"rocks\"}')", - "returns": "{ 'qgis': 'rocks' }" + "expression": "from_json('{\"1\":\"one\",\"2\":\"two\"}')", + "returns": "{ '1': 'one', '2': 'two' }" }, { "expression": "from_json('[1,2,3]')", "returns": "[1,2,3]" diff --git a/resources/function_help/json/map_to_hstore b/resources/function_help/json/map_to_hstore index 8bfa9a86480eb..49df34059e881 100644 --- a/resources/function_help/json/map_to_hstore +++ b/resources/function_help/json/map_to_hstore @@ -8,8 +8,8 @@ "description": "the input map" }], "examples": [{ - "expression": "map_to_hstore(map('qgis','rocks'))", - "returns": "'\"qgis\"=>\"rocks\"'" + "expression": "map_to_hstore(map('1','one','2','two'))", + "returns": "'\"1\"=>\"one\"','\"2\"=>\"two\"'" }], "tags": ["formatted", "hstore", "elements", "map", "merge"] } diff --git a/resources/function_help/json/map_to_html_dl b/resources/function_help/json/map_to_html_dl index 0579841ec46be..0a7da8abae2ab 100644 --- a/resources/function_help/json/map_to_html_dl +++ b/resources/function_help/json/map_to_html_dl @@ -8,8 +8,8 @@ "description": "the input map" }], "examples": [{ - "expression": "map_to_html_dl(map('qgis','rocks'))", - "returns": "

qgis
rocks
" + "expression": "map_to_html_dl(map('1','one','2','two'))", + "returns": "
1
one
2
two
" }], "tags": ["formatted", "map", "html"] } diff --git a/resources/function_help/json/map_to_html_table b/resources/function_help/json/map_to_html_table index af17eab3acb3e..4b1df88b5ff40 100644 --- a/resources/function_help/json/map_to_html_table +++ b/resources/function_help/json/map_to_html_table @@ -8,8 +8,8 @@ "description": "the input map" }], "examples": [{ - "expression": "map_to_html_table(map('qgis','rocks'))", - "returns": "
qgis
rocks
" + "expression": "map_to_html_table(map('1','one','2','two'))", + "returns": "
12
onetwo
" }], "tags": ["formatted", "map", "html"] } diff --git a/resources/function_help/json/to_json b/resources/function_help/json/to_json index 56aa1bab10c10..b8a08df5d6409 100644 --- a/resources/function_help/json/to_json +++ b/resources/function_help/json/to_json @@ -8,8 +8,8 @@ "description": "The input value" }], "examples": [{ - "expression": "to_json(map('qgis','rocks'))", - "returns": "{\"qgis\":\"rocks\"}" + "expression": "to_json(map('1','one','2','two'))", + "returns": "{\"1\":\"one\",\"2\":\"two\"}" }, { "expression": "to_json(array(1,2,3))", "returns": "[1,2,3]" diff --git a/resources/server/src/landingpage/yarn.lock b/resources/server/src/landingpage/yarn.lock index 26b998a0287aa..9cfcc254c229f 100644 --- a/resources/server/src/landingpage/yarn.lock +++ b/resources/server/src/landingpage/yarn.lock @@ -3899,9 +3899,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== ejs@^3.1.6: - version "3.1.8" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" - integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" diff --git a/scripts/spell_check/spelling.dat b/scripts/spell_check/spelling.dat index b30f747d1dc5b..10dad32b93f76 100644 --- a/scripts/spell_check/spelling.dat +++ b/scripts/spell_check/spelling.dat @@ -7491,7 +7491,7 @@ unrepetant:unrepentant unrepetent:unrepentant unresonable:unreasonable unsed:unused -unselect:deselect +unselect:deselect:* unsinged:unsigned unspported:unsupported unsual:unusual diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 8626d6baa262d..1cb5f4fa1b48c 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -332,39 +332,6 @@ void Qgs3DMapScene::onCameraChanged() emit viewed2DExtentFrom3DChanged( extent2D ); } -void removeQLayerComponentsFromHierarchy( Qt3DCore::QEntity *entity ) -{ - QVector toBeRemovedComponents; - const Qt3DCore::QComponentVector entityComponents = entity->components(); - for ( Qt3DCore::QComponent *component : entityComponents ) - { - Qt3DRender::QLayer *layer = qobject_cast( component ); - if ( layer != nullptr ) - toBeRemovedComponents.push_back( layer ); - } - for ( Qt3DCore::QComponent *component : toBeRemovedComponents ) - entity->removeComponent( component ); - const QList< Qt3DCore::QEntity *> childEntities = entity->findChildren(); - for ( Qt3DCore::QEntity *obj : childEntities ) - { - if ( obj != nullptr ) - removeQLayerComponentsFromHierarchy( obj ); - } -} - -void addQLayerComponentsToHierarchy( Qt3DCore::QEntity *entity, const QVector &layers ) -{ - for ( Qt3DRender::QLayer *layer : layers ) - entity->addComponent( layer ); - - const QList< Qt3DCore::QEntity *> childEntities = entity->findChildren(); - for ( Qt3DCore::QEntity *child : childEntities ) - { - if ( child != nullptr ) - addQLayerComponentsToHierarchy( child, layers ); - } -} - void Qgs3DMapScene::updateScene( bool forceUpdate ) { if ( forceUpdate ) diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 4b370f20d0696..92880fd42b43a 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -62,7 +62,6 @@ class QgsDoubleRange; /** * \ingroup 3d * \brief Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children. - * \note Not available in Python bindings */ #ifndef SIP_RUN class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index e9847f5041b6e..3bd86feef79d9 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -515,6 +515,10 @@ if (ANDROID) else() add_executable(${QGIS_APP_NAME} MACOSX_BUNDLE WIN32 ${QGIS_APPMAIN_SRCS}) + if(MSVC AND BUILD_WITH_QT6) + qt_disable_unicode_defines(${QGIS_APP_NAME}) + endif(MSVC AND BUILD_WITH_QT6) + # require c++17 target_compile_features(${QGIS_APP_NAME} PRIVATE cxx_std_17) target_compile_definitions(${QGIS_APP_NAME} PRIVATE "QT_PLUGINS_DIR=\"${QT_PLUGINS_DIR}\"") diff --git a/src/app/main.cpp b/src/app/main.cpp index 5cb8ee81e5865..2bfbaefbd8979 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -177,10 +177,10 @@ void usage( const QString &appName ) << QStringLiteral( " the PostGIS extension\n" ) ; // OK #ifdef Q_OS_WIN - MessageBox( nullptr, - msg.join( QString() ).toLocal8Bit().constData(), - "QGIS command line options", - MB_OK ); + MessageBoxW( nullptr, + msg.join( QString() ).toStdWString().c_str(), + L"QGIS command line options", + MB_OK ); #else std::cout << msg.join( QString() ).toLocal8Bit().constData(); #endif @@ -219,7 +219,7 @@ void myPrint( const char *fmt, ... ) #if defined(Q_OS_WIN) char buffer[1024]; vsnprintf( buffer, sizeof buffer, fmt, ap ); - OutputDebugString( buffer ); + OutputDebugStringA( buffer ); #else vfprintf( stderr, fmt, ap ); #endif diff --git a/src/app/mesh/qgsmeshelevationpropertieswidget.cpp b/src/app/mesh/qgsmeshelevationpropertieswidget.cpp index cd5ede8081b10..25e3150317ea9 100644 --- a/src/app/mesh/qgsmeshelevationpropertieswidget.cpp +++ b/src/app/mesh/qgsmeshelevationpropertieswidget.cpp @@ -20,6 +20,10 @@ #include "qgsmeshlayerelevationproperties.h" #include "qgslinesymbol.h" #include "qgsfillsymbol.h" +#include "qgsexpressionbuilderdialog.h" +#include "qgsexpressioncontextutils.h" +#include +#include QgsMeshElevationPropertiesWidget::QgsMeshElevationPropertiesWidget( QgsMeshLayer *layer, QgsMapCanvas *canvas, QWidget *parent ) : QgsMapLayerConfigWidget( layer, canvas, parent ) @@ -29,6 +33,7 @@ QgsMeshElevationPropertiesWidget::QgsMeshElevationPropertiesWidget( QgsMeshLayer mModeComboBox->addItem( tr( "From Vertices" ), QVariant::fromValue( Qgis::MeshElevationMode::FromVertices ) ); mModeComboBox->addItem( tr( "Fixed Elevation Range" ), QVariant::fromValue( Qgis::MeshElevationMode::FixedElevationRange ) ); + mModeComboBox->addItem( tr( "Fixed Elevation Range Per Group" ), QVariant::fromValue( Qgis::MeshElevationMode::FixedRangePerGroup ) ); mLimitsComboBox->addItem( tr( "Include Lower and Upper" ), QVariant::fromValue( Qgis::RangeLimits::IncludeBoth ) ); mLimitsComboBox->addItem( tr( "Include Lower, Exclude Upper" ), QVariant::fromValue( Qgis::RangeLimits::IncludeLowerExcludeUpper ) ); @@ -52,6 +57,29 @@ QgsMeshElevationPropertiesWidget::QgsMeshElevationPropertiesWidget( QgsMeshLayer mFixedLowerSpinBox->clear(); mFixedUpperSpinBox->clear(); + mFixedRangePerGroupModel = new QgsMeshGroupFixedElevationRangeModel( this ); + mGroupElevationTable->verticalHeader()->setVisible( false ); + mGroupElevationTable->setModel( mFixedRangePerGroupModel ); + QgsMeshFixedElevationRangeDelegate *tableDelegate = new QgsMeshFixedElevationRangeDelegate( mGroupElevationTable ); + mGroupElevationTable->setItemDelegateForColumn( 1, tableDelegate ); + mGroupElevationTable->setItemDelegateForColumn( 2, tableDelegate ); + + QMenu *calculateFixedRangePerGroupMenu = new QMenu( mCalculateFixedRangePerGroupButton ); + mCalculateFixedRangePerGroupButton->setMenu( calculateFixedRangePerGroupMenu ); + mCalculateFixedRangePerGroupButton->setPopupMode( QToolButton::InstantPopup ); + QAction *calculateLowerAction = new QAction( "Calculate Lower by Expression…", calculateFixedRangePerGroupMenu ); + calculateFixedRangePerGroupMenu->addAction( calculateLowerAction ); + connect( calculateLowerAction, &QAction::triggered, this, [this] + { + calculateRangeByExpression( false ); + } ); + QAction *calculateUpperAction = new QAction( "Calculate Upper by Expression…", calculateFixedRangePerGroupMenu ); + calculateFixedRangePerGroupMenu->addAction( calculateUpperAction ); + connect( calculateUpperAction, &QAction::triggered, this, [this] + { + calculateRangeByExpression( true ); + } ); + syncToLayer( layer ); connect( mModeComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsMeshElevationPropertiesWidget::modeChanged ); @@ -97,6 +125,9 @@ void QgsMeshElevationPropertiesWidget::syncToLayer( QgsMapLayer *layer ) case Qgis::MeshElevationMode::FromVertices: mStackedWidget->setCurrentWidget( mPageFromVertices ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + mStackedWidget->setCurrentWidget( mPageFixedRangePerGroup ); + break; } mOffsetZSpinBox->setValue( props->zOffset() ); @@ -116,6 +147,11 @@ void QgsMeshElevationPropertiesWidget::syncToLayer( QgsMapLayer *layer ) mFixedUpperSpinBox->clear(); mLimitsComboBox->setCurrentIndex( mLimitsComboBox->findData( QVariant::fromValue( props->fixedRange().rangeLimits() ) ) ); + mFixedRangePerGroupModel->setLayerData( mLayer, props->fixedRangePerGroup() ); + mGroupElevationTable->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::Stretch ); + mGroupElevationTable->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch ); + mGroupElevationTable->horizontalHeader()->setSectionResizeMode( 2, QHeaderView::Stretch ); + mLineStyleButton->setSymbol( props->profileLineSymbol()->clone() ); mFillStyleButton->setSymbol( props->profileFillSymbol()->clone() ); @@ -157,6 +193,7 @@ void QgsMeshElevationPropertiesWidget::apply() fixedUpper = mFixedUpperSpinBox->value(); props->setFixedRange( QgsDoubleRange( fixedLower, fixedUpper, mLimitsComboBox->currentData().value< Qgis::RangeLimits >() ) ); + props->setFixedRangePerGroup( mFixedRangePerGroupModel->rangeData() ); props->setProfileLineSymbol( mLineStyleButton->clonedSymbol< QgsLineSymbol >() ); props->setProfileFillSymbol( mFillStyleButton->clonedSymbol< QgsFillSymbol >() ); @@ -176,6 +213,9 @@ void QgsMeshElevationPropertiesWidget::modeChanged() case Qgis::MeshElevationMode::FromVertices: mStackedWidget->setCurrentWidget( mPageFromVertices ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + mStackedWidget->setCurrentWidget( mPageFixedRangePerGroup ); + break; } } @@ -188,6 +228,68 @@ void QgsMeshElevationPropertiesWidget::onChanged() emit widgetChanged(); } +void QgsMeshElevationPropertiesWidget::calculateRangeByExpression( bool isUpper ) +{ + QgsExpressionContext expressionContext; + QgsExpressionContextScope *groupScope = new QgsExpressionContextScope(); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group" ), 1, true, false, tr( "Group number" ) ) ); + const int groupIndex = mLayer->datasetGroupsIndexes().at( 0 ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group_name" ), meta.name(), true, false, tr( "Group name" ) ) ); + + expressionContext.appendScope( groupScope ); + expressionContext.setHighlightedVariables( { QStringLiteral( "group" ), QStringLiteral( "group_name" )} ); + + QgsExpressionBuilderDialog dlg = QgsExpressionBuilderDialog( nullptr, isUpper ? mFixedRangeUpperExpression : mFixedRangeLowerExpression, this, QStringLiteral( "generic" ), expressionContext ); + + QList > groupChoices; + for ( int group = 0; group < mLayer->datasetGroupCount(); ++group ) + { + const int groupIndex = mLayer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupChoices << qMakePair( meta.name(), group ); + } + dlg.expressionBuilder()->setCustomPreviewGenerator( tr( "Group" ), groupChoices, [this]( const QVariant & value )-> QgsExpressionContext + { + return createExpressionContextForGroup( value.toInt() ); + } ); + + if ( dlg.exec() ) + { + if ( isUpper ) + mFixedRangeUpperExpression = dlg.expressionText(); + else + mFixedRangeLowerExpression = dlg.expressionText(); + + QgsExpression exp( dlg.expressionText() ); + exp.prepare( &expressionContext ); + for ( int group = 0; group < mLayer->datasetGroupCount(); ++group ) + { + groupScope->setVariable( QStringLiteral( "group" ), group + 1 ); + const int groupIndex = mLayer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupScope->setVariable( QStringLiteral( "group_name" ), meta.name() ); + + const QVariant res = exp.evaluate( &expressionContext ); + mFixedRangePerGroupModel->setData( mFixedRangePerGroupModel->index( group, isUpper ? 2 : 1 ), res, Qt::EditRole ); + } + } +} + +QgsExpressionContext QgsMeshElevationPropertiesWidget::createExpressionContextForGroup( int group ) const +{ + QgsExpressionContext context; + context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); + QgsExpressionContextScope *groupScope = new QgsExpressionContextScope(); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group" ), group + 1, true, false, tr( "Group number" ) ) ); + const int groupIndex = mLayer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = mLayer->datasetGroupMetadata( groupIndex ); + groupScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "group_name" ), meta.name(), true, false, tr( "Group name" ) ) ); + context.appendScope( groupScope ); + context.setHighlightedVariables( { QStringLiteral( "group" ), QStringLiteral( "group_name" )} ); + return context; +} + // // QgsMeshElevationPropertiesWidgetFactory @@ -225,3 +327,232 @@ QString QgsMeshElevationPropertiesWidgetFactory::layerPropertiesPagePositionHint return QStringLiteral( "mOptsPage_Metadata" ); } + +// +// QgsMeshGroupFixedElevationRangeModel +// + +QgsMeshGroupFixedElevationRangeModel::QgsMeshGroupFixedElevationRangeModel( QObject *parent ) + : QAbstractItemModel( parent ) +{ + +} + +int QgsMeshGroupFixedElevationRangeModel::columnCount( const QModelIndex & ) const +{ + return 3; +} + +int QgsMeshGroupFixedElevationRangeModel::rowCount( const QModelIndex &parent ) const +{ + if ( parent.isValid() ) + return 0; + return mGroupCount; +} + +QModelIndex QgsMeshGroupFixedElevationRangeModel::index( int row, int column, const QModelIndex &parent ) const +{ + if ( hasIndex( row, column, parent ) ) + { + return createIndex( row, column, row ); + } + + return QModelIndex(); +} + +QModelIndex QgsMeshGroupFixedElevationRangeModel::parent( const QModelIndex &child ) const +{ + Q_UNUSED( child ) + return QModelIndex(); +} + +Qt::ItemFlags QgsMeshGroupFixedElevationRangeModel::flags( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return Qt::ItemFlags(); + + if ( index.row() < 0 || index.row() >= mGroupCount || index.column() < 0 || index.column() >= columnCount() ) + return Qt::ItemFlags(); + + switch ( index.column() ) + { + case 0: + return Qt::ItemFlag::ItemIsEnabled; + case 1: + case 2: + return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsEditable | Qt::ItemFlag::ItemIsSelectable; + default: + break; + } + + return Qt::ItemFlags(); +} + +QVariant QgsMeshGroupFixedElevationRangeModel::data( const QModelIndex &index, int role ) const +{ + if ( !index.isValid() ) + return QVariant(); + + if ( index.row() < 0 || index.row() >= mGroupCount || index.column() < 0 || index.column() >= columnCount() ) + return QVariant(); + + const int group = index.row(); + const QgsDoubleRange range = mRanges.value( group ); + + switch ( role ) + { + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::ToolTipRole: + { + switch ( index.column() ) + { + case 0: + return mGroupNames.value( group, QString::number( group ) ); + + case 1: + return range.lower() > std::numeric_limits< double >::lowest() ? range.lower() : QVariant(); + + case 2: + return range.upper() < std::numeric_limits< double >::max() ? range.upper() : QVariant(); + + default: + break; + } + break; + } + + case Qt::TextAlignmentRole: + { + switch ( index.column() ) + { + case 0: + return static_cast( Qt::AlignLeft | Qt::AlignVCenter ); + + case 1: + case 2: + return static_cast( Qt::AlignRight | Qt::AlignVCenter ); + default: + break; + } + break; + } + + default: + break; + } + return QVariant(); +} + +QVariant QgsMeshGroupFixedElevationRangeModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + if ( role == Qt::DisplayRole && orientation == Qt::Horizontal ) + { + switch ( section ) + { + case 0: + return tr( "Group" ); + case 1: + return tr( "Lower" ); + case 2: + return tr( "Upper" ); + default: + break; + } + } + return QAbstractItemModel::headerData( section, orientation, role ); +} + +bool QgsMeshGroupFixedElevationRangeModel::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + if ( !index.isValid() ) + return false; + + if ( index.row() >= mGroupCount || index.row() < 0 ) + return false; + + const int group = index.row(); + const QgsDoubleRange range = mRanges.value( group ); + + switch ( role ) + { + case Qt::EditRole: + { + bool ok = false; + double newValue = value.toDouble( &ok ); + if ( !ok ) + return false; + + switch ( index.column() ) + { + case 1: + { + mRanges[group] = QgsDoubleRange( newValue, range.upper(), range.includeLower(), range.includeUpper() ); + emit dataChanged( index, index, QVector() << role ); + break; + } + + case 2: + mRanges[group] = QgsDoubleRange( range.lower(), newValue, range.includeLower(), range.includeUpper() ); + emit dataChanged( index, index, QVector() << role ); + break; + + default: + break; + } + return true; + } + + default: + break; + } + + return false; +} + +void QgsMeshGroupFixedElevationRangeModel::setLayerData( QgsMeshLayer *layer, const QMap &ranges ) +{ + beginResetModel(); + + mGroupCount = layer->datasetGroupCount(); + mRanges = ranges; + + mGroupNames.clear(); + for ( int group = 0; group < mGroupCount; ++group ) + { + const int groupIndex = layer->datasetGroupsIndexes().at( group ); + const QgsMeshDatasetGroupMetadata meta = layer->datasetGroupMetadata( groupIndex ); + mGroupNames[group] = meta.name(); + } + + endResetModel(); +} + + +// +// QgsFixedElevationRangeDelegate +// + +QgsMeshFixedElevationRangeDelegate::QgsMeshFixedElevationRangeDelegate( QObject *parent ) + : QStyledItemDelegate( parent ) +{ + +} + +QWidget *QgsMeshFixedElevationRangeDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const +{ + QgsDoubleSpinBox *spin = new QgsDoubleSpinBox( parent ); + spin->setDecimals( 4 ); + spin->setMinimum( -9999999998.0 ); + spin->setMaximum( 9999999999.0 ); + spin->setShowClearButton( false ); + return spin; +} + +void QgsMeshFixedElevationRangeDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const +{ + if ( QgsDoubleSpinBox *spin = qobject_cast< QgsDoubleSpinBox * >( editor ) ) + { + model->setData( index, spin->value() ); + } +} diff --git a/src/app/mesh/qgsmeshelevationpropertieswidget.h b/src/app/mesh/qgsmeshelevationpropertieswidget.h index c55e406e7d4e8..a5af9b443ad1c 100644 --- a/src/app/mesh/qgsmeshelevationpropertieswidget.h +++ b/src/app/mesh/qgsmeshelevationpropertieswidget.h @@ -19,10 +19,53 @@ #include "qgsmaplayerconfigwidget.h" #include "qgsmaplayerconfigwidgetfactory.h" +#include + #include "ui_qgsmeshelevationpropertieswidgetbase.h" class QgsMeshLayer; +class QgsMeshGroupFixedElevationRangeModel : public QAbstractItemModel +{ + Q_OBJECT + + public: + + QgsMeshGroupFixedElevationRangeModel( QObject *parent ); + int columnCount( const QModelIndex &parent = QModelIndex() ) const override; + int rowCount( 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; + Qt::ItemFlags flags( const QModelIndex &index ) const override; + QVariant data( const QModelIndex &index, int role ) const override; + QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; + bool setData( const QModelIndex &index, const QVariant &value, int role ) override; + + void setLayerData( QgsMeshLayer *layer, const QMap &ranges ); + QMap rangeData() const { return mRanges; } + + private: + + int mGroupCount = 0; + QMap mGroupNames; + QMap mRanges; +}; + + +class QgsMeshFixedElevationRangeDelegate : public QStyledItemDelegate +{ + Q_OBJECT + + public: + + QgsMeshFixedElevationRangeDelegate( QObject *parent ); + + protected: + QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex &index ) const override; + void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const override; + +}; + class QgsMeshElevationPropertiesWidget : public QgsMapLayerConfigWidget, private Ui::QgsMeshElevationPropertiesWidgetBase { Q_OBJECT @@ -38,11 +81,16 @@ class QgsMeshElevationPropertiesWidget : public QgsMapLayerConfigWidget, private private slots: void modeChanged(); void onChanged(); + void calculateRangeByExpression( bool isUpper ); private: + QgsExpressionContext createExpressionContextForGroup( int group ) const; QgsMeshLayer *mLayer = nullptr; bool mBlockUpdates = false; + QgsMeshGroupFixedElevationRangeModel *mFixedRangePerGroupModel = nullptr; + QString mFixedRangeLowerExpression = QStringLiteral( "@group" ); + QString mFixedRangeUpperExpression = QStringLiteral( "@group" ); }; diff --git a/src/app/options/qgscodeeditoroptions.cpp b/src/app/options/qgscodeeditoroptions.cpp index 4a6de3f5fa655..f0b56f207dc53 100644 --- a/src/app/options/qgscodeeditoroptions.cpp +++ b/src/app/options/qgscodeeditoroptions.cpp @@ -71,6 +71,7 @@ QgsCodeEditorOptionsWidget::QgsCodeEditorOptionsWidget( QWidget *parent ) {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, mColorFoldIcon }, {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, mColorFoldIconHalo }, {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, mColorIndentation }, + {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, mColorSearchResult }, }; for ( auto it = mColorButtonMap.constBegin(); it != mColorButtonMap.constEnd(); ++it ) @@ -188,6 +189,12 @@ QgsCodeEditorOptionsWidget::QgsCodeEditorOptionsWidget( QWidget *parent ) mPreviewStackedWidget->setCurrentIndex( mListLanguage->currentRow() ); } ); + auto addSearchHighlight = []( QgsCodeEditor * editor, int start, int length ) + { + editor->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, QgsCodeEditor::SEARCH_RESULT_INDICATOR ); + editor->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start, length ); + }; + mPythonPreview->setText( R"""(def simple_function(x,y,z): """ Function docstring @@ -204,16 +211,20 @@ def somefunc(param1: str='', param2=0): class SomeClass: """ My class docstring + + A search result """ pass )""" ); + addSearchHighlight( mPythonPreview, 385, 13 ); mExpressionPreview->setText( R"""(aggregate(layer:='rail_stations', aggregate:='collect', -- a comment expression:=centroid($geometry), /* a comment */ - filter:="region_name" = attribute(@parent,'name') + 55 + filter:="region_name" = attribute(@parent,'name') + 55 /* a search result */ ) )"""); + addSearchHighlight( mExpressionPreview, 190, 13 ); mSQLPreview->setText( R"""(CREATE TABLE "my_table" ( "pk" serial NOT NULL PRIMARY KEY, @@ -223,7 +234,9 @@ class SomeClass: -- Retrieve values SELECT count(*) FROM "my_table" WHERE "a_field" > 'a value'; +-- A search result )"""); + addSearchHighlight( mSQLPreview, 209, 13 ); mHtmlPreview->setText(R"""( @@ -234,9 +247,11 @@ SELECT count(*) FROM "my_table" WHERE "a_field" > 'a value';

Sample paragraph

+ )"""); + addSearchHighlight( mHtmlPreview, 196, 13 ); mCssPreview->setText( R"""(@import url(print.css); @@ -250,6 +265,7 @@ p.style_name:lang(en) { background: #600; } +/* A search result */ ul > li, a:hover { line-height: 11px; text-decoration: underline; @@ -261,6 +277,7 @@ ul > li, a:hover { } } )""" ); + addSearchHighlight( mCssPreview, 178, 13 ); mJsPreview->setText( R"""(// my sample JavaScript function @@ -273,10 +290,12 @@ window.onAction(function update() { element.name = 'a string'; element.title= "another string"; + /* A search result */ if (prevPos.x > 100) { element.x += max(100*2, 100); } });)""" ); + addSearchHighlight( mJsPreview, 255, 13 ); mRPreview->setText( R"""(# a comment x <- 1:12 @@ -288,6 +307,7 @@ resample(x[x > 8]) # length 2 a_variable <- "My string" +# a search result `%func_name%` <- function(arg_1,arg_2) { # function body } @@ -297,7 +317,7 @@ a_variable <- "My string" return(x^y) } )"""); - + addSearchHighlight( mRPreview, 181, 13 ); mBashPreview->setText(R"""(#!/bin/bash @@ -309,6 +329,7 @@ a_variable <- "My string" [ ! -d "$1" ] && { echo "Error: $1 does not exist or is not a directory."; exit 1; } +# A search result echo "Files with extension .$2 in $1:" for file in "$1"/*."$2"; do @@ -316,6 +337,7 @@ for file in "$1"/*."$2"; do echo "$(basename "$file"): $((size / 1024)) KB" done )""" ); + addSearchHighlight( mBashPreview, 361, 13 ); mBatchPreview->setText( R"""(@echo off @@ -333,6 +355,8 @@ if not exist %1 ( exit /b 1 ) +REM A search result + echo Files with extension %2 in %1: for %%f in (%1\*.%2) do ( @@ -343,6 +367,7 @@ for %%f in (%1\*.%2) do ( echo Done. )""" ); + addSearchHighlight( mBatchPreview, 367, 13 ); mListLanguage->setCurrentRow( 0 ); mPreviewStackedWidget->setCurrentIndex( 0 ); diff --git a/src/app/options/qgsoptions.cpp b/src/app/options/qgsoptions.cpp index 6bd2a9bb8afc2..2032e8410312d 100644 --- a/src/app/options/qgsoptions.cpp +++ b/src/app/options/qgsoptions.cpp @@ -1589,7 +1589,7 @@ void QgsOptions::saveOptions() else mSettings->remove( QStringLiteral( "cache/directory" ) ); - mSettings->setValue( QStringLiteral( "cache/size" ), QVariant::fromValue( mCacheSize->value() * 1024L ) ); + mSettings->setValue( QStringLiteral( "cache/size" ), QVariant::fromValue( mCacheSize->value() * 1024LL ) ); //url with no proxy at all QStringList noProxyUrls; diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index ef19405bf435c..af244470b57c4 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -2216,6 +2216,8 @@ QgisApp::~QgisApp() mCoordsEdit = nullptr; delete mLayerTreeView; mLayerTreeView = nullptr; + delete mMessageButton; + mMessageButton = nullptr; QgsGui::nativePlatformInterface()->cleanup(); @@ -7751,7 +7753,9 @@ void QgisApp::changeDataSource( QgsMapLayer *layer ) { const QString path = sourceParts.value( QStringLiteral( "path" ) ).toString(); const QString closestPath = QFile::exists( path ) ? path : QgsFileUtils::findClosestExistingPath( path ); - dlg.expandPath( closestPath ); + + const QFileInfo pathInfo( closestPath ); + dlg.expandPath( pathInfo.isDir() ? closestPath : pathInfo.dir().path(), true ); if ( source.contains( path ) ) { source.replace( path, QStringLiteral( "%2" ).arg( QUrl::fromLocalFile( closestPath ).toString(), @@ -13050,7 +13054,11 @@ void QgisApp::openURL( QString url, bool useQgisDocDirectory ) CFRelease( urlRef ); #elif defined(Q_OS_WIN) if ( url.startsWith( "file://", Qt::CaseInsensitive ) ) +#ifdef UNICODE + ShellExecute( 0, 0, url.mid( 7 ).toStdWString().c_str(), 0, 0, SW_SHOWNORMAL ); +#else ShellExecute( 0, 0, url.mid( 7 ).toLocal8Bit().constData(), 0, 0, SW_SHOWNORMAL ); +#endif else QDesktopServices::openUrl( url ); #else diff --git a/src/app/qgsmergeattributesdialog.cpp b/src/app/qgsmergeattributesdialog.cpp index 18355e717b79f..76e200f3a2d1f 100644 --- a/src/app/qgsmergeattributesdialog.cpp +++ b/src/app/qgsmergeattributesdialog.cpp @@ -171,6 +171,9 @@ void QgsMergeAttributesDialog::createTableWidgetContents() if ( setup.type() == QLatin1String( "Hidden" ) || setup.type() == QLatin1String( "Immutable" ) ) { mHiddenAttributes.insert( idx ); + } + if ( setup.type() == QLatin1String( "Immutable" ) ) + { continue; } @@ -182,13 +185,18 @@ void QgsMergeAttributesDialog::createTableWidgetContents() mTableWidget->setHorizontalHeaderItem( col, item ); QComboBox *cb = createMergeComboBox( mFields.at( idx ).type(), col ); - if ( ! mVectorLayer->dataProvider()->pkAttributeIndexes().contains( mFields.fieldOriginIndex( idx ) ) && - mFields.at( idx ).constraints().constraints() & QgsFieldConstraints::ConstraintUnique ) + if ( ( ! mVectorLayer->dataProvider()->pkAttributeIndexes().contains( mFields.fieldOriginIndex( idx ) ) && + mFields.at( idx ).constraints().constraints() & QgsFieldConstraints::ConstraintUnique ) || mHiddenAttributes.contains( idx ) ) { cb->setCurrentIndex( cb->findData( "skip" ) ); } mTableWidget->setCellWidget( 0, col, cb ); + if ( mHiddenAttributes.contains( idx ) ) + { + mTableWidget->setColumnHidden( idx, col ); + } + col++; } @@ -768,13 +776,6 @@ QgsAttributes QgsMergeAttributesDialog::mergedAttributes() const QgsAttributes results( mFields.count() ); for ( int fieldIdx = 0; fieldIdx < mFields.count(); ++fieldIdx ) { - if ( mHiddenAttributes.contains( fieldIdx ) ) - { - //hidden attribute, set to default value - results[fieldIdx] = QVariant(); - continue; - } - QComboBox *comboBox = qobject_cast( mTableWidget->cellWidget( 0, widgetIndex ) ); if ( !comboBox ) continue; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 20f1221a97776..070d417c746fd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -279,6 +279,7 @@ set(QGIS_CORE_SRCS processing/models/qgsprocessingmodelcomment.cpp processing/models/qgsprocessingmodelcomponent.cpp processing/models/qgsprocessingmodelgroupbox.cpp + processing/models/qgsprocessingmodelconfig.cpp processing/models/qgsprocessingmodelparameter.cpp processing/models/qgsprocessingmodeloutput.cpp processing/models/qgsprocessingmodelresult.cpp @@ -1742,6 +1743,7 @@ set(QGIS_CORE_HDRS processing/models/qgsprocessingmodelchildparametersource.h processing/models/qgsprocessingmodelcomment.h processing/models/qgsprocessingmodelcomponent.h + processing/models/qgsprocessingmodelconfig.h processing/models/qgsprocessingmodelgroupbox.h processing/models/qgsprocessingmodeloutput.h processing/models/qgsprocessingmodelparameter.h diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 31ff412df2ffe..81cd40df84b02 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -1901,7 +1901,7 @@ static QVariant fcnMapToHtmlTable( const QVariantList &values, const QgsExpressi QString table { R"html( - + diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index ecd16457f87ce..8a38db03cc3fd 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -241,7 +241,7 @@ class CORE_EXPORT QgsGeometry bool isNull() const SIP_HOLDGIL; //! Creates a new geometry from a WKT string - static QgsGeometry fromWkt( const QString &wkt ); + Q_INVOKABLE static QgsGeometry fromWkt( const QString &wkt ); //! Creates a new geometry from a QgsPointXY object static QgsGeometry fromPointXY( const QgsPointXY &point ) SIP_HOLDGIL; @@ -2132,7 +2132,7 @@ class CORE_EXPORT QgsGeometry * \returns TRUE in case of success and FALSE else * \note precision parameter added in QGIS 2.4 */ - QString asWkt( int precision = 17 ) const; + Q_INVOKABLE QString asWkt( int precision = 17 ) const; #ifdef SIP_RUN SIP_PYOBJECT __repr__(); diff --git a/src/core/geometry/qgslinestring.cpp b/src/core/geometry/qgslinestring.cpp index 2a27cc66adb27..64e8a3c69d5bc 100644 --- a/src/core/geometry/qgslinestring.cpp +++ b/src/core/geometry/qgslinestring.cpp @@ -2387,3 +2387,176 @@ QgsLineString *QgsLineString::measuredLine( double start, double end ) const return cloned.release(); } + +QgsLineString *QgsLineString::interpolateM( bool use3DDistance ) const +{ + if ( !isMeasure() ) + return nullptr; + + const int totalPoints = numPoints(); + if ( totalPoints < 2 ) + return clone(); + + const double *xData = mX.constData(); + const double *yData = mY.constData(); + const double *mData = mM.constData(); + const double *zData = is3D() ? mZ.constData() : nullptr; + use3DDistance &= is3D(); + + QVector< double > xOut( totalPoints ); + QVector< double > yOut( totalPoints ); + QVector< double > mOut( totalPoints ); + QVector< double > zOut( is3D() ? totalPoints : 0 ); + + double *xOutData = xOut.data(); + double *yOutData = yOut.data(); + double *mOutData = mOut.data(); + double *zOutData = is3D() ? zOut.data() : nullptr; + + int i = 0; + double currentSegmentLength = 0; + double lastValidM = std::numeric_limits< double >::quiet_NaN(); + double prevX = *xData; + double prevY = *yData; + double prevZ = zData ? *zData : 0; + while ( i < totalPoints ) + { + double thisX = *xData++; + double thisY = *yData++; + double thisZ = zData ? *zData++ : 0; + double thisM = *mData++; + + currentSegmentLength = use3DDistance + ? QgsGeometryUtilsBase::distance3D( prevX, prevY, prevZ, thisX, thisY, thisZ ) + : QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY ); + + if ( !std::isnan( thisM ) ) + { + *xOutData++ = thisX; + *yOutData++ = thisY; + *mOutData++ = thisM; + if ( zOutData ) + *zOutData++ = thisZ; + lastValidM = thisM; + } + else if ( i == 0 ) + { + // nan m value at start of line, read ahead to find first non-nan value and backfill + int j = 0; + double scanAheadM = thisM; + while ( i + j + 1 < totalPoints && std::isnan( scanAheadM ) ) + { + scanAheadM = mData[ j ]; + ++j; + } + if ( std::isnan( scanAheadM ) ) + { + // no valid m values in line + return nullptr; + } + *xOutData++ = thisX; + *yOutData++ = thisY; + *mOutData++ = scanAheadM; + if ( zOutData ) + *zOutData++ = thisZ; + for ( ; i < j; ++i ) + { + thisX = *xData++; + thisY = *yData++; + *xOutData++ = thisX; + *yOutData++ = thisY; + *mOutData++ = scanAheadM; + mData++; + if ( zOutData ) + *zOutData++ = *zData++; + } + lastValidM = scanAheadM; + } + else + { + // nan m value in middle of line, read ahead till next non-nan value and interpolate + int j = 0; + double scanAheadX = thisX; + double scanAheadY = thisY; + double scanAheadZ = thisZ; + double distanceToNextValidM = currentSegmentLength; + std::vector< double > scanAheadSegmentLengths; + scanAheadSegmentLengths.emplace_back( currentSegmentLength ); + + double nextValidM = std::numeric_limits< double >::quiet_NaN(); + while ( i + j < totalPoints - 1 ) + { + double nextScanAheadX = xData[j]; + double nextScanAheadY = yData[j]; + double nextScanAheadZ = zData ? zData[j] : 0; + double nextScanAheadM = mData[ j ]; + const double scanAheadSegmentLength = use3DDistance + ? QgsGeometryUtilsBase::distance3D( scanAheadX, scanAheadY, scanAheadZ, nextScanAheadX, nextScanAheadY, nextScanAheadZ ) + : QgsGeometryUtilsBase::distance2D( scanAheadX, scanAheadY, nextScanAheadX, nextScanAheadY ); + scanAheadSegmentLengths.emplace_back( scanAheadSegmentLength ); + distanceToNextValidM += scanAheadSegmentLength; + + if ( !std::isnan( nextScanAheadM ) ) + { + nextValidM = nextScanAheadM; + break; + } + + scanAheadX = nextScanAheadX; + scanAheadY = nextScanAheadY; + scanAheadZ = nextScanAheadZ; + ++j; + } + + if ( std::isnan( nextValidM ) ) + { + // no more valid m values, so just fill remainder of vertices with previous valid m value + *xOutData++ = thisX; + *yOutData++ = thisY; + *mOutData++ = lastValidM; + if ( zOutData ) + *zOutData++ = thisZ; + ++i; + for ( ; i < totalPoints; ++i ) + { + *xOutData++ = *xData++; + *yOutData++ = *yData++; + *mOutData++ = lastValidM; + if ( zOutData ) + *zOutData++ = *zData++; + } + break; + } + else + { + // interpolate along segments + const double delta = ( nextValidM - lastValidM ) / distanceToNextValidM; + *xOutData++ = thisX; + *yOutData++ = thisY; + *mOutData++ = lastValidM + delta * scanAheadSegmentLengths[0]; + double totalScanAheadLength = scanAheadSegmentLengths[0]; + if ( zOutData ) + *zOutData++ = thisZ; + for ( int k = 1; k <= j; ++i, ++k ) + { + thisX = *xData++; + thisY = *yData++; + *xOutData++ = thisX; + *yOutData++ = thisY; + totalScanAheadLength += scanAheadSegmentLengths[k]; + *mOutData++ = lastValidM + delta * totalScanAheadLength; + mData++; + if ( zOutData ) + *zOutData++ = *zData++; + } + lastValidM = nextValidM; + } + } + + prevX = thisX; + prevY = thisY; + prevZ = thisZ; + ++i; + } + return new QgsLineString( xOut, yOut, zOut, mOut ); +} diff --git a/src/core/geometry/qgslinestring.h b/src/core/geometry/qgslinestring.h index 27ba2c973fd43..0079a33a3f6d7 100644 --- a/src/core/geometry/qgslinestring.h +++ b/src/core/geometry/qgslinestring.h @@ -626,7 +626,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve /** * Returns the z-coordinate of the specified node in the line string. * \param index index of node, where the first node in the line is 0 - * \returns z-coordinate of node, or ``nan`` if index is out of bounds or the line + * \returns z-coordinate of node, or ``NaN`` if index is out of bounds or the line * does not have a z dimension * \see setZAt() */ @@ -642,7 +642,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve /** * Returns the z-coordinate of the specified node in the line string. * - * If the LineString does not have a z-dimension then ``nan`` will be returned. + * If the LineString does not have a z-dimension then ``NaN`` will be returned. * * Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1 * corresponds to the last point in the line. @@ -672,7 +672,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve /** * Returns the m value of the specified node in the line string. * \param index index of node, where the first node in the line is 0 - * \returns m value of node, or ``nan`` if index is out of bounds or the line + * \returns m value of node, or ``NaN`` if index is out of bounds or the line * does not have m values * \see setMAt() */ @@ -688,7 +688,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve /** * Returns the m-coordinate of the specified node in the line string. * - * If the LineString does not have a m-dimension then ``nan`` will be returned. + * If the LineString does not have a m-dimension then ``NaN`` will be returned. * * Indexes can be less than 0, in which case they correspond to positions from the end of the line. E.g. an index of -1 * corresponds to the last point in the line. @@ -1187,7 +1187,6 @@ class CORE_EXPORT QgsLineString: public QgsCurve */ QgsBox3D calculateBoundingBox3D() const override; - /** * Re-write the measure ordinate (or add one, if it isn't already there) interpolating * the measure between the supplied \a start and \a end values. @@ -1196,6 +1195,20 @@ class CORE_EXPORT QgsLineString: public QgsCurve */ QgsLineString *measuredLine( double start, double end ) const SIP_FACTORY; + /** + * Returns a copy of this line with all missing (NaN) m values interpolated + * from m values of surrounding vertices. + * + * If the line does not contain m values, NULLPTR is returned. + * + * The \a use3DDistance controls whether 2D or 3D distances between vertices + * should be used during interpolation. This option is only considered for lines + * with z values. + * + * \since QGIS 3.38 + */ + QgsLineString *interpolateM( bool use3DDistance = true ) const SIP_FACTORY; + protected: int compareToSameClass( const QgsAbstractGeometry *other ) const final; diff --git a/src/core/gps/qgsgpsinformation.h b/src/core/gps/qgsgpsinformation.h index 9add166f7b4d5..7879b0a5053bf 100644 --- a/src/core/gps/qgsgpsinformation.h +++ b/src/core/gps/qgsgpsinformation.h @@ -201,6 +201,24 @@ class CORE_EXPORT QgsGpsInformation */ bool satInfoComplete = false; + /** + * Returns the navigation status. + * + * \see setNavigationStatus() + * + * \since QGIS 3.38 + */ + Qgis::GpsNavigationStatus navigationStatus() const { return mNavigationStatus; } + + /** + * Sets the navigation \a status. + * + * \see navigationStatus() + * + * \since QGIS 3.38 + */ + void setNavigationStatus( Qgis::GpsNavigationStatus status ) { mNavigationStatus = status; } + /** * Returns whether the connection information is valid * \since QGIS 3.10 @@ -230,6 +248,7 @@ class CORE_EXPORT QgsGpsInformation private: QMap< Qgis::GnssConstellation, Qgis::GpsFixStatus > mConstellationFixStatus; + Qgis::GpsNavigationStatus mNavigationStatus = Qgis::GpsNavigationStatus::NotValid; friend class QgsNmeaConnection; friend class QgsQtLocationConnection; diff --git a/src/core/gps/qgsnmeaconnection.cpp b/src/core/gps/qgsnmeaconnection.cpp index 72dc8fc7cb396..83613f47bf44a 100644 --- a/src/core/gps/qgsnmeaconnection.cpp +++ b/src/core/gps/qgsnmeaconnection.cpp @@ -377,6 +377,23 @@ void QgsNmeaConnection::processRmcSentence( const char *data, int len ) mLastGPSInformation.qualityIndicator = Qgis::GpsQualityIndicator::Invalid; } } + + if ( result.navstatus == 'S' ) + { + mLastGPSInformation.setNavigationStatus( Qgis::GpsNavigationStatus::Safe ); + } + else if ( result.navstatus == 'C' ) + { + mLastGPSInformation.setNavigationStatus( Qgis::GpsNavigationStatus::Caution ); + } + else if ( result.navstatus == 'U' ) + { + mLastGPSInformation.setNavigationStatus( Qgis::GpsNavigationStatus::Unsafe ); + } + else + { + mLastGPSInformation.setNavigationStatus( Qgis::GpsNavigationStatus::NotValid ); + } } void QgsNmeaConnection::processGsvSentence( const char *data, int len ) diff --git a/src/core/labeling/qgslabelingengine.cpp b/src/core/labeling/qgslabelingengine.cpp index f19838c7b406e..4960376744ff5 100644 --- a/src/core/labeling/qgslabelingengine.cpp +++ b/src/core/labeling/qgslabelingengine.cpp @@ -740,6 +740,9 @@ QString QgsLabelingUtils::encodePredefinedPositionOrder( const QVector QgsLabelingUtils::decodePredefinedPo result << Qgis::LabelPredefinedPointPosition::BottomSlightlyRight; else if ( cleaned == QLatin1String( "BR" ) ) result << Qgis::LabelPredefinedPointPosition::BottomRight; + else if ( cleaned == QLatin1String( "O" ) ) + result << Qgis::LabelPredefinedPointPosition::OverPoint; } return result; } diff --git a/src/core/labeling/qgspallabeling.cpp b/src/core/labeling/qgspallabeling.cpp index d0a39b3f39ded..1c2b932e11541 100644 --- a/src/core/labeling/qgspallabeling.cpp +++ b/src/core/labeling/qgspallabeling.cpp @@ -24,8 +24,6 @@ #include "qgsstyle.h" #include "qgstextrenderer.h" -#include - #include "pal/labelposition.h" #include @@ -221,7 +219,7 @@ void QgsPalLayerSettings::initPropertyDefinitions() "TSR=Top, slightly right|TR=Top right|
" "L=Left|R=Right|
" "BL=Bottom left|BSL=Bottom, slightly left|B=Bottom middle|
" - "BSR=Bottom, slightly right|BR=Bottom right]" ), origin ) + "BSR=Bottom, slightly right|BR=Bottom right|O=Over point]" ), origin ) }, { static_cast< int >( QgsPalLayerSettings::Property::LinePlacementOptions ), QgsPropertyDefinition( "LinePlacementFlags", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line placement options" ), QObject::tr( "Comma separated list of placement options
" ) diff --git a/src/core/layout/qgslayoutitemlegend.cpp b/src/core/layout/qgslayoutitemlegend.cpp index 6d1a02d3cfdaa..1ccef7dc7c81f 100644 --- a/src/core/layout/qgslayoutitemlegend.cpp +++ b/src/core/layout/qgslayoutitemlegend.cpp @@ -1198,7 +1198,7 @@ void QgsLayoutItemLegend::doUpdateFilterByMap() { mapExtent.transform( mapTransform ); } - catch ( QgsCsException &cse ) + catch ( QgsCsException & ) { continue; } diff --git a/src/core/layout/qgslayoutitemmapgrid.cpp b/src/core/layout/qgslayoutitemmapgrid.cpp index 4e6b18d0d1f69..48b06834dec2e 100644 --- a/src/core/layout/qgslayoutitemmapgrid.cpp +++ b/src/core/layout/qgslayoutitemmapgrid.cpp @@ -1912,7 +1912,6 @@ bool QgsLayoutItemMapGrid::shouldShowForDisplayMode( QgsLayoutItemMapGrid::Annot || ( mode == QgsLayoutItemMapGrid::LongitudeOnly && coordinate == QgsLayoutItemMapGrid::Longitude ); } - QgsLayoutItemMapGrid::DisplayMode gridAnnotationDisplayModeFromDD( QString ddValue, QgsLayoutItemMapGrid::DisplayMode defValue ) { if ( ddValue.compare( QLatin1String( "x_only" ), Qt::CaseInsensitive ) == 0 ) @@ -1927,7 +1926,6 @@ QgsLayoutItemMapGrid::DisplayMode gridAnnotationDisplayModeFromDD( QString ddVal return defValue; } - void QgsLayoutItemMapGrid::refreshDataDefinedProperties() { const QgsExpressionContext context = createExpressionContext(); @@ -1986,7 +1984,6 @@ void QgsLayoutItemMapGrid::refreshDataDefinedProperties() mEvaluatedRightFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::DataDefinedProperty::MapGridFrameDivisionsRight, context ), mRightFrameDivisions ); mEvaluatedTopFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::DataDefinedProperty::MapGridFrameDivisionsTop, context ), mTopFrameDivisions ); mEvaluatedBottomFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::DataDefinedProperty::MapGridFrameDivisionsBottom, context ), mBottomFrameDivisions ); - } double QgsLayoutItemMapGrid::mapWidth() const @@ -2637,3 +2634,78 @@ QList QgsLayoutItemMapGrid::trimLinesToMap( const QPolygonF &line, co } return trimmedLines; } + +void QgsLayoutItemMapGrid::copyProperties( const QgsLayoutItemMapGrid *other ) +{ + // grid + setStyle( other->style() ); + setIntervalX( other->intervalX() ); + setIntervalY( other->intervalY() ); + setOffsetX( other->offsetX() ); + setOffsetY( other->offsetX() ); + setCrossLength( other->crossLength() ); + setFrameStyle( other->frameStyle() ); + setFrameSideFlags( other->frameSideFlags() ); + setFrameWidth( other->frameWidth() ); + setFrameMargin( other->frameMargin() ); + setFramePenSize( other->framePenSize() ); + setFramePenColor( other->framePenColor() ); + setFrameFillColor1( other->frameFillColor1() ); + setFrameFillColor2( other->frameFillColor2() ); + + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + + setRotatedTicksLengthMode( other->rotatedTicksLengthMode() ); + setRotatedTicksEnabled( other->rotatedTicksEnabled() ); + setRotatedTicksMinimumAngle( other->rotatedTicksMinimumAngle() ); + setRotatedTicksMarginToCorner( other->rotatedTicksMarginToCorner() ); + setRotatedAnnotationsLengthMode( other->rotatedAnnotationsLengthMode() ); + setRotatedAnnotationsEnabled( other->rotatedAnnotationsEnabled() ); + setRotatedAnnotationsMinimumAngle( other->rotatedAnnotationsMinimumAngle() ); + setRotatedAnnotationsMarginToCorner( other->rotatedAnnotationsMarginToCorner() ); + + if ( other->lineSymbol() ) + { + setLineSymbol( other->lineSymbol()->clone() ); + } + + if ( other->markerSymbol() ) + { + setMarkerSymbol( other->markerSymbol()->clone() ); + } + + setCrs( other->crs() ); + + setBlendMode( other->blendMode() ); + + //annotation + setAnnotationEnabled( other->annotationEnabled() ); + setAnnotationFormat( other->annotationFormat() ); + setAnnotationExpression( other->annotationExpression() ); + + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + setAnnotationFrameDistance( other->annotationFrameDistance() ); + setAnnotationTextFormat( other->annotationTextFormat() ); + + setAnnotationPrecision( other->annotationPrecision() ); + setUnits( other->units() ); + setMinimumIntervalWidth( other->minimumIntervalWidth() ); + setMaximumIntervalWidth( other->maximumIntervalWidth() ); + + setDataDefinedProperties( other->dataDefinedProperties() ); + refreshDataDefinedProperties(); +} diff --git a/src/core/layout/qgslayoutitemmapgrid.h b/src/core/layout/qgslayoutitemmapgrid.h index b2ee47cba0782..c3e9309e71e62 100644 --- a/src/core/layout/qgslayoutitemmapgrid.h +++ b/src/core/layout/qgslayoutitemmapgrid.h @@ -1005,6 +1005,13 @@ class CORE_EXPORT QgsLayoutItemMapGrid : public QgsLayoutItemMapItem bool accept( QgsStyleEntityVisitorInterface *visitor ) const override; void refresh() override; + /** + * Copies properties from specified map grid. + * + * \since QGIS 3.38 + */ + void copyProperties( const QgsLayoutItemMapGrid *other ); + signals: /** diff --git a/src/core/mesh/qgsmeshdataset.cpp b/src/core/mesh/qgsmeshdataset.cpp index 56d5dc6830f59..9c6d977f6994f 100644 --- a/src/core/mesh/qgsmeshdataset.cpp +++ b/src/core/mesh/qgsmeshdataset.cpp @@ -19,6 +19,8 @@ #include "qgsmeshdataprovider.h" #include "qgsrectangle.h" #include "qgis.h" +#include +#include QgsMeshDatasetIndex::QgsMeshDatasetIndex( int group, int dataset ) : mGroupIndex( group ), mDatasetIndex( dataset ) @@ -142,6 +144,14 @@ QgsMeshDatasetGroupMetadata::QgsMeshDatasetGroupMetadata( const QString &name, , mReferenceTime( referenceTime ) , mIsTemporal( isTemporal ) { + // this relies on the naming convention used by MDAL's NetCDF driver: _: + // If future MDAL releases expose quantities via a standard API then we can safely remove this and port to the new API. + const thread_local QRegularExpression parentQuantityRegex( QStringLiteral( "^(.*):.*?$" ) ); + const QRegularExpressionMatch parentQuantityMatch = parentQuantityRegex.match( mName ); + if ( parentQuantityMatch.hasMatch() ) + { + mParentQuantityName = parentQuantityMatch.captured( 1 ); + } } QMap QgsMeshDatasetGroupMetadata::extraOptions() const @@ -169,7 +179,13 @@ QString QgsMeshDatasetGroupMetadata::name() const return mName; } -QgsMeshDatasetGroupMetadata::DataType QgsMeshDatasetGroupMetadata::dataType() const +QString QgsMeshDatasetGroupMetadata::parentQuantityName() const +{ + return mParentQuantityName; +} + +QgsMeshDatasetGroupMetadata::DataType +QgsMeshDatasetGroupMetadata::dataType() const { return mDataType; } diff --git a/src/core/mesh/qgsmeshdataset.h b/src/core/mesh/qgsmeshdataset.h index ee0d34c9df934..426ac0f40cb96 100644 --- a/src/core/mesh/qgsmeshdataset.h +++ b/src/core/mesh/qgsmeshdataset.h @@ -22,17 +22,21 @@ #include #include #include +#include +#include #include +#include #include "qgis_core.h" #include "qgis_sip.h" -#include "qgspoint.h" -#include "qgsdataprovider.h" class QgsMeshLayer; class QgsMeshDatasetGroup; class QgsRectangle; +class QDomDocument; +class QgsReadWriteContext; + struct QgsMesh; /** @@ -397,6 +401,16 @@ class CORE_EXPORT QgsMeshDatasetGroupMetadata */ QString name() const; + /** + * Returns the name of the dataset's parent quantity, if available. + * + * The quantity can be used to collect dataset groups which represent a single quantity + * but at different values (e.g. groups which represent different elevations). + * + * \since QGIS 3.38 + */ + QString parentQuantityName() const; + /** * Returns the uri of the source * @@ -457,6 +471,7 @@ class CORE_EXPORT QgsMeshDatasetGroupMetadata private: QString mName; + QString mParentQuantityName; QString mUri; bool mIsScalar = false; DataType mDataType = DataType::DataOnFaces; diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 4982232dd2594..9538c31624397 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -797,24 +797,24 @@ void QgsMeshLayer::applyClassificationOnScalarSettings( const QgsMeshDatasetGrou } } -QgsMeshDatasetIndex QgsMeshLayer::activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const +QgsMeshDatasetIndex QgsMeshLayer::activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS if ( mTemporalProperties->isActive() ) - return datasetIndexAtTime( timeRange, mRendererSettings.activeScalarDatasetGroup() ); + return datasetIndexAtTime( timeRange, group >= 0 ? group : mRendererSettings.activeScalarDatasetGroup() ); else - return QgsMeshDatasetIndex( mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); } -QgsMeshDatasetIndex QgsMeshLayer::activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const +QgsMeshDatasetIndex QgsMeshLayer::activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS if ( mTemporalProperties->isActive() ) - return datasetIndexAtTime( timeRange, mRendererSettings.activeVectorDatasetGroup() ); + return datasetIndexAtTime( timeRange, group >= 0 ? group : mRendererSettings.activeVectorDatasetGroup() ); else - return QgsMeshDatasetIndex( mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); } void QgsMeshLayer::fillNativeMesh() @@ -903,11 +903,11 @@ int QgsMeshLayer::closestEdge( const QgsPointXY &point, double searchRadius, Qgs return selectedIndex; } -QgsMeshDatasetIndex QgsMeshLayer::staticVectorDatasetIndex() const +QgsMeshDatasetIndex QgsMeshLayer::staticVectorDatasetIndex( int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - return QgsMeshDatasetIndex( mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeVectorDatasetGroup(), mStaticVectorDatasetIndex ); } void QgsMeshLayer::setReferenceTime( const QDateTime &referenceTime ) @@ -1545,11 +1545,11 @@ QList QgsMeshLayer::selectFacesByExpression( QgsExpression expression ) return ret; } -QgsMeshDatasetIndex QgsMeshLayer::staticScalarDatasetIndex() const +QgsMeshDatasetIndex QgsMeshLayer::staticScalarDatasetIndex( int group ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - return QgsMeshDatasetIndex( mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); + return QgsMeshDatasetIndex( group >= 0 ? group : mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex ); } void QgsMeshLayer::setStaticVectorDatasetIndex( const QgsMeshDatasetIndex &staticVectorDatasetIndex ) diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 75b002b5f1dfa..2da422e3b431c 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -608,27 +608,27 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo * 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 - * \returns dataset index + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \note the returned dataset index depends on the matching method, see setTemporalMatchingMethod() * * \since QGIS 3.14 */ - QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeScalarDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; /** * 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 - * \returns dataset index + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \note the returned dataset index depends on the matching method, see setTemporalMatchingMethod() * * \since QGIS 3.14 */ - QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange ) const; + QgsMeshDatasetIndex activeVectorDatasetAtTime( const QgsDateTimeRange &timeRange, int group = -1 ) const; /** * Sets the static scalar dataset index that is rendered if the temporal properties is not active @@ -649,18 +649,24 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo void setStaticVectorDatasetIndex( const QgsMeshDatasetIndex &staticVectorDatasetIndex ) SIP_SKIP; /** - * Returns the static scalar dataset index that is rendered if the temporal properties is not active + * Returns the static scalar dataset index that is rendered if the temporal properties is not active. + * + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \since QGIS 3.14 */ - QgsMeshDatasetIndex staticScalarDatasetIndex() const; + QgsMeshDatasetIndex staticScalarDatasetIndex( int group = -1 ) const; /** - * Returns the static vector dataset index that is rendered if the temporal properties is not active + * Returns the static vector dataset index that is rendered if the temporal properties is not active. + * + * Since QGIS 3.38, the \a group argument can be used to specify a fixed group + * to use. If this is not specified, then the active group from the layer's renderer will be used. * * \since QGIS 3.14 */ - QgsMeshDatasetIndex staticVectorDatasetIndex() const; + QgsMeshDatasetIndex staticVectorDatasetIndex( int group = -1 ) const; /** * Sets the reference time of the layer diff --git a/src/core/mesh/qgsmeshlayerelevationproperties.cpp b/src/core/mesh/qgsmeshlayerelevationproperties.cpp index 2ee3838f55eca..b988c74342ac4 100644 --- a/src/core/mesh/qgsmeshlayerelevationproperties.cpp +++ b/src/core/mesh/qgsmeshlayerelevationproperties.cpp @@ -59,6 +59,23 @@ QDomElement QgsMeshLayerElevationProperties::writeXml( QDomElement &parentElemen element.setAttribute( QStringLiteral( "includeUpper" ), mFixedRange.includeUpper() ? "1" : "0" ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + QDomElement ranges = document.createElement( QStringLiteral( "ranges" ) ); + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + QDomElement range = document.createElement( QStringLiteral( "range" ) ); + range.setAttribute( QStringLiteral( "group" ), it.key() ); + range.setAttribute( QStringLiteral( "lower" ), qgsDoubleToString( it.value().lower() ) ); + range.setAttribute( QStringLiteral( "upper" ), qgsDoubleToString( it.value().upper() ) ); + range.setAttribute( QStringLiteral( "includeLower" ), it.value().includeLower() ? "1" : "0" ); + range.setAttribute( QStringLiteral( "includeUpper" ), it.value().includeUpper() ? "1" : "0" ); + ranges.appendChild( range ); + } + element.appendChild( ranges ); + break; + } + case Qgis::MeshElevationMode::FromVertices: break; } @@ -98,6 +115,25 @@ bool QgsMeshLayerElevationProperties::readXml( const QDomElement &element, const mFixedRange = QgsDoubleRange( lower, upper, includeLower, includeUpper ); break; } + + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + mRangePerGroup.clear(); + + const QDomNodeList ranges = elevationElement.firstChildElement( QStringLiteral( "ranges" ) ).childNodes(); + for ( int i = 0; i < ranges.size(); ++i ) + { + const QDomElement rangeElement = ranges.at( i ).toElement(); + const int group = rangeElement.attribute( QStringLiteral( "group" ) ).toInt(); + const double lower = rangeElement.attribute( QStringLiteral( "lower" ) ).toDouble(); + const double upper = rangeElement.attribute( QStringLiteral( "upper" ) ).toDouble(); + const bool includeLower = rangeElement.attribute( QStringLiteral( "includeLower" ) ).toInt(); + const bool includeUpper = rangeElement.attribute( QStringLiteral( "includeUpper" ) ).toInt(); + mRangePerGroup.insert( group, QgsDoubleRange( lower, upper, includeLower, includeUpper ) ); + } + break; + } + case Qgis::MeshElevationMode::FromVertices: break; } @@ -126,6 +162,15 @@ QString QgsMeshLayerElevationProperties::htmlSummary() const properties << tr( "Elevation range: %1 to %2" ).arg( mFixedRange.lower() ).arg( mFixedRange.upper() ); break; + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + properties << tr( "Elevation for group %1: %2 to %3" ).arg( it.key() ).arg( it.value().lower() ).arg( it.value().upper() ); + } + break; + } + case Qgis::MeshElevationMode::FromVertices: properties << tr( "Scale: %1" ).arg( mZScale ); properties << tr( "Offset: %1" ).arg( mZOffset ); @@ -143,6 +188,7 @@ QgsMeshLayerElevationProperties *QgsMeshLayerElevationProperties::clone() const res->setProfileSymbology( mSymbology ); res->setElevationLimit( mElevationLimit ); res->setFixedRange( mFixedRange ); + res->setFixedRangePerGroup( mRangePerGroup ); res->copyCommonProperties( this ); return res.release(); } @@ -154,6 +200,16 @@ bool QgsMeshLayerElevationProperties::isVisibleInZRange( const QgsDoubleRange &r case Qgis::MeshElevationMode::FixedElevationRange: return mFixedRange.overlaps( range ); + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + if ( it.value().overlaps( range ) ) + return true; + } + return false; + } + case Qgis::MeshElevationMode::FromVertices: // TODO -- test actual mesh z range return true; @@ -168,6 +224,36 @@ QgsDoubleRange QgsMeshLayerElevationProperties::calculateZRange( QgsMapLayer * ) case Qgis::MeshElevationMode::FixedElevationRange: return mFixedRange; + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + double lower = std::numeric_limits< double >::max(); + double upper = std::numeric_limits< double >::min(); + bool includeLower = true; + bool includeUpper = true; + for ( auto it = mRangePerGroup.constBegin(); it != mRangePerGroup.constEnd(); ++it ) + { + if ( it.value().lower() < lower ) + { + lower = it.value().lower(); + includeLower = it.value().includeLower(); + } + else if ( !includeLower && it.value().lower() == lower && it.value().includeLower() ) + { + includeLower = true; + } + if ( it.value().upper() > upper ) + { + upper = it.value().upper(); + includeUpper = it.value().includeUpper(); + } + else if ( !includeUpper && it.value().upper() == upper && it.value().includeUpper() ) + { + includeUpper = true; + } + } + return QgsDoubleRange( lower, upper, includeLower, includeUpper ); + } + case Qgis::MeshElevationMode::FromVertices: // TODO -- determine actual z range from mesh statistics return QgsDoubleRange(); @@ -187,6 +273,7 @@ QgsMapLayerElevationProperties::Flags QgsMeshLayerElevationProperties::flags() c case Qgis::MeshElevationMode::FixedElevationRange: return QgsMapLayerElevationProperties::Flag::FlagDontInvalidateCachedRendersWhenRangeChanges; + case Qgis::MeshElevationMode::FixedRangePerGroup: case Qgis::MeshElevationMode::FromVertices: break; } @@ -221,6 +308,20 @@ void QgsMeshLayerElevationProperties::setFixedRange( const QgsDoubleRange &range emit changed(); } +QMap QgsMeshLayerElevationProperties::fixedRangePerGroup() const +{ + return mRangePerGroup; +} + +void QgsMeshLayerElevationProperties::setFixedRangePerGroup( const QMap &ranges ) +{ + if ( ranges == mRangePerGroup ) + return; + + mRangePerGroup = ranges; + emit changed(); +} + QgsLineSymbol *QgsMeshLayerElevationProperties::profileLineSymbol() const { return mProfileLineSymbol.get(); diff --git a/src/core/mesh/qgsmeshlayerelevationproperties.h b/src/core/mesh/qgsmeshlayerelevationproperties.h index 88be2fb7c6eb6..319f01ba3cf2f 100644 --- a/src/core/mesh/qgsmeshlayerelevationproperties.h +++ b/src/core/mesh/qgsmeshlayerelevationproperties.h @@ -22,6 +22,7 @@ #include "qgis_core.h" #include "qgis_sip.h" #include "qgsmaplayerelevationproperties.h" +#include "qgsmeshdataset.h" #include "qgis.h" class QgsLineSymbol; @@ -97,6 +98,30 @@ class CORE_EXPORT QgsMeshLayerElevationProperties : public QgsMapLayerElevationP */ void setFixedRange( const QgsDoubleRange &range ); + /** + * Returns the fixed elevation range for each group. + * + * \note This is only considered when mode() is Qgis::MeshElevationMode::FixedRangePerGroup. + * + * \note When a fixed range is set any zOffset() and zScale() is ignored. + * + * \see setFixedRangePerGroup() + * \since QGIS 3.38 + */ + QMap fixedRangePerGroup() const; + + /** + * Sets the fixed elevation range for each group. + * + * \note This is only considered when mode() is Qgis::MeshElevationMode::FixedRangePerGroup. + * + * \note When a fixed range is set any zOffset() and zScale() is ignored. + * + * \see fixedRangePerGroup() + * \since QGIS 3.38 + */ + void setFixedRangePerGroup( const QMap &ranges ); + /** * Returns the line symbol used to render the mesh profile in elevation profile plots. * @@ -180,6 +205,8 @@ class CORE_EXPORT QgsMeshLayerElevationProperties : public QgsMapLayerElevationP double mElevationLimit = std::numeric_limits< double >::quiet_NaN(); QgsDoubleRange mFixedRange; + + QMap< int, QgsDoubleRange > mRangePerGroup; }; #endif // QGSMESHLAYERELEVATIONPROPERTIES_H diff --git a/src/core/mesh/qgsmeshlayerrenderer.cpp b/src/core/mesh/qgsmeshlayerrenderer.cpp index 58d327272c774..d6003f34e5b66 100644 --- a/src/core/mesh/qgsmeshlayerrenderer.cpp +++ b/src/core/mesh/qgsmeshlayerrenderer.cpp @@ -74,20 +74,6 @@ QgsMeshLayerRenderer::QgsMeshLayerRenderer( mNativeMesh = *( layer->nativeMesh() ); mLayerExtent = layer->extent(); - // copy triangular mesh - copyTriangularMeshes( layer, context ); - - // copy datasets - copyScalarDatasetValues( layer ); - copyVectorDatasetValues( layer ); - - calculateOutputSize(); - - QSet attrs; - prepareLabeling( layer, attrs ); - - mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer ); - if ( layer->elevationProperties() && layer->elevationProperties()->hasElevation() ) { QgsMeshLayerElevationProperties *elevProp = qobject_cast( layer->elevationProperties() ); @@ -110,10 +96,69 @@ QgsMeshLayerRenderer::QgsMeshLayerRenderer( // TODO -- filtering by mesh z values is not currently implemented break; } + + case Qgis::MeshElevationMode::FixedRangePerGroup: + { + // find the top-most group which matches the map range and parent group + int currentMatchingVectorGroup = -1; + int currentMatchingScalarGroup = -1; + QgsDoubleRange currentMatchingVectorRange; + QgsDoubleRange currentMatchingScalarRange; + + const QMap rangePerGroup = elevProp->fixedRangePerGroup(); + + const int activeVectorDatasetGroup = mRendererSettings.activeVectorDatasetGroup(); + const int activeScalarDatasetGroup = mRendererSettings.activeScalarDatasetGroup(); + + for ( auto it = rangePerGroup.constBegin(); it != rangePerGroup.constEnd(); ++it ) + { + if ( it.value().overlaps( context.zRange() ) ) + { + const bool matchesVectorParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeVectorDatasetGroup ), QgsMeshDatasetIndex( it.key() ) ); + const bool matchesScalarParentGroup = QgsMeshLayerUtils::haveSameParentQuantity( layer, QgsMeshDatasetIndex( activeScalarDatasetGroup ), QgsMeshDatasetIndex( it.key() ) ); + + if ( matchesVectorParentGroup && ( + currentMatchingVectorRange.isInfinite() + || ( it.value().includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() ) + || ( !currentMatchingVectorRange.includeUpper() && it.value().upper() >= currentMatchingVectorRange.upper() ) ) ) + { + currentMatchingVectorGroup = it.key(); + currentMatchingVectorRange = it.value(); + } + + if ( matchesScalarParentGroup && ( + currentMatchingScalarRange.isInfinite() + || ( it.value().includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() ) + || ( !currentMatchingScalarRange.includeUpper() && it.value().upper() >= currentMatchingScalarRange.upper() ) ) ) + { + currentMatchingScalarGroup = it.key(); + currentMatchingScalarRange = it.value(); + } + } + } + if ( currentMatchingVectorGroup >= 0 ) + mRendererSettings.setActiveVectorDatasetGroup( currentMatchingVectorGroup ); + if ( currentMatchingScalarGroup >= 0 ) + mRendererSettings.setActiveScalarDatasetGroup( currentMatchingScalarGroup ); + } } } } + // copy triangular mesh + copyTriangularMeshes( layer, context ); + + // copy datasets + copyScalarDatasetValues( layer ); + copyVectorDatasetValues( layer ); + + calculateOutputSize(); + + QSet attrs; + prepareLabeling( layer, attrs ); + + mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer ); + mPreparationTime = timer.elapsed(); } @@ -154,9 +199,9 @@ void QgsMeshLayerRenderer::copyScalarDatasetValues( QgsMeshLayer *layer ) { QgsMeshDatasetIndex datasetIndex; if ( renderContext()->isTemporal() ) - datasetIndex = layer->activeScalarDatasetAtTime( renderContext()->temporalRange() ); + datasetIndex = layer->activeScalarDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeScalarDatasetGroup() ); else - datasetIndex = layer->staticScalarDatasetIndex(); + datasetIndex = layer->staticScalarDatasetIndex( mRendererSettings.activeScalarDatasetGroup() ); // Find out if we can use cache up to date. If yes, use it and return const int datasetGroupCount = layer->datasetGroupCount(); @@ -255,9 +300,9 @@ void QgsMeshLayerRenderer::copyVectorDatasetValues( QgsMeshLayer *layer ) { QgsMeshDatasetIndex datasetIndex; if ( renderContext()->isTemporal() ) - datasetIndex = layer->activeVectorDatasetAtTime( renderContext()->temporalRange() ); + datasetIndex = layer->activeVectorDatasetAtTime( renderContext()->temporalRange(), mRendererSettings.activeVectorDatasetGroup() ); else - datasetIndex = layer->staticVectorDatasetIndex(); + datasetIndex = layer->staticVectorDatasetIndex( mRendererSettings.activeVectorDatasetGroup() ); // Find out if we can use cache up to date. If yes, use it and return const int datasetGroupCount = layer->datasetGroupCount(); diff --git a/src/core/mesh/qgsmeshlayerutils.cpp b/src/core/mesh/qgsmeshlayerutils.cpp index e6c00b840f351..b68f5bcfe9cdc 100644 --- a/src/core/mesh/qgsmeshlayerutils.cpp +++ b/src/core/mesh/qgsmeshlayerutils.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "qgsmeshlayerutils.h" #include "qgsmeshtimesettings.h" @@ -702,4 +704,17 @@ QVector QgsMeshLayerUtils::calculateNormals( const QgsTriangularMesh return normals; } +bool QgsMeshLayerUtils::haveSameParentQuantity( const QgsMeshLayer *layer, const QgsMeshDatasetIndex &index1, const QgsMeshDatasetIndex &index2 ) +{ + const QgsMeshDatasetGroupMetadata metadata1 = layer->datasetGroupMetadata( index1 ); + if ( metadata1.parentQuantityName().isEmpty() ) + return false; + + const QgsMeshDatasetGroupMetadata metadata2 = layer->datasetGroupMetadata( index2 ); + if ( metadata2.parentQuantityName().isEmpty() ) + return false; + + return metadata1.parentQuantityName().compare( metadata2.parentQuantityName(), Qt::CaseInsensitive ) == 0; +} + ///@endcond diff --git a/src/core/mesh/qgsmeshlayerutils.h b/src/core/mesh/qgsmeshlayerutils.h index 2d25837692b98..da7c985d75fa0 100644 --- a/src/core/mesh/qgsmeshlayerutils.h +++ b/src/core/mesh/qgsmeshlayerutils.h @@ -369,6 +369,14 @@ class CORE_EXPORT QgsMeshLayerUtils const QgsTriangularMesh &triangularMesh, const QVector &verticalMagnitude, bool isRelative ); + + /** + * Returns TRUE if the datasets from \a layer at \a index1 and \a index2 share the same parent quantity. + * + * \since QGIS 3.38 + */ + static bool haveSameParentQuantity( const QgsMeshLayer *layer, const QgsMeshDatasetIndex &index1, const QgsMeshDatasetIndex &index2 ); + }; ///@endcond diff --git a/src/core/mesh/qgsmeshrenderersettings.cpp b/src/core/mesh/qgsmeshrenderersettings.cpp index 49c7037fe7b32..94a8a5074429c 100644 --- a/src/core/mesh/qgsmeshrenderersettings.cpp +++ b/src/core/mesh/qgsmeshrenderersettings.cpp @@ -612,6 +612,7 @@ QDomElement QgsMeshRendererVectorSettings::writeXml( QDomDocument &doc, const Qg elem.appendChild( mArrowsSettings.writeXml( doc ) ); elem.appendChild( mStreamLinesSettings.writeXml( doc ) ); elem.appendChild( mTracesSettings.writeXml( doc ) ); + elem.appendChild( mWindBarbSettings.writeXml( doc ) ); return elem; } @@ -644,6 +645,10 @@ void QgsMeshRendererVectorSettings::readXml( const QDomElement &elem, const QgsR const QDomElement elemTraces = elem.firstChildElement( QStringLiteral( "vector-traces-settings" ) ); if ( ! elemTraces.isNull() ) mTracesSettings.readXml( elemTraces ); + + const QDomElement elemWindBarb = elem.firstChildElement( QStringLiteral( "vector-windbarb-settings" ) ); + if ( ! elemWindBarb.isNull() ) + mWindBarbSettings.readXml( elemWindBarb ); } QgsInterpolatedLineColor::ColoringMethod QgsMeshRendererVectorSettings::coloringMethod() const @@ -744,3 +749,88 @@ bool QgsMeshRendererSettings::hasSettings( int datasetGroupIndex ) const { return mRendererScalarSettings.contains( datasetGroupIndex ) || mRendererVectorSettings.contains( datasetGroupIndex ); } + +QgsMeshRendererVectorWindBarbSettings QgsMeshRendererVectorSettings::windBarbSettings() const +{ + return mWindBarbSettings; +} + +void QgsMeshRendererVectorSettings::setWindBarbSettings( const QgsMeshRendererVectorWindBarbSettings &windBarbSettings ) +{ + mWindBarbSettings = windBarbSettings; +} + +void QgsMeshRendererVectorWindBarbSettings::readXml( const QDomElement &elem ) +{ + mShaftLength = elem.attribute( QStringLiteral( "shaft-length" ), QStringLiteral( "10" ) ).toDouble(); + mShaftLengthUnits = static_cast( + elem.attribute( QStringLiteral( "shaft-length-units" ) ).toInt() ); + mMagnitudeMultiplier = elem.attribute( QStringLiteral( "magnitude-multiplier" ), QStringLiteral( "1" ) ).toDouble(); + mMagnitudeUnits = static_cast( + elem.attribute( QStringLiteral( "magnitude-units" ), QStringLiteral( "0" ) ).toInt() ); +} + +QDomElement QgsMeshRendererVectorWindBarbSettings::writeXml( QDomDocument &doc ) const +{ + QDomElement elem = doc.createElement( QStringLiteral( "vector-windbarb-settings" ) ); + elem.setAttribute( QStringLiteral( "shaft-length" ), mShaftLength ); + elem.setAttribute( QStringLiteral( "shaft-length-units" ), static_cast< int >( mShaftLengthUnits ) ); + elem.setAttribute( QStringLiteral( "magnitude-multiplier" ), mMagnitudeMultiplier ); + elem.setAttribute( QStringLiteral( "magnitude-units" ), static_cast< int >( mMagnitudeUnits ) ); + return elem; +} + +double QgsMeshRendererVectorWindBarbSettings::magnitudeMultiplier() const +{ + switch ( mMagnitudeUnits ) + { + case QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::Knots: + return 1.0; + case QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::MetersPerSecond: + return 3600.0 / 1852.0; + case QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::KilometersPerHour: + return 1.0 / 1.852; + case QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::MilesPerHour: + return 1.609344 / 1.852; + case QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::FeetPerSecond: + return 3600.0 / 1.852 / 5280.0 * 1.609344 ; + case QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::OtherUnit: + return mMagnitudeMultiplier; + } + return 1.0; // should not reach +} + +void QgsMeshRendererVectorWindBarbSettings::setMagnitudeMultiplier( double magnitudeMultiplier ) +{ + mMagnitudeMultiplier = magnitudeMultiplier; +} + +double QgsMeshRendererVectorWindBarbSettings::shaftLength() const +{ + return mShaftLength; +} + +void QgsMeshRendererVectorWindBarbSettings::setShaftLength( double shaftLength ) +{ + mShaftLength = shaftLength; +} + +Qgis::RenderUnit QgsMeshRendererVectorWindBarbSettings::shaftLengthUnits() +{ + return mShaftLengthUnits; +} + +void QgsMeshRendererVectorWindBarbSettings::setShaftLengthUnits( Qgis::RenderUnit shaftLengthUnit ) +{ + mShaftLengthUnits = shaftLengthUnit; +} + +QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit QgsMeshRendererVectorWindBarbSettings::magnitudeUnits() const +{ + return mMagnitudeUnits; +} + +void QgsMeshRendererVectorWindBarbSettings::setMagnitudeUnits( WindSpeedUnit units ) +{ + mMagnitudeUnits = units; +} diff --git a/src/core/mesh/qgsmeshrenderersettings.h b/src/core/mesh/qgsmeshrenderersettings.h index 4a62621f54b48..2cca7ce0ef20a 100644 --- a/src/core/mesh/qgsmeshrenderersettings.h +++ b/src/core/mesh/qgsmeshrenderersettings.h @@ -395,6 +395,82 @@ class CORE_EXPORT QgsMeshRendererVectorTracesSettings }; +/** + * \ingroup core + * + * \brief Represents a mesh renderer settings for vector datasets displayed with wind barbs + * + * \note The API is considered EXPERIMENTAL and can be changed without a notice + * + * \since QGIS 3.38 + */ +class CORE_EXPORT QgsMeshRendererVectorWindBarbSettings +{ + public: + //! Wind speed units. Wind barbs use knots so we use this enum for preset conversion values + enum class WindSpeedUnit + { + MetersPerSecond = 0, //!< Meters per second + KilometersPerHour, //!< Kilometers per hour + Knots, //!< Knots (Nautical miles per hour) + MilesPerHour, //!< Miles per hour + FeetPerSecond, //!< Feet per second + OtherUnit //!< Other unit + }; + + /** + * Returns the multiplier for the magnitude to convert it to knots, according to the units set with setMagnitudeUnits() + * A custom multiplier can be set with setMagnitudeMultiplier() for the case when units are set to OtherUnit + */ + double magnitudeMultiplier() const; + + /** + * Sets a multiplier for the magnitude to convert it to knots + */ + void setMagnitudeMultiplier( double magnitudeMultiplier ); + + /** + * Returns the shaft length (in millimeters) + */ + double shaftLength() const; + + /** + * Sets the shaft length (in millimeters) + */ + void setShaftLength( double shaftLength ); + + /** + * Sets the units for the shaft length + */ + Qgis::RenderUnit shaftLengthUnits(); + + /** + * Returns the units for the shaft length + */ + void setShaftLengthUnits( Qgis::RenderUnit shaftLengthUnit ); + + /** + * Returns the units that the data are in + */ + WindSpeedUnit magnitudeUnits() const; + + /** + * Sets the units that the data are in + */ + void setMagnitudeUnits( WindSpeedUnit units ); + + //! Writes configuration to a new DOM element + QDomElement writeXml( QDomDocument &doc ) const; + //! Reads configuration from the given DOM element + void readXml( const QDomElement &elem ); + + private: + double mShaftLength = 10; + Qgis::RenderUnit mShaftLengthUnits = Qgis::RenderUnit::Millimeters; + WindSpeedUnit mMagnitudeUnits = WindSpeedUnit::MetersPerSecond; + double mMagnitudeMultiplier = 1; +}; + /** * \ingroup core * @@ -419,7 +495,9 @@ class CORE_EXPORT QgsMeshRendererVectorSettings //! Displaying vector dataset with streamlines Streamlines, //! Displaying vector dataset with particle traces - Traces + Traces, + //! Displaying vector dataset with wind barbs + WindBarbs }; //! Returns line width of the arrow (in millimeters) @@ -551,6 +629,18 @@ class CORE_EXPORT QgsMeshRendererVectorSettings */ void setTracesSettings( const QgsMeshRendererVectorTracesSettings &tracesSettings ); + /** + * Returns settings for vector rendered with wind barbs + * \since QGIS 3.38 + */ + QgsMeshRendererVectorWindBarbSettings windBarbSettings() const; + + /** + * Sets settings for vector rendered with wind barbs + * \since QGIS 3.38 + */ + void setWindBarbSettings( const QgsMeshRendererVectorWindBarbSettings &windBarbSettings ); + //! Writes configuration to a new DOM element QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context = QgsReadWriteContext() ) const; //! Reads configuration from the given DOM element @@ -573,6 +663,7 @@ class CORE_EXPORT QgsMeshRendererVectorSettings QgsMeshRendererVectorArrowSettings mArrowsSettings; QgsMeshRendererVectorStreamlineSettings mStreamLinesSettings; QgsMeshRendererVectorTracesSettings mTracesSettings; + QgsMeshRendererVectorWindBarbSettings mWindBarbSettings; }; /** diff --git a/src/core/mesh/qgsmeshvectorrenderer.cpp b/src/core/mesh/qgsmeshvectorrenderer.cpp index 34533115f29f5..d91f3dcbff9d6 100644 --- a/src/core/mesh/qgsmeshvectorrenderer.cpp +++ b/src/core/mesh/qgsmeshvectorrenderer.cpp @@ -34,15 +34,6 @@ #define M_DEG2RAD 0.0174532925 #endif -inline double mag( double input ) -{ - if ( input < 0.0 ) - { - return -1.0; - } - return 1.0; -} - inline bool nodataValue( double x, double y ) { return ( std::isnan( x ) || std::isnan( y ) ); @@ -62,11 +53,11 @@ QgsMeshVectorArrowRenderer::QgsMeshVectorArrowRenderer( , mDatasetValuesMag( datasetValuesMag ) , mMinMag( datasetMagMinimumValue ) , mMaxMag( datasetMagMaximumValue ) + , mDataType( dataType ) + , mBufferedExtent( context.mapExtent() ) , mContext( context ) , mCfg( settings ) - , mDataType( dataType ) , mOutputSize( size ) - , mBufferedExtent( context.mapExtent() ) { // should be checked in caller Q_ASSERT( !mDatasetValuesMag.empty() ); @@ -148,10 +139,10 @@ bool QgsMeshVectorArrowRenderer::calcVectorLineEnd( // Determine the angle of the vector, counter-clockwise, from east // (and associated trigs) - const double vectorAngle = -1.0 * atan( ( -1.0 * yVal ) / xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD; + const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD; - cosAlpha = cos( vectorAngle ) * mag( xVal ); - sinAlpha = sin( vectorAngle ) * mag( xVal ); + cosAlpha = cos( vectorAngle ); + sinAlpha = sin( vectorAngle ); // Now determine the X and Y distances of the end of the line from the start double xDist = 0.0; @@ -202,11 +193,8 @@ bool QgsMeshVectorArrowRenderer::calcVectorLineEnd( vectorLength = sqrt( xDist * xDist + yDist * yDist ); - // Check to see if both of the coords are outside the QImage area, if so, skip the whole vector - if ( ( lineStart.x() < 0 || lineStart.x() > mOutputSize.width() || - lineStart.y() < 0 || lineStart.y() > mOutputSize.height() ) && - ( lineEnd.x() < 0 || lineEnd.x() > mOutputSize.width() || - lineEnd.y() < 0 || lineEnd.y() > mOutputSize.height() ) ) + // skip rendering if line bbox does not intersect the QImage area + if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) ) return true; return false; //success @@ -292,7 +280,7 @@ void QgsMeshVectorArrowRenderer::drawVectorDataOnPoints( const QSet indexes const double V = mDatasetValuesMag[i]; // pre-calculated magnitude const QgsPointXY lineStart = mContext.mapToPixel().transform( center.x(), center.y() ); - drawVectorArrow( lineStart, xVal, yVal, V ); + drawVector( lineStart, xVal, yVal, V ); } } @@ -405,13 +393,13 @@ void QgsMeshVectorArrowRenderer::drawVectorDataOnGrid( ) continue; const QgsPointXY lineStart( x, y ); - drawVectorArrow( lineStart, val.x(), val.y(), val.scalar() ); + drawVector( lineStart, val.x(), val.y(), val.scalar() ); } } } } -void QgsMeshVectorArrowRenderer::drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude ) +void QgsMeshVectorArrowRenderer::drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude ) { QgsPointXY lineEnd; double vectorLength; @@ -518,10 +506,158 @@ QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer( layerExtent, datasetMagMaximumValue ); break; + case QgsMeshRendererVectorSettings::WindBarbs: + renderer = new QgsMeshVectorWindBarbRenderer( + m, + datasetVectorValues, + datasetValuesMag, + datasetMagMaximumValue, + datasetMagMinimumValue, + dataType, + settings, + context, + size ); + break; } return renderer; } +QgsMeshVectorWindBarbRenderer::QgsMeshVectorWindBarbRenderer( + const QgsTriangularMesh &m, + const QgsMeshDataBlock &datasetValues, + const QVector &datasetValuesMag, + double datasetMagMaximumValue, double datasetMagMinimumValue, + QgsMeshDatasetGroupMetadata::DataType dataType, + const QgsMeshRendererVectorSettings &settings, + QgsRenderContext &context, + QSize size ) : QgsMeshVectorArrowRenderer( m, + datasetValues, + datasetValuesMag, + datasetMagMinimumValue, + datasetMagMaximumValue, + dataType, + settings, + context, + size ) +{ +} + +QgsMeshVectorWindBarbRenderer::~QgsMeshVectorWindBarbRenderer() = default; + +void QgsMeshVectorWindBarbRenderer::drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude ) +{ + // do not render if magnitude is outside of the filtered range (if filtering is enabled) + if ( mCfg.filterMin() >= 0 && magnitude < mCfg.filterMin() ) + return; + if ( mCfg.filterMax() >= 0 && magnitude > mCfg.filterMax() ) + return; + + QPen pen( mContext.painter()->pen() ); + pen.setColor( mVectorColoring.color( magnitude ) ); + mContext.painter()->setPen( pen ); + + // we need a brush to fill center circle and pennants + QBrush brush( pen.color() ); + mContext.painter()->setBrush( brush ); + + const double shaftLength = mContext.convertToPainterUnits( mCfg.windBarbSettings().shaftLength(), + mCfg.windBarbSettings().shaftLengthUnits() ); + if ( shaftLength < 1 ) + return; + + const double d = shaftLength / 25; // this is a magic number ratio between shaft length and other barb dimensions + const double centerRadius = d; + const double zeroCircleRadius = 2 * d; + const double barbLength = 8 * d + pen.widthF(); + const double barbAngle = 135; + const double barbOffset = 2 * d + pen.widthF(); + + // Determine the angle of the vector, counter-clockwise, from east + // (and associated trigs) + const double vectorAngle = std::atan2( yVal, xVal ) - mContext.mapToPixel().mapRotation() * M_DEG2RAD; + + // Now determine the X and Y distances of the end of the line from the start + // Flip the Y axis (pixel vs real-world axis) + const double xDist = cos( vectorAngle ) * shaftLength; + const double yDist = - sin( vectorAngle ) * shaftLength; + + // Determine the line coords + const QgsPointXY lineEnd = QgsPointXY( lineStart.x() - xDist, + lineStart.y() - yDist ); + + // skip rendering if line bbox does not intersect the QImage area + if ( !QgsRectangle( lineStart, lineEnd ).intersects( QgsRectangle( 0, 0, mOutputSize.width(), mOutputSize.height() ) ) ) + return; + + // scale the magnitude to convert it to knots + double knots = magnitude * mCfg.windBarbSettings().magnitudeMultiplier() ; + QgsPointXY nextLineOrigin = lineEnd; + + // special case for no wind, just an empty circle + if ( knots < 2.5 ) + { + mContext.painter()->setBrush( Qt::NoBrush ); + mContext.painter()->drawEllipse( lineStart.toQPointF(), zeroCircleRadius, zeroCircleRadius ); + mContext.painter()->setBrush( brush ); + return; + } + + const double azimuth = lineEnd.azimuth( lineStart ); + + // conditionally draw the shaft + if ( knots < 47.5 && knots > 7.5 ) + { + // When first barb is a '10', we want to draw the shaft and barb as a single polyline for a proper join + const QVector< QPointF > pts{ lineStart.toQPointF(), + lineEnd.toQPointF(), + nextLineOrigin.project( barbLength, azimuth + barbAngle ).toQPointF() }; + mContext.painter()->drawPolyline( pts ); + nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth ); + knots -= 10; + } + else + { + // draw just the shaft + mContext.painter()->drawLine( lineStart.toQPointF(), lineEnd.toQPointF() ); + } + + // draw the center circle + mContext.painter()->drawEllipse( lineStart.toQPointF(), centerRadius, centerRadius ); + + // draw pennants (50) + while ( knots > 47.5 ) + { + const QVector< QPointF > pts{ nextLineOrigin.toQPointF(), + nextLineOrigin.project( barbLength / 1.414, azimuth + 90 ).toQPointF(), + nextLineOrigin.project( barbLength / 1.414, azimuth ).toQPointF() }; + mContext.painter()->drawPolygon( pts ); + knots -= 50; + + // don't use an offset for the next pennant + if ( knots > 47.5 ) + nextLineOrigin = nextLineOrigin.project( barbLength / 1.414, azimuth ); + else + nextLineOrigin = nextLineOrigin.project( barbLength / 1.414 + barbOffset, azimuth ); + } + + // draw large barbs (10) + while ( knots > 7.5 ) + { + mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength, azimuth + barbAngle ).toQPointF() ); + nextLineOrigin = nextLineOrigin.project( barbOffset, azimuth ); + knots -= 10; + } + + // draw small barb (5) + if ( knots > 2.5 ) + { + // a single '5' barb should not start at the line end + if ( nextLineOrigin == lineEnd ) + nextLineOrigin = nextLineOrigin.project( barbLength / 2, azimuth ); + + mContext.painter()->drawLine( nextLineOrigin.toQPointF(), nextLineOrigin.project( barbLength / 2, azimuth + barbAngle ).toQPointF() ); + } +} ///@endcond diff --git a/src/core/mesh/qgsmeshvectorrenderer.h b/src/core/mesh/qgsmeshvectorrenderer.h index bf5e62cc464dc..6bcf6a593eba3 100644 --- a/src/core/mesh/qgsmeshvectorrenderer.h +++ b/src/core/mesh/qgsmeshvectorrenderer.h @@ -105,7 +105,7 @@ class QgsMeshVectorArrowRenderer : public QgsMeshVectorRenderer //! Draws data on user-defined grid void drawVectorDataOnGrid( ); //! Draws arrow from start point and vector data - void drawVectorArrow( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude ); + virtual void drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude ); //! Calculates the end point of the arrow based on start point and vector data bool calcVectorLineEnd( QgsPointXY &lineEnd, double &vectorLength, @@ -130,18 +130,46 @@ class QgsMeshVectorArrowRenderer : public QgsMeshVectorRenderer const QVector &mDatasetValuesMag; //magnitudes double mMinMag = 0.0; double mMaxMag = 0.0; - QgsRenderContext &mContext; - const QgsMeshRendererVectorSettings mCfg; QgsMeshDatasetGroupMetadata::DataType mDataType = QgsMeshDatasetGroupMetadata::DataType::DataOnVertices; - QSize mOutputSize; QgsRectangle mBufferedExtent; QPen mPen; + protected: + QgsRenderContext &mContext; + const QgsMeshRendererVectorSettings mCfg; + QSize mOutputSize; QgsInterpolatedLineColor mVectorColoring; }; +/** + * \ingroup core + * + * \brief Helper private class for rendering vector datasets using Wind Barbs + * + * \note not available in Python bindings + * \since QGIS 3.38 + */ +class QgsMeshVectorWindBarbRenderer : public QgsMeshVectorArrowRenderer +{ + public: + //! Ctor + QgsMeshVectorWindBarbRenderer( const QgsTriangularMesh &m, + const QgsMeshDataBlock &datasetValues, + const QVector &datasetValuesMag, + double datasetMagMaximumValue, + double datasetMagMinimumValue, + QgsMeshDatasetGroupMetadata::DataType dataType, + const QgsMeshRendererVectorSettings &settings, + QgsRenderContext &context, + QSize size ); + //! Dtor + ~QgsMeshVectorWindBarbRenderer() override; + + private: + void drawVector( const QgsPointXY &lineStart, double xVal, double yVal, double magnitude ) override; +}; ///@endcond diff --git a/src/core/mesh/qgstopologicalmesh.cpp b/src/core/mesh/qgstopologicalmesh.cpp index b789f224f99d9..e6b040f7e0675 100644 --- a/src/core/mesh/qgstopologicalmesh.cpp +++ b/src/core/mesh/qgstopologicalmesh.cpp @@ -703,7 +703,7 @@ QgsMeshEditingError QgsTopologicalMesh::counterClockwiseFaces( QgsMeshFace &face if ( error != QgsMeshEditingError() ) return error; - if ( clockwise > 0 )// clockwise --> reverse the order of the index; + if ( clockwise )// clockwise --> reverse the order of the index; { for ( int i = 0; i < faceSize / 2; ++i ) { diff --git a/src/core/pal/feature.cpp b/src/core/pal/feature.cpp index 3d9962de7c0d4..aa676b38afbd0 100644 --- a/src/core/pal/feature.cpp +++ b/src/core/pal/feature.cpp @@ -41,13 +41,11 @@ #include "qgsmessagelog.h" #include "qgsgeometryutils.h" #include "qgsgeometryutils_base.h" -#include "qgslabeling.h" #include "qgspolygon.h" #include "qgstextrendererutils.h" #include #include -#include using namespace pal; @@ -523,6 +521,14 @@ void createCandidateAtOrderedPositionOverPoint( double &labelX, double &labelY, deltaX = -visualMargin.left() + symbolWidthOffset; deltaY = -labelHeight + visualMargin.top() - symbolHeightOffset; break; + + case Qgis::LabelPredefinedPointPosition::OverPoint: + quadrant = LabelPosition::QuadrantOver; + alpha = 0; + distanceToLabel = 0; + deltaX = -labelWidth / 2.0; + deltaY = -labelHeight / 2.0;// TODO - should this be adjusted by visual margin?? + break; } // Take care of the label angle when creating candidates. See pr comments #44944 for details diff --git a/src/core/pal/labelposition.cpp b/src/core/pal/labelposition.cpp index ba1a576c58d8a..caf010ed7af13 100644 --- a/src/core/pal/labelposition.cpp +++ b/src/core/pal/labelposition.cpp @@ -574,7 +574,7 @@ double LabelPosition::getDistanceToPoint( double xp, double yp, bool useOuterBou geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, xp, yp ) ); contains = ( GEOSPreparedContainsProperly_r( geosctxt, mPreparedOuterBoundsGeos, point.get() ) == 1 ); } - catch ( GEOSException &e ) + catch ( GEOSException & ) { contains = false; } diff --git a/src/core/pointcloud/qgslazdecoder.cpp b/src/core/pointcloud/qgslazdecoder.cpp index 67a4e3b7d462b..efde5717dba55 100644 --- a/src/core/pointcloud/qgslazdecoder.cpp +++ b/src/core/pointcloud/qgslazdecoder.cpp @@ -38,7 +38,9 @@ #include "lazperf/readers.hpp" #if defined(_MSC_VER) +#ifndef UNICODE #define UNICODE +#endif #include #include #endif diff --git a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp index b4868da229b2e..276167147f58b 100644 --- a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp @@ -688,7 +688,7 @@ void QgsPointCloudLayerRenderer::renderTriangulatedSurface( QgsPointCloudRenderC { delaunator.reset( new delaunator::Delaunator( points ) ); } - catch ( std::exception &e ) + catch ( std::exception & ) { // something went wrong, better to retrieve initial state QgsDebugMsgLevel( QStringLiteral( "Error with triangulation" ), 4 ); diff --git a/src/core/processing/models/qgsprocessingmodelalgorithm.cpp b/src/core/processing/models/qgsprocessingmodelalgorithm.cpp index 9df13d6240d92..398954d9b443e 100644 --- a/src/core/processing/models/qgsprocessingmodelalgorithm.cpp +++ b/src/core/processing/models/qgsprocessingmodelalgorithm.cpp @@ -300,9 +300,11 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa QSet< QString > toExecute; QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin(); QSet< QString > broken; + const QSet childSubset = context.modelInitialRunConfig() ? context.modelInitialRunConfig()->childAlgorithmSubset() : QSet(); + const bool useSubsetOfChildren = !childSubset.empty(); for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt ) { - if ( childIt->isActive() ) + if ( childIt->isActive() && ( !useSubsetOfChildren || childSubset.contains( childIt->childId() ) ) ) { if ( childIt->algorithm() ) toExecute.insert( childIt->childId() ); @@ -320,14 +322,31 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa QgsProcessingMultiStepFeedback modelFeedback( toExecute.count(), feedback ); QgsExpressionContext baseContext = createExpressionContext( parameters, context ); - QVariantMap childResults; - QVariantMap childInputs; + QVariantMap &childInputs = context.modelResult().rawChildInputs(); + QVariantMap &childResults = context.modelResult().rawChildOutputs(); + QSet< QString > &executed = context.modelResult().executedChildIds(); + + // start with initial configuration from the context's model configuration (allowing us to + // resume execution using a previous state) + if ( QgsProcessingModelInitialRunConfig *config = context.modelInitialRunConfig() ) + { + childInputs = config->initialChildInputs(); + childResults = config->initialChildOutputs(); + executed = config->previouslyExecutedChildAlgorithms(); + // discard the model config, this should only be used when running the top level model + context.setModelInitialRunConfig( nullptr ); + } + if ( useSubsetOfChildren ) + { + executed.subtract( childSubset ); + } QVariantMap finalResults; - QSet< QString > executed; + bool executedAlg = true; int previousHtmlLogLength = feedback->htmlLog().length(); - while ( executedAlg && executed.count() < toExecute.count() ) + int countExecuted = 0; + while ( executedAlg && countExecuted < toExecute.count() ) { executedAlg = false; for ( const QString &childId : std::as_const( toExecute ) ) @@ -431,7 +450,10 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa else { context.pushToThread( qApp->thread() ); +// silence false positive leak warning +#ifndef __clang_analyzer__ QMetaObject::invokeMethod( qApp, prepareOnMainThread, Qt::BlockingQueuedConnection ); +#endif } Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" ); @@ -461,7 +483,10 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa feedback->pushWarning( QObject::tr( "Algorithm “%1” cannot be run in a background thread, switching to main thread for this step" ).arg( childAlg->displayName() ) ); context.pushToThread( qApp->thread() ); +// silence false positive leak warning +#ifndef __clang_analyzer__ QMetaObject::invokeMethod( qApp, runOnMainThread, Qt::BlockingQueuedConnection ); +#endif } else { @@ -493,7 +518,10 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa else { context.pushToThread( qApp->thread() ); +// silence false positive leak warning +#ifndef __clang_analyzer__ QMetaObject::invokeMethod( qApp, postProcessOnMainThread, Qt::BlockingQueuedConnection ); +#endif } Q_ASSERT_X( QThread::currentThread() == context.thread(), "QgsProcessingModelAlgorithm::processAlgorithm", "context was not transferred back to model thread" ); @@ -614,7 +642,8 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa } childAlg.reset( nullptr ); - modelFeedback.setCurrentStep( executed.count() ); + countExecuted++; + modelFeedback.setCurrentStep( countExecuted ); if ( feedback && !skipGenericLogging ) { feedback->pushInfo( QObject::tr( "OK. Execution took %1 s (%n output(s)).", nullptr, results.count() ).arg( childTime.elapsed() / 1000.0 ) ); @@ -646,7 +675,7 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa break; } if ( feedback ) - feedback->pushDebugInfo( QObject::tr( "Model processed OK. Executed %n algorithm(s) total in %1 s.", nullptr, executed.count() ).arg( totalTime.elapsed() / 1000.0 ) ); + feedback->pushDebugInfo( QObject::tr( "Model processed OK. Executed %n algorithm(s) total in %1 s.", nullptr, countExecuted ).arg( static_cast< double >( totalTime.elapsed() ) / 1000.0 ) ); mResults = finalResults; mResults.insert( QStringLiteral( "CHILD_RESULTS" ), childResults ); diff --git a/src/core/processing/models/qgsprocessingmodelconfig.cpp b/src/core/processing/models/qgsprocessingmodelconfig.cpp new file mode 100644 index 0000000000000..be9e30115338f --- /dev/null +++ b/src/core/processing/models/qgsprocessingmodelconfig.cpp @@ -0,0 +1,42 @@ +/*************************************************************************** + qgsprocessingmodelconfig.cpp + ---------------------- + begin : April 2024 + copyright : (C) 2024 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 "qgsprocessingmodelconfig.h" +#include "qgsmaplayerstore.h" + +QgsProcessingModelInitialRunConfig::QgsProcessingModelInitialRunConfig() = default; + +QgsProcessingModelInitialRunConfig::~QgsProcessingModelInitialRunConfig() = default; + +QgsMapLayerStore *QgsProcessingModelInitialRunConfig::previousLayerStore() +{ + return mModelInitialLayerStore.get(); +} + +std::unique_ptr QgsProcessingModelInitialRunConfig::takePreviousLayerStore() +{ + return std::move( mModelInitialLayerStore ); +} + +void QgsProcessingModelInitialRunConfig::setPreviousLayerStore( std::unique_ptr store ) +{ + if ( store ) + { + Q_ASSERT_X( !store->thread(), "QgsProcessingModelInitialRunConfig::setPreviousLayerStore", "store must have been pushed to a nullptr thread prior to calling this method" ); + } + mModelInitialLayerStore = std::move( store ); +} diff --git a/src/core/processing/models/qgsprocessingmodelconfig.h b/src/core/processing/models/qgsprocessingmodelconfig.h new file mode 100644 index 0000000000000..9f455fd82b121 --- /dev/null +++ b/src/core/processing/models/qgsprocessingmodelconfig.h @@ -0,0 +1,160 @@ +/*************************************************************************** + qgsprocessingmodelconfig.h + ---------------------- + begin : April 2024 + copyright : (C) 2024 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 QGSPROCESSINGMODELCONFIG_H +#define QGSPROCESSINGMODELCONFIG_H + +#include "qgis_core.h" +#include "qgis.h" +#include + +#define SIP_NO_FILE + +class QgsMapLayerStore; + +/** + * \ingroup core + * \brief Configuration settings which control how a Processing model is executed. + * + * \note Not available in Python bindings. + * + * \since QGIS 3.38 +*/ +class CORE_EXPORT QgsProcessingModelInitialRunConfig +{ + public: + + QgsProcessingModelInitialRunConfig(); + ~QgsProcessingModelInitialRunConfig(); + + /** + * Returns the subset of child algorithms to run (by child ID). + * + * An empty set indicates the entire model should be run. + * + * \see setChildAlgorithmSubset() + */ + QSet childAlgorithmSubset() const { return mChildAlgorithmSubset; } + + /** + * Sets the \a subset of child algorithms to run (by child ID). + * + * An empty set indicates the entire model should be run. + * + * \see childAlgorithmSubset() + */ + void setChildAlgorithmSubset( const QSet &subset ) { mChildAlgorithmSubset = subset; } + + /** + * Returns the map of child algorithm inputs to use as the initial state when running the model. + * + * Map keys refer to the child algorithm IDs. + * + * \see setInitialChildInputs() + */ + QVariantMap initialChildInputs() const { return mInitialChildInputs; } + + /** + * Sets the map of child algorithm \a inputs to use as the initial state when running the model. + * + * Map keys refer to the child algorithm IDs. + * + * \see initialChildInputs() + */ + void setInitialChildInputs( const QVariantMap &inputs ) { mInitialChildInputs = inputs; } + + /** + * Returns the map of child algorithm outputs to use as the initial state when running the model. + * + * Map keys refer to the child algorithm IDs. + * + * \see setInitialChildOutputs() + */ + QVariantMap initialChildOutputs() const { return mInitialChildOutputs; } + + /** + * Sets the map of child algorithm \a outputs to use as the initial state when running the model. + * + * Map keys refer to the child algorithm IDs. + * + * \see initialChildOutputs() + */ + void setInitialChildOutputs( const QVariantMap &outputs ) { mInitialChildOutputs = outputs; } + + /** + * Returns the set of previously executed child algorithm IDs to use as the initial state + * when running the model. + * + * \see setPreviouslyExecutedChildAlgorithms() + */ + QSet< QString > previouslyExecutedChildAlgorithms() const { return mPreviouslyExecutedChildren; } + + /** + * Sets the previously executed child algorithm IDs to use as the initial state + * when running the model. + * + * \see previouslyExecutedChildAlgorithms() + */ + void setPreviouslyExecutedChildAlgorithms( const QSet< QString > &children ) { mPreviouslyExecutedChildren = children; } + + /** + * Returns a reference to a map store containing copies of temporary layers generated + * during previous model executions. + * + * This may be NULLPTR. + * + * \see setPreviousLayerStore() + * \see takePreviousLayerStore() + */ + QgsMapLayerStore *previousLayerStore(); + + /** + * Takes the map store containing copies of temporary layers generated + * during previous model executions. + * + * May return NULLPTR if this is not available. + * + * \see previousLayerStore() + * \see setPreviousLayerStore() + */ + std::unique_ptr< QgsMapLayerStore > takePreviousLayerStore(); + + /** + * Sets the map store containing copies of temporary layers generated + * during previous model executions. + * + * \warning \a store must have previous been moved to a NULLPTR thread via a call + * to QObject::moveToThread. An assert will be triggered if this condition is not met. + * + * \see previousLayerStore() + * \see takePreviousLayerStore() + */ + void setPreviousLayerStore( std::unique_ptr< QgsMapLayerStore > store ); + + private: + + QSet mChildAlgorithmSubset; + QVariantMap mInitialChildInputs; + QVariantMap mInitialChildOutputs; + QSet< QString > mPreviouslyExecutedChildren; + + std::unique_ptr< QgsMapLayerStore > mModelInitialLayerStore; + + +}; + +#endif // QGSPROCESSINGMODELCONFIG_H diff --git a/src/core/processing/models/qgsprocessingmodelresult.cpp b/src/core/processing/models/qgsprocessingmodelresult.cpp index 932663cce706a..815efc86eca5d 100644 --- a/src/core/processing/models/qgsprocessingmodelresult.cpp +++ b/src/core/processing/models/qgsprocessingmodelresult.cpp @@ -29,3 +29,28 @@ QgsProcessingModelChildAlgorithmResult::QgsProcessingModelChildAlgorithmResult() // QgsProcessingModelResult::QgsProcessingModelResult() = default; + +void QgsProcessingModelResult::clear() +{ + mChildResults.clear(); + mExecutedChildren.clear(); + mRawChildInputs.clear(); + mRawChildOutputs.clear(); +} + +void QgsProcessingModelResult::mergeWith( const QgsProcessingModelResult &other ) +{ + for ( auto it = other.mChildResults.constBegin(); it != other.mChildResults.constEnd(); ++it ) + { + mChildResults.insert( it.key(), it.value() ); + } + mExecutedChildren.unite( other.mExecutedChildren ); + for ( auto it = other.mRawChildInputs.constBegin(); it != other.mRawChildInputs.constEnd(); ++it ) + { + mRawChildInputs.insert( it.key(), it.value() ); + } + for ( auto it = other.mRawChildOutputs.constBegin(); it != other.mRawChildOutputs.constEnd(); ++it ) + { + mRawChildOutputs.insert( it.key(), it.value() ); + } +} diff --git a/src/core/processing/models/qgsprocessingmodelresult.h b/src/core/processing/models/qgsprocessingmodelresult.h index df59884fe9bc4..e49111ebacd43 100644 --- a/src/core/processing/models/qgsprocessingmodelresult.h +++ b/src/core/processing/models/qgsprocessingmodelresult.h @@ -20,6 +20,7 @@ #include "qgis_core.h" #include "qgis.h" +#include /** * \ingroup core @@ -122,6 +123,17 @@ class CORE_EXPORT QgsProcessingModelResult QgsProcessingModelResult(); + /** + * Clears any existing results. + */ + void clear(); + + /** + * Merges this set of results with an \a other set of results. + * + * Conflicting results from \a other will replace results in this object. + */ + void mergeWith( const QgsProcessingModelResult &other ); /** * Returns the map of child algorithm results. @@ -139,10 +151,47 @@ class CORE_EXPORT QgsProcessingModelResult */ QMap< QString, QgsProcessingModelChildAlgorithmResult > &childResults() SIP_SKIP { return mChildResults; } + /** + * Returns a reference to the map of raw child algorithm inputs. + * + * Map keys refer to the child algorithm IDs. Map values may take any form, including + * values which are not safe to access from Python. + * + * \note Not available in Python bindings + */ + QVariantMap &rawChildInputs() SIP_SKIP { return mRawChildInputs; } + + /** + * Returns a reference to the map of raw child algorithm outputs. + * + * Map keys refer to the child algorithm IDs. Map values may take any form, including + * values which are not safe to access from Python. + * + * \note Not available in Python bindings + */ + QVariantMap &rawChildOutputs() SIP_SKIP { return mRawChildOutputs; } + + /** + * Returns a reference to the set of child algorithm IDs which were executed + * during the model execution. + * + * \note Not available in Python bindings + */ + QSet< QString > &executedChildIds() SIP_SKIP { return mExecutedChildren; } + + /** + * Returns the set of child algorithm IDs which were executed during the model execution. + */ + QSet< QString > executedChildIds() const { return mExecutedChildren; } + private: QMap< QString, QgsProcessingModelChildAlgorithmResult > mChildResults; + QSet< QString > mExecutedChildren; + QVariantMap mRawChildInputs; + QVariantMap mRawChildOutputs; + }; diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index bd131754473ca..a569cf50a3363 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -608,10 +608,25 @@ QVariantMap QgsProcessingAlgorithm::runPrepared( const QVariantMap ¶meters, mLocalContext.reset( new QgsProcessingContext() ); // copy across everything we can safely do from the passed context mLocalContext->copyThreadSafeSettings( context ); + // and we'll run the actual algorithm processing using the local thread safe context runContext = mLocalContext.get(); } + std::unique_ptr< QgsProcessingModelInitialRunConfig > modelConfig = context.takeModelInitialRunConfig(); + if ( modelConfig ) + { + std::unique_ptr< QgsMapLayerStore > modelPreviousLayerStore = modelConfig->takePreviousLayerStore(); + if ( modelPreviousLayerStore ) + { + // move layers from previous layer store to context's temporary layer store, in a thread-safe way + Q_ASSERT_X( !modelPreviousLayerStore->thread(), "QgsProcessingAlgorithm::runPrepared", "QgsProcessingModelConfig::modelPreviousLayerStore must have been pushed to a nullptr thread" ); + modelPreviousLayerStore->moveToThread( QThread::currentThread() ); + runContext->temporaryLayerStore()->transferLayersFromStore( modelPreviousLayerStore.get() ); + } + runContext->setModelInitialRunConfig( std::move( modelConfig ) ); + } + mHasExecuted = true; try { diff --git a/src/core/processing/qgsprocessingcontext.cpp b/src/core/processing/qgsprocessingcontext.cpp index 09a39ca4ae431..3feb9876fced2 100644 --- a/src/core/processing/qgsprocessingcontext.cpp +++ b/src/core/processing/qgsprocessingcontext.cpp @@ -310,3 +310,18 @@ void QgsProcessingContext::LayerDetails::setOutputLayerName( QgsMapLayer *layer } } + +QgsProcessingModelInitialRunConfig *QgsProcessingContext::modelInitialRunConfig() +{ + return mModelConfig.get(); +} + +void QgsProcessingContext::setModelInitialRunConfig( std::unique_ptr< QgsProcessingModelInitialRunConfig > config ) +{ + mModelConfig = std::move( config ); +} + +std::unique_ptr< QgsProcessingModelInitialRunConfig > QgsProcessingContext::takeModelInitialRunConfig() +{ + return std::move( mModelConfig ); +} diff --git a/src/core/processing/qgsprocessingcontext.h b/src/core/processing/qgsprocessingcontext.h index 2fe0ce74ece25..48bef7c993e4b 100644 --- a/src/core/processing/qgsprocessingcontext.h +++ b/src/core/processing/qgsprocessingcontext.h @@ -25,6 +25,7 @@ #include "qgsprocessingfeedback.h" #include "qgsprocessingutils.h" #include "qgsprocessingmodelresult.h" +#include "qgsprocessingmodelconfig.h" #include #include @@ -735,6 +736,53 @@ class CORE_EXPORT QgsProcessingContext */ QStringList asQgisProcessArguments( QgsProcessingContext::ProcessArgumentFlags flags = QgsProcessingContext::ProcessArgumentFlags() ) const; + /** + * Returns a reference to the model initial run configuration, used + * to run a model algorithm. + * + * This may be NULLPTR, e.g. when the context is not being used to run a model. + * + * \note This configuration will only be used when running a "top-level" model algorithm, and + * will not be passed on to child models used within that initial top-level model. + * + * \note Not available in Python bindings + * + * \see setModelInitialRunConfig() + * \see takeModelInitialRunConfig() + * + * \since QGIS 3.38 + */ + QgsProcessingModelInitialRunConfig *modelInitialRunConfig() SIP_SKIP; + + /** + * Takes the model initial run configuration from the context. + * + * May return NULLPTR, e.g. when the context is not being used to run a model. + * + * \note Not available in Python bindings + * + * \see modelInitialRunConfig() + * \see setModelInitialRunConfig() + * + * \since QGIS 3.38 + */ + std::unique_ptr< QgsProcessingModelInitialRunConfig > takeModelInitialRunConfig() SIP_SKIP; + + /** + * Sets the model initial run configuration, used to run a model algorithm. + * + * \note This configuration will only be used when running a "top-level" model algorithm, and + * will not be passed on to child models used within that initial top-level model. + * + * \note Not available in Python bindings + * + * \see modelInitialRunConfig() + * \see takeModelInitialRunConfig() + * + * \since QGIS 3.38 + */ + void setModelInitialRunConfig( std::unique_ptr< QgsProcessingModelInitialRunConfig > config ) SIP_SKIP; + /** * Returns the model results, populated when the context is used to run a model algorithm. * @@ -786,6 +834,7 @@ class CORE_EXPORT QgsProcessingContext QString mTemporaryFolderOverride; int mMaximumThreads = QThread::idealThreadCount(); + std::unique_ptr< QgsProcessingModelInitialRunConfig > mModelConfig; QgsProcessingModelResult mModelResult; #ifdef SIP_RUN diff --git a/src/core/providers/gdal/qgsgdalprovider.cpp b/src/core/providers/gdal/qgsgdalprovider.cpp index 1040abb66fc7b..e90046bd58b80 100644 --- a/src/core/providers/gdal/qgsgdalprovider.cpp +++ b/src/core/providers/gdal/qgsgdalprovider.cpp @@ -37,7 +37,6 @@ #include "qgsrasterpyramid.h" #include "qgspointxy.h" #include "qgssettings.h" -#include "qgsogrutils.h" #include "qgsruntimeprofiler.h" #include "qgsprovidersublayerdetails.h" #include "qgsproviderutils.h" @@ -168,6 +167,8 @@ QgsGdalProvider::QgsGdalProvider( const QString &uri, const ProviderOptions &opt return; } + invalidateNetworkCache(); + mGdalDataset = nullptr; if ( dataset ) { @@ -480,6 +481,7 @@ void QgsGdalProvider::closeDataset() void QgsGdalProvider::reloadProviderData() { QMutexLocker locker( mpMutex ); + invalidateNetworkCache(); closeDataset(); mHasInit = false; @@ -1835,12 +1837,35 @@ QList QgsGdalProvider::sublayerDetails( GDALDatasetH } else { + + // Check if the layer has TIFFTAG_DOCUMENTNAME associated with it. If so, use that name. + GDALDatasetH datasetHandle = GDALOpen( name, GA_ReadOnly ); + + if ( datasetHandle ) + { + + QString tagTIFFDocumentName = GDALGetMetadataItem( datasetHandle, "TIFFTAG_DOCUMENTNAME", nullptr ); + if ( ! tagTIFFDocumentName.isEmpty() ) + { + layerName = tagTIFFDocumentName; + } + + QString tagTIFFImageDescription = GDALGetMetadataItem( datasetHandle, "TIFFTAG_IMAGEDESCRIPTION", nullptr ); + if ( ! tagTIFFImageDescription.isEmpty() ) + { + layerDesc = tagTIFFImageDescription; + } + + GDALClose( datasetHandle ); + } + // try to extract layer name from a path like 'NETCDF:"/baseUri":cell_node' sepIdx = layerName.indexOf( datasetPath + "\":" ); if ( sepIdx >= 0 ) { layerName = layerName.mid( layerName.indexOf( datasetPath + "\":" ) + datasetPath.length() + 2 ); } + } QgsProviderSublayerDetails details; @@ -4249,6 +4274,21 @@ Qgis::ProviderStyleStorageCapabilities QgsGdalProvider::styleStorageCapabilities return storageCapabilities; } +void QgsGdalProvider::invalidateNetworkCache() +{ + const QString uri( dataSourceUri() ); + + if ( uri.startsWith( QLatin1String( "/vsicurl/" ) ) || + uri.startsWith( QLatin1String( "/vsis3/" ) ) || + uri.startsWith( QLatin1String( "/vsigs/" ) ) || + uri.startsWith( QLatin1String( "/vsiaz/" ) ) || + uri.startsWith( QLatin1String( "/vsiadls/" ) ) ) + { + QgsDebugMsgLevel( QString( "Invalidating cache for %1" ).arg( uri ), 3 ); + VSICurlPartialClearCache( uri.toUtf8().constData() ); + } +} + // pyramids resampling // see http://www.gdal.org/gdaladdo.html @@ -4597,8 +4637,8 @@ int QgsGdalProviderMetadata::listStyles( const QString &uri, QStringList &ids, Q errCause = QObject::tr( "Cannot open %1." ).arg( uri ); return -1; } - QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); - QString layerName = uriParts["layerName"].toString(); + + QString layerName = getLayerNameForStyle( uri, ds ); return QgsOgrUtils::listStyles( ds.get(), layerName, "", ids, names, descriptions, errCause ); } @@ -4611,8 +4651,8 @@ bool QgsGdalProviderMetadata::styleExists( const QString &uri, const QString &st errCause = QObject::tr( "Cannot open %1." ).arg( uri ); return false; } - QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); - QString layerName = uriParts["layerName"] .toString(); + + QString layerName = getLayerNameForStyle( uri, ds ); return QgsOgrUtils::styleExists( ds.get(), layerName, "", styleId, errCause ); } @@ -4651,8 +4691,8 @@ bool QgsGdalProviderMetadata::saveStyle( const QString &uri, const QString &qmlS errCause = QObject::tr( "Cannot open %1." ).arg( uri ); return false; } - QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); - QString layerName = uriParts["layerName"].toString(); + + QString layerName = getLayerNameForStyle( uri, ds ); return QgsOgrUtils::saveStyle( ds.get(), layerName, "", qmlStyle, sldStyle, styleName, styleDescription, uiFileContent, useAsDefault, errCause ); } @@ -4671,9 +4711,27 @@ QString QgsGdalProviderMetadata::loadStoredStyle( const QString &uri, QString &s errCause = QObject::tr( "Cannot open %1." ).arg( uri ); return QString(); } + + QString layerName = getLayerNameForStyle( uri, ds ); + return QgsOgrUtils::loadStoredStyle( ds.get(), layerName, "", styleName, errCause ); +} + +QString QgsGdalProviderMetadata::getLayerNameForStyle( const QString &uri, gdal::dataset_unique_ptr &ds ) +{ QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); QString layerName = uriParts["layerName"].toString(); - return QgsOgrUtils::loadStoredStyle( ds.get(), layerName, "", styleName, errCause ); + if ( layerName.isEmpty() ) + { + GDALDriverH driver = GDALGetDatasetDriver( ds.get() ); + if ( driver ) + { + if ( GDALGetDriverShortName( driver ) == QStringLiteral( "GPKG" ) ) + { + layerName = GDALGetMetadataItem( ds.get(), "IDENTIFIER", "" ); + } + } + } + return layerName; } QgsGdalProviderMetadata::QgsGdalProviderMetadata(): diff --git a/src/core/providers/gdal/qgsgdalprovider.h b/src/core/providers/gdal/qgsgdalprovider.h index 52a743b42ea57..0adbca9647825 100644 --- a/src/core/providers/gdal/qgsgdalprovider.h +++ b/src/core/providers/gdal/qgsgdalprovider.h @@ -24,6 +24,7 @@ #include "qgsgdalproviderbase.h" #include "qgsrectangle.h" #include "qgscolorrampshader.h" +#include "qgsogrutils.h" #include "qgsrasterbandstats.h" #include "qgsprovidermetadata.h" #include "qgsprovidersublayerdetails.h" @@ -371,6 +372,9 @@ class QgsGdalProvider final: public QgsRasterDataProvider, QgsGdalProviderBase const QgsRectangle &reqExtent, int bufferWidthPix, int bufferHeightPix ); + + //! Invalidate GDAL /vsicurl/ RAM cache for this uri + void invalidateNetworkCache(); }; /** @@ -416,6 +420,9 @@ class QgsGdalProviderMetadata final: public QgsProviderMetadata const QString &uiFileContent, bool useAsDefault, QString &errCause ) override; QString loadStyle( const QString &uri, QString &errCause ) override; QString loadStoredStyle( const QString &uri, QString &styleName, QString &errCause ) override; + private: + //! Get layer name from gdal url + static QString getLayerNameForStyle( const QString &uri, gdal::dataset_unique_ptr &ds ); }; ///@endcond diff --git a/src/core/qgis.h b/src/core/qgis.h index d03ff08b265a7..e1442f858dc37 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -945,6 +945,7 @@ class CORE_EXPORT Qgis BottomMiddle, //!< Label directly below point BottomSlightlyRight, //!< Label below point, slightly right of center BottomRight, //!< Label on bottom right of point + OverPoint, //!< Label directly centered over point (since QGIS 3.38) }; Q_ENUM( LabelPredefinedPointPosition ) @@ -1521,6 +1522,20 @@ class CORE_EXPORT Qgis }; Q_ENUM( GpsQualityIndicator ) + /** + * GPS navigation status. + * + * \since QGIS 3.38 + */ + enum class GpsNavigationStatus : int + { + NotValid, //!< Navigation status not valid + Safe, //!< Safe + Caution, //!< Caution + Unsafe, //!< Unsafe + }; + Q_ENUM( GpsNavigationStatus ); + /** * GPS information component. * @@ -3218,6 +3233,21 @@ class CORE_EXPORT Qgis }; Q_ENUM( FieldDomainMergePolicy ) + /** + * Duplicate policy for fields. + * + * When a feature is duplicated, defines how the value of attributes are computed. + * + * \since QGIS 3.38 + */ + enum class FieldDuplicatePolicy : int + { + DefaultValue, //!< Use default field value + Duplicate, //!< Duplicate original value + UnsetField, //!< Clears the field value so that the data provider backend will populate using any backend triggers or similar logic (since QGIS 3.30) + }; + Q_ENUM( FieldDuplicatePolicy ) + /** * Types of field domain * @@ -3305,7 +3335,8 @@ class CORE_EXPORT Qgis enum class MeshElevationMode : int { FixedElevationRange = 0, //!< Layer has a fixed elevation range - FromVertices = 1 //!< Elevation should be taken from mesh vertices + FromVertices = 1, //!< Elevation should be taken from mesh vertices + FixedRangePerGroup = 2, //!< Layer has a fixed (manually specified) elevation range per group }; Q_ENUM( MeshElevationMode ) diff --git a/src/core/qgsattributetableconfig.h b/src/core/qgsattributetableconfig.h index ba50862a33122..9daf64a74a2f3 100644 --- a/src/core/qgsattributetableconfig.h +++ b/src/core/qgsattributetableconfig.h @@ -48,7 +48,7 @@ class CORE_EXPORT QgsAttributeTableConfig /** * Defines the configuration of a column in the attribute table. */ - struct ColumnConfig + struct CORE_EXPORT ColumnConfig { //! Constructor for ColumnConfig ColumnConfig() = default; diff --git a/src/core/qgsexpressioncontext.cpp b/src/core/qgsexpressioncontext.cpp index b05f99490a5e7..8e0b9c2bcedd7 100644 --- a/src/core/qgsexpressioncontext.cpp +++ b/src/core/qgsexpressioncontext.cpp @@ -106,7 +106,11 @@ void QgsExpressionContextScope::addVariable( const QgsExpressionContextScope::St bool QgsExpressionContextScope::removeVariable( const QString &name ) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return mVariables.remove( name ); +#else return mVariables.remove( name ) > 0; +#endif } bool QgsExpressionContextScope::hasVariable( const QString &name ) const diff --git a/src/core/qgsfield.cpp b/src/core/qgsfield.cpp index 0db53d8faa0ba..14107b82885f2 100644 --- a/src/core/qgsfield.cpp +++ b/src/core/qgsfield.cpp @@ -706,6 +706,11 @@ bool QgsField::convertCompatible( QVariant &v, QString *errorMessage ) const return true; } +QgsField::operator QVariant() const +{ + return QVariant::fromValue( *this ); +} + void QgsField::setEditorWidgetSetup( const QgsEditorWidgetSetup &v ) { d->editorWidgetSetup = v; @@ -736,6 +741,16 @@ void QgsField::setSplitPolicy( Qgis::FieldDomainSplitPolicy policy ) d->splitPolicy = policy; } +Qgis::FieldDuplicatePolicy QgsField::duplicatePolicy() const +{ + return d->duplicatePolicy; +} + +void QgsField::setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ) +{ + d->duplicatePolicy = policy; +} + /*************************************************************************** * This class is considered CRITICAL and any change MUST be accompanied with * full unit tests in testqgsfield.cpp. @@ -764,6 +779,7 @@ QDataStream &operator<<( QDataStream &out, const QgsField &field ) out << field.constraints().constraintDescription(); out << static_cast< quint32 >( field.subType() ); out << static_cast< int >( field.splitPolicy() ); + out << static_cast< int >( field.duplicatePolicy() ); out << field.metadata(); return out; } @@ -782,6 +798,7 @@ QDataStream &operator>>( QDataStream &in, QgsField &field ) quint32 strengthUnique; quint32 strengthExpression; int splitPolicy; + int duplicatePolicy; bool applyOnUpdate; @@ -796,7 +813,7 @@ QDataStream &operator>>( QDataStream &in, QgsField &field ) in >> name >> type >> typeName >> length >> precision >> comment >> alias >> defaultValueExpression >> applyOnUpdate >> constraints >> originNotNull >> originUnique >> originExpression >> strengthNotNull >> strengthUnique >> strengthExpression >> - constraintExpression >> constraintDescription >> subType >> splitPolicy >> metadata; + constraintExpression >> constraintDescription >> subType >> splitPolicy >> duplicatePolicy >> metadata; field.setName( name ); field.setType( static_cast< QVariant::Type >( type ) ); field.setTypeName( typeName ); @@ -806,6 +823,7 @@ QDataStream &operator>>( QDataStream &in, QgsField &field ) field.setAlias( alias ); field.setDefaultValueDefinition( QgsDefaultValue( defaultValueExpression, applyOnUpdate ) ); field.setSplitPolicy( static_cast< Qgis::FieldDomainSplitPolicy >( splitPolicy ) ); + field.setDuplicatePolicy( static_cast< Qgis::FieldDuplicatePolicy >( duplicatePolicy ) ); QgsFieldConstraints fieldConstraints; if ( constraints & QgsFieldConstraints::ConstraintNotNull ) { diff --git a/src/core/qgsfield.h b/src/core/qgsfield.h index 090b40a869136..0c67ca03cfbbd 100644 --- a/src/core/qgsfield.h +++ b/src/core/qgsfield.h @@ -441,10 +441,7 @@ class CORE_EXPORT QgsField #endif //! Allows direct construction of QVariants from fields. - operator QVariant() const - { - return QVariant::fromValue( *this ); - } + operator QVariant() const; /** * Set the editor widget setup for the field. @@ -497,6 +494,26 @@ class CORE_EXPORT QgsField */ void setSplitPolicy( Qgis::FieldDomainSplitPolicy policy ); + /** + * Returns the field's duplicate policy, which indicates how field values should + * be handled during a duplicate operation. + * + * \see setDuplicatePolicy() + * + * \since QGIS 3.38 + */ + Qgis::FieldDuplicatePolicy duplicatePolicy() const; + + /** + * Sets the field's duplicate \a policy, which indicates how field values should + * be handled during a duplicate operation. + * + * \see duplicatePolicy() + * + * \since QGIS 3.38 + */ + void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ); + #ifdef SIP_RUN SIP_PYOBJECT __repr__(); % MethodCode diff --git a/src/core/qgsfield_p.h b/src/core/qgsfield_p.h index 982e6d513533c..3889bbef810ff 100644 --- a/src/core/qgsfield_p.h +++ b/src/core/qgsfield_p.h @@ -84,6 +84,7 @@ class QgsFieldPrivate : public QSharedData , constraints( other.constraints ) , editorWidgetSetup( other.editorWidgetSetup ) , splitPolicy( other.splitPolicy ) + , duplicatePolicy( other.duplicatePolicy ) , isReadOnly( other.isReadOnly ) { } @@ -99,6 +100,7 @@ class QgsFieldPrivate : public QSharedData && ( alias == other.alias ) && ( defaultValueDefinition == other.defaultValueDefinition ) && ( constraints == other.constraints ) && ( flags == other.flags ) && ( splitPolicy == other.splitPolicy ) + && ( duplicatePolicy == other.duplicatePolicy ) && ( isReadOnly == other.isReadOnly ) && ( editorWidgetSetup == other.editorWidgetSetup ) ); } @@ -144,6 +146,9 @@ class QgsFieldPrivate : public QSharedData //! Split policy Qgis::FieldDomainSplitPolicy splitPolicy = Qgis::FieldDomainSplitPolicy::Duplicate; + //! Duplicate policy + Qgis::FieldDuplicatePolicy duplicatePolicy = Qgis::FieldDuplicatePolicy::Duplicate; + //! Read-only bool isReadOnly = false; diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index dd969da983c95..799fc4dbe5592 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -761,6 +761,7 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume layerElement.appendChild( layerId ); + if ( mVerticalCrs.isValid() ) { QDomElement verticalSrsNode = document.createElement( QStringLiteral( "verticalCrs" ) ); mVerticalCrs.writeXml( verticalSrsNode, document ); diff --git a/src/core/qgsmaplayerlegend.cpp b/src/core/qgsmaplayerlegend.cpp index 336220fb410a6..58f2557948167 100644 --- a/src/core/qgsmaplayerlegend.cpp +++ b/src/core/qgsmaplayerlegend.cpp @@ -384,27 +384,24 @@ QList QgsDefaultVectorLayerLegend::createLayerTre nodes.append( new QgsSimpleLegendNode( nodeLayer, r->legendClassificationAttribute() ) ); } - const auto constLegendSymbolItems = r->legendSymbolItems(); - for ( const QgsLegendSymbolItem &i : constLegendSymbolItems ) + const QList rendererNodes = r->createLegendNodes( nodeLayer ); + for ( QgsLayerTreeModelLegendNode *node : rendererNodes ) { - if ( auto *lDataDefinedSizeLegendSettings = i.dataDefinedSizeLegendSettings() ) - nodes << new QgsDataDefinedSizeLegendNode( nodeLayer, *lDataDefinedSizeLegendSettings ); - else + if ( QgsSymbolLegendNode *legendNode = qobject_cast< QgsSymbolLegendNode *>( node ) ) { - QgsSymbolLegendNode *legendNode = new QgsSymbolLegendNode( nodeLayer, i ); - if ( mTextOnSymbolEnabled && mTextOnSymbolContent.contains( i.ruleKey() ) ) + const QString ruleKey = legendNode->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString(); + if ( mTextOnSymbolEnabled && mTextOnSymbolContent.contains( ruleKey ) ) { - legendNode->setTextOnSymbolLabel( mTextOnSymbolContent.value( i.ruleKey() ) ); + legendNode->setTextOnSymbolLabel( mTextOnSymbolContent.value( ruleKey ) ); legendNode->setTextOnSymbolTextFormat( mTextOnSymbolTextFormat ); } - nodes << legendNode; } + nodes << node; } - if ( nodes.count() == 1 && nodes[0]->data( Qt::EditRole ).toString().isEmpty() ) + if ( nodes.count() == 1 && nodes[0]->data( Qt::EditRole ).toString().isEmpty() && qobject_cast< QgsSymbolLegendNode * >( nodes[0] ) ) nodes[0]->setEmbeddedInParent( true ); - if ( mLayer->diagramsEnabled() ) { const auto constLegendItems = mLayer->diagramRenderer()->legendItems( nodeLayer ); @@ -440,7 +437,6 @@ QList QgsDefaultVectorLayerLegend::createLayerTre } } - return nodes; } diff --git a/src/core/qgsopenclutils.cpp b/src/core/qgsopenclutils.cpp index 39fab282a0d0c..beb1cf53e1570 100644 --- a/src/core/qgsopenclutils.cpp +++ b/src/core/qgsopenclutils.cpp @@ -25,6 +25,9 @@ #include #ifdef Q_OS_WIN +#if defined(UNICODE) && !defined(_UNICODE) +#define _UNICODE +#endif #include #include #endif @@ -100,7 +103,12 @@ void QgsOpenClUtils::init() } #ifdef Q_OS_WIN - HMODULE hModule = GetModuleHandle( "OpenCL.dll" ); +#ifdef _UNICODE +#define _T(x) L##x +#else +#define _T(x) x +#endif + HMODULE hModule = GetModuleHandle( _T( "OpenCL.dll" ) ); if ( hModule ) { TCHAR pszFileName[1024]; @@ -114,13 +122,13 @@ void QgsOpenClUtils::init() DWORD dwLen = GetFileVersionInfoSize( pszFileName, &dwUseless ); if ( dwLen ) { - LPTSTR lpVI = ( LPSTR ) malloc( dwLen ); + LPTSTR lpVI = ( LPTSTR ) malloc( dwLen * sizeof( TCHAR ) ); if ( lpVI ) { if ( GetFileVersionInfo( pszFileName, 0, dwLen, lpVI ) ) { VS_FIXEDFILEINFO *lpFFI; - if ( VerQueryValue( lpVI, "\\", ( LPVOID * ) &lpFFI, ( UINT * ) &dwUseless ) ) + if ( VerQueryValue( lpVI, _T( "\\" ), ( LPVOID * ) &lpFFI, ( UINT * ) &dwUseless ) ) { QgsMessageLog::logMessage( QObject::tr( "OpenCL Product version: %1.%2.%3.%4" ) .arg( lpFFI->dwProductVersionMS >> 16 ) @@ -163,13 +171,23 @@ void QgsOpenClUtils::init() QgsDebugMsgLevel( QString( "d:%1 subBlock:%2" ).arg( d ).arg( subBlock ), 2 ); - BOOL r = VerQueryValue( lpVI, subBlock.toUtf8(), ( LPVOID * )&lpBuffer, ( UINT * )&dwUseless ); + BOOL r = VerQueryValue( lpVI, +#ifdef UNICODE + subBlock.toStdWString().c_str(), +#else + subBlock.toUtf8(), +#endif + ( LPVOID * )&lpBuffer, ( UINT * )&dwUseless ); if ( r && lpBuffer && lpBuffer != INVALID_HANDLE_VALUE && dwUseless < 1023 ) { QgsMessageLog::logMessage( QObject::tr( "Found OpenCL version info %1: %2" ) .arg( d ) +#ifdef UNICODE + .arg( QString::fromUtf16( ( const ushort * ) lpBuffer ) ), +#else .arg( QString::fromLocal8Bit( lpBuffer ) ), +#endif LOGMESSAGE_TAG, Qgis::MessageLevel::Info ); } } diff --git a/src/core/symbology/qgsheatmaprenderer.cpp b/src/core/symbology/qgsheatmaprenderer.cpp index d261983e80299..98dfa74453c60 100644 --- a/src/core/symbology/qgsheatmaprenderer.cpp +++ b/src/core/symbology/qgsheatmaprenderer.cpp @@ -23,6 +23,7 @@ #include "qgscolorrampimpl.h" #include "qgsrendercontext.h" #include "qgsstyleentityvisitor.h" +#include "qgscolorramplegendnode.h" #include #include @@ -31,6 +32,8 @@ QgsHeatmapRenderer::QgsHeatmapRenderer() : QgsFeatureRenderer( QStringLiteral( "heatmapRenderer" ) ) { mGradientRamp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 0, 0, 0 ) ); + mLegendSettings.setMinimumLabel( QObject::tr( "Minimum" ) ); + mLegendSettings.setMaximumLabel( QObject::tr( "Maximum" ) ); } QgsHeatmapRenderer::~QgsHeatmapRenderer() @@ -65,6 +68,15 @@ void QgsHeatmapRenderer::startRender( QgsRenderContext &context, const QgsFields mWeightExpression->prepare( &context.expressionContext() ); } + bool ok = false; + const double dataDefinedExplicitMax = dataDefinedProperties().valueAsDouble( Property::HeatmapMaximum, context.expressionContext(), mExplicitMax, &ok ); + if ( ok ) + mExplicitMax = dataDefinedExplicitMax; + + const double dataDefinedRadius = dataDefinedProperties().valueAsDouble( Property::HeatmapRadius, context.expressionContext(), mRadius, &ok ); + if ( ok ) + mRadius = dataDefinedRadius; + initializeValues( context ); } @@ -284,6 +296,7 @@ QgsHeatmapRenderer *QgsHeatmapRenderer::clone() const newRenderer->setMaximumValue( mExplicitMax ); newRenderer->setRenderQuality( mRenderQuality ); newRenderer->setWeightExpression( mWeightExpressionString ); + newRenderer->setLegendSettings( mLegendSettings ); copyRendererData( newRenderer ); return newRenderer; @@ -316,12 +329,16 @@ QgsFeatureRenderer *QgsHeatmapRenderer::create( QDomElement &element, const QgsR { r->setColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) ); } + + QgsColorRampLegendNodeSettings legendSettings; + legendSettings.readXml( element, context ); + r->setLegendSettings( legendSettings ); + return r; } QDomElement QgsHeatmapRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context ) { - Q_UNUSED( context ) QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME ); rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "heatmapRenderer" ) ); rendererElem.setAttribute( QStringLiteral( "radius" ), QString::number( mRadius ) ); @@ -336,6 +353,7 @@ QDomElement QgsHeatmapRenderer::save( QDomDocument &doc, const QgsReadWriteConte const QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mGradientRamp, doc ); rendererElem.appendChild( colorRampElem ); } + mLegendSettings.writeXml( doc, rendererElem, context ); saveRendererData( doc, rendererElem, context ); @@ -395,8 +413,25 @@ bool QgsHeatmapRenderer::accept( QgsStyleEntityVisitorInterface *visitor ) const return true; } +QList QgsHeatmapRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const +{ + return + { + new QgsColorRampLegendNode( nodeLayer, + mGradientRamp->clone(), + mLegendSettings, + 0, + 1 ) + }; +} + void QgsHeatmapRenderer::setColorRamp( QgsColorRamp *ramp ) { delete mGradientRamp; mGradientRamp = ramp; } + +void QgsHeatmapRenderer::setLegendSettings( const QgsColorRampLegendNodeSettings &settings ) +{ + mLegendSettings = settings; +} diff --git a/src/core/symbology/qgsheatmaprenderer.h b/src/core/symbology/qgsheatmaprenderer.h index b78f1cd4c72bb..9f275e46fe62a 100644 --- a/src/core/symbology/qgsheatmaprenderer.h +++ b/src/core/symbology/qgsheatmaprenderer.h @@ -22,6 +22,7 @@ #include "qgsgeometry.h" #include "qgsmapunitscale.h" #include "qgis.h" +#include "qgscolorramplegendnodesettings.h" class QgsColorRamp; @@ -58,6 +59,7 @@ class CORE_EXPORT QgsHeatmapRenderer : public QgsFeatureRenderer QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) override; static QgsHeatmapRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) SIP_FACTORY; bool accept( QgsStyleEntityVisitorInterface *visitor ) const override; + QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const override SIP_FACTORY; //reimplemented to extent the request so that points up to heatmap's radius distance outside //visible area are included @@ -79,6 +81,22 @@ class CORE_EXPORT QgsHeatmapRenderer : public QgsFeatureRenderer */ void setColorRamp( QgsColorRamp *ramp SIP_TRANSFER ); + /** + * Returns the color ramp legend settings. + * + * \see setLegendSettings() + * \since QGIS 3.38 + */ + const QgsColorRampLegendNodeSettings &legendSettings() const { return mLegendSettings; } + + /** + * Sets the color ramp legend \a settings. + * + * \see legendSettings() + * \since QGIS 3.38 + */ + void setLegendSettings( const QgsColorRampLegendNodeSettings &settings ); + /** * Returns the radius for the heatmap * \returns heatmap radius @@ -202,6 +220,8 @@ class CORE_EXPORT QgsHeatmapRenderer : public QgsFeatureRenderer int mFeaturesRendered = 0; + QgsColorRampLegendNodeSettings mLegendSettings; + double uniformKernel( double distance, int bandwidth ) const; double quarticKernel( double distance, int bandwidth ) const; double triweightKernel( double distance, int bandwidth ) const; diff --git a/src/core/symbology/qgsrenderer.cpp b/src/core/symbology/qgsrenderer.cpp index 2ba71427ef356..9713510aec8c6 100644 --- a/src/core/symbology/qgsrenderer.cpp +++ b/src/core/symbology/qgsrenderer.cpp @@ -32,12 +32,15 @@ #include "qgsapplication.h" #include "qgsmarkersymbol.h" #include "qgslinesymbol.h" +#include "qgslayertreemodellegendnode.h" #include #include #include #include +QgsPropertiesDefinition QgsFeatureRenderer::sPropertyDefinitions; + QPointF QgsFeatureRenderer::_getPoint( QgsRenderContext &context, const QgsPoint &point ) { return QgsSymbol::_getPoint( context, point ); @@ -56,6 +59,7 @@ void QgsFeatureRenderer::copyRendererData( QgsFeatureRenderer *destRenderer ) co destRenderer->mOrderBy = mOrderBy; destRenderer->mOrderByEnabled = mOrderByEnabled; destRenderer->mReferenceScale = mReferenceScale; + destRenderer->mDataDefinedProperties = mDataDefinedProperties; } QgsFeatureRenderer::QgsFeatureRenderer( const QString &type ) @@ -70,6 +74,12 @@ QgsFeatureRenderer::~QgsFeatureRenderer() delete mPaintEffect; } +const QgsPropertiesDefinition &QgsFeatureRenderer::propertyDefinitions() +{ + QgsFeatureRenderer::initPropertyDefinitions(); + return sPropertyDefinitions; +} + QgsFeatureRenderer *QgsFeatureRenderer::defaultRenderer( Qgis::GeometryType geomType ) { return new QgsSingleSymbolRenderer( QgsSymbol::defaultSymbol( geomType ) ); @@ -87,7 +97,7 @@ QSet< QString > QgsFeatureRenderer::legendKeysForFeature( const QgsFeature &feat return QSet< QString >(); } -void QgsFeatureRenderer::startRender( QgsRenderContext &, const QgsFields & ) +void QgsFeatureRenderer::startRender( QgsRenderContext &context, const QgsFields & ) { #ifdef QGISDEBUG if ( !mThread ) @@ -99,6 +109,8 @@ void QgsFeatureRenderer::startRender( QgsRenderContext &, const QgsFields & ) Q_ASSERT_X( mThread == QThread::currentThread(), "QgsFeatureRenderer::startRender", "startRender called in a different thread - use a cloned renderer instead" ); } #endif + + mDataDefinedProperties.prepare( context.expressionContext() ); } bool QgsFeatureRenderer::canSkipRender() @@ -185,6 +197,10 @@ QgsFeatureRenderer *QgsFeatureRenderer::load( QDomElement &element, const QgsRea const QDomElement orderByElem = element.firstChildElement( QStringLiteral( "orderby" ) ); r->mOrderBy.load( orderByElem ); r->setOrderByEnabled( element.attribute( QStringLiteral( "enableorderby" ), QStringLiteral( "0" ) ).toInt() ); + + const QDomElement elemDataDefinedProperties = element.firstChildElement( QStringLiteral( "data-defined-properties" ) ); + if ( !elemDataDefinedProperties.isNull() ) + r->mDataDefinedProperties.readXml( elemDataDefinedProperties, propertyDefinitions() ); } return r; } @@ -206,6 +222,10 @@ void QgsFeatureRenderer::saveRendererData( QDomDocument &doc, QDomElement &rende rendererElem.setAttribute( QStringLiteral( "symbollevels" ), ( mUsingSymbolLevels ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) ); rendererElem.setAttribute( QStringLiteral( "referencescale" ), mReferenceScale ); + QDomElement elemDataDefinedProperties = doc.createElement( QStringLiteral( "data-defined-properties" ) ); + mDataDefinedProperties.writeXml( elemDataDefinedProperties, propertyDefinitions() ); + rendererElem.appendChild( elemDataDefinedProperties ); + if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) ) mPaintEffect->saveProperties( doc, rendererElem ); @@ -392,6 +412,27 @@ QgsLegendSymbolList QgsFeatureRenderer::legendSymbolItems() const return QgsLegendSymbolList(); } +QList QgsFeatureRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const +{ + QList nodes; + + const QgsLegendSymbolList symbolItems = legendSymbolItems(); + nodes.reserve( symbolItems.size() ); + + for ( const QgsLegendSymbolItem &item : symbolItems ) + { + if ( const QgsDataDefinedSizeLegend *dataDefinedSizeLegendSettings = item.dataDefinedSizeLegendSettings() ) + { + nodes << new QgsDataDefinedSizeLegendNode( nodeLayer, *dataDefinedSizeLegendSettings ); + } + else + { + nodes << new QgsSymbolLegendNode( nodeLayer, item ); + } + } + return nodes; +} + void QgsFeatureRenderer::setVertexMarkerAppearance( Qgis::VertexMarkerType type, double size ) { mCurrentVertexMarkerType = type; @@ -469,6 +510,11 @@ void QgsFeatureRenderer::setPaintEffect( QgsPaintEffect *effect ) mPaintEffect = effect; } +void QgsFeatureRenderer::setDataDefinedProperty( Property key, const QgsProperty &property ) +{ + mDataDefinedProperties.setProperty( key, property ); +} + QgsFeatureRequest::OrderBy QgsFeatureRenderer::orderBy() const { return mOrderBy; @@ -538,6 +584,20 @@ void QgsFeatureRenderer::convertSymbolRotation( QgsSymbol *symbol, const QString } } +void QgsFeatureRenderer::initPropertyDefinitions() +{ + if ( !sPropertyDefinitions.isEmpty() ) + return; + + QString origin = QStringLiteral( "renderer" ); + + sPropertyDefinitions = QgsPropertiesDefinition + { + { static_cast< int >( QgsFeatureRenderer::Property::HeatmapRadius ), QgsPropertyDefinition( "heatmapRadius", QObject::tr( "Radius" ), QgsPropertyDefinition::DoublePositive, origin )}, + { static_cast< int >( QgsFeatureRenderer::Property::HeatmapMaximum ), QgsPropertyDefinition( "heatmapMaximum", QObject::tr( "Maximum" ), QgsPropertyDefinition::DoublePositive, origin )}, + }; +} + QgsSymbol *QgsSymbolLevelItem::symbol() const { return mSymbol; diff --git a/src/core/symbology/qgsrenderer.h b/src/core/symbology/qgsrenderer.h index 8a6493f0573d6..ccad7f1138796 100644 --- a/src/core/symbology/qgsrenderer.h +++ b/src/core/symbology/qgsrenderer.h @@ -23,6 +23,7 @@ #include "qgsfields.h" #include "qgsfeaturerequest.h" #include "qgsconfig.h" +#include "qgspropertycollection.h" #include #include @@ -38,6 +39,8 @@ class QgsPaintEffect; class QgsReadWriteContext; class QgsStyleEntityVisitorInterface; class QgsRenderContext; +class QgsLayerTreeModelLegendNode; +class QgsLayerTreeLayer; typedef QMap QgsStringMap SIP_SKIP; @@ -134,6 +137,24 @@ class CORE_EXPORT QgsFeatureRenderer #endif public: + + /** + * Data definable properties for renderers. + * + * \since QGIS 3.38 + */ + enum class Property : int + { + HeatmapRadius, //!< Heatmap renderer radius + HeatmapMaximum, //!< Heatmap maximum value + }; + + /** + * Returns the symbol property definitions. + * \since QGIS 3.18 + */ + static const QgsPropertiesDefinition &propertyDefinitions(); + // renderer takes ownership of its symbols! //! Returns a new renderer - used by default in vector layers @@ -396,11 +417,24 @@ class CORE_EXPORT QgsFeatureRenderer /** * Returns a list of symbology items for the legend * + * \see createLegendNodes() * \see legendKeys() - * */ virtual QgsLegendSymbolList legendSymbolItems() const; + /** + * Returns a list of legend nodes to be used for the legend for the renderer. + * + * Ownership is transferred to the caller. + * + * The default implementation creates a legend node for each symbol item returned by legendSymbolItems() + * + * \see legendSymbolItems() + * + * \since QGIS 3.38 + */ + virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const SIP_FACTORY; + /** * If supported by the renderer, return classification attribute for the use in legend */ @@ -466,6 +500,47 @@ class CORE_EXPORT QgsFeatureRenderer */ void setForceRasterRender( bool forceRaster ) { mForceRaster = forceRaster; } + /** + * Sets a data defined property for the renderer. Any existing property with the same key + * will be overwritten. + * + * \see dataDefinedProperties() + * \see Property + * + * \since QGIS 3.38 + */ + void setDataDefinedProperty( Property key, const QgsProperty &property ); + + /** + * Returns a reference to the renderer's property collection, used for data defined overrides. + * + * \see setDataDefinedProperties() + * \see Property + * + * \since QGIS 3.38 + */ + QgsPropertyCollection &dataDefinedProperties() { return mDataDefinedProperties; } + + /** + * Returns a reference to the renderer's property collection, used for data defined overrides. + * + * \see setDataDefinedProperties() + * + * \since QGIS 3.38 + */ + const QgsPropertyCollection &dataDefinedProperties() const { return mDataDefinedProperties; } SIP_SKIP + + /** + * Sets the renderer's property collection, used for data defined overrides. + * + * \param collection property collection. Existing properties will be replaced. + * + * \see dataDefinedProperties() + * + * \since QGIS 3.38 + */ + void setDataDefinedProperties( const QgsPropertyCollection &collection ) { mDataDefinedProperties = collection; } + /** * Returns the symbology reference scale. * @@ -563,6 +638,7 @@ class CORE_EXPORT QgsFeatureRenderer * - Reference scale * - Symbol levels enabled/disabled * - Force raster render enabled/disabled + * - Data defined properties * * \param destRenderer destination renderer for copied effect * \since QGIS 3.22 @@ -641,11 +717,17 @@ class CORE_EXPORT QgsFeatureRenderer QgsFeatureRenderer &operator=( const QgsFeatureRenderer & ); #endif + static void initPropertyDefinitions(); + //! Property definitions + static QgsPropertiesDefinition sPropertyDefinitions; + #ifdef QGISDEBUG //! Pointer to thread in which startRender was first called QThread *mThread = nullptr; #endif + QgsPropertyCollection mDataDefinedProperties; + Q_DISABLE_COPY( QgsFeatureRenderer ) }; diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index eac50e31e0ef0..308bea7a1d690 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -2243,6 +2243,10 @@ bool QgsVectorLayer::setDataProvider( QString const &provider, const QgsDataProv { mAttributeSplitPolicy[ field.name() ] = field.splitPolicy(); } + if ( !mAttributeDuplicatePolicy.contains( field.name() ) ) + { + mAttributeDuplicatePolicy[ field.name() ] = field.duplicatePolicy(); + } } if ( profile ) @@ -2573,6 +2577,21 @@ bool QgsVectorLayer::readSymbology( const QDomNode &layerNode, QString &errorMes } } + // The duplicate policy is - unlike alias and split policy - never defined by the data provider, so we clear the map + mAttributeDuplicatePolicy.clear(); + const QDomNode duplicatePoliciesNode = layerNode.namedItem( QStringLiteral( "duplicatePolicies" ) ); + if ( !duplicatePoliciesNode.isNull() ) + { + const QDomNodeList duplicatePolicyNodeList = duplicatePoliciesNode.toElement().elementsByTagName( QStringLiteral( "policy" ) ); + for ( int i = 0; i < duplicatePolicyNodeList.size(); ++i ) + { + const QDomElement duplicatePolicyElem = duplicatePolicyNodeList.at( i ).toElement(); + const QString field = duplicatePolicyElem.attribute( QStringLiteral( "field" ) ); + const Qgis::FieldDuplicatePolicy policy = qgsEnumKeyToValue( duplicatePolicyElem.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDuplicatePolicy::Duplicate ); + mAttributeDuplicatePolicy.insert( field, policy ); + } + } + // default expressions mDefaultExpressionMap.clear(); QDomNode defaultsNode = layerNode.namedItem( QStringLiteral( "defaults" ) ); @@ -3085,6 +3104,19 @@ bool QgsVectorLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString node.appendChild( splitPoliciesElement ); } + //duplicate policies + { + QDomElement duplicatePoliciesElement = doc.createElement( QStringLiteral( "duplicatePolicies" ) ); + for ( const QgsField &field : std::as_const( mFields ) ) + { + QDomElement duplicatePolicyElem = doc.createElement( QStringLiteral( "policy" ) ); + duplicatePolicyElem.setAttribute( QStringLiteral( "field" ), field.name() ); + duplicatePolicyElem.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.duplicatePolicy() ) ); + duplicatePoliciesElement.appendChild( duplicatePolicyElem ); + } + node.appendChild( duplicatePoliciesElement ); + } + //default expressions QDomElement defaultsElem = doc.createElement( QStringLiteral( "defaults" ) ); for ( const QgsField &field : std::as_const( mFields ) ) @@ -3588,6 +3620,22 @@ void QgsVectorLayer::setFieldSplitPolicy( int index, Qgis::FieldDomainSplitPolic emit layerModified(); // TODO[MD]: should have a different signal? } +void QgsVectorLayer::setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + if ( index < 0 || index >= fields().count() ) + return; + + const QString name = fields().at( index ).name(); + + mAttributeDuplicatePolicy.insert( name, policy ); + mFields[ index ].setDuplicatePolicy( policy ); + mEditFormConfig.setFields( mFields ); + emit layerModified(); // TODO[MD]: should have a different signal? +} + + QSet QgsVectorLayer::excludeAttributesWms() const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -4463,6 +4511,15 @@ void QgsVectorLayer::updateFields() mFields[ index ].setSplitPolicy( splitPolicyIt.value() ); } + for ( auto duplicatePolicyIt = mAttributeDuplicatePolicy.constBegin(); duplicatePolicyIt != mAttributeDuplicatePolicy.constEnd(); ++duplicatePolicyIt ) + { + int index = mFields.lookupField( duplicatePolicyIt.key() ); + if ( index < 0 ) + continue; + + mFields[ index ].setDuplicatePolicy( duplicatePolicyIt.value() ); + } + // Update configuration flags QMap< QString, Qgis::FieldConfigurationFlags >::const_iterator flagsIt = mFieldConfigurationFlags.constBegin(); for ( ; flagsIt != mFieldConfigurationFlags.constEnd(); ++flagsIt ) diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 3ecfc116e2c45..07a184243aae6 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1841,6 +1841,13 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \since QGIS 3.30 */ void setFieldSplitPolicy( int index, Qgis::FieldDomainSplitPolicy policy ); + + /** + * Sets a duplicate \a policy for the field with the specified index. + * + * \since QGIS 3.38 + */ + void setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy ); #else /** @@ -1862,6 +1869,26 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte sipCpp->setFieldSplitPolicy( a0, a1 ); } % End + + /** + * Sets a duplicate \a policy for the field with the specified index. + * + * \throws KeyError if no field with the specified index exists + * \since QGIS 3.38 + */ + void setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy ); + + % MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldDuplicatePolicy( a0, a1 ); + } + % End #endif /** @@ -2868,6 +2895,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte //! Map that stores the split policy for attributes QMap< QString, Qgis::FieldDomainSplitPolicy > mAttributeSplitPolicy; + //! Map that stores the duplicate policy for attributes + QMap< QString, Qgis::FieldDuplicatePolicy > mAttributeDuplicatePolicy; + //! An internal structure to keep track of fields that have a defaultValueOnUpdate QSet mDefaultValueOnUpdateFields; diff --git a/src/core/vector/qgsvectorlayereditutils.cpp b/src/core/vector/qgsvectorlayereditutils.cpp index bd1a45a1e26ba..5605476df7077 100644 --- a/src/core/vector/qgsvectorlayereditutils.cpp +++ b/src/core/vector/qgsvectorlayereditutils.cpp @@ -521,8 +521,7 @@ Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsC switch ( field.splitPolicy() ) { case Qgis::FieldDomainSplitPolicy::DefaultValue: - // TODO!!! - + //do nothing - default values ​​are determined break; case Qgis::FieldDomainSplitPolicy::Duplicate: diff --git a/src/core/vector/qgsvectorlayerutils.cpp b/src/core/vector/qgsvectorlayerutils.cpp index 06c5471977c53..6ea707ab27563 100644 --- a/src/core/vector/qgsvectorlayerutils.cpp +++ b/src/core/vector/qgsvectorlayerutils.cpp @@ -36,6 +36,7 @@ #include "qgsauxiliarystorage.h" #include "qgssymbollayerreference.h" #include "qgspainteffect.h" +#include "qgsunsetattributevalue.h" QgsFeatureIterator QgsVectorLayerUtils::getValuesIterator( const QgsVectorLayer *layer, const QString &fieldOrExpression, bool &ok, bool selectedOnly ) { @@ -643,7 +644,29 @@ QgsFeature QgsVectorLayerUtils::duplicateFeature( QgsVectorLayer *layer, const Q QgsExpressionContext context = layer->createExpressionContext(); context.setFeature( feature ); - QgsFeature newFeature = createFeature( layer, feature.geometry(), feature.attributes().toMap(), &context ); + //respect field duplicate policy + QgsAttributeMap attributeMap; + const int fieldCount = layer->fields().count(); + for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx ) + { + const QgsField field = layer->fields().at( fieldIdx ); + switch ( field.duplicatePolicy() ) + { + case Qgis::FieldDuplicatePolicy::DefaultValue: + //do nothing - default values ​​are determined + break; + + case Qgis::FieldDuplicatePolicy::Duplicate: + attributeMap.insert( fieldIdx, feature.attribute( fieldIdx ) ); + break; + + case Qgis::FieldDuplicatePolicy::UnsetField: + attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() ); + break; + } + } + + QgsFeature newFeature = createFeature( layer, feature.geometry(), attributeMap, &context ); layer->addFeature( newFeature ); const QList relations = project->relationManager()->referencedRelations( layer ); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 89df2a440e22a..ae6d3f0850d25 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -151,6 +151,7 @@ set(QGIS_GUI_SRCS codeeditors/qgscodeeditorshell.cpp codeeditors/qgscodeeditorsql.cpp codeeditors/qgscodeeditorexpression.cpp + codeeditors/qgscodeeditorwidget.cpp devtools/qgsdevtoolwidget.cpp devtools/qgsdevtoolwidgetfactory.cpp @@ -553,6 +554,7 @@ set(QGIS_GUI_SRCS qgsdatasourceselectdialog.cpp qgsdbqueryhistoryprovider.cpp qgsdbrelationshipwidget.cpp + qgsdecoratedscrollbar.cpp qgsdetaileditemdata.cpp qgsdetaileditemdelegate.cpp qgsdetaileditemwidget.cpp @@ -824,6 +826,7 @@ set(QGIS_GUI_HDRS qgsdatasourceselectdialog.h qgsdbqueryhistoryprovider.h qgsdbrelationshipwidget.h + qgsdecoratedscrollbar.h qgsnewdatabasetablenamewidget.h qgsdetaileditemdata.h qgsdetaileditemdelegate.h @@ -1112,6 +1115,7 @@ set(QGIS_GUI_HDRS codeeditors/qgscodeeditorr.h codeeditors/qgscodeeditorshell.h codeeditors/qgscodeeditorsql.h + codeeditors/qgscodeeditorwidget.h devtools/qgsdevtoolwidget.h devtools/qgsdevtoolwidgetfactory.h @@ -1614,6 +1618,13 @@ else() PROPERTIES COMPILE_FLAGS "-w -Wno-deprecated-declarations") endif() +if(MSVC) + set_source_files_properties( + ${CMAKE_BINARY_DIR}/src/gui/qgis_gui_autogen/mocs_compilation.cpp + PROPERTIES COMPILE_FLAGS "/bigobj" + ) +endif() + ############################################################# # qgis_gui library @@ -1709,7 +1720,7 @@ GENERATE_EXPORT_HEADER( set(QGIS_GUI_HDRS ${QGIS_GUI_HDRS} ${CMAKE_CURRENT_BINARY_DIR}/qgis_gui.h) if(NOT APPLE OR NOT QGIS_MACAPP_FRAMEWORK) - if (WIN32 ) + if (WIN32) include_directories(${CMAKE_SOURCE_DIR}/src/native/win) elseif (APPLE) include_directories(${CMAKE_SOURCE_DIR}/src/native/mac) diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index 3b1e7605935a6..71135f4e904aa 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -124,6 +124,12 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx connect( mSplitPolicyComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsAttributeTypeDialog::updateSplitPolicyLabel ); updateSplitPolicyLabel(); + + mDuplicatePolicyComboBox->addItem( tr( "Duplicate Value" ), QVariant::fromValue( Qgis::FieldDuplicatePolicy::Duplicate ) ); + mDuplicatePolicyComboBox->addItem( tr( "Use Default Value" ), QVariant::fromValue( Qgis::FieldDuplicatePolicy::DefaultValue ) ); + mDuplicatePolicyComboBox->addItem( tr( "Remove Value" ), QVariant::fromValue( Qgis::FieldDuplicatePolicy::UnsetField ) ); + connect( mDuplicatePolicyComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsAttributeTypeDialog::updateDuplicatePolicyLabel ); + updateDuplicatePolicyLabel(); } QgsAttributeTypeDialog::~QgsAttributeTypeDialog() @@ -381,6 +387,17 @@ void QgsAttributeTypeDialog::setSplitPolicy( Qgis::FieldDomainSplitPolicy policy updateSplitPolicyLabel(); } +Qgis::FieldDuplicatePolicy QgsAttributeTypeDialog::duplicatePolicy() const +{ + return mDuplicatePolicyComboBox->currentData().value< Qgis::FieldDuplicatePolicy >(); +} + +void QgsAttributeTypeDialog::setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ) +{ + mDuplicatePolicyComboBox->setCurrentIndex( mDuplicatePolicyComboBox->findData( QVariant::fromValue( policy ) ) ); + updateSplitPolicyLabel(); +} + QString QgsAttributeTypeDialog::constraintExpression() const { return constraintExpressionWidget->asExpression(); @@ -498,6 +515,26 @@ void QgsAttributeTypeDialog::updateSplitPolicyLabel() mSplitPolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); } +void QgsAttributeTypeDialog::updateDuplicatePolicyLabel() +{ + QString helperText; + switch ( mDuplicatePolicyComboBox->currentData().value< Qgis::FieldDuplicatePolicy >() ) + { + case Qgis::FieldDuplicatePolicy::DefaultValue: + helperText = tr( "Resets the field by recalculating its default value." ); + break; + + case Qgis::FieldDuplicatePolicy::Duplicate: + helperText = tr( "Copies the current field value without change." ); + break; + + case Qgis::FieldDuplicatePolicy::UnsetField: + helperText = tr( "Clears the field to an unset state." ); + break; + } + mDuplicatePolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); +} + QStandardItem *QgsAttributeTypeDialog::currentItem() const { QStandardItemModel *widgetTypeModel = qobject_cast( mWidgetTypeComboBox->model() ); diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.h b/src/gui/attributeformconfig/qgsattributetypedialog.h index 40305a0d9e32e..4f65c771679c9 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.h +++ b/src/gui/attributeformconfig/qgsattributetypedialog.h @@ -245,6 +245,24 @@ class GUI_EXPORT QgsAttributeTypeDialog: public QWidget, private Ui::QgsAttribut */ void setSplitPolicy( Qgis::FieldDomainSplitPolicy policy ); + /** + * Returns the field's duplicate policy. + * + * \see setDuplicatePolicy() + * + * \since QGIS 3.38 + */ + Qgis::FieldDuplicatePolicy duplicatePolicy() const; + + /** + * Sets the field's duplicate policy. + * + * \see duplicatePolicy() + * + * \since QGIS 3.38 + */ + void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ); + private slots: /** @@ -257,6 +275,8 @@ class GUI_EXPORT QgsAttributeTypeDialog: public QWidget, private Ui::QgsAttribut void updateSplitPolicyLabel(); + void updateDuplicatePolicyLabel(); + private: QgsVectorLayer *mLayer = nullptr; int mFieldIdx; diff --git a/src/gui/codeeditors/qgscodeeditor.cpp b/src/gui/codeeditors/qgscodeeditor.cpp index efbb94ef36791..a17a78352d71c 100644 --- a/src/gui/codeeditors/qgscodeeditor.cpp +++ b/src/gui/codeeditors/qgscodeeditor.cpp @@ -73,6 +73,7 @@ QMap< QgsCodeEditorColorScheme::ColorRole, QString > QgsCodeEditor::sColorRoleTo {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, QStringLiteral( "foldIconForeground" ) }, {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, QStringLiteral( "foldIconHalo" ) }, {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, QStringLiteral( "indentationGuide" ) }, + {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, QStringLiteral( "searchMatchBackground" ) } }; @@ -402,6 +403,12 @@ void QgsCodeEditor::runPostLexerConfigurationTasks() SendScintilla( SCI_STYLESETFORE, STYLE_INDENTGUIDE, lexerColor( QgsCodeEditorColorScheme::ColorRole::IndentationGuide ) ); SendScintilla( SCI_STYLESETBACK, STYLE_INDENTGUIDE, lexerColor( QgsCodeEditorColorScheme::ColorRole::IndentationGuide ) ); + SendScintilla( QsciScintilla::SCI_INDICSETSTYLE, SEARCH_RESULT_INDICATOR, QsciScintilla::INDIC_STRAIGHTBOX ); + SendScintilla( QsciScintilla::SCI_INDICSETFORE, SEARCH_RESULT_INDICATOR, lexerColor( QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground ) ); + SendScintilla( QsciScintilla::SCI_INDICSETALPHA, SEARCH_RESULT_INDICATOR, 100 ); + SendScintilla( QsciScintilla::SCI_INDICSETUNDER, SEARCH_RESULT_INDICATOR, true ); + SendScintilla( QsciScintilla::SCI_INDICGETOUTLINEALPHA, SEARCH_RESULT_INDICATOR, 255 ); + if ( mMode == QgsCodeEditor::Mode::CommandInput ) { setCaretLineVisible( false ); @@ -964,6 +971,7 @@ QColor QgsCodeEditor::defaultColor( QgsCodeEditorColorScheme::ColorRole role, co {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, QStringLiteral( "foldIconForeground" ) }, {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, QStringLiteral( "foldIconHalo" ) }, {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, QStringLiteral( "indentationGuide" ) }, + {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, QStringLiteral( "searchMatchBackground" ) }, }; const QgsCodeEditorColorScheme defaultScheme = QgsGui::codeEditorColorSchemeRegistry()->scheme( QStringLiteral( "default" ) ); diff --git a/src/gui/codeeditors/qgscodeeditor.h b/src/gui/codeeditors/qgscodeeditor.h index 548f3fdf4a297..b893c58abbe43 100644 --- a/src/gui/codeeditors/qgscodeeditor.h +++ b/src/gui/codeeditors/qgscodeeditor.h @@ -21,6 +21,7 @@ #include "qgscodeeditorcolorscheme.h" #include "qgis.h" #include "qgssettingstree.h" +#include "qgspanelwidget.h" // qscintilla includes #include @@ -29,6 +30,9 @@ #include +class QgsFilterLineEdit; +class QToolButton; +class QCheckBox; SIP_IF_MODULE( HAVE_QSCI_SIP ) @@ -81,8 +85,12 @@ class GUI_EXPORT QgsCodeInterpreter }; - -class QWidget; +// TODO QGIS 4.0 -- Consider making QgsCodeEditor inherit QWidget only, +// with a separate getter for the QsciScintilla child widget. This +// would give us more flexibility to add functionality to the base +// QgsCodeEditor class, eg adding a message bar or other child widgets +// to the editor widget. For now this extra functionality lives in +// the QgsCodeEditorWidget wrapper widget. /** * \ingroup gui @@ -150,6 +158,9 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla Q_DECLARE_FLAGS( Flags, Flag ) Q_FLAG( Flags ) + //! Indicator index for search results + static constexpr int SEARCH_RESULT_INDICATOR = QsciScintilla::INDIC_MAX - 1; + /** * Construct a new code editor. * diff --git a/src/gui/codeeditors/qgscodeeditorcolorscheme.h b/src/gui/codeeditors/qgscodeeditorcolorscheme.h index 13d8f222d4066..754bddc3df78d 100644 --- a/src/gui/codeeditors/qgscodeeditorcolorscheme.h +++ b/src/gui/codeeditors/qgscodeeditorcolorscheme.h @@ -71,6 +71,7 @@ class GUI_EXPORT QgsCodeEditorColorScheme FoldIconForeground, //!< Fold icon foreground color FoldIconHalo, //!< Fold icon halo color IndentationGuide, //!< Indentation guide line + SearchMatchBackground, //!< Background color for search matches (since QGIS 3.38) }; /** diff --git a/src/gui/codeeditors/qgscodeeditorcolorschemeregistry.cpp b/src/gui/codeeditors/qgscodeeditorcolorschemeregistry.cpp index f39c87dc406f1..a340cdc0d8b21 100644 --- a/src/gui/codeeditors/qgscodeeditorcolorschemeregistry.cpp +++ b/src/gui/codeeditors/qgscodeeditorcolorschemeregistry.cpp @@ -56,6 +56,7 @@ QgsCodeEditorColorSchemeRegistry::QgsCodeEditorColorSchemeRegistry() {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, QColor( "#ffffff" ) }, {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, QColor( "#000000" ) }, {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, QColor( "#d5d5d5" ) }, + {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, QColor( "#dadada" ) }, } ); addColorScheme( defaultScheme ); @@ -97,6 +98,7 @@ QgsCodeEditorColorSchemeRegistry::QgsCodeEditorColorSchemeRegistry() {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, QColor( "#ffffff" ) }, {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, QColor( "#93a1a1" ) }, {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, QColor( "#c2beb3" ) }, + {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, QColor( "#b58900" ) }, } ); addColorScheme( solarizedLight ); @@ -138,6 +140,7 @@ QgsCodeEditorColorSchemeRegistry::QgsCodeEditorColorSchemeRegistry() {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, QColor( "#586e75" ) }, {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, QColor( "#839496" ) }, {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, QColor( "#586E75" ) }, + {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, QColor( "#075e42" ) }, } ); addColorScheme( solarizedDark ); } diff --git a/src/gui/codeeditors/qgscodeeditorpython.cpp b/src/gui/codeeditors/qgscodeeditorpython.cpp index 7f2a698e15927..a0c96d5bf6d0c 100644 --- a/src/gui/codeeditors/qgscodeeditorpython.cpp +++ b/src/gui/codeeditors/qgscodeeditorpython.cpp @@ -93,6 +93,8 @@ void QgsCodeEditorPython::initializeLexer() setWhitespaceVisibility( QsciScintilla::WsVisibleAfterIndent ); + SendScintilla( QsciScintillaBase::SCI_SETPROPERTY, "highlight.current.word", "1" ); + QFont font = lexerFont(); const QColor defaultColor = lexerColor( QgsCodeEditorColorScheme::ColorRole::Default ); diff --git a/src/gui/codeeditors/qgscodeeditorwidget.cpp b/src/gui/codeeditors/qgscodeeditorwidget.cpp new file mode 100644 index 0000000000000..b1a553c6d9abe --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorwidget.cpp @@ -0,0 +1,358 @@ +/*************************************************************************** + qgscodeeditorwidget.cpp + -------------------------------------- + Date : May 2024 + Copyright : (C) 2024 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 "qgscodeeditorwidget.h" +#include "qgscodeeditor.h" +#include "qgsfilterlineedit.h" +#include "qgsapplication.h" +#include "qgsguiutils.h" +#include "qgsmessagebar.h" +#include "qgsdecoratedscrollbar.h" + +#include +#include +#include +#include + +QgsCodeEditorWidget::QgsCodeEditorWidget( + QgsCodeEditor *editor, + QgsMessageBar *messageBar, + QWidget *parent ) + : QgsPanelWidget( parent ) + , mEditor( editor ) + , mMessageBar( messageBar ) +{ + Q_ASSERT( mEditor ); + + QVBoxLayout *vl = new QVBoxLayout(); + vl->setContentsMargins( 0, 0, 0, 0 ); + vl->setSpacing( 0 ); + vl->addWidget( editor, 1 ); + + if ( !mMessageBar ) + { + QGridLayout *layout = new QGridLayout( mEditor ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addItem( new QSpacerItem( 20, 40, QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding ), 1, 0, 1, 1 ); + + mMessageBar = new QgsMessageBar(); + QSizePolicy sizePolicy = QSizePolicy( QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Fixed ); + mMessageBar->setSizePolicy( sizePolicy ); + layout->addWidget( mMessageBar, 0, 0, 1, 1 ); + } + + mFindWidget = new QWidget(); + QHBoxLayout *layoutFind = new QHBoxLayout(); + layoutFind->setContentsMargins( 0, 2, 0, 0 ); + layoutFind->setSpacing( 1 ); + mLineEditFind = new QgsFilterLineEdit(); + mLineEditFind->setShowSearchIcon( true ); + mLineEditFind->setPlaceholderText( tr( "Enter text to find…" ) ); + layoutFind->addWidget( mLineEditFind, 1 ); + + mCaseSensitiveButton = new QToolButton(); + mCaseSensitiveButton->setToolTip( tr( "Case Sensitive" ) ); + mCaseSensitiveButton->setCheckable( true ); + mCaseSensitiveButton->setAutoRaise( true ); + mCaseSensitiveButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchCaseSensitive.svg" ) ) ); + layoutFind->addWidget( mCaseSensitiveButton ); + + mWholeWordButton = new QToolButton( ); + mWholeWordButton->setToolTip( tr( "Whole Word" ) ); + mWholeWordButton->setCheckable( true ); + mWholeWordButton->setAutoRaise( true ); + mWholeWordButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchWholeWord.svg" ) ) ); + layoutFind->addWidget( mWholeWordButton ); + + mRegexButton = new QToolButton( ); + mRegexButton->setToolTip( tr( "Use Regular Expressions" ) ); + mRegexButton->setCheckable( true ); + mRegexButton->setAutoRaise( true ); + mRegexButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchRegex.svg" ) ) ); + layoutFind->addWidget( mRegexButton ); + + mWrapAroundButton = new QToolButton(); + mWrapAroundButton->setToolTip( tr( "Wrap Around" ) ); + mWrapAroundButton->setCheckable( true ); + mWrapAroundButton->setAutoRaise( true ); + mWrapAroundButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconSearchWrapAround.svg" ) ) ); + layoutFind->addWidget( mWrapAroundButton ); + + mFindPrevButton = new QToolButton(); + mFindPrevButton->setEnabled( false ); + mFindPrevButton->setToolTip( tr( "Find Previous" ) ); + mFindPrevButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconSearchPrevEditorConsole.svg" ) ) ); + mFindPrevButton->setAutoRaise( true ); + layoutFind->addWidget( mFindPrevButton ); + + mFindNextButton = new QToolButton(); + mFindNextButton->setEnabled( false ); + mFindNextButton->setToolTip( tr( "Find Next" ) ); + mFindNextButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconSearchNextEditorConsole.svg" ) ) ); + mFindNextButton->setAutoRaise( true ); + layoutFind->addWidget( mFindNextButton ); + + connect( mLineEditFind, &QLineEdit::returnPressed, this, &QgsCodeEditorWidget::findNext ); + connect( mLineEditFind, &QLineEdit::textChanged, this, &QgsCodeEditorWidget::textSearchChanged ); + connect( mFindNextButton, &QToolButton::clicked, this, &QgsCodeEditorWidget::findNext ); + connect( mFindPrevButton, &QToolButton::clicked, this, &QgsCodeEditorWidget::findPrevious ); + connect( mCaseSensitiveButton, &QToolButton::toggled, this, &QgsCodeEditorWidget::updateSearch ); + connect( mWholeWordButton, &QToolButton::toggled, this, &QgsCodeEditorWidget::updateSearch ); + connect( mRegexButton, &QToolButton::toggled, this, &QgsCodeEditorWidget::updateSearch ); + connect( mWrapAroundButton, &QCheckBox::toggled, this, &QgsCodeEditorWidget::updateSearch ); + + QShortcut *findShortcut = new QShortcut( QKeySequence::StandardKey::Find, mEditor ); + findShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut ); + connect( findShortcut, &QShortcut::activated, this, &QgsCodeEditorWidget::triggerFind ); + + QShortcut *findNextShortcut = new QShortcut( QKeySequence::StandardKey::FindNext, this ); + findNextShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut ); + connect( findNextShortcut, &QShortcut::activated, this, &QgsCodeEditorWidget::findNext ); + + QShortcut *findPreviousShortcut = new QShortcut( QKeySequence::StandardKey::FindPrevious, this ); + findPreviousShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut ); + connect( findPreviousShortcut, &QShortcut::activated, this, &QgsCodeEditorWidget::findPrevious ); + + // escape on editor hides the find bar + QShortcut *closeFindShortcut = new QShortcut( Qt::Key::Key_Escape, this ); + closeFindShortcut->setContext( Qt::ShortcutContext::WidgetWithChildrenShortcut ); + connect( closeFindShortcut, &QShortcut::activated, this, [this] + { + hideSearchBar(); + mEditor->setFocus(); + } ); + + QToolButton *closeFindButton = new QToolButton( this ); + closeFindButton->setToolTip( tr( "Close" ) ); + closeFindButton->setMinimumWidth( QgsGuiUtils::scaleIconSize( 44 ) ); + closeFindButton->setStyleSheet( + "QToolButton { border:none; background-color: rgba(0, 0, 0, 0); }" + "QToolButton::menu-button { border:none; background-color: rgba(0, 0, 0, 0); }" ); + closeFindButton->setCursor( Qt::PointingHandCursor ); + closeFindButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconClose.svg" ) ) ); + + const int iconSize = std::max( 18.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.9 ); + closeFindButton->setIconSize( QSize( iconSize, iconSize ) ); + closeFindButton->setFixedSize( QSize( iconSize, iconSize ) ); + connect( closeFindButton, &QAbstractButton::clicked, this, [this] + { + hideSearchBar(); + mEditor->setFocus(); + } ); + layoutFind->addWidget( closeFindButton ); + + mFindWidget->setLayout( layoutFind ); + vl->addWidget( mFindWidget ); + mFindWidget->hide(); + + setLayout( vl ); + + mHighlightController = std::make_unique< QgsScrollBarHighlightController >(); + mHighlightController->setScrollArea( mEditor ); +} + +QgsCodeEditorWidget::~QgsCodeEditorWidget() = default; + +bool QgsCodeEditorWidget::isSearchBarVisible() const +{ + return !mFindWidget->isHidden(); +} + +QgsMessageBar *QgsCodeEditorWidget::messageBar() +{ + return mMessageBar; +} + +void QgsCodeEditorWidget::showSearchBar() +{ + addSearchHighlights(); + mFindWidget->show(); + emit searchBarToggled( true ); +} + +void QgsCodeEditorWidget::hideSearchBar() +{ + clearSearchHighlights(); + mFindWidget->hide(); + emit searchBarToggled( false ); +} + +void QgsCodeEditorWidget::setSearchBarVisible( bool visible ) +{ + if ( visible ) + showSearchBar(); + else + hideSearchBar(); +} + +void QgsCodeEditorWidget::triggerFind() +{ + clearSearchHighlights(); + mLineEditFind->setFocus(); + if ( mEditor->hasSelectedText() ) + { + mBlockSearching++; + mLineEditFind->setText( mEditor->selectedText().trimmed() ); + mBlockSearching--; + } + mLineEditFind->selectAll(); + showSearchBar(); +} + +void QgsCodeEditorWidget::findNext() +{ + findText( true, false ); +} + +void QgsCodeEditorWidget::findPrevious() +{ + findText( false, false ); +} + +void QgsCodeEditorWidget::textSearchChanged( const QString &text ) +{ + if ( !text.isEmpty() ) + { + mFindNextButton->setEnabled( true ); + mFindPrevButton->setEnabled( true ); + updateSearch(); + } + else + { + clearSearchHighlights(); + mLineEditFind->setStyleSheet( QString() ); + mFindNextButton->setEnabled( false ); + mFindPrevButton->setEnabled( false ); + } +} + +void QgsCodeEditorWidget::updateSearch() +{ + if ( mBlockSearching ) + return; + + clearSearchHighlights(); + addSearchHighlights(); + + findText( true, true, true ); +} + +void QgsCodeEditorWidget::addSearchHighlights() +{ + const QString searchString = mLineEditFind->text(); + if ( searchString.isEmpty() ) + return; + + const long originalStartPos = mEditor->SendScintilla( QsciScintilla::SCI_GETTARGETSTART ); + const long originalEndPos = mEditor->SendScintilla( QsciScintilla::SCI_GETTARGETEND ); + long startPos = 0; + long docEnd = mEditor->length(); + + mHighlightController->setLineHeight( QFontMetrics( mEditor->font() ).lineSpacing() ); + mHighlightController->setVisibleRange( mEditor->viewport()->rect().height() ); + + int searchFlags = 0; + const bool isRegEx = mRegexButton->isChecked(); + const bool isCaseSensitive = mCaseSensitiveButton->isChecked(); + const bool isWholeWordOnly = mWholeWordButton->isChecked(); + if ( isRegEx ) + searchFlags |= QsciScintilla::SCFIND_REGEXP | QsciScintilla::SCFIND_CXX11REGEX; + if ( isCaseSensitive ) + searchFlags |= QsciScintilla::SCFIND_MATCHCASE; + if ( isWholeWordOnly ) + searchFlags |= QsciScintilla::SCFIND_WHOLEWORD; + mEditor->SendScintilla( QsciScintilla::SCI_SETSEARCHFLAGS, searchFlags ); + while ( true ) + { + mEditor->SendScintilla( QsciScintilla::SCI_SETTARGETRANGE, startPos, docEnd ); + const int fstart = mEditor->SendScintilla( QsciScintilla::SCI_SEARCHINTARGET, searchString.length(), searchString.toLocal8Bit().constData() ); + if ( fstart < 0 ) + break; + + const int matchLength = mEditor->SendScintilla( QsciScintilla::SCI_GETTARGETTEXT, 0, static_cast< void * >( nullptr ) ); + + startPos = fstart + matchLength; + + mEditor->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, QgsCodeEditor::SEARCH_RESULT_INDICATOR ); + mEditor->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, fstart, matchLength ); + + int thisLine = 0; + int thisIndex = 0; + mEditor->lineIndexFromPosition( fstart, &thisLine, &thisIndex ); + mHighlightController->addHighlight( QgsScrollBarHighlight( SearchMatch, thisLine, QColor( 0, 200, 0 ), QgsScrollBarHighlight::Priority::HighPriority ) ); + } + + mEditor->SendScintilla( QsciScintilla::SCI_SETTARGETRANGE, originalStartPos, originalEndPos ); +} + +void QgsCodeEditorWidget::clearSearchHighlights() +{ + long docStart = 0; + long docEnd = mEditor->length(); + mEditor->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, QgsCodeEditor::SEARCH_RESULT_INDICATOR ); + mEditor->SendScintilla( QsciScintilla::SCI_INDICATORCLEARRANGE, docStart, docEnd - docStart ); + + mHighlightController->removeHighlights( SearchMatch ); +} + +void QgsCodeEditorWidget::findText( bool forward, bool findFirst, bool showNotFoundWarning ) +{ + const QString searchString = mLineEditFind->text(); + if ( searchString.isEmpty() ) + return; + + int lineFrom = 0; + int indexFrom = 0; + int lineTo = 0; + int indexTo = 0; + mEditor->getSelection( &lineFrom, &indexFrom, &lineTo, &indexTo ); + + int line = 0; + int index = 0; + if ( !findFirst ) + { + mEditor->getCursorPosition( &line, &index ); + } + if ( !forward ) + { + line = lineFrom; + index = indexFrom; + } + + const bool isRegEx = mRegexButton->isChecked(); + const bool wrapAround = mWrapAroundButton->isChecked(); + const bool isCaseSensitive = mCaseSensitiveButton->isChecked(); + const bool isWholeWordOnly = mWholeWordButton->isChecked(); + + const bool found = mEditor->findFirst( searchString, isRegEx, isCaseSensitive, isWholeWordOnly, wrapAround, forward, + line, index, true, true, isRegEx ); + + if ( !found ) + { + const QString styleError = QStringLiteral( "QLineEdit {background-color: #d65253; color: #ffffff;}" ); + mLineEditFind->setStyleSheet( styleError ); + + if ( showNotFoundWarning ) + { + mMessageBar->pushMessage( QString(), tr( "\"%1\" was not found" ).arg( searchString ), + Qgis::MessageLevel::Info ); + } + } + else + { + mLineEditFind->setStyleSheet( QString() ); + } +} + diff --git a/src/gui/codeeditors/qgscodeeditorwidget.h b/src/gui/codeeditors/qgscodeeditorwidget.h new file mode 100644 index 0000000000000..48fec65fd90ae --- /dev/null +++ b/src/gui/codeeditors/qgscodeeditorwidget.h @@ -0,0 +1,154 @@ +/*************************************************************************** + qgscodeeditorwidget.h + -------------------------------------- + Date : May 2024 + Copyright : (C) 2024 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 QGSCODEEDITORWIDGET_H +#define QGSCODEEDITORWIDGET_H + +#include "qgis_gui.h" +#include "qgis_sip.h" +#include "qgspanelwidget.h" + +class QgsCodeEditor; +class QgsFilterLineEdit; +class QToolButton; +class QCheckBox; +class QgsMessageBar; +class QgsScrollBarHighlightController; + +SIP_IF_MODULE( HAVE_QSCI_SIP ) + +/** + * \ingroup gui + * \brief A widget which wraps a QgsCodeEditor in additional functionality. + * + * This widget wraps an existing QgsCodeEditor object in a widget which provides + * additional standard functionality, such as search/replace tools. The caller + * must create an unparented QgsCodeEditor object (or a subclass of QgsCodeEditor) + * first, and then construct a QgsCodeEditorWidget passing this object to the + * constructor. + * + * \note may not be available in Python bindings, depending on platform support + * + * \since QGIS 3.38 + */ +class GUI_EXPORT QgsCodeEditorWidget : public QgsPanelWidget +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsCodeEditorWidget, wrapping the specified \a editor widget. + * + * Ownership of \a editor will be transferred to this widget. + * + * If an explicit \a messageBar is specified then it will be used to provide + * feedback, otherwise an integrated message bar will be used. + */ + QgsCodeEditorWidget( QgsCodeEditor *editor SIP_TRANSFER, + QgsMessageBar *messageBar = nullptr, + QWidget *parent SIP_TRANSFERTHIS = nullptr ); + ~QgsCodeEditorWidget() override; + + /** + * Returns the wrapped code editor. + */ + QgsCodeEditor *editor() { return mEditor; } + + /** + * Returns TRUE if the search bar is visible. + */ + bool isSearchBarVisible() const; + + /** + * Returns the message bar associated with the widget, to use for user feedback. + */ + QgsMessageBar *messageBar(); + + public slots: + + /** + * Shows the search bar. + * + * \see hideSearchBar() + * \see setSearchBarVisible() + */ + void showSearchBar(); + + /** + * Hides the search bar. + * + * \see showSearchBar() + * \see setSearchBarVisible() + */ + void hideSearchBar(); + + /** + * Sets whether the search bar is \a visible. + * + * \see showSearchBar() + * \see hideSearchBar() + */ + void setSearchBarVisible( bool visible ); + + /** + * Triggers a find operation, using the default behavior. + * + * This will automatically open the search bar and start a find operation using + * the default behavior, e.g. searching for any selected text in the code editor. + */ + void triggerFind(); + + signals: + + /** + * Emitted when the visibility of the search bar is changed. + */ + void searchBarToggled( bool visible ); + + private slots: + + void findNext(); + void findPrevious(); + void textSearchChanged( const QString &text ); + void updateSearch(); + + private: + + void clearSearchHighlights(); + void addSearchHighlights(); + int searchFlags() const; + void findText( bool forward, bool findFirst, bool showNotFoundWarning = false ); + + enum HighlightCategory + { + SearchMatch = 0 + }; + + QgsCodeEditor *mEditor = nullptr; + QWidget *mFindWidget = nullptr; + QgsFilterLineEdit *mLineEditFind = nullptr; + QToolButton *mFindPrevButton = nullptr; + QToolButton *mFindNextButton = nullptr; + QToolButton *mCaseSensitiveButton = nullptr; + QToolButton *mWholeWordButton = nullptr; + QToolButton *mRegexButton = nullptr; + QToolButton *mWrapAroundButton = nullptr; + int mBlockSearching = 0; + QgsMessageBar *mMessageBar = nullptr; + std::unique_ptr< QgsScrollBarHighlightController > mHighlightController; +}; + +#endif // QGSCODEEDITORWIDGET_H diff --git a/src/gui/history/qgshistorywidget.cpp b/src/gui/history/qgshistorywidget.cpp index 83fd387e8bf8c..e252a11e681ca 100644 --- a/src/gui/history/qgshistorywidget.cpp +++ b/src/gui/history/qgshistorywidget.cpp @@ -18,10 +18,13 @@ #include "qgshistoryentrymodel.h" #include "qgshistoryentrynode.h" #include "qgssettings.h" +#include "qgsnative.h" #include #include #include +#include +#include QgsHistoryWidget::QgsHistoryWidget( const QString &providerId, Qgis::HistoryProviderBackends backends, QgsHistoryProviderRegistry *registry, const QgsHistoryWidgetContext &context, QWidget *parent ) : QgsPanelWidget( parent ) @@ -71,8 +74,10 @@ void QgsHistoryWidget::currentItemChanged( const QModelIndex &selected, const QM if ( !html.isEmpty() ) { QTextBrowser *htmlBrowser = new QTextBrowser(); - htmlBrowser->setOpenExternalLinks( true ); + htmlBrowser->setOpenLinks( false ); htmlBrowser->setHtml( html ); + connect( htmlBrowser, &QTextBrowser::anchorClicked, this, &QgsHistoryWidget::urlClicked ); + newWidget = htmlBrowser; } } @@ -124,6 +129,15 @@ void QgsHistoryWidget::showNodeContextMenu( const QPoint &pos ) } } +void QgsHistoryWidget::urlClicked( const QUrl &url ) +{ + const QFileInfo file( url.toLocalFile() ); + if ( file.exists() && !file.isDir() ) + QgsGui::nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() ); + else + QDesktopServices::openUrl( url ); +} + // // QgsHistoryEntryProxyModel // diff --git a/src/gui/history/qgshistorywidget.h b/src/gui/history/qgshistorywidget.h index 095de0ad925d5..13fa94f58548f 100644 --- a/src/gui/history/qgshistorywidget.h +++ b/src/gui/history/qgshistorywidget.h @@ -81,6 +81,7 @@ class GUI_EXPORT QgsHistoryWidget : public QgsPanelWidget, private Ui::QgsHistor void currentItemChanged( const QModelIndex &selected, const QModelIndex &previous ); void nodeDoubleClicked( const QModelIndex &index ); void showNodeContextMenu( const QPoint &pos ); + void urlClicked( const QUrl &url ); private: diff --git a/src/gui/layout/qgslayoutlegendwidget.cpp b/src/gui/layout/qgslayoutlegendwidget.cpp index 4b91628ff53cd..04327ed2c0f20 100644 --- a/src/gui/layout/qgslayoutlegendwidget.cpp +++ b/src/gui/layout/qgslayoutlegendwidget.cpp @@ -42,6 +42,7 @@ #include "qgssymbol.h" #include "qgslayoutundostack.h" #include "qgsexpressionfinder.h" +#include "qgscolorramplegendnode.h" #include #include diff --git a/src/gui/layout/qgslayoutmapwidget.cpp b/src/gui/layout/qgslayoutmapwidget.cpp index 63e27642933e8..92d30794b504e 100644 --- a/src/gui/layout/qgslayoutmapwidget.cpp +++ b/src/gui/layout/qgslayoutmapwidget.cpp @@ -73,6 +73,7 @@ QgsLayoutMapWidget::QgsLayoutMapWidget( QgsLayoutItemMap *item, QgsMapCanvas *ma connect( mAtlasPredefinedScaleRadio, &QRadioButton::toggled, this, &QgsLayoutMapWidget::mAtlasPredefinedScaleRadio_toggled ); connect( mAddGridPushButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mAddGridPushButton_clicked ); connect( mRemoveGridPushButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mRemoveGridPushButton_clicked ); + connect( mCopyGridPushButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mCopyGridPushButton_clicked ); connect( mGridUpButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mGridUpButton_clicked ); connect( mGridDownButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mGridDownButton_clicked ); connect( mGridListWidget, &QListWidget::currentItemChanged, this, &QgsLayoutMapWidget::mGridListWidget_currentItemChanged ); @@ -1245,6 +1246,47 @@ void QgsLayoutMapWidget::mRemoveGridPushButton_clicked() mMapItem->update(); } +void QgsLayoutMapWidget::mCopyGridPushButton_clicked() +{ + QListWidgetItem *item = mGridListWidget->currentItem(); + if ( !item ) + { + return; + } + + QgsLayoutItemMapGrid *sourceGrid = mMapItem->grids()->grid( item->data( Qt::UserRole ).toString() ); + if ( !sourceGrid ) + { + return; + } + int i = 0; + QString itemName = tr( "%1 - Copy" ).arg( sourceGrid->name() ); + QList< QgsLayoutItemMapGrid * > grids = mMapItem->grids()->asList(); + while ( true ) + { + const auto it = std::find_if( grids.begin(), grids.end(), [&itemName]( const QgsLayoutItemMapGrid * grd ) { return grd->name() == itemName; } ); + if ( it != grids.end() ) + { + i++; + itemName = tr( "%1 - Copy %2" ).arg( sourceGrid->name() ).arg( i ); + continue; + } + break; + } + QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( itemName, mMapItem ); + grid->copyProperties( sourceGrid ); + + mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Duplicate Map Grid" ) ); + mMapItem->grids()->addGrid( grid ); + mMapItem->layout()->undoStack()->endCommand(); + mMapItem->updateBoundingRect(); + mMapItem->update(); + + addGridListItem( grid->id(), grid->name() ); + mGridListWidget->setCurrentRow( 0 ); + mGridListWidget_currentItemChanged( mGridListWidget->currentItem(), nullptr ); +} + void QgsLayoutMapWidget::mGridUpButton_clicked() { QListWidgetItem *item = mGridListWidget->currentItem(); diff --git a/src/gui/layout/qgslayoutmapwidget.h b/src/gui/layout/qgslayoutmapwidget.h index 83a56c86f9012..3ccab9b61aaa3 100644 --- a/src/gui/layout/qgslayoutmapwidget.h +++ b/src/gui/layout/qgslayoutmapwidget.h @@ -86,6 +86,7 @@ class GUI_EXPORT QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui: void mAddGridPushButton_clicked(); void mRemoveGridPushButton_clicked(); + void mCopyGridPushButton_clicked(); void mGridUpButton_clicked(); void mGridDownButton_clicked(); diff --git a/src/gui/mesh/qgsmeshrenderervectorsettingswidget.cpp b/src/gui/mesh/qgsmeshrenderervectorsettingswidget.cpp index e152be9879c68..c693ed72950e6 100644 --- a/src/gui/mesh/qgsmeshrenderervectorsettingswidget.cpp +++ b/src/gui/mesh/qgsmeshrenderervectorsettingswidget.cpp @@ -27,7 +27,8 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge widgets << mMinMagSpinBox << mMaxMagSpinBox << mHeadWidthSpinBox << mHeadLengthSpinBox << mMinimumShaftSpinBox << mMaximumShaftSpinBox - << mScaleShaftByFactorOfSpinBox << mShaftLengthSpinBox; + << mScaleShaftByFactorOfSpinBox << mShaftLengthSpinBox + << mWindBarbLengthSpinBox << mWindBarbMagnitudeMultiplierSpinBox; // Setup defaults and clear values for spin boxes for ( const auto &widget : std::as_const( widgets ) ) @@ -48,6 +49,10 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge mTracesParticlesCountSpinBox->setClearValue( 1000 ); mTracesMaxLengthSpinBox->setClearValue( 100.0 ); + mWindBarbLengthSpinBox->setClearValue( 10.0 ); + mWindBarbMagnitudeMultiplierSpinBox->setValue( 1.0 ); + mWindBarbMagnitudeMultiplierSpinBox->setClearValue( 1.0 ); + connect( mColorWidget, &QgsColorButton::colorChanged, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged ); connect( mColoringMethodComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsMeshRendererVectorSettingsWidget::onColoringMethodChanged ); @@ -114,6 +119,19 @@ QgsMeshRendererVectorSettingsWidget::QgsMeshRendererVectorSettingsWidget( QWidge connect( mTracesTailLengthMapUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsMeshRendererVectorSettingsWidget::widgetChanged ); + + mWindBarbLengthMapUnitWidget->setUnits( + { + Qgis::RenderUnit::Millimeters, + Qgis::RenderUnit::Pixels, + Qgis::RenderUnit::Points + } ); + + connect( mWindBarbLengthMapUnitWidget, &QgsUnitSelectionWidget::changed, + this, &QgsMeshRendererVectorSettingsWidget::widgetChanged ); + connect( mWindBarbUnitsComboBox, qOverload( &QComboBox::currentIndexChanged ), + this, &QgsMeshRendererVectorSettingsWidget::onWindBarbUnitsChanged ); + onWindBarbUnitsChanged( 0 ); } void QgsMeshRendererVectorSettingsWidget::setLayer( QgsMeshLayer *layer ) @@ -191,6 +209,15 @@ QgsMeshRendererVectorSettings QgsMeshRendererVectorSettingsWidget::settings() co tracesSettings.setParticlesCount( mTracesParticlesCountSpinBox->value() ); settings.setTracesSettings( tracesSettings ); + // Wind Barb settings + QgsMeshRendererVectorWindBarbSettings windBarbSettings; + windBarbSettings.setShaftLength( mWindBarbLengthSpinBox->value() ); + windBarbSettings.setShaftLengthUnits( mWindBarbLengthMapUnitWidget->unit() ); + windBarbSettings.setMagnitudeUnits( + static_cast( mWindBarbUnitsComboBox->currentIndex() ) ); + windBarbSettings.setMagnitudeMultiplier( mWindBarbMagnitudeMultiplierSpinBox->value() ); + settings.setWindBarbSettings( windBarbSettings ); + return settings; } @@ -264,6 +291,12 @@ void QgsMeshRendererVectorSettingsWidget::syncToLayer( ) mTracesTailLengthMapUnitWidget->setUnit( tracesSettings.maximumTailLengthUnit() ); mTracesParticlesCountSpinBox->setValue( tracesSettings.particlesCount() ); + // Wind Barb settings + const QgsMeshRendererVectorWindBarbSettings windBarbSettings = settings.windBarbSettings(); + mWindBarbLengthSpinBox->setValue( windBarbSettings.shaftLength() ); + mWindBarbUnitsComboBox->setCurrentIndex( static_cast( windBarbSettings.magnitudeUnits() ) ); + if ( windBarbSettings.magnitudeUnits() == QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::OtherUnit ) + mWindBarbMagnitudeMultiplierSpinBox->setValue( windBarbSettings.magnitudeMultiplier() ); } void QgsMeshRendererVectorSettingsWidget::onSymbologyChanged( int currentIndex ) @@ -272,6 +305,7 @@ void QgsMeshRendererVectorSettingsWidget::onSymbologyChanged( int currentIndex ) mArrowLengthGroupBox->setVisible( currentIndex == QgsMeshRendererVectorSettings::Arrows ); mHeadOptionsGroupBox->setVisible( currentIndex == QgsMeshRendererVectorSettings::Arrows ); mTracesGroupBox->setVisible( currentIndex == QgsMeshRendererVectorSettings::Traces ); + mWindBarbGroupBox->setVisible( currentIndex == QgsMeshRendererVectorSettings::WindBarbs ); mDisplayVectorsOnGridGroupBox->setVisible( currentIndex != QgsMeshRendererVectorSettings::Traces ); filterByMagnitudeLabel->setVisible( currentIndex != QgsMeshRendererVectorSettings::Traces ); @@ -282,6 +316,7 @@ void QgsMeshRendererVectorSettingsWidget::onSymbologyChanged( int currentIndex ) mDisplayVectorsOnGridGroupBox->setEnabled( currentIndex == QgsMeshRendererVectorSettings::Arrows || + currentIndex == QgsMeshRendererVectorSettings::WindBarbs || ( currentIndex == QgsMeshRendererVectorSettings::Streamlines && mStreamlinesSeedingMethodComboBox->currentIndex() == QgsMeshRendererVectorStreamlineSettings::MeshGridded ) ) ; } @@ -295,6 +330,17 @@ void QgsMeshRendererVectorSettingsWidget::onStreamLineSeedingMethodChanged( int mDisplayVectorsOnGridGroupBox->setEnabled( !enabled ); } +void QgsMeshRendererVectorSettingsWidget::onWindBarbUnitsChanged( int currentIndex ) +{ + const QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit units = + static_cast( currentIndex ); + + mWindBarbMagnitudeMultiplierLabel->setVisible( units == QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::OtherUnit ); + mWindBarbMagnitudeMultiplierSpinBox->setVisible( units == QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::OtherUnit ); + + emit widgetChanged(); +} + void QgsMeshRendererVectorSettingsWidget::onColoringMethodChanged() { mColorRampShaderGroupBox->setVisible( mColoringMethodComboBox->currentData() == QgsInterpolatedLineColor::ColorRamp ); diff --git a/src/gui/mesh/qgsmeshrenderervectorsettingswidget.h b/src/gui/mesh/qgsmeshrenderervectorsettingswidget.h index a9d6bc358f807..d1b32335e0347 100644 --- a/src/gui/mesh/qgsmeshrenderervectorsettingswidget.h +++ b/src/gui/mesh/qgsmeshrenderervectorsettingswidget.h @@ -68,6 +68,7 @@ class QgsMeshRendererVectorSettingsWidget : public QWidget, private Ui::QgsMeshR private slots: void onSymbologyChanged( int currentIndex ); void onStreamLineSeedingMethodChanged( int currentIndex ); + void onWindBarbUnitsChanged( int currentIndex ); void onColoringMethodChanged(); void onColorRampMinMaxChanged(); void loadColorRampShader(); diff --git a/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp b/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp index a9b0f6da59d60..881579569f596 100644 --- a/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp +++ b/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp @@ -870,6 +870,20 @@ QgsModelChildAlgorithmGraphicItem::QgsModelChildAlgorithmGraphicItem( QgsProcess void QgsModelChildAlgorithmGraphicItem::contextMenuEvent( QGraphicsSceneContextMenuEvent *event ) { QMenu *popupmenu = new QMenu( event->widget() ); + + if ( isSelected() ) + { + QAction *runSelectedStepsAction = popupmenu->addAction( QObject::tr( "Run Selected Steps…" ) ); + runSelectedStepsAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionRunSelected.svg" ) ) ); + connect( runSelectedStepsAction, &QAction::triggered, this, &QgsModelChildAlgorithmGraphicItem::runSelected ); + } + + QAction *runFromHereAction = popupmenu->addAction( QObject::tr( "Run from Here…" ) ); + runFromHereAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStart.svg" ) ) ); + connect( runFromHereAction, &QAction::triggered, this, &QgsModelChildAlgorithmGraphicItem::runFromHere ); + + popupmenu->addSeparator(); + QAction *removeAction = popupmenu->addAction( QObject::tr( "Remove" ) ); connect( removeAction, &QAction::triggered, this, &QgsModelChildAlgorithmGraphicItem::deleteComponent ); QAction *editAction = popupmenu->addAction( QObject::tr( "Edit…" ) ); diff --git a/src/gui/processing/models/qgsmodelcomponentgraphicitem.h b/src/gui/processing/models/qgsmodelcomponentgraphicitem.h index 296619661c718..9ba6aa585d302 100644 --- a/src/gui/processing/models/qgsmodelcomponentgraphicitem.h +++ b/src/gui/processing/models/qgsmodelcomponentgraphicitem.h @@ -468,6 +468,20 @@ class GUI_EXPORT QgsModelChildAlgorithmGraphicItem : public QgsModelComponentGra signals: + /** + * Emitted when the user opts to run the model from this child algorithm. + * + * \since QGIS 3.38 + */ + void runFromHere(); + + /** + * Emitted when the user opts to run selected steps from the model. + * + * \since QGIS 3.38 + */ + void runSelected(); + /** * Emitted when the user opts to view previous results from this child algorithm. * diff --git a/src/gui/processing/models/qgsmodeldesignerdialog.cpp b/src/gui/processing/models/qgsmodeldesignerdialog.cpp index 6b20dba7c7e43..ca4ba4ab42d79 100644 --- a/src/gui/processing/models/qgsmodeldesignerdialog.cpp +++ b/src/gui/processing/models/qgsmodeldesignerdialog.cpp @@ -160,7 +160,8 @@ QgsModelDesignerDialog::QgsModelDesignerDialog( QWidget *parent, Qt::WindowFlags connect( mActionReorderOutputs, &QAction::triggered, this, &QgsModelDesignerDialog::reorderOutputs ); connect( mActionEditHelp, &QAction::triggered, this, &QgsModelDesignerDialog::editHelp ); connect( mReorderInputsButton, &QPushButton::clicked, this, &QgsModelDesignerDialog::reorderInputs ); - connect( mActionRun, &QAction::triggered, this, &QgsModelDesignerDialog::run ); + connect( mActionRun, &QAction::triggered, this, [this] { run(); } ); + connect( mActionRunSelectedSteps, &QAction::triggered, this, &QgsModelDesignerDialog::runSelectedSteps ); mActionSnappingEnabled->setChecked( settings.value( QStringLiteral( "/Processing/Modeler/enableSnapToGrid" ), false ).toBool() ); connect( mActionSnappingEnabled, &QAction::toggled, this, [ = ]( bool enabled ) @@ -512,6 +513,8 @@ void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene ) } ); connect( mScene, &QgsModelGraphicsScene::componentAboutToChange, this, [ = ]( const QString & description, int id ) { beginUndoCommand( description, id ); } ); connect( mScene, &QgsModelGraphicsScene::componentChanged, this, [ = ] { endUndoCommand(); } ); + connect( mScene, &QgsModelGraphicsScene::runFromChild, this, &QgsModelDesignerDialog::runFromChild ); + connect( mScene, &QgsModelGraphicsScene::runSelected, this, &QgsModelDesignerDialog::runSelectedSteps ); connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs, this, &QgsModelDesignerDialog::showChildAlgorithmOutputs ); connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog, this, &QgsModelDesignerDialog::showChildAlgorithmLog ); @@ -598,7 +601,7 @@ bool QgsModelDesignerDialog::checkForUnsavedChanges() void QgsModelDesignerDialog::setLastRunResult( const QgsProcessingModelResult &result ) { - mLastResult = result; + mLastResult.mergeWith( result ); if ( mScene ) mScene->setLastRunResult( mLastResult ); } @@ -993,7 +996,35 @@ void QgsModelDesignerDialog::editHelp() } } -void QgsModelDesignerDialog::run() +void QgsModelDesignerDialog::runSelectedSteps() +{ + QSet children; + const QList< QgsModelComponentGraphicItem * > items = mScene->selectedComponentItems(); + for ( QgsModelComponentGraphicItem *item : items ) + { + if ( QgsProcessingModelChildAlgorithm *childAlgorithm = dynamic_cast< QgsProcessingModelChildAlgorithm *>( item->component() ) ) + { + children.insert( childAlgorithm->childId() ); + } + } + + if ( children.isEmpty() ) + { + mMessageBar->pushWarning( QString(), tr( "No steps are selected" ) ); + return; + } + + run( children ); +} + +void QgsModelDesignerDialog::runFromChild( const QString &id ) +{ + QSet children = mModel->dependentChildAlgorithms( id ); + children.insert( id ); + run( children ); +} + +void QgsModelDesignerDialog::run( const QSet &childAlgorithmSubset ) { QStringList errors; const bool isValid = model()->validate( errors ); @@ -1027,6 +1058,33 @@ void QgsModelDesignerDialog::run() dialog->setLogLevel( Qgis::ProcessingLogLevel::ModelDebug ); dialog->setParameters( mModel->designerParameterValues() ); + connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmAboutToRun, this, [this, &childAlgorithmSubset]( QgsProcessingContext * context ) + { + if ( ! childAlgorithmSubset.empty() ) + { + // start from previous state + std::unique_ptr< QgsProcessingModelInitialRunConfig > modelConfig = std::make_unique< QgsProcessingModelInitialRunConfig >(); + modelConfig->setChildAlgorithmSubset( childAlgorithmSubset ); + modelConfig->setPreviouslyExecutedChildAlgorithms( mLastResult.executedChildIds() ); + modelConfig->setInitialChildInputs( mLastResult.rawChildInputs() ); + modelConfig->setInitialChildOutputs( mLastResult.rawChildOutputs() ); + + // add copies of layers from previous runs to context's layer store, so that they can be used + // when running the subset + const QMap previousOutputLayers = mLayerStore.temporaryLayerStore()->mapLayers(); + std::unique_ptr previousResultStore = std::make_unique< QgsMapLayerStore >(); + for ( auto it = previousOutputLayers.constBegin(); it != previousOutputLayers.constEnd(); ++it ) + { + std::unique_ptr< QgsMapLayer > clone( it.value()->clone() ); + clone->setId( it.value()->id() ); + previousResultStore->addMapLayer( clone.release() ); + } + previousResultStore->moveToThread( nullptr ); + modelConfig->setPreviousLayerStore( std::move( previousResultStore ) ); + context->setModelInitialRunConfig( std::move( modelConfig ) ); + } + } ); + connect( dialog.get(), &QgsProcessingAlgorithmDialogBase::algorithmFinished, this, [this, &dialog]( bool, const QVariantMap & ) { QgsProcessingContext *context = dialog->processingContext(); diff --git a/src/gui/processing/models/qgsmodeldesignerdialog.h b/src/gui/processing/models/qgsmodeldesignerdialog.h index cf5a9eee61850..f882bcc42688a 100644 --- a/src/gui/processing/models/qgsmodeldesignerdialog.h +++ b/src/gui/processing/models/qgsmodeldesignerdialog.h @@ -184,7 +184,9 @@ class GUI_EXPORT QgsModelDesignerDialog : public QMainWindow, public Ui::QgsMode void reorderOutputs(); void setPanelVisibility( bool hidden ); void editHelp(); - void run(); + void runSelectedSteps(); + void runFromChild( const QString &id ); + void run( const QSet &childAlgorithmSubset = QSet() ); void showChildAlgorithmOutputs( const QString &childId ); void showChildAlgorithmLog( const QString &childId ); diff --git a/src/gui/processing/models/qgsmodelgraphicsscene.cpp b/src/gui/processing/models/qgsmodelgraphicsscene.cpp index 0b52ea83bcb24..87deb39e79a98 100644 --- a/src/gui/processing/models/qgsmodelgraphicsscene.cpp +++ b/src/gui/processing/models/qgsmodelgraphicsscene.cpp @@ -145,6 +145,11 @@ void QgsModelGraphicsScene::createItems( QgsProcessingModelAlgorithm *model, Qgs connect( item, &QgsModelComponentGraphicItem::requestModelRepaint, this, &QgsModelGraphicsScene::rebuildRequired ); connect( item, &QgsModelComponentGraphicItem::changed, this, &QgsModelGraphicsScene::componentChanged ); connect( item, &QgsModelComponentGraphicItem::aboutToChange, this, &QgsModelGraphicsScene::componentAboutToChange ); + connect( item, &QgsModelChildAlgorithmGraphicItem::runFromHere, this, [this, childId] + { + emit runFromChild( childId ); + } ); + connect( item, &QgsModelChildAlgorithmGraphicItem::runSelected, this, &QgsModelGraphicsScene::runSelected ); connect( item, &QgsModelChildAlgorithmGraphicItem::showPreviousResults, this, [this, childId] { emit showChildAlgorithmOutputs( childId ); diff --git a/src/gui/processing/models/qgsmodelgraphicsscene.h b/src/gui/processing/models/qgsmodelgraphicsscene.h index b02340fb4f78c..ed6ace858273b 100644 --- a/src/gui/processing/models/qgsmodelgraphicsscene.h +++ b/src/gui/processing/models/qgsmodelgraphicsscene.h @@ -187,6 +187,20 @@ class GUI_EXPORT QgsModelGraphicsScene : public QGraphicsScene */ void selectedItemChanged( QgsModelComponentGraphicItem *selected ); + /** + * Emitted when the user opts to run selected steps from the model. + * + * \since QGIS 3.38 + */ + void runSelected(); + + /** + * Emitted when the user opts to run the part of the model starting from the specified child algorithm. + * + * \since QGIS 3.38 + */ + void runFromChild( const QString &childId ); + /** * Emitted when the user opts to view previous results from the child algorithm with matching ID. * diff --git a/src/gui/processing/qgsprocessingalgorithmdialogbase.h b/src/gui/processing/qgsprocessingalgorithmdialogbase.h index cf2920750938e..e368c6436ed1a 100644 --- a/src/gui/processing/qgsprocessingalgorithmdialogbase.h +++ b/src/gui/processing/qgsprocessingalgorithmdialogbase.h @@ -413,6 +413,15 @@ class GUI_EXPORT QgsProcessingAlgorithmDialogBase : public QDialog, public QgsPr signals: + /** + * Emitted when the algorithm is about to run in the specified \a context. + * + * This signal can be used to tweak the \a context prior to the algorithm execution. + * + * \since QGIS 3.38 + */ + void algorithmAboutToRun( QgsProcessingContext *context ); + /** * Emitted whenever an algorithm has finished executing in the dialog. * diff --git a/src/gui/processing/qgsprocessinghistoryprovider.cpp b/src/gui/processing/qgsprocessinghistoryprovider.cpp index 2aa47a050bd5b..8b765780188c7 100644 --- a/src/gui/processing/qgsprocessinghistoryprovider.cpp +++ b/src/gui/processing/qgsprocessinghistoryprovider.cpp @@ -22,6 +22,8 @@ #include "qgshistoryentrynode.h" #include "qgsprocessingregistry.h" #include "qgscodeeditorpython.h" +#include "qgscodeeditorshell.h" +#include "qgscodeeditorjson.h" #include "qgsjsonutils.h" #include @@ -83,11 +85,12 @@ void QgsProcessingHistoryProvider::portOldLog() ///@cond PRIVATE -class ProcessingHistoryNode : public QgsHistoryEntryGroup + +class ProcessingHistoryBaseNode : public QgsHistoryEntryGroup { public: - ProcessingHistoryNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider ) + ProcessingHistoryBaseNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider ) : mEntry( entry ) , mAlgorithmId( mEntry.entry.value( "algorithm_id" ).toString() ) , mPythonCommand( mEntry.entry.value( "python_command" ).toString() ) @@ -100,65 +103,7 @@ class ProcessingHistoryNode : public QgsHistoryEntryGroup { const QVariantMap parametersMap = parameters.toMap(); mInputs = parametersMap.value( QStringLiteral( "inputs" ) ).toMap(); - mDescription = QgsProcessingUtils::variantToPythonLiteral( mInputs ); - } - else - { - // an older history entry which didn't record inputs - mDescription = mPythonCommand; - } - - if ( mDescription.length() > 300 ) - { - mDescription = QObject::tr( "%1…" ).arg( mDescription.left( 299 ) ); - } - } - - QVariant data( int role = Qt::DisplayRole ) const override - { - if ( mAlgorithmInformation.displayName.isEmpty() ) - { - mAlgorithmInformation = QgsApplication::processingRegistry()->algorithmInformation( mAlgorithmId ); - } - - switch ( role ) - { - case Qt::DisplayRole: - { - const QString algName = mAlgorithmInformation.displayName; - if ( !mDescription.isEmpty() ) - return QStringLiteral( "[%1] %2 - %3" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ), - algName, - mDescription ); - else - return QStringLiteral( "[%1] %2" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ), - algName ); - } - - case Qt::DecorationRole: - { - return mAlgorithmInformation.icon; - } } - return QVariant(); - } - - QWidget *createWidget( const QgsHistoryWidgetContext & ) override - { - QgsCodeEditorPython *codeEditor = new QgsCodeEditorPython( ); - codeEditor->setReadOnly( true ); - codeEditor->setCaretLineVisible( false ); - codeEditor->setLineNumbersVisible( false ); - codeEditor->setFoldingVisible( false ); - codeEditor->setEdgeMode( QsciScintilla::EdgeNone ); - codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord ); - - - const QString introText = QStringLiteral( "\"\"\"\n%1\n\"\"\"\n\n " ).arg( - QObject::tr( "Double-click on the history item or paste the command below to re-run the algorithm" ) ); - codeEditor->setText( introText + mPythonCommand ); - - return codeEditor; } bool doubleClicked( const QgsHistoryWidgetContext & ) override @@ -248,18 +193,248 @@ class ProcessingHistoryNode : public QgsHistoryEntryGroup QString mPythonCommand; QString mProcessCommand; QVariantMap mInputs; - QString mDescription; - mutable QgsProcessingAlgorithmInformation mAlgorithmInformation; QgsProcessingHistoryProvider *mProvider = nullptr; }; +class ProcessingHistoryPythonCommandNode : public ProcessingHistoryBaseNode +{ + public: + + ProcessingHistoryPythonCommandNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider ) + : ProcessingHistoryBaseNode( entry, provider ) + {} + + QVariant data( int role = Qt::DisplayRole ) const override + { + switch ( role ) + { + case Qt::DisplayRole: + { + QString display = mPythonCommand; + if ( display.length() > 300 ) + { + display = QObject::tr( "%1…" ).arg( display.left( 299 ) ); + } + return display; + } + case Qt::DecorationRole: + return QgsApplication::getThemeIcon( QStringLiteral( "mIconPythonFile.svg" ) ); + + default: + break; + } + return QVariant(); + } + + QWidget *createWidget( const QgsHistoryWidgetContext & ) override + { + QgsCodeEditorPython *codeEditor = new QgsCodeEditorPython( ); + codeEditor->setReadOnly( true ); + codeEditor->setCaretLineVisible( false ); + codeEditor->setLineNumbersVisible( false ); + codeEditor->setFoldingVisible( false ); + codeEditor->setEdgeMode( QsciScintilla::EdgeNone ); + codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord ); + + + const QString introText = QStringLiteral( "\"\"\"\n%1\n\"\"\"\n\n " ).arg( + QObject::tr( "Double-click on the history item or paste the command below to re-run the algorithm" ) ); + codeEditor->setText( introText + mPythonCommand ); + + return codeEditor; + } +}; + +class ProcessingHistoryProcessCommandNode : public ProcessingHistoryBaseNode +{ + public: + + ProcessingHistoryProcessCommandNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider ) + : ProcessingHistoryBaseNode( entry, provider ) + {} + + QVariant data( int role = Qt::DisplayRole ) const override + { + switch ( role ) + { + case Qt::DisplayRole: + { + QString display = mProcessCommand; + if ( display.length() > 300 ) + { + display = QObject::tr( "%1…" ).arg( display.left( 299 ) ); + } + return display; + } + case Qt::DecorationRole: + return QgsApplication::getThemeIcon( QStringLiteral( "mActionTerminal.svg" ) ); + + default: + break; + } + return QVariant(); + } + + QWidget *createWidget( const QgsHistoryWidgetContext & ) override + { + QgsCodeEditorShell *codeEditor = new QgsCodeEditorShell( ); + codeEditor->setReadOnly( true ); + codeEditor->setCaretLineVisible( false ); + codeEditor->setLineNumbersVisible( false ); + codeEditor->setFoldingVisible( false ); + codeEditor->setEdgeMode( QsciScintilla::EdgeNone ); + codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord ); + + codeEditor->setText( mProcessCommand ); + + return codeEditor; + } +}; + + +class ProcessingHistoryJsonNode : public ProcessingHistoryBaseNode +{ + public: + + ProcessingHistoryJsonNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider ) + : ProcessingHistoryBaseNode( entry, provider ) + { + mJson = QString::fromStdString( QgsJsonUtils::jsonFromVariant( mInputs ).dump( 2 ) ); + mJsonSingleLine = QString::fromStdString( QgsJsonUtils::jsonFromVariant( mInputs ).dump() ); + } + + QVariant data( int role = Qt::DisplayRole ) const override + { + switch ( role ) + { + case Qt::DisplayRole: + { + QString display = mJsonSingleLine; + if ( display.length() > 300 ) + { + display = QObject::tr( "%1…" ).arg( display.left( 299 ) ); + } + return display; + } + case Qt::DecorationRole: + return QgsApplication::getThemeIcon( QStringLiteral( "mIconFieldJson.svg" ) ); + + default: + break; + } + return QVariant(); + } + + QWidget *createWidget( const QgsHistoryWidgetContext & ) override + { + QgsCodeEditorJson *codeEditor = new QgsCodeEditorJson( ); + codeEditor->setReadOnly( true ); + codeEditor->setCaretLineVisible( false ); + codeEditor->setLineNumbersVisible( false ); + codeEditor->setFoldingVisible( false ); + codeEditor->setEdgeMode( QsciScintilla::EdgeNone ); + codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord ); + + codeEditor->setText( mJson ); + + return codeEditor; + } + + QString mJson; + QString mJsonSingleLine; +}; + + +class ProcessingHistoryRootNode : public ProcessingHistoryBaseNode +{ + public: + + ProcessingHistoryRootNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider ) + : ProcessingHistoryBaseNode( entry, provider ) + { + const QVariant parameters = mEntry.entry.value( QStringLiteral( "parameters" ) ); + if ( parameters.type() == QVariant::Map ) + { + mDescription = QgsProcessingUtils::variantToPythonLiteral( mInputs ); + } + else + { + // an older history entry which didn't record inputs + mDescription = mPythonCommand; + } + + if ( mDescription.length() > 300 ) + { + mDescription = QObject::tr( "%1…" ).arg( mDescription.left( 299 ) ); + } + + addChild( new ProcessingHistoryPythonCommandNode( mEntry, mProvider ) ); + addChild( new ProcessingHistoryProcessCommandNode( mEntry, mProvider ) ); + addChild( new ProcessingHistoryJsonNode( mEntry, mProvider ) ); + } + + void setEntry( const QgsHistoryEntry &entry ) + { + mEntry = entry; + } + + QVariant data( int role = Qt::DisplayRole ) const override + { + if ( mAlgorithmInformation.displayName.isEmpty() ) + { + mAlgorithmInformation = QgsApplication::processingRegistry()->algorithmInformation( mAlgorithmId ); + } + + switch ( role ) + { + case Qt::DisplayRole: + { + const QString algName = mAlgorithmInformation.displayName; + if ( !mDescription.isEmpty() ) + return QStringLiteral( "[%1] %2 - %3" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ), + algName, + mDescription ); + else + return QStringLiteral( "[%1] %2" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ), + algName ); + } + + case Qt::DecorationRole: + { + return mAlgorithmInformation.icon; + } + + default: + break; + } + return QVariant(); + } + + QString html( const QgsHistoryWidgetContext & ) const override + { + return mEntry.entry.value( QStringLiteral( "log" ) ).toString(); + } + + QString mDescription; + mutable QgsProcessingAlgorithmInformation mAlgorithmInformation; + +}; + ///@endcond QgsHistoryEntryNode *QgsProcessingHistoryProvider::createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext & ) { - return new ProcessingHistoryNode( entry, this ); + return new ProcessingHistoryRootNode( entry, this ); +} + +void QgsProcessingHistoryProvider::updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext & ) +{ + if ( ProcessingHistoryRootNode *rootNode = dynamic_cast< ProcessingHistoryRootNode * >( node ) ) + { + rootNode->setEntry( entry ); + } } QString QgsProcessingHistoryProvider::oldLogPath() const diff --git a/src/gui/processing/qgsprocessinghistoryprovider.h b/src/gui/processing/qgsprocessinghistoryprovider.h index 03f74ddc8097c..c24fe3db6a1fa 100644 --- a/src/gui/processing/qgsprocessinghistoryprovider.h +++ b/src/gui/processing/qgsprocessinghistoryprovider.h @@ -45,6 +45,7 @@ class GUI_EXPORT QgsProcessingHistoryProvider : public QgsAbstractHistoryProvide void portOldLog(); QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) override SIP_FACTORY; + void updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) override; signals: @@ -72,7 +73,7 @@ class GUI_EXPORT QgsProcessingHistoryProvider : public QgsAbstractHistoryProvide //! Returns the path to the old log file QString oldLogPath() const; - friend class ProcessingHistoryNode; + friend class ProcessingHistoryBaseNode; }; diff --git a/src/gui/qgsbrowsertreeview.cpp b/src/gui/qgsbrowsertreeview.cpp index 6ce4a05b989d1..4377084e8e0f5 100644 --- a/src/gui/qgsbrowsertreeview.cpp +++ b/src/gui/qgsbrowsertreeview.cpp @@ -177,7 +177,7 @@ bool QgsBrowserTreeView::hasExpandedDescendant( const QModelIndex &index ) const return false; } -void QgsBrowserTreeView::expandPath( const QString &str ) +void QgsBrowserTreeView::expandPath( const QString &str, bool selectPath ) { const QStringList pathParts = QgsFileUtils::splitPathToComponents( str ); if ( pathParts.isEmpty() ) @@ -289,6 +289,7 @@ void QgsBrowserTreeView::expandPath( const QString &str ) currentDir = QDir( thisPath ); } + QgsDirectoryItem *lastItem = nullptr; for ( QgsDirectoryItem *i : std::as_const( pathItems ) ) { QModelIndex index = mBrowserModel->findItem( i ); @@ -297,7 +298,11 @@ void QgsBrowserTreeView::expandPath( const QString &str ) index = proxyModel->mapFromSource( index ); } expand( index ); + lastItem = i; } + + if ( selectPath && lastItem ) + setSelectedItem( lastItem ); } bool QgsBrowserTreeView::setSelectedItem( QgsDataItem *item ) diff --git a/src/gui/qgsbrowsertreeview.h b/src/gui/qgsbrowsertreeview.h index 121b8f6003ab6..d91a8c08e3b9b 100644 --- a/src/gui/qgsbrowsertreeview.h +++ b/src/gui/qgsbrowsertreeview.h @@ -82,9 +82,11 @@ class GUI_EXPORT QgsBrowserTreeView : public QTreeView * * The \a path must correspond to a valid directory existing on the file system. * + * Since QGIS 3.38 the \a selectPath argument can be used to automatically select the path too. + * * \since QGIS 3.28 */ - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); protected: diff --git a/src/gui/qgscolorramplegendnodewidget.cpp b/src/gui/qgscolorramplegendnodewidget.cpp index 47da95afd0a4c..3bff355631f92 100644 --- a/src/gui/qgscolorramplegendnodewidget.cpp +++ b/src/gui/qgscolorramplegendnodewidget.cpp @@ -16,13 +16,12 @@ ***************************************************************************/ #include "qgscolorramplegendnodewidget.h" -#include "qgscolorramplegendnode.h" #include "qgshelp.h" #include "qgsnumericformatselectorwidget.h" #include "qgsnumericformat.h" #include -QgsColorRampLegendNodeWidget::QgsColorRampLegendNodeWidget( QWidget *parent ) +QgsColorRampLegendNodeWidget::QgsColorRampLegendNodeWidget( QWidget *parent, Capabilities capabilities ) : QgsPanelWidget( parent ) { setupUi( this ); @@ -33,8 +32,22 @@ QgsColorRampLegendNodeWidget::QgsColorRampLegendNodeWidget( QWidget *parent ) mOrientationComboBox->addItem( tr( "Vertical" ), Qt::Vertical ); mOrientationComboBox->addItem( tr( "Horizontal" ), Qt::Horizontal ); - mMinLabelLineEdit->setPlaceholderText( tr( "Default" ) ); - mMaxLabelLineEdit->setPlaceholderText( tr( "Default" ) ); + if ( capabilities.testFlag( Capability::DefaultMinimum ) ) + { + mMinLabelLineEdit->setPlaceholderText( tr( "Default" ) ); + } + else + { + mMinLabelLineEdit->setShowClearButton( false ); + } + if ( capabilities.testFlag( Capability::DefaultMinimum ) ) + { + mMaxLabelLineEdit->setPlaceholderText( tr( "Default" ) ); + } + else + { + mMaxLabelLineEdit->setShowClearButton( false ); + } mFontButton->setShowNullFormat( true ); mFontButton->setNoFormatString( tr( "Default" ) ); @@ -54,6 +67,22 @@ QgsColorRampLegendNodeWidget::QgsColorRampLegendNodeWidget( QWidget *parent ) connect( mOrientationComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsColorRampLegendNodeWidget::onOrientationChanged ); connect( mNumberFormatPushButton, &QPushButton::clicked, this, &QgsColorRampLegendNodeWidget::changeNumberFormat ); connect( mFontButton, &QgsFontButton::changed, this, &QgsColorRampLegendNodeWidget::onChanged ); + + if ( !capabilities.testFlag( Capability::Prefix ) ) + { + mPrefixLineEdit->hide(); + mPrefixLabel->hide(); + } + if ( !capabilities.testFlag( Capability::Suffix ) ) + { + mSuffixLineEdit->hide(); + mSuffixLabel->hide(); + } + if ( !capabilities.testFlag( Capability::NumberFormat ) ) + { + mNumberFormatPushButton->hide(); + mNumberFormatLabel->hide(); + } } QgsColorRampLegendNodeSettings QgsColorRampLegendNodeWidget::settings() const @@ -137,11 +166,11 @@ void QgsColorRampLegendNodeWidget::onChanged() // QgsColorRampLegendNodeDialog // -QgsColorRampLegendNodeDialog::QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent ) +QgsColorRampLegendNodeDialog::QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent, QgsColorRampLegendNodeWidget::Capabilities capabilities ) : QDialog( parent ) { QVBoxLayout *vLayout = new QVBoxLayout(); - mWidget = new QgsColorRampLegendNodeWidget( nullptr ); + mWidget = new QgsColorRampLegendNodeWidget( nullptr, capabilities ); vLayout->addWidget( mWidget ); mButtonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok, Qt::Horizontal ); connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept ); diff --git a/src/gui/qgscolorramplegendnodewidget.h b/src/gui/qgscolorramplegendnodewidget.h index c3bdcdf52413d..b461b8c7f4898 100644 --- a/src/gui/qgscolorramplegendnodewidget.h +++ b/src/gui/qgscolorramplegendnodewidget.h @@ -21,8 +21,7 @@ #include "qgis_gui.h" #include "ui_qgscolorramplegendnodewidgetbase.h" - -#include "qgscolorramplegendnode.h" +#include "qgscolorramplegendnodesettings.h" #include class QDialogButtonBox; @@ -44,10 +43,36 @@ class GUI_EXPORT QgsColorRampLegendNodeWidget: public QgsPanelWidget, private Ui public: + /** + * Capabilities to expose in the widget. + * + * \since QGIS 3.38 + */ + enum class Capability : int SIP_ENUM_BASETYPE( IntFlag ) + { + Prefix = 1 << 0, //!< Allow editing legend prefix + Suffix = 1 << 1, //!< Allow editing legend suffix + NumberFormat = 1 << 2, //!< Allow editing number format + DefaultMinimum = 1 << 3, //!< Allow resetting minimum label to default + DefaultMaximum = 1 << 4, //!< Allow resetting maximum label to default + AllCapabilities = Prefix | Suffix | NumberFormat | DefaultMinimum | DefaultMaximum, //!< All capabilities + }; + Q_ENUM( Capability ) + + /** + * Capabilities to expose in the widget. + * + * \since QGIS 3.38 + */ + Q_DECLARE_FLAGS( Capabilities, Capability ) + Q_FLAG( Capabilities ) + /** * Constructor for QgsColorRampLegendNodeWidget, with the specified \a parent widget. + * + * Since QGIS 3.38, the \a capabilities argument can be used to fine-tune settings exposed in the widget. */ - QgsColorRampLegendNodeWidget( QWidget *parent = nullptr ); + QgsColorRampLegendNodeWidget( QWidget *parent = nullptr, QgsColorRampLegendNodeWidget::Capabilities capabilities = QgsColorRampLegendNodeWidget::Capability::AllCapabilities ); /** * Returns the legend node settings as defined by the widget. @@ -84,6 +109,7 @@ class GUI_EXPORT QgsColorRampLegendNodeWidget: public QgsPanelWidget, private Ui QgsColorRampLegendNodeSettings mSettings; }; +Q_DECLARE_OPERATORS_FOR_FLAGS( QgsColorRampLegendNodeWidget::Capabilities ) /** * \ingroup gui @@ -98,8 +124,10 @@ class GUI_EXPORT QgsColorRampLegendNodeDialog : public QDialog /** * Constructor for QgsColorRampLegendNodeDialog, initially showing the specified \a settings. + * + * Since QGIS 3.38, the \a capabilities argument can be used to fine-tune settings exposed in the dialog. */ - QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + QgsColorRampLegendNodeDialog( const QgsColorRampLegendNodeSettings &settings, QWidget *parent SIP_TRANSFERTHIS = nullptr, QgsColorRampLegendNodeWidget::Capabilities capabilities = QgsColorRampLegendNodeWidget::Capability::AllCapabilities ); /** * Returns the legend node settings as defined by the dialog. diff --git a/src/gui/qgsdatasourceselectdialog.cpp b/src/gui/qgsdatasourceselectdialog.cpp index fc76099de596f..b98b57639caa0 100644 --- a/src/gui/qgsdatasourceselectdialog.cpp +++ b/src/gui/qgsdatasourceselectdialog.cpp @@ -31,6 +31,7 @@ #include #include #include +#include QgsDataSourceSelectWidget::QgsDataSourceSelectWidget( QgsBrowserGuiModel *browserModel, @@ -116,6 +117,8 @@ QgsDataSourceSelectWidget::QgsDataSourceSelectWidget( { mActionShowFilter->trigger(); } + + setAcceptDrops( true ); } QgsDataSourceSelectWidget::~QgsDataSourceSelectWidget() = default; @@ -145,6 +148,58 @@ void QgsDataSourceSelectWidget::showEvent( QShowEvent *e ) } } +QString QgsDataSourceSelectWidget::acceptableFilePath( QDropEvent *event ) const +{ + if ( event->mimeData()->hasUrls() ) + { + const QList< QUrl > urls = event->mimeData()->urls(); + for ( const QUrl &url : urls ) + { + const QString local = url.toLocalFile(); + if ( local.isEmpty() ) + continue; + + if ( QFile::exists( local ) ) + { + return local; + } + } + } + return QString(); +} + +void QgsDataSourceSelectWidget::dragEnterEvent( QDragEnterEvent *event ) +{ + const QString filePath = acceptableFilePath( event ); + if ( !filePath.isEmpty() ) + { + event->acceptProposedAction(); + } + else + { + event->ignore(); + } +} + +void QgsDataSourceSelectWidget::dropEvent( QDropEvent *event ) +{ + const QString filePath = acceptableFilePath( event ); + if ( !filePath.isEmpty() ) + { + event->acceptProposedAction(); + + const QFileInfo fi( filePath ); + if ( fi.isDir() ) + { + expandPath( filePath, true ); + } + else + { + expandPath( fi.dir().path(), true ); + } + } +} + void QgsDataSourceSelectWidget::showFilterWidget( bool visible ) { QgsSettings().setValue( QStringLiteral( "datasourceSelectFilterVisible" ), visible, QgsSettings::Section::Gui ); @@ -194,9 +249,9 @@ void QgsDataSourceSelectWidget::setDescription( const QString &description ) } } -void QgsDataSourceSelectWidget::expandPath( const QString &path ) +void QgsDataSourceSelectWidget::expandPath( const QString &path, bool selectPath ) { - mBrowserTreeView->expandPath( path ); + mBrowserTreeView->expandPath( path, selectPath ); } void QgsDataSourceSelectWidget::setFilter() @@ -205,7 +260,6 @@ void QgsDataSourceSelectWidget::setFilter() mBrowserProxyModel.setFilterString( filter ); } - void QgsDataSourceSelectWidget::refreshModel( const QModelIndex &index ) { @@ -255,7 +309,6 @@ void QgsDataSourceSelectWidget::setValid( bool valid ) } - void QgsDataSourceSelectWidget::setFilterSyntax( QAction *action ) { if ( !action ) @@ -354,9 +407,9 @@ void QgsDataSourceSelectDialog::setDescription( const QString &description ) mWidget->setDescription( description ); } -void QgsDataSourceSelectDialog::expandPath( const QString &path ) +void QgsDataSourceSelectDialog::expandPath( const QString &path, bool selectPath ) { - mWidget->expandPath( path ); + mWidget->expandPath( path, selectPath ); } QgsMimeDataUtils::Uri QgsDataSourceSelectDialog::uri() const diff --git a/src/gui/qgsdatasourceselectdialog.h b/src/gui/qgsdatasourceselectdialog.h index d9668835d1c33..8a2796b505914 100644 --- a/src/gui/qgsdatasourceselectdialog.h +++ b/src/gui/qgsdatasourceselectdialog.h @@ -82,9 +82,11 @@ class GUI_EXPORT QgsDataSourceSelectWidget: public QgsPanelWidget, private Ui::Q * * The \a path must correspond to a valid directory existing on the file system. * + * Since QGIS 3.38 the \a selectPath argument can be used to automatically select the path too. + * * \since QGIS 3.28 */ - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); /** * Returns the (possibly invalid) uri of the selected data source @@ -102,6 +104,9 @@ class GUI_EXPORT QgsDataSourceSelectWidget: public QgsPanelWidget, private Ui::Q //! Scroll to last selected index and expand it's children void showEvent( QShowEvent *e ) override; + void dragEnterEvent( QDragEnterEvent *event ) override; + void dropEvent( QDropEvent *event ) override; + signals: /** @@ -135,6 +140,9 @@ class GUI_EXPORT QgsDataSourceSelectWidget: public QgsPanelWidget, private Ui::Q void setValid( bool valid ); + //! Returns file name if object meets drop criteria. + QString acceptableFilePath( QDropEvent *event ) const; + QgsBrowserProxyModel mBrowserProxyModel; QgsBrowserGuiModel *mBrowserModel = nullptr; QgsMimeDataUtils::Uri mUri; @@ -195,9 +203,11 @@ class GUI_EXPORT QgsDataSourceSelectDialog: public QDialog * * The \a path must correspond to a valid directory existing on the file system. * + * Since QGIS 3.38 the \a selectPath argument can be used to automatically select the path too. + * * \since QGIS 3.28 */ - void expandPath( const QString &path ); + void expandPath( const QString &path, bool selectPath = false ); /** * Returns the (possibly invalid) uri of the selected data source diff --git a/src/gui/qgsdecoratedscrollbar.cpp b/src/gui/qgsdecoratedscrollbar.cpp new file mode 100644 index 0000000000000..791ccbcdd85e2 --- /dev/null +++ b/src/gui/qgsdecoratedscrollbar.cpp @@ -0,0 +1,427 @@ +/*************************************************************************** + qgsdecoratedscrollbar.cpp + -------------------------------------- + Date : May 2024 + Copyright : (C) 2024 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 "qgsdecoratedscrollbar.h" +#include +#include +#include +#include +#include +#include + +///@cond PRIVATE + +// +// QgsScrollBarHighlightOverlay +// + +QgsScrollBarHighlightOverlay::QgsScrollBarHighlightOverlay( QgsScrollBarHighlightController *scrollBarController ) + : QWidget( scrollBarController->scrollArea() ) + , mHighlightController( scrollBarController ) +{ + setAttribute( Qt::WA_TransparentForMouseEvents ); + scrollBar()->parentWidget()->installEventFilter( this ); + doResize(); + doMove(); + setVisible( scrollBar()->isVisible() ); +} + +void QgsScrollBarHighlightOverlay::doResize() +{ + resize( scrollBar()->size() ); +} + +void QgsScrollBarHighlightOverlay::doMove() +{ + move( parentWidget()->mapFromGlobal( scrollBar()->mapToGlobal( scrollBar()->pos() ) ) ); +} + +void QgsScrollBarHighlightOverlay::scheduleUpdate() +{ + if ( mIsCacheUpdateScheduled ) + return; + + mIsCacheUpdateScheduled = true; +// silence false positive leak warning +#ifndef __clang_analyzer__ + QMetaObject::invokeMethod( this, qOverload<>( &QWidget::update ), Qt::QueuedConnection ); +#endif +} + +void QgsScrollBarHighlightOverlay::paintEvent( QPaintEvent *paintEvent ) +{ + QWidget::paintEvent( paintEvent ); + + updateCache(); + + if ( mHighlightCache.isEmpty() ) + return; + + QPainter painter( this ); + painter.setRenderHint( QPainter::Antialiasing, false ); + + const QRect &gRect = overlayRect(); + const QRect &hRect = handleRect(); + + constexpr int marginX = 3; + constexpr int marginH = -2 * marginX + 1; + const QRect aboveHandleRect = QRect( gRect.x() + marginX, + gRect.y(), + gRect.width() + marginH, + hRect.y() - gRect.y() ); + const QRect handleRect = QRect( gRect.x() + marginX, + hRect.y(), + gRect.width() + marginH, + hRect.height() ); + const QRect belowHandleRect = QRect( gRect.x() + marginX, + hRect.y() + hRect.height(), + gRect.width() + marginH, + gRect.height() - hRect.height() + gRect.y() - hRect.y() ); + + const int aboveValue = scrollBar()->value(); + const int belowValue = scrollBar()->maximum() - scrollBar()->value(); + const int sizeDocAbove = int( aboveValue * mHighlightController->lineHeight() ); + const int sizeDocBelow = int( belowValue * mHighlightController->lineHeight() ); + const int sizeDocVisible = int( mHighlightController->visibleRange() ); + + const int scrollBarBackgroundHeight = aboveHandleRect.height() + belowHandleRect.height(); + const int sizeDocInvisible = sizeDocAbove + sizeDocBelow; + const double backgroundRatio = sizeDocInvisible + ? ( ( double )scrollBarBackgroundHeight / sizeDocInvisible ) : 0; + + + if ( aboveValue ) + { + drawHighlights( &painter, + 0, + sizeDocAbove, + backgroundRatio, + 0, + aboveHandleRect ); + } + + if ( belowValue ) + { + // This is the hypothetical handle height if the handle would + // be stretched using the background ratio. + const double handleVirtualHeight = sizeDocVisible * backgroundRatio; + // Skip the doc above and visible part. + const int offset = static_cast< int >( std::round( aboveHandleRect.height() + handleVirtualHeight ) ); + + drawHighlights( &painter, + sizeDocAbove + sizeDocVisible, + sizeDocBelow, + backgroundRatio, + offset, + belowHandleRect ); + } + + const double handleRatio = sizeDocVisible + ? ( ( double )handleRect.height() / sizeDocVisible ) : 0; + + // This is the hypothetical handle position if the background would + // be stretched using the handle ratio. + const double aboveVirtualHeight = sizeDocAbove * handleRatio; + + // This is the accurate handle position (double) + const double accurateHandlePos = sizeDocAbove * backgroundRatio; + // The correction between handle position (int) and accurate position (double) + const double correction = aboveHandleRect.height() - accurateHandlePos; + // Skip the doc above and apply correction + const int offset = static_cast< int >( std::round( aboveVirtualHeight + correction ) ); + + drawHighlights( &painter, + sizeDocAbove, + sizeDocVisible, + handleRatio, + offset, + handleRect ); +} + +void QgsScrollBarHighlightOverlay::drawHighlights( QPainter *painter, + int docStart, + int docSize, + double docSizeToHandleSizeRatio, + int handleOffset, + const QRect &viewport ) +{ + if ( docSize <= 0 ) + return; + + painter->save(); + painter->setClipRect( viewport ); + + const double lineHeight = mHighlightController->lineHeight(); + + for ( const QMap> &colors : std::as_const( mHighlightCache ) ) + { + const auto itColorEnd = colors.constEnd(); + for ( auto itColor = colors.constBegin(); itColor != itColorEnd; ++itColor ) + { + const QColor color = itColor.key(); + const QMap &positions = itColor.value(); + const auto itPosEnd = positions.constEnd(); + const auto firstPos = int( docStart / lineHeight ); + auto itPos = positions.upperBound( firstPos ); + if ( itPos != positions.constBegin() ) + --itPos; + while ( itPos != itPosEnd ) + { + const double posStart = itPos.key() * lineHeight; + const double posEnd = ( itPos.value() + 1 ) * lineHeight; + if ( posEnd < docStart ) + { + ++itPos; + continue; + } + if ( posStart > docStart + docSize ) + break; + + const int height = std::max( static_cast< int >( std::round( ( posEnd - posStart ) * docSizeToHandleSizeRatio ) ), 1 ); + const int top = static_cast< int >( std::round( posStart * docSizeToHandleSizeRatio ) - handleOffset + viewport.y() ); + + const QRect rect( viewport.left(), top, viewport.width(), height ); + painter->fillRect( rect, color ); + ++itPos; + } + } + } + painter->restore(); +} + +bool QgsScrollBarHighlightOverlay::eventFilter( QObject *object, QEvent *event ) +{ + switch ( event->type() ) + { + case QEvent::Move: + doMove(); + break; + case QEvent::Resize: + doResize(); + break; + case QEvent::ZOrderChange: + raise(); + break; + case QEvent::Show: + show(); + break; + case QEvent::Hide: + hide(); + break; + default: + break; + } + return QWidget::eventFilter( object, event ); +} + +static void insertPosition( QMap *map, int position ) +{ + auto itNext = map->upperBound( position ); + + bool gluedWithPrev = false; + if ( itNext != map->begin() ) + { + auto itPrev = std::prev( itNext ); + const int keyStart = itPrev.key(); + const int keyEnd = itPrev.value(); + if ( position >= keyStart && position <= keyEnd ) + return; // pos is already included + + if ( keyEnd + 1 == position ) + { + // glue with prev + ( *itPrev )++; + gluedWithPrev = true; + } + } + + if ( itNext != map->end() && itNext.key() == position + 1 ) + { + const int keyEnd = itNext.value(); + itNext = map->erase( itNext ); + if ( gluedWithPrev ) + { + // glue with prev and next + auto itPrev = std::prev( itNext ); + *itPrev = keyEnd; + } + else + { + // glue with next + itNext = map->insert( itNext, position, keyEnd ); + } + return; // glued + } + + if ( gluedWithPrev ) + return; // glued + + map->insert( position, position ); +} + +void QgsScrollBarHighlightOverlay::updateCache() +{ + if ( !mIsCacheUpdateScheduled ) + return; + + mHighlightCache.clear(); + + const QHash> highlightsForId = mHighlightController->highlights(); + for ( const QVector &highlights : highlightsForId ) + { + for ( const QgsScrollBarHighlight &highlight : highlights ) + { + QMap &highlightMap = mHighlightCache[highlight.priority][highlight.color.rgba()]; + insertPosition( &highlightMap, highlight.position ); + } + } + + mIsCacheUpdateScheduled = false; +} + +QRect QgsScrollBarHighlightOverlay::overlayRect() const +{ + QStyleOptionSlider opt = qt_qscrollbarStyleOption( scrollBar() ); + return scrollBar()->style()->subControlRect( QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, scrollBar() ); +} + +QRect QgsScrollBarHighlightOverlay::handleRect() const +{ + QStyleOptionSlider opt = qt_qscrollbarStyleOption( scrollBar() ); + return scrollBar()->style()->subControlRect( QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, scrollBar() ); +} + +///@endcond PRIVATE + +// +// QgsScrollBarHighlight +// + +QgsScrollBarHighlight::QgsScrollBarHighlight( int category, int position, + const QColor &color, QgsScrollBarHighlight::Priority priority ) + : category( category ) + , position( position ) + , color( color ) + , priority( priority ) +{ +} + + +// +// QgsScrollBarHighlightController +// + +QgsScrollBarHighlightController::QgsScrollBarHighlightController() = default; + +QgsScrollBarHighlightController::~QgsScrollBarHighlightController() +{ + if ( mOverlay ) + delete mOverlay; +} + +QScrollBar *QgsScrollBarHighlightController::scrollBar() const +{ + if ( mScrollArea ) + return mScrollArea->verticalScrollBar(); + + return nullptr; +} + +QAbstractScrollArea *QgsScrollBarHighlightController::scrollArea() const +{ + return mScrollArea; +} + +void QgsScrollBarHighlightController::setScrollArea( QAbstractScrollArea *scrollArea ) +{ + if ( mScrollArea == scrollArea ) + return; + + if ( mOverlay ) + { + delete mOverlay; + mOverlay = nullptr; + } + + mScrollArea = scrollArea; + + if ( mScrollArea ) + { + mOverlay = new QgsScrollBarHighlightOverlay( this ); + mOverlay->scheduleUpdate(); + } +} + +double QgsScrollBarHighlightController::lineHeight() const +{ + return std::ceil( mLineHeight ); +} + +void QgsScrollBarHighlightController::setLineHeight( double lineHeight ) +{ + mLineHeight = lineHeight; +} + +double QgsScrollBarHighlightController::visibleRange() const +{ + return mVisibleRange; +} + +void QgsScrollBarHighlightController::setVisibleRange( double visibleRange ) +{ + mVisibleRange = visibleRange; +} + +double QgsScrollBarHighlightController::margin() const +{ + return mMargin; +} + +void QgsScrollBarHighlightController::setMargin( double margin ) +{ + mMargin = margin; +} + +QHash> QgsScrollBarHighlightController::highlights() const +{ + return mHighlights; +} + +void QgsScrollBarHighlightController::addHighlight( const QgsScrollBarHighlight &highlight ) +{ + if ( !mOverlay ) + return; + + mHighlights[highlight.category] << highlight; + mOverlay->scheduleUpdate(); +} + +void QgsScrollBarHighlightController::removeHighlights( int category ) +{ + if ( !mOverlay ) + return; + + mHighlights.remove( category ); + mOverlay->scheduleUpdate(); +} + +void QgsScrollBarHighlightController::removeAllHighlights() +{ + if ( !mOverlay ) + return; + + mHighlights.clear(); + mOverlay->scheduleUpdate(); +} diff --git a/src/gui/qgsdecoratedscrollbar.h b/src/gui/qgsdecoratedscrollbar.h new file mode 100644 index 0000000000000..fc85120faf9bd --- /dev/null +++ b/src/gui/qgsdecoratedscrollbar.h @@ -0,0 +1,221 @@ +/*************************************************************************** + qgsdecoratedscrollbar.h + -------------------------------------- + Date : May 2024 + Copyright : (C) 2024 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 QGSDECORATEDSCROLLBAR_H +#define QGSDECORATEDSCROLLBAR_H + +#include "qgis_gui.h" +#include "qgis_sip.h" + +#include +#include +#include +#include +#include + +class QScrollBar; +class QAbstractScrollArea; +class QgsScrollBarHighlightOverlay; + +// ported from QtCreator's HighlightScrollBarController implementation + +/** + * \ingroup gui + * \brief Encapsulates the details of a highlight in a scrollbar, used alongside QgsScrollBarHighlightController. + * \since QGIS 3.38 + */ +class GUI_EXPORT QgsScrollBarHighlight +{ + public: + + /** + * Priority, which dictates how overlapping highlights are rendered + */ + enum class Priority : int + { + Invalid = -1, //!< Invalid + LowPriority = 0, //!< Low priority, rendered below all other highlights + NormalPriority = 1, //!< Normal priority + HighPriority = 2, //!< High priority + HighestPriority = 3 //!< Highest priority, rendered above all other highlights + }; + + /** + * Constructor for QgsScrollBarHighlight. + */ + QgsScrollBarHighlight( int category, int position, const QColor &color, QgsScrollBarHighlight::Priority priority = QgsScrollBarHighlight::Priority::NormalPriority ); + + /** + * Default constructor for QgsScrollBarHighlight. + */ + QgsScrollBarHighlight() = default; + + //! Category ID + int category = -1; + + //! Position in scroll bar + int position = -1; + + //! Highlight color + QColor color; + + //! Priority, which dictates how overlapping highlights are rendered + QgsScrollBarHighlight::Priority priority = QgsScrollBarHighlight::Priority::Invalid; +}; + +/** + * \ingroup gui + * \brief Adds highlights (colored markers) to a scrollbar. + * \since QGIS 3.38 + */ +class GUI_EXPORT QgsScrollBarHighlightController +{ + public: + + QgsScrollBarHighlightController(); + ~QgsScrollBarHighlightController(); + + /** + * Returns the associated scroll bar. + */ + QScrollBar *scrollBar() const; + + /** + * Returns the associated scroll area. + * + * \see setScrollArea() + */ + QAbstractScrollArea *scrollArea() const; + + /** + * Sets the associated scroll bar. + * + * \see scrollArea() + */ + void setScrollArea( QAbstractScrollArea *scrollArea ); + + /** + * Returns the line height for text associated with the scroll area. + * + * \see setLineHeight() + */ + double lineHeight() const; + + /** + * Sets the line \a height for text associated with the scroll area. + * + * \see lineHeight() + */ + void setLineHeight( double height ); + + /** + * Returns the visible range of the scroll area (i.e. the viewport's height). + * + * \see setVisibleRange() + */ + double visibleRange() const; + + /** + * Sets the visible range of the scroll area (i.e. the viewport's height). + * + * \see visibleRange() + */ + void setVisibleRange( double visibleRange ); + + /** + * Returns the document margins for the associated viewport. + * + * \see setMargin() + */ + double margin() const; + + /** + * Sets the document \a margin for the associated viewport. + * + * \see margin() + */ + void setMargin( double margin ); + + /** + * Returns the hash of all highlights in the scrollbar, with highlight categories as hash keys. + * + * \note Not available in Python bindings + */ + QHash> highlights() const SIP_SKIP; + + /** + * Adds a \a highlight to the scrollbar. + */ + void addHighlight( const QgsScrollBarHighlight &highlight ); + + /** + * Removes all highlights with matching \a category from the scrollbar. + */ + void removeHighlights( int category ); + + /** + * Removes all highlights from the scroll bar. + */ + void removeAllHighlights(); + + private: + + QHash > mHighlights; + double mLineHeight = 0.0; + double mVisibleRange = 0.0; // in pixels + double mMargin = 0.0; // in pixels + QAbstractScrollArea *mScrollArea = nullptr; + QPointer mOverlay; +}; + +///@cond PRIVATE +#ifndef SIP_RUN +class QgsScrollBarHighlightOverlay : public QWidget +{ + Q_OBJECT + + public: + QgsScrollBarHighlightOverlay( QgsScrollBarHighlightController *scrollBarController ); + + void doResize(); + void doMove(); + void scheduleUpdate(); + + protected: + void paintEvent( QPaintEvent *paintEvent ) override; + bool eventFilter( QObject *object, QEvent *event ) override; + + private: + void drawHighlights( QPainter *painter, + int docStart, + int docSize, + double docSizeToHandleSizeRatio, + int handleOffset, + const QRect &viewport ); + void updateCache(); + QRect overlayRect() const; + QRect handleRect() const; + + // line start to line end + QMap>> mHighlightCache; + + inline QScrollBar *scrollBar() const { return mHighlightController->scrollBar(); } + QgsScrollBarHighlightController *mHighlightController = nullptr; + bool mIsCacheUpdateScheduled = true; +}; +#endif +///@endcond PRIVATE + +#endif // QGSDECORATEDSCROLLBAR_H diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index 10fff0940176a..8872997532e30 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -39,7 +39,6 @@ #include "qgspythonrunner.h" #include "qgsgeometry.h" #include "qgsfeature.h" -#include "qgsfeatureiterator.h" #include "qgsvectorlayer.h" #include "qgssettings.h" #include "qgsproject.h" @@ -49,7 +48,7 @@ #include "qgsfieldformatter.h" #include "qgsexpressionstoredialog.h" #include "qgsexpressiontreeview.h" - +#include "qgscodeeditorwidget.h" bool formatterCanProvideAvailableValues( QgsVectorLayer *layer, const QString &fieldName ) @@ -76,6 +75,13 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent ) { setupUi( this ); + txtExpressionString = new QgsCodeEditorExpression(); + QgsCodeEditorWidget *codeEditorWidget = new QgsCodeEditorWidget( txtExpressionString ); + QVBoxLayout *vl = new QVBoxLayout(); + vl->setContentsMargins( 0, 0, 0, 0 ); + vl->addWidget( codeEditorWidget ); + mExpressionEditorContainer->setLayout( vl ); + connect( btnRun, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnRun_pressed ); connect( btnNewFile, &QPushButton::clicked, this, &QgsExpressionBuilderWidget::btnNewFile_pressed ); connect( btnRemoveFile, &QPushButton::clicked, this, &QgsExpressionBuilderWidget::btnRemoveFile_pressed ); diff --git a/src/gui/qgsexpressionbuilderwidget.h b/src/gui/qgsexpressionbuilderwidget.h index c341e326229f3..73a64476cc27e 100644 --- a/src/gui/qgsexpressionbuilderwidget.h +++ b/src/gui/qgsexpressionbuilderwidget.h @@ -32,7 +32,7 @@ class QgsFields; class QgsExpressionHighlighter; class QgsRelation; - +class QgsCodeEditorExpression; /** * \ingroup gui @@ -493,6 +493,8 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp // Translated name of the user expressions group QString mUserExpressionsGroupName; + + QgsCodeEditorExpression *txtExpressionString = nullptr; }; // clazy:excludeall=qstring-allocations diff --git a/src/gui/qgsexpressionlineedit.cpp b/src/gui/qgsexpressionlineedit.cpp index 2eaeba2e79b2e..476e5d7e0c97b 100644 --- a/src/gui/qgsexpressionlineedit.cpp +++ b/src/gui/qgsexpressionlineedit.cpp @@ -19,10 +19,10 @@ #include "qgsapplication.h" #include "qgsexpressionbuilderdialog.h" #include "qgsexpressioncontextgenerator.h" -#include "qgscodeeditorsql.h" #include "qgsproject.h" #include "qgsvectorlayer.h" #include "qgsexpressioncontextutils.h" +#include "qgscodeeditorexpression.h" #include #include diff --git a/src/gui/qgsqueryresultwidget.cpp b/src/gui/qgsqueryresultwidget.cpp index 138e24c1dab4f..7fe968bb9b4f8 100644 --- a/src/gui/qgsqueryresultwidget.cpp +++ b/src/gui/qgsqueryresultwidget.cpp @@ -26,6 +26,7 @@ #include "qgshistoryentry.h" #include "qgsproviderregistry.h" #include "qgsprovidermetadata.h" +#include "qgscodeeditorwidget.h" #include #include @@ -45,6 +46,13 @@ QgsQueryResultWidget::QgsQueryResultWidget( QWidget *parent, QgsAbstractDatabase mProgressBar->hide(); + mSqlEditor = new QgsCodeEditorSQL(); + mCodeEditorWidget = new QgsCodeEditorWidget( mSqlEditor, mMessageBar ); + QVBoxLayout *vl = new QVBoxLayout(); + vl->setContentsMargins( 0, 0, 0, 0 ); + vl->addWidget( mCodeEditorWidget ); + mSqlEditorContainer->setLayout( vl ); + connect( mExecuteButton, &QPushButton::pressed, this, &QgsQueryResultWidget::executeQuery ); connect( mClearButton, &QPushButton::pressed, this, [ = ] { diff --git a/src/gui/qgsqueryresultwidget.h b/src/gui/qgsqueryresultwidget.h index 091121ed2acc1..8f1887ce94c26 100644 --- a/src/gui/qgsqueryresultwidget.h +++ b/src/gui/qgsqueryresultwidget.h @@ -28,6 +28,8 @@ #include #include +class QgsCodeEditorWidget; + ///@cond private #ifndef SIP_RUN @@ -216,6 +218,9 @@ class GUI_EXPORT QgsQueryResultWidget: public QWidget, private Ui::QgsQueryResul private: + QgsCodeEditorWidget *mCodeEditorWidget = nullptr; + QgsCodeEditorSQL *mSqlEditor = nullptr; + std::unique_ptr mConnection; std::unique_ptr mModel; std::unique_ptr mFeedback; diff --git a/src/gui/qgsrichtexteditor.cpp b/src/gui/qgsrichtexteditor.cpp index 944a5f06ff532..f6910617b88de 100644 --- a/src/gui/qgsrichtexteditor.cpp +++ b/src/gui/qgsrichtexteditor.cpp @@ -33,6 +33,7 @@ #include "qgscolorbutton.h" #include "qgscodeeditor.h" #include "qgscodeeditorhtml.h" +#include "qgscodeeditorwidget.h" #include #include @@ -59,7 +60,8 @@ QgsRichTextEditor::QgsRichTextEditor( QWidget *parent ) QVBoxLayout *sourceLayout = new QVBoxLayout(); sourceLayout->setContentsMargins( 0, 0, 0, 0 ); mSourceEdit = new QgsCodeEditorHTML(); - sourceLayout->addWidget( mSourceEdit ); + QgsCodeEditorWidget *codeEditorWidget = new QgsCodeEditorWidget( mSourceEdit ); + sourceLayout->addWidget( codeEditorWidget ); mPageSourceEdit->setLayout( sourceLayout ); mToolBar->setIconSize( QgsGuiUtils::iconSize( false ) ); diff --git a/src/gui/symbology/qgscategorizedsymbolrendererwidget.h b/src/gui/symbology/qgscategorizedsymbolrendererwidget.h index f6121b4e37cb6..f4c9420788194 100644 --- a/src/gui/symbology/qgscategorizedsymbolrendererwidget.h +++ b/src/gui/symbology/qgscategorizedsymbolrendererwidget.h @@ -112,7 +112,7 @@ class QgsCategorizedRendererViewItemDelegate: public QStyledItemDelegate * \ingroup gui * \class QgsCategorizedSymbolRendererWidget */ -class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget, private Ui::QgsCategorizedSymbolRendererWidget, private QgsExpressionContextGenerator +class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget, private Ui::QgsCategorizedSymbolRendererWidget { Q_OBJECT public: @@ -140,6 +140,7 @@ class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget, QgsFeatureRenderer *renderer() override; void setContext( const QgsSymbolWidgetContext &context ) override; void disableSymbolLevels() override SIP_SKIP; + QgsExpressionContext createExpressionContext() const override; /** * Replaces category symbols with the symbols from a style that have a matching @@ -257,8 +258,6 @@ class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget, QAction *mUnmergeCategoriesAction = nullptr; QAction *mActionLevels = nullptr; - QgsExpressionContext createExpressionContext() const override; - friend class TestQgsCategorizedRendererWidget; }; diff --git a/src/gui/symbology/qgsembeddedsymbolrendererwidget.h b/src/gui/symbology/qgsembeddedsymbolrendererwidget.h index 72348f0ded071..89ba1dc0328a5 100644 --- a/src/gui/symbology/qgsembeddedsymbolrendererwidget.h +++ b/src/gui/symbology/qgsembeddedsymbolrendererwidget.h @@ -29,7 +29,7 @@ class QgsEmbeddedSymbolRenderer; * * \since QGIS 3.20 */ -class GUI_EXPORT QgsEmbeddedSymbolRendererWidget : public QgsRendererWidget, public QgsExpressionContextGenerator, private Ui::QgsEmbeddedSymbolRendererWidgetBase +class GUI_EXPORT QgsEmbeddedSymbolRendererWidget : public QgsRendererWidget, private Ui::QgsEmbeddedSymbolRendererWidgetBase { Q_OBJECT diff --git a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h index 94acdeefd7f75..e284ae6192b0c 100644 --- a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h +++ b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h @@ -92,7 +92,7 @@ class QgsGraduatedSymbolRendererViewStyle: public QgsProxyStyle * \ingroup gui * \class QgsGraduatedSymbolRendererWidget */ -class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, private Ui::QgsGraduatedSymbolRendererWidget, private QgsExpressionContextGenerator +class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, private Ui::QgsGraduatedSymbolRendererWidget { Q_OBJECT @@ -105,6 +105,7 @@ class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, pr QgsFeatureRenderer *renderer() override; void setContext( const QgsSymbolWidgetContext &context ) override; void disableSymbolLevels() override SIP_SKIP; + QgsExpressionContext createExpressionContext() const override; public slots: void graduatedColumnChanged( const QString &field ); @@ -186,7 +187,6 @@ class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, pr SizeMode }; - QgsExpressionContext createExpressionContext() const override; void toggleMethodWidgets( MethodMode mode ); void clearParameterWidgets(); diff --git a/src/gui/symbology/qgsheatmaprendererwidget.cpp b/src/gui/symbology/qgsheatmaprendererwidget.cpp index 566ebd128d380..bdb1d01676aa9 100644 --- a/src/gui/symbology/qgsheatmaprendererwidget.cpp +++ b/src/gui/symbology/qgsheatmaprendererwidget.cpp @@ -22,6 +22,7 @@ #include "qgsstyle.h" #include "qgsproject.h" #include "qgsmapcanvas.h" +#include "qgscolorramplegendnodewidget.h" #include #include @@ -116,6 +117,7 @@ QgsHeatmapRendererWidget::QgsHeatmapRendererWidget( QgsVectorLayer *layer, QgsSt btnColorRamp->setShowGradientOnly( true ); connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsHeatmapRendererWidget::applyColorRamp ); + connect( mLegendSettingsButton, &QPushButton::clicked, this, &QgsHeatmapRendererWidget::showLegendSettings ); if ( mRenderer->colorRamp() ) { @@ -140,6 +142,9 @@ QgsHeatmapRendererWidget::QgsHeatmapRendererWidget( QgsVectorLayer *layer, QgsSt mWeightExpressionWidget->setLayer( layer ); mWeightExpressionWidget->setField( mRenderer->weightExpression() ); connect( mWeightExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsHeatmapRendererWidget::weightExpressionChanged ); + + registerDataDefinedButton( mRadiusDDBtn, QgsFeatureRenderer::Property::HeatmapRadius ); + registerDataDefinedButton( mMaximumValueDDBtn, QgsFeatureRenderer::Property::HeatmapMaximum ); } QgsHeatmapRendererWidget::~QgsHeatmapRendererWidget() = default; @@ -171,6 +176,35 @@ void QgsHeatmapRendererWidget::applyColorRamp() emit widgetChanged(); } +void QgsHeatmapRendererWidget::showLegendSettings() +{ + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) ); + if ( panel && panel->dockMode() ) + { + QgsColorRampLegendNodeWidget *legendPanel = new QgsColorRampLegendNodeWidget( nullptr, QgsColorRampLegendNodeWidget::Capabilities() ); + legendPanel->setUseContinuousRampCheckBoxVisibility( false ); + legendPanel->setPanelTitle( tr( "Legend Settings" ) ); + legendPanel->setSettings( mRenderer->legendSettings() ); + connect( legendPanel, &QgsColorRampLegendNodeWidget::widgetChanged, this, [ = ] + { + mRenderer->setLegendSettings( legendPanel->settings() ); + emit widgetChanged(); + } ); + panel->openPanel( legendPanel ); + } + else + { + QgsColorRampLegendNodeDialog dialog( mRenderer->legendSettings(), this, QgsColorRampLegendNodeWidget::Capabilities() ); + dialog.setUseContinuousRampCheckBoxVisibility( false ); + dialog.setWindowTitle( tr( "Legend Settings" ) ); + if ( dialog.exec() ) + { + mRenderer->setLegendSettings( dialog.settings() ); + emit widgetChanged(); + } + } +} + void QgsHeatmapRendererWidget::mRadiusUnitWidget_changed() { if ( !mRenderer ) diff --git a/src/gui/symbology/qgsheatmaprendererwidget.h b/src/gui/symbology/qgsheatmaprendererwidget.h index 1f0681012096b..08c8b256b895b 100644 --- a/src/gui/symbology/qgsheatmaprendererwidget.h +++ b/src/gui/symbology/qgsheatmaprendererwidget.h @@ -27,7 +27,7 @@ class QgsHeatmapRenderer; * \ingroup gui * \class QgsHeatmapRendererWidget */ -class GUI_EXPORT QgsHeatmapRendererWidget : public QgsRendererWidget, private Ui::QgsHeatmapRendererWidgetBase, private QgsExpressionContextGenerator +class GUI_EXPORT QgsHeatmapRendererWidget : public QgsRendererWidget, private Ui::QgsHeatmapRendererWidgetBase { Q_OBJECT @@ -52,15 +52,15 @@ class GUI_EXPORT QgsHeatmapRendererWidget : public QgsRendererWidget, private Ui QgsFeatureRenderer *renderer() override; void setContext( const QgsSymbolWidgetContext &context ) override; + QgsExpressionContext createExpressionContext() const override; private: std::unique_ptr< QgsHeatmapRenderer > mRenderer; - QgsExpressionContext createExpressionContext() const override; - private slots: void applyColorRamp(); + void showLegendSettings(); void mRadiusUnitWidget_changed(); void mRadiusSpinBox_valueChanged( double d ); void mMaxSpinBox_valueChanged( double d ); diff --git a/src/gui/symbology/qgspointclusterrendererwidget.h b/src/gui/symbology/qgspointclusterrendererwidget.h index d5ca52383daf3..d6997b2a6a5ec 100644 --- a/src/gui/symbology/qgspointclusterrendererwidget.h +++ b/src/gui/symbology/qgspointclusterrendererwidget.h @@ -32,7 +32,7 @@ class QgsPointClusterRenderer; * \brief A widget which allows configuration of the properties for a QgsPointClusterRenderer. */ -class GUI_EXPORT QgsPointClusterRendererWidget: public QgsRendererWidget, public QgsExpressionContextGenerator, private Ui::QgsPointClusterRendererWidgetBase +class GUI_EXPORT QgsPointClusterRendererWidget: public QgsRendererWidget, private Ui::QgsPointClusterRendererWidgetBase { Q_OBJECT diff --git a/src/gui/symbology/qgspointdisplacementrendererwidget.h b/src/gui/symbology/qgspointdisplacementrendererwidget.h index 60436aebfa5c5..cc086a65c8575 100644 --- a/src/gui/symbology/qgspointdisplacementrendererwidget.h +++ b/src/gui/symbology/qgspointdisplacementrendererwidget.h @@ -21,7 +21,6 @@ #include "ui_qgspointdisplacementrendererwidgetbase.h" #include "qgis_sip.h" #include "qgsrendererwidget.h" -#include "qgsexpressioncontextgenerator.h" #include "qgis_gui.h" class QgsPointDisplacementRenderer; @@ -30,7 +29,7 @@ class QgsPointDisplacementRenderer; * \ingroup gui * \class QgsPointDisplacementRendererWidget */ -class GUI_EXPORT QgsPointDisplacementRendererWidget: public QgsRendererWidget, public QgsExpressionContextGenerator, private Ui::QgsPointDisplacementRendererWidgetBase +class GUI_EXPORT QgsPointDisplacementRendererWidget: public QgsRendererWidget, private Ui::QgsPointDisplacementRendererWidgetBase { Q_OBJECT public: diff --git a/src/gui/symbology/qgsrendererwidget.cpp b/src/gui/symbology/qgsrendererwidget.cpp index 44cbcfe8bf0e1..d7bc2c63e602a 100644 --- a/src/gui/symbology/qgsrendererwidget.cpp +++ b/src/gui/symbology/qgsrendererwidget.cpp @@ -314,6 +314,14 @@ void QgsRendererWidget::copySymbol() QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( symbolList.at( 0 ) ) ); } +void QgsRendererWidget::updateDataDefinedProperty() +{ + QgsPropertyOverrideButton *button = qobject_cast( sender() ); + const QgsFeatureRenderer::Property key = static_cast< QgsFeatureRenderer::Property >( button->propertyKey() ); + renderer()->setDataDefinedProperty( key, button->toProperty() ); + emit widgetChanged(); +} + void QgsRendererWidget::showSymbolLevelsDialog( QgsFeatureRenderer *r ) { QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); @@ -388,6 +396,50 @@ void QgsRendererWidget::setSymbolLevels( const QList< QgsLegendSymbolItem > &, b } +void QgsRendererWidget::registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsFeatureRenderer::Property key ) +{ + // note that we don't specify the layer here -- we don't want to expose a choice of fields for renderer level buttons, + // as the settings apply to the WHOLE layer and aren't evaluated on a feature-by-feature basis + button->init( static_cast< int >( key ), renderer()->dataDefinedProperties(), QgsFeatureRenderer::propertyDefinitions(), nullptr, true ); + connect( button, &QgsPropertyOverrideButton::changed, this, &QgsRendererWidget::updateDataDefinedProperty ); + + button->registerExpressionContextGenerator( this ); +} + +QgsExpressionContext QgsRendererWidget::createExpressionContext() const +{ + if ( auto *lExpressionContext = mContext.expressionContext() ) + return *lExpressionContext; + + QgsExpressionContext expContext( mContext.globalProjectAtlasMapLayerScopes( vectorLayer() ) ); + + // additional scopes + const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes(); + for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes ) + { + expContext.appendScope( new QgsExpressionContextScope( scope ) ); + } + + //TODO - show actual value + expContext.setOriginalValueVariable( QVariant() ); + + QStringList highlights; + highlights << QgsExpressionContext::EXPR_ORIGINAL_VALUE; + + if ( expContext.hasVariable( QStringLiteral( "zoom_level" ) ) ) + { + highlights << QStringLiteral( "zoom_level" ); + } + if ( expContext.hasVariable( QStringLiteral( "vector_tile_zoom" ) ) ) + { + highlights << QStringLiteral( "vector_tile_zoom" ); + } + + expContext.setHighlightedVariables( highlights ); + + return expContext; +} + // // QgsDataDefinedValueDialog // diff --git a/src/gui/symbology/qgsrendererwidget.h b/src/gui/symbology/qgsrendererwidget.h index 0882691ccd00d..96789bd82b5d7 100644 --- a/src/gui/symbology/qgsrendererwidget.h +++ b/src/gui/symbology/qgsrendererwidget.h @@ -20,15 +20,17 @@ #include #include "qgspanelwidget.h" #include "qgssymbolwidgetcontext.h" +#include "qgsrenderer.h" +#include "qgsexpressioncontextgenerator.h" class QgsDataDefinedSizeLegend; class QgsDataDefinedSizeLegendWidget; class QgsVectorLayer; class QgsStyle; -class QgsFeatureRenderer; class QgsMapCanvas; class QgsMarkerSymbol; class QgsLegendSymbolItem; +class QgsPropertyOverrideButton; /** * \ingroup gui @@ -42,11 +44,12 @@ class QgsLegendSymbolItem; * - on any change of renderer type, create some default (dummy?) version and change the stacked widget * - when clicked OK/Apply, get the renderer from active widget and clone it for the layer */ -class GUI_EXPORT QgsRendererWidget : public QgsPanelWidget +class GUI_EXPORT QgsRendererWidget : public QgsPanelWidget, public QgsExpressionContextGenerator { Q_OBJECT public: QgsRendererWidget( QgsVectorLayer *layer, QgsStyle *style ); + QgsExpressionContext createExpressionContext() const override; //! Returns pointer to the renderer (no transfer of ownership) virtual QgsFeatureRenderer *renderer() = 0; @@ -151,6 +154,13 @@ class GUI_EXPORT QgsRendererWidget : public QgsPanelWidget */ virtual void setSymbolLevels( const QList< QgsLegendSymbolItem > &levels, bool enabled ); + /** + * Registers a data defined override button. Handles setting up connections + * for the button and initializing the button to show the correct descriptions + * and help text for the associated property. + */ + void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsFeatureRenderer::Property key ); + protected slots: void contextMenuViewCategories( QPoint p ); //! Change color of selected symbols @@ -180,6 +190,7 @@ class GUI_EXPORT QgsRendererWidget : public QgsPanelWidget private slots: void copySymbol(); + void updateDataDefinedProperty(); private: diff --git a/src/gui/vector/qgsattributesformproperties.cpp b/src/gui/vector/qgsattributesformproperties.cpp index 9a68e2d7c7267..4930f403838ea 100644 --- a/src/gui/vector/qgsattributesformproperties.cpp +++ b/src/gui/vector/qgsattributesformproperties.cpp @@ -302,6 +302,7 @@ void QgsAttributesFormProperties::loadAttributeTypeDialog() mAttributeTypeDialog->setUnique( constraints.constraints() & QgsFieldConstraints::ConstraintUnique ); mAttributeTypeDialog->setUniqueEnforced( constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) == QgsFieldConstraints::ConstraintStrengthHard ); mAttributeTypeDialog->setSplitPolicy( cfg.mSplitPolicy ); + mAttributeTypeDialog->setDuplicatePolicy( cfg.mDuplicatePolicy ); QgsFieldConstraints::Constraints providerConstraints = QgsFieldConstraints::Constraints(); if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider ) @@ -384,8 +385,9 @@ void QgsAttributesFormProperties::storeAttributeTypeDialog() cfg.mEditorWidgetType = mAttributeTypeDialog->editorWidgetType(); cfg.mEditorWidgetConfig = mAttributeTypeDialog->editorWidgetConfig(); cfg.mSplitPolicy = mAttributeTypeDialog->splitPolicy(); - const int fieldIndex = mAttributeTypeDialog->fieldIdx(); + cfg.mDuplicatePolicy = mAttributeTypeDialog->duplicatePolicy(); + const int fieldIndex = mAttributeTypeDialog->fieldIdx(); mLayer->setDefaultValueDefinition( fieldIndex, QgsDefaultValue( mAttributeTypeDialog->defaultValueExpression(), mAttributeTypeDialog->applyDefaultValueOnUpdate() ) ); const QString fieldName = mLayer->fields().at( fieldIndex ).name(); @@ -1014,6 +1016,7 @@ void QgsAttributesFormProperties::apply() mLayer->setFieldAlias( idx, cfg.mAlias ); mLayer->setFieldSplitPolicy( idx, cfg.mSplitPolicy ); + mLayer->setFieldDuplicatePolicy( idx, cfg.mDuplicatePolicy ); } // tabs and groups @@ -1086,6 +1089,7 @@ QgsAttributesFormProperties::FieldConfig::FieldConfig( QgsVectorLayer *layer, in mEditorWidgetType = setup.type(); mEditorWidgetConfig = setup.config(); mSplitPolicy = layer->fields().at( idx ).splitPolicy(); + mDuplicatePolicy = layer->fields().at( idx ).duplicatePolicy(); } QgsAttributesFormProperties::FieldConfig::operator QVariant() diff --git a/src/gui/vector/qgsattributesformproperties.h b/src/gui/vector/qgsattributesformproperties.h index bcf7bde2e1483..4eef017a99050 100644 --- a/src/gui/vector/qgsattributesformproperties.h +++ b/src/gui/vector/qgsattributesformproperties.h @@ -338,6 +338,7 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress QgsPropertyCollection mDataDefinedProperties; QString mComment; Qgis::FieldDomainSplitPolicy mSplitPolicy = Qgis::FieldDomainSplitPolicy::Duplicate; + Qgis::FieldDuplicatePolicy mDuplicatePolicy = Qgis::FieldDuplicatePolicy::Duplicate; operator QVariant(); }; diff --git a/src/native/CMakeLists.txt b/src/native/CMakeLists.txt index cedc517814a18..73edb952b1ad8 100644 --- a/src/native/CMakeLists.txt +++ b/src/native/CMakeLists.txt @@ -144,7 +144,7 @@ if (UNIX AND NOT APPLE AND NOT ANDROID) target_link_libraries(qgis_native ${QT_VERSION_BASE}::DBus) endif() -if (MSVC) +if (MSVC AND NOT BUILD_WITH_QT6) find_package(${QT_VERSION_BASE}WinExtras) target_link_libraries(qgis_native shell32) diff --git a/src/native/win/qgswinnative.cpp b/src/native/win/qgswinnative.cpp index 008c207a35aaf..dbc83f6819b29 100644 --- a/src/native/win/qgswinnative.cpp +++ b/src/native/win/qgswinnative.cpp @@ -23,16 +23,24 @@ #include #include #include +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #include #include #include #include +#endif #include "wintoastlib.h" #include #include #include +#ifdef UNICODE +#define _T(x) L##x +#else +#define _T(x) x +#endif + struct LPITEMIDLISTDeleter { @@ -57,6 +65,7 @@ void QgsWinNative::initializeMainWindow( QWindow *window, const QString &version ) { mWindow = window; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if ( mTaskButton ) return; // already initialized! @@ -64,6 +73,7 @@ void QgsWinNative::initializeMainWindow( QWindow *window, mTaskButton->setWindow( window ); mTaskProgress = mTaskButton->progress(); mTaskProgress->setVisible( false ); +#endif QString appName = qgetenv( "QGIS_WIN_APP_NAME" ); if ( appName.isEmpty() ) @@ -126,7 +136,7 @@ void QgsWinNative::showFileProperties( const QString &path ) info.nShow = SW_SHOWNORMAL; info.fMask = SEE_MASK_INVOKEIDLIST; info.lpIDList = pidl.get(); - info.lpVerb = "properties"; + info.lpVerb = _T( "properties" ); ShellExecuteEx( &info ); } @@ -134,24 +144,31 @@ void QgsWinNative::showFileProperties( const QString &path ) void QgsWinNative::showUndefinedApplicationProgress() { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) mTaskProgress->setMaximum( 0 ); mTaskProgress->show(); +#endif } void QgsWinNative::setApplicationProgress( double progress ) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) mTaskProgress->setMaximum( 100 ); mTaskProgress->show(); mTaskProgress->setValue( static_cast< int >( std::round( progress ) ) ); +#endif } void QgsWinNative::hideApplicationProgress() { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) mTaskProgress->hide(); +#endif } void QgsWinNative::onRecentProjectsChanged( const std::vector &recentProjects ) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QWinJumpList jumplist; jumplist.recent()->clear(); for ( const RecentProjectProperties &recentProject : recentProjects ) @@ -163,6 +180,7 @@ void QgsWinNative::onRecentProjectsChanged( const std::vectorsetArguments( QStringList( recentProject.path ) ); jumplist.recent()->addItem( newProject ); } +#endif } class NotificationHandler : public WinToastLib::IWinToastHandler @@ -228,7 +246,11 @@ bool QgsWinNative::openTerminalAtPath( const QString &path ) return process.startDetached( &pid ); } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) bool QgsWinNativeEventFilter::nativeEventFilter( const QByteArray &eventType, void *message, long * ) +#else +bool QgsWinNativeEventFilter::nativeEventFilter( const QByteArray &eventType, void *message, qintptr * ) +#endif { static const QByteArray sWindowsGenericMSG{ "windows_generic_MSG" }; if ( !message || eventType != sWindowsGenericMSG ) diff --git a/src/native/win/qgswinnative.h b/src/native/win/qgswinnative.h index 9f0e7479d144e..c679029981289 100644 --- a/src/native/win/qgswinnative.h +++ b/src/native/win/qgswinnative.h @@ -15,8 +15,8 @@ * * ***************************************************************************/ -#ifndef QGSMACNATIVE_H -#define QGSMACNATIVE_H +#ifndef QGSWINNATIVE_H +#define QGSWINNATIVE_H #include "qgsnative.h" #include @@ -25,8 +25,10 @@ #include #pragma comment(lib,"Shell32.lib") +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) class QWinTaskbarButton; class QWinTaskbarProgress; +#endif class QWindow; @@ -35,7 +37,11 @@ class QgsWinNativeEventFilter : public QObject, public QAbstractNativeEventFilte Q_OBJECT public: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) bool nativeEventFilter( const QByteArray &eventType, void *message, long * ) override; +#else + bool nativeEventFilter( const QByteArray &eventType, void *message, qintptr *result ) override; +#endif signals: @@ -70,10 +76,12 @@ class NATIVE_EXPORT QgsWinNative : public QgsNative QWindow *mWindow = nullptr; Capabilities mCapabilities = NativeFilePropertiesDialog | NativeOpenTerminalAtPath; bool mWinToastInitialized = false; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QWinTaskbarButton *mTaskButton = nullptr; QWinTaskbarProgress *mTaskProgress = nullptr; +#endif QgsWinNativeEventFilter *mNativeEventFilter = nullptr; }; -#endif // QGSMACNATIVE_H +#endif // QGSWINNATIVE_H diff --git a/src/providers/grass/qgsgrass.cpp b/src/providers/grass/qgsgrass.cpp index eb8585c112e3e..946af31a1ded9 100644 --- a/src/providers/grass/qgsgrass.cpp +++ b/src/providers/grass/qgsgrass.cpp @@ -270,8 +270,8 @@ QString QgsGrass::pathSeparator() #include QString QgsGrass::shortPath( const QString &path ) { - TCHAR buf[MAX_PATH]; - int len = GetShortPathName( path.toUtf8().constData(), buf, MAX_PATH ); + char buf[MAX_PATH]; + int len = GetShortPathNameA( path.toUtf8().constData(), buf, MAX_PATH ); if ( len == 0 || len > MAX_PATH ) { diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index e48859dcffa08..c964d57e0ea24 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -1492,113 +1492,110 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities() mEnabledCapabilities = QgsVectorDataProvider::Capability::ReloadData; + QString sql; QgsPostgresResult testAccess; + + bool forceReadOnly = ( mReadFlags & QgsDataProvider::ForceReadOnly ); + bool inRecovery = false; + sql = QStringLiteral( "SELECT " + "has_table_privilege(%1,'SELECT')," // 0 + "pg_is_in_recovery()," // 1 + "current_schema(), " // 2 + "has_table_privilege(%1,'INSERT')," // 3 + "has_table_privilege(%1,'DELETE')" ) // 4 + .arg( quotedValue( mQuery ) ); + if ( !mIsQuery ) { - // Check that we can read from the table (i.e., we have select permission). - QString sql = QStringLiteral( "SELECT * FROM %1 LIMIT 1" ).arg( mQuery ); - QgsPostgresResult testAccess( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); + + // postgres has fast access to features at id (thanks to primary key / unique index) + // the latter flag is here just for compatibility + if ( !mSelectAtIdDisabled ) + { + mEnabledCapabilities |= QgsVectorDataProvider::SelectAtId; + } + + if ( connectionRO()->pgVersion() >= 80400 ) + { + sql += QString( ",has_any_column_privilege(%1,'UPDATE')" // 5 + ",%2" ) // 6 + .arg( quotedValue( mQuery ), + mGeometryColumn.isNull() + ? QStringLiteral( "'f'" ) + : QStringLiteral( "has_column_privilege(%1,%2,'UPDATE')" ) + .arg( quotedValue( mQuery ), + quotedValue( mGeometryColumn ) ) + ); + } + else + { + sql += QString( ",has_table_privilege(%1,'UPDATE')" // 5 + ",has_table_privilege(%1,'UPDATE')" ) // 6 + .arg( quotedValue( mQuery ) ); + } + + testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK ) { - QgsMessageLog::logMessage( tr( "Unable to access the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" ) + QgsMessageLog::logMessage( tr( "Unable to determine table access privileges for the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" ) .arg( mQuery, testAccess.PQresultErrorMessage(), - sql ), tr( "PostGIS" ) ); + sql ), + tr( "PostGIS" ) ); return false; } - bool forceReadOnly = ( mReadFlags & QgsDataProvider::ForceReadOnly ); - bool inRecovery = false; - // Check if the database is still in recovery after a database crash - // or if you are connected to a (read-only) standby server - // only if the provider has not been force to be in read-only mode - if ( !forceReadOnly && connectionRO()->pgVersion() >= 90000 ) + if ( testAccess.PQgetvalue( 0, 0 ) != QLatin1String( "t" ) ) { - testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", QStringLiteral( "SELECT pg_is_in_recovery()" ) ); - if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK || testAccess.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) ) - { - QgsMessageLog::logMessage( tr( "PostgreSQL is still in recovery after a database crash\n(or you are connected to a (read-only) standby server).\nWrite accesses will be denied." ), tr( "PostGIS" ) ); - inRecovery = true; - } + // SELECT + QgsMessageLog::logMessage( tr( "User has no SELECT privilege on %1 relation." ) + .arg( mQuery ), tr( "PostGIS" ) ); + return false; } - // postgres has fast access to features at id (thanks to primary key / unique index) - // the latter flag is here just for compatibility - if ( !mSelectAtIdDisabled ) + if ( testAccess.PQgetvalue( 0, 1 ) == QLatin1String( "t" ) ) { - mEnabledCapabilities |= QgsVectorDataProvider::SelectAtId; + // RECOVERY + QgsMessageLog::logMessage( + tr( "PostgreSQL is still in recovery after a database crash\n(or you are connected to a (read-only) standby server).\nWrite accesses will be denied." ), + tr( "PostGIS" ) + ); + inRecovery = true; } - // Do not check the editable capabilities if the provider has been forced to be + // CURRENT SCHEMA + if ( mSchemaName.isEmpty() ) + mSchemaName = testAccess.PQgetvalue( 0, 2 ); + + // Do not set editable capabilities if the provider has been forced to be // in read-only mode or if the database is still in recovery if ( !forceReadOnly && !inRecovery ) { - if ( connectionRO()->pgVersion() >= 80400 ) - { - sql = QString( "SELECT " - "has_table_privilege(%1,'DELETE')," - "has_any_column_privilege(%1,'UPDATE')," - "%2" - "has_table_privilege(%1,'INSERT')," - "current_schema()" ) - .arg( quotedValue( mQuery ), - mGeometryColumn.isNull() - ? QStringLiteral( "'f'," ) - : QStringLiteral( "has_column_privilege(%1,%2,'UPDATE')," ) - .arg( quotedValue( mQuery ), - quotedValue( mGeometryColumn ) ) - ); - } - else - { - sql = QString( "SELECT " - "has_table_privilege(%1,'DELETE')," - "has_table_privilege(%1,'UPDATE')," - "has_table_privilege(%1,'UPDATE')," - "has_table_privilege(%1,'INSERT')," - "current_schema()" ) - .arg( quotedValue( mQuery ) ); - } - - testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); - if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK ) + if ( testAccess.PQgetvalue( 0, 3 ) == QLatin1String( "t" ) ) { - QgsMessageLog::logMessage( tr( "Unable to determine table access privileges for the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" ) - .arg( mQuery, - testAccess.PQresultErrorMessage(), - sql ), - tr( "PostGIS" ) ); - return false; + // INSERT + mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures; } - - if ( testAccess.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) ) + if ( testAccess.PQgetvalue( 0, 4 ) == QLatin1String( "t" ) ) { // DELETE mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::FastTruncate; } - if ( testAccess.PQgetvalue( 0, 1 ) == QLatin1String( "t" ) ) + if ( testAccess.PQgetvalue( 0, 5 ) == QLatin1String( "t" ) ) { // UPDATE mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues; } - if ( testAccess.PQgetvalue( 0, 2 ) == QLatin1String( "t" ) ) + if ( testAccess.PQgetvalue( 0, 6 ) == QLatin1String( "t" ) ) { - // UPDATE + // UPDATE (geom column specific) mEnabledCapabilities |= QgsVectorDataProvider::ChangeGeometries; } - if ( testAccess.PQgetvalue( 0, 3 ) == QLatin1String( "t" ) ) - { - // INSERT - mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures; - } - - if ( mSchemaName.isEmpty() ) - mSchemaName = testAccess.PQgetvalue( 0, 4 ); - + // TODO: merge this with the previous query sql = QString( "SELECT 1 FROM pg_class,pg_namespace WHERE " "pg_class.relnamespace=pg_namespace.oid AND " "%3 AND " @@ -3975,8 +3972,8 @@ QgsBox3D QgsPostgresProvider::extent3D() const { QgsDebugMsgLevel( QStringLiteral( "Got extents (%1) using: %2" ).arg( ext ).arg( sql ), 2 ); - const thread_local QRegularExpression rx( "\\((.+) (.+) (.+),(.+) (.+) (.+)\\)" ); - const QRegularExpressionMatch match = rx.match( ext ); + const thread_local QRegularExpression rx3d( "\\((.+) (.+) (.+),(.+) (.+) (.+)\\)" ); + const QRegularExpressionMatch match = rx3d.match( ext ); if ( match.hasMatch() ) { mLayerExtent.setXMinimum( match.captured( 1 ).toDouble() ); @@ -3994,7 +3991,19 @@ QgsBox3D QgsPostgresProvider::extent3D() const } else { - QgsMessageLog::logMessage( tr( "result of extents query invalid: %1" ).arg( ext ), tr( "PostGIS" ) ); + const thread_local QRegularExpression rx2d( "\\((.+) (.+),(.+) (.+)\\)" ); + const QRegularExpressionMatch match = rx2d.match( ext ); + if ( match.hasMatch() ) + { + mLayerExtent.setXMinimum( match.captured( 1 ).toDouble() ); + mLayerExtent.setYMinimum( match.captured( 2 ).toDouble() ); + mLayerExtent.setXMaximum( match.captured( 3 ).toDouble() ); + mLayerExtent.setYMaximum( match.captured( 4 ).toDouble() ); + } + else + { + QgsMessageLog::logMessage( tr( "result of extents query invalid: %1" ).arg( ext ), tr( "PostGIS" ) ); + } } } diff --git a/src/server/qgis_mapserver.cpp b/src/server/qgis_mapserver.cpp index 38d9fe5dcb7ce..1ed9042085e09 100644 --- a/src/server/qgis_mapserver.cpp +++ b/src/server/qgis_mapserver.cpp @@ -177,7 +177,7 @@ class TcpServerWorker: public QObject }; // This will delete the connection - QTcpSocket::connect( clientConnection, &QAbstractSocket::disconnected, clientConnection, connectionDeleter, Qt::QueuedConnection ); + QObject::connect( clientConnection, &QAbstractSocket::disconnected, clientConnection, connectionDeleter, Qt::QueuedConnection ); #if 0 // Debugging output clientConnection->connect( clientConnection, &QAbstractSocket::errorOccurred, clientConnection, [ = ]( QAbstractSocket::SocketError socketError ) @@ -187,7 +187,7 @@ class TcpServerWorker: public QObject #endif // Incoming connection parser - QTcpSocket::connect( clientConnection, &QIODevice::readyRead, context, [ = ] { + QObject::connect( clientConnection, &QIODevice::readyRead, context, [ = ] { // Read all incoming data while ( clientConnection->bytesAvailable() > 0 ) diff --git a/src/server/services/wms/qgswmsrestorer.cpp b/src/server/services/wms/qgswmsrestorer.cpp index 9ccd804e849b0..6094f46d440fb 100644 --- a/src/server/services/wms/qgswmsrestorer.cpp +++ b/src/server/services/wms/qgswmsrestorer.cpp @@ -58,7 +58,7 @@ QgsLayerRestorer::QgsLayerRestorer( const QList &layers ) { QgsRasterLayer *rLayer = qobject_cast( layer ); - if ( rLayer ) + if ( rLayer && rLayer->renderer() ) { settings.mOpacity = rLayer->renderer()->opacity(); } @@ -119,7 +119,7 @@ QgsLayerRestorer::~QgsLayerRestorer() { QgsRasterLayer *rLayer = qobject_cast( layer ); - if ( rLayer ) + if ( rLayer && rLayer->renderer() ) { rLayer->renderer()->setOpacity( settings.mOpacity ); } diff --git a/src/ui/attributeformconfig/qgsattributetypeedit.ui b/src/ui/attributeformconfig/qgsattributetypeedit.ui index 31bdb54180b42..7c46ca9b4f3ab 100644 --- a/src/ui/attributeformconfig/qgsattributetypeedit.ui +++ b/src/ui/attributeformconfig/qgsattributetypeedit.ui @@ -149,7 +149,7 @@ - + Qt::StrongFocus @@ -283,10 +283,20 @@ Policies - - + + + + + + When duplicating features + + + + + + @@ -294,7 +304,17 @@ - + + + + TextLabel + + + true + + + + TextLabel diff --git a/src/ui/layout/qgslayoutmapwidgetbase.ui b/src/ui/layout/qgslayoutmapwidgetbase.ui index 5a667e478743d..1cb6df2b49cae 100644 --- a/src/ui/layout/qgslayoutmapwidgetbase.ui +++ b/src/ui/layout/qgslayoutmapwidgetbase.ui @@ -88,9 +88,9 @@ 0 - -123 - 548 - 1478 + -798 + 539 + 1640 @@ -752,6 +752,20 @@ + + + + Duplicate selected grid + + + + + + + :/images/themes/default/mActionDuplicateLayout.svg:/images/themes/default/mActionDuplicateLayout.svg + + + diff --git a/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui b/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui index 5dbb914e0da02..cf4175997d394 100644 --- a/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui +++ b/src/ui/mesh/qgsmeshelevationpropertieswidgetbase.ui @@ -36,6 +36,143 @@ + + + + Profile Chart Appearance + + + + + + + + + Style + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Line style + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Limit + + + + + + + Fill style + + + + + + + 6 + + + -99999.000000000000000 + + + 99999.000000000000000 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -48,7 +185,7 @@ QFrame::NoFrame - 1 + 2 @@ -198,145 +335,68 @@ - - - - - - Profile Chart Appearance - - - - - - - - - Style - - - - - - - - 0 - 0 - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Line style - - - - - - - - 0 - 0 - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Limit - - - - - - - Fill style - - - - - - - 6 - - - -99999.000000000000000 - - - 99999.000000000000000 - - - - - - - - 0 - 0 - - - - - - - - + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Each group in the mesh layer is associated with a fixed elevation range.</span></p><p>This mode can be used when a layer has elevation data exposed through different dataset groups.</p></body></html> + + + true + - - - + + + + + + + + 0 + + + + + Qt::Horizontal + + + + 494 + 20 + + + + + + + + ... + + + + :/images/themes/default/mIconExpression.svg:/images/themes/default/mIconExpression.svg + + + QToolButton::MenuButtonPopup + + + false + + + + + + + - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -357,6 +417,8 @@ 1 - + + + diff --git a/src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui b/src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui index f086cfcd5712c..1e217dc743d22 100644 --- a/src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui +++ b/src/ui/mesh/qgsmeshrenderervectorsettingswidgetbase.ui @@ -6,8 +6,8 @@ 0 0 - 399 - 861 + 426 + 1006 @@ -26,20 +26,7 @@ 0 - - - - - 120 - 0 - - - - - - - - + Qt::Vertical @@ -52,94 +39,65 @@ - - - - Traces + + + + + + Filter by magnitude + + + + + + + Max + + + + + + + + 0 + 0 + + + + -1000000000000000.000000000000000 + + + 1000000000000000.000000000000000 + + + + + + + Min + + + + + + + -1000000000000000.000000000000000 + + + 1000000000000000.000000000000000 + + + + + + + + + + 120 + 0 + - - - QLayout::SetDefaultConstraint - - - - - 0 - - - - - Particles count - - - - - - - 1000000 - - - 100 - - - 1000 - - - - - - - - - 0 - - - - - Max tail length - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - 1.000000000000000 - - - 99999999999999.000000000000000 - - - 10.000000000000000 - - - 10.000000000000000 - - - - - - - - - @@ -231,29 +189,17 @@ - - - - Coloring Method - - + + - - - - - 0 - - - 0 - - - 0 - - - 0 - - + + + + + 0 + 0 + + @@ -273,58 +219,94 @@ Traces + + + Wind Barbs + + - - - - - - Filter by magnitude - - - - - - - Max - - - - - - - - 0 - 0 - - - - -1000000000000000.000000000000000 - - - 1000000000000000.000000000000000 - - - - - - - Min - - - - - - - -1000000000000000.000000000000000 - - - 1000000000000000.000000000000000 - - - - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Head Options + + + + + + % of shaft length + + + + + + + 100.000000000000000 + + + + + + + + 0 + 0 + + + + 100.000000000000000 + + + + + + + % of shaft length + + + + + + + Length + + + + + + + Width + + + + + + + + + + Coloring Method + + @@ -376,20 +358,6 @@ - - - - Symbology - - - - - - - Line width - - - @@ -457,77 +425,24 @@ - - - - Head Options + + + + Symbology - - - - - % of shaft length - - - - - - - 100.000000000000000 - - - - - - - - 0 - 0 - - - - 100.000000000000000 - - - - - - - % of shaft length - - - - - - - Length - - - - - - - Width - - - - - - + + - Color + Line width - - - - - 0 - 0 - + + + + Color @@ -670,6 +585,184 @@ + + + + Traces + + + + QLayout::SetDefaultConstraint + + + + + 0 + + + + + Particles count + + + + + + + 1000000 + + + 100 + + + 1000 + + + + + + + + + 0 + + + + + Max tail length + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 1.000000000000000 + + + 99999999999999.000000000000000 + + + 10.000000000000000 + + + 10.000000000000000 + + + + + + + + + + + + + + + Wind Barbs + + + + + + Length + + + + + + + Data units + + + + + + + Select the units the data are in.<br>Values are converted to knots for rendering the wind barbs. + + + + m/s + + + + + km/h + + + + + knots + + + + + mi/h + + + + + ft/s + + + + + other units + + + + + + + + Multiplier + + + + + + + Data will be multiplied by this value to be converted to knots (nautical miles per hour) + + + + + + + + + This defines the shaft length.<br>The pennants and barbs are scaled proportionally. + + + + + + + + + + + diff --git a/src/ui/processing/qgsmodeldesignerdialogbase.ui b/src/ui/processing/qgsmodeldesignerdialogbase.ui index b143baa2718cb..a4508b75bfcb9 100644 --- a/src/ui/processing/qgsmodeldesignerdialogbase.ui +++ b/src/ui/processing/qgsmodeldesignerdialogbase.ui @@ -60,6 +60,8 @@ + + @@ -440,7 +442,7 @@ Open Model… - Open model (Ctrl+O) + Open model Ctrl+O @@ -455,7 +457,7 @@ Save Model - Save model (Ctrl+S) + Save model Ctrl+S @@ -470,7 +472,7 @@ Save Model as… - Save model as (Ctrl+S) + Save model as Ctrl+Shift+S @@ -605,7 +607,7 @@ Run Model… - Run model (F5) + Run model F5 @@ -765,6 +767,21 @@ Sets the order for adding layers generated by the model to projects + + + + :/images/themes/default/mActionRunSelected.svg:/images/themes/default/mActionRunSelected.svg + + + Run Selected Steps… + + + Run only the selected steps in the model + + + Shift+F5 + + diff --git a/src/ui/qgscodeditorsettings.ui b/src/ui/qgscodeditorsettings.ui index f515bce2d53c4..d362d86c95be7 100644 --- a/src/ui/qgscodeditorsettings.ui +++ b/src/ui/qgscodeditorsettings.ui @@ -244,8 +244,8 @@ 0 0 - 663 - 549 + 677 + 531 @@ -825,6 +825,20 @@ + + + + + + + + + + + Search result + + + diff --git a/src/ui/qgscolorramplegendnodewidgetbase.ui b/src/ui/qgscolorramplegendnodewidgetbase.ui index b3eff07544c46..72d45bbc89601 100644 --- a/src/ui/qgscolorramplegendnodewidgetbase.ui +++ b/src/ui/qgscolorramplegendnodewidgetbase.ui @@ -7,7 +7,7 @@ 0 0 359 - 368 + 399 @@ -78,7 +78,7 @@ - + Prefix @@ -92,7 +92,7 @@ - + Suffix @@ -106,7 +106,7 @@ - + Number format diff --git a/src/ui/qgsexpressionbuilder.ui b/src/ui/qgsexpressionbuilder.ui index 4b1a8b7180c30..6469220c56800 100644 --- a/src/ui/qgsexpressionbuilder.ui +++ b/src/ui/qgsexpressionbuilder.ui @@ -220,7 +220,7 @@ - + @@ -881,12 +881,6 @@ Saved scripts are auto loaded on QGIS startup. QLineEdit
qgsfilterlineedit.h
- - QgsCodeEditorExpression - QWidget -
qgscodeeditorexpression.h
- 1 -
QgsCodeEditorPython QWidget diff --git a/src/ui/qgsqueryresultwidgetbase.ui b/src/ui/qgsqueryresultwidgetbase.ui index d7536c3140963..9a7c54c9a874d 100644 --- a/src/ui/qgsqueryresultwidgetbase.ui +++ b/src/ui/qgsqueryresultwidgetbase.ui @@ -18,7 +18,7 @@
- + diff --git a/src/ui/symbollayer/qgsheatmaprendererwidgetbase.ui b/src/ui/symbollayer/qgsheatmaprendererwidgetbase.ui index b92dc98f27fcd..a94898d8d1043 100644 --- a/src/ui/symbollayer/qgsheatmaprendererwidgetbase.ui +++ b/src/ui/symbollayer/qgsheatmaprendererwidgetbase.ui @@ -1,20 +1,27 @@ QgsHeatmapRendererWidgetBase - + 0 0 - 323 + 334 386 Form - - + + + + + Maximum value + + + + Qt::Vertical @@ -27,6 +34,20 @@ + + + + Color ramp + + + + + + + Rendering quality + + + @@ -56,70 +77,54 @@ - - + + - Rendering quality + Weight points by - - + + - - - <html><head/><body><p><span style=" font-style:italic;">Best</span></p></body></html> - - - Qt::RichText + + + + 1 + 0 + - - - - - - 1 + + 6 - 5 - - - 1 - - - Qt::Horizontal - - - false - - - false + 99999999.000000000000000 - - QSlider::TicksBelow + + 0.200000000000000 - - - <html><head/><body><p><span style=" font-style:italic;">Fastest</span></p></body></html> - - - Qt::RichText - - + - - + + - Color ramp + + + + + + + + - + - + @@ -141,14 +146,7 @@ - - - - Maximum value - - - - + @@ -167,36 +165,82 @@ - - - - Weight points by - - - - - + + - - - - 1 - 0 - + + + <html><head/><body><p><span style=" font-style:italic;">Best</span></p></body></html> - - 6 + + Qt::RichText + + + + + + + 1 - 99999999.000000000000000 + 5 - - 0.200000000000000 + + 1 + + + Qt::Horizontal + + + false + + + false + + + QSlider::TicksBelow - + + + <html><head/><body><p><span style=" font-style:italic;">Fastest</span></p></body></html> + + + Qt::RichText + + + + + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Legend Settings… + + @@ -226,13 +270,27 @@
qgscolorrampbutton.h
1 + + QgsPanelWidget + QWidget +
qgspanelwidget.h
+ 1 +
+ + QgsPropertyOverrideButton + QToolButton +
qgspropertyoverridebutton.h
+
btnColorRamp mRadiusSpinBox + mRadiusDDBtn mMaxSpinBox + mMaximumValueDDBtn mWeightExpressionWidget mQualitySlider + mLegendSettingsButton diff --git a/tests/src/3d/testqgs3dcameracontroller.cpp b/tests/src/3d/testqgs3dcameracontroller.cpp index bf9ddf2f11868..b3dc625bff732 100644 --- a/tests/src/3d/testqgs3dcameracontroller.cpp +++ b/tests/src/3d/testqgs3dcameracontroller.cpp @@ -283,9 +283,9 @@ void TestQgs3DCameraController::testZoomWheel() QImage depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); scene->cameraController()->depthBufferCaptured( depthImage ); - QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -1382.3, -1.0, -1036.7 ), 2.0 ); - QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -480.0, -353.7, -360.6 ), 1.0 ); - QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1985.3, 1.0 ); + QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -1381.3, 1.0, -1036.7 ), 5.0 ); + QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -480.0, -351.7, -360.6 ), 5.0 ); + QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1982.3, 5.0 ); QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 ); QCOMPARE( scene->cameraController()->mClickPoint, QPoint() ); QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::None ); @@ -548,7 +548,7 @@ void TestQgs3DCameraController::testRotationCenterZoomWheelRotationCenter() QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 283.2, -27.0, 923.1 ), 1.5 ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( 120.3, -116.8, 308.9 ), 2.0 ); - QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1742.9, 1.0 ); + QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1742.9, 2.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 ); @@ -858,7 +858,7 @@ void TestQgs3DCameraController::testTranslateZoomWheelTranslate() QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 4.8, 9.9, 4.4 ), 1.0 ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -615.0, -108.0, -116.6 ), 1.0 ); - QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1743.4, 1.0 ); + QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1743.4, 2.0 ); QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 ); QCOMPARE( scene->cameraController()->mClickPoint, QPoint() ); QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::None ); diff --git a/tests/src/analysis/testqgsprocessingmodelalgorithm.cpp b/tests/src/analysis/testqgsprocessingmodelalgorithm.cpp index 7df66218f3a02..798f69be20a61 100644 --- a/tests/src/analysis/testqgsprocessingmodelalgorithm.cpp +++ b/tests/src/analysis/testqgsprocessingmodelalgorithm.cpp @@ -148,6 +148,7 @@ class TestQgsProcessingModelAlgorithm: public QgsTest void modelInputs(); void modelOutputs(); void modelWithChildException(); + void modelExecuteWithPreviousState(); void modelDependencies(); void modelSource(); void modelNameMatchesFileName(); @@ -1657,6 +1658,7 @@ void TestQgsProcessingModelAlgorithm::modelBranchPruning() // raster input params.insert( QStringLiteral( "LAYER" ), QStringLiteral( "R1" ) ); + context.modelResult().clear(); results = model1.run( params, context, &feedback ); // we should get the raster branch outputs only QVERIFY( !results.value( QStringLiteral( "fill2:RASTER_OUTPUT" ) ).toString().isEmpty() ); @@ -1719,6 +1721,7 @@ void TestQgsProcessingModelAlgorithm::modelBranchPruningConditional() context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "var1" ), 0 ); context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "var2" ), 1 ); + context.modelResult().clear(); results = model1.run( params, context, &feedback, &ok ); QVERIFY( ok ); // the branch with the exception should NOT be hit } @@ -2364,10 +2367,132 @@ void TestQgsProcessingModelAlgorithm::modelWithChildException() QCOMPARE( context.modelResult().childResults().value( "buffer" ).htmlLog().left( 50 ), QStringLiteral( "Prepare algorithm: buffer" ) ); QCOMPARE( context.modelResult().childResults().value( "buffer" ).htmlLog().right( 21 ), QStringLiteral( "s (1 output(s)).
" ) ); QVERIFY( context.temporaryLayerStore()->mapLayer( context.modelResult().childResults().value( "buffer" ).outputs().value( "OUTPUT" ).toString() ) ); + QCOMPARE( context.modelResult().rawChildInputs().value( "buffer" ).toMap().value( "INPUT" ).toString(), QStringLiteral( "v1" ) ); + QCOMPARE( context.modelResult().rawChildInputs().value( "buffer" ).toMap().value( "OUTPUT" ).toString(), QStringLiteral( "memory:Buffered" ) ); + QCOMPARE( context.modelResult().rawChildOutputs().value( "buffer" ).toMap().value( "OUTPUT" ).toString(), context.modelResult().childResults().value( "buffer" ).outputs().value( "OUTPUT" ).toString() ); QCOMPARE( context.modelResult().childResults().value( "raise" ).executionStatus(), Qgis::ProcessingModelChildAlgorithmExecutionStatus::Failed ); QCOMPARE( context.modelResult().childResults().value( "raise" ).htmlLog().left( 49 ), QStringLiteral( "Prepare algorithm: raise" ) ); QVERIFY( context.modelResult().childResults().value( "raise" ).htmlLog().contains( QStringLiteral( "Error encountered while running my second step: something bad happened" ) ) ); + + QSet expected{ QStringLiteral( "buffer" ) }; + QCOMPARE( context.modelResult().executedChildIds(), expected ); +} + +void TestQgsProcessingModelAlgorithm::modelExecuteWithPreviousState() +{ + QgsProcessingModelAlgorithm m; + + const QgsProcessingModelParameter sourceParam( "test" ); + m.addModelParameter( new QgsProcessingParameterString( "test" ), sourceParam ); + + QgsProcessingModelChildAlgorithm childAlgorithm; + childAlgorithm.setChildId( QStringLiteral( "calculate" ) ); + childAlgorithm.setAlgorithmId( "native:calculateexpression" ); + childAlgorithm.addParameterSources( "INPUT", { QgsProcessingModelChildParameterSource::fromExpression( " @test || '_1'" ) } ); + m.addChildAlgorithm( childAlgorithm ); + + QgsProcessingModelChildAlgorithm childAlgorithm2; + childAlgorithm2.setChildId( QStringLiteral( "calculate2" ) ); + childAlgorithm2.setAlgorithmId( "native:calculateexpression" ); + childAlgorithm2.addParameterSources( "INPUT", { QgsProcessingModelChildParameterSource::fromExpression( " @calculate_OUTPUT || '_2'" ) } ); + childAlgorithm2.setDependencies( { QgsProcessingModelChildDependency( QStringLiteral( "calculate" ) ) } ); + m.addChildAlgorithm( childAlgorithm2 ); + + // run and check context details + QgsProcessingContext context; + context.setLogLevel( Qgis::ProcessingLogLevel::ModelDebug ); + QgsProcessingFeedback feedback; + QVariantMap params; + params.insert( QStringLiteral( "test" ), QStringLiteral( "my string" ) ); + + // start with no initial state + bool ok = false; + m.run( params, context, &feedback, &ok ); + QVERIFY( ok ); + QCOMPARE( context.modelResult().childResults().value( "calculate" ).executionStatus(), Qgis::ProcessingModelChildAlgorithmExecutionStatus::Success ); + QCOMPARE( context.modelResult().childResults().value( "calculate" ).inputs().value( "INPUT" ).toString(), QStringLiteral( "my string_1" ) ); + QCOMPARE( context.modelResult().childResults().value( "calculate" ).outputs().value( "OUTPUT" ).toString(), QStringLiteral( "my string_1" ) ); + QCOMPARE( context.modelResult().rawChildInputs().value( "calculate" ).toMap().value( "INPUT" ).toString(), QStringLiteral( "my string_1" ) ); + QCOMPARE( context.modelResult().rawChildOutputs().value( "calculate" ).toMap().value( "OUTPUT" ).toString(), QStringLiteral( "my string_1" ) ); + + QCOMPARE( context.modelResult().childResults().value( "calculate2" ).executionStatus(), Qgis::ProcessingModelChildAlgorithmExecutionStatus::Success ); + QCOMPARE( context.modelResult().childResults().value( "calculate2" ).inputs().value( "INPUT" ).toString(), QStringLiteral( "my string_1_2" ) ); + QCOMPARE( context.modelResult().childResults().value( "calculate2" ).outputs().value( "OUTPUT" ).toString(), QStringLiteral( "my string_1_2" ) ); + QCOMPARE( context.modelResult().rawChildInputs().value( "calculate2" ).toMap().value( "INPUT" ).toString(), QStringLiteral( "my string_1_2" ) ); + QCOMPARE( context.modelResult().rawChildOutputs().value( "calculate2" ).toMap().value( "OUTPUT" ).toString(), QStringLiteral( "my string_1_2" ) ); + + QSet expected{ QStringLiteral( "calculate" ), QStringLiteral( "calculate2" ) }; + QCOMPARE( context.modelResult().executedChildIds(), expected ); + QgsProcessingModelResult firstResult = context.modelResult(); + + context.modelResult().clear(); + // start with an initial state + + std::unique_ptr< QgsProcessingModelInitialRunConfig > modelConfig = std::make_unique< QgsProcessingModelInitialRunConfig >(); + modelConfig->setPreviouslyExecutedChildAlgorithms( { QStringLiteral( "calculate" )} ); + modelConfig->setInitialChildInputs( QVariantMap{ { + QStringLiteral( "calculate" ), QVariantMap{ + { QStringLiteral( "INPUT" ), QStringLiteral( "a different string" ) } + } + }} ); + modelConfig->setInitialChildOutputs( QVariantMap{ { + QStringLiteral( "calculate" ), QVariantMap{ + { QStringLiteral( "OUTPUT" ), QStringLiteral( "a different string" ) } + } + }} ); + context.setModelInitialRunConfig( std::move( modelConfig ) ); + + m.run( params, context, &feedback, &ok ); + QVERIFY( ok ); + // "calculate" should not be re-executed + QCOMPARE( context.modelResult().childResults().value( "calculate" ).executionStatus(), Qgis::ProcessingModelChildAlgorithmExecutionStatus::NotExecuted ); + QVERIFY( context.modelResult().childResults().value( "calculate" ).inputs().isEmpty() ); + QVERIFY( context.modelResult().childResults().value( "calculate" ).outputs().isEmpty() ); + + // the second child algorithm should be re-run + QCOMPARE( context.modelResult().childResults().value( "calculate2" ).executionStatus(), Qgis::ProcessingModelChildAlgorithmExecutionStatus::Success ); + QCOMPARE( context.modelResult().childResults().value( "calculate2" ).inputs().value( "INPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + QCOMPARE( context.modelResult().childResults().value( "calculate2" ).outputs().value( "OUTPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + QCOMPARE( context.modelResult().rawChildInputs().value( "calculate2" ).toMap().value( "INPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + QCOMPARE( context.modelResult().rawChildOutputs().value( "calculate2" ).toMap().value( "OUTPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + + expected = QSet { QStringLiteral( "calculate" ), QStringLiteral( "calculate2" ) }; + QCOMPARE( context.modelResult().executedChildIds(), expected ); + + // config should be discarded, it should never be re-used or passed on to non top-level models + QVERIFY( !context.modelInitialRunConfig() ); + + // merge with first result, to get complete set of results across both executions + firstResult.mergeWith( context.modelResult() ); + QCOMPARE( firstResult.childResults().value( "calculate" ).executionStatus(), Qgis::ProcessingModelChildAlgorithmExecutionStatus::Success ); + QCOMPARE( firstResult.childResults().value( "calculate" ).inputs().value( "INPUT" ).toString(), QStringLiteral( "my string_1" ) ); + QCOMPARE( firstResult.childResults().value( "calculate" ).outputs().value( "OUTPUT" ).toString(), QStringLiteral( "my string_1" ) ); + QCOMPARE( firstResult.rawChildInputs().value( "calculate" ).toMap().value( "INPUT" ).toString(), QStringLiteral( "a different string" ) ); + QCOMPARE( firstResult.rawChildOutputs().value( "calculate" ).toMap().value( "OUTPUT" ).toString(), QStringLiteral( "a different string" ) ); + QCOMPARE( firstResult.childResults().value( "calculate2" ).executionStatus(), Qgis::ProcessingModelChildAlgorithmExecutionStatus::Success ); + QCOMPARE( firstResult.childResults().value( "calculate2" ).inputs().value( "INPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + QCOMPARE( firstResult.childResults().value( "calculate2" ).outputs().value( "OUTPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + QCOMPARE( firstResult.rawChildInputs().value( "calculate2" ).toMap().value( "INPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + QCOMPARE( firstResult.rawChildOutputs().value( "calculate2" ).toMap().value( "OUTPUT" ).toString(), QStringLiteral( "a different string_2" ) ); + + QCOMPARE( context.temporaryLayerStore()->count(), 0 ); + + // test handling of temporary layers generated during earlier runs + modelConfig = std::make_unique< QgsProcessingModelInitialRunConfig >(); + + std::unique_ptr < QgsMapLayerStore > previousStore = std::make_unique< QgsMapLayerStore >(); + QgsVectorLayer *layer = new QgsVectorLayer( "Point?crs=epsg:3111", "v1", "memory" ); + previousStore->addMapLayer( layer ); + previousStore->moveToThread( nullptr ); + modelConfig->setPreviousLayerStore( std::move( previousStore ) ); + + context.setModelInitialRunConfig( std::move( modelConfig ) ); + m.run( params, context, &feedback, &ok ); + QVERIFY( ok ); + // layer should have been transferred to context's temporary layer store as part of model execution + QCOMPARE( context.temporaryLayerStore()->count(), 1 ); + QCOMPARE( context.temporaryLayerStore()->mapLayersByName( QStringLiteral( "v1" ) ).at( 0 ), layer ); } void TestQgsProcessingModelAlgorithm::modelDependencies() diff --git a/tests/src/app/testqgsmergeattributesdialog.cpp b/tests/src/app/testqgsmergeattributesdialog.cpp index f6e9a99ca17db..1d89bf26c9590 100644 --- a/tests/src/app/testqgsmergeattributesdialog.cpp +++ b/tests/src/app/testqgsmergeattributesdialog.cpp @@ -150,6 +150,74 @@ class TestQgsMergeattributesDialog : public QgsTest QVERIFY( !dialog.mergedAttributes().at( 0 ).isValid() ); QCOMPARE( dialog.mergedAttributes().at( 1 ), 22 ); } + + void testWithHiddenField() + { + // Create test layer + QgsVectorFileWriter::SaveVectorOptions options; + QgsVectorLayer ml( "LineString", "test", "memory" ); + QVERIFY( ml.isValid() ); + + QgsField notHiddenField( QStringLiteral( "not_hidden" ), QVariant::Int ); + QgsField hiddenField( QStringLiteral( "hidden" ), QVariant::Int ); + // hide the field + ml.setEditorWidgetSetup( 1, QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() ) ); + QVERIFY( ml.dataProvider()->addAttributes( { notHiddenField, hiddenField } ) ); + ml.updateFields(); + + // Create features + QgsFeature f1( ml.fields(), 1 ); + f1.setAttributes( QVector() << 1 << 2 ); + f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f1 ) ); + QCOMPARE( ml.featureCount(), 1 ); + + QgsFeature f2( ml.fields(), 2 ); + f2.setAttributes( QVector() << 3 << 4 ); + f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f2 ) ); + QCOMPARE( ml.featureCount(), 2 ); + + // Merge the attributes together + QgsMergeAttributesDialog dialog( QgsFeatureList() << f1 << f2, &ml, mQgisApp->mapCanvas() ); + QVERIFY( QMetaObject::invokeMethod( &dialog, "mFromLargestPushButton_clicked" ) ); + QCOMPARE( dialog.mergedAttributes(), QgsAttributes() << 1 << 2 ); + } + + void testWithHiddenFieldDefaultsToEmpty() + { + // Create test layer + QgsVectorFileWriter::SaveVectorOptions options; + QgsVectorLayer ml( "LineString", "test", "memory" ); + QVERIFY( ml.isValid() ); + + QgsField notHiddenField( QStringLiteral( "not_hidden" ), QVariant::Int ); + QgsField hiddenField( QStringLiteral( "hidden" ), QVariant::Int ); + QVERIFY( ml.dataProvider()->addAttributes( { notHiddenField, hiddenField } ) ); + ml.updateFields(); + + // hide the field + ml.setEditorWidgetSetup( 1, QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() ) ); + + + // Create features + QgsFeature f1( ml.fields(), 1 ); + f1.setAttributes( QVector() << 1 << 2 ); + f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f1 ) ); + QCOMPARE( ml.featureCount(), 1 ); + + QgsFeature f2( ml.fields(), 2 ); + f2.setAttributes( QVector() << 3 << 4 ); + f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f2 ) ); + QCOMPARE( ml.featureCount(), 2 ); + + // Merge the attributes together + QgsMergeAttributesDialog dialog( QgsFeatureList() << f1 << f2, &ml, mQgisApp->mapCanvas() ); + // QVariant gets turned into default value while saving the layer + QCOMPARE( dialog.mergedAttributes(), QgsAttributes() << 1 << QVariant() ); + } }; QGSTEST_MAIN( TestQgsMergeattributesDialog ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 06cd19e17486b..50b3a637279e8 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1750,7 +1750,7 @@ class TestQgsExpression: public QObject QTest::newRow( "array_replace (map)" ) << "array_replace(array('APP','SHOULD','ROCK'),map('APP','QGIS','SHOULD','DOES'))" << false << QVariant( QVariantList() << "QGIS" << "DOES" << "ROCK" ); // map HTML formatting - QTest::newRow( "map_to_html_table (map)" ) << "map_to_html_table(map('APP','QGIS','','DOES'))" << false << QVariant( "\n
%1
%1
%2
\n \n \n \n \n \n \n
<SHOULD>APP
DOESQGIS
" ); + QTest::newRow( "map_to_html_table (map)" ) << "map_to_html_table(map('APP','QGIS','','DOES'))" << false << QVariant( "\n \n \n \n \n \n \n \n
<SHOULD>APP
DOESQGIS
" ); QTest::newRow( "map_to_html_dl (map)" ) << "map_to_html_dl(map('APP','QGIS','','DOES'))" << false << QVariant( "\n
\n
<SHOULD>
DOES
APP
QGIS
\n
" ); //fuzzy matching diff --git a/tests/src/core/testqgsfield.cpp b/tests/src/core/testqgsfield.cpp index 73c54163542e6..d596e7f30576b 100644 --- a/tests/src/core/testqgsfield.cpp +++ b/tests/src/core/testqgsfield.cpp @@ -103,6 +103,7 @@ void TestQgsField::copy() original.setConstraints( constraints ); original.setReadOnly( true ); original.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio ); + original.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::UnsetField ); original.setMetadata( {{ 1, QStringLiteral( "abc" )}, {2, 5 }} ); QVariantMap config; @@ -130,6 +131,7 @@ void TestQgsField::assignment() original.setConstraints( constraints ); original.setReadOnly( true ); original.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio ); + original.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::UnsetField ); original.setMetadata( {{ 1, QStringLiteral( "abc" )}, {2, 5 }} ); QgsField copy; copy = original; @@ -204,6 +206,9 @@ void TestQgsField::gettersSetters() field.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio ); QCOMPARE( field.splitPolicy(), Qgis::FieldDomainSplitPolicy::GeometryRatio ); + field.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::UnsetField ); + QCOMPARE( field.duplicatePolicy(), Qgis::FieldDuplicatePolicy::UnsetField ); + field.setMetadata( {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }} ); QMap< int, QVariant> expected {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }}; QCOMPARE( field.metadata(), expected ); @@ -359,6 +364,12 @@ void TestQgsField::equality() field2.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio ); QVERIFY( field1 == field2 ); + field1.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::UnsetField ); + QVERIFY( !( field1 == field2 ) ); + QVERIFY( field1 != field2 ); + field2.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::UnsetField ); + QVERIFY( field1 == field2 ); + field1.setMetadata( {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }} ); QVERIFY( !( field1 == field2 ) ); QVERIFY( field1 != field2 ); @@ -923,6 +934,7 @@ void TestQgsField::dataStream() original.setAlias( QStringLiteral( "alias" ) ); original.setDefaultValueDefinition( QgsDefaultValue( QStringLiteral( "default" ) ) ); original.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio ); + original.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::DefaultValue ); QgsFieldConstraints constraints; constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginProvider ); constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginLayer ); diff --git a/tests/src/core/testqgsgdalprovider.cpp b/tests/src/core/testqgsgdalprovider.cpp index a064e36275c26..5a1a7e2b480b9 100644 --- a/tests/src/core/testqgsgdalprovider.cpp +++ b/tests/src/core/testqgsgdalprovider.cpp @@ -667,6 +667,28 @@ void TestQgsGdalProvider::testGdalProviderQuerySublayers() QCOMPARE( res.at( 0 ).driverName(), QStringLiteral( "SENTINEL2" ) ); rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) ); QVERIFY( rl->isValid() ); + + // tiff with two raster layers and TIFF Tags describing sublayers + res = mGdalMetadata->querySublayers( QStringLiteral( TEST_DATA_DIR ) + "/raster/gtiff_subdataset_tags.tif" ); + QCOMPARE( res.count(), 2 ); + QCOMPARE( res.at( 0 ).layerNumber(), 1 ); + QCOMPARE( res.at( 0 ).name(), QStringLiteral( "Test Document Name 1" ) ); + QCOMPARE( res.at( 0 ).description(), QStringLiteral( "Test Image Description 1" ) ); + QCOMPARE( res.at( 0 ).uri(), QStringLiteral( "GTIFF_DIR:1:%1/raster/gtiff_subdataset_tags.tif" ).arg( QStringLiteral( TEST_DATA_DIR ) ) ); + QCOMPARE( res.at( 0 ).providerKey(), QStringLiteral( "gdal" ) ); + QCOMPARE( res.at( 0 ).type(), Qgis::LayerType::Raster ); + QCOMPARE( res.at( 0 ).driverName(), QStringLiteral( "GTiff" ) ); + rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) ); + QVERIFY( rl->isValid() ); + QCOMPARE( res.at( 1 ).layerNumber(), 2 ); + QCOMPARE( res.at( 1 ).name(), QStringLiteral( "Test Document Name 2" ) ); + QCOMPARE( res.at( 1 ).description(), QStringLiteral( "Test Image Description 2" ) ); + QCOMPARE( res.at( 1 ).uri(), QStringLiteral( "GTIFF_DIR:2:%1/raster/gtiff_subdataset_tags.tif" ).arg( QStringLiteral( TEST_DATA_DIR ) ) ); + QCOMPARE( res.at( 1 ).providerKey(), QStringLiteral( "gdal" ) ); + QCOMPARE( res.at( 1 ).type(), Qgis::LayerType::Raster ); + QCOMPARE( res.at( 1 ).driverName(), QStringLiteral( "GTiff" ) ); + rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 1 ).toLayer( options ) ) ); + QVERIFY( rl->isValid() ); } void TestQgsGdalProvider::testGdalProviderQuerySublayers_NetCDF() diff --git a/tests/src/core/testqgslegendrenderer.cpp b/tests/src/core/testqgslegendrenderer.cpp index 6c3868c80c2a9..788de1810fb28 100644 --- a/tests/src/core/testqgslegendrenderer.cpp +++ b/tests/src/core/testqgslegendrenderer.cpp @@ -48,6 +48,7 @@ #include "qgslinesymbol.h" #include "qgsmarkersymbol.h" #include "qgsfillsymbol.h" +#include "qgsheatmaprenderer.h" static void _setStandardTestFont( QgsLegendSettings &settings, const QString &style = QStringLiteral( "Roman" ) ) { @@ -206,6 +207,7 @@ class TestQgsLegendRenderer : public QgsTest void testBigMarkerJson(); void testLabelLegend(); + void testHeatmap(); private: QgsLayerTree *mRoot = nullptr; @@ -1804,6 +1806,39 @@ void TestQgsLegendRenderer::testLabelLegend() mVL3->setLabelsEnabled( bkLabelsEnabled ); } +void TestQgsLegendRenderer::testHeatmap() +{ + std::unique_ptr< QgsLayerTree > root( new QgsLayerTree() ); + + QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "Points" ), QStringLiteral( "Points" ), QStringLiteral( "memory" ) ); + QgsProject::instance()->addMapLayer( vl ); + QgsHeatmapRenderer *renderer = new QgsHeatmapRenderer(); + renderer->setColorRamp( new QgsGradientColorRamp( QColor( 255, 0, 0 ), QColor( 255, 200, 100 ) ) ); + QgsColorRampLegendNodeSettings rampSettings; + + QFont font( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) ); + QgsTextFormat f; + f.setSize( 16 ); + f.setFont( font ); + rampSettings.setTextFormat( f ); + rampSettings.setMinimumLabel( "min" ); + rampSettings.setMaximumLabel( "max" ); + renderer->setLegendSettings( rampSettings ); + + vl->setRenderer( renderer ); + vl->setLegend( new QgsDefaultVectorLayerLegend( vl ) ); + root->addLayer( vl ); + + QgsLayerTreeModel legendModel( root.get() ); + QgsLegendSettings settings; + settings.rstyle( QgsLegendStyle::Style::Symbol ).setMargin( QgsLegendStyle::Side::Top, 9 ); + _setStandardTestFont( settings, QStringLiteral( "Bold" ) ); + const QImage res = _renderLegend( &legendModel, settings ); + + QgsProject::instance()->removeMapLayer( vl ); + QVERIFY( _verifyImage( res, QStringLiteral( "heatmap" ) ) ); +} + QGSTEST_MAIN( TestQgsLegendRenderer ) #include "testqgslegendrenderer.moc" diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 73c3b4786b25e..5c6b80909e54e 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -28,6 +28,7 @@ #include "qgstriangularmesh.h" #include "qgsexpression.h" #include "qgsmeshlayertemporalproperties.h" +#include "qgsmeshlayerutils.h" #include "qgsmeshdataprovidertemporalcapabilities.h" #include "qgsprovidermetadata.h" @@ -37,12 +38,14 @@ * \ingroup UnitTests * This is a unit test for a mesh layer */ -class TestQgsMeshLayer : public QObject +class TestQgsMeshLayer : public QgsTest { Q_OBJECT public: - TestQgsMeshLayer() = default; + TestQgsMeshLayer() + : QgsTest( QStringLiteral( "Mesh layer tests" ) ) + {} private: QString mDataDir; @@ -108,6 +111,8 @@ class TestQgsMeshLayer : public QObject void keepDatasetIndexConsistency(); void symbologyConsistencyWithName(); void updateTimePropertiesWhenReloading(); + + void testHaveSameParentQuantity(); }; QString TestQgsMeshLayer::readFile( const QString &fname ) const @@ -2339,5 +2344,27 @@ void TestQgsMeshLayer::updateTimePropertiesWhenReloading() QCOMPARE( timeExtent1, static_cast( layer->temporalProperties() )->timeExtent() ); } +void TestQgsMeshLayer::testHaveSameParentQuantity() +{ + QgsMeshLayer layer1( + testDataPath( "mesh/netcdf_parent_quantity.nc" ), + QStringLiteral( "mesh" ), + QStringLiteral( "mdal" ) ); + QVERIFY( layer1.isValid() ); + + QVERIFY( QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 1 ) ) ); + QVERIFY( QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 2 ) ) ); + QVERIFY( QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 2 ), QgsMeshDatasetIndex( 3 ) ) ); + QVERIFY( !QgsMeshLayerUtils::haveSameParentQuantity( &layer1, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 4 ) ) ); + + QgsMeshLayer layer2( + testDataPath( "mesh/mesh_z_ws_d.nc" ), + QStringLiteral( "mesh" ), + QStringLiteral( "mdal" ) ); + QVERIFY( layer2.isValid() ); + + QVERIFY( !QgsMeshLayerUtils::haveSameParentQuantity( &layer2, QgsMeshDatasetIndex( 0 ), QgsMeshDatasetIndex( 1 ) ) ); +} + QGSTEST_MAIN( TestQgsMeshLayer ) #include "testqgsmeshlayer.moc" diff --git a/tests/src/core/testqgsmeshlayerrenderer.cpp b/tests/src/core/testqgsmeshlayerrenderer.cpp index 2416dadf22f25..8c3cb3692d409 100644 --- a/tests/src/core/testqgsmeshlayerrenderer.cpp +++ b/tests/src/core/testqgsmeshlayerrenderer.cpp @@ -35,9 +35,6 @@ #include "qgsmesh3daveraging.h" #include "qgsmaplayertemporalproperties.h" -//qgis test includes -#include "qgsrenderchecker.h" - /** * \ingroup UnitTests * This is a unit test for the different renderers for mesh layers. @@ -47,7 +44,7 @@ class TestQgsMeshRenderer : public QgsTest Q_OBJECT public: - TestQgsMeshRenderer() : QgsTest( QStringLiteral( "Mesh Layer Rendering Tests" ) ) {} + TestQgsMeshRenderer() : QgsTest( QStringLiteral( "Mesh Layer Rendering Tests" ), QStringLiteral( "mesh" ) ) {} private: QString mDataDir; @@ -61,10 +58,8 @@ class TestQgsMeshRenderer : public QgsTest void initTestCase();// will be called before the first testfunction is executed. void cleanupTestCase();// will be called after the last testfunction was executed. void init(); // will be called before each testfunction is executed. - bool imageCheck( const QString &testType, QgsMeshLayer *layer, double rotation = 0.0 ); QString readFile( const QString &fname ) const; - void test_native_mesh_rendering(); void test_native_mesh_renderingWithClipping(); void test_triangular_mesh_rendering(); @@ -85,6 +80,7 @@ class TestQgsMeshRenderer : public QgsTest void test_face_vector_on_user_grid(); void test_face_vector_on_user_grid_streamlines(); void test_vertex_vector_on_user_grid(); + void test_vertex_vector_on_user_grid_wind_barbs(); void test_vertex_vector_on_user_grid_streamlines(); void test_vertex_vector_on_user_grid_streamlines_colorRamp(); void test_vertex_vector_traces(); @@ -161,6 +157,7 @@ void TestQgsMeshRenderer::initTestCase() // Mdal layer mMdalLayer = new QgsMeshLayer( mDataDir + "/quad_and_triangle.2dm", "Triangle and Quad Mdal", "mdal" ); mMdalLayer->dataProvider()->addDataset( mDataDir + "/quad_and_triangle_vertex_scalar_with_inactive_face.dat" ); + mMdalLayer->dataProvider()->addDataset( mDataDir + "/quad_and_triangle_vertex_vector2.dat" ); QVERIFY( mMdalLayer->isValid() ); // Memory layer @@ -213,23 +210,6 @@ QString TestQgsMeshRenderer::readFile( const QString &fname ) const return uri; } -bool TestQgsMeshRenderer::imageCheck( const QString &testType, QgsMeshLayer *layer, double rotation ) -{ - mMapSettings->setDestinationCrs( layer->crs() ); - mMapSettings->setExtent( layer->extent() ); - mMapSettings->setRotation( rotation ); - mMapSettings->setOutputDpi( 96 ); - - QgsRenderChecker myChecker; - myChecker.setControlPathPrefix( QStringLiteral( "mesh" ) ); - myChecker.setControlName( "expected_" + testType ); - myChecker.setMapSettings( *mMapSettings ); - myChecker.setColorTolerance( 15 ); - const bool myResultFlag = myChecker.runTest( testType, 0 ); - mReport += myChecker.report(); - return myResultFlag; -} - void TestQgsMeshRenderer::test_native_mesh_rendering() { QgsMeshRendererSettings rendererSettings = mMemoryLayer->rendererSettings(); @@ -238,8 +218,16 @@ void TestQgsMeshRenderer::test_native_mesh_rendering() settings.setLineWidth( 1. ); rendererSettings.setNativeMeshSettings( settings ); mMemoryLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "quad_and_triangle_native_mesh", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_native_mesh_rotated_45", mMemoryLayer, 45.0 ) ); + + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_native_mesh", "quad_and_triangle_native_mesh", *mMapSettings, 0, 15 ); + + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_native_mesh_rotated_45", "quad_and_triangle_native_mesh_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_native_mesh_renderingWithClipping() @@ -258,10 +246,12 @@ void TestQgsMeshRenderer::test_native_mesh_renderingWithClipping() mMapSettings->addClippingRegion( region ); mMapSettings->addClippingRegion( region2 ); - const bool res = imageCheck( "painterclip_region", mMemoryLayer ); - + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "painterclip_region", "painterclip_region", *mMapSettings, 0, 15 ); mMapSettings->setClippingRegions( QList< QgsMapClippingRegion >() ); - QVERIFY( res ); } void TestQgsMeshRenderer::test_triangular_mesh_rendering() @@ -273,8 +263,16 @@ void TestQgsMeshRenderer::test_triangular_mesh_rendering() settings.setLineWidth( 0.26 ); rendererSettings.setTriangularMeshSettings( settings ); mMemoryLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "quad_and_triangle_triangular_mesh", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_triangular_mesh_rotated_45", mMemoryLayer, 45.0 ) ); + + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setRotation( 0 ); + mMapSettings->setOutputDpi( 96 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_triangular_mesh", "quad_and_triangle_triangular_mesh", *mMapSettings, 0, 15 ); + + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_triangular_mesh_rotated_45", "quad_and_triangle_triangular_mesh_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_edge_mesh_rendering() @@ -286,7 +284,12 @@ void TestQgsMeshRenderer::test_edge_mesh_rendering() settings.setLineWidth( 0.26 ); rendererSettings.setEdgeMeshSettings( settings ); mMemory1DLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "lines_edge_mesh", mMemory1DLayer ) ); + + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_mesh", "lines_edge_mesh", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_1d_vertex_scalar_dataset_rendering() @@ -306,8 +309,14 @@ void TestQgsMeshRenderer::test_1d_vertex_scalar_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_vertex_scalar_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_vertex_scalar_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_scalar_dataset", "lines_vertex_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_scalar_dataset_rotated_45", "lines_vertex_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_1d_vertex_vector_dataset_rendering() @@ -325,8 +334,14 @@ void TestQgsMeshRenderer::test_1d_vertex_vector_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_vertex_vector_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_vertex_vector_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_vector_dataset", "lines_vertex_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_vertex_vector_dataset_rotated_45", "lines_vertex_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_1d_edge_scalar_dataset_rendering() @@ -346,8 +361,14 @@ void TestQgsMeshRenderer::test_1d_edge_scalar_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_edge_scalar_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_edge_scalar_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_scalar_dataset", "lines_edge_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_scalar_dataset_rotated_45", "lines_edge_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_1d_edge_vector_dataset_rendering() @@ -360,8 +381,14 @@ void TestQgsMeshRenderer::test_1d_edge_vector_dataset_rendering() mMemory1DLayer->setRendererSettings( rendererSettings ); mMemory1DLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "lines_edge_vector_dataset", mMemory1DLayer ) ); - QVERIFY( imageCheck( "lines_edge_vector_dataset_rotated_45", mMemory1DLayer, 45 ) ); + mMapSettings->setDestinationCrs( mMemory1DLayer->crs() ); + mMapSettings->setExtent( mMemory1DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_vector_dataset", "lines_edge_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_vector_dataset_rotated_45", "lines_edge_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_scalar_dataset_rendering() @@ -374,8 +401,14 @@ void TestQgsMeshRenderer::test_vertex_scalar_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_scalar_dataset", "quad_and_triangle_vertex_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_scalar_dataset_rotated_45", "quad_and_triangle_vertex_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_dataset_rendering() @@ -393,8 +426,14 @@ void TestQgsMeshRenderer::test_vertex_vector_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_dataset", "quad_and_triangle_vertex_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_dataset_rotated_45", "quad_and_triangle_vertex_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_dataset_colorRamp_rendering() @@ -413,7 +452,11 @@ void TestQgsMeshRenderer::test_vertex_vector_dataset_colorRamp_rendering() rendererSettings.setVectorSettings( ds.group(), settings ); mMemoryLayer->setRendererSettings( rendererSettings ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_dataset_colorRamp", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_dataset_colorRamp", "quad_and_triangle_vertex_vector_dataset_colorRamp", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_face_scalar_dataset_rendering() @@ -426,8 +469,14 @@ void TestQgsMeshRenderer::test_face_scalar_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_scalar_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_scalar_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_scalar_dataset", "quad_and_triangle_face_scalar_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_scalar_dataset_rotated_45", "quad_and_triangle_face_scalar_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_face_scalar_dataset_interpolated_neighbour_average_rendering() @@ -443,10 +492,13 @@ void TestQgsMeshRenderer::test_face_scalar_dataset_interpolated_neighbour_averag mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_scalar_interpolated_neighbour_average_dataset", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_scalar_interpolated_neighbour_average_dataset", "quad_and_triangle_face_scalar_interpolated_neighbour_average_dataset", *mMapSettings, 0, 15 ); } - void TestQgsMeshRenderer::test_face_vector_dataset_rendering() { const QgsMeshDatasetIndex ds( 3, 0 ); @@ -457,8 +509,14 @@ void TestQgsMeshRenderer::test_face_vector_dataset_rendering() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_dataset", "quad_and_triangle_face_vector_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_dataset_rotated_45", "quad_and_triangle_face_vector_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_scalar_dataset_with_inactive_face_rendering() @@ -471,7 +529,45 @@ void TestQgsMeshRenderer::test_vertex_scalar_dataset_with_inactive_face_renderin mMdalLayer->setRendererSettings( rendererSettings ); mMdalLayer->setStaticScalarDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", mMdalLayer ) ); + mMapSettings->setDestinationCrs( mMdalLayer->crs() ); + mMapSettings->setExtent( mMdalLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", "quad_and_triangle_vertex_scalar_dataset_with_inactive_face", *mMapSettings, 0, 15 ); +} + +void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_wind_barbs() +{ + const QgsMeshDatasetIndex ds( 2, 0 ); + const QgsMeshDatasetGroupMetadata metadata = mMdalLayer->dataProvider()->datasetGroupMetadata( ds ); + QCOMPARE( metadata.name(), QStringLiteral( "VertexVectorDataset2" ) ); + + QgsMeshRendererSettings rendererSettings = mMdalLayer->rendererSettings(); + QgsMeshRendererVectorSettings settings = rendererSettings.vectorSettings( ds.group() ); + settings.setOnUserDefinedGrid( true ); + settings.setUserGridCellWidth( 30 ); + settings.setUserGridCellHeight( 30 ); + settings.setLineWidth( 0.5 ); + settings.setSymbology( QgsMeshRendererVectorSettings::WindBarbs ); + settings.setColoringMethod( QgsInterpolatedLineColor::SingleColor ); + QgsMeshRendererVectorWindBarbSettings windBarbSettings = settings.windBarbSettings(); + windBarbSettings.setShaftLength( 20 ); + windBarbSettings.setShaftLengthUnits( Qgis::RenderUnit::Pixels ); + windBarbSettings.setMagnitudeUnits( QgsMeshRendererVectorWindBarbSettings::WindSpeedUnit::OtherUnit ); + windBarbSettings.setMagnitudeMultiplier( 2 ); + settings.setWindBarbSettings( windBarbSettings ); + rendererSettings.setVectorSettings( ds.group(), settings ); + mMdalLayer->setRendererSettings( rendererSettings ); + mMdalLayer->setStaticVectorDatasetIndex( ds ); + + mMapSettings->setDestinationCrs( mMdalLayer->crs() ); + mMapSettings->setExtent( mMdalLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs", "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45", "quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_face_vector_on_user_grid() @@ -491,8 +587,14 @@ void TestQgsMeshRenderer::test_face_vector_on_user_grid() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset", "quad_and_triangle_face_vector_user_grid_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset_rotated_45", "quad_and_triangle_face_vector_user_grid_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_face_vector_on_user_grid_streamlines() @@ -512,8 +614,14 @@ void TestQgsMeshRenderer::test_face_vector_on_user_grid_streamlines() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset_streamlines", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_face_vector_user_grid_dataset_streamlines_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset_streamlines", "quad_and_triangle_face_vector_user_grid_dataset_streamlines", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_face_vector_user_grid_dataset_streamlines_rotated_45", "quad_and_triangle_face_vector_user_grid_dataset_streamlines_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_on_user_grid() @@ -534,8 +642,14 @@ void TestQgsMeshRenderer::test_vertex_vector_on_user_grid() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset", "quad_and_triangle_vertex_vector_user_grid_dataset", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_rotated_45", "quad_and_triangle_vertex_vector_user_grid_dataset_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines() @@ -556,8 +670,14 @@ void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines", "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_rotated_45", "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines_colorRamp() @@ -578,7 +698,11 @@ void TestQgsMeshRenderer::test_vertex_vector_on_user_grid_streamlines_colorRamp( mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_colorRamp", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_colorRamp", "quad_and_triangle_vertex_vector_user_grid_dataset_streamlines_colorRamp", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_vertex_vector_traces() @@ -605,8 +729,14 @@ void TestQgsMeshRenderer::test_vertex_vector_traces() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_traces", mMemoryLayer ) ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_traces_rotated_45", mMemoryLayer, 45.0 ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "lines_edge_quad_and_triangle_vertex_vector_traces", "quad_and_triangle_vertex_vector_traces", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 45 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_traces_rotated_45", "quad_and_triangle_vertex_vector_traces_rotated_45", *mMapSettings, 0, 15 ); + mMapSettings->setRotation( 0 ); } void TestQgsMeshRenderer::test_vertex_vector_traces_colorRamp() @@ -633,7 +763,11 @@ void TestQgsMeshRenderer::test_vertex_vector_traces_colorRamp() mMemoryLayer->setRendererSettings( rendererSettings ); mMemoryLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "quad_and_triangle_vertex_vector_traces_colorRamp", mMemoryLayer ) ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "quad_and_triangle_vertex_vector_traces_colorRamp", "quad_and_triangle_vertex_vector_traces_colorRamp", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_signals() @@ -686,7 +820,11 @@ void TestQgsMeshRenderer::test_stacked_3d_mesh_single_level_averaging() mMdal3DLayer->setRendererSettings( rendererSettings ); mMdal3DLayer->setStaticVectorDatasetIndex( ds ); - QVERIFY( imageCheck( "stacked_3d_mesh_single_level_averaging", mMdal3DLayer ) ); + mMapSettings->setDestinationCrs( mMdal3DLayer->crs() ); + mMapSettings->setExtent( mMdal3DLayer->extent() ); + mMapSettings->setRotation( 0 ); + mMapSettings->setOutputDpi( 96 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "stacked_3d_mesh_single_level_averaging", "stacked_3d_mesh_single_level_averaging", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_simplified_triangular_mesh_rendering() @@ -703,7 +841,12 @@ void TestQgsMeshRenderer::test_simplified_triangular_mesh_rendering() mMdal3DLayer->setRendererSettings( rendererSettings ); mMdal3DLayer->setMeshSimplificationSettings( simplificatationSettings ); - QVERIFY( imageCheck( "simplified_triangular_mesh", mMdal3DLayer ) ); + + mMapSettings->setDestinationCrs( mMdal3DLayer->crs() ); + mMapSettings->setExtent( mMdal3DLayer->extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "simplified_triangular_mesh", "simplified_triangular_mesh", *mMapSettings, 0, 15 ); } void TestQgsMeshRenderer::test_classified_values() @@ -717,7 +860,11 @@ void TestQgsMeshRenderer::test_classified_values() classifiedMesh.temporalProperties()->setIsActive( false ); classifiedMesh.setStaticScalarDatasetIndex( QgsMeshDatasetIndex( 3, 4 ) ); - QVERIFY( imageCheck( "classified_values", &classifiedMesh ) ); + mMapSettings->setDestinationCrs( classifiedMesh.crs() ); + mMapSettings->setExtent( classifiedMesh.extent() ); + mMapSettings->setOutputDpi( 96 ); + mMapSettings->setRotation( 0 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "classified_values", "classified_values", *mMapSettings, 0, 15 ); } QGSTEST_MAIN( TestQgsMeshRenderer ) diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 7b1402a7b3266..ad77a8f5c02ff 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -106,6 +106,7 @@ ADD_PYTHON_TEST(PyQgsGraduatedSymbolRenderer test_qgsgraduatedsymbolrenderer.py) ADD_PYTHON_TEST(PyQgsGraph test_qgsgraph.py) ADD_PYTHON_TEST(PyQgsGroupLayer test_qgsgrouplayer.py) ADD_PYTHON_TEST(PyQgsHashLineSymbolLayer test_qgshashlinesymbollayer.py) +ADD_PYTHON_TEST(PyQgsHeatmapRenderer test_qgsheatmaprenderer.py) ADD_PYTHON_TEST(PyQgsHillshadeRenderer test_qgshillshaderenderer.py) ADD_PYTHON_TEST(PyQgsImageCache test_qgsimagecache.py) ADD_PYTHON_TEST(PyQgsInterpolatedLineSymbolLayer test_qgsinterpolatedlinesymbollayers.py) @@ -176,6 +177,7 @@ ADD_PYTHON_TEST(PyQgsMargins test_qgsmargins.py) ADD_PYTHON_TEST(PyQgsMarkerLineSymbolLayer test_qgsmarkerlinesymbollayer.py) ADD_PYTHON_TEST(PyQgsMatrix4x4 test_qgsmatrix4x4.py) ADD_PYTHON_TEST(PyQgsMergedFeatureRenderer test_qgsmergedfeaturerenderer.py) +ADD_PYTHON_TEST(PyQgsMeshLayer test_qgsmeshlayer.py) ADD_PYTHON_TEST(PyQgsMeshLayerElevationProperties test_qgsmeshlayerelevationproperties.py) ADD_PYTHON_TEST(PyQgsMeshLayerRenderer test_qgsmeshlayerrenderer.py) ADD_PYTHON_TEST(PyQgsMessageLog test_qgsmessagelog.py) diff --git a/tests/src/python/test_provider_postgres_latency.py b/tests/src/python/test_provider_postgres_latency.py index ce138c82601c5..707a24e62a090 100644 --- a/tests/src/python/test_provider_postgres_latency.py +++ b/tests/src/python/test_provider_postgres_latency.py @@ -156,7 +156,7 @@ def testProjectOpenTime(self): settings = QgsSettingsTree.node('core').childSetting('provider-parallel-loading') settings.setVariantValue(False) - davg = 8.67 + davg = 7.61 dmin = round(davg - 0.2, 2) dmax = round(davg + 0.3, 2) error_string = 'expected from {0}s to {1}s, got {2}s\nHINT: set davg={2} to pass the test :)' diff --git a/tests/src/python/test_qgsheatmaprenderer.py b/tests/src/python/test_qgsheatmaprenderer.py new file mode 100644 index 0000000000000..d34439addf82d --- /dev/null +++ b/tests/src/python/test_qgsheatmaprenderer.py @@ -0,0 +1,226 @@ +"""QGIS Unit tests for QgsHeatmapRenderer + +From build dir, run: ctest -R PyQgsHeatmapRenderer -V + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" + +import os + +from qgis.PyQt.QtCore import QSize +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtXml import QDomDocument +from qgis.core import ( + Qgis, + QgsHeatmapRenderer, + QgsGradientColorRamp, + QgsReadWriteContext, + QgsColorRampLegendNodeSettings, + QgsProperty, + QgsFeatureRenderer, + QgsVectorLayer, + QgsMapSettings, + QgsExpressionContext, + QgsExpressionContextScope +) +import unittest +from qgis.testing import start_app, QgisTestCase + +from utilities import unitTestDataPath + +start_app() + +TEST_DATA_DIR = unitTestDataPath() + + +class TestQgsHeatmapRenderer(QgisTestCase): + + @classmethod + def control_path_prefix(cls): + return 'heatmap_renderer' + + def test_clone(self): + """ + Test cloning renderer + """ + + renderer = QgsHeatmapRenderer() + renderer.setColorRamp( + QgsGradientColorRamp(QColor(255, 0, 0), QColor(255, 200, 100))) + + legend_settings = QgsColorRampLegendNodeSettings() + legend_settings.setMaximumLabel('my max') + legend_settings.setMinimumLabel('my min') + renderer.setLegendSettings(legend_settings) + + renderer.setDataDefinedProperty( + QgsFeatureRenderer.Property.HeatmapRadius, + QgsProperty.fromField('radius_field') + ) + renderer.setDataDefinedProperty( + QgsFeatureRenderer.Property.HeatmapMaximum, + QgsProperty.fromField('max_field') + ) + + renderer2 = renderer.clone() + self.assertEqual(renderer2.colorRamp().color1(), QColor(255, 0, 0)) + self.assertEqual(renderer2.colorRamp().color2(), QColor(255, 200, 100)) + self.assertEqual(renderer2.legendSettings().minimumLabel(), 'my min') + self.assertEqual(renderer2.legendSettings().maximumLabel(), 'my max') + self.assertEqual( + renderer2.dataDefinedProperties().property(QgsFeatureRenderer.Property.HeatmapRadius).field(), + 'radius_field' + ) + self.assertEqual( + renderer2.dataDefinedProperties().property(QgsFeatureRenderer.Property.HeatmapMaximum).field(), + 'max_field' + ) + + def test_write_read_xml(self): + """ + Test writing renderer to xml and restoring + """ + + renderer = QgsHeatmapRenderer() + renderer.setColorRamp( + QgsGradientColorRamp(QColor(255, 0, 0), QColor(255, 200, 100))) + + legend_settings = QgsColorRampLegendNodeSettings() + legend_settings.setMaximumLabel('my max') + legend_settings.setMinimumLabel('my min') + renderer.setLegendSettings(legend_settings) + + renderer.setDataDefinedProperty( + QgsFeatureRenderer.Property.HeatmapRadius, + QgsProperty.fromField('radius_field') + ) + renderer.setDataDefinedProperty( + QgsFeatureRenderer.Property.HeatmapMaximum, + QgsProperty.fromField('max_field') + ) + + doc = QDomDocument("testdoc") + elem = renderer.save(doc, QgsReadWriteContext()) + + renderer2 = QgsFeatureRenderer.load(elem, QgsReadWriteContext()) + self.assertEqual(renderer2.colorRamp().color1(), QColor(255, 0, 0)) + self.assertEqual(renderer2.colorRamp().color2(), QColor(255, 200, 100)) + self.assertEqual(renderer2.legendSettings().minimumLabel(), 'my min') + self.assertEqual(renderer2.legendSettings().maximumLabel(), 'my max') + + self.assertEqual( + renderer2.dataDefinedProperties().property(QgsFeatureRenderer.Property.HeatmapRadius).field(), + 'radius_field' + ) + self.assertEqual( + renderer2.dataDefinedProperties().property(QgsFeatureRenderer.Property.HeatmapMaximum).field(), + 'max_field' + ) + + def test_render(self): + """ + Test heatmap rendering + """ + layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, 'points.shp'), 'Points', 'ogr') + self.assertTrue(layer.isValid()) + + renderer = QgsHeatmapRenderer() + renderer.setColorRamp( + QgsGradientColorRamp(QColor(255, 0, 0), QColor(255, 200, 100))) + renderer.setRadius(20) + renderer.setRadiusUnit(Qgis.RenderUnit.Millimeters) + layer.setRenderer(renderer) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setExtent(layer.extent()) + mapsettings.setLayers([layer]) + + self.assertTrue( + self.render_map_settings_check( + 'Render heatmap', + 'render_heatmap', + mapsettings) + ) + + def test_data_defined_radius(self): + """ + Test heatmap rendering with data defined radius + """ + layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, 'points.shp'), 'Points', 'ogr') + self.assertTrue(layer.isValid()) + + renderer = QgsHeatmapRenderer() + renderer.setColorRamp( + QgsGradientColorRamp(QColor(255, 0, 0), QColor(255, 200, 100))) + renderer.setRadius(20) + renderer.setRadiusUnit(Qgis.RenderUnit.Millimeters) + renderer.setDataDefinedProperty( + QgsFeatureRenderer.Property.HeatmapRadius, + QgsProperty.fromExpression('@my_var * 2') + ) + + layer.setRenderer(renderer) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setExtent(layer.extent()) + mapsettings.setLayers([layer]) + scope = QgsExpressionContextScope() + scope.setVariable('my_var', 20) + context = QgsExpressionContext() + context.appendScope(scope) + mapsettings.setExpressionContext(context) + + self.assertTrue( + self.render_map_settings_check( + 'Render heatmap with data defined radius', + 'data_defined_radius', + mapsettings) + ) + + def test_data_defined_maximum(self): + """ + Test heatmap rendering with data defined maximum value + """ + layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, 'points.shp'), 'Points', 'ogr') + self.assertTrue(layer.isValid()) + + renderer = QgsHeatmapRenderer() + renderer.setColorRamp( + QgsGradientColorRamp(QColor(255, 0, 0), QColor(255, 200, 100))) + renderer.setRadius(20) + renderer.setRadiusUnit(Qgis.RenderUnit.Millimeters) + renderer.setDataDefinedProperty( + QgsFeatureRenderer.Property.HeatmapMaximum, + QgsProperty.fromExpression('@my_var * 2') + ) + + layer.setRenderer(renderer) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setExtent(layer.extent()) + mapsettings.setLayers([layer]) + scope = QgsExpressionContextScope() + scope.setVariable('my_var', 0.5) + context = QgsExpressionContext() + context.appendScope(scope) + mapsettings.setExpressionContext(context) + + self.assertTrue( + self.render_map_settings_check( + 'Render heatmap with data defined maximum', + 'data_defined_maximum', + mapsettings) + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/src/python/test_qgslayoutmapgrid.py b/tests/src/python/test_qgslayoutmapgrid.py index 13acb4e1dbb94..9b7bea23d7342 100644 --- a/tests/src/python/test_qgslayoutmapgrid.py +++ b/tests/src/python/test_qgslayoutmapgrid.py @@ -1097,6 +1097,67 @@ def testCrsChanged(self): grid.setCrs(QgsCoordinateReferenceSystem("EPSG:3111")) self.assertEqual(len(spy), 9) + def testCopyGrid(self): + layout = QgsLayout(QgsProject.instance()) + layout.initializeDefaults() + map = QgsLayoutItemMap(layout) + map.attemptSetSceneRect(QRectF(20, 20, 200, 100)) + map.setFrameEnabled(True) + map.setBackgroundColor(QColor(150, 100, 100)) + layout.addLayoutItem(map) + myRectangle = QgsRectangle(781662.375, 3339523.125, 793062.375, 3345223.125) + map.setExtent(myRectangle) + map.grid().setEnabled(True) + map.grid().setIntervalX(2000) + map.grid().setIntervalY(2000) + map.grid().setAnnotationEnabled(True) + map.grid().setGridLineColor(QColor(0, 255, 0)) + map.grid().setGridLineWidth(0.5) + + format = QgsTextFormat.fromQFont(getTestFont("Bold", 20)) + format.setColor(QColor(255, 0, 0)) + format.setOpacity(150 / 255) + map.grid().setAnnotationTextFormat(format) + + map.grid().setAnnotationPrecision(0) + map.grid().setAnnotationDisplay( + QgsLayoutItemMapGrid.DisplayMode.HideAll, QgsLayoutItemMapGrid.BorderSide.Left + ) + map.grid().setAnnotationPosition( + QgsLayoutItemMapGrid.AnnotationPosition.OutsideMapFrame, QgsLayoutItemMapGrid.BorderSide.Right + ) + map.grid().setAnnotationDisplay( + QgsLayoutItemMapGrid.DisplayMode.HideAll, QgsLayoutItemMapGrid.BorderSide.Top + ) + map.grid().setAnnotationPosition( + QgsLayoutItemMapGrid.AnnotationPosition.OutsideMapFrame, QgsLayoutItemMapGrid.BorderSide.Bottom + ) + map.grid().setAnnotationDirection( + QgsLayoutItemMapGrid.AnnotationDirection.Horizontal, QgsLayoutItemMapGrid.BorderSide.Right + ) + map.grid().setAnnotationDirection( + QgsLayoutItemMapGrid.AnnotationDirection.Horizontal, QgsLayoutItemMapGrid.BorderSide.Bottom + ) + map.grid().setBlendMode(QPainter.CompositionMode.CompositionMode_Overlay) + map.updateBoundingRect() + + map.grid().dataDefinedProperties().setProperty( + QgsLayoutObject.DataDefinedProperty.MapGridLabelDistance, QgsProperty.fromValue(10) + ) + map.grid().refresh() + + source_grid = map.grid() + grid = QgsLayoutItemMapGrid("testGrid", map) + grid.copyProperties(source_grid) + map.grids().removeGrid(source_grid.id()) + self.assertEqual(map.grids().size(), 0) + map.grids().addGrid(grid) + map.grid().refresh() + self.assertTrue( + self.render_layout_check("composermap_datadefined_annotationdistance", + layout) + ) + if __name__ == "__main__": unittest.main() diff --git a/tests/src/python/test_qgslinestring.py b/tests/src/python/test_qgslinestring.py index 0191088a6e709..86bb1b2c8d7a5 100644 --- a/tests/src/python/test_qgslinestring.py +++ b/tests/src/python/test_qgslinestring.py @@ -9,10 +9,10 @@ __date__ = '12/09/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA - -from qgis.core import QgsLineString, QgsPoint import unittest +import math + +from qgis.core import Qgis, QgsLineString, QgsPoint from qgis.testing import start_app, QgisTestCase start_app() @@ -117,6 +117,108 @@ def testFuzzyComparisons(self): self.assertTrue(geom1.fuzzyEqual(geom2, epsilon)) self.assertTrue(geom1.fuzzyDistanceEqual(geom2, epsilon)) + def testInterpolateM(self): + line = QgsLineString() + + # empty line + self.assertIsNone(line.interpolateM()) + + # not m + line.fromWkt('LineString (10 6, 20 6)') + self.assertIsNone(line.interpolateM()) + + # single point + line.fromWkt('LineStringM (10 6 0)') + self.assertEqual(line.interpolateM().asWkt(), 'LineStringM (10 6 0)') + + # valid cases + line.fromWkt('LineStringM (10 6 0, 20 6 10)') + self.assertEqual(line.interpolateM().asWkt(), 'LineStringM (10 6 0, 20 6 10)') + + line.fromWkt('LineStringM (10 6 1, 20 6 0, 10 10 1)') + self.assertEqual(line.interpolateM().asWkt(), 'LineStringM (10 6 1, 20 6 0, 10 10 1)') + + line.fromWkt('LineStringZM (10 6 1 5, 20 6 0 6, 10 10 1 7)') + self.assertEqual(line.interpolateM().asWkt(), 'LineStringZM (10 6 1 5, 20 6 0 6, 10 10 1 7)') + + # no valid m values + line = QgsLineString([[10, 6, 1, math.nan], [20, 6, 2, math.nan]]) + self.assertEqual(line.wkbType(), Qgis.WkbType.LineStringZM) + self.assertIsNone(line.interpolateM()) + + # missing m values at start of line + line = QgsLineString([[10, 6, 1, math.nan], [20, 6, 2, 13], [20, 10, 5, 17]]) + self.assertEqual(line.interpolateM().asWkt(), 'LineStringZM (10 6 1 13, 20 6 2 13, 20 10 5 17)') + + line = QgsLineString([[10, 6, 1, math.nan], [20, 6, 2, math.nan], [20, 10, 5, 17]]) + self.assertEqual(line.interpolateM().asWkt(), 'LineStringZM (10 6 1 17, 20 6 2 17, 20 10 5 17)') + + line = QgsLineString([[10, 6, 1, math.nan], [20, 6, 2, math.nan], [20, 10, 5, 17], [22, 10, 15, 19]]) + self.assertEqual(line.interpolateM().asWkt(), 'LineStringZM (10 6 1 17, 20 6 2 17, 20 10 5 17, 22 10 15 19)') + + # missing m values at end of line + line = QgsLineString([[20, 6, 2, 13], [20, 10, 5, 17], [10, 6, 1, math.nan]]) + self.assertEqual(line.interpolateM().asWkt(), 'LineStringZM (20 6 2 13, 20 10 5 17, 10 6 1 17)') + + line = QgsLineString([[20, 10, 5, 17], [10, 6, 1, math.nan], [20, 6, 2, math.nan]]) + self.assertEqual(line.interpolateM().asWkt(), 'LineStringZM (20 10 5 17, 10 6 1 17, 20 6 2 17)') + + line = QgsLineString([[20, 10, 5, 17], [22, 10, 15, 19], [10, 6, 1, math.nan], [20, 6, 2, math.nan]]) + self.assertEqual(line.interpolateM().asWkt(), 'LineStringZM (20 10 5 17, 22 10 15 19, 10 6 1 19, 20 6 2 19)') + + # missing m values in middle of line + line = QgsLineString([[20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, 27]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (20 10 5 17, 30 10 12 19.5, 30 40 17 27)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (20 10 5 17, 30 10 12 19.86, 30 40 17 27)') + + line = QgsLineString([[20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, math.nan], [20, 40, 19, 27]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (20 10 5 17, 30 10 12 19, 30 40 17 25, 20 40 19 27)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (20 10 5 17, 30 10 12 19.31, 30 40 17 25.07, 20 40 19 27)') + + line = QgsLineString([[20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, math.nan], [20, 40, 19, math.nan], [20, 50, 21, 29]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (20 10 5 17, 30 10 12 19, 30 40 17 25, 20 40 19 27, 20 50 21 29)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (20 10 5 17, 30 10 12 19.32, 30 40 17 25.12, 20 40 19 27.06, 20 50 21 29)') + + # multiple missing chunks + line = QgsLineString([[20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, math.nan], [20, 40, 19, 27], [20, 50, 21, math.nan], [25, 50, 22, 30]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (20 10 5 17, 30 10 12 19, 30 40 17 25, 20 40 19 27, 20 50 21 29, 25 50 22 30)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (20 10 5 17, 30 10 12 19.31, 30 40 17 25.07, 20 40 19 27, 20 50 21 29, 25 50 22 30)') + + line = QgsLineString([[20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, math.nan], [20, 40, 19, 27], [20, 50, 21, math.nan], [25, 50, 22, math.nan], [25, 55, 22, math.nan], [30, 55, 22, 37]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (20 10 5 17, 30 10 12 19, 30 40 17 25, 20 40 19 27, 20 50 21 31, 25 50 22 33, 25 55 22 35, 30 55 22 37)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (20 10 5 17, 30 10 12 19.31, 30 40 17 25.07, 20 40 19 27, 20 50 21 31.03, 25 50 22 33.05, 25 55 22 35.02, 30 55 22 37)') + + # missing at start and middle + line = QgsLineString([[10, 10, 1, math.nan], [10, 12, 2, math.nan], [20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, math.nan], [20, 40, 19, math.nan], [20, 50, 21, 29]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (10 10 1 17, 10 12 2 17, 20 10 5 17, 30 10 12 19, 30 40 17 25, 20 40 19 27, 20 50 21 29)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (10 10 1 17, 10 12 2 17, 20 10 5 17, 30 10 12 19.72, 30 40 17 25.27, 20 40 19 27.14, 20 50 21 29)') + + # missing at middle and end + line = QgsLineString([[20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, math.nan], [20, 40, 19, 27], [20, 50, 21, math.nan], [25, 50, 22, math.nan], [25, 55, 22, math.nan]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (20 10 5 17, 30 10 12 19, 30 40 17 25, 20 40 19 27, 20 50 21 27, 25 50 22 27, 25 55 22 27)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (20 10 5 17, 30 10 12 19.31, 30 40 17 25.07, 20 40 19 27, 20 50 21 27, 25 50 22 27, 25 55 22 27)') + + # missing at start, middle, end + line = QgsLineString([[5, 10, 15, math.nan], [6, 11, 16, math.nan], [20, 10, 5, 17], [30, 10, 12, math.nan], [30, 40, 17, math.nan], [20, 40, 19, 27], [20, 50, 21, math.nan], [25, 50, 22, math.nan], [25, 55, 22, math.nan]]) + # 2d distance + self.assertEqual(line.interpolateM(False).asWkt(), 'LineStringZM (5 10 15 17, 6 11 16 17, 20 10 5 17, 30 10 12 19, 30 40 17 25, 20 40 19 27, 20 50 21 27, 25 50 22 27, 25 55 22 27)') + # 3d distance + self.assertEqual(line.interpolateM(True).asWkt(2), 'LineStringZM (5 10 15 17, 6 11 16 17, 20 10 5 17, 30 10 12 19.05, 30 40 17 25, 20 40 19 27, 20 50 21 27, 25 50 22 27, 25 55 22 27)') + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_qgsmeshlayer.py b/tests/src/python/test_qgsmeshlayer.py new file mode 100644 index 0000000000000..ac023360fddfb --- /dev/null +++ b/tests/src/python/test_qgsmeshlayer.py @@ -0,0 +1,68 @@ +"""QGIS Unit tests for QgsMeshLayer + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" + +from qgis.core import ( + QgsMeshLayer, + QgsMeshDatasetIndex +) +import unittest +from qgis.testing import start_app, QgisTestCase + +start_app() + + +class TestQgsMeshLayer(QgisTestCase): + + def test_dataset_group_metadata(self): + """ + Test datasetGroupMetadata + """ + layer = QgsMeshLayer( + self.get_test_data_path('mesh/netcdf_parent_quantity.nc').as_posix(), + 'mesh', + 'mdal' + ) + self.assertTrue(layer.isValid()) + + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(0)).name(), + 'air_temperature_height:10') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(0)).parentQuantityName(), + 'air_temperature_height') + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(1)).name(), + 'air_temperature_height:20') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(1)).parentQuantityName(), + 'air_temperature_height') + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(2)).name(), + 'air_temperature_height:30') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(2)).parentQuantityName(), + 'air_temperature_height') + self.assertEqual( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(3)).name(), + 'air_temperature_height:5') + self.assertEqual( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(3)).parentQuantityName(), + 'air_temperature_height') + self.assertFalse( + layer.datasetGroupMetadata(QgsMeshDatasetIndex(4)).name()) + self.assertFalse( + layer.datasetGroupMetadata( + QgsMeshDatasetIndex(4)).parentQuantityName()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgsmeshlayerrenderer.py b/tests/src/python/test_qgsmeshlayerrenderer.py index b8e44a14afe1e..2668064018f74 100644 --- a/tests/src/python/test_qgsmeshlayerrenderer.py +++ b/tests/src/python/test_qgsmeshlayerrenderer.py @@ -81,6 +81,72 @@ def test_render_fixed_elevation_range_with_z_range_filter(self): map_settings) ) + def test_render_fixed_range_per_group_with_z_range_filter(self): + """ + Test rendering a mesh with a fixed range per group when + map settings has a z range filter + """ + layer = QgsMeshLayer( + self.get_test_data_path( + 'mesh/netcdf_parent_quantity.nc').as_posix(), + 'mesh', + 'mdal' + ) + self.assertTrue(layer.isValid()) + + # set layer as elevation enabled + layer.elevationProperties().setMode( + Qgis.MeshElevationMode.FixedRangePerGroup + ) + layer.elevationProperties().setFixedRangePerGroup( + {1: QgsDoubleRange(33, 38), + 2: QgsDoubleRange(35, 40), + 3: QgsDoubleRange(40, 48)} + ) + + map_settings = QgsMapSettings() + map_settings.setOutputSize(QSize(400, 400)) + map_settings.setOutputDpi(96) + map_settings.setDestinationCrs(layer.crs()) + map_settings.setExtent(layer.extent()) + map_settings.setLayers([layer]) + + # no filter on map settings + map_settings.setZRange(QgsDoubleRange()) + self.assertTrue( + self.render_map_settings_check( + 'No Z range filter on map settings, elevation range per group', + 'elevation_range_per_group_no_filter', + map_settings) + ) + + # map settings range matches group 3 only + map_settings.setZRange(QgsDoubleRange(40.5, 49.5)) + self.assertTrue( + self.render_map_settings_check( + 'Z range filter on map settings matches group 3 only', + 'elevation_range_per_group_match3', + map_settings) + ) + + # map settings range matches group 1 and 2 + map_settings.setZRange(QgsDoubleRange(33, 39.5)) + self.assertTrue( + self.render_map_settings_check( + 'Z range filter on map settings matches group 1 and 2', + 'elevation_range_per_group_match1and2', + map_settings) + ) + + # map settings range excludes layer's range + map_settings.setZRange(QgsDoubleRange(130, 135)) + self.assertTrue( + self.render_map_settings_check( + 'Z range filter on map settings outside of layer group ranges', + 'fixed_elevation_range_excluded', + map_settings) + ) + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_qgspallabeling_placement.py b/tests/src/python/test_qgspallabeling_placement.py index f6ba6319faf34..29561cf07c30c 100644 --- a/tests/src/python/test_qgspallabeling_placement.py +++ b/tests/src/python/test_qgspallabeling_placement.py @@ -745,6 +745,18 @@ def test_point_dd_ordered_placement1(self): self.lyr.dataDefinedProperties().setProperty(QgsPalLayerSettings.Property.PredefinedPositionOrder, QgsProperty()) self.layer = None + def test_point_ordered_placement_over_point(self): + # Test ordered placements using over point placement + self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement') + self._TestMapSettings = self.cloneMapSettings(self._MapSettings) + self.lyr.placement = QgsPalLayerSettings.Placement.OrderedPositionsAroundPoint + self.lyr.dist = 2 + self.lyr.dataDefinedProperties().setProperty(QgsPalLayerSettings.Property.PredefinedPositionOrder, QgsProperty.fromExpression("'O'")) + self.checkTest() + self.removeMapLayer(self.layer) + self.lyr.dataDefinedProperties().setProperty(QgsPalLayerSettings.Property.PredefinedPositionOrder, QgsProperty()) + self.layer = None + def test_point_ordered_symbol_bound_offset(self): # Test ordered placements for point using symbol bounds offset self.layer = TestQgsPalLabeling.loadFeatureLayer('point_ordered_placement') diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 44db1a908b847..7513d1bd1c1a0 100644 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -4547,6 +4547,46 @@ def test_split_policies(self): self.assertEqual(vl2.fields()[3].splitPolicy(), Qgis.FieldDomainSplitPolicy.GeometryRatio) + def test_duplicate_policies(self): + vl = QgsVectorLayer('Point?crs=epsg:3111&field=field_default:integer&field=field_dupe:integer&field=field_unset:integer', 'test', 'memory') + self.assertTrue(vl.isValid()) + + with self.assertRaises(KeyError): + vl.setFieldDuplicatePolicy(-1, Qgis.FieldDuplicatePolicy.DefaultValue) + with self.assertRaises(KeyError): + vl.setFieldDuplicatePolicy(4, Qgis.FieldDuplicatePolicy.DefaultValue) + + vl.setFieldDuplicatePolicy(0, Qgis.FieldDuplicatePolicy.DefaultValue) + vl.setFieldDuplicatePolicy(1, Qgis.FieldDuplicatePolicy.Duplicate) + vl.setFieldDuplicatePolicy(2, Qgis.FieldDuplicatePolicy.UnsetField) + + self.assertEqual(vl.fields()[0].duplicatePolicy(), + Qgis.FieldDuplicatePolicy.DefaultValue) + self.assertEqual(vl.fields()[1].duplicatePolicy(), + Qgis.FieldDuplicatePolicy.Duplicate) + self.assertEqual(vl.fields()[2].duplicatePolicy(), + Qgis.FieldDuplicatePolicy.UnsetField) + + p = QgsProject() + p.addMapLayer(vl) + + # test saving and restoring split policies + with tempfile.TemporaryDirectory() as temp: + self.assertTrue(p.write(temp + '/test.qgs')) + + p2 = QgsProject() + self.assertTrue(p2.read(temp + '/test.qgs')) + + vl2 = list(p2.mapLayers().values())[0] + self.assertEqual(vl2.name(), vl.name()) + + self.assertEqual(vl2.fields()[0].duplicatePolicy(), + Qgis.FieldDuplicatePolicy.DefaultValue) + self.assertEqual(vl2.fields()[1].duplicatePolicy(), + Qgis.FieldDuplicatePolicy.Duplicate) + self.assertEqual(vl2.fields()[2].duplicatePolicy(), + Qgis.FieldDuplicatePolicy.UnsetField) + def test_selection_properties(self): vl = QgsVectorLayer( 'Point?crs=epsg:3111&field=field_default:integer&field=field_dupe:integer&field=field_unset:integer&field=field_ratio:integer', diff --git a/tests/src/python/test_qgsvectorlayerutils.py b/tests/src/python/test_qgsvectorlayerutils.py index 5e5e9bece97ab..a995a0da48aa3 100644 --- a/tests/src/python/test_qgsvectorlayerutils.py +++ b/tests/src/python/test_qgsvectorlayerutils.py @@ -15,6 +15,7 @@ from qgis.PyQt.QtCore import QVariant from qgis.core import ( NULL, + Qgis, QgsDefaultValue, QgsFeature, QgsField, @@ -478,29 +479,53 @@ def testCreateFeature(self): self.assertEqual(f.attributes(), ['test_5', 132, NULL]) def testDuplicateFeature(self): - """ test duplicating a feature """ + """ test duplicating a feature with relations """ project = QgsProject().instance() # LAYERS # - add first layer (parent) - layer1 = QgsVectorLayer("Point?field=fldtxt:string&field=pkid:integer", + layer1 = QgsVectorLayer("Point?field=fldtxt:string&field=pkid:integer&field=policycheck1value:text&field=policycheck2value:text&field=policycheck3value:text", "parentlayer", "memory") # > check first layer (parent) self.assertTrue(layer1.isValid()) - # - set the value for the copy + # - set the default values for pk and policy check and the field policy layer1.setDefaultValueDefinition(1, QgsDefaultValue("rand(1000,2000)")) + layer1.setDefaultValueDefinition(2, QgsDefaultValue("'Def Blabla L1'")) + layer1.setDefaultValueDefinition(3, QgsDefaultValue("'Def Blabla L1'")) + layer1.setDefaultValueDefinition(4, QgsDefaultValue("'Def Blabla L1'")) + layer1.setFieldDuplicatePolicy(2, Qgis.FieldDuplicatePolicy.Duplicate) + layer1.setFieldDuplicatePolicy(3, Qgis.FieldDuplicatePolicy.DefaultValue) + layer1.setFieldDuplicatePolicy(4, Qgis.FieldDuplicatePolicy.UnsetField) # > check first layer (parent) self.assertTrue(layer1.isValid()) # - add second layer (child) - layer2 = QgsVectorLayer("Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer", + layer2 = QgsVectorLayer("Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer&field=policycheck1value:text&field=policycheck2value:text&field=policycheck3value:text", "childlayer1", "memory") # > check second layer (child) self.assertTrue(layer2.isValid()) - # - add second layer (child) - layer3 = QgsVectorLayer("Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer", - "childlayer2", "memory") + # - set the default values for pk and policy check and the field policy + layer2.setDefaultValueDefinition(3, QgsDefaultValue("'Def Blabla L2'")) + layer2.setDefaultValueDefinition(4, QgsDefaultValue("'Def Blabla L2'")) + layer2.setDefaultValueDefinition(5, QgsDefaultValue("'Def Blabla L2'")) + layer2.setFieldDuplicatePolicy(3, Qgis.FieldDuplicatePolicy.Duplicate) + layer2.setFieldDuplicatePolicy(4, Qgis.FieldDuplicatePolicy.DefaultValue) + layer2.setFieldDuplicatePolicy(5, Qgis.FieldDuplicatePolicy.UnsetField) # > check second layer (child) + self.assertTrue(layer2.isValid()) + # - add third layer (child) + layer3 = QgsVectorLayer("Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer&field=policycheck1value:text&field=policycheck2value:text&field=policycheck3value:text", + "childlayer2", "memory") + # > check third layer (child) + self.assertTrue(layer3.isValid()) + # - set the default values for pk and policy check and the field policy + layer3.setDefaultValueDefinition(3, QgsDefaultValue("'Def Blabla L3'")) + layer3.setDefaultValueDefinition(4, QgsDefaultValue("'Def Blabla L3'")) + layer3.setDefaultValueDefinition(5, QgsDefaultValue("'Def Blabla L3'")) + layer3.setFieldDuplicatePolicy(3, Qgis.FieldDuplicatePolicy.Duplicate) + layer3.setFieldDuplicatePolicy(4, Qgis.FieldDuplicatePolicy.DefaultValue) + layer3.setFieldDuplicatePolicy(5, Qgis.FieldDuplicatePolicy.UnsetField) + # > check third layer (child) self.assertTrue(layer3.isValid()) # - add layers project.addMapLayers([layer1, layer2, layer3]) @@ -509,34 +534,34 @@ def testDuplicateFeature(self): # - add 2 features on layer1 (parent) l1f1orig = QgsFeature() l1f1orig.setFields(layer1.fields()) - l1f1orig.setAttributes(["F_l1f1", 100]) + l1f1orig.setAttributes(["F_l1f1", 100, 'Orig Blabla L1', 'Orig Blabla L1', 'Orig Blabla L1']) l1f2orig = QgsFeature() l1f2orig.setFields(layer1.fields()) - l1f2orig.setAttributes(["F_l1f2", 101]) + l1f2orig.setAttributes(["F_l1f2", 101, 'Orig Blabla L1', 'Orig Blabla L1', 'Orig Blabla L1']) # > check by adding features self.assertTrue(layer1.dataProvider().addFeatures([l1f1orig, l1f2orig])) # add 4 features on layer2 (child) l2f1orig = QgsFeature() l2f1orig.setFields(layer2.fields()) - l2f1orig.setAttributes(["F_l2f1", 201, 100]) + l2f1orig.setAttributes(["F_l2f1", 201, 100, 'Orig Blabla L2', 'Orig Blabla L2', 'Orig Blabla L2']) l2f2orig = QgsFeature() l2f2orig.setFields(layer2.fields()) - l2f2orig.setAttributes(["F_l2f2", 202, 100]) + l2f2orig.setAttributes(["F_l2f2", 202, 100, 'Orig Blabla L2', 'Orig Blabla L2', 'Orig Blabla L2']) l2f3orig = QgsFeature() l2f3orig.setFields(layer2.fields()) - l2f3orig.setAttributes(["F_l2f3", 203, 100]) + l2f3orig.setAttributes(["F_l2f3", 203, 100, 'Orig Blabla L2', 'Orig Blabla L2', 'Orig Blabla L2']) l2f4orig = QgsFeature() l2f4orig.setFields(layer2.fields()) - l2f4orig.setAttributes(["F_l2f4", 204, 101]) + l2f4orig.setAttributes(["F_l2f4", 204, 101, 'Orig Blabla L2', 'Orig Blabla L2', 'Orig Blabla L2']) # > check by adding features self.assertTrue(layer2.dataProvider().addFeatures([l2f1orig, l2f2orig, l2f3orig, l2f4orig])) # add 2 features on layer3 (child) l3f1orig = QgsFeature() l3f1orig.setFields(layer3.fields()) - l3f1orig.setAttributes(["F_l3f1", 201, 100]) + l3f1orig.setAttributes(["F_l3f1", 301, 100, 'Orig Blabla L3', 'Orig Blabla L3', 'Orig Blabla L3']) l3f2orig = QgsFeature() l3f2orig.setFields(layer2.fields()) - l3f2orig.setAttributes(["F_l3f2", 202, 100]) + l3f2orig.setAttributes(["F_l3f2", 302, 100, 'Orig Blabla L3', 'Orig Blabla L3', 'Orig Blabla L3']) # > check by adding features self.assertTrue(layer3.dataProvider().addFeatures([l3f1orig, l3f2orig])) @@ -618,16 +643,30 @@ def testDuplicateFeature(self): results = QgsVectorLayerUtils.duplicateFeature(layer1, l1f1orig, project, 0) # > check if name is name of duplicated (pk is different) + # > and duplicate policy is concerned result_feature = results[0] self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) + self.assertEqual(result_feature.attribute('policycheck1value'), 'Orig Blabla L1') # duplicated + self.assertEqual(result_feature.attribute('policycheck2value'), 'Def Blabla L1') # default Value + self.assertEqual(result_feature.attribute('policycheck3value'), None) # unset # > check duplicated children occurred on both layers self.assertEqual(len(results[1].layers()), 2) idx = results[1].layers().index(layer2) self.assertEqual(results[1].layers()[idx], layer2) self.assertTrue(results[1].duplicatedFeatures(layer2)) + for child_fid in results[1].duplicatedFeatures(layer2): + child_feature = layer2.getFeature(child_fid) + self.assertEqual(child_feature.attribute('policycheck1value'), 'Orig Blabla L2') # duplicated + self.assertEqual(child_feature.attribute('policycheck2value'), 'Def Blabla L2') # default Value + self.assertEqual(child_feature.attribute('policycheck3value'), None) # unset idx = results[1].layers().index(layer3) self.assertEqual(results[1].layers()[idx], layer3) self.assertTrue(results[1].duplicatedFeatures(layer3)) + for child_fid in results[1].duplicatedFeatures(layer3): + child_feature = layer3.getFeature(child_fid) + self.assertEqual(child_feature.attribute('policycheck1value'), 'Orig Blabla L3') # duplicated + self.assertEqual(child_feature.attribute('policycheck2value'), 'Def Blabla L3') # default Value + self.assertEqual(child_feature.attribute('policycheck3value'), None) # unset ''' # testoutput 2 diff --git a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_flipped_horizontal_icon/expected_color_ramp_legend_node_flipped_horizontal_icon_mask.png b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_flipped_horizontal_icon/expected_color_ramp_legend_node_flipped_horizontal_icon_mask.png index d33c15e8a0e59..677c94a0bf09b 100644 Binary files a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_flipped_horizontal_icon/expected_color_ramp_legend_node_flipped_horizontal_icon_mask.png and b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_flipped_horizontal_icon/expected_color_ramp_legend_node_flipped_horizontal_icon_mask.png differ diff --git a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_horizontal_icon/expected_color_ramp_legend_node_horizontal_icon_mask.png b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_horizontal_icon/expected_color_ramp_legend_node_horizontal_icon_mask.png index b4c264c442abe..3f7a5dd5f7c96 100644 Binary files a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_horizontal_icon/expected_color_ramp_legend_node_horizontal_icon_mask.png and b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_horizontal_icon/expected_color_ramp_legend_node_horizontal_icon_mask.png differ diff --git a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_icon/set1/expected_color_ramp_legend_node_icon_mask.png b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_icon/set1/expected_color_ramp_legend_node_icon_mask.png index b648b4f094ae0..099bacb032447 100644 Binary files a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_icon/set1/expected_color_ramp_legend_node_icon_mask.png and b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_icon/set1/expected_color_ramp_legend_node_icon_mask.png differ diff --git a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_prefix_suffix_icon/set1/expected_color_ramp_legend_node_prefix_suffix_icon_mask.png b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_prefix_suffix_icon/set1/expected_color_ramp_legend_node_prefix_suffix_icon_mask.png index 4bf39b9e5db4e..27e5ee08585a3 100644 Binary files a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_prefix_suffix_icon/set1/expected_color_ramp_legend_node_prefix_suffix_icon_mask.png and b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_prefix_suffix_icon/set1/expected_color_ramp_legend_node_prefix_suffix_icon_mask.png differ diff --git a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_settings_icon/set1/expected_color_ramp_legend_node_settings_icon_mask.png b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_settings_icon/set1/expected_color_ramp_legend_node_settings_icon_mask.png index eac542a38924b..0c4f0bc185026 100644 Binary files a/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_settings_icon/set1/expected_color_ramp_legend_node_settings_icon_mask.png and b/tests/testdata/control_images/color_ramp_legend_node/expected_color_ramp_legend_node_settings_icon/set1/expected_color_ramp_legend_node_settings_icon_mask.png differ diff --git a/tests/testdata/control_images/expected_pal_placement/sp_point_ordered_placement_over_point/sp_point_ordered_placement_over_point.png b/tests/testdata/control_images/expected_pal_placement/sp_point_ordered_placement_over_point/sp_point_ordered_placement_over_point.png new file mode 100644 index 0000000000000..175d28a00bfa5 Binary files /dev/null and b/tests/testdata/control_images/expected_pal_placement/sp_point_ordered_placement_over_point/sp_point_ordered_placement_over_point.png differ diff --git a/tests/testdata/control_images/heatmap_renderer/expected_data_defined_maximum/expected_data_defined_maximum.png b/tests/testdata/control_images/heatmap_renderer/expected_data_defined_maximum/expected_data_defined_maximum.png new file mode 100644 index 0000000000000..afd19ebd49c96 Binary files /dev/null and b/tests/testdata/control_images/heatmap_renderer/expected_data_defined_maximum/expected_data_defined_maximum.png differ diff --git a/tests/testdata/control_images/heatmap_renderer/expected_data_defined_radius/expected_data_defined_radius.png b/tests/testdata/control_images/heatmap_renderer/expected_data_defined_radius/expected_data_defined_radius.png new file mode 100644 index 0000000000000..d382b7f854abb Binary files /dev/null and b/tests/testdata/control_images/heatmap_renderer/expected_data_defined_radius/expected_data_defined_radius.png differ diff --git a/tests/testdata/control_images/heatmap_renderer/expected_render_heatmap/expected_render_heatmap.png b/tests/testdata/control_images/heatmap_renderer/expected_render_heatmap/expected_render_heatmap.png new file mode 100644 index 0000000000000..c00acc1659dcf Binary files /dev/null and b/tests/testdata/control_images/heatmap_renderer/expected_render_heatmap/expected_render_heatmap.png differ diff --git a/tests/testdata/control_images/legend/expected_heatmap/expected_heatmap.png b/tests/testdata/control_images/legend/expected_heatmap/expected_heatmap.png new file mode 100644 index 0000000000000..dcb45625bd2b6 Binary files /dev/null and b/tests/testdata/control_images/legend/expected_heatmap/expected_heatmap.png differ diff --git a/tests/testdata/control_images/legend/expected_heatmap/expected_heatmap_mask.png b/tests/testdata/control_images/legend/expected_heatmap/expected_heatmap_mask.png new file mode 100644 index 0000000000000..01071994c90c3 Binary files /dev/null and b/tests/testdata/control_images/legend/expected_heatmap/expected_heatmap_mask.png differ diff --git a/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match1and2/expected_elevation_range_per_group_match1and2.png b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match1and2/expected_elevation_range_per_group_match1and2.png new file mode 100644 index 0000000000000..a318096cbc569 Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match1and2/expected_elevation_range_per_group_match1and2.png differ diff --git a/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match3/expected_elevation_range_per_group_match3.png b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match3/expected_elevation_range_per_group_match3.png new file mode 100644 index 0000000000000..4a218535db07b Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_match3/expected_elevation_range_per_group_match3.png differ diff --git a/tests/testdata/control_images/mesh/expected_elevation_range_per_group_no_filter/expected_elevation_range_per_group_no_filter.png b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_no_filter/expected_elevation_range_per_group_no_filter.png new file mode 100644 index 0000000000000..3e98a42c40f8e Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_elevation_range_per_group_no_filter/expected_elevation_range_per_group_no_filter.png differ diff --git a/tests/testdata/control_images/mesh/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs.png b/tests/testdata/control_images/mesh/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs.png new file mode 100644 index 0000000000000..4d52273c26489 Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs.png differ diff --git a/tests/testdata/control_images/mesh/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45.png b/tests/testdata/control_images/mesh/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45.png new file mode 100644 index 0000000000000..4470df0e83b0e Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45/expected_quad_and_triangle_vertex_vector_user_grid_dataset_wind_barbs_rotated_45.png differ diff --git a/tests/testdata/mesh/netcdf_parent_quantity.nc b/tests/testdata/mesh/netcdf_parent_quantity.nc new file mode 100644 index 0000000000000..1d3a88195ea16 Binary files /dev/null and b/tests/testdata/mesh/netcdf_parent_quantity.nc differ diff --git a/tests/testdata/raster/gtiff_subdataset_tags.tif b/tests/testdata/raster/gtiff_subdataset_tags.tif new file mode 100644 index 0000000000000..dcbb232185625 Binary files /dev/null and b/tests/testdata/raster/gtiff_subdataset_tags.tif differ