diff --git a/CMakeLists.txt b/CMakeLists.txt index e1ce0906cb33..1eebc4b0fc5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,6 +278,8 @@ ENDIF (WITH_QTMOBILITY) # search for QScintilla2 (C++ lib) FIND_PACKAGE(QScintilla REQUIRED) +# Password helper +FIND_PACKAGE(QtKeychain REQUIRED) # Master password hash and authentication encryption FIND_PACKAGE(QCA REQUIRED) # Check for runtime dependency of qca-ossl plugin diff --git a/ci/travis/linux/before_install.sh b/ci/travis/linux/before_install.sh index f18d3e661e19..cc7c7aef7f2f 100755 --- a/ci/travis/linux/before_install.sh +++ b/ci/travis/linux/before_install.sh @@ -22,7 +22,7 @@ pushd ${HOME} # fetching data from github should be just as fast as S3 -curl -s -S -L https://github.com/opengisch/osgeo4travis/archive/qt5bin.tar.gz | tar --strip-components=1 -xz -C /home/travis & +curl -s -S -L https://github.com/opengisch/osgeo4travis/archive/qt55bin.tar.gz | tar --strip-components=1 -xz -C /home/travis & SETUP_OSGEO4W_PID=$! mkdir /home/travis/osgeo4travis diff --git a/cmake/FindQtKeychain.cmake b/cmake/FindQtKeychain.cmake new file mode 100644 index 000000000000..c2b55fc4a696 --- /dev/null +++ b/cmake/FindQtKeychain.cmake @@ -0,0 +1,51 @@ +# Find QtKeychain +# ~~~~~~~~~~~~~~~ +# Copyright (c) 2016, Boundless Spatial +# Author: Larry Shaffer +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +# CMake module to search for QtKeychain library from: +# https://github.com/frankosterfeld/qtkeychain +# +# If it's found it sets QTKEYCHAIN_FOUND to TRUE +# and following variables are set: +# QTKEYCHAIN_INCLUDE_DIR +# QTKEYCHAIN_LIBRARY + +FIND_PATH(QTKEYCHAIN_INCLUDE_DIR keychain.h + PATHS + ${LIB_DIR}/include + "$ENV{LIB_DIR}/include" + $ENV{INCLUDE} + /usr/local/include + /usr/include + PATH_SUFFIXES qt5keychain qtkeychain +) + +FIND_LIBRARY(QTKEYCHAIN_LIBRARY NAMES qt5keychain qtkeychain + PATHS + ${LIB_DIR} + "$ENV{LIB_DIR}" + $ENV{LIB} + /usr/local/lib + /usr/lib +) + + +IF (QTKEYCHAIN_INCLUDE_DIR AND QTKEYCHAIN_LIBRARY) + SET(QTKEYCHAIN_FOUND TRUE) +ELSE() + SET(QTKEYCHAIN_FOUND FALSE) +ENDIF (QTKEYCHAIN_INCLUDE_DIR AND QTKEYCHAIN_LIBRARY) + +IF (QTKEYCHAIN_FOUND) + IF (NOT QTKEYCHAIN_FIND_QUIETLY) + MESSAGE(STATUS "Found QtKeychain: ${QTKEYCHAIN_LIBRARY}") + ENDIF (NOT QTKEYCHAIN_FIND_QUIETLY) +ELSE (QTKEYCHAIN_FOUND) + IF (QTKEYCHAIN_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find QtKeychain") + ENDIF (QTKEYCHAIN_FIND_REQUIRED) +ENDIF (QTKEYCHAIN_FOUND) diff --git a/debian/control.in b/debian/control.in index a76128c24e54..90732d037662 100644 --- a/debian/control.in +++ b/debian/control.in @@ -20,7 +20,7 @@ Build-Depends: libspatialite-dev, libsqlite3-dev, libspatialindex-dev, - qtbase5-dev, qttools5-dev-tools, qttools5-dev, qtscript5-dev, qtpositioning5-dev, + qtbase5-dev, qttools5-dev-tools, qttools5-dev, qtscript5-dev, qtpositioning5-dev, qt5keychain-dev, libqt5svg5-dev, libqt5xmlpatterns5-dev, libqt5webkit5-dev, libqt5opengl5-dev, libqt5sql5-sqlite, libqt5scintilla2-dev, libqwt-qt5-dev, libqca-qt5-2-dev, libqca-qt5-2-plugins, python3-dev, python3-all-dev, python3-sip, python3-sip-dev, diff --git a/images/images.qrc b/images/images.qrc index 9465a8adbe0f..d76789cac148 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -557,6 +557,7 @@ themes/default/mActionNewMap.svg themes/default/mActionMapSettings.svg themes/default/mActionLockExtent.svg + icons/qgis_icon.svg qgis_tips/symbol_levels.png diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 52b092ffe52c..6f5da3bd70c9 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -89,6 +89,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${QEXTSERIALPORT_INCLUDE_DIR} ${QSCINTILLA_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( diff --git a/python/auto_sip.blacklist b/python/auto_sip.blacklist index 8f1534dc9e09..8d8593c61ec6 100644 --- a/python/auto_sip.blacklist +++ b/python/auto_sip.blacklist @@ -95,7 +95,6 @@ core/qgspropertycollection.sip core/qgsprovidermetadata.sip core/qgsproviderregistry.sip core/qgspythonrunner.sip -core/qgsrectangle.sip core/qgsrelation.sip core/qgsrelationmanager.sip core/qgsrenderchecker.sip diff --git a/python/core/auth/qgsauthmanager.sip b/python/core/auth/qgsauthmanager.sip index 0e76112718df..32951fed8396 100644 --- a/python/core/auth/qgsauthmanager.sip +++ b/python/core/auth/qgsauthmanager.sip @@ -442,6 +442,19 @@ class QgsAuthManager : QObject QMutex *mutex(); signals: + + /** + * Signals emitted on password helper failure, + * mainly used in the tests to exit main application loop + */ + void passwordHelperFailure(); + + /** + * Signals emitted on password helper success, + * mainly used in the tests to exit main application loop + */ + void passwordHelperSuccess(); + /** * Custom logging signal to relay to console output and QgsMessageLog * @see QgsMessageLog @@ -449,7 +462,16 @@ class QgsAuthManager : QObject * @param tag Associated tag (title) * @param level Message log level */ - void messageOut( const QString& message, const QString& tag, QgsAuthManager::MessageLevel level = INFO ) const; + void messageOut( const QString& message, const QString& tag = QgsAuthManager::AUTH_MAN_TAG, QgsAuthManager::MessageLevel level = INFO ) const; + + /** + * Custom logging signal to inform the user about master password <-> password manager interactions + * @see QgsMessageLog + * @param message Message to send + * @param tag Associated tag (title) + * @param level Message log level + */ + void passwordHelperMessageOut( const QString &message, const QString &tag = QgsAuthManager::AUTH_MAN_TAG, QgsAuthManager::MessageLevel level = INFO ) const; /** * Emitted when a password has been verify (or not) diff --git a/python/core/core.sip b/python/core/core.sip index d484816762ab..b36ea2cdcb78 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -126,7 +126,6 @@ %Include qgsprovidermetadata.sip %Include qgsproviderregistry.sip %Include qgspythonrunner.sip -%Include qgsrectangle.sip %Include qgsrelation.sip %Include qgsrelationmanager.sip %Include qgsrenderchecker.sip @@ -357,6 +356,7 @@ %Include dxf/qgsdxfexport.sip %Include geometry/qgsabstractgeometry.sip +%Include geometry/qgsbox3d.sip %Include geometry/qgscircularstring.sip %Include geometry/qgscompoundcurve.sip %Include geometry/qgscurvepolygon.sip @@ -373,6 +373,7 @@ %Include geometry/qgsmultisurface.sip %Include geometry/qgspointv2.sip %Include geometry/qgspolygon.sip +%Include geometry/qgsrectangle.sip %Include geometry/qgssurface.sip %Include geometry/qgstriangle.sip %Include geometry/qgswkbtypes.sip diff --git a/python/core/geometry/qgsbox3d.sip b/python/core/geometry/qgsbox3d.sip new file mode 100644 index 000000000000..278253917e49 --- /dev/null +++ b/python/core/geometry/qgsbox3d.sip @@ -0,0 +1,206 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsbox3d.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsBox3d +{ +%Docstring + A 3-dimensional box composed of x, y, z coordinates. + + A box composed of x/y/z minimum and maximum values. It is often used to return the 3D + extent of a geometry or collection of geometries. + +.. versionadded:: 3.0 + \see QgsRectangle +%End + +%TypeHeaderCode +#include "qgsbox3d.h" +%End + public: + + QgsBox3d( double xmin = 0, double ymin = 0, double mZmin = 0, double xmax = 0, double ymax = 0, double mZmax = 0 ); +%Docstring + Constructor for QgsBox3D which accepts the ranges of x/y/z coordinates. +%End + + QgsBox3d( const QgsPointV2 &p1, const QgsPointV2 &p2 ); +%Docstring + Constructs a QgsBox3D from two points representing opposite corners of the box. + The box is normalized after construction. +%End + + void setXMinimum( double x ); +%Docstring + Sets the minimum x value. + \see xMinimum() + \see setXMaximum() +%End + + void setXMaximum( double x ); +%Docstring + Sets the maximum x value. + \see xMaximum() + \see setXMinimum() +%End + + double xMinimum() const; +%Docstring + Returns the minimum x value. + \see setXMinimum() + \see xMaximum() + :rtype: float +%End + + double xMaximum() const; +%Docstring + Returns the maximum x value. + \see setXMaximum() + \see xMinimum() + :rtype: float +%End + + void setYMinimum( double y ); +%Docstring + Sets the minimum y value. + \see yMinimum() + \see setYMaximum() +%End + + void setYMaximum( double y ); +%Docstring + Sets the maximum y value. + \see yMaximum() + \see setYMinimum() +%End + + double yMinimum() const; +%Docstring + Returns the minimum y value. + \see setYMinimum() + \see yMaximum() + :rtype: float +%End + + double yMaximum() const; +%Docstring + Returns the maximum y value. + \see setYMaximum() + \see yMinimum() + :rtype: float +%End + + void setZMinimum( double z ); +%Docstring + Sets the minimum z value. + \see zMinimum() + \see setZMaximum() +%End + + void setZMaximum( double z ); +%Docstring + Sets the maximum z value. + \see zMaximum() + \see setZMinimum() +%End + + double zMinimum() const; +%Docstring + Returns the minimum z value. + \see setZMinimum() + \see zMaximum() + :rtype: float +%End + + double zMaximum() const; +%Docstring + Returns the maximum z value. + \see setZMaximum() + \see zMinimum() + :rtype: float +%End + + void normalize(); +%Docstring + Normalize the box so it has non-negative width/height/depth. +%End + + double width() const; +%Docstring + Returns the width of the box. + \see height() + \see depth() + :rtype: float +%End + + double height() const; +%Docstring + Returns the height of the box. + \see width() + \see depth() + :rtype: float +%End + + double depth() const; +%Docstring + Returns the depth of the box. + \see width() + \see height() + :rtype: float +%End + + double volume() const; +%Docstring + Returns the volume of the box. + :rtype: float +%End + + QgsBox3d intersect( const QgsBox3d &other ) const; +%Docstring + Returns the intersection of this box and another 3D box. + :rtype: QgsBox3d +%End + + bool intersects( const QgsBox3d &other ) const; +%Docstring + Returns true if box intersects with another box. + :rtype: bool +%End + + bool contains( const QgsBox3d &other ) const; +%Docstring + Returns true when box contains other box. + :rtype: bool +%End + + bool contains( const QgsPointV2 &point ) const; +%Docstring + Returns true when box contains a point. + + If the point is a 2D point (no z-coordinate), then the containment test + will be performed on the x/y extent of the box only. + :rtype: bool +%End + + QgsRectangle toRectangle() const; +%Docstring + Converts the box to a 2D rectangle. + :rtype: QgsRectangle +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsbox3d.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/geometry/qgsrectangle.sip b/python/core/geometry/qgsrectangle.sip new file mode 100644 index 000000000000..a092039f326c --- /dev/null +++ b/python/core/geometry/qgsrectangle.sip @@ -0,0 +1,310 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsrectangle.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsRectangle +{ +%Docstring + A rectangle specified with double values. + + QgsRectangle is used to store a rectangle when double values are required. + Examples are storing a layer extent or the current view extent of a map + \see QgsBox3d +%End + +%TypeHeaderCode +#include "qgsrectangle.h" +%End + public: + QgsRectangle( double xMin = 0, double yMin = 0, double xMax = 0, double yMax = 0 ); +%Docstring +Constructor +%End + QgsRectangle( const QgsPoint &p1, const QgsPoint &p2 ); +%Docstring +Construct a rectangle from two points. The rectangle is normalized after construction. +%End + QgsRectangle( const QRectF &qRectF ); +%Docstring +Construct a rectangle from a QRectF. The rectangle is normalized after construction. +%End + QgsRectangle( const QgsRectangle &other ); +%Docstring +Copy constructor +%End + + void set( const QgsPoint &p1, const QgsPoint &p2 ); +%Docstring + Sets the rectangle from two QgsPoints. The rectangle is + normalised after construction. +%End + + void set( double mXmin, double mYmin, double mXmax, double mYmax ); +%Docstring + Sets the rectangle from four points. The rectangle is + normalised after construction. +%End + + void setXMinimum( double x ); +%Docstring + Set the minimum x value. +%End + + void setXMaximum( double x ); +%Docstring + Set the maximum x value. +%End + + void setYMinimum( double y ); +%Docstring + Set the minimum y value. +%End + + void setYMaximum( double y ); +%Docstring + Set the maximum y value. +%End + + void setMinimal(); +%Docstring + Set a rectangle so that min corner is at max + and max corner is at min. It is NOT normalized. +%End + + double xMaximum() const; +%Docstring + Returns the x maximum value (right side of rectangle). + :rtype: float +%End + + double xMinimum() const; +%Docstring + Returns the x minimum value (left side of rectangle). + :rtype: float +%End + + double yMaximum() const; +%Docstring + Returns the y maximum value (top side of rectangle). + :rtype: float +%End + + double yMinimum() const; +%Docstring + Returns the y minimum value (bottom side of rectangle). + :rtype: float +%End + + void normalize(); +%Docstring + Normalize the rectangle so it has non-negative width/height. +%End + + double width() const; +%Docstring + Returns the width of the rectangle. + \see height() + \see area() + :rtype: float +%End + + double height() const; +%Docstring + Returns the height of the rectangle. + \see width() + \see area() + :rtype: float +%End + + double area() const; +%Docstring + Returns the area of the rectangle. +.. versionadded:: 3.0 + \see width() + \see height() + \see perimeter() + :rtype: float +%End + + double perimeter() const; +%Docstring + Returns the perimeter of the rectangle. +.. versionadded:: 3.0 + \see area() + :rtype: float +%End + + QgsPoint center() const; +%Docstring + Returns the center point of the rectangle. + :rtype: QgsPoint +%End + + void scale( double scaleFactor, const QgsPoint *c = 0 ); +%Docstring + Scale the rectangle around its center point. +%End + + void scale( double scaleFactor, double centerX, double centerY ); +%Docstring + Scale the rectangle around its center point. +%End + + void grow( double delta ); +%Docstring + Grows the rectangle by the specified amount. +%End + + void include( const QgsPoint &p ); +%Docstring + Updates the rectangle to include the specified point. +%End + + QgsRectangle buffer( double width ); +%Docstring + Get rectangle enlarged by buffer. +.. versionadded:: 2.1 + :rtype: QgsRectangle +%End + + QgsRectangle intersect( const QgsRectangle *rect ) const; +%Docstring + Return the intersection with the given rectangle. + :rtype: QgsRectangle +%End + + bool intersects( const QgsRectangle &rect ) const; +%Docstring + Returns true when rectangle intersects with other rectangle. + :rtype: bool +%End + + bool contains( const QgsRectangle &rect ) const; +%Docstring + Return true when rectangle contains other rectangle. + :rtype: bool +%End + + bool contains( const QgsPoint &p ) const; +%Docstring + Return true when rectangle contains a point. + :rtype: bool +%End + + void combineExtentWith( const QgsRectangle &rect ); +%Docstring + Expand the rectangle so that covers both the original rectangle and the given rectangle. +%End + + void combineExtentWith( double x, double y ); +%Docstring + Expand the rectangle so that covers both the original rectangle and the given point. +%End + + bool isEmpty() const; +%Docstring + Returns true if the rectangle is empty. + An empty rectangle may still be non-null if it contains valid information (e.g. bounding box of a point). + :rtype: bool +%End + + bool isNull() const; +%Docstring + Test if the rectangle is null (all coordinates zero or after call to setMinimal()). + A null rectangle is also an empty rectangle. +.. versionadded:: 2.4 + :rtype: bool +%End + + QString asWktCoordinates() const; +%Docstring + Returns a string representation of the rectangle in WKT format. + :rtype: str +%End + + QString asWktPolygon() const; +%Docstring + Returns a string representation of the rectangle as a WKT Polygon. + :rtype: str +%End + + QRectF toRectF() const; +%Docstring + Returns a QRectF with same coordinates as the rectangle. + :rtype: QRectF +%End + + QString toString( int precision = 16 ) const; +%Docstring + Returns a string representation of form xmin,ymin : xmax,ymax + Coordinates will be truncated to the specified precision. + If the specified precision is less than 0, a suitable minimum precision is used. + :rtype: str +%End + + QString asPolygon() const; +%Docstring + Returns the rectangle as a polygon. + :rtype: str +%End + + bool operator==( const QgsRectangle &r1 ) const; +%Docstring + Comparison operator + :return: True if rectangles are equal + :rtype: bool +%End + + bool operator!=( const QgsRectangle &r1 ) const; +%Docstring + Comparison operator + :return: False if rectangles are equal + :rtype: bool +%End + +// QgsRectangle &operator=( const QgsRectangle &r1 ); + + void unionRect( const QgsRectangle &rect ); +%Docstring + Updates the rectangle to include another rectangle. +%End + + bool isFinite() const; +%Docstring + Returns true if the rectangle has finite boundaries. Will + return false if any of the rectangle boundaries are NaN or Inf. + :rtype: bool +%End + + void invert(); +%Docstring + Swap x/y coordinates in the rectangle. +%End + + QgsBox3d toBox3d( double zMin, double zMax ) const; +%Docstring + Converts the rectangle to a 3D box, with the specified + zMin and zMax z values. +.. versionadded:: 3.0 + :rtype: QgsBox3d +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsrectangle.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/qgsrectangle.sip b/python/core/qgsrectangle.sip deleted file mode 100644 index 12c7703754cb..000000000000 --- a/python/core/qgsrectangle.sip +++ /dev/null @@ -1,119 +0,0 @@ - -/** \ingroup core - * A rectangle specified with double values. - * - * QgsRectangle is used to store a rectangle when double values are required. - * Examples are storing a layer extent or the current view extent of a map - */ -class QgsRectangle -{ -%TypeHeaderCode -#include -%End - - public: - //! Constructor - QgsRectangle( double xmin = 0, double ymin = 0, double xmax = 0, double ymax = 0 ); - //! Construct a rectangle from two points. The rectangle is normalized after construction. - QgsRectangle( const QgsPoint & p1, const QgsPoint & p2 ); - //! Construct a rectangle from a QRectF. The rectangle is normalized after construction. - QgsRectangle( const QRectF & qRectF ); - //! Copy constructor - QgsRectangle( const QgsRectangle &other ); - //! Destructor - ~QgsRectangle(); - //! Set the rectangle from two QgsPoints. The rectangle is - //! normalised after construction. - void set( const QgsPoint& p1, const QgsPoint& p2 ); - //! Set the rectangle from four points. The rectangle is - //! normalised after construction. - void set( double xmin, double ymin, double xmax, double ymax ); - //! Set the minimum x value - void setXMinimum( double x ); - //! Set the maximum x value - void setXMaximum( double x ); - //! Set the minimum y value - void setYMinimum( double y ); - //! Set the maximum y value - void setYMaximum( double y ); - //! Set a rectangle so that min corner is at max - //! and max corner is at min. It is NOT normalized. - void setMinimal(); - //! Get the x maximum value (right side of rectangle) - double xMaximum() const; - //! Get the x minimum value (left side of rectangle) - double xMinimum() const; - //! Get the y maximum value (top side of rectangle) - double yMaximum() const; - //! Get the y minimum value (bottom side of rectangle) - double yMinimum() const; - //! Normalize the rectangle so it has non-negative width/height - void normalize(); - //! Width of the rectangle - double width() const; - //! Height of the rectangle - double height() const; - //! Center point of the rectangle - QgsPoint center() const; - //! Scale the rectangle around its center point - void scale( double scaleFactor, const QgsPoint *c = 0 ); - void scale( double scaleFactor, double centerX, double centerY ); - //! Grow the rectangle by the specified amount - void grow( double delta ); - /** Updates the rectangle to include the specified point */ - void include( const QgsPoint& p ); - /** Get rectangle enlarged by buffer. - * @note added in 2.1 */ - QgsRectangle buffer( double width ); - //! return the intersection with the given rectangle - QgsRectangle intersect( const QgsRectangle *rect ) const; - //! returns true when rectangle intersects with other rectangle - bool intersects( const QgsRectangle& rect ) const; - //! return true when rectangle contains other rectangle - bool contains( const QgsRectangle& rect ) const; - //! return true when rectangle contains a point - bool contains( const QgsPoint &p ) const; - //! expand the rectangle so that covers both the original rectangle and the given rectangle - void combineExtentWith( const QgsRectangle& rect ); - //! expand the rectangle so that covers both the original rectangle and the given point - void combineExtentWith( double x, double y ); - //! test if rectangle is empty. - //! Empty rectangle may still be non-null if it contains valid information (e.g. bounding box of a point) - bool isEmpty() const; - //! test if the rectangle is null (all coordinates zero or after call to setMinimal()). - //! Null rectangle is also an empty rectangle. - //! @note added in 2.4 - bool isNull() const; - //! returns string representation in Wkt form - QString asWktCoordinates() const; - //! returns string representation as WKT Polygon - QString asWktPolygon() const; - //! returns a QRectF with same coordinates. - QRectF toRectF() const; - /** - * returns a string representation of form xmin,ymin : xmax,ymax - * Coordinates will be truncated to the specified precision. - * If the specified precision is less than 0, a suitable minimum precision is used. - */ - QString toString( int precision = 16 ) const; - //! returns rectangle as a polygon - QString asPolygon() const; - /** Comparison operator - * @return True if rectangles are equal - */ - bool operator==( const QgsRectangle &r1 ) const; - /** Comparison operator - * @return False if rectangles are equal - */ - bool operator!=( const QgsRectangle &r1 ) const; - /** Updates rectangle to include passed argument */ - void unionRect( const QgsRectangle& rect ); - - /** Returns true if the rectangle has finite boundaries. Will - * return false if any of the rectangle boundaries are NaN or Inf. - */ - bool isFinite() const; - - //! swap x/y - void invert(); -}; diff --git a/python/core/qgssettings.sip b/python/core/qgssettings.sip index 437dff4e0b40..d221f5b08a42 100644 --- a/python/core/qgssettings.sip +++ b/python/core/qgssettings.sip @@ -33,8 +33,10 @@ class QgsSettings : QObject - Gui - Server - Plugins - - Misc - Auth + - App + - Providers + - Misc .. versionadded:: 3 %End @@ -169,17 +171,30 @@ Set the Global Settings QSettings storage file Adds prefix to the current group and starts reading from an array. Returns the size of the array. :rtype: int %End + + void beginWriteArray( const QString &prefix, int size = -1 ); +%Docstring + Adds prefix to the current group and starts writing an array of size size. + If size is -1 (the default), it is automatically determined based on the indexes of the entries written. +.. note:: + + This will completely shadow any existing array with the same name in the global settings +%End void endArray(); %Docstring Closes the array that was started using beginReadArray() or beginWriteArray(). %End + void setArrayIndex( int i ); %Docstring -remove(), and contains() will operate on the array entry at that index. + Sets the current array index to i. Calls to functions such as setValue(), value(), + remove(), and contains() will operate on the array entry at that index. %End + void setValue( const QString &key, const QVariant &value, const QgsSettings::Section section = QgsSettings::NoSection ); %Docstring -An optional Section argument can be used to set a value to a specific Section. + Sets the value of setting key to value. If the key already exists, the previous value is overwritten. + An optional Section argument can be used to set a value to a specific Section. %End SIP_PYOBJECT value( const QString &key, const QVariant &defaultValue = QVariant(), @@ -205,9 +220,11 @@ An optional Section argument can be used to set a value to a specific Section. sipIsErr = !sipRes; %End + bool contains( const QString &key, const QgsSettings::Section section = QgsSettings::NoSection ) const; %Docstring -If a group is set using beginGroup(), key is taken to be relative to that group. + Returns true if there exists a setting called key; returns false otherwise. + If a group is set using beginGroup(), key is taken to be relative to that group. :rtype: bool %End QString fileName() const; @@ -215,9 +232,13 @@ If a group is set using beginGroup(), key is taken to be relative to that group. Returns the path where settings written using this QSettings object are stored. :rtype: str %End + void sync(); %Docstring -loop at regular intervals, so you normally don't need to call it yourself. + Writes any unsaved changes to permanent storage, and reloads any settings that have been + changed in the meantime by another application. + This function is called automatically from QSettings's destructor and by the event + loop at regular intervals, so you normally don't need to call it yourself. %End void remove( const QString &key ); %Docstring diff --git a/python/plugins/processing/algs/qgis/BarPlot.py b/python/plugins/processing/algs/qgis/BarPlot.py index b3b18a71b59b..16818e18d318 100644 --- a/python/plugins/processing/algs/qgis/BarPlot.py +++ b/python/plugins/processing/algs/qgis/BarPlot.py @@ -27,7 +27,7 @@ import plotly as plt import plotly.graph_objs as go -import numpy as np + from qgis.core import (QgsApplication) from processing.core.parameters import ParameterTable @@ -65,7 +65,7 @@ def defineCharacteristics(self): self.addParameter(ParameterTableField(self.NAME_FIELD, self.tr('Category name field'), self.INPUT, - ParameterTableField.DATA_TYPE_NUMBER)) + ParameterTableField.DATA_TYPE_ANY)) self.addParameter(ParameterTableField(self.VALUE_FIELD, self.tr('Value field'), self.INPUT, @@ -81,9 +81,10 @@ def processAlgorithm(self, feedback): output = self.getOutputValue(self.OUTPUT) - values = vector.values(layer, namefieldname, valuefieldname) + values = vector.values(layer, valuefieldname) + + x_var = [i[namefieldname] for i in layer.getFeatures()] - ind = np.arange(len(values[namefieldname])) - data = [go.Bar(x=ind, + data = [go.Bar(x=x_var, y=values[valuefieldname])] plt.offline.plot(data, filename=output, auto_open=False) diff --git a/python/plugins/processing/algs/qgis/BoxPlot.py b/python/plugins/processing/algs/qgis/BoxPlot.py new file mode 100644 index 000000000000..215d5ab6f33e --- /dev/null +++ b/python/plugins/processing/algs/qgis/BoxPlot.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + BarPlot.py + --------------------- + Date : March 2015 + Copyright : (C) 2017 by Matteo Ghetta + Email : matteo dot ghetta at gmail dot com +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +__author__ = 'Matteo Ghetta' +__date__ = 'March 2017' +__copyright__ = '(C) 2017, Matteo Ghetta' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + +import plotly as plt +import plotly.graph_objs as go + +from qgis.core import (QgsApplication) +from processing.core.parameters import ParameterTable +from processing.core.parameters import ParameterTableField +from processing.core.parameters import ParameterSelection +from processing.core.GeoAlgorithm import GeoAlgorithm +from processing.core.outputs import OutputHTML +from processing.tools import vector +from processing.tools import dataobjects + + +class BoxPlot(GeoAlgorithm): + + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' + NAME_FIELD = 'NAME_FIELD' + VALUE_FIELD = 'VALUE_FIELD' + MSD = 'MSD' + + def icon(self): + return QgsApplication.getThemeIcon("/providerQgis.svg") + + def svgIconPath(self): + return QgsApplication.iconPath("providerQgis.svg") + + def group(self): + return self.tr('Graphics') + + def name(self): + return 'boxplot' + + def displayName(self): + return self.tr('Box plot') + + def defineCharacteristics(self): + self.addParameter(ParameterTable(self.INPUT, self.tr('Input table'))) + self.addParameter(ParameterTableField(self.NAME_FIELD, + self.tr('Category name field'), + self.INPUT, + ParameterTableField.DATA_TYPE_ANY)) + self.addParameter(ParameterTableField(self.VALUE_FIELD, + self.tr('Value field'), + self.INPUT, + ParameterTableField.DATA_TYPE_NUMBER)) + msd = [self.tr('Show Mean'), + self.tr('Show Standard Deviation'), + self.tr('Don\'t show Mean and Standard Deviation') + ] + self.addParameter(ParameterSelection( + self.MSD, + self.tr('Additional Statistic Lines'), + msd, default=0)) + + self.addOutput(OutputHTML(self.OUTPUT, self.tr('Box plot'))) + + def processAlgorithm(self, feedback): + layer = dataobjects.getLayerFromString( + self.getParameterValue(self.INPUT)) + namefieldname = self.getParameterValue(self.NAME_FIELD) + valuefieldname = self.getParameterValue(self.VALUE_FIELD) + + output = self.getOutputValue(self.OUTPUT) + + values = vector.values(layer, valuefieldname) + + x_var = [i[namefieldname] for i in layer.getFeatures()] + + msdIndex = self.getParameterValue(self.MSD) + msd = True + + if msdIndex == 1: + msd = 'sd' + elif msdIndex == 2: + msd = False + + data = [go.Box( + x=x_var, + y=values[valuefieldname], + boxmean=msd)] + + plt.offline.plot(data, filename=output, auto_open=False) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index fb0330b8b196..4391645617e3 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -265,10 +265,13 @@ def getAlgs(self): from .MeanAndStdDevPlot import MeanAndStdDevPlot from .BarPlot import BarPlot from .PolarPlot import PolarPlot + from .BoxPlot import BoxPlot + from .VectorLayerScatterplot3D import VectorLayerScatterplot3D algs.extend([VectorLayerHistogram(), RasterLayerHistogram(), VectorLayerScatterplot(), MeanAndStdDevPlot(), - BarPlot(), PolarPlot()]) + BarPlot(), PolarPlot(), BoxPlot(), + VectorLayerScatterplot3D()]) # to store algs added by 3rd party plugins as scripts folder = os.path.join(os.path.dirname(__file__), 'scripts') diff --git a/python/plugins/processing/algs/qgis/VectorLayerScatterplot3D.py b/python/plugins/processing/algs/qgis/VectorLayerScatterplot3D.py new file mode 100644 index 000000000000..69363591590e --- /dev/null +++ b/python/plugins/processing/algs/qgis/VectorLayerScatterplot3D.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + EquivalentNumField.py + --------------------- + Date : January 2013 + Copyright : (C) 2013 by Victor Olaya + Email : volayaf at gmail dot com +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +__author__ = 'Victor Olaya' +__date__ = 'January 2013' +__copyright__ = '(C) 2013, Victor Olaya' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + +import plotly as plt +import plotly.graph_objs as go + +from qgis.core import (QgsApplication) +from processing.core.GeoAlgorithm import GeoAlgorithm +from processing.core.parameters import ParameterVector +from processing.core.parameters import ParameterTableField +from processing.core.outputs import OutputHTML + +from processing.tools import vector +from processing.tools import dataobjects + + +class VectorLayerScatterplot3D(GeoAlgorithm): + + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' + XFIELD = 'XFIELD' + YFIELD = 'YFIELD' + ZFIELD = 'ZFIELD' + + def icon(self): + return QgsApplication.getThemeIcon("/providerQgis.svg") + + def svgIconPath(self): + return QgsApplication.iconPath("providerQgis.svg") + + def group(self): + return self.tr('Graphics') + + def name(self): + return 'scatter3dplot' + + def displayName(self): + return self.tr('Vector layer scatterplot 3D') + + def defineCharacteristics(self): + self.addParameter(ParameterVector(self.INPUT, + self.tr('Input layer'))) + self.addParameter(ParameterTableField(self.XFIELD, + self.tr('X attribute'), + self.INPUT, + ParameterTableField.DATA_TYPE_NUMBER)) + self.addParameter(ParameterTableField(self.YFIELD, + self.tr('Y attribute'), + self.INPUT, + ParameterTableField.DATA_TYPE_NUMBER)) + self.addParameter(ParameterTableField(self.ZFIELD, + self.tr('Z attribute'), + self.INPUT, + ParameterTableField.DATA_TYPE_NUMBER)) + + self.addOutput(OutputHTML(self.OUTPUT, self.tr('Scatterplot 3D'))) + + def processAlgorithm(self, feedback): + + layer = dataobjects.getLayerFromString( + self.getParameterValue(self.INPUT)) + xfieldname = self.getParameterValue(self.XFIELD) + yfieldname = self.getParameterValue(self.YFIELD) + zfieldname = self.getParameterValue(self.ZFIELD) + + output = self.getOutputValue(self.OUTPUT) + + values = vector.values(layer, xfieldname, yfieldname, zfieldname) + + data = [go.Scatter3d( + x=values[xfieldname], + y=values[yfieldname], + z=values[zfieldname], + mode='markers')] + + plt.offline.plot(data, filename=output, auto_open=False) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 242f2bd76181..51c15bc4c345 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -178,6 +178,7 @@ SET(QGIS_APP_SRCS qgssettingstree.cpp qgsvariantdelegate.cpp + qgscrashdialog.cpp ) SET (QGIS_APP_MOC_HDRS @@ -348,6 +349,7 @@ SET (QGIS_APP_MOC_HDRS qgssettingstree.h qgsvariantdelegate.h + qgscrashdialog.h ) SET (WITH_QWTPOLAR FALSE CACHE BOOL "Determines whether QwtPolar should be built") @@ -578,6 +580,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${GDAL_INCLUDE_DIR} ${QWTPOLAR_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) IF(ENABLE_MODELTEST) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 5bd797c58fb5..84517ef6fd77 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -110,6 +110,7 @@ // QGIS Specific Includes // +#include "qgscrashdialog.h" #include "qgisapp.h" #include "qgisappinterface.h" #include "qgisappstylesheet.h" @@ -12254,6 +12255,8 @@ void QgisApp::masterPasswordSetup() { connect( QgsAuthManager::instance(), &QgsAuthManager::messageOut, this, &QgisApp::authMessageOut ); + connect( QgsAuthManager::instance(), &QgsAuthManager::passwordHelperMessageOut, + this, &QgisApp::authMessageOut ); connect( QgsAuthManager::instance(), &QgsAuthManager::authDatabaseEraseRequested, this, &QgisApp::eraseAuthenticationDatabase ); } @@ -12289,12 +12292,17 @@ void QgisApp::eraseAuthenticationDatabase() void QgisApp::authMessageOut( const QString &message, const QString &authtag, QgsAuthManager::MessageLevel level ) { - // only if main window is active window + // Use system notifications if the main window is not the active one, + // push message to the message bar if the main window is active if ( qApp->activeWindow() != this ) - return; - - int levelint = static_cast< int >( level ); - messageBar()->pushMessage( authtag, message, static_cast< QgsMessageBar::MessageLevel >( levelint ), 7 ); + { + showSystemNotification( tr( "QGIS Authentication" ), message ); + } + else + { + int levelint = static_cast< int >( level ); + messageBar()->pushMessage( authtag, message, static_cast< QgsMessageBar::MessageLevel >( levelint ), 7 ); + } } void QgisApp::completeInitialization() @@ -12498,6 +12506,9 @@ void QgisApp::transactionGroupCommitError( const QString &error ) #ifdef Q_OS_WIN LONG WINAPI QgisApp::qgisCrashDump( struct _EXCEPTION_POINTERS *ExceptionInfo ) { + // Crash dump creation will be move to a new class in the near future. + +#if 0 QString dumpName = QDir::toNativeSeparators( QString( "%1\\qgis-%2-%3-%4-%5.dmp" ) .arg( QDir::tempPath() ) @@ -12531,8 +12542,18 @@ LONG WINAPI QgisApp::qgisCrashDump( struct _EXCEPTION_POINTERS *ExceptionInfo ) { msg = QObject::tr( "creation of minidump to %1 failed (%2)" ).arg( dumpName ).arg( GetLastError(), 0, 16 ); } +#endif - QMessageBox::critical( 0, QObject::tr( "Crash dumped" ), msg ); + QgsCrashDialog dlg( QApplication::activeWindow() ); + if ( dlg.exec() ) + { + QStringList arguments; + arguments = QCoreApplication::arguments(); + QString path = arguments.at( 0 ); + arguments.removeFirst(); + arguments << QgsProject::instance()->fileName(); + QProcess::startDetached( path, arguments, QDir::toNativeSeparators( QCoreApplication::applicationDirPath() ) ); + } return EXCEPTION_EXECUTE_HANDLER; } diff --git a/src/app/qgscrashdialog.cpp b/src/app/qgscrashdialog.cpp new file mode 100644 index 000000000000..ff3fc7e7f250 --- /dev/null +++ b/src/app/qgscrashdialog.cpp @@ -0,0 +1,32 @@ +/*************************************************************************** + qgscrashdialog.cpp - QgsCrashDialog + + --------------------- + begin : 11.4.2017 + copyright : (C) 2017 by Nathan Woodrow + email : woodrow.nathan@gmail.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 "qgscrashdialog.h" + +QgsCrashDialog::QgsCrashDialog( QWidget *parent ) + : QDialog( parent ) +{ + setupUi( this ); + setWindowTitle( tr( "Oh. Snap!" ) ); + + mCrashHeaderMessage->setText( tr( "Ouch!" ) ); + mCrashMessage->setText( tr("Sorry it looks like QGIS crashed. \nSomething unexpected happened that we didn't handle correctly.")); + + mHelpLabel->setText( tr( "Keen to help us fix it?
Follow the steps to help devs." ) ); + mHelpLabel->setTextInteractionFlags( Qt::TextBrowserInteraction ); + mHelpLabel->setOpenExternalLinks( true ); +} diff --git a/src/app/qgscrashdialog.h b/src/app/qgscrashdialog.h new file mode 100644 index 000000000000..51fa02e0aeea --- /dev/null +++ b/src/app/qgscrashdialog.h @@ -0,0 +1,38 @@ +/*************************************************************************** + qgscrashdialog.h - QgsCrashDialog + + --------------------- + begin : 11.4.2017 + copyright : (C) 2017 by Nathan Woodrow + email : woodrow.nathan@gmail.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 QGSCRASHDIALOG_H +#define QGSCRASHDIALOG_H + +#include +#include "ui_qgscrashdialog.h" +#include "qgis_app.h" + +/** + * A dialog to show a nicer crash dialog to the user. + */ +class APP_EXPORT QgsCrashDialog : public QDialog, private Ui::QgsCrashDialog +{ + Q_OBJECT + public: + + /** + * A dialog to show a nicer crash dialog to the user. + */ + QgsCrashDialog( QWidget *parent = nullptr ); +}; + +#endif // QGSCRASHDIALOG_H diff --git a/src/app/qgsvectorlayerproperties.cpp b/src/app/qgsvectorlayerproperties.cpp index 3ffe60858ee1..ea3f533a367b 100644 --- a/src/app/qgsvectorlayerproperties.cpp +++ b/src/app/qgsvectorlayerproperties.cpp @@ -292,6 +292,12 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( ) ); + QString myStyle = QgsApplication::reportStyleSheet(); + teMetadataViewer->clear(); + teMetadataViewer->document()->setDefaultStyleSheet( myStyle ); + teMetadataViewer->setHtml( htmlMetadata() ); + mMetadataFilled = true; + QgsSettings settings; // if dialog hasn't been opened/closed yet, default to Styles tab, which is used most often // this will be read by restoreOptionsBaseUi() @@ -383,7 +389,6 @@ void QgsVectorLayerProperties::syncToLayer() // populate the general information mLayerOrigNameLineEdit->setText( mLayer->originalName() ); txtDisplayName->setText( mLayer->name() ); - txtLayerSource->setText( mLayer->publicSource() ); pbnQueryBuilder->setWhatsThis( tr( "This button opens the query " "builder and allows you to create a subset of features to display on " "the map canvas rather than displaying all features in the layer" ) ); @@ -562,19 +567,58 @@ void QgsVectorLayerProperties::apply() } //layer title and abstract + if ( mLayer->shortName() != mLayerShortNameLineEdit->text() ) + mMetadataFilled = false; mLayer->setShortName( mLayerShortNameLineEdit->text() ); + + if ( mLayer->title() != mLayerTitleLineEdit->text() ) + mMetadataFilled = false; mLayer->setTitle( mLayerTitleLineEdit->text() ); + + if ( mLayer->abstract() != mLayerAbstractTextEdit->toPlainText() ) + mMetadataFilled = false; mLayer->setAbstract( mLayerAbstractTextEdit->toPlainText() ); + + if ( mLayer->keywordList() != mLayerKeywordListLineEdit->text() ) + mMetadataFilled = false; mLayer->setKeywordList( mLayerKeywordListLineEdit->text() ); + + if ( mLayer->dataUrl() != mLayerDataUrlLineEdit->text() ) + mMetadataFilled = false; mLayer->setDataUrl( mLayerDataUrlLineEdit->text() ); + + if ( mLayer->dataUrlFormat() != mLayerDataUrlFormatComboBox->currentText() ) + mMetadataFilled = false; mLayer->setDataUrlFormat( mLayerDataUrlFormatComboBox->currentText() ); + //layer attribution and metadataUrl + if ( mLayer->attribution() != mLayerAttributionLineEdit->text() ) + mMetadataFilled = false; mLayer->setAttribution( mLayerAttributionLineEdit->text() ); + + if ( mLayer->attributionUrl() != mLayerAttributionUrlLineEdit->text() ) + mMetadataFilled = false; mLayer->setAttributionUrl( mLayerAttributionUrlLineEdit->text() ); + + if ( mLayer->metadataUrl() != mLayerMetadataUrlLineEdit->text() ) + mMetadataFilled = false; mLayer->setMetadataUrl( mLayerMetadataUrlLineEdit->text() ); + + if ( mLayer->metadataUrlType() != mLayerMetadataUrlTypeComboBox->currentText() ) + mMetadataFilled = false; mLayer->setMetadataUrlType( mLayerMetadataUrlTypeComboBox->currentText() ); + + if ( mLayer->metadataUrlFormat() != mLayerMetadataUrlFormatComboBox->currentText() ) + mMetadataFilled = false; mLayer->setMetadataUrlFormat( mLayerMetadataUrlFormatComboBox->currentText() ); + + // LegendURL + if ( mLayer->legendUrl() != mLayerLegendUrlLineEdit->text() ) + mMetadataFilled = false; mLayer->setLegendUrl( mLayerLegendUrlLineEdit->text() ); + + if ( mLayer->legendUrlFormat() != mLayerLegendUrlFormatComboBox->currentText() ) + mMetadataFilled = false; mLayer->setLegendUrlFormat( mLayerLegendUrlFormatComboBox->currentText() ); //layer simplify drawing configuration @@ -701,7 +745,7 @@ void QgsVectorLayerProperties::on_pbnIndex_clicked() } } -QString QgsVectorLayerProperties::metadata() +QString QgsVectorLayerProperties::htmlMetadata() { return mLayer->htmlMetadata(); } @@ -714,6 +758,7 @@ void QgsVectorLayerProperties::on_mLayerOrigNameLineEdit_textEdited( const QStri void QgsVectorLayerProperties::on_mCrsSelector_crsChanged( const QgsCoordinateReferenceSystem &crs ) { mLayer->setCrs( crs ); + mMetadataFilled = false; } void QgsVectorLayerProperties::loadDefaultStyle_clicked() @@ -1310,14 +1355,12 @@ void QgsVectorLayerProperties::on_pbnUpdateExtents_clicked() void QgsVectorLayerProperties::mOptionsStackedWidget_CurrentChanged( int indx ) { - if ( indx != mOptStackedWidget->indexOf( mOptsPage_Metadata ) || mMetadataFilled ) + if ( indx != mOptStackedWidget->indexOf( mOptsPage_Information ) || mMetadataFilled ) return; //set the metadata contents (which can be expensive) - QString myStyle = QgsApplication::reportStyleSheet(); - teMetadata->clear(); - teMetadata->document()->setDefaultStyleSheet( myStyle ); - teMetadata->setHtml( metadata() ); + teMetadataViewer->clear(); + teMetadataViewer->setHtml( htmlMetadata() ); mMetadataFilled = true; } diff --git a/src/app/qgsvectorlayerproperties.h b/src/app/qgsvectorlayerproperties.h index b1afa3507a7b..57e593b8d4ae 100644 --- a/src/app/qgsvectorlayerproperties.h +++ b/src/app/qgsvectorlayerproperties.h @@ -81,7 +81,7 @@ class APP_EXPORT QgsVectorLayerProperties : public QgsOptionsDialogBase, private void syncToLayer(); //! Get metadata about the layer in nice formatted html - QString metadata(); + QString htmlMetadata(); //! Slot to update layer display name as original is edited void on_mLayerOrigNameLineEdit_textEdited( const QString &text ); diff --git a/src/auth/basic/CMakeLists.txt b/src/auth/basic/CMakeLists.txt index cca6c9d43a4d..889346993066 100644 --- a/src/auth/basic/CMakeLists.txt +++ b/src/auth/basic/CMakeLists.txt @@ -24,6 +24,7 @@ INCLUDE_DIRECTORIES ( ) INCLUDE_DIRECTORIES (SYSTEM ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) INCLUDE_DIRECTORIES ( ../../gui diff --git a/src/auth/identcert/CMakeLists.txt b/src/auth/identcert/CMakeLists.txt index 0819efd5014c..9391efdec58e 100644 --- a/src/auth/identcert/CMakeLists.txt +++ b/src/auth/identcert/CMakeLists.txt @@ -24,6 +24,7 @@ INCLUDE_DIRECTORIES ( ) INCLUDE_DIRECTORIES (SYSTEM ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) INCLUDE_DIRECTORIES ( ../../gui diff --git a/src/auth/pkipaths/CMakeLists.txt b/src/auth/pkipaths/CMakeLists.txt index 093078f347a6..e7c33bad30eb 100644 --- a/src/auth/pkipaths/CMakeLists.txt +++ b/src/auth/pkipaths/CMakeLists.txt @@ -24,6 +24,7 @@ INCLUDE_DIRECTORIES ( ) INCLUDE_DIRECTORIES (SYSTEM ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) INCLUDE_DIRECTORIES ( ../../gui diff --git a/src/auth/pkipkcs12/CMakeLists.txt b/src/auth/pkipkcs12/CMakeLists.txt index 39009b860b2f..d1ed476acb9b 100644 --- a/src/auth/pkipkcs12/CMakeLists.txt +++ b/src/auth/pkipkcs12/CMakeLists.txt @@ -24,6 +24,7 @@ INCLUDE_DIRECTORIES ( ) INCLUDE_DIRECTORIES (SYSTEM ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) INCLUDE_DIRECTORIES ( ../../gui diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index afd9c459a785..6ab708091199 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -209,7 +209,6 @@ SET(QGIS_CORE_SRCS qgsprovidermetadata.cpp qgsproviderregistry.cpp qgspythonrunner.cpp - qgsrectangle.cpp qgsrelation.cpp qgsrelationmanager.cpp qgsrenderchecker.cpp @@ -368,6 +367,7 @@ SET(QGIS_CORE_SRCS raster/qgshillshaderenderer.cpp geometry/qgsabstractgeometry.cpp + geometry/qgsbox3d.cpp geometry/qgscircularstring.cpp geometry/qgscompoundcurve.cpp geometry/qgscurvepolygon.cpp @@ -388,6 +388,7 @@ SET(QGIS_CORE_SRCS geometry/qgsmultisurface.cpp geometry/qgspointv2.cpp geometry/qgspolygon.cpp + geometry/qgsrectangle.cpp geometry/qgstriangle.cpp geometry/qgswkbptr.cpp geometry/qgswkbtypes.cpp @@ -760,7 +761,6 @@ SET(QGIS_CORE_HDRS qgsprovidermetadata.h qgsproviderregistry.h qgspythonrunner.h - qgsrectangle.h qgsrenderchecker.h qgsrendercontext.h qgsruntimeprofiler.h @@ -912,6 +912,7 @@ SET(QGIS_CORE_HDRS layertree/qgslayertreeutils.h geometry/qgsabstractgeometry.h + geometry/qgsbox3d.h geometry/qgscircularstring.h geometry/qgscompoundcurve.h geometry/qgscurvepolygon.h @@ -932,6 +933,7 @@ SET(QGIS_CORE_HDRS geometry/qgsmultisurface.h geometry/qgspointv2.h geometry/qgspolygon.h + geometry/qgsrectangle.h geometry/qgstriangle.h geometry/qgssurface.h geometry/qgswkbptr.h @@ -980,6 +982,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${SQLITE3_INCLUDE_DIR} ${SPATIALITE_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) #for PAL classes @@ -1071,7 +1074,7 @@ TARGET_LINK_LIBRARIES(qgis_core ${OPTIONAL_QTWEBKIT} ${QT_QTSQL_LIBRARY} ${QCA_LIBRARY} - + ${QTKEYCHAIN_LIBRARY} ${PROJ_LIBRARY} ${GEOS_LIBRARY} ${GDAL_LIBRARY} diff --git a/src/core/auth/qgsauthmanager.cpp b/src/core/auth/qgsauthmanager.cpp index 9e4efd810748..cd2673f6d677 100644 --- a/src/core/auth/qgsauthmanager.cpp +++ b/src/core/auth/qgsauthmanager.cpp @@ -37,6 +37,10 @@ #include #endif +// QtKeyChain library +#include "keychain.h" + +// QGIS includes #include "qgsapplication.h" #include "qgsauthcertutils.h" #include "qgsauthcrypto.h" @@ -45,6 +49,8 @@ #include "qgsauthmethodregistry.h" #include "qgscredentials.h" #include "qgslogger.h" +#include "qgsmessagelog.h" +#include "qgssettings.h" QgsAuthManager *QgsAuthManager::sInstance = nullptr; @@ -59,6 +65,24 @@ const QString QgsAuthManager::AUTH_MAN_TAG = QObject::tr( "Authentication Manage const QString QgsAuthManager::AUTH_CFG_REGEX = QStringLiteral( "authcfg=([a-z]|[A-Z]|[0-9]){7}" ); +const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME( "QGIS-Master-Password" ); +const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_FOLDER_NAME( "QGIS" ); + +#if defined(Q_OS_MAC) +const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Keychain" ); +static const QString sDescription = QObject::tr( "Master Password <-> KeyChain storage plugin. Store and retrieve your master password in your KeyChain" ); +#elif defined(Q_OS_WIN) +const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" ); +static const QString sDescription = QObject::tr( "Master Password <-> Password Manager storage plugin. Store and retrieve your master password in your Password Manager" ); +#elif defined(Q_OS_LINUX) +const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Wallet/KeyRing" ); +static const QString sDescription = QObject::tr( "Master Password <-> Wallet/KeyRing storage plugin. Store and retrieve your master password in your Wallet/KeyRing" ); +#else +const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" ); +static const QString sDescription = QObject::tr( "Master Password <-> KeyChain storage plugin. Store and retrieve your master password in your Wallet/KeyChain/Password Manager" ); +#endif + + QgsAuthManager *QgsAuthManager::instance() { if ( !sInstance ) @@ -2719,6 +2743,15 @@ const QByteArray QgsAuthManager::getTrustedCaCertsPemText() return capem; } +bool QgsAuthManager::passwordHelperSync() +{ + if ( masterPasswordIsSet( ) ) + { + return passwordHelperWrite( mMasterPass ); + } + return false; +} + ////////////////// Certificate calls - end /////////////////////// @@ -2821,6 +2854,12 @@ QgsAuthManager::QgsAuthManager() , mScheduledDbEraseRequestCount( 0 ) , mMutex( nullptr ) , mIgnoredSslErrorsCache( QHash >() ) + , mPasswordHelperVerificationError( false ) + , mPasswordHelperErrorMessage( "" ) + , mPasswordHelperErrorCode( QKeychain::NoError ) + , mPasswordHelperLoggingEnabled( false ) + , mPasswordHelperFailedInit( false ) + { mMutex = new QMutex( QMutex::Recursive ); connect( this, &QgsAuthManager::messageOut, @@ -2847,20 +2886,243 @@ QgsAuthManager::~QgsAuthManager() QSqlDatabase::removeDatabase( QStringLiteral( "authentication.configs" ) ); } + +QString QgsAuthManager::passwordHelperName() const +{ + return tr( "Password Helper" ); +} + + +void QgsAuthManager::passwordHelperLog( const QString &msg ) const +{ + if ( passwordHelperLoggingEnabled( ) ) + { + QgsMessageLog::logMessage( msg, passwordHelperName() ); + } +} + +bool QgsAuthManager::passwordHelperDelete() +{ + passwordHelperLog( tr( "Opening %1 for DELETE ..." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) ); + bool result; + QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME ); + QgsSettings settings; + job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool( ) ); + job.setAutoDelete( false ); + job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); + QEventLoop loop; + job.connect( &job, SIGNAL( finished( QKeychain::Job * ) ), &loop, SLOT( quit() ) ); + job.start(); + loop.exec(); + if ( job.error() ) + { + mPasswordHelperErrorCode = job.error(); + mPasswordHelperErrorMessage = tr( "Delete password failed: %1." ).arg( job.errorString() ); + // Signals used in the tests to exit main application loop + emit passwordHelperFailure(); + result = false; + } + else + { + // Signals used in the tests to exit main application loop + emit passwordHelperSuccess(); + result = true; + } + passwordHelperProcessError(); + return result; +} + +QString QgsAuthManager::passwordHelperRead() +{ + // Retrieve it! + QString password( "" ); + passwordHelperLog( tr( "Opening %1 for READ ..." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) ); + QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME ); + QgsSettings settings; + job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool( ) ); + job.setAutoDelete( false ); + job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); + QEventLoop loop; + job.connect( &job, SIGNAL( finished( QKeychain::Job * ) ), &loop, SLOT( quit() ) ); + job.start(); + loop.exec(); + if ( job.error() ) + { + mPasswordHelperErrorCode = job.error(); + mPasswordHelperErrorMessage = tr( "Retrieving password from your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() ); + // Signals used in the tests to exit main application loop + emit passwordHelperFailure(); + } + else + { + password = job.textData(); + // Password is there but it is empty, treat it like if it was not found + if ( password.isEmpty() ) + { + mPasswordHelperErrorCode = QKeychain::EntryNotFound; + mPasswordHelperErrorMessage = tr( "Empty password retrieved from your %1." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ); + // Signals used in the tests to exit main application loop + emit passwordHelperFailure(); + } + else + { + // Signals used in the tests to exit main application loop + emit passwordHelperSuccess(); + } + } + passwordHelperProcessError(); + return password; +} + +bool QgsAuthManager::passwordHelperWrite( const QString &password ) +{ + Q_ASSERT( !password.isEmpty() ); + bool result; + passwordHelperLog( tr( "Opening %1 for WRITE ..." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) ); + QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME ); + QgsSettings settings; + job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool( ) ); + job.setAutoDelete( false ); + job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); + job.setTextData( password ); + QEventLoop loop; + job.connect( &job, SIGNAL( finished( QKeychain::Job * ) ), &loop, SLOT( quit() ) ); + job.start(); + loop.exec(); + if ( job.error() ) + { + mPasswordHelperErrorCode = job.error(); + mPasswordHelperErrorMessage = tr( "Storing password in your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() ); + // Signals used in the tests to exit main application loop + emit passwordHelperFailure(); + result = false; + } + else + { + passwordHelperClearErrors(); + // Signals used in the tests to exit main application loop + emit passwordHelperSuccess(); + result = true; + } + passwordHelperProcessError( ); + return result; +} + +bool QgsAuthManager::passwordHelperEnabled() const +{ + // Does the user want to store the password in the wallet? + QgsSettings settings; + return settings.value( QStringLiteral( "use_password_helper" ), true, QgsSettings::Section::Auth ).toBool( ); +} + +void QgsAuthManager::setPasswordHelperEnabled( const bool enabled ) +{ + QgsSettings settings; + settings.setValue( QStringLiteral( "use_password_helper" ), enabled, QgsSettings::Section::Auth ); + emit messageOut( enabled ? tr( "Your %1 will be used from now on to store and retrieve the master password." ) + .arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) : + tr( "Your %1 will not be used anymore to store and retrieve the master password." ) + .arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) ); +} + +bool QgsAuthManager::passwordHelperLoggingEnabled() const +{ + // Does the user want to store the password in the wallet? + QgsSettings settings; + return settings.value( QStringLiteral( "password_helper_logging" ), false, QgsSettings::Section::Auth ).toBool( ); +} + +void QgsAuthManager::setPasswordHelperLoggingEnabled( const bool enabled ) +{ + QgsSettings settings; + settings.setValue( QStringLiteral( "password_helper_logging" ), enabled, QgsSettings::Section::Auth ); +} + +void QgsAuthManager::passwordHelperClearErrors() +{ + mPasswordHelperErrorCode = QKeychain::NoError; + mPasswordHelperErrorMessage = ""; +} + +void QgsAuthManager::passwordHelperProcessError() +{ + if ( mPasswordHelperErrorCode == QKeychain::AccessDenied || + mPasswordHelperErrorCode == QKeychain::AccessDeniedByUser || + mPasswordHelperErrorCode == QKeychain::NoBackendAvailable || + mPasswordHelperErrorCode == QKeychain::NotImplemented ) + { + // If the error is permanent or the user denied access to the wallet + // we also want to disable the wallet system to prevent annoying + // notification on each subsequent access. + setPasswordHelperEnabled( false ); + mPasswordHelperErrorMessage = tr( "There was an error and integration with your %1 system has been disabled. " + "You can re-enable it at any time through the \"Utilities\" menu " + "in the Authentication pane of the options dialog. %2" ) + .arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) + .arg( mPasswordHelperErrorMessage ); + } + if ( mPasswordHelperErrorCode != QKeychain::NoError ) + { + // We've got an error from the wallet + passwordHelperLog( tr( "Error in %1: %2" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage ) ); + emit passwordHelperMessageOut( mPasswordHelperErrorMessage, authManTag(), CRITICAL ); + } + passwordHelperClearErrors(); +} + + bool QgsAuthManager::masterPasswordInput() { if ( isDisabled() ) return false; QString pass; - QgsCredentials *creds = QgsCredentials::instance(); - creds->lock(); - bool ok = creds->getMasterPassword( pass, masterPasswordHashInDatabase() ); - creds->unlock(); + bool storedPasswordIsValid = false; + bool ok = false; - if ( ok && !pass.isEmpty() && !masterPasswordSame( pass ) ) + // Read the password from the wallet + if ( passwordHelperEnabled( ) ) + { + pass = passwordHelperRead( ); + if ( ! pass.isEmpty( ) && ( mPasswordHelperErrorCode == QKeychain::NoError ) ) + { + // Let's check the password! + if ( verifyMasterPassword( pass ) ) + { + ok = true; + storedPasswordIsValid = true; + emit passwordHelperMessageOut( tr( "Master password has been successfully read from your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), INFO ); + } + else + { + emit passwordHelperMessageOut( tr( "Master password stored in your %1 is not valid" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), WARNING ); + } + } + } + + if ( ! ok ) + { + QgsCredentials *creds = QgsCredentials::instance(); + creds->lock(); + pass.clear(); + ok = creds->getMasterPassword( pass, masterPasswordHashInDatabase() ); + creds->unlock(); + } + + if ( ok && !pass.isEmpty() && mMasterPass != pass ) { mMasterPass = pass; + if ( passwordHelperEnabled( ) && ! storedPasswordIsValid ) + { + if ( passwordHelperWrite( pass ) ) + { + emit passwordHelperMessageOut( tr( "Master password has been successfully written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), INFO ); + } + else + { + emit passwordHelperMessageOut( tr( "Master password could not be written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), WARNING ); + } + } return true; } return false; diff --git a/src/core/auth/qgsauthmanager.h b/src/core/auth/qgsauthmanager.h index 6f54e5a26750..c4db2192b9ca 100644 --- a/src/core/auth/qgsauthmanager.h +++ b/src/core/auth/qgsauthmanager.h @@ -37,6 +37,9 @@ #include "qgsauthconfig.h" #include "qgsauthmethod.h" +// Qt5KeyChain library +#include "keychain.h" + namespace QCA { class Initializer; @@ -496,8 +499,54 @@ class CORE_EXPORT QgsAuthManager : public QObject //! Return pointer to mutex QMutex *mutex() { return mMutex; } + //! Error message getter + //! @note not available in Python bindings + const QString passwordHelperErrorMessage() { return mPasswordHelperErrorMessage; } + + //! Delete master password from wallet + //! @note not available in Python bindings + bool passwordHelperDelete(); + + //! Password helper enabled getter + //! @note not available in Python bindings + bool passwordHelperEnabled() const; + + //! Password helper enabled setter + //! @note not available in Python bindings + void setPasswordHelperEnabled( const bool enabled ); + + //! Password helper logging enabled getter + //! @note not available in Python bindings + bool passwordHelperLoggingEnabled() const; + + //! Password helper logging enabled setter + //! @note not available in Python bindings + void setPasswordHelperLoggingEnabled( const bool enabled ); + + //! Store the password manager into the wallet + //! @note not available in Python bindings + bool passwordHelperSync( ); + + //! The display name of the password helper (platform dependent) + static const QString AUTH_PASSWORD_HELPER_DISPLAY_NAME; + + //! The display name of the Authentication Manager + static const QString AUTH_MAN_TAG; + signals: + /** + * Signals emitted on password helper failure, + * mainly used in the tests to exit main application loop + */ + void passwordHelperFailure(); + + /** + * Signals emitted on password helper success, + * mainly used in the tests to exit main application loop + */ + void passwordHelperSuccess(); + /** * Custom logging signal to relay to console output and QgsMessageLog * \see QgsMessageLog @@ -507,6 +556,16 @@ class CORE_EXPORT QgsAuthManager : public QObject */ void messageOut( const QString &message, const QString &tag = AUTH_MAN_TAG, QgsAuthManager::MessageLevel level = INFO ) const; + /** + * Custom logging signal to inform the user about master password <-> password manager interactions + * @see QgsMessageLog + * @param message Message to send + * @param tag Associated tag (title) + * @param level Message log level + */ + void passwordHelperMessageOut( const QString &message, const QString &tag = AUTH_MAN_TAG, QgsAuthManager::MessageLevel level = INFO ) const; + + /** * Emitted when a password has been verify (or not) * \param verified The state of password's verification @@ -544,6 +603,31 @@ class CORE_EXPORT QgsAuthManager : public QObject private: + ////////////////////////////////////////////////////////////////////////////// + // Password Helper methods + + //! Return name for logging + QString passwordHelperName() const; + + //! Print a debug message in QGIS + void passwordHelperLog( const QString &msg ) const; + + //! Read Master password from the wallet + QString passwordHelperRead(); + + //! Store Master password in the wallet + bool passwordHelperWrite( const QString &password ); + + //! Error message setter + void passwordHelperSetErrorMessage( const QString errorMessage ) { mPasswordHelperErrorMessage = errorMessage; } + + //! Clear error code and message + void passwordHelperClearErrors(); + + //! Process the error: show it and/or disable the password helper system in case of + //! access denied or no backend, reset error flags at the end + void passwordHelperProcessError(); + bool createConfigTables(); bool createCertTables(); @@ -604,7 +688,6 @@ class CORE_EXPORT QgsAuthManager : public QObject static const QString AUTH_SERVERS_TABLE; static const QString AUTH_AUTHORITIES_TABLE; static const QString AUTH_TRUST_TABLE; - static const QString AUTH_MAN_TAG; static const QString AUTH_CFG_REGEX; bool mAuthInit; @@ -637,6 +720,31 @@ class CORE_EXPORT QgsAuthManager : public QObject // cache of SSL errors to be ignored in network connections, per sha-hostport QHash > mIgnoredSslErrorsCache; #endif + + ////////////////////////////////////////////////////////////////////////////// + // Password Helper Variables + + //! Master password verification has failed + bool mPasswordHelperVerificationError; + + //! Store last error message + QString mPasswordHelperErrorMessage; + + //! Store last error code (enum) + QKeychain::Error mPasswordHelperErrorCode; + + //! Enable logging + bool mPasswordHelperLoggingEnabled; + + //! Whether the keychain bridge failed to initialize + bool mPasswordHelperFailedInit; + + //! Master password name in the wallets + static const QLatin1String AUTH_PASSWORD_HELPER_KEY_NAME; + + //! password helper folder in the wallets + static const QLatin1String AUTH_PASSWORD_HELPER_FOLDER_NAME; + }; #endif // QGSAUTHMANAGER_H diff --git a/src/core/composer/qgscomposerlegend.cpp b/src/core/composer/qgscomposerlegend.cpp index 400191c26c83..1ed3f567275f 100644 --- a/src/core/composer/qgscomposerlegend.cpp +++ b/src/core/composer/qgscomposerlegend.cpp @@ -35,6 +35,7 @@ QgsComposerLegend::QgsComposerLegend( QgsComposition *composition ) : QgsComposerItem( composition ) + , mLegendModel( new QgsLegendModel( mComposition->project()->layerTreeRoot() ) ) , mCustomLayerTree( nullptr ) , mComposerMap( nullptr ) , mLegendFilterByMap( false ) @@ -46,8 +47,6 @@ QgsComposerLegend::QgsComposerLegend( QgsComposition *composition ) , mForceResize( false ) , mSizeToContents( true ) { - mLegendModel = new QgsLegendModel( mComposition->project()->layerTreeRoot() ); - connect( &composition->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsComposerLegend::onAtlasEnded ); connect( &composition->atlasComposition(), &QgsAtlasComposition::featureChanged, this, &QgsComposerLegend::onAtlasFeature ); @@ -73,12 +72,6 @@ QgsComposerLegend::QgsComposerLegend() } -QgsComposerLegend::~QgsComposerLegend() -{ - delete mLegendModel; - delete mCustomLayerTree; -} - void QgsComposerLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) { Q_UNUSED( itemStyle ); @@ -119,7 +112,7 @@ void QgsComposerLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem } mInitialMapScaleCalculated = true; - QgsLegendRenderer legendRenderer( mLegendModel, mSettings ); + QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings ); legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() ); //adjust box if width or height is too small @@ -180,7 +173,7 @@ QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter *painter ) doUpdateFilterByMap(); } - QgsLegendRenderer legendRenderer( mLegendModel, mSettings ); + QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings ); QSizeF size = legendRenderer.minimumSize(); if ( painter ) legendRenderer.drawLegend( painter ); @@ -202,7 +195,7 @@ void QgsComposerLegend::adjustBoxSize() return; } - QgsLegendRenderer legendRenderer( mLegendModel, mSettings ); + QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings ); QSizeF size = legendRenderer.minimumSize(); QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) ); if ( size.isValid() ) @@ -227,8 +220,7 @@ void QgsComposerLegend::setCustomLayerTree( QgsLayerTree *rootGroup ) { mLegendModel->setRootGroup( rootGroup ? rootGroup : mComposition->project()->layerTreeRoot() ); - delete mCustomLayerTree; - mCustomLayerTree = rootGroup; + mCustomLayerTree.reset( rootGroup ); } @@ -493,7 +485,15 @@ bool QgsComposerLegend::readXml( const QDomElement &itemElem, const QDomDocument if ( layerTreeElem.isNull() ) layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree-group" ) ); - setCustomLayerTree( QgsLayerTree::readXml( layerTreeElem ) ); + if ( !layerTreeElem.isNull() ) + { + std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem ) ); + if ( mComposition ) + tree->resolveReferences( mComposition->project() ); + setCustomLayerTree( tree.release() ); + } + else + setCustomLayerTree( nullptr ); //restore general composer item properties QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) ); @@ -686,7 +686,7 @@ void QgsComposerLegend::doUpdateFilterByMap() mLegendModel->setLayerStyleOverrides( QMap() ); - bool filterByExpression = QgsLayerTreeUtils::hasLegendFilterExpression( *( mCustomLayerTree ? mCustomLayerTree : mComposition->project()->layerTreeRoot() ) ); + bool filterByExpression = QgsLayerTreeUtils::hasLegendFilterExpression( *( mCustomLayerTree ? mCustomLayerTree.get() : mComposition->project()->layerTreeRoot() ) ); if ( mComposerMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) ) { diff --git a/src/core/composer/qgscomposerlegend.h b/src/core/composer/qgscomposerlegend.h index 56dfe39894a0..377f04d92e16 100644 --- a/src/core/composer/qgscomposerlegend.h +++ b/src/core/composer/qgscomposerlegend.h @@ -22,6 +22,7 @@ #include "qgscomposeritem.h" #include "qgslayertreemodel.h" #include "qgslegendsettings.h" +#include "qgslayertreegroup.h" class QgsLayerTreeModel; class QgsSymbol; @@ -58,7 +59,6 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem public: QgsComposerLegend( QgsComposition *composition ); - ~QgsComposerLegend(); //! Return correct graphics item type. virtual int type() const override { return ComposerLegend; } @@ -90,7 +90,7 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem /** * Returns the legend model */ - QgsLegendModel *model() { return mLegendModel; } + QgsLegendModel *model() { return mLegendModel.get(); } //! \since QGIS 2.6 void setAutoUpdateModel( bool autoUpdate ); @@ -308,8 +308,8 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem //! use new custom layer tree and update model. if new root is null pointer, will use project's tree void setCustomLayerTree( QgsLayerTree *rootGroup ); - QgsLegendModel *mLegendModel = nullptr; - QgsLayerTreeGroup *mCustomLayerTree = nullptr; + std::unique_ptr< QgsLegendModel > mLegendModel; + std::unique_ptr< QgsLayerTreeGroup > mCustomLayerTree; QgsLegendSettings mSettings; diff --git a/src/core/geometry/qgsbox3d.cpp b/src/core/geometry/qgsbox3d.cpp new file mode 100644 index 000000000000..cfd096f60a2a --- /dev/null +++ b/src/core/geometry/qgsbox3d.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + qgsbox3d.cpp + ------------ + begin : April 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsbox3d.h" + + +QgsBox3d::QgsBox3d( double xmin, double ymin, double zmin, double xmax, double ymax, double zmax ) + : mBounds2d( xmin, ymin, xmax, ymax ) + , mZmin( zmin ) + , mZmax( zmax ) +{} + +QgsBox3d::QgsBox3d( const QgsPointV2 &p1, const QgsPointV2 &p2 ) + : mBounds2d( p1.x(), p1.y(), p2.x(), p2.y() ) + , mZmin( qMin( p1.z(), p2.z() ) ) + , mZmax( qMax( p1.z(), p2.z() ) ) +{ + mBounds2d.normalize(); +} + +void QgsBox3d::setXMinimum( double x ) +{ + mBounds2d.setXMinimum( x ); +} + +void QgsBox3d::setXMaximum( double x ) +{ + mBounds2d.setXMaximum( x ); +} + +void QgsBox3d::setYMinimum( double y ) +{ + mBounds2d.setYMinimum( y ); +} + +void QgsBox3d::setYMaximum( double y ) +{ + mBounds2d.setYMaximum( y ); +} + +void QgsBox3d::setZMinimum( double z ) +{ + mZmin = z; +} + +void QgsBox3d::setZMaximum( double z ) +{ + mZmax = z; +} + +void QgsBox3d::normalize() +{ + mBounds2d.normalize(); + double z1 = qMin( mZmin, mZmax ); + double z2 = qMax( mZmin, mZmax ); + mZmin = z1; + mZmax = z2; +} + +QgsBox3d QgsBox3d::intersect( const QgsBox3d &other ) const +{ + QgsRectangle intersect2d = mBounds2d.intersect( &( other.mBounds2d ) ); + double zMin = qMax( mZmin, other.mZmin ); + double zMax = qMin( mZmax, other.mZmax ); + return QgsBox3d( intersect2d.xMinimum(), intersect2d.yMinimum(), zMin, + intersect2d.xMaximum(), intersect2d.yMaximum(), zMax ); +} + +bool QgsBox3d::intersects( const QgsBox3d &other ) const +{ + if ( !mBounds2d.intersects( other.mBounds2d ) ) + return false; + + double z1 = ( mZmin > other.mZmin ? mZmin : other.mZmin ); + double z2 = ( mZmax < other.mZmax ? mZmax : other.mZmax ); + if ( z1 > z2 ) + return false; + + return true; +} + +bool QgsBox3d::contains( const QgsBox3d &other ) const +{ + if ( !mBounds2d.contains( other.mBounds2d ) ) + return false; + + return ( other.mZmin >= mZmin && other.mZmax <= mZmax ); +} + +bool QgsBox3d::contains( const QgsPointV2 &p ) const +{ + if ( !mBounds2d.contains( QgsPoint( p.x(), p.y() ) ) ) + return false; + + if ( p.is3D() ) + return mZmin <= p.z() && p.z() <= mZmax; + else + return true; +} diff --git a/src/core/geometry/qgsbox3d.h b/src/core/geometry/qgsbox3d.h new file mode 100644 index 000000000000..dbf166646a36 --- /dev/null +++ b/src/core/geometry/qgsbox3d.h @@ -0,0 +1,200 @@ +/*************************************************************************** + qgsbox3d.h + ---------- + begin : April 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSBOX3D_H +#define QGSBOX3D_H + +#include "qgis_core.h" +#include "qgsrectangle.h" +#include "qgspointv2.h" + +/** \ingroup core + * A 3-dimensional box composed of x, y, z coordinates. + * + * A box composed of x/y/z minimum and maximum values. It is often used to return the 3D + * extent of a geometry or collection of geometries. + * + * \since QGIS 3.0 + * \see QgsRectangle + */ +class CORE_EXPORT QgsBox3d +{ + public: + + /** + * Constructor for QgsBox3D which accepts the ranges of x/y/z coordinates. + */ + QgsBox3d( double xmin = 0, double ymin = 0, double mZmin = 0, double xmax = 0, double ymax = 0, double mZmax = 0 ); + + /** + * Constructs a QgsBox3D from two points representing opposite corners of the box. + * The box is normalized after construction. + */ + QgsBox3d( const QgsPointV2 &p1, const QgsPointV2 &p2 ); + + /** + * Sets the minimum \a x value. + * \see xMinimum() + * \see setXMaximum() + */ + void setXMinimum( double x ); + + /** + * Sets the maximum \a x value. + * \see xMaximum() + * \see setXMinimum() + */ + void setXMaximum( double x ); + + /** + * Returns the minimum x value. + * \see setXMinimum() + * \see xMaximum() + */ + double xMinimum() const { return mBounds2d.xMinimum(); } + + /** + * Returns the maximum x value. + * \see setXMaximum() + * \see xMinimum() + */ + double xMaximum() const { return mBounds2d.xMaximum(); } + + /** + * Sets the minimum \a y value. + * \see yMinimum() + * \see setYMaximum() + */ + void setYMinimum( double y ); + + /** + * Sets the maximum \a y value. + * \see yMaximum() + * \see setYMinimum() + */ + void setYMaximum( double y ); + + /** + * Returns the minimum y value. + * \see setYMinimum() + * \see yMaximum() + */ + double yMinimum() const { return mBounds2d.yMinimum(); } + + /** + * Returns the maximum y value. + * \see setYMaximum() + * \see yMinimum() + */ + double yMaximum() const { return mBounds2d.yMaximum(); } + + /** + * Sets the minimum \a z value. + * \see zMinimum() + * \see setZMaximum() + */ + void setZMinimum( double z ); + + /** + * Sets the maximum \a z value. + * \see zMaximum() + * \see setZMinimum() + */ + void setZMaximum( double z ); + + /** + * Returns the minimum z value. + * \see setZMinimum() + * \see zMaximum() + */ + double zMinimum() const { return mZmin; } + + /** + * Returns the maximum z value. + * \see setZMaximum() + * \see zMinimum() + */ + double zMaximum() const { return mZmax; } + + /** + * Normalize the box so it has non-negative width/height/depth. + */ + void normalize(); + + /** + * Returns the width of the box. + * \see height() + * \see depth() + */ + double width() const { return mBounds2d.width(); } + + /** + * Returns the height of the box. + * \see width() + * \see depth() + */ + double height() const { return mBounds2d.height(); } + + /** + * Returns the depth of the box. + * \see width() + * \see height() + */ + double depth() const { return mZmax - mZmin; } + + /** + * Returns the volume of the box. + */ + double volume() const { return mBounds2d.area() * ( mZmax - mZmin ); } + + /** + * Returns the intersection of this box and another 3D box. + */ + QgsBox3d intersect( const QgsBox3d &other ) const; + + /** + * Returns true if box intersects with another box. + */ + bool intersects( const QgsBox3d &other ) const; + + /** + * Returns true when box contains other box. + */ + bool contains( const QgsBox3d &other ) const; + + /** + * Returns true when box contains a \a point. + * + * If the point is a 2D point (no z-coordinate), then the containment test + * will be performed on the x/y extent of the box only. + */ + bool contains( const QgsPointV2 &point ) const; + + /** + * Converts the box to a 2D rectangle. + */ + QgsRectangle toRectangle() const { return mBounds2d; } + + private: + + QgsRectangle mBounds2d; + double mZmin = 0.0; + double mZmax = 0.0; + +}; + +#endif // QGSBOX3D_H diff --git a/src/core/qgsrectangle.cpp b/src/core/geometry/qgsrectangle.cpp similarity index 95% rename from src/core/qgsrectangle.cpp rename to src/core/geometry/qgsrectangle.cpp index 79a37ef67f13..6278fe50eb74 100644 --- a/src/core/qgsrectangle.cpp +++ b/src/core/geometry/qgsrectangle.cpp @@ -28,12 +28,13 @@ #include "qgspoint.h" #include "qgsrectangle.h" #include "qgslogger.h" +#include "qgsbox3d.h" -QgsRectangle::QgsRectangle( double newxmin, double newymin, double newxmax, double newymax ) - : mXmin( newxmin ) - , mYmin( newymin ) - , mXmax( newxmax ) - , mYmax( newymax ) +QgsRectangle::QgsRectangle( double xMin, double yMin, double xMax, double yMax ) + : mXmin( xMin ) + , mYmin( yMin ) + , mXmax( xMax ) + , mYmax( yMax ) { normalize(); } @@ -59,7 +60,6 @@ QgsRectangle::QgsRectangle( const QgsRectangle &r ) mYmax = r.yMaximum(); } - void QgsRectangle::set( const QgsPoint &p1, const QgsPoint &p2 ) { mXmin = p1.x(); @@ -91,8 +91,7 @@ void QgsRectangle::normalize() { std::swap( mYmin, mYmax ); } -} // QgsRectangle::normalize() - +} void QgsRectangle::setMinimal() { @@ -264,9 +263,6 @@ QRectF QgsRectangle::toRectF() const return QRectF( static_cast< qreal >( mXmin ), static_cast< qreal >( mYmin ), static_cast< qreal >( mXmax - mXmin ), static_cast< qreal >( mYmax - mYmin ) ); } -// Returns a string representation of form xmin,ymin : xmax,ymax. Coordinates will be truncated -// to the specified \a precision. If \a precision is less than 0 then a suitable minimum precision -// will be automatically calculated. QString QgsRectangle::toString( int precision ) const { QString rep; @@ -297,8 +293,6 @@ QString QgsRectangle::toString( int precision ) const return rep; } - -// Return the rectangle as a set of polygon coordinates QString QgsRectangle::asPolygon() const { // QString rep = tmp.sprintf("%16f %16f,%16f %16f,%16f %16f,%16f %16f,%16f %16f", @@ -320,8 +314,7 @@ QString QgsRectangle::asPolygon() const return rep; -} // QgsRectangle::asPolygon() const - +} bool QgsRectangle::operator==( const QgsRectangle &r1 ) const { @@ -331,13 +324,11 @@ bool QgsRectangle::operator==( const QgsRectangle &r1 ) const qgsDoubleNear( r1.yMinimum(), yMinimum() ); } - bool QgsRectangle::operator!=( const QgsRectangle &r1 ) const { return ( ! operator==( r1 ) ); } - QgsRectangle &QgsRectangle::operator=( const QgsRectangle &r ) { if ( &r != this ) @@ -351,7 +342,6 @@ QgsRectangle &QgsRectangle::operator=( const QgsRectangle &r ) return *this; } - void QgsRectangle::unionRect( const QgsRectangle &r ) { if ( r.xMinimum() < xMinimum() ) @@ -388,6 +378,11 @@ void QgsRectangle::invert() mYmax = tmp; } +QgsBox3d QgsRectangle::toBox3d( double zMin, double zMax ) const +{ + return QgsBox3d( mXmin, mYmin, zMin, mXmax, mYmax, zMax ); +} + QDataStream &operator<<( QDataStream &out, const QgsRectangle &rectangle ) { out << rectangle.xMinimum() << rectangle.yMinimum() << rectangle.xMaximum() << rectangle.yMaximum(); diff --git a/src/core/geometry/qgsrectangle.h b/src/core/geometry/qgsrectangle.h new file mode 100644 index 000000000000..6099f39c524d --- /dev/null +++ b/src/core/geometry/qgsrectangle.h @@ -0,0 +1,313 @@ +/*************************************************************************** + qgsrectangle.h - description + ------------------- + begin : Sat Jun 22 2002 + copyright : (C) 2002 by Gary E.Sherman + email : sherman at mrcc.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 QGSRECTANGLE_H +#define QGSRECTANGLE_H + +#include "qgis_core.h" +#include +#include + +class QString; +class QRectF; +class QgsBox3d; +#include "qgspoint.h" + + +/** \ingroup core + * A rectangle specified with double values. + * + * QgsRectangle is used to store a rectangle when double values are required. + * Examples are storing a layer extent or the current view extent of a map + * \see QgsBox3d + */ +class CORE_EXPORT QgsRectangle +{ + public: + //! Constructor + QgsRectangle( double xMin = 0, double yMin = 0, double xMax = 0, double yMax = 0 ); + //! Construct a rectangle from two points. The rectangle is normalized after construction. + QgsRectangle( const QgsPoint &p1, const QgsPoint &p2 ); + //! Construct a rectangle from a QRectF. The rectangle is normalized after construction. + QgsRectangle( const QRectF &qRectF ); + //! Copy constructor + QgsRectangle( const QgsRectangle &other ); + + /** + * Sets the rectangle from two QgsPoints. The rectangle is + * normalised after construction. + */ + void set( const QgsPoint &p1, const QgsPoint &p2 ); + + /** + * Sets the rectangle from four points. The rectangle is + * normalised after construction. + */ + void set( double mXmin, double mYmin, double mXmax, double mYmax ); + + /** + * Set the minimum x value. + */ + void setXMinimum( double x ) { mXmin = x; } + + /** + * Set the maximum x value. + */ + void setXMaximum( double x ) { mXmax = x; } + + /** + * Set the minimum y value. + */ + void setYMinimum( double y ) { mYmin = y; } + + /** + * Set the maximum y value. + */ + void setYMaximum( double y ) { mYmax = y; } + + /** + * Set a rectangle so that min corner is at max + * and max corner is at min. It is NOT normalized. + */ + void setMinimal(); + + /** + * Returns the x maximum value (right side of rectangle). + */ + double xMaximum() const { return mXmax; } + + /** + * Returns the x minimum value (left side of rectangle). + */ + double xMinimum() const { return mXmin; } + + /** + * Returns the y maximum value (top side of rectangle). + */ + double yMaximum() const { return mYmax; } + + /** + * Returns the y minimum value (bottom side of rectangle). + */ + double yMinimum() const { return mYmin; } + + /** + * Normalize the rectangle so it has non-negative width/height. + */ + void normalize(); + + /** + * Returns the width of the rectangle. + * \see height() + * \see area() + */ + double width() const { return mXmax - mXmin; } + + /** + * Returns the height of the rectangle. + * \see width() + * \see area() + */ + double height() const { return mYmax - mYmin; } + + /** + * Returns the area of the rectangle. + * \since QGIS 3.0 + * \see width() + * \see height() + * \see perimeter() + */ + double area() const { return ( mXmax - mXmin ) * ( mYmax - mYmin ); } + + /** + * Returns the perimeter of the rectangle. + * \since QGIS 3.0 + * \see area() + */ + double perimeter() const { return 2 * ( mXmax - mXmin ) + 2 * ( mYmax - mYmin ); } + + /** + * Returns the center point of the rectangle. + */ + QgsPoint center() const { return QgsPoint( mXmin + width() / 2, mYmin + height() / 2 ); } + + /** + * Scale the rectangle around its center point. + */ + void scale( double scaleFactor, const QgsPoint *c = nullptr ); + + /** + * Scale the rectangle around its center point. + */ + void scale( double scaleFactor, double centerX, double centerY ); + + /** + * Grows the rectangle by the specified amount. + */ + void grow( double delta ); + + /** + * Updates the rectangle to include the specified point. + */ + void include( const QgsPoint &p ); + + /** + * Get rectangle enlarged by buffer. + * \since QGIS 2.1 + */ + QgsRectangle buffer( double width ); + + /** + * Return the intersection with the given rectangle. + */ + QgsRectangle intersect( const QgsRectangle *rect ) const; + + /** + * Returns true when rectangle intersects with other rectangle. + */ + bool intersects( const QgsRectangle &rect ) const; + + /** + * Return true when rectangle contains other rectangle. + */ + bool contains( const QgsRectangle &rect ) const; + + /** + * Return true when rectangle contains a point. + */ + bool contains( const QgsPoint &p ) const; + + /** + * Expand the rectangle so that covers both the original rectangle and the given rectangle. + */ + void combineExtentWith( const QgsRectangle &rect ); + + /** + * Expand the rectangle so that covers both the original rectangle and the given point. + */ + void combineExtentWith( double x, double y ); + + /** + * Returns true if the rectangle is empty. + * An empty rectangle may still be non-null if it contains valid information (e.g. bounding box of a point). + */ + bool isEmpty() const; + + /** + * Test if the rectangle is null (all coordinates zero or after call to setMinimal()). + * A null rectangle is also an empty rectangle. + * \since QGIS 2.4 + */ + bool isNull() const; + + /** + * Returns a string representation of the rectangle in WKT format. + */ + QString asWktCoordinates() const; + + /** + * Returns a string representation of the rectangle as a WKT Polygon. + */ + QString asWktPolygon() const; + + /** + * Returns a QRectF with same coordinates as the rectangle. + */ + QRectF toRectF() const; + + /** + * Returns a string representation of form xmin,ymin : xmax,ymax + * Coordinates will be truncated to the specified precision. + * If the specified precision is less than 0, a suitable minimum precision is used. + */ + QString toString( int precision = 16 ) const; + + /** + * Returns the rectangle as a polygon. + */ + QString asPolygon() const; + + /** + * Comparison operator + * \returns True if rectangles are equal + */ + bool operator==( const QgsRectangle &r1 ) const; + + /** + * Comparison operator + * \returns False if rectangles are equal + */ + bool operator!=( const QgsRectangle &r1 ) const; + + /** + * Assignment operator + * \param r1 QgsRectangle to assign from + */ + QgsRectangle &operator=( const QgsRectangle &r1 ); + + /** + * Updates the rectangle to include another rectangle. + */ + void unionRect( const QgsRectangle &rect ); + + /** + * Returns true if the rectangle has finite boundaries. Will + * return false if any of the rectangle boundaries are NaN or Inf. + */ + bool isFinite() const; + + /** + * Swap x/y coordinates in the rectangle. + */ + void invert(); + + /** + * Converts the rectangle to a 3D box, with the specified + * \a zMin and \a zMax z values. + * \since QGIS 3.0 + */ + QgsBox3d toBox3d( double zMin, double zMax ) const; + + private: + + double mXmin; + double mYmin; + double mXmax; + double mYmax; + +}; + +#ifndef SIP_RUN + +/** + * Writes the list rectangle to stream out. QGIS version compatibility is not guaranteed. + */ +CORE_EXPORT QDataStream &operator<<( QDataStream &out, const QgsRectangle &rectangle ); + +/** + * Reads a rectangle from stream in into rectangle. QGIS version compatibility is not guaranteed. + */ +CORE_EXPORT QDataStream &operator>>( QDataStream &in, QgsRectangle &rectangle ); + +inline std::ostream &operator << ( std::ostream &os, const QgsRectangle &r ) +{ + return os << r.toString().toLocal8Bit().data(); +} + +#endif + +#endif // QGSRECTANGLE_H diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 2297a07877ae..c53df85b415f 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -963,49 +963,49 @@ QString QgsApplication::reportStyleSheet() " color: black;" " border: 1px solid #6c6c6c;" "}" - ".overview{ font: 1.82em; font-weight: bold;}" - "body{ background: white;" + ".overview{" + " font: 1.82em;" + " font-weight: bold;" + "}" + "body{" + " background: white;" " color: black;" - " font-family: arial,sans-serif;" + " font-family: 'Lato', 'Ubuntu', 'Lucida Grande', 'Segoe UI', 'Arial', sans-serif;" + " width: 100%;" "}" "h1{ background-color: #F6F6F6;" - " color: #8FB171; " + " color: #589632; " // from http://qgis.org/en/site/getinvolved/styleguide.html " font-size: x-large; " " font-weight: normal;" - " font-family: luxi serif, georgia, times new roman, times, serif;" " background: none;" " padding: 0.75em 0 0;" " margin: 0;" " line-height: 3em;" "}" "h2{ background-color: #F6F6F6;" - " color: #8FB171; " + " color: #589632; " // from http://qgis.org/en/site/getinvolved/styleguide.html " font-size: medium; " " font-weight: normal;" - " font-family: luxi serif, georgia, times new roman, times, serif;" " background: none;" " padding: 0.75em 0 0;" " margin: 0;" " line-height: 1.1em;" "}" "h3{ background-color: #F6F6F6;" - " color: #729FCF;" - " font-family: luxi serif, georgia, times new roman, times, serif;" + " color: #93b023;" // from http://qgis.org/en/site/getinvolved/styleguide.html " font-weight: bold;" " font-size: large;" " text-align: right;" " border-bottom: 5px solid #DCEB5C;" "}" "h4{ background-color: #F6F6F6;" - " color: #729FCF;" - " font-family: luxi serif, georgia, times new roman, times, serif;" + " color: #93b023;" // from http://qgis.org/en/site/getinvolved/styleguide.html " font-weight: bold;" " font-size: medium;" " text-align: right;" "}" "h5{ background-color: #F6F6F6;" - " color: #729FCF;" - " font-family: luxi serif, georgia, times new roman, times, serif;" + " color: #93b023;" // from http://qgis.org/en/site/getinvolved/styleguide.html " font-weight: bold;" " font-size: small;" " text-align: right;" @@ -1019,6 +1019,33 @@ QString QgsApplication::reportStyleSheet() " margin: 1px;" " padding: 0px 3px; " " font-size: small;" + "}" + ".section {" + " font-weight: bold;" + " padding-top:25px;" + "}" + ".list-view .highlight {" + " text-align: right;" + " border: 0px;" + " width: 20%;" + " padding-right: 15px;" + " padding-left: 20px;" + " font-weight: bold;" + "}" + ".tabular-view{ " + " border-collapse: collapse;" + " width: 95%;" + "}" + ".tabular-view th, .tabular-view td { " + " border:10px solid black;" + "}" + ".tabular-view .odd-row{" + " background-color: #f9f9f9;" + "}" + "hr {" + " border: 0;" + " height: 0;" + " border-top: 1px solid black;" "}"; return myStyle; } diff --git a/src/core/qgsrectangle.h b/src/core/qgsrectangle.h deleted file mode 100644 index 9041cfe0c92e..000000000000 --- a/src/core/qgsrectangle.h +++ /dev/null @@ -1,231 +0,0 @@ -/*************************************************************************** - qgsrectangle.h - description - ------------------- - begin : Sat Jun 22 2002 - copyright : (C) 2002 by Gary E.Sherman - email : sherman at mrcc.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 QGSRECTANGLE_H -#define QGSRECTANGLE_H - -#include "qgis_core.h" -#include -#include - -class QString; -class QRectF; -#include "qgspoint.h" - - -/** \ingroup core - * A rectangle specified with double values. - * - * QgsRectangle is used to store a rectangle when double values are required. - * Examples are storing a layer extent or the current view extent of a map - */ -class CORE_EXPORT QgsRectangle -{ - public: - //! Constructor - QgsRectangle( double mXmin = 0, double mYmin = 0, double mXmax = 0, double mYmax = 0 ); - //! Construct a rectangle from two points. The rectangle is normalized after construction. - QgsRectangle( const QgsPoint &p1, const QgsPoint &p2 ); - //! Construct a rectangle from a QRectF. The rectangle is normalized after construction. - QgsRectangle( const QRectF &qRectF ); - //! Copy constructor - QgsRectangle( const QgsRectangle &other ); - - ~QgsRectangle(); - //! Set the rectangle from two QgsPoints. The rectangle is - //! normalised after construction. - void set( const QgsPoint &p1, const QgsPoint &p2 ); - //! Set the rectangle from four points. The rectangle is - //! normalised after construction. - void set( double mXmin, double mYmin, double mXmax, double mYmax ); - //! Set the minimum x value - void setXMinimum( double x ); - //! Set the maximum x value - void setXMaximum( double x ); - //! Set the minimum y value - void setYMinimum( double y ); - //! Set the maximum y value - void setYMaximum( double y ); - //! Set a rectangle so that min corner is at max - //! and max corner is at min. It is NOT normalized. - void setMinimal(); - //! Get the x maximum value (right side of rectangle) - double xMaximum() const; - //! Get the x minimum value (left side of rectangle) - double xMinimum() const; - //! Get the y maximum value (top side of rectangle) - double yMaximum() const; - //! Get the y minimum value (bottom side of rectangle) - double yMinimum() const; - //! Normalize the rectangle so it has non-negative width/height - void normalize(); - //! Width of the rectangle - double width() const; - //! Height of the rectangle - double height() const; - //! Center point of the rectangle - QgsPoint center() const; - //! Scale the rectangle around its center point - void scale( double scaleFactor, const QgsPoint *c = nullptr ); - void scale( double scaleFactor, double centerX, double centerY ); - //! Grow the rectangle by the specified amount - void grow( double delta ); - //! Updates the rectangle to include the specified point - void include( const QgsPoint &p ); - - /** Get rectangle enlarged by buffer. - * \since QGIS 2.1 */ - QgsRectangle buffer( double width ); - //! return the intersection with the given rectangle - QgsRectangle intersect( const QgsRectangle *rect ) const; - //! returns true when rectangle intersects with other rectangle - bool intersects( const QgsRectangle &rect ) const; - //! return true when rectangle contains other rectangle - bool contains( const QgsRectangle &rect ) const; - //! return true when rectangle contains a point - bool contains( const QgsPoint &p ) const; - //! expand the rectangle so that covers both the original rectangle and the given rectangle - void combineExtentWith( const QgsRectangle &rect ); - //! expand the rectangle so that covers both the original rectangle and the given point - void combineExtentWith( double x, double y ); - //! test if rectangle is empty. - //! Empty rectangle may still be non-null if it contains valid information (e.g. bounding box of a point) - bool isEmpty() const; - //! test if the rectangle is null (all coordinates zero or after call to setMinimal()). - //! Null rectangle is also an empty rectangle. - //! \since QGIS 2.4 - bool isNull() const; - //! returns string representation in Wkt form - QString asWktCoordinates() const; - //! returns string representation as WKT Polygon - QString asWktPolygon() const; - //! returns a QRectF with same coordinates. - QRectF toRectF() const; - - /** - * returns a string representation of form xmin,ymin : xmax,ymax - * Coordinates will be truncated to the specified precision. - * If the specified precision is less than 0, a suitable minimum precision is used. - */ - QString toString( int precision = 16 ) const; - //! returns rectangle as a polygon - QString asPolygon() const; - - /** Comparison operator - * \returns True if rectangles are equal - */ - bool operator==( const QgsRectangle &r1 ) const; - - /** Comparison operator - * \returns False if rectangles are equal - */ - bool operator!=( const QgsRectangle &r1 ) const; - - /** Assignment operator - * \param r1 QgsRectangle to assign from - */ - QgsRectangle &operator=( const QgsRectangle &r1 ); - - //! Updates rectangle to include passed argument - void unionRect( const QgsRectangle &rect ); - - /** Returns true if the rectangle has finite boundaries. Will - * return false if any of the rectangle boundaries are NaN or Inf. - */ - bool isFinite() const; - - //! swap x/y - void invert(); - - private: - - double mXmin; - double mYmin; - double mXmax; - double mYmax; - -}; - -//! Writes the list rectangle to stream out. QGIS version compatibility is not guaranteed. -CORE_EXPORT QDataStream &operator<<( QDataStream &out, const QgsRectangle &rectangle ); -//! Reads a rectangle from stream in into rectangle. QGIS version compatibility is not guaranteed. -CORE_EXPORT QDataStream &operator>>( QDataStream &in, QgsRectangle &rectangle ); - -inline QgsRectangle::~QgsRectangle() -{ -} - -inline void QgsRectangle::setXMinimum( double x ) -{ - mXmin = x; -} - -inline void QgsRectangle::setXMaximum( double x ) -{ - mXmax = x; -} - -inline void QgsRectangle::setYMinimum( double y ) -{ - mYmin = y; -} - -inline void QgsRectangle::setYMaximum( double y ) -{ - mYmax = y; -} - -inline double QgsRectangle::xMaximum() const -{ - return mXmax; -} - -inline double QgsRectangle::xMinimum() const -{ - return mXmin; -} - -inline double QgsRectangle::yMaximum() const -{ - return mYmax; -} - -inline double QgsRectangle::yMinimum() const -{ - return mYmin; -} - -inline double QgsRectangle::width() const -{ - return mXmax - mXmin; -} - -inline double QgsRectangle::height() const -{ - return mYmax - mYmin; -} - -inline QgsPoint QgsRectangle::center() const -{ - return QgsPoint( mXmin + width() / 2, mYmin + height() / 2 ); -} -inline std::ostream &operator << ( std::ostream &os, const QgsRectangle &r ) -{ - return os << r.toString().toLocal8Bit().data(); -} - -#endif // QGSRECTANGLE_H diff --git a/src/core/qgssettings.cpp b/src/core/qgssettings.cpp index 7419f22ff494..6670cab8b6a5 100644 --- a/src/core/qgssettings.cpp +++ b/src/core/qgssettings.cpp @@ -234,6 +234,12 @@ int QgsSettings::beginReadArray( const QString &prefix ) return size; } +void QgsSettings::beginWriteArray( const QString &prefix, int size ) +{ + mUsingGlobalArray = false; + mUserSettings->beginWriteArray( prefix, size ); +} + void QgsSettings::endArray() { mUserSettings->endArray(); diff --git a/src/core/qgssettings.h b/src/core/qgssettings.h index 73502e6808f8..0ee8f008542f 100644 --- a/src/core/qgssettings.h +++ b/src/core/qgssettings.h @@ -44,8 +44,10 @@ * - Gui * - Server * - Plugins - * - Misc * - Auth + * - App + * - Providers + * - Misc * * \since QGIS 3 */ @@ -154,13 +156,23 @@ class CORE_EXPORT QgsSettings : public QObject static bool setGlobalSettingsPath( QString path ); //! Adds prefix to the current group and starts reading from an array. Returns the size of the array. int beginReadArray( const QString &prefix ); + + /** Adds prefix to the current group and starts writing an array of size size. + * If size is -1 (the default), it is automatically determined based on the indexes of the entries written. + * @note This will completely shadow any existing array with the same name in the global settings + */ + void beginWriteArray( const QString &prefix, int size = -1 ); //! Closes the array that was started using beginReadArray() or beginWriteArray(). void endArray(); - //! Sets the current array index to i. Calls to functions such as setValue(), value(), - //! remove(), and contains() will operate on the array entry at that index. + + /** Sets the current array index to i. Calls to functions such as setValue(), value(), + * remove(), and contains() will operate on the array entry at that index. + */ void setArrayIndex( int i ); - //! Sets the value of setting key to value. If the key already exists, the previous value is overwritten. - //! An optional Section argument can be used to set a value to a specific Section. + + /** Sets the value of setting key to value. If the key already exists, the previous value is overwritten. + * An optional Section argument can be used to set a value to a specific Section. + */ void setValue( const QString &key, const QVariant &value, const QgsSettings::Section section = QgsSettings::NoSection ); /** Returns the value for setting key. If the setting doesn't exist, it will be @@ -190,15 +202,19 @@ class CORE_EXPORT QgsSettings : public QObject sipIsErr = !sipRes; % End #endif - //! Returns true if there exists a setting called key; returns false otherwise. - //! If a group is set using beginGroup(), key is taken to be relative to that group. + + /** Returns true if there exists a setting called key; returns false otherwise. + * If a group is set using beginGroup(), key is taken to be relative to that group. + */ bool contains( const QString &key, const QgsSettings::Section section = QgsSettings::NoSection ) const; //! Returns the path where settings written using this QSettings object are stored. QString fileName() const; - //! Writes any unsaved changes to permanent storage, and reloads any settings that have been - //! changed in the meantime by another application. - //! This function is called automatically from QSettings's destructor and by the event - //! loop at regular intervals, so you normally don't need to call it yourself. + + /** Writes any unsaved changes to permanent storage, and reloads any settings that have been + * changed in the meantime by another application. + * This function is called automatically from QSettings's destructor and by the event + * loop at regular intervals, so you normally don't need to call it yourself. + */ void sync(); //! Removes the setting key and any sub-settings of key. void remove( const QString &key ); diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 0a639c0dfc55..a71326d708bf 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "qgssettings.h" #include "qgsvectorlayer.h" @@ -82,6 +83,7 @@ #include "qgsexpressioncontext.h" #include "qgsfeedback.h" #include "qgsxmlutils.h" +#include "qgsunittypes.h" #include "diagram/qgsdiagram.h" @@ -3987,65 +3989,55 @@ void QgsVectorLayer::setDiagramLayerSettings( const QgsDiagramLayerSettings &s ) QString QgsVectorLayer::htmlMetadata() const { - QString myMetadata = QStringLiteral( "" ); + QString myMetadata = QStringLiteral( "\n\n" ); - //------------- + // Identification section + myMetadata += QLatin1String( "

