diff --git a/.ci/travis/linux/docker-build-test.sh b/.ci/travis/linux/docker-build-test.sh index c29fcde8584..2cc2a5a4fc3 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 \ -DUSE_CCACHE=OFF \ + -DWITH_QUICK=ON \ -DWITH_3D=ON \ -DWITH_STAGED_PLUGINS=ON \ -DWITH_GRASS=OFF \ diff --git a/.docker/qgis3-build-deps.dockerfile b/.docker/qgis3-build-deps.dockerfile index 9c5e20b1bf6..29f87cfc59e 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 \ @@ -77,6 +83,8 @@ RUN apt-get update \ qt3d-scene2d-plugin \ qt5keychain-dev \ qtbase5-dev \ + qtdeclarative5-dev-tools \ + qtdeclarative5-qtquick2-plugin \ qtpositioning5-dev \ qttools5-dev \ qttools5-dev-tools \ diff --git a/.gitignore b/.gitignore index 34f7fed68eb..900ac2d0a1d 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 1288712817a..00faa6b5e9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,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) @@ -322,6 +324,18 @@ 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") + + # following variable is used in qgsconfig.h + SET (HAVE_QUICK TRUE) + ENDIF (WITH_QUICK) IF(WITH_QTWEBKIT) SET(OPTIONAL_QTWEBKIT ${Qt5WebKitWidgets_LIBRARIES}) @@ -373,6 +387,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 @@ -513,6 +530,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) @@ -583,6 +601,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) @@ -610,6 +629,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) @@ -658,6 +678,7 @@ 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") @@ -673,6 +694,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 00000000000..c0cbf50d9da --- /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 67bbefe2af8..2310ac83d9e 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}" @@ -64,5 +65,7 @@ #cmakedefine QGISDEBUG +#cmakedefine HAVE_QUICK + #endif diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 631f8ed7c6a..4a38b1b2e77 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -100,6 +100,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 02bc2720e37..a56f71d9835 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 diff --git a/doc/index.dox b/doc/index.dox index a8308865341..688ec146a50 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/core/qgsrelationmanager.cpp b/src/core/qgsrelationmanager.cpp index 6c79784ccc8..a0c6c722d24 100644 --- a/src/core/qgsrelationmanager.cpp +++ b/src/core/qgsrelationmanager.cpp @@ -25,9 +25,12 @@ QgsRelationManager::QgsRelationManager( QgsProject *project ) : QObject( project ) , mProject( project ) { - connect( project, &QgsProject::readProject, this, &QgsRelationManager::readProject ); - connect( project, &QgsProject::writeProject, this, &QgsRelationManager::writeProject ); - connect( project, &QgsProject::layersRemoved, this, &QgsRelationManager::layersRemoved ); + if ( mProject ) + { + connect( project, &QgsProject::readProject, this, &QgsRelationManager::readProject ); + connect( project, &QgsProject::writeProject, this, &QgsRelationManager::writeProject ); + connect( project, &QgsProject::layersRemoved, this, &QgsRelationManager::layersRemoved ); + } } void QgsRelationManager::setRelations( const QList &relations ) diff --git a/src/quickgui/CMakeLists.txt b/src/quickgui/CMakeLists.txt new file mode 100644 index 00000000000..041e8a7b25c --- /dev/null +++ b/src/quickgui/CMakeLists.txt @@ -0,0 +1,121 @@ +############################################################ +# sources +SET(QGIS_QUICK_GUI_MOC_HDRS + qgsquickmapcanvasmap.h + qgsquickmapsettings.h + qgsquickutils.h +) + +SET(QGIS_QUICK_GUI_HDRS +) + +SET(QGIS_QUICK_GUI_SRC + qgsquickmapcanvasmap.cpp + qgsquickmapsettings.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=) + +############################################################ +# 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 "-DQT_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/plugin/CMakeLists.txt b/src/quickgui/plugin/CMakeLists.txt new file mode 100644 index 00000000000..027d3f10155 --- /dev/null +++ b/src/quickgui/plugin/CMakeLists.txt @@ -0,0 +1,121 @@ +############################################################ +# sources + +SET(QGIS_QUICK_PLUGIN_MOC_HDRS + qgsquickplugin.h +) + +SET(QGIS_QUICK_PLUGIN_SRC + qgsquickplugin.cpp +) + +SET(QGIS_QUICK_PLUGIN_RESOURCES + qgsquickmapcanvas.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_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=" "-DQT_NO_FOREACH") + +# Copy qml files to output directory, we need qml files in the same directory as the plugin shared library +FOREACH(qmlfile ${QGIS_QUICK_PLUGIN_RESOURCES}) + ADD_CUSTOM_COMMAND(TARGET qgis_quick_plugin + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${qmlfile} ${QGIS_QUICK_PLUGIN_RUNTIME_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${qmlfile} + ) +ENDFOREACH(qmlfile) + +IF(QMLPLUGINDUMP_FOUND) + # To create typeinfo file, no qml files must be in the directory, otherwise + # bunch of "QObject: Cannot create children for a parent that is in a different thread." errors + # appear and typeinfo file is not generated + SET(QGIS_QUICK_TYPEINFO_GENERATE_DIR ${CMAKE_CURRENT_BINARY_DIR}/QgisQuick) + + # 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 ${CMAKE_COMMAND} -E make_directory ${QGIS_QUICK_TYPEINFO_GENERATE_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/qmldir ${QGIS_QUICK_TYPEINFO_GENERATE_DIR} + COMMAND ${CMAKE_COMMAND} -E copy $ ${QGIS_QUICK_TYPEINFO_GENERATE_DIR} + COMMAND ${QMLPLUGINDUMP_EXECUTABLE} + ARGS QgisQuick 0.1 . -v --output ${QGIS_QUICK_PLUGIN_TYPEINFO} + 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/qgsquickmapcanvas.qml b/src/quickgui/plugin/qgsquickmapcanvas.qml new file mode 100644 index 00000000000..911f1bb5a9c --- /dev/null +++ b/src/quickgui/plugin/qgsquickmapcanvas.qml @@ -0,0 +1,142 @@ + +/*************************************************************************** + 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/qgsquickplugin.cpp b/src/quickgui/plugin/qgsquickplugin.cpp new file mode 100644 index 00000000000..96ec50ec435 --- /dev/null +++ b/src/quickgui/plugin/qgsquickplugin.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + 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 "qgsquickmapcanvasmap.h" +#include "qgsquickmapsettings.h" +#include "qgsquickplugin.h" +#include "qgsquickutils.h" + +static QObject *_utilsProvider( QQmlEngine *engine, QJSEngine *scriptEngine ) +{ + Q_UNUSED( engine ) + Q_UNUSED( scriptEngine ) + return new QgsQuickUtils(); // the object will be owned by QML engine and destroyed by the engine on exit +} + +void QgisQuickPlugin::registerTypes( const char *uri ) +{ + qRegisterMetaType< QList >( "QList" ); + qRegisterMetaType< QgsAttributes > ( "QgsAttributes" ); + qRegisterMetaType< QgsCoordinateReferenceSystem >( "QgsCoordinateReferenceSystem" ); + qRegisterMetaType< QgsCoordinateTransformContext >( "QgsCoordinateTransformContext" ); + qRegisterMetaType< QgsFeature > ( "QgsFeature " ); + qRegisterMetaType< QgsFeatureId > ( "QgsFeatureId" ); + qRegisterMetaType< QgsPoint >( "QgsPoint" ); + qRegisterMetaType< QgsPointXY >( "QgsPointXY" ); + + qmlRegisterType< QgsProject >( uri, 0, 1, "Project" ); + qmlRegisterType< QgsQuickMapCanvasMap >( uri, 0, 1, "MapCanvasMap" ); + qmlRegisterType< QgsQuickMapSettings >( uri, 0, 1, "MapSettings" ); + qmlRegisterType< QgsVectorLayer >( uri, 0, 1, "VectorLayer" ); + + qmlRegisterSingletonType< QgsQuickUtils >( uri, 0, 1, "Utils", _utilsProvider ); + + qmlRegisterUncreatableType< QgsRelationManager >( uri, 0, 1, "RelationManager", "The relation manager is available from the Project. Try `qgisProject.relationManager`" ); + 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 00000000000..e98ba920f57 --- /dev/null +++ b/src/quickgui/plugin/qgsquickplugin.h @@ -0,0 +1,42 @@ +/*************************************************************************** + 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/qmldir b/src/quickgui/plugin/qmldir new file mode 100644 index 00000000000..b6dfdeee660 --- /dev/null +++ b/src/quickgui/plugin/qmldir @@ -0,0 +1,18 @@ +# 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 + +typeinfo qgisquick.qmltypes diff --git a/src/quickgui/qgsquickmapcanvasmap.cpp b/src/quickgui/qgsquickmapcanvasmap.cpp new file mode 100644 index 00000000000..9919133a005 --- /dev/null +++ b/src/quickgui/qgsquickmapcanvasmap.cpp @@ -0,0 +1,394 @@ +/*************************************************************************** + 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 "qgsmaprendererparalleljob.h" +#include "qgsmessagelog.h" +#include "qgspallabeling.h" +#include "qgsproject.h" +#include "qgsvectorlayer.h" +#include "qgis.h" + +#include "qgsquickmapcanvasmap.h" +#include "qgsquickmapsettings.h" + + +QgsQuickMapCanvasMap::QgsQuickMapCanvasMap( QQuickItem *parent ) + : QQuickItem( parent ) + , mMapSettings( new QgsQuickMapSettings() ) +{ + connect( this, &QQuickItem::windowChanged, this, &QgsQuickMapCanvasMap::onWindowChanged ); + connect( &mRefreshTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::refreshMap ); + connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsQuickMapCanvasMap::renderJobUpdated ); + + Q_ASSERT( mMapSettings ); + 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 ); +} + +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() +{ + const QgsMapRendererJob::Errors errors = mJob->errors(); + for ( const QgsMapRendererJob::Error &error : errors ) + { + QgsMessageLog::logMessage( QStringLiteral( "%1 :: %2" ).arg( 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 ( mMapUpdateTimer.interval() == 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() && + !qgsDoubleNear( rect.width() / rect.height(), mImage.width() / mImage.height() ) ) + { + if ( qgsDoubleNear( 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(); + + for ( const QMetaObject::Connection &conn : qgis::as_const( mLayerConnections ) ) + { + disconnect( conn ); + } + mLayerConnections.clear(); + + const QList layers = mMapSettings->layers(); + for ( QgsMapLayer *layer : layers ) + { + 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; + const QList layers = mMapSettings->layers(); + for ( QgsMapLayer *layer : 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 00000000000..9df302325ce --- /dev/null +++ b/src/quickgui/qgsquickmapcanvasmap.h @@ -0,0 +1,202 @@ +/*************************************************************************** + 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 QgsProject should be attached to the QgsQuickMapSettings. + * 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 + + /** + * The mapSettings property contains configuration for rendering of the map. + * + * It should be used as a primary source of map settings (and project) for + * all other components in the application. + * + * This is a readonly property. + */ + Q_PROPERTY( QgsQuickMapSettings *mapSettings READ mapSettings ) + + /** + * When freeze property is set to true, the map canvas does not refresh. + * The value temporary changes during the rendering process. + */ + Q_PROPERTY( bool freeze READ freeze WRITE setFreeze NOTIFY freezeChanged ) + + /** + * The isRendering property is set to true while a rendering job is pending for this map canvas map. + * It can be used to show a notification icon about an ongoing rendering job. + * This is a readonly property. + */ + 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 ) + + /** + * When the incrementalRendering property is set to true, the automatic refresh of map canvas during rendering is allowed. + */ + Q_PROPERTY( bool incrementalRendering READ incrementalRendering WRITE setIncrementalRendering NOTIFY incrementalRenderingChanged ) + + public: + //! Create map canvas map + QgsQuickMapCanvasMap( QQuickItem *parent = nullptr ); + ~QgsQuickMapCanvasMap() = default; + + virtual QSGNode *updatePaintNode( QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * ) override; + + //! \copydoc QgsQuickMapCanvasMap::mapSettings + QgsQuickMapSettings *mapSettings() const; + + //! \copydoc QgsQuickMapCanvasMap::freeze + bool freeze() const; + + //! \copydoc QgsQuickMapCanvasMap::freeze + void setFreeze( bool freeze ); + + //! \copydoc QgsQuickMapCanvasMap::isRendering + bool isRendering() const; + + //! \copydoc QgsQuickMapCanvasMap::mapUpdateInterval + int mapUpdateInterval() const; + + //! \copydoc QgsQuickMapCanvasMap::mapUpdateInterval + void setMapUpdateInterval( int mapUpdateInterval ); + + //! \copydoc QgsQuickMapCanvasMap::incrementalRendering + bool incrementalRendering() const; + + //! \copydoc QgsQuickMapCanvasMap::incrementalRendering + void setIncrementalRendering( bool incrementalRendering ); + + signals: + + /** + * Signal is emitted when a rendering is starting + */ + void renderStarting(); + + /** + * Signal is emitted when a canvas is refreshed + */ + void mapCanvasRefreshed(); + + //! \copydoc QgsQuickMapCanvasMap::freeze + void freezeChanged(); + + //! \copydoc QgsQuickMapCanvasMap::isRendering + void isRenderingChanged(); + + //!\copydoc QgsQuickMapCanvasMap::mapUpdateInterval + void mapUpdateIntervalChanged(); + + //!\copydoc QgsQuickMapCanvasMap::incrementalRendering + void incrementalRenderingChanged(); + + protected: + virtual void geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry ) override; + + public slots: + //! Stop map rendering + void stopRendering(); + + /** + * Set map setting's extent (zoom the map) on the center by given scale + */ + void zoom( QPointF center, qreal scale ); + + /** + * Set map setting's extent (pan the map) based on the difference of positions + */ + void pan( QPointF oldPos, QPointF newPos ); + + /** + * Refresh the map canvas. + * Does nothing when output size of map settings is not set + */ + 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 by stopRendering()! + */ + void destroyJob( QgsMapRendererJob *job ); + QgsMapSettings prepareMapSettings() const; + void updateTransform(); + void zoomToFullExtent(); + + QgsQuickMapSettings *mMapSettings; + bool mPinching = false; + QPoint mPinchStartPoint; + QgsMapRendererParallelJob *mJob = nullptr; + QgsMapRendererCache *mCache = nullptr; + QgsLabelingResults *mLabelingResults = nullptr; + QImage mImage; + QgsMapSettings mImageMapSettings; + QTimer mRefreshTimer; + bool mDirty = false; + bool mFreeze = false; + QList mLayerConnections; + QTimer mMapUpdateTimer; + bool mIncrementalRendering = false; +}; + +#endif // QGSQUICKMAPCANVASMAP_H diff --git a/src/quickgui/qgsquickmapsettings.cpp b/src/quickgui/qgsquickmapsettings.cpp new file mode 100644 index 00000000000..e9b2cf5c4fd --- /dev/null +++ b/src/quickgui/qgsquickmapsettings.cpp @@ -0,0 +1,214 @@ +/*************************************************************************** + 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 "qgis.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 ); +} + +void QgsQuickMapSettings::setProject( QgsProject *project ) +{ + if ( project == mProject ) + return; + + // If we have already something connected, disconnect it! + if ( mProject ) + { + mProject->disconnect( this ); + } + + mProject = project; + + // Connect all signals + if ( mProject ) + { + connect( mProject, &QgsProject::readProject, this, &QgsQuickMapSettings::onReadProject ); + setDestinationCrs( mProject->crs() ); + 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 ( qgsDoubleNear( 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 ( !qgsDoubleNear( 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 ( !qgsDoubleNear( rotation, 0 ) ) + QgsMessageLog::logMessage( tr( "Map Canvas rotation is not supported. Resetting from %1 to 0." ).arg( rotation ) ); +} diff --git a/src/quickgui/qgsquickmapsettings.h b/src/quickgui/qgsquickmapsettings.h new file mode 100644 index 00000000000..3b61db3b039 --- /dev/null +++ b/src/quickgui/qgsquickmapsettings.h @@ -0,0 +1,237 @@ +/*************************************************************************** + 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. + * QgsProject should be attached before it is read. + * + * \note QML Type: MapSettings + * + * \sa QgsMapCanvas + * + * \since QGIS 3.2 + */ +class QUICK_EXPORT QgsQuickMapSettings : public QObject +{ + Q_OBJECT + + /** + * A project property should be used as a primary source of project all other components + * in the application. QgsProject should be attached to QgsQuickMapSettings before + * it is read (QgsProject::read) + * + * When project is read, map settings (CRS, extent, ...) are automatically set from its DOM. + */ + Q_PROPERTY( QgsProject *project READ project WRITE setProject NOTIFY projectChanged ) + + /** + * Geographical coordinates of the rectangle that should be rendered. + * The actual visible extent used for rendering could be slightly different + * since the given extent may be expanded in order to fit the aspect ratio + * of output size. Use QgsQuickMapSettings::visibleExtent to get the resulting extent. + * + * Automatically loaded from project on QgsProject::readProject + */ + Q_PROPERTY( QgsRectangle extent READ extent WRITE setExtent NOTIFY extentChanged ) + //! \copydoc QgsMapSettings::visibleExtent() + Q_PROPERTY( QgsRectangle visibleExtent READ visibleExtent NOTIFY visibleExtentChanged ) + //! \copydoc QgsMapSettings::mapUnitsPerPixel() + Q_PROPERTY( double mapUnitsPerPixel READ mapUnitsPerPixel NOTIFY mapUnitsPerPixelChanged ) + + /** + * The rotation of the resulting map image, in degrees clockwise. + * Map canvas rotation support is not implemented, 0 is used + */ + Q_PROPERTY( double rotation READ rotation WRITE setRotation NOTIFY rotationChanged ) + + /** + * The size of the resulting map image + * + * Automatically loaded from project on QgsProject::readProject + */ + Q_PROPERTY( QSize outputSize READ outputSize WRITE setOutputSize NOTIFY outputSizeChanged ) + + /** + * Output DPI used for conversion between real world units (e.g. mm) and pixels + * + * Automatically loaded from project on QgsProject::readProject + */ + Q_PROPERTY( double outputDpi READ outputDpi WRITE setOutputDpi NOTIFY outputDpiChanged ) + + /** + * CRS of destination coordinate reference system. + * + * Automatically loaded from project on QgsProject::readProject + */ + Q_PROPERTY( QgsCoordinateReferenceSystem destinationCrs READ destinationCrs WRITE setDestinationCrs NOTIFY destinationCrsChanged ) + + /** + * Set list of layers for map rendering. The layers must be registered in QgsProject. + * The layers are stored in the reverse order of how they are rendered (layer with index 0 will be on top) + * + * \note Any non-spatial layers will be automatically stripped from the list (since they cannot be rendered!). + * + * Not loaded automatically from the associated project + */ + Q_PROPERTY( QList layers READ layers WRITE setLayers NOTIFY layersChanged ) + + public: + //! Create new map settings + QgsQuickMapSettings( QObject *parent = nullptr ); + ~QgsQuickMapSettings() = default; + + //! Clone map settings + QgsMapSettings mapSettings() const; + + //! \copydoc QgsMapSettings::extent() + QgsRectangle extent() const; + + //! \copydoc QgsMapSettings::setExtent() + void setExtent( const QgsRectangle &extent ); + + //! \copydoc QgsQuickMapSettings::project + void setProject( QgsProject *project ); + + //! \copydoc QgsQuickMapSettings::project + QgsProject *project() const; + + //! Move current map extent to have center point defined by \a center + Q_INVOKABLE void setCenter( const QgsPoint ¢er ); + + //! \copydoc QgsMapSettings::mapUnitsPerPixel() + double mapUnitsPerPixel() const; + + //! \copydoc QgsMapSettings::visibleExtent() + QgsRectangle visibleExtent() const; + + //! \copydoc QgsMapSettings::transformContext() + 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; + + //! \copydoc QgsMapSettings::setTransformContext() + void setTransformContext( const QgsCoordinateTransformContext &context ); + + //! \copydoc QgsQuickMapSettings::rotation + double rotation() const; + + //! \copydoc QgsQuickMapSettings::rotation + void setRotation( double rotation ); + + //! \copydoc QgsMapSettings::outputSize() + QSize outputSize() const; + + //! \copydoc QgsMapSettings::setOutputSize() + void setOutputSize( const QSize &outputSize ); + + //! \copydoc QgsMapSettings::outputDpi() + double outputDpi() const; + + //! \copydoc QgsMapSettings::setOutputDpi() + void setOutputDpi( double outputDpi ); + + //! \copydoc QgsMapSettings::destinationCrs() + QgsCoordinateReferenceSystem destinationCrs() const; + + //! \copydoc QgsMapSettings::setDestinationCrs() + void setDestinationCrs( const QgsCoordinateReferenceSystem &destinationCrs ); + + //! \copydoc QgsMapSettings::layers() + QList layers() const; + + //! \copydoc QgsMapSettings::setLayers() + void setLayers( const QList &layers ); + + signals: + //! \copydoc QgsQuickMapSettings::project + void projectChanged(); + + //! \copydoc QgsQuickMapSettings::extent + void extentChanged(); + + //! \copydoc QgsQuickMapSettings::destinationCrs + void destinationCrsChanged(); + + //! \copydoc QgsQuickMapSettings::mapUnitsPerPixel + void mapUnitsPerPixelChanged(); + + //! \copydoc QgsQuickMapSettings::rotation + void rotationChanged(); + + //! \copydoc QgsQuickMapSettings::visibleExtent + void visibleExtentChanged(); + + //! \copydoc QgsQuickMapSettings::outputSize + void outputSizeChanged(); + + //! \copydoc QgsQuickMapSettings::outputDpi + void outputDpiChanged(); + + //! \copydoc QgsQuickMapSettings::layers + 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/qgsquickutils.cpp b/src/quickgui/qgsquickutils.cpp new file mode 100644 index 00000000000..b1c3a31c5fa --- /dev/null +++ b/src/quickgui/qgsquickutils.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + 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" +#include "qgsunittypes.h" + + +QgsQuickUtils::QgsQuickUtils( QObject *parent ) + : QObject( parent ) + , mScreenDensity( calculateScreenDensity() ) +{ +} + +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 = static_cast( width ) / dpiX * 25.4; + double sizeY = static_cast( height ) / dpiY * 25.4; + + QString msg; + msg += tr( "screen resolution: %1x%2 px\n" ).arg( width ).arg( height ); + msg += tr( "screen DPI: %1x%2\n" ).arg( dpiX ).arg( dpiY ); + msg += tr( "screen size: %1x%2 mm\n" ).arg( QString::number( sizeX, 'f', 0 ), QString::number( sizeY, 'f', 0 ) ); + msg += tr( "screen density: %1" ).arg( mScreenDensity ); + return msg; +} + +qreal QgsQuickUtils::screenDensity() const +{ + return mScreenDensity; +} + +qreal QgsQuickUtils::calculateScreenDensity() +{ + // 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 + return dpi / 160.; // 160 DPI is baseline for density-independent pixels in Android +} diff --git a/src/quickgui/qgsquickutils.h b/src/quickgui/qgsquickutils.h new file mode 100644 index 00000000000..53499ab849f --- /dev/null +++ b/src/quickgui/qgsquickutils.h @@ -0,0 +1,74 @@ +/*************************************************************************** + 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 "qgis.h" +#include "qgis_quick.h" + +/** + * \ingroup quick + * + * Encapsulating the common utilies for QgsQuick library. + * + * \note QML Type: Utils (Singleton) + * + * \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. + * + * This is a readonly property. + */ + Q_PROPERTY( qreal dp READ screenDensity CONSTANT ) + + public: + //! Create new utilities + QgsQuickUtils( QObject *parent = nullptr ); + //! dtor + ~QgsQuickUtils() = default; + + //! \copydoc QgsQuickUtils::dp + qreal screenDensity() const; + + /** + * Returns a string with information about screen size and resolution + * + * Useful to log for debugging of graphical problems on various display sizes + */ + QString dumpScreenInfo() const; + + private: + static qreal calculateScreenDensity(); + + qreal mScreenDensity; +}; + +#endif // QGSQUICKUTILS_H diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index c803622e9ee..514902f5cc2 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 00000000000..af99c766567 --- /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 00000000000..20c1dbe4b7a --- /dev/null +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -0,0 +1,80 @@ +SET(QGIS_QUICK_APP_MOC_HDRS +) + +SET(QGIS_QUICK_APP_SRCS + main.cpp +) + +SET(QGIS_QUICK_APP_QMLS + main.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 "-DQT_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/main.cpp b/tests/src/quickgui/app/main.cpp new file mode 100644 index 00000000000..ceac47ad76d --- /dev/null +++ b/tests/src/quickgui/app/main.cpp @@ -0,0 +1,92 @@ +/*************************************************************************** + 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() ) ); + + 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().dumpScreenInfo() ); + QgsDebugMsg( QStringLiteral( "data directory: %1" ).arg( dataDir ) ); + + return app.exec(); +} + diff --git a/tests/src/quickgui/app/main.qml b/tests/src/quickgui/app/main.qml new file mode 100644 index 00000000000..0c74a3923a7 --- /dev/null +++ b/tests/src/quickgui/app/main.qml @@ -0,0 +1,40 @@ +/*************************************************************************** + 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") + + QgsQuick.MapCanvas { + id: mapCanvas + + height: parent.height + width: parent.width + + mapSettings.project: __project + mapSettings.layers: __layers + + onClicked: { + var screenPoint = Qt.point(mouse.x, mouse.y) + console.log("clicked:" + screenPoint) + } + } +} diff --git a/tests/src/quickgui/app/qml.qrc b/tests/src/quickgui/app/qml.qrc new file mode 100644 index 00000000000..5f6483ac33f --- /dev/null +++ b/tests/src/quickgui/app/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/tests/src/quickgui/app/qtquickcontrols2.conf b/tests/src/quickgui/app/qtquickcontrols2.conf new file mode 100644 index 00000000000..1764b16fb48 --- /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 00000000000..a83f7a9e5c5 --- /dev/null +++ b/tests/src/quickgui/testqgsquickutils.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + 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 "qgsapplication.h" +#include "qgstest.h" +#include "qgis.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 screen_density(); + + void dump_screen_info(); + + private: + QgsQuickUtils utils; +}; + +void TestQgsQuickUtils::screen_density() +{ + qreal dp = utils.screenDensity(); + QVERIFY( ( dp > 0 ) && ( dp < 1000 ) ); +} + +void TestQgsQuickUtils::dump_screen_info() +{ + qreal dp = utils.screenDensity(); + QVERIFY( utils.dumpScreenInfo().contains( QStringLiteral( "%1" ).arg( dp ) ) ); +} + +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 00000000000..5cc9aa6a5bc --- /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 + + + + + + + +