diff --git a/.ci/travis/linux/docker-build-test.sh b/.ci/travis/linux/docker-build-test.sh index 761810d14534..4f8a7ffb88ba 100755 --- a/.ci/travis/linux/docker-build-test.sh +++ b/.ci/travis/linux/docker-build-test.sh @@ -40,6 +40,7 @@ echo "${bold}Running cmake...${endbold}" cmake \ -GNinja \ -DWITH_3D=ON \ + -DWITH_QUICK=ON \ -DWITH_STAGED_PLUGINS=ON \ -DWITH_GRASS=OFF \ -DSUPPRESS_QT_WARNINGS=ON \ diff --git a/.docker/qgis3-build-deps.dockerfile b/.docker/qgis3-build-deps.dockerfile index a620f726c3bd..6c05693e4e87 100644 --- a/.docker/qgis3-build-deps.dockerfile +++ b/.docker/qgis3-build-deps.dockerfile @@ -33,11 +33,17 @@ RUN apt-get update \ libqca-qt5-2-dev \ libqca-qt5-2-plugins \ libqt53drender5 \ + libqt5concurrent5 \ libqt5opengl5-dev \ + libqt5positioning5 \ + libqt5qml5 \ + libqt5quick5 \ + libqt5quickcontrols2-5 \ libqt5scintilla2-dev \ libqt5sql5-sqlite \ libqt5svg5-dev \ libqt5webkit5-dev \ + libqt5xml5 \ libqt5xmlpatterns5-dev \ libqwt-qt5-dev \ libspatialindex-dev \ diff --git a/.gitignore b/.gitignore index 34f7fed68ebd..900ac2d0a1d3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ desktop.ini doc/INSTALL.tex i18n/*.qm Makefile +*.pro.user +*.stash ms-windows/*.exe* ms-windows/Installer-Files/postinstall.bat ms-windows/Installer-Files/preremove.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index a0f6bd6131e0..6e66cca60f8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,8 @@ IF(WITH_CORE) SET (WITH_3D FALSE CACHE BOOL "Determines whether QGIS 3D library should be built") + SET (WITH_QUICK FALSE CACHE BOOL "Determines whether QGIS Quick library should be built") + # server disabled default because it needs FastCGI (which is optional dependency) SET (WITH_SERVER FALSE CACHE BOOL "Determines whether QGIS server should be built") IF(WITH_SERVER) @@ -302,6 +304,15 @@ IF(WITH_CORE) ENDIF (WITH_3D) INCLUDE("cmake/modules/ECMQt4To5Porting.cmake") MESSAGE(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") + IF (WITH_QUICK) + FIND_PACKAGE(Qt5Qml REQUIRED) + FIND_PACKAGE(Qt5Quick REQUIRED) + IF(${CMAKE_SYSTEM_NAME} MATCHES "Android") + FIND_PACKAGE(Qt5AndroidExtras) + ELSE(${CMAKE_SYSTEM_NAME} MATCHES "Android") + FIND_PACKAGE(QtQmlTools REQUIRED) + ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Android") + ENDIF (WITH_QUICK) IF(WITH_QTWEBKIT) SET(OPTIONAL_QTWEBKIT ${Qt5WebKitWidgets_LIBRARIES}) @@ -353,6 +364,9 @@ ENDIF(WITH_CORE) # build our version of astyle SET (WITH_ASTYLE FALSE CACHE BOOL "If you plan to contribute you should reindent with scripts/prepare-commit.sh (using 'our' astyle)") +# QML +SET(QML_IMPORT_PATH "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" CACHE PATH "QML directory for QML autocomplete") + ############################################################# # testing # whether unit tests should be build @@ -493,6 +507,7 @@ IF (WITH_CORE) SET (DEFAULT_DATA_SUBDIR .) SET (DEFAULT_PLUGIN_SUBDIR plugins) SET (DEFAULT_INCLUDE_SUBDIR include) + SET (DEFAULT_QML_SUBDIR qml) SET (DEFAULT_SERVER_MODULE_SUBDIR server) @@ -563,6 +578,7 @@ IF (WITH_CORE) SET (DEFAULT_PLUGIN_SUBDIR ../PlugIns/qgis) SET (QGIS_PLUGIN_SUBDIR_REV ../../MacOS) SET (DEFAULT_INCLUDE_SUBDIR include/qgis) + SET (DEFAULT_QML_SUBDIR qml) # Set server moodules path to DEFAULT_LIBEXEC_SUBDIR+'/server' SET (DEFAULT_SERVER_MODULE_SUBDIR ${DEFAULT_LIBEXEC_SUBDIR}/server) @@ -590,6 +606,7 @@ IF (WITH_CORE) SET (DEFAULT_LIBEXEC_SUBDIR lib${LIB_SUFFIX}/qgis) SET (DEFAULT_PLUGIN_SUBDIR lib${LIB_SUFFIX}/qgis/plugins) SET (DEFAULT_INCLUDE_SUBDIR include/qgis) + SET (DEFAULT_QML_SUBDIR qml) SET (DEFAULT_SERVER_MODULE_SUBDIR ${DEFAULT_LIBEXEC_SUBDIR}/server) ENDIF (APPLE) @@ -638,12 +655,13 @@ SET (QGIS_LIBEXEC_SUBDIR ${DEFAULT_LIBEXEC_SUBDIR} CACHE STRING "Subdirectory wh SET (QGIS_DATA_SUBDIR ${DEFAULT_DATA_SUBDIR} CACHE STRING "Subdirectory where QGIS data will be installed") SET (QGIS_PLUGIN_SUBDIR ${DEFAULT_PLUGIN_SUBDIR} CACHE STRING "Subdirectory where plugins will be installed") SET (QGIS_INCLUDE_SUBDIR ${DEFAULT_INCLUDE_SUBDIR} CACHE STRING "Subdirectory where header files will be installed") +SET (QGIS_QML_SUBDIR ${DEFAULT_QML_SUBDIR} CACHE STRING "Subdirectory where qml files/libraries will be installed") SET (QGIS_SERVER_MODULE_SUBDIR ${DEFAULT_SERVER_MODULE_SUBDIR} CACHE STRING "Subdirectory where server modules will be installed") # mark *_SUBDIR variables as advanced as this is not something # that an average user would use -MARK_AS_ADVANCED (QGIS_BIN_SUBDIR QGIS_CGIBIN_SUBDIR QGIS_LIB_SUBDIR QGIS_LIBEXEC_SUBDIR QGIS_DATA_SUBDIR QGIS_PLUGIN_SUBDIR QGIS_INCLUDE_SUBDIR) +MARK_AS_ADVANCED (QGIS_BIN_SUBDIR QGIS_CGIBIN_SUBDIR QGIS_LIB_SUBDIR QGIS_LIBEXEC_SUBDIR QGIS_DATA_SUBDIR QGIS_PLUGIN_SUBDIR QGIS_INCLUDE_SUBDIR QGIS_QML_SUBDIR) # full paths for the installation SET (QGIS_BIN_DIR ${QGIS_BIN_SUBDIR}) @@ -653,6 +671,7 @@ SET (QGIS_LIBEXEC_DIR ${QGIS_LIBEXEC_SUBDIR}) SET (QGIS_DATA_DIR ${QGIS_DATA_SUBDIR}) SET (QGIS_PLUGIN_DIR ${QGIS_PLUGIN_SUBDIR}) SET (QGIS_INCLUDE_DIR ${QGIS_INCLUDE_SUBDIR}) +SET (QGIS_QML_DIR ${QGIS_QML_SUBDIR}) SET (QGIS_SERVER_MODULE_DIR ${QGIS_SERVER_MODULE_SUBDIR}) diff --git a/cmake/FindQtQmlTools.cmake b/cmake/FindQtQmlTools.cmake new file mode 100644 index 000000000000..c0cbf50d9daf --- /dev/null +++ b/cmake/FindQtQmlTools.cmake @@ -0,0 +1,47 @@ +# Qt QML Tools +# ~~~~~~~~~~~~ +# +# To generate qmltypes files required by Qt Creator to allow QML code inspection +# (http://doc.qt.io/qtcreator/creator-qml-modules-with-plugins.html#generating-qmltypes-files) +# we need to have installed qmlplugindump unity (shipped with Qt 4.8 and later) +# http://doc.qt.io/qtcreator/creator-qml-modules-with-plugins.html#dumping-plugins-automatically +# +# Find the installed version of qmlplugindump utility. +# FindQtQmlTools should be called after Qt5 has been found +# +# This file defines the following variables: +# +# QMLPLUGINDUMP_FOUND - system has qmlplugindump +# QMLPLUGINDUMP_EXECUTABLE - Path to qmlplugindump executable +# +# Also defines MACRO to create qmltypes file, when QML directory is supplied +# +# Copyright (c) 2017, Peter Petrik +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +MACRO(FIND_QMLPLUGINDUMP) + IF(NOT QMLPLUGINDUMP_EXECUTABLE) + IF (MSVC) + FIND_PROGRAM(QMLPLUGINDUMP_EXECUTABLE qmlplugindump.exe) + ELSE (MSVC) + FIND_PROGRAM(QMLPLUGINDUMP_EXECUTABLE qmlplugindump) + ENDIF (MSVC) + ENDIF(NOT QMLPLUGINDUMP_EXECUTABLE) + + IF (QMLPLUGINDUMP_EXECUTABLE) + SET(QMLPLUGINDUMP_FOUND TRUE) + MESSAGE(STATUS "Found qmlplugindump: ${QMLPLUGINDUMP_EXECUTABLE}") + ELSE() + SET(QMLPLUGINDUMP_FOUND FALSE) + IF (QMLPLUGINDUMP_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find qmlplugindump") + ELSE (QMLPLUGINDUMP_FIND_REQUIRED) + MESSAGE(WARNING "Could not find qmlplugindump") + ENDIF (QMLPLUGINDUMP_FIND_REQUIRED) + ENDIF (QMLPLUGINDUMP_EXECUTABLE) +ENDMACRO(FIND_QMLPLUGINDUMP) + +IF (NOT QMLPLUGINDUMP_FOUND) + FIND_QMLPLUGINDUMP() +ENDIF (NOT QMLPLUGINDUMP_FOUND) diff --git a/cmake_templates/qgsconfig.h.in b/cmake_templates/qgsconfig.h.in index 67bbefe2af8e..60dc74e99382 100644 --- a/cmake_templates/qgsconfig.h.in +++ b/cmake_templates/qgsconfig.h.in @@ -25,6 +25,7 @@ #define QGIS_DATA_SUBDIR "${QGIS_DATA_SUBDIR}" #define QGIS_LIBEXEC_SUBDIR "${QGIS_LIBEXEC_SUBDIR}" #define QGIS_LIB_SUBDIR "${QGIS_LIB_SUBDIR}" +#define QGIS_QML_SUBDIR "${QGIS_QML_SUBDIR}" #define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" #define CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}" diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 7b1b300de2ec..0fced3aa0086 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -99,6 +99,8 @@ IF(WITH_APIDOC) ${CMAKE_SOURCE_DIR}/src/3d/symbols ${CMAKE_SOURCE_DIR}/src/3d/terrain ${CMAKE_SOURCE_DIR}/src/plugins + ${CMAKE_SOURCE_DIR}/src/quickgui + ${CMAKE_SOURCE_DIR}/src/quickgui/plugin ) IF(WITH_SERVER_PLUGINS) diff --git a/doc/CONTRIBUTORS b/doc/CONTRIBUTORS index 7ef112deb480..b18a2ac09848 100644 --- a/doc/CONTRIBUTORS +++ b/doc/CONTRIBUTORS @@ -71,6 +71,7 @@ Nikos Alexandris Paolo Cavallini Paul Blottiere Paul Ramsey +Peter Petrik Pierre Auckenthaler Raymond Nijssen Richard Duivenvoorde @@ -85,6 +86,7 @@ Tamas Szekeres Tom Russo Tyler Mitchell Vita Cizek +Viktor Sklencar Yann Chemin Yves Jacolin diff --git a/doc/index.dox b/doc/index.dox index a83088653419..688ec146a50d 100644 --- a/doc/index.dox +++ b/doc/index.dox @@ -45,6 +45,10 @@ website: 1.7 and 1.6 +\section qgsquick_docs QgsQuick library documentation + +See \ref qgsquick for information about QGIS Quick (QML) components library + \section index_maillist Mailing Lists For support we encourage you to join our ABISYM( mSystemEnvVars ); diff --git a/src/quickgui/CMakeLists.txt b/src/quickgui/CMakeLists.txt new file mode 100644 index 000000000000..892537386507 --- /dev/null +++ b/src/quickgui/CMakeLists.txt @@ -0,0 +1,153 @@ +############################################################ +# sources +SET(QGIS_QUICK_GUI_MOC_HDRS + qgsquickattributeformmodel.h + qgsquickattributeformmodelbase.h + qgsquickcoordinatetransformer.h + qgsquickfeature.h + qgsquickfeaturemodel.h + qgsquickfeaturehighlight.h + qgsquickidentifykit.h + qgsquickmapcanvasmap.h + qgsquickmapsettings.h + qgsquickmaptransform.h + qgsquickmessagelogmodel.h + qgsquickpositionkit.h + qgsquickscalebarkit.h + qgsquicksimulatedpositionsource.h + qgsquicksubmodel.h + qgsquickutils.h +) + +SET(QGIS_QUICK_GUI_HDRS + qgsquickhighlightsgnode.h +) + +SET(QGIS_QUICK_GUI_SRC + qgsquickattributeformmodel.cpp + qgsquickattributeformmodelbase.cpp + qgsquickcoordinatetransformer.cpp + qgsquickfeature.cpp + qgsquickfeaturemodel.cpp + qgsquickfeaturehighlight.cpp + qgsquickhighlightsgnode.cpp + qgsquickidentifykit.cpp + qgsquickmapcanvasmap.cpp + qgsquickmapsettings.cpp + qgsquickmaptransform.cpp + qgsquickmessagelogmodel.cpp + qgsquickpositionkit.cpp + qgsquickscalebarkit.cpp + qgsquicksimulatedpositionsource.cpp + qgsquicksubmodel.cpp + qgsquickutils.cpp +) + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + + ${CMAKE_SOURCE_DIR}/src/core + ${CMAKE_SOURCE_DIR}/src/core/annotations + ${CMAKE_SOURCE_DIR}/src/core/auth + ${CMAKE_SOURCE_DIR}/src/core/composer + ${CMAKE_SOURCE_DIR}/src/core/fieldformatter + ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/layertree + ${CMAKE_SOURCE_DIR}/src/core/layout + ${CMAKE_SOURCE_DIR}/src/core/locator + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/providers/memory + ${CMAKE_SOURCE_DIR}/src/core/raster + ${CMAKE_SOURCE_DIR}/src/core/scalebar + ${CMAKE_SOURCE_DIR}/src/core/symbology + ${CMAKE_SOURCE_DIR}/src/core/effects + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/expression + + ${CMAKE_BINARY_DIR}/src/core +) + +INCLUDE_DIRECTORIES(SYSTEM + ${LIBZIP_INCLUDE_DIRS} + ${SPATIALINDEX_INCLUDE_DIR} + ${PROJ_INCLUDE_DIR} + ${GEOS_INCLUDE_DIR} + ${GDAL_INCLUDE_DIR} + ${EXPAT_INCLUDE_DIR} + ${SQLITE3_INCLUDE_DIR} + ${SPATIALITE_INCLUDE_DIR} + ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} +) + +ADD_DEFINITIONS(-DCORE_EXPORT=) + + +SET(QGIS_QUICK_GUI_IMAGE_RCCS ./images/images.qrc) +QT5_ADD_RESOURCES(QGIS_QUICK_GUI_IMAGE_RCC_SRCS ${QGIS_QUICK_GUI_IMAGE_RCCS}) + +############################################################ +# qgis_quick shared library +QT5_WRAP_CPP(QGIS_QUICK_GUI_MOC_SRCS ${QGIS_QUICK_GUI_MOC_HDRS}) +IF(MSVC) + SET_SOURCE_FILES_PROPERTIES(${QGIS_QUICK_GUI_MOC_SRCS} PROPERTIES COMPILE_FLAGS "/wd4512 /wd4996" ) +ELSE(MSVC) + SET_SOURCE_FILES_PROPERTIES(${QGIS_QUICK_GUI_MOC_SRCS} PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations" ) +ENDIF(MSVC) + +ADD_LIBRARY(qgis_quick SHARED + ${QGIS_QUICK_GUI_IMAGE_RCC_SRCS} + ${QGIS_QUICK_GUI_SRC} + ${QGIS_QUICK_GUI_MOC_HDRS} + ${QGIS_QUICK_GUI_MOC_SRCS} + ${QGIS_QUICK_GUI_HDRS}) +TARGET_LINK_LIBRARIES(qgis_quick Qt5::Quick Qt5::Qml Qt5::Xml Qt5::Concurrent Qt5::Positioning qgis_core) +IF(CMAKE_SYSTEM_NAME STREQUAL "Android") + TARGET_LINK_LIBRARIES(qgis_quick Qt5::AndroidExtras) +ENDIF(CMAKE_SYSTEM_NAME STREQUAL "Android") +TARGET_COMPILE_DEFINITIONS(qgis_quick PRIVATE "-DDQT_NO_FOREACH") + +GENERATE_EXPORT_HEADER( + qgis_quick + BASE_NAME QUICK + EXPORT_FILE_NAME qgis_quick.h +) +SET(QGIS_CORE_HDRS ${QGIS_QUICK_GUI_HDRS} ${CMAKE_CURRENT_BINARY_DIR}/qgis_core.h) + +# Installation +INSTALL(TARGETS qgis_quick + RUNTIME DESTINATION ${QGIS_BIN_DIR} + LIBRARY DESTINATION ${QGIS_LIB_DIR} + ARCHIVE DESTINATION ${QGIS_LIB_DIR} + FRAMEWORK DESTINATION ${QGIS_FW_SUBDIR} + PUBLIC_HEADER DESTINATION ${QGIS_INCLUDE_DIR}) + +IF(NOT APPLE) + INSTALL(FILES ${QGIS_QUICK_GUI_HDRS} ${QGIS_QUICK_GUI_MOC_HDRS} DESTINATION ${QGIS_INCLUDE_DIR}) +ELSE(NOT APPLE) + SET_TARGET_PROPERTIES(qgis_quick PROPERTIES + CLEAN_DIRECT_OUTPUT 1 + FRAMEWORK 1 + FRAMEWORK_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}" + MACOSX_FRAMEWORK_INFO_PLIST "${CMAKE_SOURCE_DIR}/mac/framework.info.plist.in" + MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${COMPLETE_VERSION} + MACOSX_FRAMEWORK_IDENTIFIER org.qgis.qgis3_quick + BUILD_WITH_INSTALL_RPATH TRUE + PUBLIC_HEADER "${QGIS_QUICK_GUI_HDRS};${QGIS_QUICK_GUI_MOC_HDRS}" + LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}" + ) + # generated export header does not get copied with PUBLIC_HEADER files + ADD_CUSTOM_COMMAND(TARGET qgis_quick + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy qgis_quick.h + "${QGIS_OUTPUT_DIRECTORY}/${QGIS_LIB_SUBDIR}/qgis_core.framework/Headers" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS qgis_quick.h + ) +ENDIF(NOT APPLE) + +############################################################ +# qgis_quick_plugin module (QML) library +ADD_SUBDIRECTORY(plugin) + diff --git a/src/quickgui/images/ic_broken_image_black.svg b/src/quickgui/images/ic_broken_image_black.svg new file mode 100644 index 000000000000..4c7e52f7807f --- /dev/null +++ b/src/quickgui/images/ic_broken_image_black.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/quickgui/images/ic_camera_alt_border.svg b/src/quickgui/images/ic_camera_alt_border.svg new file mode 100644 index 000000000000..7bd93c3322bb --- /dev/null +++ b/src/quickgui/images/ic_camera_alt_border.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/quickgui/images/ic_check_black.svg b/src/quickgui/images/ic_check_black.svg new file mode 100644 index 000000000000..3fab25f262b0 --- /dev/null +++ b/src/quickgui/images/ic_check_black.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/quickgui/images/ic_clear_black.svg b/src/quickgui/images/ic_clear_black.svg new file mode 100644 index 000000000000..ec8d116194f6 --- /dev/null +++ b/src/quickgui/images/ic_clear_black.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/quickgui/images/ic_clear_white.svg b/src/quickgui/images/ic_clear_white.svg new file mode 100644 index 000000000000..570480017eb8 --- /dev/null +++ b/src/quickgui/images/ic_clear_white.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/quickgui/images/ic_delete_forever_white.svg b/src/quickgui/images/ic_delete_forever_white.svg new file mode 100644 index 000000000000..ff2e74c8fd31 --- /dev/null +++ b/src/quickgui/images/ic_delete_forever_white.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/quickgui/images/ic_navigation_black.svg b/src/quickgui/images/ic_navigation_black.svg new file mode 100644 index 000000000000..20d6b393a80d --- /dev/null +++ b/src/quickgui/images/ic_navigation_black.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/quickgui/images/ic_photo_notavailable_white.svg b/src/quickgui/images/ic_photo_notavailable_white.svg new file mode 100644 index 000000000000..89eaad55ec52 --- /dev/null +++ b/src/quickgui/images/ic_photo_notavailable_white.svg @@ -0,0 +1,66 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/quickgui/images/ic_save_white.svg b/src/quickgui/images/ic_save_white.svg new file mode 100644 index 000000000000..05684dfc3d75 --- /dev/null +++ b/src/quickgui/images/ic_save_white.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/quickgui/images/images.qrc b/src/quickgui/images/images.qrc new file mode 100644 index 000000000000..15efe002a634 --- /dev/null +++ b/src/quickgui/images/images.qrc @@ -0,0 +1,13 @@ + + + ic_broken_image_black.svg + ic_camera_alt_border.svg + ic_check_black.svg + ic_clear_black.svg + ic_clear_white.svg + ic_delete_forever_white.svg + ic_navigation_black.svg + ic_photo_notavailable_white.svg + ic_save_white.svg + + diff --git a/src/quickgui/plugin/CMakeLists.txt b/src/quickgui/plugin/CMakeLists.txt new file mode 100644 index 000000000000..6fc1509c1b51 --- /dev/null +++ b/src/quickgui/plugin/CMakeLists.txt @@ -0,0 +1,134 @@ +############################################################ +# sources + +SET(QGIS_QUICK_PLUGIN_MOC_HDRS + qgsquickplugin.h +) + +SET(QGIS_QUICK_PLUGIN_SRC + qgsquickplugin.cpp +) + +SET(QGIS_QUICK_PLUGIN_RESOURCES + editor/qgsquickcheckbox.qml + editor/qgsquickdatetime.qml + editor/qgsquickexternalresource.qml + editor/qgsquicktextedit.qml + editor/qgsquickvaluemap.qml + qgsquickfeatureform.qml + qgsquickfeatureformstyling.qml + qgsquickmapcanvas.qml + qgsquickmessagelog.qml + qgsquickphotopanel.qml + qgsquickpositionmarker.qml + qgsquickscalebar.qml + qmldir +) + + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + + ${CMAKE_SOURCE_DIR}/src/core + ${CMAKE_SOURCE_DIR}/src/core/annotations + ${CMAKE_SOURCE_DIR}/src/core/auth + ${CMAKE_SOURCE_DIR}/src/core/composer + ${CMAKE_SOURCE_DIR}/src/core/fieldformatter + ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/layertree + ${CMAKE_SOURCE_DIR}/src/core/layout + ${CMAKE_SOURCE_DIR}/src/core/locator + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/providers/memory + ${CMAKE_SOURCE_DIR}/src/core/raster + ${CMAKE_SOURCE_DIR}/src/core/scalebar + ${CMAKE_SOURCE_DIR}/src/core/symbology + ${CMAKE_SOURCE_DIR}/src/core/effects + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/expression + ${CMAKE_SOURCE_DIR}/src/quickgui + + ${CMAKE_BINARY_DIR}/src/core + ${CMAKE_BINARY_DIR}/src/quickgui +) + +INCLUDE_DIRECTORIES(SYSTEM + ${LIBZIP_INCLUDE_DIRS} + ${SPATIALINDEX_INCLUDE_DIR} + ${PROJ_INCLUDE_DIR} + ${GEOS_INCLUDE_DIR} + ${GDAL_INCLUDE_DIR} + ${EXPAT_INCLUDE_DIR} + ${SQLITE3_INCLUDE_DIR} + ${SPATIALITE_INCLUDE_DIR} + ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} +) + +ADD_DEFINITIONS(-DCORE_EXPORT=) + +############################################################ +# qgis_quick_plugin module (QML) library + +QT5_WRAP_CPP(QGIS_QUICK_PLUGIN_MOC_SRCS ${QGIS_QUICK_PLUGIN_MOC_HDRS}) +IF(MSVC) + SET_SOURCE_FILES_PROPERTIES(${QGIS_QUICK_PLUGIN_MOC_SRCS} PROPERTIES COMPILE_FLAGS "/wd4512 /wd4996" ) +ELSE(MSVC) + SET_SOURCE_FILES_PROPERTIES(${QGIS_QUICK_PLUGIN_MOC_SRCS} PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations" ) +ENDIF(MSVC) + +SET(QGIS_QUICK_PLUGIN_RUNTIME_DIR ${QGIS_OUTPUT_DIRECTORY}/${QGIS_QML_SUBDIR}/QgisQuick) + +ADD_CUSTOM_TARGET(qgis_quick_qml_copy) + +ADD_CUSTOM_COMMAND(TARGET qgis_quick_qml_copy + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${QGIS_QUICK_PLUGIN_RUNTIME_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +FOREACH(qmlfile ${QGIS_QUICK_PLUGIN_RESOURCES}) + ADD_CUSTOM_COMMAND(TARGET qgis_quick_qml_copy + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${qmlfile} ${QGIS_QUICK_PLUGIN_RUNTIME_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${qmlfile} + ) +ENDFOREACH(qmlfile) + +ADD_LIBRARY(qgis_quick_plugin MODULE + ${QGIS_QUICK_PLUGIN_SRC} + ${QGIS_QUICK_PLUGIN_MOC_HDRS} + ${QGIS_QUICK_PLUGIN_MOC_SRCS} + ${QGIS_QUICK_PLUGIN_RESOURCES} +) +TARGET_LINK_LIBRARIES(qgis_quick_plugin qgis_quick) +SET_TARGET_PROPERTIES(qgis_quick_plugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${QGIS_QUICK_PLUGIN_RUNTIME_DIR}) +TARGET_COMPILE_DEFINITIONS(qgis_quick_plugin PRIVATE "-DQUICK_EXPORT=" "-DDQT_NO_FOREACH") +ADD_DEPENDENCIES(qgis_quick_plugin qgis_quick_qml_copy) + +# TODO: temporarily disabled running of qmlplugindump as it was not behaving well (unknown failures when run) +IF(QMLPLUGINDUMP_FOUND AND FALSE) + # Extract QML Types Info from our QML plugin. This is useful for development with Qt Creator as it allows + # Qt Creator understand also the C++ classes registered in the plugin and thus available in QML code + SET(QGIS_QUICK_PLUGIN_TYPEINFO ${QGIS_QUICK_PLUGIN_RUNTIME_DIR}/qgisquick.qmltypes) + ADD_CUSTOM_COMMAND( + TARGET qgis_quick_plugin + COMMAND ${QMLPLUGINDUMP_EXECUTABLE} + ARGS QgisQuick 0.1 ${QGIS_QUICK_PLUGIN_RUNTIME_DIR} --output ${QGIS_QUICK_PLUGIN_TYPEINFO} --noinstantiate + DEPENDS ${QGIS_QUICK_PLUGIN_RESOURCES} + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + POST_BUILD + ) +ENDIF() + +# Installation +SET(QUICK_PLUGIN_INSTALL_DIR ${QGIS_QML_DIR}/QgisQuick) +INSTALL(TARGETS qgis_quick_plugin + RUNTIME DESTINATION ${QUICK_PLUGIN_INSTALL_DIR} + LIBRARY DESTINATION ${QUICK_PLUGIN_INSTALL_DIR} +) +INSTALL(FILES ${QGIS_QUICK_PLUGIN_RESOURCES} ${QGIS_QUICK_PLUGIN_TYPEINFO} + DESTINATION ${QUICK_PLUGIN_INSTALL_DIR} +) diff --git a/src/quickgui/plugin/editor/qgsquickcheckbox.qml b/src/quickgui/plugin/editor/qgsquickcheckbox.qml new file mode 100644 index 000000000000..2dacb7e1f56c --- /dev/null +++ b/src/quickgui/plugin/editor/qgsquickcheckbox.qml @@ -0,0 +1,48 @@ +/*************************************************************************** + qgsquickcheckbox.qml + -------------------------------------- + Date : 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QtQuick 2.0 +import QtQuick.Controls 2.2 +import QgisQuick 0.1 as QgsQuick + +// Checkbox for QGIS Attribute Form +// Requires various global properties set to function, see qgsquickfeatureform Loader section +// Do not use directly from Application QML + +Item { + signal valueChanged( var value, bool isNull ) + + height: childrenRect.height + anchors { + right: parent.right + left: parent.left + } + + CheckBox { + property var currentValue: value + + checked: value == config['CheckedState'] + + onCheckedChanged: { + valueChanged( checked ? config['CheckedState'] : config['UncheckedState'], false ) + forceActiveFocus() + } + + // Workaround to get a signal when the value has changed + onCurrentValueChanged: { + checked = currentValue == config['CheckedState'] + } + } +} diff --git a/src/quickgui/plugin/editor/qgsquickdatetime.qml b/src/quickgui/plugin/editor/qgsquickdatetime.qml new file mode 100644 index 000000000000..33aa7bdafeb8 --- /dev/null +++ b/src/quickgui/plugin/editor/qgsquickdatetime.qml @@ -0,0 +1,124 @@ +/*************************************************************************** + qgsquickdatetime.qml + -------------------------------------- + Date : 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QtQuick 2.0 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.4 as Controls1 +import QgisQuick 0.1 as QgsQuick + +// Calendar for QGIS Attribute Form +// Requires various global properties set to function, see qgsquickfeatureform Loader section +// Do not use directly from Application QML + +Item { + signal valueChanged(var value, bool isNull) + + height: childrenRect.height + anchors { right: parent.right; left: parent.left } + + ColumnLayout { + id: main + property var currentValue: value + + anchors { right: parent.right; left: parent.left } + + Item { + anchors { right: parent.right; left: parent.left } + Layout.minimumHeight: 48 * QgsQuick.Utils.dp + + Rectangle { + anchors.fill: parent + id: backgroundRect + border.color: "#17a81a" + border.width: 2 + color: "#dddddd" + radius: 2 + } + + Label { + id: label + + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + + MouseArea { + anchors.fill: parent + onClicked: { + popup.open() + } + } + + Image { + source: QgsQuick.Utils.getThemeIcon("ic_clear_black") + anchors.left: parent.right + visible: main.currentValue !== undefined && config['allow_null'] + + MouseArea { + anchors.fill: parent + onClicked: { + main.currentValue = undefined + } + } + } + } + } + + Popup { + id: popup + modal: true + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + parent: ApplicationWindow.overlay + + ColumnLayout { + + Controls1.Calendar { + id: calendar + selectedDate: main.currentValue + weekNumbersVisible: true + focus: false + + onSelectedDateChanged: { + main.currentValue = selectedDate + } + } + + RowLayout { + Button { + text: qsTr( "Ok" ) + Layout.fillWidth: true + + onClicked: popup.close() + } + } + } + } + + onCurrentValueChanged: { + valueChanged(currentValue, main.currentValue === undefined) + if (main.currentValue === undefined) + { + label.text = qsTr('(no date)') + label.color = 'gray' + } + else + { + label.color = 'black' + label.text = new Date(value).toLocaleString(Qt.locale(), config['display_format'] ) + } + } + } +} diff --git a/src/quickgui/plugin/editor/qgsquickexternalresource.qml b/src/quickgui/plugin/editor/qgsquickexternalresource.qml new file mode 100644 index 000000000000..a2ea40b4a384 --- /dev/null +++ b/src/quickgui/plugin/editor/qgsquickexternalresource.qml @@ -0,0 +1,86 @@ +/*************************************************************************** + qgsquickexternalresource.qml + -------------------------------------- + Date : 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QtQuick 2.5 +import QtQuick.Controls 2.0 +import QgisQuick 0.1 as QgsQuick + +// External Resource (Photo capture) for QGIS Attribute Form +// Requires various global properties set to function, see qgsquickfeatureform Loader section +// Do not use directly from Application QML + +Item { + signal valueChanged(var value, bool isNull) + + property var image: image + + id: fieldItem + anchors.left: parent.left + anchors.right: parent.right + + height: Math.max(image.height, button.height) + + QgsQuick.PhotoCapture { + id: photoCapturePanel + visible: false + height: window.height + width: window.width + edge: Qt.RightEdge + } + + Image { + property var currentValue: value + + id: image + width: 200 * QgsQuick.Utils.dp + autoTransform: true + fillMode: Image.PreserveAspectFit + + Component.onCompleted: image.source = getSource() + + function getSource() { + if (image.status === Image.Error) + return QgsQuick.Utils.getThemeIcon("ic_broken_image_black") + else if (image.currentValue && QgsQuick.Utils.fileExists(homePath + "/" + image.currentValue)) { + return homePath + "/" + image.currentValue + } + else { + return QgsQuick.Utils.getThemeIcon("ic_photo_notavailable_white") + } + } + } + + Button { + id: button + visible: fieldItem.enabled + width: 45 * QgsQuick.Utils.dp + height: 45 * QgsQuick.Utils.dp + + anchors.right: parent.right + anchors.bottom: parent.bottom + + onClicked: { + photoCapturePanel.visible = true + photoCapturePanel.targetDir = homePath + photoCapturePanel.fieldItem = fieldItem + } + + background: Image { + source: QgsQuick.Utils.getThemeIcon("ic_camera_alt_border") + width: button.width + height: button.height + } + } +} diff --git a/src/quickgui/plugin/editor/qgsquicktextedit.qml b/src/quickgui/plugin/editor/qgsquicktextedit.qml new file mode 100644 index 000000000000..11a269c31226 --- /dev/null +++ b/src/quickgui/plugin/editor/qgsquicktextedit.qml @@ -0,0 +1,87 @@ +/*************************************************************************** + qgsquicktextedit.qml + -------------------------------------- + Date : 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QtQuick 2.0 +import QtQuick.Controls 2.2 +import QtQuick 2.5 +import QgisQuick 0.1 as QgsQuick + +// Text Edit for QGIS Attribute Form +// Requires various global properties set to function, see qgsquickfeatureform Loader section +// Do not use directly from Application QML + +Item { + signal valueChanged(var value, bool isNull) + + height: childrenRect.height + + TextField { + id: textField + height: textArea.height == 0 ? fontMetrics.height + 20 * QgsQuick.Utils.dp : 0 + topPadding: 10 * QgsQuick.Utils.dp + bottomPadding: 10 * QgsQuick.Utils.dp + visible: height !== 0 + anchors.left: parent.left + anchors.right: parent.right + font.pointSize: 14 + + text: value || '' + + inputMethodHints: field.isNumeric || widget == 'Range' ? field.precision === 0 ? Qt.ImhDigitsOnly : Qt.ImhFormattedNumbersOnly : Qt.ImhNone + + // Make sure we do not input more characters than allowed for strings + states: [ + State { + name: "limitedTextLengthState" + when: (!field.isNumeric) && (field.length > 0) + PropertyChanges { + target: textField + maximumLength: field.length + } + } + ] + + background: Rectangle { + y: textField.height - height - textField.bottomPadding / 2 + implicitWidth: 120 * QgsQuick.Utils.dp + height: textField.activeFocus ? 2 * QgsQuick.Utils.dp : 1 * QgsQuick.Utils.dp + color: textField.activeFocus ? "#4CAF50" : "#C8E6C9" + } + + onTextChanged: { + valueChanged( text, text == '' ) + } + } + + TextArea { + id: textArea + height: config['IsMultiline'] === true ? undefined : 0 + visible: height !== 0 + anchors.left: parent.left + anchors.right: parent.right + + text: value || '' + textFormat: config['UseHtml'] ? TextEdit.RichText : TextEdit.PlainText + + onEditingFinished: { + valueChanged( text, text == '' ) + } + } + + FontMetrics { + id: fontMetrics + font: textField.font + } +} diff --git a/src/quickgui/plugin/editor/qgsquickvaluemap.qml b/src/quickgui/plugin/editor/qgsquickvaluemap.qml new file mode 100644 index 000000000000..96cd886d0c9f --- /dev/null +++ b/src/quickgui/plugin/editor/qgsquickvaluemap.qml @@ -0,0 +1,110 @@ +/*************************************************************************** + qgsquickvaluemap.qml + -------------------------------------- + Date : 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QtQuick 2.0 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import QgisQuick 0.1 as QgsQuick + +// Value Map for QGIS Attribute Form +// Requires various global properties set to function, see qgsquickfeatureform Loader section +// Do not use directly from Application QML + +Item { + signal valueChanged(var value, bool isNull) + + anchors { + left: parent.left + right: parent.right + rightMargin: 10 * QgsQuick.Utils.dp + } + + height: childrenRect.height + 10 * QgsQuick.Utils.dp + + + + ComboBox { + id: comboBox + + property var reverseConfig: ({}) + property var currentValue: value + + anchors { left: parent.left; right: parent.right } + + currentIndex: find(reverseConfig[value]) + + Component.onCompleted: { + model = Object.keys(config['map']); + for(var key in config['map']) { + reverseConfig[config['map'][key]] = key; + } + currentIndex = find(reverseConfig[value]) + } + + onCurrentTextChanged: { + valueChanged(config['map'][currentText], false) + } + + // Workaround to get a signal when the value has changed + onCurrentValueChanged: { + currentIndex = find(reverseConfig[value]) + } + + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + + onClicked: mouse.accepted = false + onPressed: { forceActiveFocus(); mouse.accepted = false; } + onReleased: mouse.accepted = false; + onDoubleClicked: mouse.accepted = false; + onPositionChanged: mouse.accepted = false; + onPressAndHold: mouse.accepted = false; + } + + // [hidpi fixes] + delegate: ItemDelegate { + width: comboBox.width + height: 36 * QgsQuick.Utils.dp + text: modelData + font.weight: comboBox.currentIndex === index ? Font.DemiBold : Font.Normal + font.pointSize: 12 + highlighted: comboBox.highlightedIndex == index + } + + contentItem: Text { + height: 36 * QgsQuick.Utils.dp + text: comboBox.displayText + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Item { + implicitWidth: 120 * QgsQuick.Utils.dp + implicitHeight: 36 * QgsQuick.Utils.dp + + Rectangle { + anchors.fill: parent + id: backgroundRect + border.color: comboBox.pressed ? "#17a81a" : "#21be2b" + border.width: comboBox.visualFocus ? 2 : 1 + color: "#dddddd" + radius: 2 + } + } + // [/hidpi fixes] + } +} diff --git a/src/quickgui/plugin/qgsquickfeatureform.qml b/src/quickgui/plugin/qgsquickfeatureform.qml new file mode 100644 index 000000000000..b15e034aaeb8 --- /dev/null +++ b/src/quickgui/plugin/qgsquickfeatureform.qml @@ -0,0 +1,469 @@ +/*************************************************************************** + qgsquickfeatureform.qml + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QtQuick 2.6 +import QtQuick.Controls 2.0 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 +import QtQml.Models 2.2 +import QtQml 2.2 + +// We use calendar in datetime widget that is not +// yet implemented in Controls 2.2 +import QtQuick.Controls 1.4 as Controls1 + +import QgisQuick 0.1 as QgsQuick + +Item { + signal saved + signal canceled + signal aboutToSave + + property QgsQuick.AttributeFormModel model + property alias toolbarVisible: toolbar.visible + property bool allowRememberAttribute: false // when adding new feature, add checkbox to be able to save the same value for the next feature as default + property QgsQuick.Project project + + property var saveButtonIcon: QgsQuick.Utils.getThemeIcon( "ic_save_white" ) + property var deleteButtonIcon: QgsQuick.Utils.getThemeIcon( "ic_delete_forever_white" ) + property var closeButtonIcon: QgsQuick.Utils.getThemeIcon( "ic_clear_white" ) + + property FeatureFormStyling style: FeatureFormStyling {} + + function reset() { + master.reset() + } + + id: form + + states: [ + State { + name: "ReadOnly" + }, + State { + name: "Edit" + }, + State { + name: "Add" + } + ] + + /** + * This is a relay to forward private signals to internal components. + */ + QtObject { + id: master + + /** + * This signal is emitted whenever the state of Flickables and TabBars should + * be restored. + */ + signal reset + } + + Item { + id: container + + clip: true + + anchors { + top: toolbar.bottom + bottom: parent.bottom + left: parent.left + right: parent.right + } + + Flickable { + id: flickable + anchors { + left: parent.left + right: parent.right + } + height: tabRow.height + + flickableDirection: Flickable.HorizontalFlick + contentWidth: tabRow.width + + // Tabs + TabBar { + id: tabRow + visible: model.hasTabs + height: form.style.tabs.height + + Connections { + target: master + onReset: tabRow.currentIndex = 0 + } + + Connections { + target: swipeView + onCurrentIndexChanged: tabRow.currentIndex = swipeView.currentIndex + } + + Repeater { + model: form.model + + TabButton { + id: tabButton + text: Name + leftPadding: 8 * QgsQuick.Utils.dp + rightPadding: 8 * QgsQuick.Utils.dp + + width: contentItem.width + leftPadding + rightPadding + height: form.style.tabs.height + + contentItem: Text { + // Make sure the width is derived from the text so we can get wider + // than the parent item and the Flickable is useful + width: paintedWidth + text: tabButton.text + color: !tabButton.enabled ? form.style.tabs.disabledColor : tabButton.down || + tabButton.checked ? form.style.tabs.activeColor : form.style.tabs.normalColor + font.weight: tabButton.checked ? Font.DemiBold : Font.Normal + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + } + } + } + + SwipeView { + id: swipeView + currentIndex: tabRow.currentIndex + anchors { + top: flickable.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + + Repeater { + // One page per tab in tabbed forms, 1 page in auto forms + model: form.model.hasTabs ? form.model : 1 + + Item { + id: formPage + property int currentIndex: index + + /** + * The main form content area + */ + Rectangle { + anchors.fill: parent + color: form.style.backgroundColor + opacity: form.style.backgroundOpacity + } + + ListView { + id: content + anchors.fill: parent + clip: true + section.property: "Group" + section.labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels + section.delegate: Component { + // section header: group box name + Rectangle { + width: parent.width + height: section === "" ? 0 : form.style.group.height + color: form.style.group.backgroundColor + + Text { + anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + font.bold: true + text: section + } + } + } + + Connections { + target: master + onReset: content.contentY = 0 + } + + model: QgsQuick.SubModel { + id: contentModel + model: form.model + rootIndex: form.model.hasTabs ? form.model.index(currentIndex, 0) : undefined + } + + delegate: fieldItem + } + } + } + } + } + + /** + * A field editor + */ + Component { + id: fieldItem + + Item { + id: fieldContainer + visible: Type === 'field' + height: childrenRect.height + + anchors { + left: parent.left + right: parent.right + leftMargin: 12 * QgsQuick.Utils.dp + } + + Label { + id: fieldLabel + + text: Name || '' + font.bold: true + color: ConstraintValid ? form.style.constraint.validColor : form.style.constraint.invalidColor + } + + Label { + id: constraintDescriptionLabel + anchors { + left: parent.left + right: parent.right + top: fieldLabel.bottom + } + + text: ConstraintDescription + height: ConstraintValid ? 0 : undefined + visible: !ConstraintValid + + color: form.style.constraint.descriptionColor + } + + Item { + id: placeholder + height: childrenRect.height + anchors { left: parent.left; right: rememberCheckbox.left; top: constraintDescriptionLabel.bottom } + + Loader { + id: attributeEditorLoader + + height: childrenRect.height + anchors { left: parent.left; right: parent.right } + + enabled: form.state !== "ReadOnly" && !!AttributeEditable + + property var value: AttributeValue + property var config: EditorWidgetConfig + property var widget: EditorWidget + property var field: Field + property var constraintValid: ConstraintValid + property var homePath: form.project ? form.project.homePath : "" + + active: widget !== 'Hidden' + source: 'qgsquick' + widget.toLowerCase() + '.qml' + + onStatusChanged: { + if ( attributeEditorLoader.status === Loader.Error ) + { + console.warn( "Editor widget type '" + EditorWidget + "' is not supported" ); + source = 'qgsquicktextedit.qml'; + } + } + } + + Connections { + target: form + onAboutToSave: { + try { + attributeEditorLoader.item.pushChanges() + } + catch ( err ) + {} + } + } + + Connections { + target: attributeEditorLoader.item + onValueChanged: { + AttributeValue = isNull ? undefined : value + } + } + } + + CheckBox { + id: rememberCheckbox + checked: RememberValue ? true : false + + visible: form.allowRememberAttribute && form.state === "Add" && EditorWidget !== "Hidden" + width: visible ? undefined : 0 + + anchors { right: parent.right; top: fieldLabel.bottom } + + onCheckedChanged: { + RememberValue = checked + } + } + } + } + + function save() { + parent.focus = true + aboutToSave() + + if ( form.state === "Add" ) { + model.create() + state = "Edit" + } + else + { + model.save() + } + + saved() + } + + Connections { + target: Qt.inputMethod + onVisibleChanged: { + Qt.inputMethod.commit() + } + } + + /** The title toolbar **/ + Item { + id: toolbar + height: visible ? 48 * QgsQuick.Utils.dp : 0 + visible: form.state === 'Add' + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + RowLayout { + anchors.fill: parent + Layout.margins: 0 + + ToolButton { + id: saveButton + Layout.preferredWidth: form.style.toolbutton.size + Layout.preferredHeight: form.style.toolbutton.size + + visible: form.state !== "ReadOnly" + + contentItem: Image { + source: form.saveButtonIcon + sourceSize: Qt.size(width, height) + } + + background: Rectangle { + color: model.constraintsValid ? form.style.toolbutton.backgroundColor : form.style.toolbutton.backgroundColorInvalid + } + + enabled: model.constraintsValid + + onClicked: { + save() + } + } + + ToolButton { + id: deleteButton + + Layout.preferredWidth: form.style.toolbutton.size + Layout.preferredHeight: form.style.toolbutton.size + + visible: form.state === "Edit" + + contentItem: Image { + source: form.deleteButtonIcon + sourceSize: Qt.size(width, height) + } + + background: Rectangle { + color: form.style.toolbutton.backgroundColor + } + + onClicked: deleteDialog.visible = true + } + + Label { + id: titleLabel + + text: + { + var currentLayer = model.featureModel.layer + var layerName = 'N/A' + if (currentLayer !== null) + layerName = currentLayer.name + + if ( form.state === 'Add' ) + qsTr( 'Add feature on %1' ).arg(layerName ) + else if ( form.state === 'Edit' ) + qsTr( 'Edit feature on %1' ).arg(layerName) + else + qsTr( 'View feature on %1' ).arg(layerName) + } + font.bold: true + font.pointSize: 16 + elide: Label.ElideRight + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + Layout.fillWidth: true + color: "white" + } + + ToolButton { + id: closeButton + anchors.right: parent.right + + Layout.preferredWidth: form.style.toolbutton.size + Layout.preferredHeight: form.style.toolbutton.size + + contentItem: Image { + source: form.closeButtonIcon + sourceSize: Qt.size(width, height) + } + + background: Rectangle { + color: form.style.toolbutton.backgroundColor + } + + onClicked: { + Qt.inputMethod.hide() + + canceled() + } + } + } + } + + MessageDialog { + id: deleteDialog + + visible: false + + title: qsTr( "Delete feature" ) + text: qsTr( "Really delete this feature?" ) + icon: StandardIcon.Warning + standardButtons: StandardButton.Ok | StandardButton.Cancel + onAccepted: { + model.featureModel.deleteFeature() + visible = false + + canceled() + } + onRejected: { + visible = false + } + } +} diff --git a/src/quickgui/plugin/qgsquickfeatureformstyling.qml b/src/quickgui/plugin/qgsquickfeatureformstyling.qml new file mode 100644 index 000000000000..99e5b93e5755 --- /dev/null +++ b/src/quickgui/plugin/qgsquickfeatureformstyling.qml @@ -0,0 +1,47 @@ +/*************************************************************************** + qgsquickfeatureformstyling.qml + -------------------------------------- + Date : January 2018 + Copyright : (C) 2018 by Martin Dobias + Email : wonder dot sk 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. * + * * + ***************************************************************************/ + +import QtQuick 2.0 + +import QgisQuick 0.1 as QgsQuick + +QtObject { + property color backgroundColor: "white" + property real backgroundOpacity: 1 + + property QtObject group: QtObject { + property color backgroundColor: "lightGray" + property real height: 30 * QgsQuick.Utils.dp + } + + property QtObject tabs: QtObject { + property color normalColor: "#4CAF50" + property color activeColor: "#1B5E20" + property color disabledColor: "#999999" + property real height: 48 * QgsQuick.Utils.dp + } + + property QtObject constraint: QtObject { + property color validColor: "black" + property color invalidColor: "#c0392b" + property color descriptionColor: "#e67e22" + } + + property QtObject toolbutton: QtObject { + property color backgroundColor: "transparent" + property color backgroundColorInvalid: "#bdc3c7" + property real size: 80 * QgsQuick.Utils.dp + } +} diff --git a/src/quickgui/plugin/qgsquickmapcanvas.qml b/src/quickgui/plugin/qgsquickmapcanvas.qml new file mode 100644 index 000000000000..b265dfdac459 --- /dev/null +++ b/src/quickgui/plugin/qgsquickmapcanvas.qml @@ -0,0 +1,140 @@ +/*************************************************************************** + qgsquickmapcanvas.qml + -------------------------------------- + Date : 10.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QtQuick 2.3 +import QtQuick.Controls 2.2 +import QtQml 2.2 +import QgisQuick 0.1 as QgsQuick + +Item { + id: mapArea + property alias mapSettings: mapCanvasWrapper.mapSettings + property alias isRendering: mapCanvasWrapper.isRendering + property alias incrementalRendering: mapCanvasWrapper.incrementalRendering + + signal clicked(var mouse) + + /** + * Freezes the map canvas refreshes. + * + * In case of repeated geometry changes (animated resizes, pinch, pan...) + * triggering refreshes all the time can cause severe performance impacts. + * + * If freeze is called, an internal counter is incremented and only when the + * counter is 0, refreshes will happen. + * It is therefore important to call freeze() and unfreeze() exactly the same + * number of times. + */ + function freeze(id) { + mapCanvasWrapper.__freezecount[id] = true + mapCanvasWrapper.freeze = true + } + + function unfreeze(id) { + delete mapCanvasWrapper.__freezecount[id] + mapCanvasWrapper.freeze = Object.keys(mapCanvasWrapper.__freezecount).length !== 0 + } + + QgsQuick.MapCanvasMap { + id: mapCanvasWrapper + + anchors.fill: parent + + property var __freezecount: ({}) + + freeze: false + } + + PinchArea { + id: pinchArea + + anchors.fill: parent + + onPinchStarted: { + freeze('pinch') + } + + onPinchUpdated: { + mapCanvasWrapper.zoom( pinch.center, pinch.previousScale / pinch.scale ) + mapCanvasWrapper.pan( pinch.center, pinch.previousCenter ) + } + + onPinchFinished: { + unfreeze('pinch') + mapCanvasWrapper.refresh() + } + + MouseArea { + id: mouseArea + + property point __initialPosition + property point __lastPosition + + anchors.fill : parent + + onDoubleClicked: { + var center = Qt.point( mouse.x, mouse.y ) + mapCanvasWrapper.zoom( center, 0.8 ) + } + + onClicked: { + if ( mouse.button === Qt.RightButton ) + { + var center = Qt.point( mouse.x, mouse.y ) + mapCanvasWrapper.zoom( center, 1.2 ) + } + else + { + var distance = Math.abs( mouse.x - __initialPosition.x ) + Math.abs( mouse.y - __initialPosition.y ) + + if ( distance < 5 * QgsQuick.Utils.dp) + mapArea.clicked( mouse ) + } + } + + onPressed: { + __lastPosition = Qt.point( mouse.x, mouse.y) + __initialPosition = __lastPosition + freeze('pan') + } + + onReleased: { + unfreeze('pan') + } + + onPositionChanged: { + var currentPosition = Qt.point( mouse.x, mouse.y ) + mapCanvasWrapper.pan( currentPosition, __lastPosition ) + __lastPosition = currentPosition + } + + onCanceled: { + unfreezePanTimer.start() + } + + onWheel: { + mapCanvasWrapper.zoom( Qt.point( wheel.x, wheel.y ), Math.pow( 0.8, wheel.angleDelta.y / 60 ) ) + } + + Timer { + id: unfreezePanTimer + interval: 500; + running: false; + repeat: false + onTriggered: unfreeze('pan') + } + } + } +} diff --git a/src/quickgui/plugin/qgsquickmessagelog.qml b/src/quickgui/plugin/qgsquickmessagelog.qml new file mode 100644 index 000000000000..88de4167698d --- /dev/null +++ b/src/quickgui/plugin/qgsquickmessagelog.qml @@ -0,0 +1,71 @@ +/*************************************************************************** + qgsquickmessagelog.qml + -------------------------------------- + Date : January 2018 + Copyright : (C) 2018 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick.Controls 2.0 +import QtQuick 2.5 +import QgisQuick 0.1 as QgsQuick + +Item { + property alias model: table.model + property color bgColor: "white" + property color separatorColor: "gray" + property int separatorSize: 1 * QgsQuick.Utils.dp + property bool unreadMessages: false + + id: item + + Rectangle { + color: item.bgColor + anchors.fill: parent + } + + ListView { + id: table + anchors.fill: parent + + delegate: Column { + Text { + text: MessageTag + font.bold: true + } + + Text { + text: Message + width: table.width + wrapMode: Text.WordWrap + } + + Rectangle { + color: item.separatorColor + height: item.separatorSize + width: table.width + } + } + } + + Connections { + target: model + + onRowsInserted: { + if ( !visible ) + unreadMessages = true + } + } + + onVisibleChanged: { + if ( visible ) + unreadMessages = false + } +} diff --git a/src/quickgui/plugin/qgsquickphotopanel.qml b/src/quickgui/plugin/qgsquickphotopanel.qml new file mode 100644 index 000000000000..effbe2ed8040 --- /dev/null +++ b/src/quickgui/plugin/qgsquickphotopanel.qml @@ -0,0 +1,221 @@ +/*************************************************************************** + qgsquickphotopanel.qml + -------------------------------------- + Date : Dec 2017 + Copyright : (C) 2017 by Viktor Sklencar + Email : vsklencar 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. * + * * + ***************************************************************************/ + +import QtQuick 2.3 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.2 +import QtQml 2.2 +import QtMultimedia 5.8 +import QtGraphicalEffects 1.0 +import QgisQuick 0.1 as QgsQuick + +Drawer { + property var targetDir + property var lastPhotoName + property int iconSize: photoPanel.width/20 + property var fieldItem + + property color bgColor: "white" + property color borderColor: "black" + + // icons: + property var captureButtonIcon: QgsQuick.Utils.getThemeIcon("ic_camera_alt_border") + property var okButtonIcon: QgsQuick.Utils.getThemeIcon("ic_check_black") + property var cancelButtonIcon: QgsQuick.Utils.getThemeIcon("ic_clear_black") + + + id: photoPanel + visible: false + modal: true + interactive: true + dragMargin: 0 // prevents opening the drawer by dragging. + + + + background: Rectangle { + color: photoPanel.bgColor + opacity: 0.8 + } + + onVisibleChanged: { + if (visible) { + camera.setCameraState(Camera.ActiveState) + camera.start() + } else { + camera.stop() + photoPreview.visible = false + } + } + + // PhotoCapture item + Item { + + property bool saveImage: false + + id: captureItem + width: window.width + height: window.height + + Component.onDestruction: { + if (!captureItem && camera.imageCapture.capturedImagePath != ""){ + captureItem.saveImage = false + QgsQuick.Utils.remove(camera.imageCapture.capturedImagePath) + } + captureItem.saveImage = false + } + + Camera { + id: camera + cameraState: Camera.UnloadedState + + imageCapture { + onImageCaptured: { + // Show the preview in an Image + photoPreview.source = preview + } + } + } + + // Flipped VideoOutput on android - known ButtonGroup + // https://bugreports.qt.io/browse/QTBUG-64764 + VideoOutput { + id: videoOutput + source: camera + focus : visible // to receive focus and capture key events when visible + anchors.fill: parent + autoOrientation: true + + Rectangle { + id: captureButton + property int borderWidth: 10 * QgsQuick.Utils.dp + width: parent.width/20 + height: parent.width/20 + color: photoPanel.bgColor + border.color: photoPanel.borderColor + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + border.width: borderWidth + radius: width*0.5 + antialiasing: true + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + if (targetDir !== "") { + camera.imageCapture.captureToLocation(photoPanel.targetDir); + } else { + // saved to default location - TODO handle this case + camera.imageCapture.capture(); + } + photoPreview.visible = true; + } + } + + Image { + id: captureButtonImage + fillMode: Image.PreserveAspectFit + anchors.centerIn: parent + sourceSize.height: captureButton.height/2 + height: captureButton.height/2 + source: photoPanel.captureButtonIcon + } + + } + + Image { + id: photoPreview + width: parent.width + height: parent.height + fillMode: Image.PreserveAspectFit + + // Cancel button + Rectangle { + id: cancelButton + visible: camera.imageCapture.capturedImagePath != "" + + property int borderWidth: 10 * QgsQuick.Utils.dp + width: parent.width/20 + height: parent.width/20 + color: photoPanel.bgColor + border.color: photoPanel.borderColor + anchors.right: parent.right + anchors.top: confirmButton.bottom + border.width: borderWidth + radius: width*0.5 + antialiasing: true + + MouseArea { + anchors.fill: parent + onClicked: { + captureItem.saveImage = false + photoPreview.visible = false + if (camera.imageCapture.capturedImagePath != "") { + QgsQuick.Utils.remove(camera.imageCapture.capturedImagePath) + } + } + } + + Image { + fillMode: Image.PreserveAspectFit + anchors.centerIn: parent + sourceSize.height: captureButton.height/2 + height: captureButton.height/2 + source: photoPanel.cancelButtonIcon + } + } + + // OK button + Rectangle { + id: confirmButton + visible: camera.imageCapture.capturedImagePath != "" + + property int borderWidth: 10 * QgsQuick.Utils.dp + width: parent.width/20 + height: parent.width/20 + color: photoPanel.bgColor + border.color: photoPanel.borderColor + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + border.width: borderWidth + radius: width*0.5 + antialiasing: true + + MouseArea { + anchors.fill: parent + onClicked: { + captureItem.saveImage = true + photoPanel.visible = false + photoPanel.lastPhotoName = QgsQuick.Utils.getFileName(camera.imageCapture.capturedImagePath) + if (photoPanel.lastPhotoName !== "") { + fieldItem.image.source = photoPanel.targetDir + "/" + photoPanel.lastPhotoName + fieldItem.valueChanged(photoPanel.lastPhotoName, photoPanel.lastPhotoName === "" || photoPanel.lastPhotoName === null) + } + } + } + + Image { + fillMode: Image.PreserveAspectFit + anchors.centerIn: parent + sourceSize.height: captureButton.height/2 + height: captureButton.height/2 + source: photoPanel.okButtonIcon + } + } + } + } + } +} + diff --git a/src/quickgui/plugin/qgsquickplugin.cpp b/src/quickgui/plugin/qgsquickplugin.cpp new file mode 100644 index 000000000000..d99f72a6d824 --- /dev/null +++ b/src/quickgui/plugin/qgsquickplugin.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + qgsquickplugin.cpp + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include + +#include +#include +#include + +#include "qgsfeature.h" +#include "qgslogger.h" +#include "qgsmaplayer.h" +#include "qgsmessagelog.h" +#include "qgspointxy.h" +#include "qgsproject.h" +#include "qgsrelationmanager.h" +#include "qgscoordinatetransformcontext.h" +#include "qgsvectorlayer.h" + +#include "qgsquickattributeformmodel.h" +#include "qgsquickattributeformmodelbase.h" +#include "qgsquickcoordinatetransformer.h" +#include "qgsquickfeaturemodel.h" +#include "qgsquickfeaturehighlight.h" +#include "qgsquickidentifykit.h" +#include "qgsquickfeature.h" +#include "qgsquickmapcanvasmap.h" +#include "qgsquickmapsettings.h" +#include "qgsquickmaptransform.h" +#include "qgsquickmessagelogmodel.h" +#include "qgsquickplugin.h" +#include "qgsquickpositionkit.h" +#include "qgsquickscalebarkit.h" +#include "qgsquicksubmodel.h" +#include "qgsquickutils.h" + +static QObject *_utilsProvider( QQmlEngine *engine, QJSEngine *scriptEngine ) +{ + Q_UNUSED( engine ) + Q_UNUSED( scriptEngine ) + return QgsQuickUtils::instance(); // the object will be owned by QML engine and destroyed by the engine on exit +} + +void QgisQuickPlugin::registerTypes( const char *uri ) +{ + QgsDebugMsg( QStringLiteral( "REGISTERING QQmlExtensionInterface: QgisQuick" ) ); + + qRegisterMetaType< QList >( "QList" ); + qRegisterMetaType< QgsAttributes > ( "QgsAttributes" ); + qRegisterMetaType< QgsCoordinateReferenceSystem >( "QgsCoordinateReferenceSystem" ); + qRegisterMetaType< QgsCoordinateTransformContext >( "QgsCoordinateTransformContext" ); + qRegisterMetaType< QgsFeature > ( "QgsFeature " ); + qRegisterMetaType< QgsFeatureId > ( "QgsFeatureId" ); + qRegisterMetaType< QgsPoint >( "QgsPoint" ); + qRegisterMetaType< QgsPointXY >( "QgsPointXY" ); + qRegisterMetaType< QgsQuickFeature >( "QgsQuickFeature" ); + + qmlRegisterType< QgsProject >( uri, 0, 1, "Project" ); + qmlRegisterType< QgsQuickAttributeFormModel >( uri, 0, 1, "AttributeFormModel" ); + qmlRegisterType< QgsQuickCoordinateTransformer >( uri, 0, 1, "CoordinateTransformer" ); + qmlRegisterType< QgsQuickFeatureModel >( uri, 0, 1, "FeatureModel" ); + qmlRegisterType< QgsQuickFeatureHighlight >( uri, 0, 1, "FeatureHighlight" ); + qmlRegisterType< QgsQuickIdentifyKit >( uri, 0, 1, "IdentifyKit" ); + qmlRegisterType< QgsQuickMapCanvasMap >( uri, 0, 1, "MapCanvasMap" ); + qmlRegisterType< QgsQuickMapSettings >( uri, 0, 1, "MapSettings" ); + qmlRegisterType< QgsQuickMapTransform >( uri, 0, 1, "MapTransform" ); + qmlRegisterType< QgsQuickMessageLogModel >( uri, 0, 1, "MessageLogModel" ); + qmlRegisterType< QgsQuickPositionKit >( uri, 0, 1, "PositionKit" ); + qmlRegisterType< QgsQuickScaleBarKit >( uri, 0, 1, "ScaleBarKit" ); + qmlRegisterType< QgsQuickSubModel >( uri, 0, 1, "SubModel" ); + qmlRegisterType< QgsRelationManager >( uri, 0, 1, "RelationManager" ); + qmlRegisterType< QgsVectorLayer >( uri, 0, 1, "VectorLayer" ); + + qmlRegisterSingletonType< QgsQuickUtils >( uri, 0, 1, "Utils", _utilsProvider ); + + qmlRegisterUncreatableType< QgsMessageLog >( uri, 0, 1, "QgsMessageLog", "Expose MessageLevel" ); +} + diff --git a/src/quickgui/plugin/qgsquickplugin.h b/src/quickgui/plugin/qgsquickplugin.h new file mode 100644 index 000000000000..770dd5b09c2f --- /dev/null +++ b/src/quickgui/plugin/qgsquickplugin.h @@ -0,0 +1,43 @@ +/*************************************************************************** + qgsquickplugin.h + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSQUICKPLUGIN_H +#define QGSQUICKPLUGIN_H + +#include + +/** + * \ingroup quick + * + * Qgis Qml Extension Plugin responsible for exposing C++ Qgis classes to QML + * + * \since QGIS 3.2 + */ +class QgisQuickPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA( IID "org.qt-project.Qt.QQmlExtensionInterface" ) + + public: + + /** + * Registers the QGIS QML types in the given uri + * \param uri an identifier for the plugin generated by the QML engine + */ + void registerTypes( const char *uri ); +}; + +#endif // QGSQUICKPLUGIN_H + diff --git a/src/quickgui/plugin/qgsquickpositionmarker.qml b/src/quickgui/plugin/qgsquickpositionmarker.qml new file mode 100644 index 000000000000..33ac7561997a --- /dev/null +++ b/src/quickgui/plugin/qgsquickpositionmarker.qml @@ -0,0 +1,155 @@ +/*************************************************************************** + qgsquickpositionmarker.qml + -------------------------------------- + Date : Dec 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick 2.3 +import QtQuick.Controls 2.2 +import QtQml 2.2 +import QtGraphicalEffects 1.0 +import QgisQuick 0.1 as QgsQuick + +Item { + id: positionMarker + property int size: 48 * QgsQuick.Utils.dp + + property QgsQuick.MapSettings mapSettings // required to be connected from parent! + property QgsQuick.PositionKit positionKit: QgsQuick.PositionKit {id: positionKit} + + property var simulatePositionLongLatRad // use in debug mode to simulate movement around some GPS location + // longitude, latitude, and radius, all in degrees WSG84 + // e.g. simulatePositionLongLatRad = [60, 10, 0.02] + + property point screenPosition // in pixels + property color baseColor: "darkblue" + property color unavailableColor: "gray" + property alias mapPosition: wgs84toMapCrs.projectedPosition // in map coordinates + property alias gpsPosition: positionKit.position // in WGS84 coordinates + property alias gpsAccuracy: positionKit.accuracy // in meters + + property var gpsPositionLabel: { + if (positionKit.hasPosition) { + QgsQuick.Utils.qgsPointToString(positionKit.position, 3) // e.g -2.243, 45.441 + } else { + qsTr( "GPS signal lost" ).arg( layerName ) + } + } + property var gpsAccuracyLabel: { + if (positionMarker.withAccuracy) { + if (positionKit.hasPosition && positionKit.accuracy > 0) { + QgsQuick.Utils.distanceToString(positionKit.accuracy, 0) // e.g 1 km or 15 m or 500 mm + } else { + qsTr( "GPS accuracy N/A" ) + } + } else { + "" + } + } + property var withAccuracy: true + property var markerIcon: QgsQuick.Utils.getThemeIcon("ic_navigation_black") + + onMapSettingsChanged: update_location() + + Connections { + target: mapSettings + onVisibleExtentChanged: update_location() + } + + function update_location() { + if (mapSettings) + screenPosition = mapSettings.coordinateToScreen(wgs84toMapCrs.projectedPosition) + } + + onSimulatePositionLongLatRadChanged: { + if (simulatePositionLongLatRad) { + var longitude = simulatePositionLongLatRad[0] + var latitude = simulatePositionLongLatRad[1] + var radius = simulatePositionLongLatRad[2] + console.log("Use simulated position around longlat: " + longitude + ", " + latitude + ", radius " + radius ) + positionKit.use_simulated_location(longitude, latitude, radius); + } else { + positionKit.use_gps_location(); + } + } + + QgsQuick.CoordinateTransformer { + id: wgs84toMapCrs + sourceCrs: QgsQuick.Utils.coordinateReferenceSystemFromEpsgId(4326) + destinationCrs: mapSettings.destinationCrs + sourcePosition: positionKit.position + mapSettings: positionMarker.mapSettings + onProjectedPositionChanged: update_location() + } + + // GPS accuracy circle-shaped indicator around positionMarker + Rectangle { + id: accuracyIndicator + visible: mapSettings && + withAccuracy && + positionKit.hasPosition && + (positionKit.accuracy > 0) && + (accuracyIndicator.width > positionMarker.size / 2.0) + x: positionMarker.screenPosition.x - width/2 + y: positionMarker.screenPosition.y - height/2 + width: { + if (positionKit.accuracy > 0) { + var scpm = QgsQuick.Utils.screenUnitsToMeters(positionMarker.mapSettings, 1) // scpm is how much meters is 1 pixel + if (scpm > 0) + 2 * ( positionKit.accuracy / scpm ) + else + 2 + } + else 2 + } + height: accuracyIndicator.width + color: baseColor + border.color: "black" + border.width: 3 + radius: width*0.5 + opacity: 0.1 + } + + Rectangle { + id: navigationMarker + property int borderWidth: 2 + width: positionMarker.size + 20 + height: width + color: "white" + border.color: baseColor + border.width: borderWidth + radius: width*0.5 + antialiasing: true + x: positionMarker.screenPosition.x - width/2 + y: positionMarker.screenPosition.y - height/2 + + Image { + id: navigation + source: positionMarker.markerIcon + fillMode: Image.PreserveAspectFit + rotation: positionKit.direction + anchors.centerIn: parent + width: positionMarker.size + height: width + } + + // Makes positionMarker (navigation) grey if gps signal is lost. + ColorOverlay { + anchors.fill: navigation + source: navigation + color: positionKit.hasPosition ? baseColor : unavailableColor + rotation: positionKit.direction + visible: !(positionKit.hasPosition && baseColor == "black") + } + } + +} diff --git a/src/quickgui/plugin/qgsquickscalebar.qml b/src/quickgui/plugin/qgsquickscalebar.qml new file mode 100644 index 000000000000..7fcbc0ce47ac --- /dev/null +++ b/src/quickgui/plugin/qgsquickscalebar.qml @@ -0,0 +1,102 @@ +/*************************************************************************** + qgsquickscalebar.qml + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QgisQuick 0.1 as QgsQuick + +Item { + id: scaleBar + property alias mapSettings: scaleBarKit.mapSettings + property alias preferredWidth: scaleBarKit.preferredWidth + property QgsQuick.ScaleBarKit scaleBarKit: QgsQuick.ScaleBarKit {id: scaleBarKit} + + property int textWidth: fontMetrics.averageCharacterWidth * 8 + property color barColor: "white" + property color barBackgroundColor: "grey" + property double barOpacity: 0.8 + property string barText: scaleBarKit.distance + " " + scaleBarKit.units + property int barWidth: scaleBarKit.width + property int lineWidth: 5 * QgsQuick.Utils.dp + + width: textWidth + barWidth + + MouseArea { + anchors.fill: background + onClicked: { + animation.restart() + } + } + + NumberAnimation { + id: animation + target: scaleBar + property: "barWidth" + to: 200 + duration: 1000 + } + + Rectangle { + id: background + color: scaleBar.barBackgroundColor + opacity: scaleBar.barOpacity + width: parent.width + height: parent.height + } + + FontMetrics { + id: fontMetrics + font: text.font + } + + Row { + opacity: 1 + spacing: 0 + + Text { + id: text + width: textWidth + height: scaleBar.height + text: barText + color: barColor + font.pixelSize: scaleBar.height - 2 * scaleBar.lineWidth + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + Rectangle { + id: leftBar + width: scaleBar.lineWidth + height: scaleBar.height - 20 * QgsQuick.Utils.dp + y: (scaleBar.height - leftBar.height) / 2 + color: barColor + opacity: 1 + } + + Rectangle { + width: scaleBar.width - text.width - 15 * QgsQuick.Utils.dp + height: scaleBar.lineWidth + y: (scaleBar.height - scaleBar.lineWidth) / 2 + color: barColor + } + + Rectangle { + id: rightBar + width: scaleBar.lineWidth + height: scaleBar.height - 20 * QgsQuick.Utils.dp + y: (scaleBar.height - leftBar.height) / 2 + color: barColor + } + } +} diff --git a/src/quickgui/plugin/qmldir b/src/quickgui/plugin/qmldir new file mode 100644 index 000000000000..b988324f65b7 --- /dev/null +++ b/src/quickgui/plugin/qmldir @@ -0,0 +1,24 @@ +# qmldir +# -------------------------------------- +# Date : Nov 2017 +# Copyright : (C) 2017 by Peter Petrik +# Email : zilolv at gmail dot com +# *************************************************************************** * +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + + +module QgisQuick +plugin qgis_quick_plugin + +MapCanvas 0.1 qgsquickmapcanvas.qml +FeatureForm 0.1 qgsquickfeatureform.qml +FeatureFormStyling 0.1 qgsquickfeatureformstyling.qml +PositionMarker 0.1 qgsquickpositionmarker.qml +ScaleBar 0.1 qgsquickscalebar.qml +PhotoCapture 0.1 qgsquickphotopanel.qml +MessageLog 0.1 qgsquickmessagelog.qml + +typeinfo qgisquick.qmltypes diff --git a/src/quickgui/qgsquickattributeformmodel.cpp b/src/quickgui/qgsquickattributeformmodel.cpp new file mode 100644 index 000000000000..041ccd734258 --- /dev/null +++ b/src/quickgui/qgsquickattributeformmodel.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** + qgsquickattributeformmodel.cpp + -------------------------------------- + Date : 22.9.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 "qgsquickattributeformmodel.h" +#include "qgsquickattributeformmodelbase.h" + +QgsQuickAttributeFormModel::QgsQuickAttributeFormModel( QObject *parent ) + : QSortFilterProxyModel( parent ) + , mSourceModel( new QgsQuickAttributeFormModelBase( this ) ) +{ + setSourceModel( mSourceModel ); + connect( mSourceModel, &QgsQuickAttributeFormModelBase::hasTabsChanged, this, &QgsQuickAttributeFormModel::hasTabsChanged ); + connect( mSourceModel, &QgsQuickAttributeFormModelBase::featureModelChanged, this, &QgsQuickAttributeFormModel::featureModelChanged ); + connect( mSourceModel, &QgsQuickAttributeFormModelBase::featureChanged, this, &QgsQuickAttributeFormModel::featureChanged ); + connect( mSourceModel, &QgsQuickAttributeFormModelBase::constraintsValidChanged, this, &QgsQuickAttributeFormModel::constraintsValidChanged ); +} + +bool QgsQuickAttributeFormModel::hasTabs() const +{ + return mSourceModel->hasTabs(); +} + +void QgsQuickAttributeFormModel::setHasTabs( bool hasTabs ) +{ + mSourceModel->setHasTabs( hasTabs ); +} + +QgsQuickFeatureModel *QgsQuickAttributeFormModel::featureModel() const +{ + return mSourceModel->featureModel(); +} + +void QgsQuickAttributeFormModel::setFeatureModel( QgsQuickFeatureModel *featureModel ) +{ + mSourceModel->setFeatureModel( featureModel ); +} + +bool QgsQuickAttributeFormModel::constraintsValid() const +{ + return mSourceModel->constraintsValid(); +} + +void QgsQuickAttributeFormModel::save() +{ + mSourceModel->save(); +} + +void QgsQuickAttributeFormModel::create() +{ + mSourceModel->create(); +} + +QVariant QgsQuickAttributeFormModel::attribute( const QString &name ) const +{ + return mSourceModel->attribute( name ); +} + +bool QgsQuickAttributeFormModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const +{ + return mSourceModel->data( mSourceModel->index( source_row, 0, source_parent ), CurrentlyVisible ).toBool(); +} diff --git a/src/quickgui/qgsquickattributeformmodel.h b/src/quickgui/qgsquickattributeformmodel.h new file mode 100644 index 000000000000..058373f97851 --- /dev/null +++ b/src/quickgui/qgsquickattributeformmodel.h @@ -0,0 +1,123 @@ +/*************************************************************************** + qgsquickattributeformmodel.h + -------------------------------------- + Date : 22.9.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKATTRIBUTEFORMMODEL_H +#define QGSQUICKATTRIBUTEFORMMODEL_H + +#include + +#include "qgis_quick.h" + +class QgsQuickAttributeFormModelBase; +class QgsQuickFeatureModel; + +/** + * \ingroup quick + * This is a model implementation for attribute form of a feature from a vector layer. + * + * The model is based on vector layer's edit form config (QgsEditFormConfig). It supports + * auto-generated editor layouts and "tab" layout (layout defined with groups and tabs). + * The form layout gets flattened into a list, each row has a bunch of roles with values + * extracted from the edit form config. + * + * It also adds filtering of attribute (attributes may be visible or hidden based on expressions). + * + * \note QML Type: AttributeFormModel + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickAttributeFormModel : public QSortFilterProxyModel +{ + Q_OBJECT + + //! Feature model with attributes + Q_PROPERTY( QgsQuickFeatureModel *featureModel READ featureModel WRITE setFeatureModel NOTIFY featureModelChanged ) + //! Whether use tabs layout + Q_PROPERTY( bool hasTabs READ hasTabs WRITE setHasTabs NOTIFY hasTabsChanged ) + + //! Returns true if all constraints defined on fields are satisfied with the current attribute values + Q_PROPERTY( bool constraintsValid READ constraintsValid NOTIFY constraintsValidChanged ) + + public: + + //! Feature fields's roles + enum FeatureRoles + { + ElementType = Qt::UserRole + 1, //!< Element type + Name, //!< Field Name + AttributeValue, //!< Field Value + AttributeEditable, //!< Field editable + EditorWidget, //!< Widget + EditorWidgetConfig, //!< Config + RememberValue, //!< Remember value + Field, //!< Field + FieldIndex, //!< Index + Group, //!< Group + AttributeEditorElement, //!< Attribute editor element + CurrentlyVisible, //!< Field visible + ConstraintValid, //!< Contraint valid + ConstraintDescription //!< Contraint description + }; + + Q_ENUM( FeatureRoles ) + + //! Create new attribute form model + QgsQuickAttributeFormModel( QObject *parent = nullptr ); + + //! Return whether model has tabs layout + bool hasTabs() const; + + //! Set tabs layout + void setHasTabs( bool hasTabs ); + + //! Return feature model associated + QgsQuickFeatureModel *featureModel() const; + + //! Set feature model + void setFeatureModel( QgsQuickFeatureModel *featureModel ); + + //! Whether all constraints are valid for feature model + bool constraintsValid() const; + + //! Update QgsFeature based on changes + Q_INVOKABLE void save(); + + //! Create new QgsFeature + Q_INVOKABLE void create(); + + //! Return attribute value with name + Q_INVOKABLE QVariant attribute( const QString &name ) const; + + signals: + //! feature model changed + void featureModelChanged(); + + //! has tabs changed + void hasTabsChanged(); + + //! feature changed + void featureChanged(); + + //! constraints valid changed + void constraintsValidChanged(); + + protected: + virtual bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override; + + private: + QgsQuickAttributeFormModelBase *mSourceModel; +}; + +#endif // QGSQUICKATTRIBUTEFORMMODEL_H diff --git a/src/quickgui/qgsquickattributeformmodelbase.cpp b/src/quickgui/qgsquickattributeformmodelbase.cpp new file mode 100644 index 000000000000..a17af5d4f592 --- /dev/null +++ b/src/quickgui/qgsquickattributeformmodelbase.cpp @@ -0,0 +1,385 @@ +/*************************************************************************** + qgsquickfeaturemodelbase.cpp + -------------------------------------- + Date : 16.8.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 "qgseditorwidgetsetup.h" +#include "qgsvectorlayer.h" + +#include "qgsquickattributeformmodelbase.h" +#include "qgsquickattributeformmodel.h" + +/// @cond PRIVATE + +QgsQuickAttributeFormModelBase::QgsQuickAttributeFormModelBase( QObject *parent ) + : QStandardItemModel( 0, 1, parent ) +{ +} + + +QHash QgsQuickAttributeFormModelBase::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + + roles[QgsQuickAttributeFormModel::ElementType] = "Type"; + roles[QgsQuickAttributeFormModel::Name] = "Name"; + roles[QgsQuickAttributeFormModel::AttributeValue] = "AttributeValue"; + roles[QgsQuickAttributeFormModel::AttributeEditable] = "AttributeEditable"; + roles[QgsQuickAttributeFormModel::EditorWidget] = "EditorWidget"; + roles[QgsQuickAttributeFormModel::EditorWidgetConfig] = "EditorWidgetConfig"; + roles[QgsQuickAttributeFormModel::RememberValue] = "RememberValue"; + roles[QgsQuickAttributeFormModel::Field] = "Field"; + roles[QgsQuickAttributeFormModel::Group] = "Group"; + roles[QgsQuickAttributeFormModel::ConstraintValid] = "ConstraintValid"; + roles[QgsQuickAttributeFormModel::ConstraintDescription] = "ConstraintDescription"; + + return roles; +} + +bool QgsQuickAttributeFormModelBase::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + if ( data( index, role ) != value ) + { + switch ( role ) + { + case QgsQuickAttributeFormModel::RememberValue: + { + QStandardItem *item = itemFromIndex( index ); + int fieldIndex = item->data( QgsQuickAttributeFormModel::FieldIndex ).toInt(); + mFeatureModel->setData( mFeatureModel->index( fieldIndex ), value, QgsQuickFeatureModel::RememberAttribute ); + item->setData( value, QgsQuickAttributeFormModel::RememberValue ); + break; + } + + case QgsQuickAttributeFormModel::AttributeValue: + { + QStandardItem *item = itemFromIndex( index ); + int fieldIndex = item->data( QgsQuickAttributeFormModel::FieldIndex ).toInt(); + bool changed = mFeatureModel->setData( mFeatureModel->index( fieldIndex ), value, QgsQuickFeatureModel::AttributeValue ); + if ( changed ) + { + item->setData( value, QgsQuickAttributeFormModel::AttributeValue ); + emit dataChanged( index, index, QVector() << role ); + } + updateVisibility( fieldIndex ); + return changed; + break; + } + } + } + return false; +} + +QgsQuickFeatureModel *QgsQuickAttributeFormModelBase::featureModel() const +{ + return mFeatureModel; +} + +void QgsQuickAttributeFormModelBase::setFeatureModel( QgsQuickFeatureModel *featureModel ) +{ + if ( mFeatureModel == featureModel ) + return; + + if ( mFeatureModel ) + { + disconnect( mFeatureModel, &QgsQuickFeatureModel::layerChanged, this, &QgsQuickAttributeFormModelBase::onLayerChanged ); + disconnect( mFeatureModel, &QgsQuickFeatureModel::featureChanged, this, &QgsQuickAttributeFormModelBase::onFeatureChanged ); + disconnect( mFeatureModel, &QgsQuickFeatureModel::modelReset, this, &QgsQuickAttributeFormModelBase::onFeatureChanged ); + } + + mFeatureModel = featureModel; + + if ( mFeatureModel ) + { + connect( mFeatureModel, &QgsQuickFeatureModel::layerChanged, this, &QgsQuickAttributeFormModelBase::onLayerChanged ); + connect( mFeatureModel, &QgsQuickFeatureModel::featureChanged, this, &QgsQuickAttributeFormModelBase::onFeatureChanged ); + connect( mFeatureModel, &QgsQuickFeatureModel::modelReset, this, &QgsQuickAttributeFormModelBase::onFeatureChanged ); + } + + emit featureModelChanged(); +} + +void QgsQuickAttributeFormModelBase::onLayerChanged() +{ + clear(); + + mLayer = mFeatureModel->layer(); + mVisibilityExpressions.clear(); + mConstraints.clear(); + + if ( mLayer ) + { + QgsAttributeEditorContainer *root; + mTemporaryContainer = nullptr; + + if ( mLayer->editFormConfig().layout() == QgsEditFormConfig::TabLayout ) + { + root = mLayer->editFormConfig().invisibleRootContainer(); + } + else + { + root = generateRootContainer(); //#spellok + mTemporaryContainer.reset( root ); + } + + setHasTabs( !root->children().isEmpty() && QgsAttributeEditorElement::AeTypeContainer == root->children().first()->type() ); + + invisibleRootItem()->setColumnCount( 1 ); + if ( mHasTabs ) + { + Q_FOREACH ( QgsAttributeEditorElement *element, root->children() ) + { + if ( element->type() == QgsAttributeEditorElement::AeTypeContainer ) + { + QgsAttributeEditorContainer *container = static_cast( element ); + + QStandardItem *item = new QStandardItem(); + item->setData( element->name(), QgsQuickAttributeFormModel::Name ); + item->setData( "container", QgsQuickAttributeFormModel::ElementType ); + item->setData( true, QgsQuickAttributeFormModel::CurrentlyVisible ); + invisibleRootItem()->appendRow( item ); + + if ( container->visibilityExpression().enabled() ) + { + mVisibilityExpressions.append( qMakePair( container->visibilityExpression().data(), QVector() << item ) ); + } + + QVector dummy; + flatten( container, item, QString(), dummy ); + } + } + } + else + { + QVector dummy; + flatten( invisibleRootContainer(), invisibleRootItem(), QString(), dummy ); + } + + mExpressionContext = mLayer->createExpressionContext(); + } +} + +void QgsQuickAttributeFormModelBase::onFeatureChanged() +{ + for ( int i = 0 ; i < invisibleRootItem()->rowCount(); ++i ) + { + updateAttributeValue( invisibleRootItem()->child( i ) ); + } + + updateVisibility(); +} + +QgsAttributeEditorContainer *QgsQuickAttributeFormModelBase::generateRootContainer() const //#spellok +{ + QgsAttributeEditorContainer *root = new QgsAttributeEditorContainer( QString(), nullptr ); + QgsFields fields = mLayer->fields(); + for ( int i = 0; i < fields.size(); ++i ) + { + if ( fields.at( i ).editorWidgetSetup().type() != QStringLiteral( "Hidden" ) ) + { + QgsAttributeEditorField *field = new QgsAttributeEditorField( fields.at( i ).name(), i, root ); + root->addChildElement( field ); + } + } + return root; +} + +QgsAttributeEditorContainer *QgsQuickAttributeFormModelBase::invisibleRootContainer() const +{ + return mTemporaryContainer ? mTemporaryContainer.get() : mLayer->editFormConfig().invisibleRootContainer(); +} + +void QgsQuickAttributeFormModelBase::updateAttributeValue( QStandardItem *item ) +{ + if ( item->data( QgsQuickAttributeFormModel::ElementType ) == "field" ) + { + item->setData( mFeatureModel->feature().attribute( item->data( QgsQuickAttributeFormModel::FieldIndex ).toInt() ), QgsQuickAttributeFormModel::AttributeValue ); + } + else + { + for ( int i = 0; i < item->rowCount(); ++i ) + { + updateAttributeValue( item->child( i ) ); + } + } +} + +void QgsQuickAttributeFormModelBase::flatten( QgsAttributeEditorContainer *container, QStandardItem *parent, const QString &parentVisibilityExpressions, QVector &items ) +{ + Q_FOREACH ( QgsAttributeEditorElement *element, container->children() ) + { + switch ( element->type() ) + { + case QgsAttributeEditorElement::AeTypeContainer: + { + QString visibilityExpression = parentVisibilityExpressions; + QgsAttributeEditorContainer *container = static_cast( element ); + if ( container->visibilityExpression().enabled() ) + { + if ( visibilityExpression.isNull() ) + visibilityExpression = container->visibilityExpression().data().expression(); + else + visibilityExpression += " AND " + container->visibilityExpression().data().expression(); + } + + QVector newItems; + flatten( container, parent, visibilityExpression, newItems ); + if ( !visibilityExpression.isEmpty() ) + mVisibilityExpressions.append( qMakePair( QgsExpression( visibilityExpression ), newItems ) ); + break; + } + + case QgsAttributeEditorElement::AeTypeField: + { + QgsAttributeEditorField *editorField = static_cast( element ); + int fieldIndex = editorField->idx(); + if ( fieldIndex < 0 || fieldIndex >= mLayer->fields().size() ) + continue; + + QgsField field = mLayer->fields().at( fieldIndex ); + + QStandardItem *item = new QStandardItem(); + + + item->setData( mLayer->attributeDisplayName( fieldIndex ), QgsQuickAttributeFormModel::Name ); + item->setData( mFeatureModel->feature().attribute( fieldIndex ), QgsQuickAttributeFormModel::AttributeValue ); + item->setData( !mLayer->editFormConfig().readOnly( fieldIndex ), QgsQuickAttributeFormModel::AttributeEditable ); + QgsEditorWidgetSetup setup = mLayer->editorWidgetSetup( fieldIndex ); + item->setData( setup.type(), QgsQuickAttributeFormModel::EditorWidget ); + item->setData( setup.config(), QgsQuickAttributeFormModel::EditorWidgetConfig ); + item->setData( mFeatureModel->rememberedAttributes().at( fieldIndex ) ? Qt::Checked : Qt::Unchecked, QgsQuickAttributeFormModel::RememberValue ); + item->setData( mLayer->fields().at( fieldIndex ), QgsQuickAttributeFormModel::Field ); + item->setData( "field", QgsQuickAttributeFormModel::ElementType ); + item->setData( fieldIndex, QgsQuickAttributeFormModel::FieldIndex ); + item->setData( container->isGroupBox() ? container->name() : QString(), QgsQuickAttributeFormModel::Group ); + item->setData( true, QgsQuickAttributeFormModel::CurrentlyVisible ); + item->setData( true, QgsQuickAttributeFormModel::ConstraintValid ); + item->setData( field.constraints().constraintDescription(), QgsQuickAttributeFormModel::ConstraintDescription ); + + if ( !field.constraints().constraintExpression().isEmpty() ) + { + mConstraints.insert( item, field.constraints().constraintExpression() ); + } + + items.append( item ); + + parent->appendRow( item ); + break; + } + + case QgsAttributeEditorElement::AeTypeRelation: + // todo + break; + + case QgsAttributeEditorElement::AeTypeInvalid: + // todo + break; + } + } +} + +void QgsQuickAttributeFormModelBase::updateVisibility( int fieldIndex ) +{ + QgsFields fields = mFeatureModel->feature().fields(); + mExpressionContext.setFields( fields ); + mExpressionContext.setFeature( mFeatureModel->feature() ); + + Q_FOREACH ( const VisibilityExpression &it, mVisibilityExpressions ) + { + if ( fieldIndex == -1 || it.first.referencedAttributeIndexes( fields ).contains( fieldIndex ) ) + { + QgsExpression exp = it.first; + exp.prepare( &mExpressionContext ); + + bool visible = exp.evaluate( &mExpressionContext ).toInt(); + Q_FOREACH ( QStandardItem *item, it.second ) + { + if ( item->data( QgsQuickAttributeFormModel::CurrentlyVisible ).toBool() != visible ) + { + item->setData( visible, QgsQuickAttributeFormModel::CurrentlyVisible ); + } + } + } + } + + bool allConstraintsValid = true; + QMap::ConstIterator constraintIterator( mConstraints.constBegin() ); + for ( ; constraintIterator != mConstraints.constEnd(); ++constraintIterator ) + { + QStandardItem *item = constraintIterator.key(); + QgsExpression exp = constraintIterator.value(); + exp.prepare( &mExpressionContext ); + bool constraintSatisfied = exp.evaluate( &mExpressionContext ).toBool(); + + if ( constraintSatisfied != item->data( QgsQuickAttributeFormModel::ConstraintValid ).toBool() ) + { + item->setData( constraintSatisfied, QgsQuickAttributeFormModel::ConstraintValid ); + } + + if ( !item->data( QgsQuickAttributeFormModel::ConstraintValid ).toBool() ) + { + allConstraintsValid = false; + } + } + + setConstraintsValid( allConstraintsValid ); +} + +bool QgsQuickAttributeFormModelBase::constraintsValid() const +{ + return mConstraintsValid; +} + +QVariant QgsQuickAttributeFormModelBase::attribute( const QString &name ) const +{ + if ( !mLayer ) + return QVariant(); + + int idx = mLayer->fields().indexOf( name ); + return mFeatureModel->feature().attribute( idx ); +} + +void QgsQuickAttributeFormModelBase::setConstraintsValid( bool constraintsValid ) +{ + if ( constraintsValid == mConstraintsValid ) + return; + + mConstraintsValid = constraintsValid; + emit constraintsValidChanged(); +} + +bool QgsQuickAttributeFormModelBase::hasTabs() const +{ + return mHasTabs; +} + +void QgsQuickAttributeFormModelBase::setHasTabs( bool hasTabs ) +{ + if ( hasTabs == mHasTabs ) + return; + + mHasTabs = hasTabs; + emit hasTabsChanged(); +} + +void QgsQuickAttributeFormModelBase::save() +{ + mFeatureModel->save(); +} + +void QgsQuickAttributeFormModelBase::create() +{ + mFeatureModel->create(); +} + +/// @endcond diff --git a/src/quickgui/qgsquickattributeformmodelbase.h b/src/quickgui/qgsquickattributeformmodelbase.h new file mode 100644 index 000000000000..632bc448f968 --- /dev/null +++ b/src/quickgui/qgsquickattributeformmodelbase.h @@ -0,0 +1,120 @@ +/*************************************************************************** + qgsquickfeaturemodelbase.h + -------------------------------------- + Date : 16.8.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKATTRIBUTEFORMMODELBASE_H +#define QGSQUICKATTRIBUTEFORMMODELBASE_H + +/// @cond PRIVATE + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QGIS API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// + +#include +#include + +#include "qgseditformconfig.h" +#include "qgsexpressioncontext.h" + +#include "qgis_quick.h" +#include "qgsquickfeaturemodel.h" + +/** + * \ingroup quick + * This is an internal (implementation) class used as the source model for QgsQuickAttributeFormModel. + * + * \sa QgsQuickAttributeFormModel + * + * \since QGIS 3.2 + */ +class QgsQuickAttributeFormModelBase : public QStandardItemModel +{ + Q_OBJECT + + Q_PROPERTY( QgsQuickFeatureModel *featureModel READ featureModel WRITE setFeatureModel NOTIFY featureModelChanged ) + Q_PROPERTY( bool hasTabs READ hasTabs WRITE setHasTabs NOTIFY hasTabsChanged ) + Q_PROPERTY( bool constraintsValid READ constraintsValid NOTIFY constraintsValidChanged ) + + public: + explicit QgsQuickAttributeFormModelBase( QObject *parent = nullptr ); + ~QgsQuickAttributeFormModelBase() = default; + + QHash roleNames() const override; + + bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override; + + QgsQuickFeatureModel *featureModel() const; + void setFeatureModel( QgsQuickFeatureModel *featureModel ); + + bool hasTabs() const; + void setHasTabs( bool hasTabs ); + + void save(); + + void create(); + + bool constraintsValid() const; + + QVariant attribute( const QString &name ) const; + + signals: + void featureModelChanged(); + void hasTabsChanged(); + void featureChanged(); + void constraintsValidChanged(); + + private slots: + void onLayerChanged(); + void onFeatureChanged(); + + private: + + /** + * Generates a root container for autogenerated layouts, so we can just use the same + * form logic to deal with them. + */ + QgsAttributeEditorContainer *generateRootContainer() const; //#spellok + + QgsAttributeEditorContainer *invisibleRootContainer() const; + + void updateAttributeValue( QStandardItem *item ); + + void flatten( QgsAttributeEditorContainer *container, QStandardItem *parent, const QString &parentVisibilityExpressions, QVector &items ); + + void updateVisibility( int fieldIndex = -1 ); + + void setConstraintsValid( bool constraintsValid ); + + QgsQuickFeatureModel *mFeatureModel = nullptr; + QgsVectorLayer *mLayer = nullptr; + std::unique_ptr mTemporaryContainer; + bool mHasTabs; + + typedef QPair > VisibilityExpression; + QList mVisibilityExpressions; + QMap mConstraints; + + QgsExpressionContext mExpressionContext; + bool mConstraintsValid; +}; + +/// @endcond + +#endif // QGSQUICKATTRIBUTEFORMMODELBASE_H diff --git a/src/quickgui/qgsquickcoordinatetransformer.cpp b/src/quickgui/qgsquickcoordinatetransformer.cpp new file mode 100644 index 000000000000..eb1fc5cd9a6d --- /dev/null +++ b/src/quickgui/qgsquickcoordinatetransformer.cpp @@ -0,0 +1,102 @@ +/*************************************************************************** + qgsquickcoordinatetransformer.cpp + -------------------------------------- + Date : 1.6.2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 + +#include "qgsquickcoordinatetransformer.h" + +QgsQuickCoordinateTransformer::QgsQuickCoordinateTransformer( QObject *parent ) + : QObject( parent ) +{ + mCoordinateTransform.setSourceCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) ); + mCoordinateTransform.setContext( QgsCoordinateTransformContext() ); +} + +QgsPoint QgsQuickCoordinateTransformer::projectedPosition() const +{ + return mProjectedPosition; +} + +QgsPoint QgsQuickCoordinateTransformer::sourcePosition() const +{ + return mSourcePosition; +} + +void QgsQuickCoordinateTransformer::setSourcePosition( QgsPoint sourcePosition ) +{ + if ( mSourcePosition == sourcePosition ) + return; + + mSourcePosition = sourcePosition; + + emit sourcePositionChanged(); + updatePosition(); +} + +QgsCoordinateReferenceSystem QgsQuickCoordinateTransformer::destinationCrs() const +{ + return mCoordinateTransform.destinationCrs(); +} + +void QgsQuickCoordinateTransformer::setDestinationCrs( const QgsCoordinateReferenceSystem &destinationCrs ) +{ + if ( destinationCrs == mCoordinateTransform.destinationCrs() ) + return; + + mCoordinateTransform.setDestinationCrs( destinationCrs ); + emit destinationCrsChanged(); + updatePosition(); +} + +QgsCoordinateReferenceSystem QgsQuickCoordinateTransformer::sourceCrs() const +{ + return mCoordinateTransform.sourceCrs(); +} + +void QgsQuickCoordinateTransformer::setSourceCrs( const QgsCoordinateReferenceSystem &sourceCrs ) +{ + if ( sourceCrs == mCoordinateTransform.sourceCrs() ) + return; + + mCoordinateTransform.setSourceCrs( sourceCrs ); + + emit sourceCrsChanged(); + updatePosition(); +} + +void QgsQuickCoordinateTransformer::updatePosition() +{ + double x = mSourcePosition.x(); + double y = mSourcePosition.y(); + double z = mSourcePosition.z(); + + // If Z is NaN, coordinate transformation (proj4) will + // also set X and Y to NaN. But we also want to get projected + // coords if we do not have any Z coordinate. + if ( qIsNaN( z ) ) + { + z = 0; + } + + if ( mMapSettings ) + mCoordinateTransform.setContext( mMapSettings->transformContext() ); + + mCoordinateTransform.transformInPlace( x, y, z ); + + mProjectedPosition = QgsPoint( x, y ); + mProjectedPosition.addZValue( mSourcePosition.z() ); + + emit projectedPositionChanged(); +} diff --git a/src/quickgui/qgsquickcoordinatetransformer.h b/src/quickgui/qgsquickcoordinatetransformer.h new file mode 100644 index 000000000000..2a8924f0658b --- /dev/null +++ b/src/quickgui/qgsquickcoordinatetransformer.h @@ -0,0 +1,103 @@ +/*************************************************************************** + qgsquickcoordinatetransformer.h + -------------------------------------- + Date : 1.6.2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKCOORDINATETRANSFORMER_H +#define QGSQUICKCOORDINATETRANSFORMER_H + +#include + +#include "qgspoint.h" + +#include "qgis_quick.h" +#include "qgsquickmapsettings.h" + +/** + * \ingroup quick + * Helper class for transform of coordinates (QgsPoint) to a different coordinate reference system. + * + * \note QML Type: CoordinateTransformer + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickCoordinateTransformer : public QObject +{ + Q_OBJECT + + //! projected (destination) position + Q_PROPERTY( QgsPoint projectedPosition READ projectedPosition NOTIFY projectedPositionChanged ) + + //! Source position + Q_PROPERTY( QgsPoint sourcePosition READ sourcePosition WRITE setSourcePosition NOTIFY sourcePositionChanged ) + + //! Destination CRS + Q_PROPERTY( QgsCoordinateReferenceSystem destinationCrs READ destinationCrs WRITE setDestinationCrs NOTIFY destinationCrsChanged ) + + //! Source CRS, default 4326 + Q_PROPERTY( QgsCoordinateReferenceSystem sourceCrs READ sourceCrs WRITE setSourceCrs NOTIFY sourceCrsChanged ) + + //! Map settings, for getting transformation context + Q_PROPERTY( QgsQuickMapSettings *mapSettings MEMBER mMapSettings NOTIFY mapSettingsChanged ) + + public: + //! create new coordinate transformer + explicit QgsQuickCoordinateTransformer( QObject *parent = 0 ); + + //! Return projected position (in destination CRS) + QgsPoint projectedPosition() const; + + //! Return source position (in source CRS) + QgsPoint sourcePosition() const; + + //! Set source position (in source CRS) + void setSourcePosition( QgsPoint sourcePosition ); + + //! Return destination CRS + QgsCoordinateReferenceSystem destinationCrs() const; + + //! Set destination CRS + void setDestinationCrs( const QgsCoordinateReferenceSystem &destinationCrs ); + + //! Return source CRS + QgsCoordinateReferenceSystem sourceCrs() const; + + //! Set source CRS + void setSourceCrs( const QgsCoordinateReferenceSystem &sourceCrs ); + + signals: + //! projected position changed + void projectedPositionChanged(); + + //! source position changed + void sourcePositionChanged(); + + //! destination CRS changed + void destinationCrsChanged(); + + //! source CRS changed + void sourceCrsChanged(); + + //! map settings changed + void mapSettingsChanged(); + + private: + void updatePosition(); + + QgsPoint mProjectedPosition; + QgsPoint mSourcePosition; + QgsCoordinateTransform mCoordinateTransform; + QgsQuickMapSettings *mMapSettings = nullptr; +}; + +#endif // QGSQUICKCOORDINATETRANSFORMER_H diff --git a/src/quickgui/qgsquickfeature.cpp b/src/quickgui/qgsquickfeature.cpp new file mode 100644 index 000000000000..a27bd055c129 --- /dev/null +++ b/src/quickgui/qgsquickfeature.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + qgsquickfeature.cpp + --------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsvectorlayer.h" +#include "qgsfeature.h" + +#include "qgsquickfeature.h" + +QgsQuickFeature::QgsQuickFeature( const QgsFeature &feature, QgsVectorLayer *layer ) + : mLayer( layer ) + , mFeature( feature ) +{ + if ( !mLayer ) + { + mFeature.setValid( false ); + } +} + +QgsQuickFeature::QgsQuickFeature(): mLayer( 0 ) +{ + mFeature.setValid( false ); +} + +QgsVectorLayer *QgsQuickFeature::layer() const +{ + return mLayer; +} + +QgsFeature QgsQuickFeature::feature() const +{ + return mFeature; +} + +bool QgsQuickFeature::valid() const +{ + return ( mLayer && mFeature.isValid() ); +} diff --git a/src/quickgui/qgsquickfeature.h b/src/quickgui/qgsquickfeature.h new file mode 100644 index 000000000000..d27ea54dd709 --- /dev/null +++ b/src/quickgui/qgsquickfeature.h @@ -0,0 +1,70 @@ +/*************************************************************************** + qgsquickfeature.h + --------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSQUICKFEATURE_H +#define QGSQUICKFEATURE_H + +#include + +#include "qgsfeature.h" + +#include "qgis_quick.h" + +class QgsVectorLayer; + +/** + * \ingroup quick + * Helper class for QgsFeature and QgsVectorLayer where it belongs. + * + * \note QML Type: IdentifyResult + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickFeature +{ + Q_GADGET + + //! feature + Q_PROPERTY( QgsVectorLayer *layer READ layer ) + //! layer to which \a feature belogs + Q_PROPERTY( QgsFeature feature READ feature ) + //! whether is feature valid + Q_PROPERTY( bool valid READ valid ) + + public: + //! create new feature + QgsQuickFeature(); + + //! create new feature + QgsQuickFeature( const QgsFeature &feature, + QgsVectorLayer *layer ); + + //! Return layer + QgsVectorLayer *layer() const; + + //! Return feature + QgsFeature feature() const; + + //! Return whether is feature valid + bool valid() const; + + private: + QgsVectorLayer *mLayer; + QgsFeature mFeature; +}; + +Q_DECLARE_METATYPE( QgsQuickFeature ) + +#endif // QGSQUICKFEATURE_H diff --git a/src/quickgui/qgsquickfeaturehighlight.cpp b/src/quickgui/qgsquickfeaturehighlight.cpp new file mode 100644 index 000000000000..f84429416be8 --- /dev/null +++ b/src/quickgui/qgsquickfeaturehighlight.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** + qgsqguickfeaturehighlight.cpp + -------------------------------------- + Date : 9.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 "qgsvectorlayer.h" + +#include "qgsquickfeaturemodel.h" +#include "qgsquickfeaturehighlight.h" +#include "qgsquickmapsettings.h" +#include "qgsquickhighlightsgnode.h" + + +QgsQuickFeatureHighlight::QgsQuickFeatureHighlight( QQuickItem *parent ) + : QQuickItem( parent ) +{ + setFlags( QQuickItem::ItemHasContents ); + setAntialiasing( true ); + + connect( this, &QgsQuickFeatureHighlight::modelChanged, this, &QgsQuickFeatureHighlight::onDataChanged ); +} + +void QgsQuickFeatureHighlight::onDataChanged() +{ + if ( mModel ) + { + connect( mModel, &QgsQuickFeatureModel::modelReset, this, &QgsQuickFeatureHighlight::onModelDataChanged ); + connect( mModel, &QgsQuickFeatureModel::rowsRemoved, this, &QgsQuickFeatureHighlight::onModelDataChanged ); + } + + onModelDataChanged(); +} + +void QgsQuickFeatureHighlight::onModelDataChanged() +{ + mDirty = true; + update(); +} + +QSGNode *QgsQuickFeatureHighlight::updatePaintNode( QSGNode *n, QQuickItem::UpdatePaintNodeData * ) +{ + if ( !mDirty || !mMapSettings ) + return n; + + delete n; + n = new QSGNode; + + if ( !mModel ) + return n; + + QgsVectorLayer *layer = mModel->layer(); + if ( layer ) + { + QgsCoordinateTransform transf( layer->crs(), mMapSettings->destinationCrs(), mMapSettings->transformContext() ); + + QgsFeature feature = mModel->feature(); + QgsGeometry geom( feature.geometry() ); + geom.transform( transf ); + + // TODO: this is very crude conversion! QgsQuickHighlightsNode should accept any type of geometry + QVector points; + for ( auto it = geom.vertices_begin(); it != geom.vertices_end(); ++it ) + points.append( *it ); + + QgsQuickHighlightSGNode *rb = new QgsQuickHighlightSGNode( points, geom.type(), mColor, mWidth ); + rb->setFlag( QSGNode::OwnedByParent ); + n->appendChildNode( rb ); + } + mDirty = false; + + return n; +} diff --git a/src/quickgui/qgsquickfeaturehighlight.h b/src/quickgui/qgsquickfeaturehighlight.h new file mode 100644 index 000000000000..3280291d4ddb --- /dev/null +++ b/src/quickgui/qgsquickfeaturehighlight.h @@ -0,0 +1,84 @@ +/*************************************************************************** + qgsqguickfeaturehighlight.h + -------------------------------------- + Date : 9.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKFEATUREHIGHLIGHT_H +#define QGSQUICKFEATUREHIGHLIGHT_H + +#include + +#include "qgis_quick.h" + +class QgsQuickMapSettings; +class QgsQuickFeatureModel; + +/** + * \ingroup quick + * + * Creates map highlights for a geometry provided by a FeatureModel. + * + * The highlights are compatible with the QtQuick scene graph. + * + * \note QML Type: FeatureModelHighlight + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickFeatureHighlight : public QQuickItem +{ + Q_OBJECT + + //! map settings + Q_PROPERTY( QgsQuickMapSettings *mapSettings MEMBER mMapSettings NOTIFY mapSettingsChanged ) + //! feature model (for geometry) + Q_PROPERTY( QgsQuickFeatureModel *model MEMBER mModel NOTIFY modelChanged ) + //! color of the highlighed geometry + Q_PROPERTY( QColor color MEMBER mColor NOTIFY colorChanged ) + //! pen width of the highlight + Q_PROPERTY( unsigned int width MEMBER mWidth NOTIFY widthChanged ) + + public: + //! create new feature highlight + explicit QgsQuickFeatureHighlight( QQuickItem *parent = 0 ); + + signals: + //! model changed + void modelChanged(); + + //! color changed + void colorChanged(); + + //! map canvas changed + void mapCanvasChanged(); + + //! width changed + void widthChanged(); + + //! map settings changed + void mapSettingsChanged(); + + private slots: + void onDataChanged(); + void onModelDataChanged(); + + private: + virtual QSGNode *updatePaintNode( QSGNode *n, UpdatePaintNodeData * ) override; + + QColor mColor; + QgsQuickFeatureModel *mModel = nullptr; + bool mDirty = false; + unsigned int mWidth; + QgsQuickMapSettings *mMapSettings = nullptr; +}; + +#endif // QGSQUICKFEATUREHIGHLIGHT_H diff --git a/src/quickgui/qgsquickfeaturemodel.cpp b/src/quickgui/qgsquickfeaturemodel.cpp new file mode 100644 index 000000000000..e6c75dc83286 --- /dev/null +++ b/src/quickgui/qgsquickfeaturemodel.cpp @@ -0,0 +1,293 @@ +/*************************************************************************** + qgsquickfeaturemodel.cpp + -------------------------------------- + Date : 10.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 "qgis.h" +#include "qgsmessagelog.h" +#include "qgsvectorlayer.h" + +#include "qgsquickfeaturemodel.h" + +QgsQuickFeatureModel::QgsQuickFeatureModel( QObject *parent ) + : QAbstractListModel( parent ) +{ + connect( this, &QgsQuickFeatureModel::modelReset, this, &QgsQuickFeatureModel::featureChanged ); +} + +void QgsQuickFeatureModel::setFeature( const QgsFeature &feature ) +{ + if ( mFeature == feature ) + return; + + beginResetModel(); + mFeature = feature; + endResetModel(); + emit featureChanged(); +} + +void QgsQuickFeatureModel::setLayer( QgsVectorLayer *layer ) +{ + if ( layer == mLayer ) + return; + + mLayer = layer; + if ( mLayer ) + { + mFeature = QgsFeature( mLayer->fields() ); + + mRememberedAttributes.resize( layer->fields().size() ); + mRememberedAttributes.fill( false ); + } + + emit layerChanged(); +} + +QgsVectorLayer *QgsQuickFeatureModel::layer() const +{ + return mLayer; +} + +QgsFeature QgsQuickFeatureModel::feature() const +{ + return mFeature; +} + +QHash QgsQuickFeatureModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles[AttributeName] = "AttributeName"; + roles[AttributeValue] = "AttributeValue"; + roles[Field] = "Field"; + roles[RememberAttribute] = "RememberAttribute"; + + return roles; +} + + +int QgsQuickFeatureModel::rowCount( const QModelIndex &parent ) const +{ + if ( parent.isValid() ) + return 0; + else + return mFeature.attributes().count(); +} + +QVariant QgsQuickFeatureModel::data( const QModelIndex &index, int role ) const +{ + switch ( role ) + { + case AttributeName: + return mLayer->attributeDisplayName( index.row() ); + break; + + case AttributeValue: + return mFeature.attribute( index.row() ); + break; + + case Field: + return mLayer->fields().at( index.row() ); + break; + + case RememberAttribute: + return mRememberedAttributes.at( index.row() ); + break; + } + + return QVariant(); +} + +bool QgsQuickFeatureModel::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + if ( data( index, role ) == value ) + return true; + + switch ( role ) + { + case AttributeValue: + { + QVariant val( value ); + QgsField fld = mFeature.fields().at( index.row() ); + + if ( !fld.convertCompatible( val ) ) + { + QgsMessageLog::logMessage( tr( "Value \"%1\" %4 could not be converted to a compatible value for field %2(%3)." ).arg( value.toString(), fld.name(), fld.typeName(), value.isNull() ? "NULL" : "NOT NULL" ) ); + return false; + } + bool success = mFeature.setAttribute( index.row(), val ); + if ( success ) + emit dataChanged( index, index, QVector() << role ); + return success; + break; + } + + case RememberAttribute: + { + mRememberedAttributes[ index.row() ] = value.toBool(); + emit dataChanged( index, index, QVector() << role ); + break; + } + } + + return false; +} + +bool QgsQuickFeatureModel::save() +{ + if ( !mLayer ) + return false; + + bool rv = true; + + if ( !startEditing() ) + { + rv = false; + } + + QgsFeature feat = mFeature; + if ( !mLayer->updateFeature( feat ) ) + QgsMessageLog::logMessage( tr( "Cannot update feature" ), "QgsQuick", Qgis::Warning ); + rv = commit(); + + if ( rv ) + { + QgsFeature feat; + if ( mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( feat ) ) + setFeature( feat ); + else + QgsMessageLog::logMessage( tr( "Feature %1 could not be fetched after commit" ).arg( mFeature.id() ), "QgsQuick", Qgis::Warning ); + } + return rv; +} + +bool QgsQuickFeatureModel::deleteFeature() +{ + if ( !mLayer ) + return false; + + bool rv = true; + + if ( !startEditing() ) + { + rv = false; + } + + if ( !mLayer->deleteFeature( mFeature.id() ) ) + QgsMessageLog::logMessage( tr( "Cannot delete feature" ), "QgsQuick", Qgis::Warning ); + rv = commit(); + + return rv; +} + +void QgsQuickFeatureModel::reset() +{ + if ( !mLayer ) + return; + + mLayer->rollBack(); +} + +bool QgsQuickFeatureModel::suppressFeatureForm() const +{ + if ( !mLayer ) + return false; + + return mLayer->editFormConfig().suppress(); +} + +void QgsQuickFeatureModel::resetAttributes() +{ + if ( !mLayer ) + return; + + QgsExpressionContext expressionContext = mLayer->createExpressionContext(); + expressionContext.setFeature( mFeature ); + + QgsFields fields = mLayer->fields(); + + beginResetModel(); + for ( int i = 0; i < fields.count(); ++i ) + { + if ( !mRememberedAttributes.at( i ) ) + { + if ( !fields.at( i ).defaultValueDefinition().expression().isEmpty() ) + { + QgsExpression exp( fields.at( i ).defaultValueDefinition().expression() ); + exp.prepare( &expressionContext ); + if ( exp.hasParserError() ) + QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has parser error: %3" ).arg( mLayer->name(), fields.at( i ).name(), exp.parserErrorString() ), QStringLiteral( "QgsQuick" ), Qgis::Warning ); + + QVariant value = exp.evaluate( &expressionContext ); + + if ( exp.hasEvalError() ) + QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has evaluation error: %3" ).arg( mLayer->name(), fields.at( i ).name(), exp.evalErrorString() ), QStringLiteral( "QgsQuick" ), Qgis::Warning ); + + mFeature.setAttribute( i, value ); + } + else + { + mFeature.setAttribute( i, QVariant() ); + } + } + } + endResetModel(); +} + +void QgsQuickFeatureModel::create() +{ + if ( !mLayer ) + return; + + startEditing(); + if ( !mLayer->addFeature( mFeature ) ) + { + QgsMessageLog::logMessage( tr( "Feature could not be added" ), "QgsQuick", Qgis::Critical ); + } + commit(); +} + +bool QgsQuickFeatureModel::commit() +{ + if ( !mLayer->commitChanges() ) + { + QgsMessageLog::logMessage( tr( "Could not save changes. Rolling back." ), "QgsQuick", Qgis::Critical ); + mLayer->rollBack(); + return false; + } + else + { + return true; + } +} + +bool QgsQuickFeatureModel::startEditing() +{ + // Already an edit session active + if ( mLayer->editBuffer() ) + return true; + + if ( !mLayer->startEditing() ) + { + QgsMessageLog::logMessage( tr( "Cannot start editing" ), "QgsQuick", Qgis::Warning ); + return false; + } + else + { + return true; + } +} + +QVector QgsQuickFeatureModel::rememberedAttributes() const +{ + return mRememberedAttributes; +} diff --git a/src/quickgui/qgsquickfeaturemodel.h b/src/quickgui/qgsquickfeaturemodel.h new file mode 100644 index 000000000000..215d674d6b15 --- /dev/null +++ b/src/quickgui/qgsquickfeaturemodel.h @@ -0,0 +1,135 @@ +/*************************************************************************** + qgsquickfeaturemodel.h + -------------------------------------- + Date : 10.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKFEATUREMODEL_H +#define QGSQUICKFEATUREMODEL_H + +#include +#include + +#include "qgsfeature.h" +#include "qgsvectorlayer.h" + +#include "qgis_quick.h" + +/** + * \ingroup quick + * Item model implementation for attributes of QgsFeature: each attribute gets a row in the model. + * + * \note QML Type: FeatureModel + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickFeatureModel : public QAbstractListModel +{ + Q_OBJECT + //! feature + Q_PROPERTY( QgsFeature feature READ feature WRITE setFeature NOTIFY featureChanged ) + //! layer to which \a feature belogs + Q_PROPERTY( QgsVectorLayer *layer READ layer WRITE setLayer NOTIFY layerChanged ) + //! feature roles + Q_ENUMS( FeatureRoles ) + + public: + enum FeatureRoles + { + AttributeName = Qt::UserRole + 1, //!< Attribute's display name (the original field name or a custom alias) + AttributeValue, //!< Value of the feature's attribute + Field, //!< Field definition (QgsField) + RememberAttribute //!< Remember attribute value for next feature + }; + + //! Create new feature model + explicit QgsQuickFeatureModel( QObject *parent = 0 ); + + //! Create new feature model + explicit QgsQuickFeatureModel( const QgsFeature &feat, QObject *parent = 0 ); + + //! Set feature to feature model + void setFeature( const QgsFeature &feature ); + + /** + * Return the feature wrapped in a QVariant for passing it around in QML + */ + QgsFeature feature() const; + + //! Set feature model layer + void setLayer( QgsVectorLayer *layer ); + + //! Return feature model layer + QgsVectorLayer *layer() const; + + + QHash roleNames() const override; + int rowCount( const QModelIndex &parent ) const override; + QVariant data( const QModelIndex &index, int role ) const override; + bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override; + + /** + * Will commit the edit buffer of this layer. + * May change in the future to only commit the changes buffered in this model. + * + * @return Success of the operation + */ + Q_INVOKABLE bool save(); + + /** + * Will delete the current feature from the layer and commit the changes. + * @return Success of the operation + */ + Q_INVOKABLE bool deleteFeature(); + + /** + * Will reset the feature to the original values and dismiss any buffered edits. + */ + Q_INVOKABLE void reset(); + + //! Add mFeature to mLayer + Q_INVOKABLE void create(); + + /** + * Suppress layer's QgsEditFormConfig + * + * \sa QgsEditFormConfig::suppress + */ + Q_INVOKABLE bool suppressFeatureForm() const; + + //! Reset remembered attributes + Q_INVOKABLE virtual void resetAttributes(); + + //! Get remembered attributes + QVector rememberedAttributes() const; + + public slots: + + signals: + //! feature changed + void featureChanged(); + + //! layer changed + void layerChanged(); + + protected: + //! commit model changes + bool commit(); + //! start editing model + bool startEditing(); + + QgsVectorLayer *mLayer = nullptr; + QgsFeature mFeature; + QVector mRememberedAttributes; +}; + +#endif // QGSQUICKFEATUREMODEL_H diff --git a/src/quickgui/qgsquickhighlightsgnode.cpp b/src/quickgui/qgsquickhighlightsgnode.cpp new file mode 100644 index 000000000000..3aa8e19e6e97 --- /dev/null +++ b/src/quickgui/qgsquickhighlightsgnode.cpp @@ -0,0 +1,99 @@ +/*************************************************************************** + qgsquickhighlightsgnode.cpp + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 "qgsquickhighlightsgnode.h" + + +QgsQuickHighlightSGNode::QgsQuickHighlightSGNode( const QVector &points, QgsWkbTypes::GeometryType type, const QColor &color, qreal width ) + : QSGNode() +{ + mMaterial.setColor( color ); + + // TODO: support multi-part geometries + switch ( type ) + { + case QgsWkbTypes::PointGeometry: + { + if ( !points.isEmpty() ) + appendChildNode( createPointGeometry( points.at( 0 ), width ) ); + break; + } + + case QgsWkbTypes::LineGeometry: + { + appendChildNode( createLineGeometry( points, width ) ); + break; + } + + case QgsWkbTypes::PolygonGeometry: + { + // TODO: support polygon geometries + break; + } + + case QgsWkbTypes::UnknownGeometry: + case QgsWkbTypes::NullGeometry: + break; + } +} + +QgsQuickHighlightSGNode::~QgsQuickHighlightSGNode() +{ +} + +QSGGeometryNode *QgsQuickHighlightSGNode::createLineGeometry( const QVector &points, qreal width ) +{ + QSGGeometryNode *node = new QSGGeometryNode; + QSGGeometry *sgGeom = new QSGGeometry( QSGGeometry::defaultAttributes_Point2D(), points.count() ); + QSGGeometry::Point2D *vertices = sgGeom->vertexDataAsPoint2D(); + + int i = 0; + Q_FOREACH ( const QgsPoint &pt, points ) + { + vertices[i++].set( pt.x(), pt.y() ); + } + + sgGeom->setLineWidth( width ); + sgGeom->setDrawingMode( GL_LINE_STRIP ); + node->setGeometry( sgGeom ); + node->setMaterial( &mMaterial ); + node->setFlag( QSGNode::OwnsGeometry ); + node->setFlag( QSGNode::OwnedByParent ); + return node; +} + +QSGGeometryNode *QgsQuickHighlightSGNode::createPointGeometry( const QgsPoint &point, qreal width ) +{ + QSGGeometryNode *node = new QSGGeometryNode; + + QSGGeometry *sgGeom = new QSGGeometry( QSGGeometry::defaultAttributes_Point2D(), 1 ); + + QSGGeometry::Point2D *vertices = sgGeom->vertexDataAsPoint2D(); + vertices[0].set( point.x(), point.y() ); + sgGeom->setDrawingMode( GL_POINTS ); + sgGeom->setLineWidth( width ); + + node->setGeometry( sgGeom ); + node->setMaterial( &mMaterial ); + node->setFlag( QSGNode::OwnsGeometry ); + node->setFlag( QSGNode::OwnedByParent ); + return node; +} + +QSGGeometryNode *QgsQuickHighlightSGNode::createPolygonGeometry( const QVector &points ) +{ + Q_UNUSED( points ); + return 0; +} diff --git a/src/quickgui/qgsquickhighlightsgnode.h b/src/quickgui/qgsquickhighlightsgnode.h new file mode 100644 index 000000000000..d95ec697d7f0 --- /dev/null +++ b/src/quickgui/qgsquickhighlightsgnode.h @@ -0,0 +1,57 @@ +/*************************************************************************** + qgsquickhighlightsgnode.h + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKHIGHLIGHTSGNODE_H +#define QGSQUICKHIGHLIGHTSGNODE_H + +#include +#include + +#include "qgspoint.h" +#include "qgswkbtypes.h" + +#include "qgis_quick.h" + +/** + * \ingroup quick + * + * This is used to transform (render) QgsGeometry to node for QtQuick scene graph. + * + * Note: support for multi-part geometries and polygons is not implemented + * + * \note QML Type: not exported + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickHighlightSGNode : public QSGNode +{ + public: + //! Create new QT Quick scene node based on geometry + QgsQuickHighlightSGNode( const QVector &points, QgsWkbTypes::GeometryType type, const QColor &color, qreal width ); + //! Destroy node + virtual ~QgsQuickHighlightSGNode(); + + protected: + //! Construct line geometry from points + QSGGeometryNode *createLineGeometry( const QVector &points, qreal width ); + //! Construct point geometry from qgs point + QSGGeometryNode *createPointGeometry( const QgsPoint &point, qreal width ); + //! Construct polygon geometry from points (not implemented) + virtual QSGGeometryNode *createPolygonGeometry( const QVector &points ); + + QSGFlatColorMaterial mMaterial; +}; + +#endif // QGSQUICKHIGHLIGHTSGNODE diff --git a/src/quickgui/qgsquickidentifykit.cpp b/src/quickgui/qgsquickidentifykit.cpp new file mode 100644 index 000000000000..c6d4a39941cc --- /dev/null +++ b/src/quickgui/qgsquickidentifykit.cpp @@ -0,0 +1,287 @@ +/*************************************************************************** + qgsquickidentifykit.cpp + --------------------- + Date : 30.8.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 "qgsproject.h" +#include "qgslogger.h" +#include "qgsrenderer.h" +#include "qgsvectorlayer.h" + +#include "qgsquickidentifykit.h" +#include "qgsquickmapsettings.h" + +QgsQuickIdentifyKit::QgsQuickIdentifyKit( QObject *parent ) + : QObject( parent ) + , mSearchRadiusMm( 8 ) + , mFeaturesLimit( 100 ) +{ +} + +QgsQuickMapSettings *QgsQuickIdentifyKit::mapSettings() const +{ + return mMapSettings; +} + +void QgsQuickIdentifyKit::setMapSettings( QgsQuickMapSettings *mapSettings ) +{ + if ( mapSettings == mMapSettings ) + return; + + mMapSettings = mapSettings; + emit mapSettingsChanged(); +} + + +QList QgsQuickIdentifyKit::identify( const QPointF &point ) +{ + QList results; + + if ( !mMapSettings ) + { + qWarning() << "Unable to use IdentifyKit without mapSettings property set."; + return results; + } + + QgsPointXY mapPoint = mMapSettings->mapSettings().mapToPixel().toMapCoordinates( point.toPoint() ); + + QStringList noIdentifyLayerIdList; + if ( mMapSettings->project() ) + { + noIdentifyLayerIdList = mMapSettings->project()->nonIdentifiableLayers(); + } + + Q_FOREACH ( QgsMapLayer *layer, mMapSettings->mapSettings().layers() ) + { + if ( mMapSettings->project() && noIdentifyLayerIdList.contains( layer->id() ) ) + continue; + + QgsVectorLayer *vl = qobject_cast( layer ); + if ( vl ) + { + QgsFeatureList featureList = identifyVectorLayer( vl, mapPoint ); + + Q_FOREACH ( const QgsFeature &feature, featureList ) + { + results.append( QgsQuickFeature( feature, vl ) ); + } + } + } + + QgsDebugMsg( QStringLiteral( "IdentifyKit identified %1 results" ).arg( results.count() ) ); + return results; +} + +static QgsFeature _closestFeature( const QgsFeatureList &results, const QgsMapSettings &mapSettings, QgsVectorLayer *layer, const QPointF &point ) +{ + QgsPointXY mapPoint = mapSettings.mapToPixel().toMapCoordinates( point.toPoint() ); + QgsGeometry mapPointGeom( QgsGeometry::fromPointXY( mapPoint ) ); + QgsCoordinateTransform ctLayerToMap = mapSettings.layerTransform( layer ); + + Q_ASSERT( results.count() != 0 ); + double distMin = 1e10; + int iMin = -1; + for ( int i = 0; i < results.count(); ++i ) + { + QgsGeometry geom( results.at( i ).geometry() ); + geom.transform( ctLayerToMap ); + + double dist = geom.distance( mapPointGeom ); + if ( dist < distMin ) + { + iMin = i; + distMin = dist; + } + } + return results.at( iMin ); +} + + +static QgsQuickFeature _closestFeature( const QList &results, const QgsMapSettings &mapSettings, const QPointF &point ) +{ + QgsPointXY mapPoint = mapSettings.mapToPixel().toMapCoordinates( point.toPoint() ); + QgsGeometry mapPointGeom( QgsGeometry::fromPointXY( mapPoint ) ); + + Q_ASSERT( results.count() != 0 ); + double distMin = 1e10; + int iMin = -1; + for ( int i = 0; i < results.count(); ++i ) + { + const QgsQuickFeature &res = results.at( i ); + QgsGeometry geom( res.feature().geometry() ); + geom.transform( mapSettings.layerTransform( res.layer() ) ); + + double dist = geom.distance( mapPointGeom ); + if ( dist < distMin ) + { + iMin = i; + distMin = dist; + } + } + return results.at( iMin ); +} + + +QgsFeature QgsQuickIdentifyKit::identifyOne( QgsVectorLayer *layer, const QPointF &point ) +{ + QgsFeatureList results = identify( layer, point ); + if ( results.empty() ) + { + QgsFeature f = QgsFeature(); + f.setValid( false ); + return f; + } + else + { + return _closestFeature( results, mMapSettings->mapSettings(), layer, point ); + } +} + + +QgsQuickFeature QgsQuickIdentifyKit::identifyOne( const QPointF &point ) +{ + QList results = identify( point ); + if ( results.empty() ) + { + QgsQuickFeature emptyRes; + return emptyRes; + } + else + { + return _closestFeature( results, mMapSettings->mapSettings(), point ); + } +} + +QgsFeatureList QgsQuickIdentifyKit::identify( QgsVectorLayer *layer, const QPointF &point ) +{ + QgsFeatureList results; + + Q_ASSERT( layer ); + + if ( !mMapSettings ) + { + QgsDebugMsg( QStringLiteral( "Unable to use IdentifyKit without mapSettings property set." ) ); + return results; + } + QgsPointXY mapPoint = mMapSettings->mapSettings().mapToPixel().toMapCoordinates( point.toPoint() ); + + results = identifyVectorLayer( layer, mapPoint ); + + QgsDebugMsg( QStringLiteral( "IdentifyKit identified %1 results for layer %2" ).arg( results.count() ).arg( layer->name() ) ); + return results; +} + + +QgsFeatureList QgsQuickIdentifyKit::identifyVectorLayer( QgsVectorLayer *layer, const QgsPointXY &point ) const +{ + QgsFeatureList results; + + if ( !layer || !layer->isSpatial() ) + return results; + + if ( !layer->isInScaleRange( mMapSettings->mapSettings().scale() ) ) + return results; + + QgsFeatureList featureList; + + // toLayerCoordinates will throw an exception for an 'invalid' point. + // For example, if you project a world map onto a globe using EPSG 2163 + // and then click somewhere off the globe, an exception will be thrown. + try + { + // create the search rectangle + double searchRadius = searchRadiusMU(); + + QgsRectangle r; + r.setXMinimum( point.x() - searchRadius ); + r.setXMaximum( point.x() + searchRadius ); + r.setYMinimum( point.y() - searchRadius ); + r.setYMaximum( point.y() + searchRadius ); + + r = toLayerCoordinates( layer, r ); + + QgsFeatureRequest req; + req.setFilterRect( r ); + req.setLimit( mFeaturesLimit ); + req.setFlags( QgsFeatureRequest::ExactIntersect ); + + QgsFeatureIterator fit = layer->getFeatures( req ); + QgsFeature f; + while ( fit.nextFeature( f ) ) + featureList << QgsFeature( f ); + } + catch ( QgsCsException &cse ) + { + Q_UNUSED( cse ); + // catch exception for 'invalid' point and proceed with no features found + } + + bool filter = false; + + QgsRenderContext context( QgsRenderContext::fromMapSettings( mMapSettings->mapSettings() ) ); + context.expressionContext() << QgsExpressionContextUtils::layerScope( layer ); + QgsFeatureRenderer *renderer = layer->renderer(); + if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent ) + { + // setup scale for scale dependent visibility (rule based) + renderer->startRender( context, layer->fields() ); + filter = renderer->capabilities() & QgsFeatureRenderer::Filter; + } + + Q_FOREACH ( const QgsFeature &feature, featureList ) + { + context.expressionContext().setFeature( feature ); + + if ( filter && !renderer->willRenderFeature( const_cast( feature ), context ) ) + continue; + + results.append( feature ); + } + + if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent ) + { + renderer->stopRender( context ); + } + + return results; +} + +double QgsQuickIdentifyKit::searchRadiusMU( const QgsRenderContext &context ) const +{ + return mSearchRadiusMm * context.scaleFactor() * context.mapToPixel().mapUnitsPerPixel(); +} + +double QgsQuickIdentifyKit::searchRadiusMU() const +{ + QgsRenderContext context = QgsRenderContext::fromMapSettings( mMapSettings->mapSettings() ); + return searchRadiusMU( context ); +} + +QgsRectangle QgsQuickIdentifyKit::toLayerCoordinates( QgsMapLayer *layer, const QgsRectangle &rect ) const +{ + return mMapSettings->mapSettings().mapToLayerCoordinates( layer, rect ); +} + +double QgsQuickIdentifyKit::searchRadiusMm() const +{ + return mSearchRadiusMm; +} + +void QgsQuickIdentifyKit::setSearchRadiusMm( double searchRadiusMm ) +{ + if ( mSearchRadiusMm == searchRadiusMm ) + return; + + mSearchRadiusMm = searchRadiusMm; + emit searchRadiusMmChanged(); +} diff --git a/src/quickgui/qgsquickidentifykit.h b/src/quickgui/qgsquickidentifykit.h new file mode 100644 index 000000000000..5abea91d5680 --- /dev/null +++ b/src/quickgui/qgsquickidentifykit.h @@ -0,0 +1,117 @@ +/*************************************************************************** + qgsquickidentifykit.h + --------------------- + Date : 30.8.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKIDENTIFYKIT_H +#define QGSQUICKIDENTIFYKIT_H + +#include +#include + +#include "qgsfeature.h" +#include "qgsmapsettings.h" +#include "qgspoint.h" +#include "qgsrendercontext.h" + +#include "qgis_quick.h" +#include "qgsquickfeature.h" + +class QgsMapLayer; +class QgsQuickMapSettings; +class QgsVectorLayer; + +/** + * \ingroup quick + * Convenient set of tools to get a list of QgsFeatures in a defined radius from a point. + * Also possible to get a feature with the closest distance to the point or feature(s) from + * specified QgsVectorLayer + * + * \note QML Type: IdentifyKit + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickIdentifyKit : public QObject +{ + Q_OBJECT + //! map settings. Set directly when creating QML object + Q_PROPERTY( QgsQuickMapSettings *mapSettings READ mapSettings WRITE setMapSettings NOTIFY mapSettingsChanged ) + + /** + * Search radius for the identify functions from the point. Default is 8 + */ + Q_PROPERTY( double searchRadiusMm READ searchRadiusMm WRITE setSearchRadiusMm NOTIFY searchRadiusMmChanged ) + + /** + * Maximum number of feature returned from by the identify functions in QgsFeatureList. Default is 100 + */ + Q_PROPERTY( long featuresLimit MEMBER mFeaturesLimit NOTIFY featuresLimitChanged ) + + public: + //! create new identify kit + explicit QgsQuickIdentifyKit( QObject *parent = 0 ); + + //! Getter for map settings + QgsQuickMapSettings *mapSettings() const; + + //! Set map settings + void setMapSettings( QgsQuickMapSettings *mapSettings ); + + //! Return search radius for looking for features. + double searchRadiusMm() const; + //! Set search radius + void setSearchRadiusMm( double searchRadiusMm ); + + /** + * Get the closest feature to the point from the layer in case it is identifiable layer + */ + Q_INVOKABLE QgsFeature identifyOne( QgsVectorLayer *layer, const QPointF &point ); + + /** + * Get the closest feature to the point from any identifiable layer + */ + Q_INVOKABLE QgsQuickFeature identifyOne( const QPointF &point ); + + /** + * Get all features interseting the point from the layer in case it is identifiable layer + */ + Q_INVOKABLE QgsFeatureList identify( QgsVectorLayer *layer, const QPointF &point ); + + /** + * Get all features interseting the point from any identifiable layer + */ + Q_INVOKABLE QList identify( const QPointF &point ); + + + signals: + //! map settings changed + void mapSettingsChanged(); + //! search radius changed + void searchRadiusMmChanged(); + //! features limit changed + void featuresLimitChanged(); + + private: + QgsQuickMapSettings *mMapSettings = nullptr; + + double searchRadiusMU( const QgsRenderContext &context ) const; + double searchRadiusMU() const; + + QgsRectangle toLayerCoordinates( QgsMapLayer *layer, const QgsRectangle &rect ) const; + QgsFeatureList identifyVectorLayer( QgsVectorLayer *layer, const QgsPointXY &point ) const; + + double mSearchRadiusMm; + int mFeaturesLimit; +}; + +#endif // QGSQUICKIDENTIFYKIT_H diff --git a/src/quickgui/qgsquickmapcanvasmap.cpp b/src/quickgui/qgsquickmapcanvasmap.cpp new file mode 100644 index 000000000000..ef942c960dda --- /dev/null +++ b/src/quickgui/qgsquickmapcanvasmap.cpp @@ -0,0 +1,396 @@ +/*************************************************************************** + qgsquickmapcanvasmap.cpp + -------------------------------------- + Date : 10.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "qgsquickmapcanvasmap.h" +#include "qgsquickmapsettings.h" + + +QgsQuickMapCanvasMap::QgsQuickMapCanvasMap( QQuickItem *parent ) + : QQuickItem( parent ) + , mMapSettings( new QgsQuickMapSettings() ) + , mPinching( false ) + , mDirty( false ) + , mFreeze( false ) + , mIncrementalRendering( false ) +{ + connect( this, &QQuickItem::windowChanged, this, &QgsQuickMapCanvasMap::onWindowChanged ); + connect( &mRefreshTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::refreshMap ); + connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::renderJobUpdated ); + + connect( mMapSettings, &QgsQuickMapSettings::extentChanged, this, &QgsQuickMapCanvasMap::onExtentChanged ); + connect( mMapSettings, &QgsQuickMapSettings::layersChanged, this, &QgsQuickMapCanvasMap::onLayersChanged ); + + connect( this, &QgsQuickMapCanvasMap::renderStarting, this, &QgsQuickMapCanvasMap::isRenderingChanged ); + connect( this, &QgsQuickMapCanvasMap::mapCanvasRefreshed, this, &QgsQuickMapCanvasMap::isRenderingChanged ); + + mMapUpdateTimer.setSingleShot( false ); + mMapUpdateTimer.setInterval( 250 ); + mRefreshTimer.setSingleShot( true ); + setTransformOrigin( QQuickItem::TopLeft ); + setFlags( QQuickItem::ItemHasContents ); +} + +QgsQuickMapCanvasMap::~QgsQuickMapCanvasMap() +{ +} + +QgsQuickMapSettings *QgsQuickMapCanvasMap::mapSettings() const +{ + Q_ASSERT( mMapSettings ); + return mMapSettings; +} + +void QgsQuickMapCanvasMap::zoom( QPointF center, qreal scale ) +{ + Q_ASSERT( mMapSettings ); + QgsRectangle extent = mMapSettings->extent(); + QgsPoint oldCenter( extent.center() ); + QgsPoint mousePos( mMapSettings->screenToCoordinate( center ) ); + QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * scale ), + mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * scale ) ); + + // same as zoomWithCenter (no coordinate transformations are needed) + extent.scale( scale, &newCenter ); + mMapSettings->setExtent( extent ); +} + +void QgsQuickMapCanvasMap::pan( QPointF oldPos, QPointF newPos ) +{ + Q_ASSERT( mMapSettings ); + QgsPoint start = mMapSettings->screenToCoordinate( oldPos.toPoint() ); + QgsPoint end = mMapSettings->screenToCoordinate( newPos.toPoint() ); + + double dx = end.x() - start.x(); + double dy = end.y() - start.y(); + + // modify the extent + QgsRectangle extent = mMapSettings->extent(); + + extent.setXMinimum( extent.xMinimum() + dx ); + extent.setXMaximum( extent.xMaximum() + dx ); + extent.setYMaximum( extent.yMaximum() + dy ); + extent.setYMinimum( extent.yMinimum() + dy ); + + mMapSettings->setExtent( extent ); +} + +void QgsQuickMapCanvasMap::refreshMap() +{ + Q_ASSERT( mMapSettings ); + + stopRendering(); // if any... + + QgsMapSettings mapSettings = mMapSettings->mapSettings(); + + //build the expression context + QgsExpressionContext expressionContext; + expressionContext << QgsExpressionContextUtils::globalScope() + << QgsExpressionContextUtils::mapSettingsScope( mapSettings ); + + QgsProject *project = mMapSettings->project(); + if ( project ) + { + expressionContext << QgsExpressionContextUtils::projectScope( project ); + } + + mapSettings.setExpressionContext( expressionContext ); + + // create the renderer job + Q_ASSERT( !mJob ); + mJob = new QgsMapRendererParallelJob( mapSettings ); + + if ( mIncrementalRendering ) + mMapUpdateTimer.start(); + + connect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated ); + connect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished ); + mJob->setCache( mCache ); + + mJob->start(); + + emit renderStarting(); +} + +void QgsQuickMapCanvasMap::renderJobUpdated() +{ + mImage = mJob->renderedImage(); + mImageMapSettings = mJob->mapSettings(); + mDirty = true; + // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint + bool freeze = mFreeze; + mFreeze = true; + updateTransform(); + mFreeze = freeze; + + update(); + emit mapCanvasRefreshed(); +} + +void QgsQuickMapCanvasMap::renderJobFinished() +{ + Q_FOREACH ( const QgsMapRendererJob::Error &error, mJob->errors() ) + { + QgsMessageLog::logMessage( error.layerID + " :: " + error.message, tr( "Rendering" ) ); + } + + // take labeling results before emitting renderComplete, so labeling map tools + // connected to signal work with correct results + delete mLabelingResults; + mLabelingResults = mJob->takeLabelingResults(); + + mImage = mJob->renderedImage(); + mImageMapSettings = mJob->mapSettings(); + + // now we are in a slot called from mJob - do not delete it immediately + // so the class is still valid when the execution returns to the class + mJob->deleteLater(); + mJob = nullptr; + mDirty = true; + mMapUpdateTimer.stop(); + + // Temporarily freeze the canvas, we only need to reset the geometry but not trigger a repaint + bool freeze = mFreeze; + mFreeze = true; + updateTransform(); + mFreeze = freeze; + + update(); + emit mapCanvasRefreshed(); +} + +void QgsQuickMapCanvasMap::onWindowChanged( QQuickWindow *window ) +{ + disconnect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged ); + if ( window ) + { + connect( window, &QQuickWindow::screenChanged, this, &QgsQuickMapCanvasMap::onScreenChanged ); + onScreenChanged( window->screen() ); + } +} + +void QgsQuickMapCanvasMap::onScreenChanged( QScreen *screen ) +{ + Q_ASSERT( mMapSettings ); + if ( screen ) + mMapSettings->setOutputDpi( screen->physicalDotsPerInch() ); +} + +void QgsQuickMapCanvasMap::onExtentChanged() +{ + updateTransform(); + + // And trigger a new rendering job + refresh(); +} + +void QgsQuickMapCanvasMap::updateTransform() +{ + Q_ASSERT( mMapSettings ); + QgsMapSettings currentMapSettings = mMapSettings->mapSettings(); + QgsMapToPixel mtp = currentMapSettings.mapToPixel(); + + QgsRectangle imageExtent = mImageMapSettings.visibleExtent(); + QgsRectangle newExtent = currentMapSettings.visibleExtent(); + QgsPointXY pixelPt = mtp.transform( imageExtent.xMinimum(), imageExtent.yMaximum() ); + setScale( imageExtent.width() / newExtent.width() ); + + setX( pixelPt.x() ); + setY( pixelPt.y() ); +} + +int QgsQuickMapCanvasMap::mapUpdateInterval() const +{ + return mMapUpdateTimer.interval(); +} + +void QgsQuickMapCanvasMap::setMapUpdateInterval( int mapUpdateInterval ) +{ + if ( mMapUpdateInterval == mapUpdateInterval ) + return; + + mMapUpdateTimer.setInterval( mapUpdateInterval ); + + emit mapUpdateIntervalChanged(); +} + +bool QgsQuickMapCanvasMap::incrementalRendering() const +{ + return mIncrementalRendering; +} + +void QgsQuickMapCanvasMap::setIncrementalRendering( bool incrementalRendering ) +{ + if ( incrementalRendering == mIncrementalRendering ) + return; + + mIncrementalRendering = incrementalRendering; + emit incrementalRenderingChanged(); +} + +bool QgsQuickMapCanvasMap::freeze() const +{ + return mFreeze; +} + +void QgsQuickMapCanvasMap::setFreeze( bool freeze ) +{ + if ( freeze == mFreeze ) + return; + + mFreeze = freeze; + + if ( !mFreeze ) + refresh(); + + emit freezeChanged(); +} + +bool QgsQuickMapCanvasMap::isRendering() const +{ + return mJob; +} + +QSGNode *QgsQuickMapCanvasMap::updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * ) +{ + if ( mDirty ) + { + delete oldNode; + oldNode = nullptr; + mDirty = false; + } + + QSGSimpleTextureNode *node = static_cast( oldNode ); + if ( !node ) + { + node = new QSGSimpleTextureNode(); + QSGTexture *texture = window()->createTextureFromImage( mImage ); + node->setTexture( texture ); + node->setOwnsTexture( true ); + } + + QRectF rect( boundingRect() ); + + // Check for resizes that change the w/h ratio + if ( !rect.isEmpty() && !mImage.size().isEmpty() && rect.width() / rect.height() != mImage.width() / mImage.height() ) + { + if ( rect.height() == mImage.height() ) + { + rect.setHeight( rect.width() / mImage.width() * mImage.height() ); + } + else + { + rect.setWidth( rect.height() / mImage.height() * mImage.width() ); + } + } + + node->setRect( rect ); + + return node; +} + +void QgsQuickMapCanvasMap::geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry ) +{ + Q_UNUSED( oldGeometry ) + Q_ASSERT( mMapSettings ); + // The Qt documentation advices to call the base method here. + // However, this introduces instabilities and heavy performance impacts on Android. + // It seems on desktop disabling it prevents us from downsizing the window... + // Be careful when re-enabling it. + // QQuickItem::geometryChanged( newGeometry, oldGeometry ); + + mMapSettings->setOutputSize( newGeometry.size().toSize() ); + refresh(); +} + +void QgsQuickMapCanvasMap::onLayersChanged() +{ + Q_ASSERT( mMapSettings ); + if ( mMapSettings->extent().isEmpty() ) + zoomToFullExtent(); + + Q_FOREACH ( const QMetaObject::Connection &conn, mLayerConnections ) + { + disconnect( conn ); + } + mLayerConnections.clear(); + + Q_FOREACH ( QgsMapLayer *layer, mMapSettings->layers() ) + { + // QgsVectorLayer* vl = qobject_cast( layer ); + mLayerConnections << connect( layer, &QgsMapLayer::repaintRequested, this, &QgsQuickMapCanvasMap::refresh ); + } + + refresh(); +} + +void QgsQuickMapCanvasMap::destroyJob( QgsMapRendererJob *job ) +{ + job->cancel(); + job->deleteLater(); +} + +void QgsQuickMapCanvasMap::stopRendering() +{ + if ( mJob ) + { + disconnect( mJob, &QgsMapRendererJob::renderingLayersFinished, this, &QgsQuickMapCanvasMap::renderJobUpdated ); + disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsQuickMapCanvasMap::renderJobFinished ); + + mJob->cancelWithoutBlocking(); + mJob = nullptr; + } +} + +void QgsQuickMapCanvasMap::zoomToFullExtent() +{ + Q_ASSERT( mMapSettings ); + QgsRectangle extent; + Q_FOREACH ( QgsMapLayer *layer, mMapSettings->layers() ) + { + if ( mMapSettings->destinationCrs() != layer->crs() ) + { + QgsCoordinateTransform transform( layer->crs(), mMapSettings->destinationCrs(), mMapSettings->transformContext() ); + extent.combineExtentWith( transform.transformBoundingBox( layer->extent() ) ); + } + else + { + extent.combineExtentWith( layer->extent() ); + } + } + mMapSettings->setExtent( extent ); + + refresh(); +} + +void QgsQuickMapCanvasMap::refresh() +{ + Q_ASSERT( mMapSettings ); + if ( mMapSettings->outputSize().isNull() ) + return; // the map image size has not been set yet + + if ( !mFreeze ) + mRefreshTimer.start( 1 ); +} diff --git a/src/quickgui/qgsquickmapcanvasmap.h b/src/quickgui/qgsquickmapcanvasmap.h new file mode 100644 index 000000000000..c4b54625731e --- /dev/null +++ b/src/quickgui/qgsquickmapcanvasmap.h @@ -0,0 +1,173 @@ +/*************************************************************************** + qgsquickmapcanvasmap.h + -------------------------------------- + Date : 10.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKMAPCANVASMAP_H +#define QGSQUICKMAPCANVASMAP_H + +#include +#include +#include + +#include "qgsmapsettings.h" +#include "qgspoint.h" + +#include "qgis_quick.h" +#include "qgsquickmapsettings.h" + +class QgsMapRendererParallelJob; +class QgsMapRendererCache; +class QgsLabelingResults; + +/** + * \ingroup quick + * This class implements a visual Qt Quick Item that does map rendering + * according to the current map settings. Client code is expected to use + * MapCanvas item rather than using this class directly. + * + * QgsQuickMapCanvasMap instance internally creates QgsQuickMapSettings in + * constructor. The map settings for other QgsQuick components should + * be initialized from QgsQuickMapCanvasMap's mapSettings + * + * \note QML Type: MapCanvasMap + * + * \sa QgsQuickMapCanvas + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickMapCanvasMap : public QQuickItem +{ + Q_OBJECT + + //! map settings. The map settings should be source of map settings for all other components + Q_PROPERTY( QgsQuickMapSettings *mapSettings READ mapSettings ) + //! do not refresh canvas + Q_PROPERTY( bool freeze READ freeze WRITE setFreeze NOTIFY freezeChanged ) + //! QgsMapRendererParallelJob is running + Q_PROPERTY( bool isRendering READ isRendering NOTIFY isRenderingChanged ) + + /** + * Interval in milliseconds after which the map canvas will be updated while a rendering job is ongoing. + * This only has an effect if incrementalRendering is activated. + * Default is 250 [ms]. + */ + Q_PROPERTY( int mapUpdateInterval READ mapUpdateInterval WRITE setMapUpdateInterval NOTIFY mapUpdateIntervalChanged ) + //! allow increamental rendering - automatic refresh of map canvas while a rendering job is ongoing + Q_PROPERTY( bool incrementalRendering READ incrementalRendering WRITE setIncrementalRendering NOTIFY incrementalRenderingChanged ) + + public: + //! create map canvas map + QgsQuickMapCanvasMap( QQuickItem *parent = 0 ); + ~QgsQuickMapCanvasMap(); + + //! Return map settings associated + QgsQuickMapSettings *mapSettings() const; + + //! Return map canvas for Qt Quick scene graph changes + virtual QSGNode *updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * ); + + //! Return whether canvas refresh is frozen + bool freeze() const; + + //! Freeze canvas refresh + void setFreeze( bool freeze ); + + //! Whether renderer job is running + bool isRendering() const; + + //! Return current map update interval for incremental rendering + int mapUpdateInterval() const; + + //! Set map update interval for incremental rendering + void setMapUpdateInterval( int mapUpdateInterval ); + + //! Return whether incremental rendering is on + bool incrementalRendering() const; + //! Set incremental rendering + void setIncrementalRendering( bool incrementalRendering ); + + signals: + //! rendering starting + void renderStarting(); + + //! canvas refreshed + void mapCanvasRefreshed(); + + //! freeze changed + void freezeChanged(); + + //! is rendering changed + void isRenderingChanged(); + + //! map update interval for incremental rendering changed + void mapUpdateIntervalChanged(); + + //! incremental rendering changed + void incrementalRenderingChanged(); + + protected: + //! geometry changed + void geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry ); + + public slots: + //! stop rendering + void stopRendering(); + + //! Zoom the map by adjusting map settings extent + void zoom( QPointF center, qreal scale ); + + //! Pan the map + void pan( QPointF oldPos, QPointF newPos ); + + //! Refresh the map + void refresh(); + + private slots: + void refreshMap(); + void renderJobUpdated(); + void renderJobFinished(); + void onWindowChanged( QQuickWindow *window ); + void onScreenChanged( QScreen *screen ); + void onExtentChanged(); + void onLayersChanged(); + + private: + + /** + * Should only be called ba stopRendering()! + */ + void destroyJob( QgsMapRendererJob *job ); + QgsMapSettings prepareMapSettings() const; + void updateTransform(); + void zoomToFullExtent(); + + QgsQuickMapSettings *mMapSettings; + + bool mPinching; + QPoint mPinchStartPoint; + QgsMapRendererParallelJob *mJob = nullptr; + QgsMapRendererCache *mCache = nullptr; + QgsLabelingResults *mLabelingResults = nullptr; + QImage mImage; + QgsMapSettings mImageMapSettings; + QTimer mRefreshTimer; + bool mDirty; + bool mFreeze; + QList mLayerConnections; + QTimer mMapUpdateTimer; + int mMapUpdateInterval; + bool mIncrementalRendering; +}; + +#endif // QGSQUICKMAPCANVASMAP_H diff --git a/src/quickgui/qgsquickmapsettings.cpp b/src/quickgui/qgsquickmapsettings.cpp new file mode 100644 index 000000000000..363a955ad963 --- /dev/null +++ b/src/quickgui/qgsquickmapsettings.cpp @@ -0,0 +1,228 @@ +/*************************************************************************** + qgsquickmapsettings.cpp + -------------------------------------- + Date : 27.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 "qgsmaplayer.h" +#include "qgsmaplayerstylemanager.h" +#include "qgsmessagelog.h" +#include "qgsproject.h" + +#include "qgsquickmapsettings.h" + +QgsQuickMapSettings::QgsQuickMapSettings( QObject *parent ) + : QObject( parent ) +{ + // Connect signals for derived values + connect( this, &QgsQuickMapSettings::destinationCrsChanged, this, &QgsQuickMapSettings::mapUnitsPerPixelChanged ); + connect( this, &QgsQuickMapSettings::extentChanged, this, &QgsQuickMapSettings::mapUnitsPerPixelChanged ); + connect( this, &QgsQuickMapSettings::outputSizeChanged, this, &QgsQuickMapSettings::mapUnitsPerPixelChanged ); + connect( this, &QgsQuickMapSettings::extentChanged, this, &QgsQuickMapSettings::visibleExtentChanged ); + connect( this, &QgsQuickMapSettings::rotationChanged, this, &QgsQuickMapSettings::visibleExtentChanged ); + connect( this, &QgsQuickMapSettings::outputSizeChanged, this, &QgsQuickMapSettings::visibleExtentChanged ); +} + +QgsQuickMapSettings::~QgsQuickMapSettings() +{ + +} + +void QgsQuickMapSettings::setProject( QgsProject *project ) +{ + if ( project == mProject ) + return; + + // If we have already something connected, disconnect it! + if ( mProject ) + { + disconnect( mProject, 0, this, 0 ); + } + + mProject = project; + + // Connect all signals + if ( mProject ) + { + connect( mProject, &QgsProject::readProject, this, &QgsQuickMapSettings::onReadProject ); + + // TODO: we have a problem here, since project can have alread .qgs loaded, so + // we are unable to get DOM to reload project settings for the canvas + // fortunately setProject is used only once at the very beginning of the run, + // so it should work .... + setDestinationCrs( mProject->crs() ); + + // Set Context too + mMapSettings.setTransformContext( mProject->transformContext() ); + } + else + { + mMapSettings.setTransformContext( QgsCoordinateTransformContext() ); + } + + emit projectChanged(); +} + +QgsProject *QgsQuickMapSettings::project() const +{ + return mProject; +} + +QgsCoordinateTransformContext QgsQuickMapSettings::transformContext() const +{ + return mMapSettings.transformContext(); +} + +QgsRectangle QgsQuickMapSettings::extent() const +{ + return mMapSettings.extent(); +} + +void QgsQuickMapSettings::setExtent( const QgsRectangle &extent ) +{ + if ( mMapSettings.extent() == extent ) + return; + + mMapSettings.setExtent( extent ); + emit extentChanged(); +} + +void QgsQuickMapSettings::setCenter( const QgsPoint ¢er ) +{ + QgsVector delta = QgsPointXY( center ) - mMapSettings.extent().center(); + + QgsRectangle e = mMapSettings.extent(); + e.setXMinimum( e.xMinimum() + delta.x() ); + e.setXMaximum( e.xMaximum() + delta.x() ); + e.setYMinimum( e.yMinimum() + delta.y() ); + e.setYMaximum( e.yMaximum() + delta.y() ); + + setExtent( e ); +} + +double QgsQuickMapSettings::mapUnitsPerPixel() const +{ + return mMapSettings.mapUnitsPerPixel(); +} + +QgsRectangle QgsQuickMapSettings::visibleExtent() const +{ + return mMapSettings.visibleExtent(); +} + +QPointF QgsQuickMapSettings::coordinateToScreen( const QgsPoint &p ) const +{ + QgsPointXY pt( p.x(), p.y() ); + QgsPointXY pp = mMapSettings.mapToPixel().transform( pt ); + return pp.toQPointF(); +} + +QgsPoint QgsQuickMapSettings::screenToCoordinate( const QPointF &p ) const +{ + const QgsPointXY pp = mMapSettings.mapToPixel().toMapCoordinates( p.toPoint() ); + return QgsPoint( pp ); +} + +QgsMapSettings QgsQuickMapSettings::mapSettings() const +{ + return mMapSettings; +} + +QSize QgsQuickMapSettings::outputSize() const +{ + return mMapSettings.outputSize(); +} + +void QgsQuickMapSettings::setOutputSize( const QSize &outputSize ) +{ + if ( mMapSettings.outputSize() == outputSize ) + return; + + mMapSettings.setOutputSize( outputSize ); + emit outputSizeChanged(); +} + +double QgsQuickMapSettings::outputDpi() const +{ + return mMapSettings.outputDpi(); +} + +void QgsQuickMapSettings::setOutputDpi( double outputDpi ) +{ + if ( mMapSettings.outputDpi() == outputDpi ) + return; + + mMapSettings.setOutputDpi( outputDpi ); + emit outputDpiChanged(); +} + +QgsCoordinateReferenceSystem QgsQuickMapSettings::destinationCrs() const +{ + return mMapSettings.destinationCrs(); +} + +void QgsQuickMapSettings::setDestinationCrs( const QgsCoordinateReferenceSystem &destinationCrs ) +{ + if ( mMapSettings.destinationCrs() == destinationCrs ) + return; + + mMapSettings.setDestinationCrs( destinationCrs ); + emit destinationCrsChanged(); +} + +QList QgsQuickMapSettings::layers() const +{ + return mMapSettings.layers(); +} + +void QgsQuickMapSettings::setLayers( const QList &layers ) +{ + mMapSettings.setLayers( layers ); + emit layersChanged(); +} + +void QgsQuickMapSettings::onReadProject( const QDomDocument &doc ) +{ + QDomNodeList nodes = doc.elementsByTagName( "mapcanvas" ); + if ( nodes.count() ) + { + QDomNode node = nodes.item( 0 ); + + mMapSettings.readXml( node ); + + if ( mMapSettings.rotation() != 0 ) + QgsMessageLog::logMessage( tr( "Map Canvas rotation is not supported. Resetting from %1 to 0." ).arg( mMapSettings.rotation() ) ); + + mMapSettings.setRotation( 0 ); + + emit extentChanged(); + emit destinationCrsChanged(); + emit outputSizeChanged(); + emit outputDpiChanged(); + emit layersChanged(); + } +} + +double QgsQuickMapSettings::rotation() const +{ + return mMapSettings.rotation(); +} + +void QgsQuickMapSettings::setRotation( double rotation ) +{ + if ( rotation == mMapSettings.rotation() ) + return; + + mMapSettings.setRotation( rotation ); + emit rotationChanged(); +} diff --git a/src/quickgui/qgsquickmapsettings.h b/src/quickgui/qgsquickmapsettings.h new file mode 100644 index 000000000000..cfae5652b3d1 --- /dev/null +++ b/src/quickgui/qgsquickmapsettings.h @@ -0,0 +1,232 @@ +/*************************************************************************** + qgsquickmapsettings.h + -------------------------------------- + Date : 27.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKMAPSETTINGS_H +#define QGSQUICKMAPSETTINGS_H + +#include + +#include "qgscoordinatetransformcontext.h" +#include "qgsmapsettings.h" +#include "qgsmapthemecollection.h" +#include "qgspoint.h" +#include "qgsrectangle.h" + +#include "qgis_quick.h" + +class QgsProject; + +/** + * \ingroup quick + * The QgsQuickMapSettings class encapsulates QgsMapSettings class to offer + * settings of configuration of map rendering via QML properties. + * + * On top of QgsMapSettings functionality, when QgsProject is attached, + * it automatically loads its default settings from the project. + * + * \note QML Type: MapSettings + * + * \sa QgsMapCanvas + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickMapSettings : public QObject +{ + Q_OBJECT + + //! QGIS project + Q_PROPERTY( QgsProject *project READ project WRITE setProject NOTIFY projectChanged ) + //! \see QgsMapSettings::extent() + Q_PROPERTY( QgsRectangle extent READ extent WRITE setExtent NOTIFY extentChanged ) + //! \see QgsMapSettings::visibleExtent() + Q_PROPERTY( QgsRectangle visibleExtent READ visibleExtent NOTIFY visibleExtentChanged ) + //! \see QgsMapSettings::mapUnitsPerPixel() + Q_PROPERTY( double mapUnitsPerPixel READ mapUnitsPerPixel NOTIFY mapUnitsPerPixelChanged ) + //! \see QgsMapSettings::rotation() + Q_PROPERTY( double rotation READ rotation WRITE setRotation NOTIFY rotationChanged ) + //! \see QgsMapSettings::outputSize() + Q_PROPERTY( QSize outputSize READ outputSize WRITE setOutputSize NOTIFY outputSizeChanged ) + //! \see QgsMapSettings::outputDpi() + Q_PROPERTY( double outputDpi READ outputDpi WRITE setOutputDpi NOTIFY outputDpiChanged ) + //! \see QgsMapSettings::destinationCrs() + Q_PROPERTY( QgsCoordinateReferenceSystem destinationCrs READ destinationCrs WRITE setDestinationCrs NOTIFY destinationCrsChanged ) + //! \see QgsMapSettings::layers() + Q_PROPERTY( QList layers READ layers WRITE setLayers NOTIFY layersChanged ) + + public: + //! Create new map settings + QgsQuickMapSettings( QObject *parent = 0 ); + ~QgsQuickMapSettings(); + + //! Clone map settings + QgsMapSettings mapSettings() const; + + //! \see QgsMapSettings::extent() + QgsRectangle extent() const; + + //! \see QgsMapSettings::setExtent() + void setExtent( const QgsRectangle &extent ); + + /** + * Attach \a project with the map settings. + * When project is loaded, map settings are automatically populated from it's stored values + */ + void setProject( QgsProject *project ); + //! Return associated QGIS project + QgsProject *project() const; + + //! Move current map extent to have center point defined by \a center + Q_INVOKABLE void setCenter( const QgsPoint ¢er ); + + //! \see QgsMapSettings::mapUnitsPerPixel() + double mapUnitsPerPixel() const; + + //! \see QgsMapSettings::visibleExtent() + QgsRectangle visibleExtent() const; + + /** + * Returns the coordinate transform context, which stores various + * information regarding which datum transforms should be used when transforming points + * from a source to destination coordinate reference system. + */ + Q_INVOKABLE QgsCoordinateTransformContext transformContext() const; + + /** + * Convert a map coordinate to screen pixel coordinates + * + * \param p A coordinate in map coordinates + * + * \return A coordinate in pixel / screen space + */ + Q_INVOKABLE QPointF coordinateToScreen( const QgsPoint &p ) const; + + + /** + * Convert a screen coordinate to a map coordinate + * + * \param p A coordinate in pixel / screen coordinates + * + * \return A coordinate in map coordinates + */ + Q_INVOKABLE QgsPoint screenToCoordinate( const QPointF &p ) const; + + /** + * Sets the coordinate transform \a context, which stores various + * information regarding which datum transforms should be used when transforming points + * from a source to destination coordinate reference system. + * + * \since QGIS 3.0 + * \see transformContext() + */ + void setTransformContext( const QgsCoordinateTransformContext &context ); + + //! Return rotation + double rotation() const; + + //! Set rotation + void setRotation( double rotation ); + + /** + * Return output size + * \see QgsMapSettings::outputSize() + */ + QSize outputSize() const; + + /** + * Set output size and emit outputSizeChanged + * \see QgsMapSettings::setOutputSize() + */ + void setOutputSize( const QSize &outputSize ); + + /** + * Return output DPI + * \see QgsMapSettings::outputDpi() + */ + double outputDpi() const; + + /** + * Set output DPI and emit outputDpiChanged + * \see QgsMapSettings::setOutputDpi() + */ + void setOutputDpi( double outputDpi ); + + /** + * Return destination CRS + * \see QgsMapSettings::destinationCrs() + */ + QgsCoordinateReferenceSystem destinationCrs() const; + + /** + * Set destination CRS and emit destinationCrsChanged + * \see QgsMapSettings::setDestinationCrs() + */ + void setDestinationCrs( const QgsCoordinateReferenceSystem &destinationCrs ); + + /** + * Return layers + * \see QgsMapSettings::layers() + */ + QList layers() const; + + /** + * Set layers and emit layersChanged + * \see QgsMapSettings::setLayers() + */ + void setLayers( const QList &layers ); + + signals: + //! project changed + void projectChanged(); + + //! extent changed + void extentChanged(); + + //! destination CRS changed + void destinationCrsChanged(); + + //! map units per pixel changed + void mapUnitsPerPixelChanged(); + + //! rotation changed + void rotationChanged(); + + //! visible extent changed + void visibleExtentChanged(); + + //! output size changed + void outputSizeChanged(); + + //! output DPI changed + void outputDpiChanged(); + + //! layers changed + void layersChanged(); + + private slots: + + /** + * Read map canvas settings stored in a QGIS project file + * + * \param doc parsed DOM of a QgsProject + */ + void onReadProject( const QDomDocument &doc ); + + private: + QgsProject *mProject = nullptr; + QgsMapSettings mMapSettings; + +}; + +#endif // QGSQUICKMAPSETTINGS_H diff --git a/src/quickgui/qgsquickmaptransform.cpp b/src/quickgui/qgsquickmaptransform.cpp new file mode 100644 index 000000000000..2ab97a714326 --- /dev/null +++ b/src/quickgui/qgsquickmaptransform.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + qgsquickmaptransform.cpp + -------------------------------------- + Date : 27.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 "qgsquickmaptransform.h" +#include "qgsquickmapsettings.h" + +QgsQuickMapTransform::QgsQuickMapTransform() +{ +} + +QgsQuickMapTransform::~QgsQuickMapTransform() +{ +} + +void QgsQuickMapTransform::applyTo( QMatrix4x4 *matrix ) const +{ + *matrix *= mMatrix; + matrix->optimize(); +} + +QgsQuickMapSettings *QgsQuickMapTransform::mapSettings() const +{ + return mMapSettings; +} + +void QgsQuickMapTransform::setMapSettings( QgsQuickMapSettings *mapSettings ) +{ + if ( mapSettings == mMapSettings ) + return; + + if ( mMapSettings ) + disconnect( mMapSettings, &QgsQuickMapSettings::visibleExtentChanged, this, &QgsQuickMapTransform::updateMatrix ); + + mMapSettings = mapSettings; + + if ( mMapSettings ) + connect( mMapSettings, &QgsQuickMapSettings::visibleExtentChanged, this, &QgsQuickMapTransform::updateMatrix ); + + emit mapSettingsChanged(); +} + +void QgsQuickMapTransform::updateMatrix() +{ + QMatrix4x4 matrix; + float scaleFactor = 1 / mMapSettings->mapUnitsPerPixel(); + + matrix.scale( scaleFactor, -scaleFactor ); + matrix.translate( -mMapSettings->visibleExtent().xMinimum(), -mMapSettings->visibleExtent().yMaximum() ); + + mMatrix = matrix; + update(); +} diff --git a/src/quickgui/qgsquickmaptransform.h b/src/quickgui/qgsquickmaptransform.h new file mode 100644 index 000000000000..5b3ad7e49604 --- /dev/null +++ b/src/quickgui/qgsquickmaptransform.h @@ -0,0 +1,75 @@ +/*************************************************************************** + qgsquickmaptransform.h + -------------------------------------- + Date : 27.12.2014 + Copyright : (C) 2014 by Matthias Kuhn + Email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKMAPTRANSFORM_H +#define QGSQUICKMAPTRANSFORM_H + +#include +#include + +#include "qgis_quick.h" + +class QgsQuickMapSettings; + +/** + * \ingroup quick + * The QgsQuickMapTransform is transformation that can be attached to any QQuickItem. Transformation scales and translates + * Item based on the current QgsQuickMapSettings settings. + * + * For example it can be used on QgsQuickFeatureHighlight to place it correctly on the map canvas. + * + * \note QML Type: MapTransform + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickMapTransform : public QQuickTransform +{ + Q_OBJECT + + //! map settings + Q_PROPERTY( QgsQuickMapSettings *mapSettings READ mapSettings WRITE setMapSettings NOTIFY mapSettingsChanged ) + + public: + //! create new map transform + QgsQuickMapTransform(); + ~QgsQuickMapTransform(); + + /** + * Apply transformation based on current map settings to a matrix. + * + * Also optimize resulting matrix after transformation + * \param matrix Matrix to be transformed + */ + void applyTo( QMatrix4x4 *matrix ) const; + + //! Return map settings + QgsQuickMapSettings *mapSettings() const; + + //! Set map settings + void setMapSettings( QgsQuickMapSettings *mapSettings ); + + signals: + //! Map settings changed + void mapSettingsChanged(); + + private slots: + void updateMatrix(); + + private: + QgsQuickMapSettings *mMapSettings = nullptr; + QMatrix4x4 mMatrix; +}; + +#endif // QGSQUICKMAPTRANSFORM_H diff --git a/src/quickgui/qgsquickmessagelogmodel.cpp b/src/quickgui/qgsquickmessagelogmodel.cpp new file mode 100644 index 000000000000..0652f5693634 --- /dev/null +++ b/src/quickgui/qgsquickmessagelogmodel.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + qgsquickmessagelogmodel.cpp + -------------------------------------- + date : 13.7.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 "qgis.h" +#include "qgslogger.h" +#include "qgsmessagelog.h" +#include "qgsapplication.h" + +#include "qgsquickmessagelogmodel.h" + +QgsQuickMessageLogModel::QgsQuickMessageLogModel( QObject *parent ) + : QAbstractListModel( parent ) + , mMessageLog( QgsApplication::messageLog() ) +{ + connect( mMessageLog, static_cast( &QgsMessageLog::messageReceived ), this, &QgsQuickMessageLogModel::onMessageReceived ); +} + +QHash QgsQuickMessageLogModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles[MessageRole] = "Message"; + roles[MessageTagRole] = "MessageTag"; + roles[MessageLevelRole] = "MessageLevel"; + + return roles; +} + +int QgsQuickMessageLogModel::rowCount( const QModelIndex &parent ) const +{ + Q_UNUSED( parent ) + return mMessages.size(); +} + +QVariant QgsQuickMessageLogModel::data( const QModelIndex &index, int role ) const +{ + if ( index.row() >= mMessages.size() ) + return QVariant(); + + if ( role == MessageRole ) + return mMessages.at( index.row() ).message; + else if ( role == MessageTagRole ) + return mMessages.at( index.row() ).tag; + else if ( role == MessageLevelRole ) + return mMessages.at( index.row() ).level; + + return QVariant(); +} + +void QgsQuickMessageLogModel::onMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level ) +{ + beginInsertRows( QModelIndex(), 0, 0 ); + mMessages.prepend( LogMessage( tag, message, level ) ); + QgsDebugMsg( QStringLiteral( "Next message %1 : %2" ).arg( tag, message ) ); + endInsertRows(); +} diff --git a/src/quickgui/qgsquickmessagelogmodel.h b/src/quickgui/qgsquickmessagelogmodel.h new file mode 100644 index 000000000000..6d32d17c45bc --- /dev/null +++ b/src/quickgui/qgsquickmessagelogmodel.h @@ -0,0 +1,82 @@ +/*************************************************************************** + qgsquickmessagelogmodel.h + -------------------------------------- + date : 13.7.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias (at) opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKMESSAGELOGMODEL_H +#define QGSQUICKMESSAGELOGMODEL_H + +#include + +#include "qgis.h" +#include "qgsmessagelog.h" + +#include "qgis_quick.h" + +/** + * \ingroup quick + * + * This model will connect to the QgsMessageLog and publish any + * messages received from there. Can be used as a model for QListView, + * for example QgsQuick MessageLog class. + * + * \note QML Type: MessageLogModel + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickMessageLogModel : public QAbstractListModel +{ + Q_OBJECT + + struct LogMessage + { + LogMessage() + {} + + LogMessage( const QString &tag, const QString &message, Qgis::MessageLevel level ) + { + this->tag = tag; + this->message = message; + this->level = level; + } + + QString tag; + QString message; + Qgis::MessageLevel level; + }; + + enum Roles + { + MessageRole = Qt::UserRole, + MessageTagRole, + MessageLevelRole + }; + + public: + //! Create new message log model + QgsQuickMessageLogModel( QObject *parent = nullptr ); + + QHash roleNames() const override; + + int rowCount( const QModelIndex &parent ) const override; + QVariant data( const QModelIndex &index, int role ) const override; + + private slots: + void onMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level ); + + private: + QgsMessageLog *mMessageLog; + QVector mMessages; +}; + +#endif // QGSQUICKMESSAGELOGMODEL_H diff --git a/src/quickgui/qgsquickpositionkit.cpp b/src/quickgui/qgsquickpositionkit.cpp new file mode 100644 index 000000000000..21a653b299bb --- /dev/null +++ b/src/quickgui/qgsquickpositionkit.cpp @@ -0,0 +1,160 @@ +/*************************************************************************** + qgsquickpositionkit.cpp + -------------------------------------- + Date : Dec. 2017 + Copyright : (C) 2017 Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgis.h" +#include "qgslogger.h" +#include "qgsmessagelog.h" + +#include "qgsquickpositionkit.h" +#include "qgsquicksimulatedpositionsource.h" + +QgsQuickPositionKit::QgsQuickPositionKit( QObject *parent ) + : QObject( parent ) + , mAccuracy( -1 ) + , mDirection( -1 ) + , mHasPosition( false ) + , mIsSimulated( false ) +{ + use_gps_location(); +} + +QGeoPositionInfoSource *QgsQuickPositionKit::gpsSource() +{ + // this should give us "true" position source + // on Linux it comes from Geoclue library + QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource( this ); + if ( source->error() != QGeoPositionInfoSource::NoError ) + { + QgsMessageLog::logMessage( tr( "Unable to create default GPS Position Source" ) + + "(" + QString::number( ( long )source->error() ) + ")" + , "QgsQuick" + , Qgis::Warning ); + delete source; + return nullptr; + } + else + { + return source; + } +} + +QGeoPositionInfoSource *QgsQuickPositionKit::simulatedSource( double longitude, double latitude, double radius ) +{ + return new QgsQuickSimulatedPositionSource( this, longitude, latitude, radius ); +} + +void QgsQuickPositionKit::use_simulated_location( double longitude, double latitude, double radius ) +{ + QGeoPositionInfoSource *source = simulatedSource( longitude, latitude, radius ); + mIsSimulated = true; + replacePositionSource( source ); +} + +void QgsQuickPositionKit::use_gps_location() +{ + QGeoPositionInfoSource *source = gpsSource(); + mIsSimulated = false; + replacePositionSource( source ); +} + +void QgsQuickPositionKit::replacePositionSource( QGeoPositionInfoSource *source ) +{ + if ( mSource == source ) + return; + + if ( mSource ) + { + disconnect( mSource, nullptr, this, nullptr ); + delete mSource; + mSource = nullptr; + } + + mSource = source; + + if ( mSource ) + { + connect( mSource, &QGeoPositionInfoSource::positionUpdated, this, &QgsQuickPositionKit::positionUpdated ); + connect( mSource, &QGeoPositionInfoSource::updateTimeout, this, &QgsQuickPositionKit::onUpdateTimeout ); + mSource->startUpdates(); + + QgsDebugMsg( QStringLiteral( "Position source changed: %1" ).arg( mSource->sourceName() ) ); + } +} + +void QgsQuickPositionKit::positionUpdated( const QGeoPositionInfo &info ) +{ + if ( !info.coordinate().isValid() ) + { + // keep last valid position + mHasPosition = false; + emit hasPositionChanged(); + } + + + mPosition = QgsPoint( info.coordinate().longitude(), + info.coordinate().latitude(), + info.coordinate().altitude() ); // can be NaN + + if ( info.hasAttribute( QGeoPositionInfo::HorizontalAccuracy ) ) + mAccuracy = info.attribute( QGeoPositionInfo::HorizontalAccuracy ); + else + mAccuracy = -1; + if ( info.hasAttribute( QGeoPositionInfo::Direction ) ) + mDirection = info.attribute( QGeoPositionInfo::Direction ); + else + mDirection = -1; + + emit positionChanged(); + + if ( !mHasPosition ) + { + mHasPosition = true; + emit hasPositionChanged(); + } +} + +void QgsQuickPositionKit::onUpdateTimeout() +{ + if ( mHasPosition ) + { + mHasPosition = false; + emit hasPositionChanged(); + } +} + +bool QgsQuickPositionKit::hasPosition() const +{ + return mHasPosition; +} + +QgsPoint QgsQuickPositionKit::position() const +{ + return mPosition; +} + +qreal QgsQuickPositionKit::accuracy() const +{ + return mAccuracy; +} + +qreal QgsQuickPositionKit::direction() const +{ + return mDirection; +} + +bool QgsQuickPositionKit::simulated() const +{ + return mIsSimulated; +} diff --git a/src/quickgui/qgsquickpositionkit.h b/src/quickgui/qgsquickpositionkit.h new file mode 100644 index 000000000000..dd26a51ef885 --- /dev/null +++ b/src/quickgui/qgsquickpositionkit.h @@ -0,0 +1,142 @@ +/*************************************************************************** + qgsquickpositionkit.h + -------------------------------------- + Date : Dec. 2017 + Copyright : (C) 2017 Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSQUICKPOSITIONKIT_H +#define QGSQUICKPOSITIONKIT_H + +#include +#include + +#include "qgspoint.h" + +#include "qgis_quick.h" + +/** + * \ingroup quick + * Convenient set of tools to read GPS position and accuracy. + * + * Also, if one can use use_simulated_location to specify simulated position. + * Simulated position source generates random points in circles around the selected + * point and radius. Real GPS position is not used in this mode. + * + * \note QML Type: PositionKit + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickPositionKit : public QObject +{ + Q_OBJECT + + /** + * GPS position in WGS84 coords + */ + Q_PROPERTY( QgsPoint position READ position NOTIFY positionChanged ) + + /** + * GPS position is available (position property is a valid number) + */ + Q_PROPERTY( bool hasPosition READ hasPosition NOTIFY hasPositionChanged ) + + /** + * GPS horizontal accuracy in meters, -1 if not available + */ + Q_PROPERTY( qreal accuracy READ accuracy NOTIFY positionChanged ) + + /** + * GPS direction, bearing in degrees clockwise from north to direction of travel. -1 if not available + */ + Q_PROPERTY( qreal direction READ direction NOTIFY positionChanged ) + + /** + * GPS position and accuracy is simulated (not real from GPS sensor). Default false (use real GPS) + */ + Q_PROPERTY( bool isSimulated READ simulated NOTIFY isSimulatedChanged ) + + public: + //! Create new position kit + explicit QgsQuickPositionKit( QObject *parent = 0 ); + + //! Return if GPS position is available + bool hasPosition() const; + + //! Return GPS position in WGS84 coords + QgsPoint position() const; + + //! Return GPS horizontal accuracy in meters, -1 if not available + qreal accuracy() const; + + //! Return GPS direction, bearing in degrees clockwise from north to direction of travel. -1 if not available + qreal direction() const; + + //! Return whether GPS source is simulated. + bool simulated() const; + + /** + * Use simulated GPS source. + * + * Simulated GPS source emulates point on circle around defined point in specified radius + * + * We do not want to have the origin point as property + * We basically want to set it once based on project/map cente and keep + * it that way regardless of mapsettings change (e.g. zoom etc) + * + * \param longitude longitude of the centre of the emulated points + * \param latitude latitude of the centre of the emulated points + * \param radius distance of emulated points from the centre (in degrees WSG84) + */ + Q_INVOKABLE void use_simulated_location( double longitude, double latitude, double radius ); + + /** + * Use real GPS source (not simulated) + */ + Q_INVOKABLE void use_gps_location(); + + signals: + //! GPS position changed + void positionChanged(); + + //! hasPosition changed + void hasPositionChanged(); + + //! changed if GPS position is simulated or not + void isSimulatedChanged(); + + public slots: + + private slots: + void positionUpdated( const QGeoPositionInfo &info ); + void onUpdateTimeout(); + + protected: + QgsPoint mPosition; + qreal mAccuracy; + qreal mDirection; + bool mHasPosition; + + // Simulated source + bool mIsSimulated; + + QGeoPositionInfoSource *mSource = nullptr; + + private: + void replacePositionSource( QGeoPositionInfoSource *source ); + QString calculateStatusLabel(); + + QGeoPositionInfoSource *gpsSource(); + QGeoPositionInfoSource *simulatedSource( double longitude, double latitude, double radius ); + +}; + +#endif // QGSQUICKPOSITIONKIT_H diff --git a/src/quickgui/qgsquickscalebarkit.cpp b/src/quickgui/qgsquickscalebarkit.cpp new file mode 100644 index 000000000000..9dbc4c11f4bd --- /dev/null +++ b/src/quickgui/qgsquickscalebarkit.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** + qgsquickscalebarkit.cpp + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include + +#include "qgsdistancearea.h" +#include "qgspointxy.h" + +#include "qgsquickmapsettings.h" +#include "qgsquickscalebarkit.h" +#include "qgsquickutils.h" +#include "qgsunittypes.h" + +QgsQuickScaleBarKit::QgsQuickScaleBarKit( QObject *parent ) + : QObject( parent ) + , mPreferredWidth( 300 ) + , mWidth( mPreferredWidth ) + , mDistance( 0 ) + , mUnits( "" ) +{ + connect( this, &QgsQuickScaleBarKit::mapSettingsChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); + connect( this, &QgsQuickScaleBarKit::preferredWidthChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); +} + + + +void QgsQuickScaleBarKit::setMapSettings( QgsQuickMapSettings *mapSettings ) +{ + if ( mMapSettings == mapSettings ) + return; + + // If we have already something connected, disconnect it! + if ( mMapSettings ) + { + disconnect( mMapSettings, nullptr, this, nullptr ); + } + + mMapSettings = mapSettings; + + // Connect all signals to change scale bar when needed! + if ( mMapSettings ) + { + connect( mMapSettings, &QgsQuickMapSettings::extentChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); + connect( mMapSettings, &QgsQuickMapSettings::destinationCrsChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); + connect( mMapSettings, &QgsQuickMapSettings::mapUnitsPerPixelChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); + connect( mMapSettings, &QgsQuickMapSettings::visibleExtentChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); + connect( mMapSettings, &QgsQuickMapSettings::outputSizeChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); + connect( mMapSettings, &QgsQuickMapSettings::outputDpiChanged, this, &QgsQuickScaleBarKit::updateScaleBar ); + } + + emit mapSettingsChanged(); +} + +int QgsQuickScaleBarKit::width() const +{ + return mWidth; +} + +QString QgsQuickScaleBarKit::units() const +{ + return mUnits; +} + +int QgsQuickScaleBarKit::distance() const +{ + return mDistance; +} + +void QgsQuickScaleBarKit::updateScaleBar() +{ + if ( !mMapSettings ) + return; + + double dist = QgsQuickUtils::instance()->screenUnitsToMeters( mMapSettings, mPreferredWidth ); // meters + if ( dist > 1000.0 ) + { + dist = dist / 1000.0; // meters to kilometers + mUnits = QgsUnitTypes::toAbbreviatedString( QgsUnitTypes::DistanceKilometers ); + } + else + { + mUnits = QgsUnitTypes::toAbbreviatedString( QgsUnitTypes::DistanceMeters ); + } + + // we want to show nice round distances e.g. 200 km instead of e.g. 273 km + // so we determine which "nice" number to use and also update the scale bar + // length accordingly. First digit will be 1, 2 or 5, the rest will be zeroes. + int digits = floor( log10( ( dist ) ) ); // number of digits after first one + double base = pow( 10, digits ); // e.g. for 1234 this will be 1000 + double first_digit = dist / base; // get the first digit + int round_digit; + if ( first_digit < 2 ) + round_digit = 1; + else if ( first_digit < 5 ) + round_digit = 2; + else + round_digit = 5; + + mDistance = round_digit * base; + mWidth = mPreferredWidth * mDistance / dist; + + emit scaleBarChanged(); +} diff --git a/src/quickgui/qgsquickscalebarkit.h b/src/quickgui/qgsquickscalebarkit.h new file mode 100644 index 000000000000..27332dcf0165 --- /dev/null +++ b/src/quickgui/qgsquickscalebarkit.h @@ -0,0 +1,122 @@ +/*************************************************************************** + qgsquickscalebarkit.h + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSQUICKSCALEBARKIT_H +#define QGSQUICKSCALEBARKIT_H + +#include +#include + +#include "qgis_quick.h" + +class QgsQuickMapSettings; + +/** + * \ingroup quick + * + * The class QgsQuickScaleBarKit encapsulates the utilies to calculate + * scale bar properties + * + * It requires connection to mapSettings of the active canvas to automatically + * update text and width + * + * From preferred width in pixel, it calculates the width (pixel) of scalebar + * distance in meters or kilometers (int) rounded to "nice" number (e.g. 72.4 to 100) + * and units text (e.g. km) + * + * \note QML Type: ScaleBarKit + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickScaleBarKit : public QObject +{ + Q_OBJECT + + /** + * Associated map settings + */ + Q_PROPERTY( QgsQuickMapSettings *mapSettings MEMBER mMapSettings WRITE setMapSettings NOTIFY mapSettingsChanged ) + + /** + * Preferred width of scalebar in pixels. Defaults to 300 + */ + Q_PROPERTY( int preferredWidth MEMBER mPreferredWidth NOTIFY preferredWidthChanged ) + + /** + * Units of distance (e.g. km or m) Read-only (result) + */ + Q_PROPERTY( QString units READ units NOTIFY scaleBarChanged ) + + /** + * Distance rounded to "nice" number (e.g. 100, 20). To be used with units property for labels. Read-only (result) + */ + Q_PROPERTY( int distance READ distance NOTIFY scaleBarChanged ) + + /** + * Calculated width of scalebar in pixels representing distance + units. Differs minimum possible from preferredWidth to + * get "nice" distance number. + */ + Q_PROPERTY( int width READ width NOTIFY scaleBarChanged ) + + public: + //! create new scale bar kit + explicit QgsQuickScaleBarKit( QObject *parent = nullptr ); + ~QgsQuickScaleBarKit() = default; + + //! Set map settings + void setMapSettings( QgsQuickMapSettings *mapSettings ); + + //! Returns calculated width in pixels + int width() const; + + /** + * Returns distance corensponding to width + * + * \see QgsQuickScaleBarKit::units() + */ + int distance() const; + + /** + * Returns units of distance (m, km) + * + * \see QgsQuickScaleBarKit::distance() + */ + QString units() const; + + signals: + //! width, distance and/or units changed + void scaleBarChanged(); + + //! map settings changed + void mapSettingsChanged(); + + //! preferred width changed + void preferredWidthChanged(); + + public slots: + //! recalculate width, distance and units + void updateScaleBar(); + + private: + QgsQuickMapSettings *mMapSettings = nullptr; + + int mPreferredWidth; // pixels + int mWidth; // pixels + int mDistance; // in meters or kilometers, rounded + QString mUnits; // km or m +}; + + +#endif // QGSQUICKSCALEBARKIT_H diff --git a/src/quickgui/qgsquicksimulatedpositionsource.cpp b/src/quickgui/qgsquicksimulatedpositionsource.cpp new file mode 100644 index 000000000000..b2a99c526a2f --- /dev/null +++ b/src/quickgui/qgsquicksimulatedpositionsource.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + qgsquicksimulatedpositionsource.cpp + -------------------------------------- + Date : Dec. 2017 + Copyright : (C) 2017 Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsquicksimulatedpositionsource.h" + +/// @cond PRIVATE + +QgsQuickSimulatedPositionSource::QgsQuickSimulatedPositionSource( QObject *parent, double longitude, double latitude, double flightRadius ) + : QGeoPositionInfoSource( parent ) + , mTimer( new QTimer( this ) ) + , mAngle( 0 ) + , mFlightRadius( flightRadius ) + , mLongitude( longitude ) + , mLatitude( latitude ) +{ + connect( mTimer, &QTimer::timeout, this, &QgsQuickSimulatedPositionSource::readNextPosition ); +} + +void QgsQuickSimulatedPositionSource::startUpdates() +{ + int interval = updateInterval(); + if ( interval < minimumUpdateInterval() ) + interval = minimumUpdateInterval(); + + mTimer->start( interval ); +} + +void QgsQuickSimulatedPositionSource::stopUpdates() +{ + mTimer->stop(); +} + +void QgsQuickSimulatedPositionSource::requestUpdate( int /*timeout*/ ) +{ + readNextPosition(); +} + +void QgsQuickSimulatedPositionSource::readNextPosition() +{ + double latitude = mLatitude, longitude = mLongitude; + latitude += sin( mAngle * M_PI / 180 ) * mFlightRadius; + longitude += cos( mAngle * M_PI / 180 ) * mFlightRadius; + mAngle += 1; + + QGeoCoordinate coordinate( latitude, longitude ); + double altitude = std::rand() % 40 + 20; // rand altitude <20,55>m and lost (0) + if ( altitude <= 55 ) + { + coordinate.setAltitude( altitude ); // 3D + } + + QDateTime timestamp = QDateTime::currentDateTime(); + + QGeoPositionInfo info( coordinate, timestamp ); + if ( info.isValid() ) + { + mLastPosition = info; + info.setAttribute( QGeoPositionInfo::Direction, 360 - int( mAngle ) % 360 ); + int accuracy = std::rand() % 40 + 20; // rand accuracy <20,55>m and lost (-1) + if ( accuracy > 55 ) + { + accuracy = -1; + } + info.setAttribute( QGeoPositionInfo::HorizontalAccuracy, accuracy ); + emit positionUpdated( info ); + } +} + +/// @endcond diff --git a/src/quickgui/qgsquicksimulatedpositionsource.h b/src/quickgui/qgsquicksimulatedpositionsource.h new file mode 100644 index 000000000000..4f2fded39570 --- /dev/null +++ b/src/quickgui/qgsquicksimulatedpositionsource.h @@ -0,0 +1,80 @@ +/*************************************************************************** + qgsquicksimulatedpositionsource.h + -------------------------------------- + Date : Dec. 2017 + Copyright : (C) 2017 Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSQUICKSIMULATEDPOSITIONSOURCE_H +#define QGSQUICKSIMULATEDPOSITIONSOURCE_H + +/// @cond PRIVATE + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QGIS API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// + +#include "qgis_quick.h" +#include +#include +#include + +/** + * \ingroup quick + * This is an internal (implementation) class used to generate (fake) GPS position source + * Useful for for testing purposes (e.g. testing of the application with map for different + * location then your physical (GPS) location) + * + * Simulated position source generates random points in circles around the selected + * point and radius. Real GPS position is not used in this mode. + * + * \note QML Type: not exported + * + * \since QGIS 3.2 + */ +class QUICK_NO_EXPORT QgsQuickSimulatedPositionSource : public QGeoPositionInfoSource +{ + Q_OBJECT + public: + QgsQuickSimulatedPositionSource( QObject *parent, double longitude, double latitude, double flightRadius ); + + QGeoPositionInfo lastKnownPosition( bool /*fromSatellitePositioningMethodsOnly = false*/ ) const { return mLastPosition; } + PositioningMethods supportedPositioningMethods() const { return AllPositioningMethods; } + int minimumUpdateInterval() const { return 1000; } + Error error() const { return QGeoPositionInfoSource::NoError; } + + public slots: + virtual void startUpdates(); + virtual void stopUpdates(); + + virtual void requestUpdate( int timeout = 5000 ); + + private slots: + void readNextPosition(); + + private: + QTimer *mTimer; + QGeoPositionInfo mLastPosition; + double mAngle; + + double mFlightRadius; + double mLongitude; + double mLatitude; +}; + +/// @endcond + +#endif // QGSQUICKSIMULATEDPOSITIONSOURCE_H diff --git a/src/quickgui/qgsquicksubmodel.cpp b/src/quickgui/qgsquicksubmodel.cpp new file mode 100644 index 000000000000..806433c92f88 --- /dev/null +++ b/src/quickgui/qgsquicksubmodel.cpp @@ -0,0 +1,183 @@ +/*************************************************************************** + qgsquicksubmodel.cpp + -------------------------------------- + Date : 16.9.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 "qgsquicksubmodel.h" + +QgsQuickSubModel::QgsQuickSubModel( QObject *parent ) + : QAbstractItemModel( parent ) +{ +} + +QModelIndex QgsQuickSubModel::index( int row, int column, const QModelIndex &parent ) const +{ + if ( !mModel ) + return mRootIndex; + + QModelIndex sourceIndex = mModel->index( row, column, parent.isValid() ? mapToSource( parent ) : static_cast( mRootIndex ) ); + return mapFromSource( sourceIndex ); +} + +QModelIndex QgsQuickSubModel::parent( const QModelIndex &child ) const +{ + if ( !mModel ) + return mRootIndex; + + QModelIndex idx = mModel->parent( child ); + if ( idx == mRootIndex ) + return QModelIndex(); + else + return mapFromSource( idx ); +} + +int QgsQuickSubModel::rowCount( const QModelIndex &parent ) const +{ + if ( !mModel ) + return 0; + + return mModel->rowCount( parent.isValid() ? mapToSource( parent ) : static_cast( mRootIndex ) ); +} + +int QgsQuickSubModel::columnCount( const QModelIndex &parent ) const +{ + if ( !mModel ) + return 0; + + return mModel->columnCount( parent.isValid() ? mapToSource( parent ) : static_cast( mRootIndex ) ); +} + +QVariant QgsQuickSubModel::data( const QModelIndex &index, int role ) const +{ + if ( !mModel ) + return QVariant(); + + return mModel->data( mapToSource( index ), role ); +} + +bool QgsQuickSubModel::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + if ( !mModel ) + return false; + + return mModel->setData( mapToSource( index ), value, role ); +} + +QHash QgsQuickSubModel::roleNames() const +{ + if ( !mModel ) + return QHash(); + + return mModel->roleNames(); +} + +QModelIndex QgsQuickSubModel::rootIndex() const +{ + return mRootIndex; +} + +void QgsQuickSubModel::setRootIndex( const QModelIndex &rootIndex ) +{ + if ( rootIndex == mRootIndex ) + return; + + beginResetModel(); + mRootIndex = rootIndex; + endResetModel(); + emit rootIndexChanged(); +} + +QAbstractItemModel *QgsQuickSubModel::model() const +{ + return mModel; +} + +void QgsQuickSubModel::setModel( QAbstractItemModel *model ) +{ + if ( model == mModel ) + return; + + if ( model ) + { + connect( model, &QAbstractItemModel::rowsAboutToBeInserted, this, &QgsQuickSubModel::onRowsAboutToBeInserted ); + connect( model, &QAbstractItemModel::rowsInserted, this, &QgsQuickSubModel::onRowsInserted ); + connect( model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &QgsQuickSubModel::onRowsAboutToBeRemoved ); + connect( model, &QAbstractItemModel::rowsRemoved, this, &QgsQuickSubModel::onRowsRemoved ); + connect( model, &QAbstractItemModel::modelAboutToBeReset, this, &QgsQuickSubModel::onModelAboutToBeReset ); + connect( model, &QAbstractItemModel::modelReset, this, &QAbstractItemModel::modelReset ); + connect( model, &QAbstractItemModel::dataChanged, this, &QgsQuickSubModel::onDataChanged ); + } + + mModel = model; + emit modelChanged(); +} + +void QgsQuickSubModel::onRowsAboutToBeInserted( const QModelIndex &parent, int first, int last ) +{ + emit beginInsertRows( mapFromSource( parent ), first, last ); +} + +void QgsQuickSubModel::onRowsInserted( const QModelIndex &parent, int first, int last ) +{ + Q_UNUSED( parent ) + Q_UNUSED( first ) + Q_UNUSED( last ) + emit endInsertRows(); +} + +void QgsQuickSubModel::onRowsAboutToBeRemoved( const QModelIndex &parent, int first, int last ) +{ + emit beginRemoveRows( mapFromSource( parent ), first, last ); +} + +void QgsQuickSubModel::onRowsRemoved( const QModelIndex &parent, int first, int last ) +{ + Q_UNUSED( parent ) + Q_UNUSED( first ) + Q_UNUSED( last ) + emit endRemoveRows(); +} + +void QgsQuickSubModel::onModelAboutToBeReset() +{ + mMappings.clear(); +} + +void QgsQuickSubModel::onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles ) +{ + emit dataChanged( mapFromSource( topLeft ), mapFromSource( bottomRight ), roles ); +} + +QModelIndex QgsQuickSubModel::mapFromSource( const QModelIndex &sourceIndex ) const +{ + if ( sourceIndex == mRootIndex || !sourceIndex.isValid() ) + return QModelIndex(); + + if ( !mMappings.contains( sourceIndex.internalId() ) ) + { + mMappings.insert( sourceIndex.internalId(), sourceIndex.parent() ); + } + + return createIndex( sourceIndex.row(), sourceIndex.column(), sourceIndex.internalId() ); +} + +QModelIndex QgsQuickSubModel::mapToSource( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return mRootIndex; + + if ( !mModel ) + return mRootIndex; + + return mModel->index( index.row(), index.column(), mMappings.find( index.internalId() ).value() ); +} diff --git a/src/quickgui/qgsquicksubmodel.h b/src/quickgui/qgsquicksubmodel.h new file mode 100644 index 000000000000..61806e3c50e9 --- /dev/null +++ b/src/quickgui/qgsquicksubmodel.h @@ -0,0 +1,106 @@ +/*************************************************************************** + qgsquicksubmodel.h + -------------------------------------- + Date : 16.9.2016 + Copyright : (C) 2016 by Matthias Kuhn + Email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QGSQUICKSUBMODEL_H +#define QGSQUICKSUBMODEL_H + +#include "qgis_quick.h" +#include +#include "qgsquickattributeformmodel.h" + +/** + * \ingroup quick + * + * Helper class for submodels (e.g. tabs within feature model) + * + * It uses internal mapping from internal indexes to indexes in the parent model + * + * \note QML Type: SubModel + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickSubModel : public QAbstractItemModel +{ + Q_OBJECT + + //! parent model (e.g QgsQuickAttributeFormModel) + Q_PROPERTY( QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged ) + + //! root index of parent model + Q_PROPERTY( QModelIndex rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged ) + + public: + //! Create new sub model + QgsQuickSubModel( QObject *parent = nullptr ); + + //! Returns the index of the item in the model specified by the given row, column and parent index. + QModelIndex index( int row, int column, const QModelIndex &parent ) const override; + + //! Returns the parent of the model item with the given index. If the item has no parent, an invalid QModelIndex is returned. + QModelIndex parent( const QModelIndex &child ) const override; + + //! Returns the number of rows under the given parent. Returns 0 on invalid mModel + int rowCount( const QModelIndex &parent ) const override; + + //! Returns the number of columns under the given parent. Returns 0 on invalid mModel + int columnCount( const QModelIndex &parent ) const override; + + //! Returns the data stored under the given role for the item referred to by the index. Returns empty QVariant on invalid mModel + QVariant data( const QModelIndex &index, int role ) const override; + + //! Sets the role data for the item at index to value. + bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override; + + //! Returns the mModel's role names. Returns empty QHash on invalid mModel + QHash roleNames() const override; + + //! Return root index + QModelIndex rootIndex() const; + + //! Set root index + void setRootIndex( const QModelIndex &rootIndex ); + + //! Return model + QAbstractItemModel *model() const; + + //! Set model + void setModel( QAbstractItemModel *model ); + + signals: + //! mModel is changed + void modelChanged(); + + //! mRootIndex is changed + void rootIndexChanged(); + + private slots: + void onRowsAboutToBeInserted( const QModelIndex &parent, int first, int last ); + void onRowsInserted( const QModelIndex &parent, int first, int last ); + void onRowsAboutToBeRemoved( const QModelIndex &parent, int first, int last ); + void onRowsRemoved( const QModelIndex &parent, int first, int last ); + void onModelAboutToBeReset(); + void onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector() ); + + private: + QModelIndex mapFromSource( const QModelIndex &sourceIndex ) const; + QModelIndex mapToSource( const QModelIndex &index ) const; + + QAbstractItemModel *mModel = nullptr; + QPersistentModelIndex mRootIndex; + + // Map internal id to parent index + mutable QHash mMappings; +}; + +#endif // QGSQUICKSUBMODEL_H diff --git a/src/quickgui/qgsquickutils.cpp b/src/quickgui/qgsquickutils.cpp new file mode 100644 index 000000000000..4303ab098722 --- /dev/null +++ b/src/quickgui/qgsquickutils.cpp @@ -0,0 +1,244 @@ +/*************************************************************************** + qgsquickutils.cpp + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "qgis.h" +#include "qgscoordinatereferencesystem.h" +#include "qgscoordinatetransform.h" +#include "qgsdistancearea.h" +#include "qgslogger.h" +#include "qgsvectorlayer.h" + +#include "qgsquickmapsettings.h" +#include "qgsquickutils.h" + + +QgsQuickUtils *QgsQuickUtils::sInstance = 0; + +QgsQuickUtils *QgsQuickUtils::instance() +{ + if ( !sInstance ) + { + QgsDebugMsg( QStringLiteral( "QgsQuickUtils created: %1" ).arg( long( QThread::currentThreadId() ) ) ); + sInstance = new QgsQuickUtils(); + } + return sInstance; +} + +QgsQuickUtils::QgsQuickUtils( QObject *parent ) + : QObject( parent ) +{ + + // calculate screen density for calculation of real pixel sizes from density-independent pixels + int dpiX = QApplication::desktop()->physicalDpiX(); + int dpiY = QApplication::desktop()->physicalDpiY(); + int dpi = dpiX < dpiY ? dpiX : dpiY; // In case of asymmetrical DPI. Improbable + mScreenDensity = dpi / 160.; // 160 DPI is baseline for density-independent pixels in Android +} + +QgsQuickUtils::~QgsQuickUtils() +{ +} + +QgsCoordinateReferenceSystem QgsQuickUtils::coordinateReferenceSystemFromEpsgId( long epsg ) const +{ + return QgsCoordinateReferenceSystem::fromEpsgId( epsg ); +} + +QgsPointXY QgsQuickUtils::pointXYFactory( double x, double y ) const +{ + return QgsPointXY( x, y ); +} + +QgsPoint QgsQuickUtils::pointFactory( double x, double y ) const +{ + return QgsPoint( x, y ); +} + +QgsPoint QgsQuickUtils::coordinateToPoint( const QGeoCoordinate &coor ) const +{ + return QgsPoint( coor.longitude(), coor.latitude(), coor.altitude() ); +} + +QgsPointXY QgsQuickUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs, + const QgsCoordinateReferenceSystem &destCrs, + const QgsCoordinateTransformContext &context, + const QgsPointXY &srcPoint ) const +{ + QgsCoordinateTransform mTransform( srcCrs, destCrs, context ); + QgsPointXY pt = mTransform.transform( srcPoint ); + return pt; +} + +bool QgsQuickUtils::hasValidGeometry( QgsVectorLayer *layer, const QgsFeature &feat ) +{ + if ( !layer ) + return false; + + if ( !feat.hasGeometry() ) + return false; + + if ( feat.geometry().type() != layer->geometryType() ) + return false; + + if ( QgsWkbTypes::hasZ( layer->wkbType() ) != QgsWkbTypes::hasZ( feat.geometry().wkbType() ) ) + return false; + + return true; +} + +double QgsQuickUtils::screenUnitsToMeters( QgsQuickMapSettings *mapSettings, int baseLengthPixels ) const +{ + if ( mapSettings == 0 ) return 0; + + QgsDistanceArea mDistanceArea; + mDistanceArea.setEllipsoid( "WGS84" ); + mDistanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() ); + + // calculate the geographic distance from the central point of extent + // to the specified number of points on the right side + QSize s = mapSettings->outputSize(); + QPoint pointCenter( s.width() / 2, s.height() / 2 ); + QgsPointXY p1 = mapSettings->screenToCoordinate( pointCenter ); + QgsPointXY p2 = mapSettings->screenToCoordinate( pointCenter + QPoint( baseLengthPixels, 0 ) ); + return mDistanceArea.measureLine( p1, p2 ); +} + +bool QgsQuickUtils::fileExists( QString path ) +{ + QFileInfo check_file( path ); + // check if file exists and if yes: Is it really a file and no directory? + return ( check_file.exists() && check_file.isFile() ); +} + +void QgsQuickUtils::copyFile( QString sourcePath, QString targetPath ) +{ + if ( !fileExists( sourcePath ) ) + { + QgsDebugMsg( QStringLiteral( "Source file does not exist! %1" ).arg( sourcePath ) ); + return; + } + + if ( !QDir::root().mkpath( targetPath ) ) + { + QgsApplication::messageLog()->logMessage( tr( "Could not create folder %1" ).arg( targetPath ), "QgsQuick", Qgis::Critical ); + return; + } + + QDir dir( targetPath ); + QString filename( QFile( sourcePath ).fileName() ); + + if ( !QFile( sourcePath ).rename( dir.absoluteFilePath( filename ) ) ) + { + QgsDebugMsg( QStringLiteral( "Couldn't rename file! Trying to copy instead! %1" ).arg( filename ) ); + if ( !QFile( sourcePath ).copy( dir.absoluteFilePath( filename ) ) ) + { + QgsApplication::messageLog()->logMessage( tr( "File %1 could not be copied to folder %2.", "QgsQuick", Qgis::Critical ).arg( sourcePath, targetPath ) ); + return; + } + } +} + +void QgsQuickUtils::remove( QString path ) +{ + QFile::remove( path ); +} + +QString QgsQuickUtils::getFileName( QString path ) +{ + QFileInfo fileInfo( path ); + QString filename( fileInfo.fileName() ); + return filename; +} + +void QgsQuickUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level ) +{ + QgsMessageLog::logMessage( message, tag, level ); +} + +QUrl QgsQuickUtils::getThemeIcon( const QString &name ) +{ + QString extension( ".svg" ); + QString path = "qrc:/" + name + extension; + QgsDebugMsg( QStringLiteral( "Using icon %1 from %2" ).arg( name, path ) ); + return QUrl( path ); +} + +QString QgsQuickUtils::qgsPointToString( const QgsPoint &point, int decimals ) +{ + QString label; + label += QString::number( point.x(), 'f', decimals ); + label += ", "; + label += QString::number( point.y(), 'f', decimals ); + return label; +} + +QString QgsQuickUtils::distanceToString( qreal dist, int decimals ) +{ + if ( dist < 0 ) + { + return "0 m"; + } + + QString label; + if ( dist > 1000 ) + { + label += QString::number( dist / 1000.0, 'f', decimals ); + label += QString( " km" ); + } + else + { + if ( dist > 1 ) + { + label += QString::number( dist, 'f', decimals ); + label += QString( " m" ); + } + else + { + label += QString::number( dist * 1000, 'f', decimals ); + label += QString( " mm" ); + } + } + return label; +} + +QString QgsQuickUtils::dumpScreenInfo() const +{ + QRect rec = QApplication::desktop()->screenGeometry(); + int dpiX = QApplication::desktop()->physicalDpiX(); + int dpiY = QApplication::desktop()->physicalDpiY(); + int height = rec.height(); + int width = rec.width(); + double sizeX = ( double ) width / dpiX * 25.4; + double sizeY = ( double ) height / dpiY * 25.4; + + QString msg; + msg += "screen resolution: " + QString::number( width ) + "x" + QString::number( height ) + " px\n"; + msg += "screen DPI: " + QString::number( dpiX ) + "x" + QString::number( dpiY ) + "\n"; + msg += "screen size: " + QString::number( sizeX, 'f', 0 ) + "x" + QString::number( sizeY, 'f', 0 ) + " mm\n"; + msg += "screen density: " + QString::number( mScreenDensity ); + return msg; +} + +qreal QgsQuickUtils::screenDensity() const +{ + return mScreenDensity; +} diff --git a/src/quickgui/qgsquickutils.h b/src/quickgui/qgsquickutils.h new file mode 100644 index 000000000000..d717280a02ba --- /dev/null +++ b/src/quickgui/qgsquickutils.h @@ -0,0 +1,154 @@ +/*************************************************************************** + qgsquickutils.h + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSQUICKUTILS_H +#define QGSQUICKUTILS_H + + +#include +#include +#include +#include + +#include "qgis.h" +#include "qgsfeature.h" +#include "qgsmessagelog.h" +#include "qgspoint.h" +#include "qgspointxy.h" + +#include "qgsquickmapsettings.h" +#include "qgis_quick.h" + + +class QgsFeature; +class QgsVectorLayer; +class QgsCoordinateReferenceSystem; + +/** + * \ingroup quick + * + * Singleton encapsulating the common utilies for QgsQuick library. + * + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickUtils: public QObject +{ + Q_OBJECT + + /** + * "dp" is useful for building building components that work well with different screen densities. + * It stands for density-independent pixels. A width of 10dp is going to be the same physical size + * on all screens regardless their density. In QML code, all values are specified in screen pixels, + * so in order to set a width of 10dp, one would use the following code: "width: 10 * QgsQuick.Utils.dp" + * + * 1dp is approximately 0.16mm. When screen has 160 DPI (baseline), the value of "dp" is 1. + * On high DPI screen the value will be greater, e.g. 1.5. + */ + Q_PROPERTY( qreal dp READ screenDensity CONSTANT ) + + public: + //! return instance of the QgsQuickUtils singleton + static QgsQuickUtils *instance(); + + //! Calculated density of the screen - see "dp" property for more details + qreal screenDensity() const; + + /** + * Create crs from epsg code in QML + */ + Q_INVOKABLE QgsCoordinateReferenceSystem coordinateReferenceSystemFromEpsgId( long epsg ) const; + + /** + * Create QgsPointXY in QML + */ + Q_INVOKABLE QgsPointXY pointXYFactory( double x, double y ) const; + + /** + * Create QgsPoint in QML + */ + Q_INVOKABLE QgsPoint pointFactory( double x, double y ) const; + + /** + * Convert QGeoCoordinate to QgsPoint + */ + Q_INVOKABLE QgsPoint coordinateToPoint( const QGeoCoordinate &coor ) const; + + /** + * Transform point between different crs from QML + */ + Q_INVOKABLE QgsPointXY transformPoint( const QgsCoordinateReferenceSystem &srcCrs, + const QgsCoordinateReferenceSystem &destCrs, + const QgsCoordinateTransformContext &context, + const QgsPointXY &srcPoint ) const; + + /** + * Calculate the distance in meter representing baseLengthPixels pixels on the screen based on the current map settings. + */ + Q_INVOKABLE double screenUnitsToMeters( QgsQuickMapSettings *mapSettings, int baseLengthPixels ) const; + + /** + * Has QgsFeature a geometry that can be added to the layer (non-empty, same geometry type)? + */ + Q_INVOKABLE bool hasValidGeometry( QgsVectorLayer *layer, const QgsFeature &feat ); + + //! Check if file on path exists + Q_INVOKABLE bool fileExists( QString path ); + + //! Copy file from sourcePath to targetPath + Q_INVOKABLE void copyFile( QString sourcePath, QString targetPath ); + + //! Delete file on path from disk + Q_INVOKABLE void remove( QString path ); + + //! Extract filename from path + Q_INVOKABLE QString getFileName( QString path ); + + //! Log message in QgsMessageLog + Q_INVOKABLE void logMessage( const QString &message, + const QString &tag = QString( "QgsQuick" ), + Qgis::MessageLevel level = Qgis::Warning ); + + /** + * Get icon from custom theme dir or default if not found in the theme dir + */ + Q_INVOKABLE QUrl getThemeIcon( const QString &name ); + + /** + * point to string, e.g. -2.234521, 34.4444421 -> -2.234, 34.444 + */ + Q_INVOKABLE QString qgsPointToString( const QgsPoint &point, int decimals = 3 ); + + /** + * distance in meters to human readable length e.g. 1222.234 m -> 1.2 km + */ + Q_INVOKABLE QString distanceToString( qreal dist, int decimals = 1 ); + + //! Returns a string with information about screen size and resolution - useful for debugging + QString dumpScreenInfo() const; + + signals: + + private: + explicit QgsQuickUtils( QObject *parent = nullptr ); + ~QgsQuickUtils(); + + static QgsQuickUtils *sInstance; + + qreal mScreenDensity; + +}; + +#endif // QGSQUICKUTILS_H diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index c803622e9ee3..514902f5cc29 100644 --- a/tests/src/CMakeLists.txt +++ b/tests/src/CMakeLists.txt @@ -46,4 +46,7 @@ IF (ENABLE_TESTS) ADD_SUBDIRECTORY(python) ENDIF (WITH_BINDINGS) ADD_SUBDIRECTORY(geometry_checker) + IF (WITH_QUICK) + ADD_SUBDIRECTORY(quickgui) + ENDIF (WITH_QUICK) ENDIF (ENABLE_TESTS) diff --git a/tests/src/quickgui/CMakeLists.txt b/tests/src/quickgui/CMakeLists.txt new file mode 100644 index 000000000000..af99c766567b --- /dev/null +++ b/tests/src/quickgui/CMakeLists.txt @@ -0,0 +1,86 @@ +# Standard includes and utils to compile into all tests. + +##################################################### +# Don't forget to include output directory, otherwise +# the UI file won't be wrapped! +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + + ${CMAKE_SOURCE_DIR}/src/core + ${CMAKE_SOURCE_DIR}/src/core/annotations + ${CMAKE_SOURCE_DIR}/src/core/auth + ${CMAKE_SOURCE_DIR}/src/core/composer + ${CMAKE_SOURCE_DIR}/src/core/fieldformatter + ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/layertree + ${CMAKE_SOURCE_DIR}/src/core/layout + ${CMAKE_SOURCE_DIR}/src/core/locator + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/providers/memory + ${CMAKE_SOURCE_DIR}/src/core/raster + ${CMAKE_SOURCE_DIR}/src/core/scalebar + ${CMAKE_SOURCE_DIR}/src/core/symbology + ${CMAKE_SOURCE_DIR}/src/core/effects + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/expression + ${CMAKE_SOURCE_DIR}/src/native + ${CMAKE_SOURCE_DIR}/src/quickgui + ${CMAKE_SOURCE_DIR}/src/test + + ${CMAKE_BINARY_DIR}/src/core + ${CMAKE_BINARY_DIR}/src/native + ${CMAKE_BINARY_DIR}/src/quickgui +) + +INCLUDE_DIRECTORIES(SYSTEM + ${LIBZIP_INCLUDE_DIRS} + ${SPATIALINDEX_INCLUDE_DIR} + ${PROJ_INCLUDE_DIR} + ${GEOS_INCLUDE_DIR} + ${GDAL_INCLUDE_DIR} + ${EXPAT_INCLUDE_DIR} + ${SQLITE3_INCLUDE_DIR} + ${SPATIALITE_INCLUDE_DIR} + ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} +) + +#note for tests we should not include the moc of our +#qtests in the executable file list as the moc is +#directly included in the sources +#and should not be compiled twice. Trying to include +#them in will cause an error at build time + +#No relinking and full RPATH for the install tree +#See: http://www.cmake.org/Wiki/CMake_RPATH_handling#No_relinking_and_full_RPATH_for_the_install_tree + +MACRO (ADD_QGIS_TEST testname testsrc) + SET(qgis_${testname}_SRCS ${testsrc} ${util_SRCS}) + ADD_CUSTOM_TARGET(qgis_${testname}moc ALL DEPENDS ${qgis_${testname}_MOC_SRCS}) + ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS}) + SET_TARGET_PROPERTIES(qgis_${testname} PROPERTIES AUTOMOC TRUE) + TARGET_LINK_LIBRARIES(qgis_${testname} + ${Qt5Xml_LIBRARIES} + ${Qt5Core_LIBRARIES} + ${Qt5Svg_LIBRARIES} + ${Qt5Test_LIBRARIES} + Qt5::Gui + Qt5::Qml + Qt5::Quick + Qt5::Xml + qgis_core + qgis_quick) + + ADD_TEST(qgis_${testname} ${CMAKE_CURRENT_BINARY_DIR}/../../../output/bin/qgis_${testname} -maxwarnings 10000) +ENDMACRO (ADD_QGIS_TEST) + +############################################################# +# Tests: + +ADD_QGIS_TEST(qgsquickutils testqgsquickutils.cpp) + + +############################################################# +# Add also test application +ADD_SUBDIRECTORY(app) diff --git a/tests/src/quickgui/app/CMakeLists.txt b/tests/src/quickgui/app/CMakeLists.txt new file mode 100644 index 000000000000..5c573425a1db --- /dev/null +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -0,0 +1,82 @@ +SET(QGIS_QUICK_APP_MOC_HDRS +) + +SET(QGIS_QUICK_APP_SRCS + main.cpp +) + +SET(QGIS_QUICK_APP_QMLS + main.qml + FeaturePanel.qml +) + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + + ${CMAKE_SOURCE_DIR}/src/core + ${CMAKE_SOURCE_DIR}/src/core/annotations + ${CMAKE_SOURCE_DIR}/src/core/auth + ${CMAKE_SOURCE_DIR}/src/core/composer + ${CMAKE_SOURCE_DIR}/src/core/fieldformatter + ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/layertree + ${CMAKE_SOURCE_DIR}/src/core/layout + ${CMAKE_SOURCE_DIR}/src/core/locator + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/providers/memory + ${CMAKE_SOURCE_DIR}/src/core/raster + ${CMAKE_SOURCE_DIR}/src/core/scalebar + ${CMAKE_SOURCE_DIR}/src/core/symbology + ${CMAKE_SOURCE_DIR}/src/core/effects + ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/expression + ${CMAKE_SOURCE_DIR}/src/native + ${CMAKE_SOURCE_DIR}/src/quickgui + + ${CMAKE_BINARY_DIR}/src/core + ${CMAKE_BINARY_DIR}/src/native + ${CMAKE_BINARY_DIR}/src/quickgui +) + +INCLUDE_DIRECTORIES(SYSTEM + ${LIBZIP_INCLUDE_DIRS} + ${SPATIALINDEX_INCLUDE_DIR} + ${PROJ_INCLUDE_DIR} + ${GEOS_INCLUDE_DIR} + ${GDAL_INCLUDE_DIR} + ${EXPAT_INCLUDE_DIR} + ${SQLITE3_INCLUDE_DIR} + ${SPATIALITE_INCLUDE_DIR} + ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} +) + +QT5_WRAP_CPP(QGIS_QUICK_APP_MOC_SRCS ${QGIS_QUICK_APP_MOC_HDRS}) +QT5_ADD_RESOURCES(QGIS_QUICK_APP_RESOURCES qml.qrc) +SET(QGIS_QUICK_APP_NAME qgis_quickapp) +ADD_EXECUTABLE(${QGIS_QUICK_APP_NAME} + ${QGIS_QUICK_APP_RESOURCES} + ${QGIS_QUICK_APP_QMLS} + ${QGIS_QUICK_APP_SRCS} + ${QGIS_QUICK_APP_MOC_SRCS} +) + +TARGET_LINK_LIBRARIES(${QGIS_QUICK_APP_NAME} Qt5::Gui Qt5::Qml Qt5::Quick Qt5::Xml qgis_core qgis_quick) +SET_TARGET_PROPERTIES(${QGIS_QUICK_APP_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + +TARGET_COMPILE_DEFINITIONS(${QGIS_QUICK_APP_NAME} PRIVATE "-DDQT_NO_FOREACH") +ADD_DEPENDENCIES(${QGIS_QUICK_APP_NAME} + qgis_quick_plugin + ogrprovider + gdalprovider + spatialiteprovider + virtuallayerprovider) + +INSTALL(TARGETS ${QGIS_QUICK_APP_NAME} + RUNTIME DESTINATION ${QGIS_BIN_DIR} + LIBRARY DESTINATION ${QGIS_LIB_DIR} + ARCHIVE DESTINATION ${QGIS_LIB_DIR} + FRAMEWORK DESTINATION ${QGIS_FW_SUBDIR} + PUBLIC_HEADER DESTINATION ${QGIS_INCLUDE_DIR}) + diff --git a/tests/src/quickgui/app/FeaturePanel.qml b/tests/src/quickgui/app/FeaturePanel.qml new file mode 100644 index 000000000000..47593659a377 --- /dev/null +++ b/tests/src/quickgui/app/FeaturePanel.qml @@ -0,0 +1,79 @@ +/*************************************************************************** + FeaturePanel.qml + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QgisQuick 0.1 as QgsQuick + +Drawer { + + property var mapSettings + property var project + + property alias state: featureForm.state + property alias layer: featureModel.layer + property alias feature: featureModel.feature + property alias currentFeatureModel: featureModel + + id: featurePanel + visible: false + modal: true + interactive: true + dragMargin: 0 // prevents opening the drawer by dragging. + + background: Rectangle { + color: "white" + opacity: 0.5 + } + + function show_panel(layer, feature, state) { + if (QgsQuick.Utils.hasValidGeometry(layer, feature)) { + // layer needs to be set before the feature otherwise the panel ends up empty on layer change + featurePanel.layer = layer + featurePanel.feature = feature + featurePanel.state = state + + // visible needs to be after setting correct layer&feature, + // so currentFeatureModel is already up to date (required for feature highlight) + featurePanel.visible = true + } else { + QgsQuick.Utils.logMessage("The feature " + layer.name + " has a wrong geometry." , "YDNPA Surveys") + } + } + + QgsQuick.FeatureForm { + id: featureForm + + // using anchors here is not working well as + width: featurePanel.width + height: featurePanel.height + + model: QgsQuick.AttributeFormModel { + featureModel: QgsQuick.FeatureModel { + id: featureModel + } + } + + project: featurePanel.project + + toolbarVisible: true + + onSaved: { + featurePanel.visible = false + } + onCanceled: featurePanel.visible = false + } + +} diff --git a/tests/src/quickgui/app/main.cpp b/tests/src/quickgui/app/main.cpp new file mode 100644 index 000000000000..e97b0d542342 --- /dev/null +++ b/tests/src/quickgui/app/main.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + main.cpp + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include + +#include "qgis.h" +#include "qgsapplication.h" +#include "qgsproject.h" +#include "qgslayertree.h" +#include "qgsmessagelog.h" +#include "qgsquickutils.h" +#include "qgslogger.h" + +int main( int argc, char *argv[] ) +{ + // 1) Initialize QGIS + QgsApplication app( argc, argv, true ); + + // Set up the QSettings environment must be done after qapp is created + QCoreApplication::setOrganizationName( "QGIS" ); + QCoreApplication::setOrganizationDomain( "qgis.org" ); + QCoreApplication::setApplicationName( "QgsQuick Test App" ); + QCoreApplication::setApplicationVersion( Qgis::QGIS_VERSION ); + + QCommandLineParser parser; + parser.addVersionOption(); + parser.process( app ); + + QgsApplication::init(); + QgsApplication::initQgis(); + + // 2) Load QGIS Project + QString dataDir( TEST_DATA_DIR ); // defined in CMakeLists.txt + QString projectFile = dataDir + "/quickapp_project.qgs"; + QgsDebugMsg( QStringLiteral( "project file: %1" ).arg( projectFile ) ); + QgsProject project; + bool res = project.read( projectFile ); + Q_ASSERT( res ); + + QQmlEngine engine; + engine.addImportPath( QgsApplication::qmlImportPath() ); + engine.rootContext()->setContextProperty( "__project", &project ); + engine.rootContext()->setContextProperty( "__layers", QVariant::fromValue( project.layerTreeRoot()->layerOrder() ) ); + + // Set simulated position for desktop builds + bool use_simulated_position = true; + engine.rootContext()->setContextProperty( "__use_simulated_position", use_simulated_position ); + + QQmlComponent component( &engine, QUrl( QStringLiteral( "qrc:/main.qml" ) ) ); + QObject *object = component.create(); + + if ( !component.errors().isEmpty() ) + { + QgsDebugMsg( QStringLiteral( "%s" ).arg( QgsApplication::showSettings().toLocal8Bit().data() ) ); + + QgsDebugMsg( QStringLiteral( "****************************************" ) ); + QgsDebugMsg( QStringLiteral( "***** QML errors: *****" ) ); + QgsDebugMsg( QStringLiteral( "****************************************" ) ); + const QList errors = component.errors(); + for ( const QQmlError &error : errors ) + { + QgsDebugMsg( error.toString() ); + } + QgsDebugMsg( QStringLiteral( "****************************************" ) ); + QgsDebugMsg( QStringLiteral( "****************************************" ) ); + } + + if ( object == nullptr ) + { + QgsDebugMsg( QStringLiteral( "FATAL ERROR: unable to create main.qml" ) ); + return EXIT_FAILURE; + } + + // Add some data for debugging if needed + QgsApplication::messageLog()->logMessage( QgsQuickUtils::instance()->dumpScreenInfo() ); + QgsApplication::messageLog()->logMessage( "data directory: " + dataDir ); + QgsApplication::messageLog()->logMessage( "All up and running" ); + + return app.exec(); +} + diff --git a/tests/src/quickgui/app/main.qml b/tests/src/quickgui/app/main.qml new file mode 100644 index 000000000000..2dea4e2b23c4 --- /dev/null +++ b/tests/src/quickgui/app/main.qml @@ -0,0 +1,134 @@ +/*************************************************************************** + main.qml + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QgisQuick 0.1 as QgsQuick +import "." + +ApplicationWindow { + id: window + visible: true + visibility: "Maximized" + title: qsTr("QGIS Quick Test App") + + // Some info + Button { + id: logbutton + text: "Log" + onClicked: logPanel.visible = true + z: 1 + } + + Label { + text: positionMarker.gpsPositionLabel + z: 1 + x: logbutton.width + 10 + } + + QgsQuick.MapCanvas { + id: mapCanvas + + height: parent.height + width: parent.width + + mapSettings.project: __project + mapSettings.layers: __layers + + QgsQuick.IdentifyKit { + id: identifyKit + mapSettings: mapCanvas.mapSettings + } + + onClicked: { + + var screenPoint = Qt.point( mouse.x, mouse.y ); + var res = identifyKit.identifyOne(screenPoint); + if (res.valid) + { + featurePanel.show_panel( + res.layer, + res.feature, + "Edit" ) + } + } + } + + Item { + anchors.fill: mapCanvas + transform: QgsQuick.MapTransform { + mapSettings: mapCanvas.mapSettings + } + + QgsQuick.FeatureHighlight { + color: "red" + width: 20 + model: featurePanel.visible ? featurePanel.currentFeatureModel : null + mapSettings: mapCanvas.mapSettings + } + + z: 1 // make sure items from here are on top of the Z-order + } + + + Drawer { + id: logPanel + visible: false + modal: true + interactive: true + dragMargin: 0 // prevents opening the drawer by dragging. + height: window.height + width: QgsQuick.Utils.dp * 700 + edge: Qt.RightEdge + z: 2 // make sure items from here are on top of the Z-order + + background: Rectangle { + color: "white" + } + + QgsQuick.MessageLog { + id: messageLog + width: parent.width + height: parent.height + model: QgsQuick.MessageLogModel {} + visible: true + } + } + + QgsQuick.PositionMarker { + id: positionMarker + mapSettings: mapCanvas.mapSettings + simulatePositionLongLatRad: __use_simulated_position ? [-97.36, 36.93, 2] : undefined + } + + QgsQuick.ScaleBar { + id: scaleBar + y: window.height - height + height: 50 + mapSettings: mapCanvas.mapSettings + preferredWidth: 115 * QgsQuick.Utils.dp + z: 1 + } + + FeaturePanel { + id: featurePanel + height: window.height + width: QgsQuick.Utils.dp * 700 + edge: Qt.RightEdge + mapSettings: mapCanvas.mapSettings + project: __project + } + +} diff --git a/tests/src/quickgui/app/qml.qrc b/tests/src/quickgui/app/qml.qrc new file mode 100644 index 000000000000..94dd1b25a29f --- /dev/null +++ b/tests/src/quickgui/app/qml.qrc @@ -0,0 +1,6 @@ + + + main.qml + FeaturePanel.qml + + diff --git a/tests/src/quickgui/app/qtquickcontrols2.conf b/tests/src/quickgui/app/qtquickcontrols2.conf new file mode 100644 index 000000000000..1764b16fb484 --- /dev/null +++ b/tests/src/quickgui/app/qtquickcontrols2.conf @@ -0,0 +1,15 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Default + +[Universal] +Theme=Light +;Accent=Steel + +[Material] +Theme=Light +;Accent=BlueGrey +;Primary=BlueGray diff --git a/tests/src/quickgui/testqgsquickutils.cpp b/tests/src/quickgui/testqgsquickutils.cpp new file mode 100644 index 000000000000..d6abc854a72a --- /dev/null +++ b/tests/src/quickgui/testqgsquickutils.cpp @@ -0,0 +1,104 @@ +/*************************************************************************** + testqgsquickutils.cpp + -------------------------------------- + Date : Nov 2017 + Copyright : (C) 2017 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include +#include +#include +#include +#include + +#include "qgsapplication.h" +#include "qgscoordinatereferencesystem.h" +#include "qgscoordinatetransformcontext.h" +#include "qgspoint.h" +#include "qgspointxy.h" +#include "qgstest.h" + +#include "qgsquickutils.h" + +class TestQgsQuickUtils: public QObject +{ + Q_OBJECT + private slots: + void init() {} // will be called before each testfunction is executed. + void cleanup() {} // will be called after every testfunction. + + void crs_and_geometry(); + void formatting(); +}; + +void TestQgsQuickUtils::crs_and_geometry() +{ + QgsCoordinateReferenceSystem crs3857 = QgsQuickUtils::instance()->coordinateReferenceSystemFromEpsgId( 3857 ); + QVERIFY( crs3857.authid() == "EPSG:3857" ); + + QgsCoordinateReferenceSystem crsGPS = QgsQuickUtils::instance()->coordinateReferenceSystemFromEpsgId( 4326 ); + QVERIFY( crsGPS.authid() == "EPSG:4326" ); + + QgsPointXY pointXY = QgsQuickUtils::instance()->pointXYFactory( 49.9, 16.3 ); + QVERIFY( pointXY.x() == 49.9 ); + QVERIFY( pointXY.y() == 16.3 ); + + QgsPoint point = QgsQuickUtils::instance()->pointFactory( 1.0, -1.0 ); + QVERIFY( point.x() == 1.0 ); + QVERIFY( point.y() == -1.0 ); + + QgsPointXY transformedPoint = QgsQuickUtils::instance()->transformPoint( crsGPS, + crs3857, + QgsCoordinateTransformContext(), + pointXY ); + QVERIFY( fabs( transformedPoint.x() - 5554843 ) < 1.0 ); + QVERIFY( fabs( transformedPoint.y() - 1839491 ) < 1.0 ); + + QgsQuickMapSettings ms; + ms.setDestinationCrs( crsGPS ); + ms.setExtent( QgsRectangle( 49, 16, 50, 17 ) ); + ms.setOutputSize( QSize( 1000, 500 ) ); + double sutm = QgsQuickUtils::instance()->screenUnitsToMeters( &ms, 1 ); + QVERIFY( fabs( sutm - 213 ) < 1.0 ); +} + +void TestQgsQuickUtils::formatting() +{ + QgsPoint point( -2.234521, 34.4444421 ); + QString point2str = QgsQuickUtils::instance()->qgsPointToString( point, 3 ); + QVERIFY( point2str == "-2.235, 34.444" ); + + point2str = QgsQuickUtils::instance()->qgsPointToString( point, 2 ); + QVERIFY( point2str == "-2.23, 34.44" ); + + point2str = QgsQuickUtils::instance()->qgsPointToString( point, 1 ); + QVERIFY( point2str == "-2.2, 34.4" ); + + point2str = QgsQuickUtils::instance()->qgsPointToString( point, 0 ); + QVERIFY( point2str == "-2, 34" ); + + QString dist2str = QgsQuickUtils::instance()->distanceToString( 1222.234, 2 ); + QVERIFY( dist2str == "1.22 km" ); + + dist2str = QgsQuickUtils::instance()->distanceToString( 1222.234, 1 ); + QVERIFY( dist2str == "1.2 km" ); + + dist2str = QgsQuickUtils::instance()->distanceToString( 1222.234, 0 ); + QVERIFY( dist2str == "1 km" ); + + dist2str = QgsQuickUtils::instance()->distanceToString( 700.22, 1 ); + QVERIFY( dist2str == "700.2 m" ); + + dist2str = QgsQuickUtils::instance()->distanceToString( 0.22, 0 ); + QVERIFY( dist2str == "220 mm" ); +} + +QGSTEST_MAIN( TestQgsQuickUtils ) +#include "testqgsquickutils.moc" diff --git a/tests/testdata/quickapp_project.qgs b/tests/testdata/quickapp_project.qgs new file mode 100644 index 000000000000..5cc9aa6a5bc7 --- /dev/null +++ b/tests/testdata/quickapp_project.qgs @@ -0,0 +1,1018 @@ + + + + + + + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + points20151123133104693 + polys20151123133114244 + lines20151123133101198 + + + + + + + + + + + + degrees + + -119.35580667290255974 + 25.42676323270561767 + -82.46631384217860727 + 48.75598131139545188 + + 0 + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + 0 + + + + + + + + + + + + + + + + + + -117.62319839219053108 + 23.20820580488508966 + -82.32264950769274492 + 46.18290982947509349 + + lines20151123133101198 + ./lines.shp + + + + Roads + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + ogr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../src/quickgui/app/qgis-data + + 0 + ../src/quickgui/app/qgis-data + + 0 + generatedlayout + + + + + + + + + COALESCE( "Name", '<NULL>' ) + + + + + -118.88888888888877204 + 22.80020703933767834 + -83.33333333333315807 + 46.87198067632875365 + + points20151123133104693 + ./points.shp + + + + Planes + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + 0 + 0 + + + + + false + + + + + + ogr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../src/quickgui/app/qgis-data + + 0 + ../src/quickgui/app/qgis-data + + 0 + generatedlayout + + + + + + + + + COALESCE( "Class", '<NULL>' ) + + + + + -118.92286230599032137 + 24.50786971868489061 + -83.79001199101509201 + 46.72617265077044379 + + polys20151123133114244 + ./polys.shp + + + + Land + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + 0 + 0 + + + + + false + + + + + + ogr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../src/quickgui/app/qgis-data + + 0 + ../src/quickgui/app/qgis-data + + 0 + generatedlayout + + + + + + + + + COALESCE( "Name", '<NULL>' ) + + + + + + + + + + 8 + + conditions unknown + false + + false + + + + false + + + + + + None + + + + + + + + + + + + + MU + 2 + true + + false + + + + + + + + + + 255 + + + + true + + false + + + + + EPSG:4326 + 1 + 3452 + +proj=longlat +datum=WGS84 +no_defs + + + NONE + + + + + false + + + + m2 + meters + + + to vertex and segment + 1 + + lines20151123133101198 + points20151123133104693 + polys20151123133114244 + + 20 + + 20.000000 + 20.000000 + 20.000000 + + + to_vertex_and_segment + to_vertex_and_segment + to_vertex_and_segment + + current_layer + + + 1 + 1 + 1 + + + enabled + enabled + enabled + + + + 90 + + 255 + 255 + 255 + 255 + 255 + 0 + 255 + + + + + + + +