" ) % tr( "Identification" ) % QLatin1String( "

\n
\n\n" ); - myMetadata += QLatin1String( R"(

)" ); - myMetadata += tr( "General" ); - myMetadata += QLatin1String( "

\n" ); + // ID + myMetadata += QLatin1String( "\n" ); - // data comment - if ( !( dataComment().isEmpty() ) ) - { - myMetadata += R"(

)" + tr( "Layer comment" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += dataComment(); - myMetadata += QLatin1String( "

\n" ); - } + // original name + myMetadata += QLatin1String( "\n" ); - //storage type - myMetadata += R"(

)" + tr( "Storage type of this layer" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += storageType(); - myMetadata += QLatin1String( "

\n" ); + // name + myMetadata += QLatin1String( "\n" ); - if ( dataProvider() ) - { - //provider description - myMetadata += R"(

)" + tr( "Description of this provider" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += dataProvider()->description().replace( '\n', QLatin1String( "
" ) ); - myMetadata += QLatin1String( "

\n" ); + // short + myMetadata += QLatin1String( "\n" ); - QVariantMap dataProviderMetadata = mDataProvider->metadata(); - if ( !dataProviderMetadata.isEmpty() ) - { - myMetadata += R"(

)" + tr( "Provider Metadata" ) + "

\n"; - myMetadata += "

" ) % tr( "ID" ) % QLatin1String( "" ) % id() % QLatin1String( "
" ) % tr( "Original" ) % QLatin1String( "" ) % originalName() % QLatin1String( "
" ) % tr( "Name" ) % QLatin1String( "" ) % name() % QLatin1String( "
" ) % tr( "Short" ) % QLatin1String( "" ) % shortName() % QLatin1String( "
\n"; - QMapIterator i( dataProviderMetadata ); - while ( i.hasNext() ) - { - i.next(); - myMetadata += ""; - myMetadata += ""; - myMetadata += ""; - myMetadata += "\n"; - } - myMetadata += QLatin1String( "
" + tr( "Metadata name" ) + "" + tr( "Metadata value" ) + "
" + mDataProvider->translateMetadataKey( i.key() ) + ":" + mDataProvider->translateMetadataValue( i.key(), i.value() ) + "

\n" ); - } - } + // title + myMetadata += QLatin1String( "" ) % tr( "Title" ) % QLatin1String( "" ) % title() % QLatin1String( "\n" ); + + // abstract + myMetadata += QLatin1String( "" ) % tr( "Abstract" ) % QLatin1String( "" ) % abstract() % QLatin1String( "\n" ); + + // keywords + myMetadata += QLatin1String( "" ) % tr( "Keywords" ) % QLatin1String( "" ) % keywordList() % QLatin1String( "\n" ); + + // lang, waiting for the proper metadata implementation QEP #91 Work package 2 + // myMetadata += QLatin1String( "" ) % tr( "Language" ) % QLatin1String( "en-CA\n" ); + + // comment + myMetadata += QLatin1String( "" ) % tr( "Comment" ) % QLatin1String( "" ) % dataComment() % QLatin1String( "\n" ); + + // date, waiting for the proper metadata implementation QEP #91 Work package 2 + // myMetadata += QLatin1String( "" ) % tr( "Date" ) % QLatin1String( "28/03/17\n" ); + + // storage type + myMetadata += QLatin1String( "" ) % tr( "Storage" ) % QLatin1String( "" ) % storageType() % QLatin1String( "\n" ); // data source - myMetadata += R"(

)" + tr( "Source for this layer" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += publicSource(); - myMetadata += QLatin1String( "

\n" ); + myMetadata += QLatin1String( "" ) % tr( "Source" ) % QLatin1String( "" ) % publicSource() % QLatin1String( "\n" ); - //geom type + // encoding + myMetadata += QLatin1String( "" ) % tr( "Encoding" ) % QLatin1String( "" ) % dataProvider()->encoding() % QLatin1String( "\n" ); - QgsWkbTypes::GeometryType type = geometryType(); + // Section spatial + myMetadata += QLatin1String( "\n

" ) % tr( "Spatial" ) % QLatin1String( "

\n
\n\n" ); + // geom type + QgsWkbTypes::GeometryType type = geometryType(); if ( type < 0 || type > QgsWkbTypes::NullGeometry ) { QgsDebugMsg( "Invalid vector type" ); @@ -4053,204 +4045,86 @@ QString QgsVectorLayer::htmlMetadata() const else { QString typeString( QgsWkbTypes::geometryDisplayString( geometryType() ) ); - - myMetadata += R"(

)" + tr( "Geometry type of the features in this layer" ) + "

\n"; - myMetadata += QStringLiteral( "

%1

\n" ).arg( typeString ); + myMetadata += QLatin1String( "\n" ); } + // EPSG + myMetadata += QLatin1String( "\n" ); + + // Extent + myMetadata += QLatin1String( "\n" ); + + // unit + myMetadata += QLatin1String( "\n" ); + + // max scale + // myMetadata += QLatin1String( "\n" ); + + // min scale + // myMetadata += QLatin1String( "\n" ); + + // feature count + myMetadata += QLatin1String( "\n" ); + + // Fields section + myMetadata += QLatin1String( "
" ) % tr( "Geometry" ) % QLatin1String( "" ) % typeString % QLatin1String( "
" ) % tr( "CRS" ) % QLatin1String( "" ) % crs().authid() % QLatin1String( " - " ); + myMetadata += crs().description() % QLatin1String( " - " ); + if ( crs().isGeographic() ) + myMetadata += tr( "Geographic" ); + else + myMetadata += tr( "Projected" ); + myMetadata += QLatin1String( "
" ) % tr( "Extent" ) % QLatin1String( "" ) % extent().toString() % QLatin1String( "
" ) % tr( "Unit" ) % QLatin1String( "" ) % QgsUnitTypes::toString( crs().mapUnits() ) % QLatin1String( "
" ) % tr( "Max scale" ) % QLatin1String( "" ) % QString::number( maximumScale() ) % QLatin1String( "
" ) % tr( "Min scale" ) % QLatin1String( "" ) % QString::number( minimumScale() ) % QLatin1String( "
" ) % tr( "Feature count" ) % QLatin1String( "" ) % QString::number( featureCount() ) % QLatin1String( "
\n

" ) % tr( "Fields" ) % QLatin1String( "

\n
\n\n" ); + + // primary key QgsAttributeList pkAttrList = pkAttributeList(); if ( !pkAttrList.isEmpty() ) { - myMetadata += R"(

)" + tr( "Primary key attributes" ) + "

\n"; - myMetadata += QLatin1String( "

" ); + myMetadata += QLatin1String( "

\n" ); } - myMetadata += QLatin1String( "

\n" ); + const QgsFields &myFields = pendingFields(); - //extents in project cs + // count fields + myMetadata += QLatin1String( "\n" ); - try - { -#if 0 - // TODO: currently disabled, will revisit later [MD] - QgsRectangle myProjectedExtent = coordinateTransform->transformBoundingBox( extent() ); - myMetadata += "

" + tr( "In project spatial reference system units" ) + "

\n"; - myMetadata += "

"; - myMetadata += tr( "xMin,yMin %1,%2 : xMax,yMax %3,%4" ) - .arg( myProjectedExtent.xMinimum() ) - .arg( myProjectedExtent.yMinimum() ) - .arg( myProjectedExtent.xMaximum() ) - .arg( myProjectedExtent.yMaximum() ); - myMetadata += "

\n"; -#endif + myMetadata += "
" ) % tr( "Primary key attributes" ) % QLatin1String( "" ); Q_FOREACH ( int idx, pkAttrList ) { - myMetadata += fields().at( idx ).name() + ' '; - } - myMetadata += QLatin1String( "

\n" ); - } - - - //feature count - myMetadata += R"(

)" + tr( "The number of features in this layer" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += QString::number( featureCount() ); - myMetadata += QLatin1String( "

\n" ); - //capabilities - myMetadata += R"(

)" + tr( "Capabilities of this layer" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += capabilitiesString(); - myMetadata += QLatin1String( "

\n" ); - - //------------- - - QgsRectangle myExtent = extent(); - myMetadata += QLatin1String( R"(

)" ); - myMetadata += tr( "Extents" ); - myMetadata += QLatin1String( "

\n" ); - - //extents in layer cs TODO...maybe make a little nested table to improve layout... - myMetadata += R"(

)" + tr( "In layer spatial reference system units" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - // Try to be a bit clever over what number format we use for the - // extents. Some people don't like it using scientific notation when the - // numbers get large, but for small numbers this is the more practical - // option (so we can't force the format to 'f' for all values). - // The scheme: - // - for all numbers with more than 5 digits, force non-scientific notation - // and 2 digits after the decimal point. - // - for all smaller numbers let the OS decide which format to use (it will - // generally use non-scientific unless the number gets much less than 1). - - if ( !myExtent.isEmpty() ) - { - QString xMin, yMin, xMax, yMax; - double changeoverValue = 99999; // The 'largest' 5 digit number - if ( qAbs( myExtent.xMinimum() ) > changeoverValue ) - { - xMin = QStringLiteral( "%1" ).arg( myExtent.xMinimum(), 0, 'f', 2 ); - } - else - { - xMin = QStringLiteral( "%1" ).arg( myExtent.xMinimum() ); - } - if ( qAbs( myExtent.yMinimum() ) > changeoverValue ) - { - yMin = QStringLiteral( "%1" ).arg( myExtent.yMinimum(), 0, 'f', 2 ); - } - else - { - yMin = QStringLiteral( "%1" ).arg( myExtent.yMinimum() ); - } - if ( qAbs( myExtent.xMaximum() ) > changeoverValue ) - { - xMax = QStringLiteral( "%1" ).arg( myExtent.xMaximum(), 0, 'f', 2 ); - } - else - { - xMax = QStringLiteral( "%1" ).arg( myExtent.xMaximum() ); - } - if ( qAbs( myExtent.yMaximum() ) > changeoverValue ) - { - yMax = QStringLiteral( "%1" ).arg( myExtent.yMaximum(), 0, 'f', 2 ); - } - else - { - yMax = QStringLiteral( "%1" ).arg( myExtent.yMaximum() ); + myMetadata += fields().at( idx ).name() % ' '; } - - myMetadata += tr( "xMin,yMin %1,%2 : xMax,yMax %3,%4" ) - .arg( xMin, yMin, xMax, yMax ); - } - else - { - myMetadata += tr( "unknown extent" ); + myMetadata += QLatin1String( "

" ) % tr( "Count" ) % QLatin1String( "" ) % QString::number( myFields.size() ) % QLatin1String( "
\n
\n"; + myMetadata += "\n"; - // - // Display layer spatial ref system - // - myMetadata += R"(

)" + tr( "Layer Spatial Reference System" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += crs().toProj4().replace( '"', QLatin1String( " \"" ) ); - myMetadata += QLatin1String( "

\n" ); - - // - // Display project (output) spatial ref system - // -#if 0 - // TODO: disabled for now, will revisit later [MD] - //myMetadata += " < td bgcolor = \"gray\">"; - myMetadata += "

" + tr( "Project (Output) Spatial Reference System" ) + "

\n"; - myMetadata += "

"; - myMetadata += coordinateTransform->destCRS().toProj4().replace( '"', " \"" ); - myMetadata += "

\n"; -#endif - } - catch ( QgsCsException &cse ) + for ( int i = 0; i < myFields.size(); ++i ) { - Q_UNUSED( cse ); - QgsDebugMsg( cse.what() ); + QgsField myField = myFields.at( i ); + QString rowClass = QString( "" ); + if ( i % 2 ) + rowClass = QString( "class=\"odd-row\"" ); + myMetadata += "\n"; + } - myMetadata += R"(

)" + tr( "In project spatial reference system units" ) + "

\n"; - myMetadata += QLatin1String( "

" ); - myMetadata += tr( "(Invalid transformation of layer extents)" ); - myMetadata += QLatin1String( "

\n" ); + //close field list and start references + myMetadata += QLatin1String( "
" % tr( "Field" ) % "" % tr( "Type" ) % "" % tr( "Length" ) % "" % tr( "Precision" ) % "" % tr( "Comment" ) % "
" % myField.name() % "" % myField.typeName() % "" % QString::number( myField.length() ) % "" % QString::number( myField.precision() ) % "" % myField.comment() % "
\n

" ) % tr( "References" ) % QLatin1String( "

\n
\n\n" ); - } + // data URL + myMetadata += QLatin1String( "\n" ); + myMetadata += QLatin1String( "\n" ); -#if 0 - // - // Add the info about each field in the attribute table - // - myMetadata += "

" + tr( "Attribute field info" ) + "

\n"; - myMetadata += "

"; - - // Start a nested table in this trow - myMetadata += "

" ) % tr( "Data URL" ) % QLatin1String( "" ) % dataUrl() % QLatin1String( "
" ) % tr( "Data Format" ) % QLatin1String( "" ) % dataUrlFormat() % QLatin1String( "
"; - myMetadata += ""; - myMetadata += ""; - myMetadata += ""; - myMetadata += ""; - myMetadata += ""; - - //get info for each field by looping through them - const QgsFields &myFields = pendingFields(); - for ( int i = 0, n = myFields.size(); i < n; ++i ) - { - QgsField myField = fields.at( i ); - - myMetadata += ""; - myMetadata += ""; - myMetadata += ""; - myMetadata += ""; - myMetadata += ""; - } - - //close field list - myMetadata += "
"; - myMetadata += tr( "Field" ); - myMetadata += ""; - myMetadata += tr( "Type" ); - myMetadata += ""; - myMetadata += tr( "Length" ); - myMetadata += ""; - myMetadata += tr( "Precision" ); - myMetadata += ""; - myMetadata += tr( "Comment" ); - myMetadata += "
"; - myMetadata += myField.name(); - myMetadata += ""; - myMetadata += myField.typeName(); - myMetadata += ""; - myMetadata += QString( "%1" ).arg( myField.length() ); - myMetadata += ""; - myMetadata += QString( "%1" ).arg( myField.precision() ); - myMetadata += ""; - myMetadata += QString( "%1" ).arg( myField.comment() ); - myMetadata += "
"; //end of nested table -#endif + // attribution + myMetadata += QLatin1String( "" ) % tr( "Attribution" ) % QLatin1String( "" ) % attribution() % QLatin1String( "\n" ); + myMetadata += QLatin1String( "" ) % tr( "Attribution URL" ) % QLatin1String( "" ) % attributionUrl() % QLatin1String( "\n" ); + + // metadata URL + myMetadata += QLatin1String( "" ) % tr( "Metadata URL" ) % QLatin1String( "" ) % metadataUrl() % QLatin1String( "\n" ); + myMetadata += QLatin1String( "" ) % tr( "Metadata Type" ) % QLatin1String( "" ) % metadataUrlType() % QLatin1String( "\n" ); + myMetadata += QLatin1String( "" ) % tr( "Metadata Format" ) % QLatin1String( "" ) % metadataUrlFormat() % QLatin1String( "\n" ); + + // legend URL + myMetadata += QLatin1String( "" ) % tr( "Legend URL" ) % QLatin1String( "" ) % legendUrl() % QLatin1String( "\n" ); + myMetadata += QLatin1String( "" ) % tr( "Legend Format" ) % QLatin1String( "" ) % legendUrlFormat() % QLatin1String( "\n" ); - myMetadata += QLatin1String( "" ); + myMetadata += QStringLiteral( "\n\n" ); return myMetadata; } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ff09a264877b..0296d731ec54 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -762,6 +762,7 @@ INCLUDE_DIRECTORIES( ) INCLUDE_DIRECTORIES(SYSTEM ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ${QWT_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${QSCINTILLA_INCLUDE_DIR} diff --git a/src/gui/auth/qgsautheditorwidgets.cpp b/src/gui/auth/qgsautheditorwidgets.cpp index 13b49b700ee9..f91155d118ac 100644 --- a/src/gui/auth/qgsautheditorwidgets.cpp +++ b/src/gui/auth/qgsautheditorwidgets.cpp @@ -152,6 +152,20 @@ void QgsAuthEditorWidgets::setupUtilitiesMenu() mActionRemoveAuthConfigs = new QAction( QStringLiteral( "Remove all authentication configurations" ), this ); mActionEraseAuthDatabase = new QAction( QStringLiteral( "Erase authentication database" ), this ); + mActionPasswordHelperSync = new QAction( tr( "Store/update the master password in your %1" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ), this ); + mActionPasswordHelperDelete = new QAction( tr( "Clear the master password from your %1" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ), this ); + mActionPasswordHelperEnable = new QAction( tr( "Integrate master password with your %1" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ), this ); + mActionPasswordHelperLoggingEnable = new QAction( tr( "Enable password helper debug log" ), this ); + + mActionPasswordHelperEnable->setCheckable( true ); + mActionPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled( ) ); + + mActionPasswordHelperLoggingEnable->setCheckable( true ); + mActionPasswordHelperLoggingEnable->setChecked( QgsAuthManager::instance()->passwordHelperLoggingEnabled( ) ); + connect( mActionSetMasterPassword, &QAction::triggered, this, &QgsAuthEditorWidgets::setMasterPassword ); connect( mActionClearCachedMasterPassword, &QAction::triggered, this, &QgsAuthEditorWidgets::clearCachedMasterPassword ); connect( mActionResetMasterPassword, &QAction::triggered, this, &QgsAuthEditorWidgets::resetMasterPassword ); @@ -159,11 +173,21 @@ void QgsAuthEditorWidgets::setupUtilitiesMenu() connect( mActionRemoveAuthConfigs, &QAction::triggered, this, &QgsAuthEditorWidgets::removeAuthenticationConfigs ); connect( mActionEraseAuthDatabase, &QAction::triggered, this, &QgsAuthEditorWidgets::eraseAuthenticationDatabase ); + connect( mActionPasswordHelperSync, &QAction::triggered, this, &QgsAuthEditorWidgets::passwordHelperSync ); + connect( mActionPasswordHelperDelete, &QAction::triggered, this, &QgsAuthEditorWidgets::passwordHelperDelete ); + connect( mActionPasswordHelperEnable, &QAction::triggered, this, &QgsAuthEditorWidgets::passwordHelperEnableTriggered ); + connect( mActionPasswordHelperLoggingEnable, &QAction::triggered, this, &QgsAuthEditorWidgets::passwordHelperLoggingEnableTriggered ); + mAuthUtilitiesMenu = new QMenu( this ); mAuthUtilitiesMenu->addAction( mActionSetMasterPassword ); mAuthUtilitiesMenu->addAction( mActionClearCachedMasterPassword ); mAuthUtilitiesMenu->addAction( mActionResetMasterPassword ); mAuthUtilitiesMenu->addSeparator(); + mAuthUtilitiesMenu->addAction( mActionPasswordHelperEnable ); + mAuthUtilitiesMenu->addAction( mActionPasswordHelperSync ); + mAuthUtilitiesMenu->addAction( mActionPasswordHelperDelete ); + mAuthUtilitiesMenu->addAction( mActionPasswordHelperLoggingEnable ); + mAuthUtilitiesMenu->addSeparator(); mAuthUtilitiesMenu->addAction( mActionClearCachedAuthConfigs ); mAuthUtilitiesMenu->addAction( mActionRemoveAuthConfigs ); mAuthUtilitiesMenu->addSeparator(); @@ -208,6 +232,27 @@ void QgsAuthEditorWidgets::authMessageOut( const QString &message, const QString messageBar()->pushMessage( authtag, message, ( QgsMessageBar::MessageLevel )levelint, 7 ); } +void QgsAuthEditorWidgets::passwordHelperDelete() +{ + QgsAuthGuiUtils::passwordHelperDelete( messageBar(), messageTimeout(), this ); +} + +void QgsAuthEditorWidgets::passwordHelperSync() +{ + QgsAuthGuiUtils::passwordHelperSync( messageBar(), messageTimeout() ); +} + +void QgsAuthEditorWidgets::passwordHelperEnableTriggered() +{ + // Only fire on real changes + QgsAuthGuiUtils::passwordHelperEnable( mActionPasswordHelperEnable->isChecked(), messageBar(), messageTimeout() ); +} + +void QgsAuthEditorWidgets::passwordHelperLoggingEnableTriggered() +{ + QgsAuthGuiUtils::passwordHelperLoggingEnable( mActionPasswordHelperLoggingEnable->isChecked(), messageBar(), messageTimeout() ); +} + QgsMessageBar *QgsAuthEditorWidgets::messageBar() { return mMsgBar; diff --git a/src/gui/auth/qgsautheditorwidgets.h b/src/gui/auth/qgsautheditorwidgets.h index 89f7ca6a123c..e2ac4daa1a71 100644 --- a/src/gui/auth/qgsautheditorwidgets.h +++ b/src/gui/auth/qgsautheditorwidgets.h @@ -88,6 +88,18 @@ class GUI_EXPORT QgsAuthEditorWidgets : public QWidget, private Ui::QgsAuthEdito //! Relay messages to widget's messagebar void authMessageOut( const QString &message, const QString &authtag, QgsAuthManager::MessageLevel level ); + //! Remove master password from wallet + void passwordHelperDelete( ); + + //! Store master password into the wallet + void passwordHelperSync( ); + + //! Toggle password helper (enable/disable) + void passwordHelperEnableTriggered( ); + + //! Toggle password helper logging (enable/disable) + void passwordHelperLoggingEnableTriggered( ); + private: void setupUtilitiesMenu(); @@ -101,6 +113,10 @@ class GUI_EXPORT QgsAuthEditorWidgets : public QWidget, private Ui::QgsAuthEdito QAction *mActionClearCachedAuthConfigs = nullptr; QAction *mActionRemoveAuthConfigs = nullptr; QAction *mActionEraseAuthDatabase = nullptr; + QAction *mActionPasswordHelperDelete = nullptr; + QAction *mActionPasswordHelperSync = nullptr; + QAction *mActionPasswordHelperEnable = nullptr; + QAction *mActionPasswordHelperLoggingEnable = nullptr; }; #endif // QGSAUTHEDITORWIDGETS_H diff --git a/src/gui/auth/qgsauthguiutils.cpp b/src/gui/auth/qgsauthguiutils.cpp index 9d141c3fb3f9..5ababdac6e69 100644 --- a/src/gui/auth/qgsauthguiutils.cpp +++ b/src/gui/auth/qgsauthguiutils.cpp @@ -260,3 +260,73 @@ QString QgsAuthGuiUtils::getOpenFileName( QWidget *parent, const QString &title, } return f; } + +void QgsAuthGuiUtils::passwordHelperDelete( QgsMessageBar *msgbar, int timeout, QWidget *parent ) +{ + if ( QMessageBox::warning( parent, + QObject::tr( "Delete confirmation" ), + QObject::tr( "Do you really want to delete the master password from your %1?" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Cancel ) == QMessageBox::Cancel ) + { + return; + } + QString msg; + QgsMessageBar::MessageLevel level; + if ( ! QgsAuthManager::instance()->passwordHelperDelete() ) + { + msg = QgsAuthManager::instance()->passwordHelperErrorMessage(); + level = QgsMessageBar::WARNING; + } + else + { + msg = QObject::tr( "Master password was successfully deleted from your %1" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ); + + level = QgsMessageBar::INFO; + } + msgbar->pushMessage( QObject::tr( "Password helper delete" ), msg, level, timeout ); +} + +void QgsAuthGuiUtils::passwordHelperSync( QgsMessageBar *msgbar, int timeout ) +{ + QString msg; + QgsMessageBar::MessageLevel level; + if ( ! QgsAuthManager::instance()->masterPasswordIsSet( ) ) + { + msg = QObject::tr( "Master password is not set and cannot be stored in your %1" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ); + level = QgsMessageBar::WARNING; + } + else if ( ! QgsAuthManager::instance()->passwordHelperSync() ) + { + msg = QgsAuthManager::instance()->passwordHelperErrorMessage(); + level = QgsMessageBar::WARNING; + } + else + { + msg = QObject::tr( "Master password has been successfully stored in your %1" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ); + + level = QgsMessageBar::INFO; + } + msgbar->pushMessage( QObject::tr( "Password helper write" ), msg, level, timeout ); +} + +void QgsAuthGuiUtils::passwordHelperEnable( bool enabled, QgsMessageBar *msgbar, int timeout ) +{ + QgsAuthManager::instance()->setPasswordHelperEnabled( enabled ); + QString msg = enabled ? QObject::tr( "Your %1 will be used from now on to store and retrieve the master password." ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ) : + QObject::tr( "Your %1 will not be used anymore to store and retrieve the master password." ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ); + msgbar->pushMessage( QObject::tr( "Password helper write" ), msg, QgsMessageBar::INFO, timeout ); +} + +void QgsAuthGuiUtils::passwordHelperLoggingEnable( bool enabled, QgsMessageBar *msgbar, int timeout ) +{ + Q_UNUSED( msgbar ); + Q_UNUSED( timeout ); + QgsAuthManager::instance()->setPasswordHelperLoggingEnabled( enabled ); +} diff --git a/src/gui/auth/qgsauthguiutils.h b/src/gui/auth/qgsauthguiutils.h index e1ad64e39673..8dd18bb9830d 100644 --- a/src/gui/auth/qgsauthguiutils.h +++ b/src/gui/auth/qgsauthguiutils.h @@ -80,6 +80,19 @@ class GUI_EXPORT QgsAuthGuiUtils //! Open file dialog for auth associated widgets static QString getOpenFileName( QWidget *parent, const QString &title, const QString &extfilter ); + + //! Remove master password from wallet + static void passwordHelperDelete( QgsMessageBar *msgbar, int timeout = 0, QWidget *parent = nullptr ); + + //! Store master password into the wallet + static void passwordHelperSync( QgsMessageBar *msgbar, int timeout = 0 ); + + //! Set password helper enabled (enable/disable) + static void passwordHelperEnable( bool enabled, QgsMessageBar *msgbar, int timeout = 0 ); + + //! Set password helper logging enabled (enable/disable) + static void passwordHelperLoggingEnable( bool enabled, QgsMessageBar *msgbar, int timeout = 0 ); + }; #endif // QGSAUTHGUIUTILS_H diff --git a/src/gui/qgscredentialdialog.cpp b/src/gui/qgscredentialdialog.cpp index 448760597307..201f63743193 100644 --- a/src/gui/qgscredentialdialog.cpp +++ b/src/gui/qgscredentialdialog.cpp @@ -19,9 +19,9 @@ #include "qgsauthmanager.h" #include "qgslogger.h" +#include "qgssettings.h" #include -#include #include static QString invalidStyle_( const QString &selector = QStringLiteral( "QLineEdit" ) ) @@ -43,6 +43,8 @@ QgsCredentialDialog::QgsCredentialDialog( QWidget *parent, Qt::WindowFlags fl ) Qt::BlockingQueuedConnection ); mOkButton = buttonBox->button( QDialogButtonBox::Ok ); leMasterPass->setPlaceholderText( tr( "Required" ) ); + chkbxPasswordHelperEnable->setText( tr( "Store/update the master password in your %1" ) + .arg( QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME ) ); leUsername->setFocus(); } @@ -68,6 +70,7 @@ void QgsCredentialDialog::requestCredentials( const QString &realm, QString *use QgsDebugMsg( "Entering." ); stackedWidget->setCurrentIndex( 0 ); + chkbxPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled( ) ); labelRealm->setText( realm ); leUsername->setText( *username ); lePassword->setText( *password ); @@ -121,6 +124,8 @@ void QgsCredentialDialog::requestCredentialsMasterPassword( QString *password, b QString titletxt( stored ? tr( "Enter CURRENT master authentication password" ) : tr( "Set NEW master authentication password" ) ); lblPasswordTitle->setText( titletxt ); + chkbxPasswordHelperEnable->setChecked( QgsAuthManager::instance()->passwordHelperEnabled( ) ); + leMasterPassVerify->setVisible( !stored ); lblDontForget->setVisible( !stored ); @@ -170,6 +175,11 @@ void QgsCredentialDialog::requestCredentialsMasterPassword( QString *password, b else { *password = leMasterPass->text(); + // Let's store user's preferences to use the password helper + if ( chkbxPasswordHelperEnable->isChecked() != QgsAuthManager::instance()->passwordHelperEnabled( ) ) + { + QgsAuthManager::instance()->setPasswordHelperEnabled( chkbxPasswordHelperEnable->isChecked() ); + } } break; } diff --git a/src/providers/mssql/qgsmssqlprovider.cpp b/src/providers/mssql/qgsmssqlprovider.cpp index 521d7d3bcd95..fccede10f269 100644 --- a/src/providers/mssql/qgsmssqlprovider.cpp +++ b/src/providers/mssql/qgsmssqlprovider.cpp @@ -823,6 +823,9 @@ bool QgsMssqlProvider::addFeatures( QgsFeatureList &flist ) QgsField fld = mAttributeFields.at( i ); + if ( fld.typeName().toLower() == QLatin1String( "timestamp" ) ) + continue; // You can't update timestamp columns they are server only. + if ( fld.typeName().endsWith( QLatin1String( " identity" ), Qt::CaseInsensitive ) ) continue; // skip identity field @@ -897,6 +900,9 @@ bool QgsMssqlProvider::addFeatures( QgsFeatureList &flist ) QgsField fld = mAttributeFields.at( i ); + if ( fld.typeName().toLower() == QLatin1String( "timestamp" ) ) + continue; // You can't update timestamp columns they are server only. + if ( fld.typeName().endsWith( QLatin1String( " identity" ), Qt::CaseInsensitive ) ) continue; // skip identity field @@ -1127,6 +1133,9 @@ bool QgsMssqlProvider::changeAttributeValues( const QgsChangedAttributesMap &att { QgsField fld = mAttributeFields.at( it2.key() ); + if ( fld.typeName().toLower() == QLatin1String( "timestamp" ) ) + continue; // You can't update timestamp columns they are server only. + if ( fld.typeName().endsWith( QLatin1String( " identity" ), Qt::CaseInsensitive ) ) continue; // skip identity field @@ -1158,6 +1167,9 @@ bool QgsMssqlProvider::changeAttributeValues( const QgsChangedAttributesMap &att { QgsField fld = mAttributeFields.at( it2.key() ); + if ( fld.typeName().toLower() == QLatin1String( "timestamp" ) ) + continue; // You can't update timestamp columns they are server only. + if ( fld.typeName().endsWith( QLatin1String( " identity" ), Qt::CaseInsensitive ) ) continue; // skip identity field diff --git a/src/providers/postgres/CMakeLists.txt b/src/providers/postgres/CMakeLists.txt index 601a1de3b092..8eefdd5e1a36 100644 --- a/src/providers/postgres/CMakeLists.txt +++ b/src/providers/postgres/CMakeLists.txt @@ -49,6 +49,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${GEOS_INCLUDE_DIR} ${QSCINTILLA_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ../../core diff --git a/src/providers/wcs/CMakeLists.txt b/src/providers/wcs/CMakeLists.txt index 542fa59832b6..565ec17e28c8 100644 --- a/src/providers/wcs/CMakeLists.txt +++ b/src/providers/wcs/CMakeLists.txt @@ -32,6 +32,7 @@ INCLUDE_DIRECTORIES( INCLUDE_DIRECTORIES(SYSTEM ${GDAL_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) ADD_LIBRARY(wcsprovider MODULE ${WCS_SRCS} ${WCS_MOC_SRCS}) diff --git a/src/providers/wfs/CMakeLists.txt b/src/providers/wfs/CMakeLists.txt index befa85fa96d4..ea11207254e3 100644 --- a/src/providers/wfs/CMakeLists.txt +++ b/src/providers/wfs/CMakeLists.txt @@ -53,6 +53,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${EXPAT_INCLUDE_DIR} ${QSCINTILLA_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} # needed by qgsvectorfilewriter.h ${SQLITE3_INCLUDE_DIR} ) diff --git a/src/providers/wms/CMakeLists.txt b/src/providers/wms/CMakeLists.txt index b2195812ff15..42a06896cdaf 100644 --- a/src/providers/wms/CMakeLists.txt +++ b/src/providers/wms/CMakeLists.txt @@ -43,6 +43,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${GEOS_INCLUDE_DIR} ${QT_QTSCRIPT_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) ADD_LIBRARY(wmsprovider_a STATIC ${WMS_SRCS} ${WMS_MOC_SRCS}) diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index e111f01d711d..27d83941f80b 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -105,6 +105,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${QT_INCLUDE_DIR} ${QGIS_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${CMAKE_BINARY_DIR}/src/core diff --git a/src/server/qgswmsprojectparser.cpp b/src/server/qgswmsprojectparser.cpp index ac6a8a13d453..a5f8572849c8 100644 --- a/src/server/qgswmsprojectparser.cpp +++ b/src/server/qgswmsprojectparser.cpp @@ -2106,7 +2106,6 @@ void QgsWmsProjectParser::drawOverlays( QPainter *p, int dpi, int width, int hei //consider DPI double scaleFactor = dpi / 88.0; //assume 88 as standard dpi - QgsRectangle prjExtent = mProjectParser->projectExtent(); //text annotations QList< QPair< QTextDocument *, QDomElement > >::const_iterator textIt = mTextAnnotationItems.constBegin(); diff --git a/src/ui/qgscrashdialog.ui b/src/ui/qgscrashdialog.ui new file mode 100644 index 000000000000..52c59c175b6c --- /dev/null +++ b/src/ui/qgscrashdialog.ui @@ -0,0 +1,600 @@ + + + QgsCrashDialog + + + + 0 + 0 + 603 + 346 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 170 + 170 + 170 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 170 + 170 + 170 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 127 + 127 + 127 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 170 + 170 + 170 + + + + + + + 127 + 127 + 127 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 127 + 127 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + Dialog + + + + + + + + + + + + + + :/images/icons/qgis_icon.svg + + + false + + + Qt::AlignCenter + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + MS Shell Dlg 2 + 30 + 9 + false + false + + + + font: 75 30pt "MS Shell Dlg 2"; + + + Header + + + + + + + Message + + + true + + + + + + + Help Message + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + + 200 + 0 + + + + Reload QGIS + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Quit + + + + + + + + + + + + + + + pushButton + clicked() + QgsCrashDialog + accept() + + + 352 + 257 + + + 468 + -36 + + + + + pushButton_2 + clicked() + QgsCrashDialog + reject() + + + 486 + 246 + + + 457 + -30 + + + + + diff --git a/src/ui/qgscredentialdialog.ui b/src/ui/qgscredentialdialog.ui index 1fd3f077ad20..c592cefe03b9 100644 --- a/src/ui/qgscredentialdialog.ui +++ b/src/ui/qgscredentialdialog.ui @@ -6,7 +6,7 @@ 0 0 - 277 + 396 289 @@ -30,7 +30,7 @@ - 0 + 1 @@ -154,6 +154,13 @@ + + + + Store master password in your password manager + + + diff --git a/src/ui/qgsvectorlayerpropertiesbase.ui b/src/ui/qgsvectorlayerpropertiesbase.ui index 06448078c041..1b6045118f9b 100755 --- a/src/ui/qgsvectorlayerpropertiesbase.ui +++ b/src/ui/qgsvectorlayerpropertiesbase.ui @@ -98,14 +98,38 @@ - General + Information + + + Information + + + + :/images/themes/default/propertyicons/metadata.svg:/images/themes/default/propertyicons/metadata.svg + + + + + Metadata General - :/images/themes/default/propertyicons/general.svg:/images/themes/default/propertyicons/general.svg + :/images/themes/default/console/iconShowEditorConsole.png:/images/themes/default/console/iconShowEditorConsole.png + + + + + Source + + + Source + + + + :/images/themes/default/propertyicons/system.svg:/images/themes/default/propertyicons/system.svg @@ -204,18 +228,6 @@ :/images/themes/default/propertyicons/diagram.svg:/images/themes/default/propertyicons/diagram.svg - - - Metadata - - - Metadata - - - - :/images/themes/default/propertyicons/metadata.svg:/images/themes/default/propertyicons/metadata.svg - - Variables @@ -284,6 +296,17 @@ 0 + + + + + + QFrame::NoFrame + + + + + @@ -311,263 +334,397 @@ 0 0 - 652 - 541 + 629 + 572 - - 0 - - - 0 - - - 0 - - - 0 - - + - Layer info + Description - vectorgeneral + vectormeta - - - + + + - Layer name + DataUrl - - - - - 0 - - - 0 - - - 0 - - - 0 - - + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + The abstract is a descriptive narrative providing more information about the layer. + - - + + + + Abstract + + - - - - true + + + + The title is for the benefit of humans to identify layer. + + + The title is for the benefit of humans to identify layer. - - + + + + A name used to identify the layer. The short name is a text string used for machine-to-machine communication. + + + + - displayed as + + + + A name used to identify the layer. The short name is a text string used for machine-to-machine communication. - - + + - Layer source + Short name - - - - true - - - true + + + + Keyword list - - + + - - - - 0 - 0 - + + + An URL of the data presentation. - - Data source encoding + + An URL of the data presentation. - + + + Format + + - - - Qt::Horizontal - - - - 0 - 20 - - - + + + + text/html + + + + + text/plain + + + + + application/pdf + + + - - - - - - - Qt::StrongFocus - - - Coordinate reference system - - - false - - - vectorgeneral - - - - 6 - - - - - Qt::StrongFocus + + + + List of keywords separated by comma to help catalog searching. + + + List of keywords separated by comma to help catalog searching. - - + + + + Title + + + + + + + Layer name + + + + + - - - Create spatial index - - + - + - Update extents + displayed as - - - Qt::Horizontal + + + true - - - 40 - 20 - + + color: #505050; +background-color: #F0F0F0; +border: 1px solid #B0B0B0; +border-radius: 2px; - + + true + + - - - - Qt::Vertical - - - - + - Scale dependent visibility + Attribution - - true + + vectormeta - + - - - Qt::StrongFocus + + + Title - + + + + Attribution's title indicates the provider of the layer. + + + Attribution's title indicates the provider of the data layer. + + + + + + + Url + + + + + + + Attribution's url gives a link to the webpage of the provider of the data layer. + + + Attribution's url gives a link to the webpage of the provider of the data layer. + + + + - - - - 0 - 3 - - + - Provider feature filter + MetadataUrl - - - + + vectormeta + + + + - Query Builder + Url - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 0 - 21 - - - - - - - - false - - - false + + + + The URL of the metadata document. - - false + + The URL of the metadata document. + + + + + + Type + + + + + + + + + + + + + FGDC + + + + + TC211 + + + + + + + + Format + + + + + + + + + + + + + text/plain + + + + + text/xml + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + LegendUrl + + + + + + + + Url + + + + + + + An URL of the legend image. + + + An URL of the legend image. + + + + + + + Format + + + + + + + + 137 + 0 + + + + 0 + + + + image/png + + + + + image/jpeg + + + + + image/jpg + + + + + + - txtSubsetSQL - pbnQueryBuilder @@ -589,8 +746,8 @@ - - + + 0 @@ -604,57 +761,284 @@ 0 - + QFrame::NoFrame true - + 0 0 - 100 - 30 + 644 + 522 - - - 0 - - - 0 - - - 0 - - - 0 - + - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Sunken - - - -1 + + + Settings - - - - - + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Data source encoding + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + + + Qt::StrongFocus + + + Coordinate reference system + + + false + + + vectorgeneral + + + + 6 + + + + + Set source coordinate reference system + + + + + + + Qt::StrongFocus + + label_5 + + + + + + + + Create spatial index + + + + + + + Update extents + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + + + + + + + + 0 + 3 + + + + Provider feature filter + + + + + + Query Builder + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 0 + 21 + + + + + + + + false + + + false + + + false + + + + + txtSubsetSQL + pbnQueryBuilder + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 644 + 522 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Sunken + + + -1 + + + + + + @@ -717,8 +1101,8 @@ 0 0 - 100 - 30 + 644 + 522 @@ -777,11 +1161,30 @@ 0 0 - 523 - 222 + 686 + 507 + + + + Scale dependent visibility + + + true + + + + + + Qt::StrongFocus + + + + + + @@ -953,7 +1356,7 @@ - + Qt::Vertical @@ -1160,8 +1563,8 @@ 0 0 - 104 - 103 + 199 + 123 @@ -1302,436 +1705,6 @@ - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 297 - 556 - - - - - - - Description - - - vectormeta - - - - - - Title - - - - - - - The title is for the benefit of humans to identify layer. - - - The title is for the benefit of humans to identify layer. - - - - - - - Abstract - - - - - - - - 0 - 0 - - - - - 16777215 - 50 - - - - The abstract is a descriptive narrative providing more information about the layer. - - - - - - - Keyword list - - - - - - - List of keywords separated by comma to help catalog searching. - - - List of keywords separated by comma to help catalog searching. - - - - - - - DataUrl - - - - - - - - - An URL of the data presentation. - - - An URL of the data presentation. - - - - - - - Format - - - - - - - - text/html - - - - - text/plain - - - - - application/pdf - - - - - - - - - - Short name - - - - - - - A name used to identify the layer. The short name is a text string used for machine-to-machine communication. - - - - - - - - - A name used to identify the layer. The short name is a text string used for machine-to-machine communication. - - - - - - - - - - Attribution - - - vectormeta - - - - - - Title - - - - - - - Attribution's title indicates the provider of the layer. - - - Attribution's title indicates the provider of the data layer. - - - - - - - Url - - - - - - - Attribution's url gives a link to the webpage of the provider of the data layer. - - - Attribution's url gives a link to the webpage of the provider of the data layer. - - - - - - - - - - MetadataUrl - - - vectormeta - - - - - - Url - - - - - - - The URL of the metadata document. - - - The URL of the metadata document. - - - - - - - - - Type - - - - - - - - - - - - - FGDC - - - - - TC211 - - - - - - - - Format - - - - - - - - - - - - - text/plain - - - - - text/xml - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - LegendUrl - - - - - - - - Url - - - - - - - An URL of the legend image. - - - An URL of the legend image. - - - - - - - Format - - - - - - - - 137 - 0 - - - - 0 - - - - image/png - - - - - image/jpeg - - - - - image/jpg - - - - - - - - - - - - - - 0 - 5 - - - - Properties - - - vectormeta - - - - - - 2 - - - true - - - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - - @@ -1936,19 +1909,6 @@ mOptionsListWidget scrollArea - groupBox - mLayerOrigNameLineEdit - txtDisplayName - txtLayerSource - cboProviderEncoding - indexGroupBox - mCrsSelector - pbnIndex - pbnUpdateExtents - mScaleVisibilityGroupBox - mScaleRangeWidget - mSubsetGroupBox - txtSubsetSQL scrollArea_3 scrollArea_5 scrollArea_19 @@ -1969,28 +1929,7 @@ mButtonRemoveJoin mButtonEditJoin mJoinTreeWidget - scrollArea_2 - mMetaDescriptionGrpBx - mLayerShortNameLineEdit - mLayerTitleLineEdit - mLayerAbstractTextEdit - mLayerKeywordListLineEdit - mLayerDataUrlLineEdit - mLayerDataUrlFormatComboBox - mMetaAttributionGrpBx - mLayerAttributionLineEdit - mLayerAttributionUrlLineEdit - mMetaMetaUrlGrpBx - mLayerMetadataUrlLineEdit - mLayerMetadataUrlTypeComboBox - mLayerMetadataUrlFormatComboBox - mMetaLegendGrpBx - mLayerLegendUrlLineEdit - mLayerLegendUrlFormatComboBox - mMetaPropertiesGrpBx - teMetadata mLayersDependenciesTreeView - pbnQueryBuilder diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index e0fd497770e5..b1470479a117 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -32,6 +32,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ${QSCINTILLA_INCLUDE_DIR} ) diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 13a870f30cd5..b70b95d5b5b7 100644 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -26,6 +26,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ) diff --git a/tests/src/core/testqgsauthmanager.cpp b/tests/src/core/testqgsauthmanager.cpp index c975eafb33ee..7d254f0af5f9 100644 --- a/tests/src/core/testqgsauthmanager.cpp +++ b/tests/src/core/testqgsauthmanager.cpp @@ -27,6 +27,7 @@ #include "qgsapplication.h" #include "qgsauthmanager.h" #include "qgsauthconfig.h" +#include "qgssettings.h" /** \ingroup UnitTests * Unit tests for QgsAuthManager @@ -38,15 +39,20 @@ class TestQgsAuthManager: public QObject public: TestQgsAuthManager(); + public slots: + + void doSync(); + private slots: void initTestCase(); void cleanupTestCase(); void init(); - void cleanup() {} + void cleanup(); void testMasterPassword(); void testAuthConfigs(); void testAuthMethods(); + void testPasswordHelper(); private: void cleanupTempDir(); @@ -129,6 +135,13 @@ void TestQgsAuthManager::initTestCase() // all tests should now have a valid qgis-auth.db and stored/set master password } +void TestQgsAuthManager::cleanup() +{ + // Restore password_helper_insecure_fallback value + QgsSettings settings; + settings.setValue( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ); +} + void TestQgsAuthManager::cleanupTempDir() { QDir tmpDir = QDir( mTempDir ); @@ -402,5 +415,58 @@ QList TestQgsAuthManager::registerAuthConfigs() return configs; } + +void TestQgsAuthManager::doSync() +{ + QgsAuthManager *authm = QgsAuthManager::instance(); + QVERIFY( authm->passwordHelperSync( ) ); +} + +void TestQgsAuthManager::testPasswordHelper() +{ + + QgsAuthManager *authm = QgsAuthManager::instance(); + authm->clearMasterPassword(); + + QgsSettings settings; + settings.setValue( QStringLiteral( "password_helper_insecure_fallback" ), true, QgsSettings::Section::Auth ); + + // Test enable/disable + // It should be enabled by default + QVERIFY( authm->passwordHelperEnabled() ); + authm->setPasswordHelperEnabled( false ); + QVERIFY( ! authm->passwordHelperEnabled() ); + authm->setPasswordHelperEnabled( true ); + QVERIFY( authm->passwordHelperEnabled() ); + + // Sync with wallet + QVERIFY( authm->setMasterPassword( mPass, true ) ); + QVERIFY( authm->masterPasswordIsSet( ) ); + QObject::connect( authm, &QgsAuthManager::passwordHelperSuccess, + QApplication::instance(), &QCoreApplication::quit ); + QObject::connect( authm, &QgsAuthManager::passwordHelperFailure, + QApplication::instance(), &QCoreApplication::quit ); + QMetaObject::invokeMethod( this, "doSync", Qt::QueuedConnection ); + qApp->exec(); + authm->clearMasterPassword(); + QVERIFY( authm->setMasterPassword( ) ); + QVERIFY( authm->masterPasswordIsSet( ) ); + + // Delete from wallet + authm->clearMasterPassword(); + QVERIFY( authm->passwordHelperDelete( ) ); + QVERIFY( ! authm->setMasterPassword( ) ); + QVERIFY( ! authm->masterPasswordIsSet( ) ); + + // Re-sync + QVERIFY( authm->setMasterPassword( mPass, true ) ); + QMetaObject::invokeMethod( this, "doSync", Qt::QueuedConnection ); + qApp->exec(); + authm->clearMasterPassword(); + QVERIFY( authm->setMasterPassword( ) ); + QVERIFY( authm->masterPasswordIsSet( ) ); + +} + QGSTEST_MAIN( TestQgsAuthManager ) #include "testqgsauthmanager.moc" diff --git a/tests/src/core/testqgscomposition.cpp b/tests/src/core/testqgscomposition.cpp index 9c5ef50f02d8..1ad4ca6ce53b 100644 --- a/tests/src/core/testqgscomposition.cpp +++ b/tests/src/core/testqgscomposition.cpp @@ -27,6 +27,11 @@ #include "qgsmultirenderchecker.h" #include "qgsfillsymbollayer.h" #include "qgsproject.h" +#include "qgscomposerlegend.h" +#include "qgsvectorlayer.h" +#include "qgslayertreegroup.h" +#include "qgslayertreelayer.h" +#include "qgslayertree.h" #include #include "qgstest.h" @@ -58,6 +63,8 @@ class TestQgsComposition : public QObject void variablesEdited(); void itemVariablesFunction(); void referenceMap(); + void legendRestoredFromTemplate(); + void legendRestoredFromTemplateAutoUpdate(); private: QgsComposition *mComposition = nullptr; @@ -659,5 +666,142 @@ void TestQgsComposition::referenceMap() delete composition; } +void TestQgsComposition::legendRestoredFromTemplate() +{ + // load a layer + + QFileInfo vectorFileInfo( QString( TEST_DATA_DIR ) + "/points.shp" ); + QgsVectorLayer *layer = new QgsVectorLayer( vectorFileInfo.filePath(), + vectorFileInfo.completeBaseName(), + "ogr" ); + QgsProject p; + p.addMapLayer( layer ); + + // create composition + QgsComposition c( &p ); + // add a legend + QgsComposerLegend *legend = new QgsComposerLegend( &c ); + c.addComposerLegend( legend ); + legend->setAutoUpdateModel( false ); + + QgsLegendModel *model = legend->model(); + QgsLayerTreeNode *node = model->rootGroup()->children().at( 0 ); + // make sure we've got right node + QgsLayerTreeLayer *layerNode = dynamic_cast< QgsLayerTreeLayer * >( node ); + QVERIFY( layerNode ); + QCOMPARE( layerNode->layer(), layer ); + + // got it! + layerNode->setCustomProperty( "legend/title-label", QString( "new title!" ) ); + // make sure new title stuck + QCOMPARE( model->data( model->node2index( layerNode ), Qt::DisplayRole ).toString(), QString( "new title!" ) ); + + // save composition to template + QDomDocument doc; + QDomElement composerElem = doc.createElement( "Composer" ); + doc.appendChild( composerElem ); + c.writeXml( composerElem, doc ); + c.atlasComposition().writeXml( composerElem, doc ); + + + // make a new composition from template + QgsComposition c2( &p ); + QVERIFY( c2.loadFromTemplate( doc ) ); + // get legend from new composition + QList< QgsComposerLegend * > legends2; + c2.composerItems( legends2 ); + QgsComposerLegend *legend2 = legends2.at( 0 ); + QVERIFY( legend2 ); + + QgsLegendModel *model2 = legend2->model(); + QgsLayerTreeNode *node2 = model2->rootGroup()->children().at( 0 ); + QgsLayerTreeLayer *layerNode2 = dynamic_cast< QgsLayerTreeLayer * >( node2 ); + QVERIFY( layerNode2 ); + QCOMPARE( layerNode2->layer(), layer ); + QCOMPARE( model2->data( model->node2index( layerNode2 ), Qt::DisplayRole ).toString(), QString( "new title!" ) ); + +#if 0 //expected failure (#2738) + QString oldId = layer->id(); + // new test + // remove existing layer + p.removeMapLayer( layer ); + + // reload it, with a new id + QgsVectorLayer *layer2 = new QgsVectorLayer( vectorFileInfo.filePath(), + vectorFileInfo.completeBaseName(), + "ogr" ); + p.addMapLayer( layer2 ); + QVERIFY( oldId != layer2->id() ); + + // load composition from template + QgsComposition c3( &p ); + QVERIFY( c3.loadFromTemplate( doc ) ); + // get legend from new composition + QList< QgsComposerLegend * > legends3; + c3.composerItems( legends3 ); + QgsComposerLegend *legend3 = legends3.at( 0 ); + QVERIFY( legend3 ); + + //make sure customisation remains intact + QgsLegendModel *model3 = legend3->model(); + QgsLayerTreeNode *node3 = model3->rootGroup()->children().at( 0 ); + QgsLayerTreeLayer *layerNode3 = dynamic_cast< QgsLayerTreeLayer * >( node3 ); + QVERIFY( layerNode3 ); + QCOMPARE( layerNode3->layer(), layer2 ); + QCOMPARE( model3->data( model->node2index( layerNode3 ), Qt::DisplayRole ).toString(), QString( "new title!" ) ); +#endif +} + +void TestQgsComposition::legendRestoredFromTemplateAutoUpdate() +{ + // load a layer + + QFileInfo vectorFileInfo( QString( TEST_DATA_DIR ) + "/points.shp" ); + QgsVectorLayer *layer = new QgsVectorLayer( vectorFileInfo.filePath(), + vectorFileInfo.completeBaseName(), + "ogr" ); + QgsProject p; + p.addMapLayer( layer ); + + // create composition + QgsComposition c( &p ); + // add a legend + QgsComposerLegend *legend = new QgsComposerLegend( &c ); + c.addComposerLegend( legend ); + legend->setAutoUpdateModel( true ); + + QgsLegendModel *model = legend->model(); + QgsLayerTreeNode *node = model->rootGroup()->children().at( 0 ); + // make sure we've got right node + QgsLayerTreeLayer *layerNode = dynamic_cast< QgsLayerTreeLayer * >( node ); + QVERIFY( layerNode ); + QCOMPARE( layerNode->layer(), layer ); + QCOMPARE( model->data( model->node2index( layerNode ), Qt::DisplayRole ).toString(), QString( "points" ) ); + + // save composition to template + QDomDocument doc; + QDomElement composerElem = doc.createElement( "Composer" ); + doc.appendChild( composerElem ); + c.writeXml( composerElem, doc ); + c.atlasComposition().writeXml( composerElem, doc ); + + + // make a new composition from template + QgsComposition c2( &p ); + QVERIFY( c2.loadFromTemplate( doc ) ); + // get legend from new composition + QList< QgsComposerLegend * > legends2; + c2.composerItems( legends2 ); + QgsComposerLegend *legend2 = legends2.at( 0 ); + QVERIFY( legend2 ); + + QgsLegendModel *model2 = legend2->model(); + QgsLayerTreeNode *node2 = model2->rootGroup()->children().at( 0 ); + QgsLayerTreeLayer *layerNode2 = dynamic_cast< QgsLayerTreeLayer * >( node2 ); + QVERIFY( layerNode2 ); + QCOMPARE( layerNode2->layer(), layer ); + QCOMPARE( model2->data( model->node2index( layerNode2 ), Qt::DisplayRole ).toString(), QString( "points" ) ); +} + QGSTEST_MAIN( TestQgsComposition ) #include "testqgscomposition.moc" diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt index 0fe606a638c2..bf8b68cd226b 100644 --- a/tests/src/gui/CMakeLists.txt +++ b/tests/src/gui/CMakeLists.txt @@ -30,6 +30,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${GEOS_INCLUDE_DIR} ${QWT_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ${QSCINTILLA_INCLUDE_DIR} ) diff --git a/tests/src/providers/CMakeLists.txt b/tests/src/providers/CMakeLists.txt index d5a6b46a22a7..2b6ebf3d932f 100644 --- a/tests/src/providers/CMakeLists.txt +++ b/tests/src/providers/CMakeLists.txt @@ -20,6 +20,7 @@ INCLUDE_DIRECTORIES(SYSTEM ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${QCA_INCLUDE_DIR} + ${QTKEYCHAIN_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 61752e96f874..f139e1083c92 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -19,6 +19,7 @@ ADD_PYTHON_TEST(PyQgsAttributeTableModel test_qgsattributetablemodel.py) #ADD_PYTHON_TEST(PyQgsAuthenticationSystem test_qgsauthsystem.py) ADD_PYTHON_TEST(PyQgsBearingUtils test_qgsbearingutils.py) ADD_PYTHON_TEST(PyQgsBlendModes test_qgsblendmodes.py) +ADD_PYTHON_TEST(PyQgsBox3d test_qgsbox3d.py) ADD_PYTHON_TEST(PyQgsCategorizedSymbolRenderer test_qgscategorizedsymbolrenderer.py) ADD_PYTHON_TEST(PyQgsCheckableComboBox test_qgscheckablecombobox.py) ADD_PYTHON_TEST(PyQgsColorButton test_qgscolorbutton.py) diff --git a/tests/src/python/test_qgsbox3d.py b/tests/src/python/test_qgsbox3d.py new file mode 100644 index 000000000000..a8f9c03519ae --- /dev/null +++ b/tests/src/python/test_qgsbox3d.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsBox3d. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '11/04/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.core import (QgsBox3d, + QgsPoint, + QgsPointV2, + QgsWkbTypes, + QgsRectangle) + +from qgis.testing import unittest + + +class TestQgsBox3d(unittest.TestCase): + + def testCtor(self): + box = QgsBox3d(5.0, 6.0, 7.0, 10.0, 11.0, 12.0) + + self.assertEqual(box.xMinimum(), 5.0) + self.assertEqual(box.yMinimum(), 6.0) + self.assertEqual(box.zMinimum(), 7.0) + self.assertEqual(box.xMaximum(), 10.0) + self.assertEqual(box.yMaximum(), 11.0) + self.assertEqual(box.zMaximum(), 12.0) + + box = QgsBox3d(QgsPointV2(QgsWkbTypes.PointZ, 5, 6, 7), QgsPointV2(QgsWkbTypes.PointZ, 10, 11, 12)) + self.assertEqual(box.xMinimum(), 5.0) + self.assertEqual(box.yMinimum(), 6.0) + self.assertEqual(box.zMinimum(), 7.0) + self.assertEqual(box.xMaximum(), 10.0) + self.assertEqual(box.yMaximum(), 11.0) + self.assertEqual(box.zMaximum(), 12.0) + + # point constructor should normalize + box = QgsBox3d(QgsPointV2(QgsWkbTypes.PointZ, 10, 11, 12), QgsPointV2(QgsWkbTypes.PointZ, 5, 6, 7)) + self.assertEqual(box.xMinimum(), 5.0) + self.assertEqual(box.yMinimum(), 6.0) + self.assertEqual(box.zMinimum(), 7.0) + self.assertEqual(box.xMaximum(), 10.0) + self.assertEqual(box.yMaximum(), 11.0) + self.assertEqual(box.zMaximum(), 12.0) + + def testSetters(self): + box = QgsBox3d(5.0, 6.0, 7.0, 10.0, 11.0, 12.0) + + box.setXMinimum(35.0) + box.setYMinimum(36.0) + box.setZMinimum(37.0) + box.setXMaximum(40.0) + box.setYMaximum(41.0) + box.setZMaximum(42.0) + self.assertEqual(box.xMinimum(), 35.0) + self.assertEqual(box.yMinimum(), 36.0) + self.assertEqual(box.zMinimum(), 37.0) + self.assertEqual(box.xMaximum(), 40.0) + self.assertEqual(box.yMaximum(), 41.0) + self.assertEqual(box.zMaximum(), 42.0) + + def testNormalize(self): + box = QgsBox3d() + box.setXMinimum(10.0) + box.setYMinimum(11.0) + box.setZMinimum(12.0) + box.setXMaximum(5.0) + box.setYMaximum(6.0) + box.setZMaximum(7.0) + + box.normalize() + self.assertEqual(box.xMinimum(), 5.0) + self.assertEqual(box.yMinimum(), 6.0) + self.assertEqual(box.zMinimum(), 7.0) + self.assertEqual(box.xMaximum(), 10.0) + self.assertEqual(box.yMaximum(), 11.0) + self.assertEqual(box.zMaximum(), 12.0) + + def testDimensions(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + self.assertEqual(box.width(), 6.0) + self.assertEqual(box.height(), 7.0) + self.assertEqual(box.depth(), 8.0) + + def testIntersect(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + box2 = box.intersect(QgsBox3d(7.0, 8.0, 9.0, 10.0, 11.0, 12.0)) + self.assertEqual(box2.xMinimum(), 7.0) + self.assertEqual(box2.yMinimum(), 8.0) + self.assertEqual(box2.zMinimum(), 9.0) + self.assertEqual(box2.xMaximum(), 10.0) + self.assertEqual(box2.yMaximum(), 11.0) + self.assertEqual(box2.zMaximum(), 12.0) + box2 = box.intersect(QgsBox3d(0.0, 1.0, 2.0, 100.0, 111.0, 112.0)) + self.assertEqual(box2.xMinimum(), 5.0) + self.assertEqual(box2.yMinimum(), 6.0) + self.assertEqual(box2.zMinimum(), 7.0) + self.assertEqual(box2.xMaximum(), 11.0) + self.assertEqual(box2.yMaximum(), 13.0) + self.assertEqual(box2.zMaximum(), 15.0) + box2 = box.intersect(QgsBox3d(1.0, 2.0, 3.0, 6.0, 7.0, 8.0)) + self.assertEqual(box2.xMinimum(), 5.0) + self.assertEqual(box2.yMinimum(), 6.0) + self.assertEqual(box2.zMinimum(), 7.0) + self.assertEqual(box2.xMaximum(), 6.0) + self.assertEqual(box2.yMaximum(), 7.0) + self.assertEqual(box2.zMaximum(), 8.0) + + def testIntersects(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + self.assertTrue(box.intersects(QgsBox3d(7.0, 8.0, 9.0, 10.0, 11.0, 12.0))) + self.assertTrue(box.intersects(QgsBox3d(0.0, 1.0, 2.0, 100.0, 111.0, 112.0))) + self.assertTrue(box.intersects(QgsBox3d(1.0, 2.0, 3.0, 6.0, 7.0, 8.0))) + self.assertFalse(box.intersects(QgsBox3d(15.0, 16.0, 17.0, 110.0, 112.0, 113.0))) + self.assertFalse(box.intersects(QgsBox3d(5.0, 6.0, 17.0, 11.0, 13.0, 113.0))) + self.assertFalse(box.intersects(QgsBox3d(5.0, 16.0, 7.0, 11.0, 23.0, 15.0))) + self.assertFalse(box.intersects(QgsBox3d(15.0, 6.0, 7.0, 21.0, 13.0, 15.0))) + + def testContains(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + self.assertTrue(box.contains(QgsBox3d(7.0, 8.0, 9.0, 10.0, 11.0, 12.0))) + self.assertFalse(box.contains(QgsBox3d(0.0, 1.0, 2.0, 100.0, 111.0, 112.0))) + self.assertFalse(box.contains(QgsBox3d(1.0, 2.0, 3.0, 6.0, 7.0, 8.0))) + self.assertFalse(box.contains(QgsBox3d(15.0, 16.0, 17.0, 110.0, 112.0, 113.0))) + self.assertFalse(box.contains(QgsBox3d(5.0, 6.0, 17.0, 11.0, 13.0, 113.0))) + self.assertFalse(box.contains(QgsBox3d(5.0, 16.0, 7.0, 11.0, 23.0, 15.0))) + self.assertFalse(box.contains(QgsBox3d(15.0, 6.0, 7.0, 21.0, 13.0, 15.0))) + + def testContainsPoint(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + self.assertTrue(box.contains(QgsPointV2(QgsWkbTypes.PointZ, 6, 7, 8))) + self.assertFalse(box.contains(QgsPointV2(QgsWkbTypes.PointZ, 16, 7, 8))) + self.assertFalse(box.contains(QgsPointV2(QgsWkbTypes.PointZ, 6, 17, 8))) + self.assertFalse(box.contains(QgsPointV2(QgsWkbTypes.PointZ, 6, 7, 18))) + # 2d containment + self.assertTrue(box.contains(QgsPointV2(QgsWkbTypes.Point, 6, 7))) + self.assertFalse(box.contains(QgsPointV2(QgsWkbTypes.Point, 16, 7))) + self.assertFalse(box.contains(QgsPointV2(QgsWkbTypes.Point, 6, 17))) + + def testVolume(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + self.assertEqual(box.volume(), 336.0) + + def testToRectangle(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + rect = box.toRectangle() + self.assertEqual(rect, QgsRectangle(5, 6, 11, 13)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgsrectangle.py b/tests/src/python/test_qgsrectangle.py index 4e432fb8b1e7..241d873aafdc 100644 --- a/tests/src/python/test_qgsrectangle.py +++ b/tests/src/python/test_qgsrectangle.py @@ -76,6 +76,11 @@ def testDimensions(self): (20.0, rect.height())) assert rect.height() == 20.0, myMessage + def testCalculations(self): + rect = QgsRectangle(0.0, 1.0, 20.0, 10.0) + self.assertEqual(rect.area(), 180.0) + self.assertEqual(rect.perimeter(), 58.0) + def testIntersection(self): rect1 = QgsRectangle(0.0, 0.0, 5.0, 5.0) rect2 = QgsRectangle(2.0, 2.0, 7.0, 7.0) @@ -237,6 +242,16 @@ def testToString(self): (myExpectedString, myString)) assert myString == myExpectedString, myMessage + def testToBox3d(self): + rect = QgsRectangle(0, 0.1, 0.2, 0.3) + box = rect.toBox3d(0.4, 0.5) + self.assertEqual(box.xMinimum(), 0.0) + self.assertEqual(box.yMinimum(), 0.1) + self.assertEqual(box.zMinimum(), 0.4) + self.assertEqual(box.xMaximum(), 0.2) + self.assertEqual(box.yMaximum(), 0.3) + self.assertEqual(box.zMaximum(), 0.5) + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_qgssettings.py b/tests/src/python/test_qgssettings.py index e90a8fc81744..b5dec17bf8a0 100644 --- a/tests/src/python/test_qgssettings.py +++ b/tests/src/python/test_qgssettings.py @@ -68,6 +68,14 @@ def addArrayToDefaults(self, prefix, key, values): self.globalsettings.endArray() self.globalsettings.sync() + def addGroupToDefaults(self, prefix, kvp): + defaults = QSettings(self.settings.globalSettingsPath(), QSettings.IniFormat) # NOQA + self.globalsettings.beginGroup(prefix) + for k, v in kvp.items(): + self.globalsettings.setValue(k, v) + self.globalsettings.endGroup() + self.globalsettings.sync() + def test_basic_functionality(self): self.assertEqual(self.settings.value('testqgissettings/doesnotexists', 'notexist'), 'notexist') self.settings.setValue('testqgissettings/name', 'qgisrocks') @@ -95,13 +103,69 @@ def test_allkeys(self): self.assertEqual(3, len(self.settings.allKeys())) self.assertEqual(2, len(self.globalsettings.allKeys())) - def test_precedence(self): + def test_precedence_simple(self): self.assertEqual(self.settings.allKeys(), []) self.addToDefaults('testqgissettings/names/name1', 'qgisrocks1') self.settings.setValue('testqgissettings/names/name1', 'qgisrocks-1') self.assertEqual(self.settings.value('testqgissettings/names/name1'), 'qgisrocks-1') + def test_precedence_group(self): + """Test if user can override a group value""" + self.assertEqual(self.settings.allKeys(), []) + self.addGroupToDefaults('connections-xyz', { + 'OSM': 'http://a.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'OSM-b': 'http://b.tile.openstreetmap.org/{z}/{x}/{y}.png', + }) + self.settings.beginGroup('connections-xyz') + self.assertEqual(self.settings.value('OSM'), 'http://a.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.assertEqual(self.settings.value('OSM-b'), 'http://b.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.settings.endGroup() + + # Override edit + self.settings.beginGroup('connections-xyz') + self.settings.setValue('OSM', 'http://c.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.settings.endGroup() + + # Check it again! + self.settings.beginGroup('connections-xyz') + self.assertEqual(self.settings.value('OSM'), 'http://c.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.assertEqual(self.settings.value('OSM-b'), 'http://b.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.settings.endGroup() + + # Override remove: the global value will be resumed!!! + self.settings.beginGroup('connections-xyz') + self.settings.remove('OSM') + self.settings.endGroup() + + # Check it again! + self.settings.beginGroup('connections-xyz') + self.assertEqual(self.settings.value('OSM'), 'http://a.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.assertEqual(self.settings.value('OSM-b'), 'http://b.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.settings.endGroup() + + # Override remove: store a blank! + self.settings.beginGroup('connections-xyz') + self.settings.setValue('OSM', '') + self.settings.endGroup() + + # Check it again! + self.settings.beginGroup('connections-xyz') + self.assertEqual(self.settings.value('OSM'), '') + self.assertEqual(self.settings.value('OSM-b'), 'http://b.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.settings.endGroup() + + # Override remove: store a None: will resume the global setting! + self.settings.beginGroup('connections-xyz') + self.settings.setValue('OSM', None) + self.settings.endGroup() + + # Check it again! + self.settings.beginGroup('connections-xyz') + self.assertEqual(self.settings.value('OSM'), 'http://a.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.assertEqual(self.settings.value('OSM-b'), 'http://b.tile.openstreetmap.org/{z}/{x}/{y}.png') + self.settings.endGroup() + def test_uft8(self): self.assertEqual(self.settings.allKeys(), []) self.addToDefaults('testqgissettings/names/namèé↓1', 'qgisrocks↓1') @@ -153,6 +217,35 @@ def test_array(self): self.assertEqual(values, ['qgisrocks1', 'qgisrocks2', 'qgisrocks3']) + def test_array_overrides(self): + """Test if an array completely shadows the global one""" + self.assertEqual(self.settings.allKeys(), []) + self.addArrayToDefaults('testqgissettings', 'key', ['qgisrocks1', 'qgisrocks2', 'qgisrocks3']) + self.assertEqual(self.settings.allKeys(), ['testqgissettings/1/key', 'testqgissettings/2/key', 'testqgissettings/3/key', 'testqgissettings/size']) + self.assertEqual(self.globalsettings.allKeys(), ['testqgissettings/1/key', 'testqgissettings/2/key', 'testqgissettings/3/key', 'testqgissettings/size']) + + self.assertEqual(3, self.globalsettings.beginReadArray('testqgissettings')) + self.globalsettings.endArray() + self.assertEqual(3, self.settings.beginReadArray('testqgissettings')) + + # Now override! + self.settings.beginWriteArray('testqgissettings') + self.settings.setArrayIndex(0) + self.settings.setValue('key', 'myqgisrocksmore1') + self.settings.setArrayIndex(1) + self.settings.setValue('key', 'myqgisrocksmore2') + self.settings.endArray() + + # Check it! + self.assertEqual(2, self.settings.beginReadArray('testqgissettings')) + + values = [] + for i in range(2): + self.settings.setArrayIndex(i) + values.append(self.settings.value("key")) + + self.assertEqual(values, ['myqgisrocksmore1', 'myqgisrocksmore2']) + def test_section_getters_setters(self): self.assertEqual(self.settings.allKeys(), [])