From 64d3c788f19acc19ca428274a355ae696e344f77 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Fri, 26 Aug 2016 15:36:07 +0200 Subject: [PATCH] Use toolbar or status bar for snapping config --- images/images.qrc | 9 + images/themes/default/mIconSnapping.svg | 105 ++ .../default/mIconSnappingActiveLayer.svg | 1133 ++++++++++++++ .../themes/default/mIconSnappingAdvanced.svg | 1363 +++++++++++++++++ .../themes/default/mIconSnappingAllLayers.svg | 1167 ++++++++++++++ .../default/mIconSnappingIntersection.svg | 1124 ++++++++++++++ .../themes/default/mIconSnappingSegment.svg | 1105 +++++++++++++ images/themes/default/mIconSnappingVertex.svg | 1120 ++++++++++++++ .../default/mIconSnappingVertexAndSegment.svg | 1126 ++++++++++++++ .../default/mIconTopologicalEditing.svg | 1118 ++++++++++++++ python/core/core.sip | 1 + python/core/qgsmaplayermodel.sip | 2 - python/core/qgsproject.sip | 38 +- python/core/qgssnappingconfig.sip | 238 +++ python/core/qgssnappingutils.sip | 36 +- src/app/CMakeLists.txt | 4 + src/app/qgisapp.cpp | 63 +- src/app/qgisapp.h | 34 +- src/app/qgsmaptooloffsetcurve.cpp | 21 +- src/app/qgsoptions.cpp | 14 +- src/app/qgssnappingdialog.cpp | 16 +- src/app/qgssnappinglayertreemodel.cpp | 636 ++++++++ src/app/qgssnappinglayertreemodel.h | 94 ++ src/app/qgssnappingwidget.cpp | 476 ++++++ src/app/qgssnappingwidget.h | 130 ++ src/core/CMakeLists.txt | 3 + src/core/qgsproject.cpp | 162 +- src/core/qgsproject.h | 79 +- src/core/qgssnappingconfig.cpp | 509 ++++++ src/core/qgssnappingconfig.h | 245 +++ src/core/qgssnappingutils.cpp | 234 +-- src/core/qgssnappingutils.h | 52 +- src/gui/qgsmapcanvastracer.cpp | 11 +- src/gui/qgsmapmouseevent.cpp | 38 +- src/ui/qgisapp.ui | 13 +- src/ui/qgsoptionsbase.ui | 132 +- tests/src/core/testqgssnappingutils.cpp | 40 +- tests/src/python/test_layer_dependencies.py | 32 +- 38 files changed, 12138 insertions(+), 585 deletions(-) create mode 100644 images/themes/default/mIconSnapping.svg create mode 100644 images/themes/default/mIconSnappingActiveLayer.svg create mode 100644 images/themes/default/mIconSnappingAdvanced.svg create mode 100644 images/themes/default/mIconSnappingAllLayers.svg create mode 100644 images/themes/default/mIconSnappingIntersection.svg create mode 100644 images/themes/default/mIconSnappingSegment.svg create mode 100644 images/themes/default/mIconSnappingVertex.svg create mode 100644 images/themes/default/mIconSnappingVertexAndSegment.svg create mode 100644 images/themes/default/mIconTopologicalEditing.svg create mode 100644 python/core/qgssnappingconfig.sip create mode 100644 src/app/qgssnappinglayertreemodel.cpp create mode 100644 src/app/qgssnappinglayertreemodel.h create mode 100644 src/app/qgssnappingwidget.cpp create mode 100644 src/app/qgssnappingwidget.h create mode 100644 src/core/qgssnappingconfig.cpp create mode 100644 src/core/qgssnappingconfig.h diff --git a/images/images.qrc b/images/images.qrc index f3c4e148bba..c1f9ec46097 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -574,6 +574,15 @@ themes/default/mIconClearText.svg themes/default/mIconClearTextHover.svg themes/default/rendererPointClusterSymbol.svg + themes/default/mIconSnapping.svg + themes/default/mIconSnappingActiveLayer.svg + themes/default/mIconSnappingAdvanced.svg + themes/default/mIconSnappingAllLayers.svg + themes/default/mIconSnappingVertexAndSegment.svg + themes/default/mIconSnappingVertex.svg + themes/default/mIconSnappingSegment.svg + themes/default/mIconTopologicalEditing.svg + themes/default/mIconSnappingIntersection.svg qgis_tips/symbol_levels.png diff --git a/images/themes/default/mIconSnapping.svg b/images/themes/default/mIconSnapping.svg new file mode 100644 index 00000000000..6151ab07d8a --- /dev/null +++ b/images/themes/default/mIconSnapping.svg @@ -0,0 +1,105 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconSnappingActiveLayer.svg b/images/themes/default/mIconSnappingActiveLayer.svg new file mode 100644 index 00000000000..03bda8119bc --- /dev/null +++ b/images/themes/default/mIconSnappingActiveLayer.svg @@ -0,0 +1,1133 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconSnappingAdvanced.svg b/images/themes/default/mIconSnappingAdvanced.svg new file mode 100644 index 00000000000..07db9ec22c4 --- /dev/null +++ b/images/themes/default/mIconSnappingAdvanced.svg @@ -0,0 +1,1363 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconSnappingAllLayers.svg b/images/themes/default/mIconSnappingAllLayers.svg new file mode 100644 index 00000000000..2f49090185d --- /dev/null +++ b/images/themes/default/mIconSnappingAllLayers.svg @@ -0,0 +1,1167 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconSnappingIntersection.svg b/images/themes/default/mIconSnappingIntersection.svg new file mode 100644 index 00000000000..9fd14fa2bd6 --- /dev/null +++ b/images/themes/default/mIconSnappingIntersection.svg @@ -0,0 +1,1124 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconSnappingSegment.svg b/images/themes/default/mIconSnappingSegment.svg new file mode 100644 index 00000000000..d7ddc89cf34 --- /dev/null +++ b/images/themes/default/mIconSnappingSegment.svg @@ -0,0 +1,1105 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconSnappingVertex.svg b/images/themes/default/mIconSnappingVertex.svg new file mode 100644 index 00000000000..4102cfa7522 --- /dev/null +++ b/images/themes/default/mIconSnappingVertex.svg @@ -0,0 +1,1120 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconSnappingVertexAndSegment.svg b/images/themes/default/mIconSnappingVertexAndSegment.svg new file mode 100644 index 00000000000..86614fe7ae0 --- /dev/null +++ b/images/themes/default/mIconSnappingVertexAndSegment.svg @@ -0,0 +1,1126 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + + + + diff --git a/images/themes/default/mIconTopologicalEditing.svg b/images/themes/default/mIconTopologicalEditing.svg new file mode 100644 index 00000000000..dd01ee70b4b --- /dev/null +++ b/images/themes/default/mIconTopologicalEditing.svg @@ -0,0 +1,1118 @@ + + + + + ring fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + ring fill + 2014-02-04 + + + Robert Szczepanek + + + + + + + + + + ring fill + + + GIS icons 0.2 + + + + + + + + + + + + + + + + + + diff --git a/python/core/core.sip b/python/core/core.sip index 6dd36227ae6..267998d9a73 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -111,6 +111,7 @@ %Include qgspointlocator.sip %Include qgsproject.sip %Include qgsprojectproperty.sip +%Include qgssnappingconfig.sip %Include qgsprojectversion.sip %Include qgsprovidermetadata.sip %Include qgsproviderregistry.sip diff --git a/python/core/qgsmaplayermodel.sip b/python/core/qgsmaplayermodel.sip index 3ea7faa7587..80565e911cb 100644 --- a/python/core/qgsmaplayermodel.sip +++ b/python/core/qgsmaplayermodel.sip @@ -65,8 +65,6 @@ class QgsMapLayerModel : QAbstractItemModel /** * Returns strings for all roles supported by this model. - * - * @note Available only with Qt5 (python and c++) */ QHash roleNames() const; diff --git a/python/core/qgsproject.sip b/python/core/qgsproject.sip index c4790386a12..334e7b6dc3d 100644 --- a/python/core/qgsproject.sip +++ b/python/core/qgsproject.sip @@ -274,14 +274,6 @@ class QgsProject : QObject */ QgsLayerTreeGroup* createEmbeddedGroup( const QString& groupName, const QString& projectFilePath, const QStringList &invisibleLayers ); - /** Convenience function to set snap settings per layer */ - void setSnapSettingsForLayer( const QString& layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, - bool avoidIntersection ); - - /** Convenience function to query snap settings of a layer */ - bool snapSettingsForLayer( const QString& layerId, bool& enabled /Out/, QgsSnapper::SnappingType& type /Out/, QgsTolerance::UnitType& units /Out/, double& tolerance /Out/, - bool& avoidIntersection /Out/ ) const; - /** Convenience function to set topological editing */ void setTopologicalEditing( bool enabled ); @@ -389,27 +381,14 @@ class QgsProject : QObject QgsExpressionContext createExpressionContext() const; - protected: - - /** Set error message from read/write operation - * @note not available in Python bindings + /** + * The snapping configuration for this project. */ - //void setError( const QString& errorMessage ); - - /** Clear error message - * @note not available in Python bindings + QgsSnappingConfig snappingConfig() const; + /** + * The snapping configuration for this project. */ - //void clearError(); - - //! Creates layer and adds it to maplayer registry - //! @note not available in python bindings - // bool addLayer( const QDomElement& layerElem, QList& brokenNodes, QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList ); - - //! @note not available in python bindings - // void initializeEmbeddedSubtree( const QString& projectFilePath, QgsLayerTreeGroup* group ); - - //! @note not available in python bindings - // void loadEmbeddedNodes( QgsLayerTreeGroup* group ); + void setSnappingConfig( const QgsSnappingConfig& snappingConfig ); signals: //! emitted when project is being read @@ -451,8 +430,6 @@ class QgsProject : QObject void loadingLayer( const QString& ); - void snapSettingsChanged(); - //! Emitted when the list of layer which are excluded from map identification changes void nonIdentifiableLayersChanged( QStringList nonIdentifiableLayers ); @@ -462,6 +439,9 @@ class QgsProject : QObject //! Emitted when the home path of the project changes void homePathChanged(); + //! emitted whenever the configuration for snapping has changed + void snappingConfigChanged(); + /** Emitted whenever the expression variables stored in the project have been changed. * @note added in QGIS 3.0 */ diff --git a/python/core/qgssnappingconfig.sip b/python/core/qgssnappingconfig.sip new file mode 100644 index 00000000000..268dbdf47c7 --- /dev/null +++ b/python/core/qgssnappingconfig.sip @@ -0,0 +1,238 @@ +/** \ingroup core + * This is a container for configuration of the snapping of the project + * @note added in 3.0 + */ +class QgsSnappingConfig +{ +%TypeHeaderCode +#include +%End + public: + /** + * SnappingMode defines on which layer the snapping is performed + */ + enum SnappingMode + { + ActiveLayer, /*!< on the active layer */ + AllLayers, /*!< on all vector layers */ + AdvancedConfiguration, /*!< on a per layer configuration basis */ + }; + + /** + * SnappingType defines on what object the snapping is performed + */ + enum SnappingType + { + Vertex, /*!< on vertices only */ + VertexAndSegment, /*!< both on vertices and segments */ + Segment, /*!< on segments only */ + }; + + /** \ingroup core + * This is a container of advanced configuration (per layer) of the snapping of the project + * @note added in 3.0 + */ + class IndividualLayerSettings + { + public: + /** + * @brief IndividualLayerSettings + * @param enabled + * @param type + * @param tolerance + * @param units + * @param avoidIntersection + */ + IndividualLayerSettings( bool enabled, QgsSnappingConfig::SnappingType type, double tolerance, QgsTolerance::UnitType units, bool avoidIntersection = false ); + + /** + * Constructs an invalid setting + */ + IndividualLayerSettings(); + + //! return if settings are valid + bool valid() const; + + //! return if snaping is enbaled + bool enabled() const; + + //! enables the snapping + void setEnabled( bool enabled ); + + //! return the type (vertices and/or segments) + QgsSnappingConfig::SnappingType type() const; + + //! define the type of snapping + void setType( QgsSnappingConfig::SnappingType type ); + + //! return the tolerance + double tolerance() const; + + //! set the tolerance + void setTolerance( double tolerance ); + + //! return the type of units + QgsTolerance::UnitType units() const; + + //! set the type of units + void setUnits( QgsTolerance::UnitType units ); + + //! return if it shall avoid intersection (polygon layers only) + bool avoidIntersection() const; + + //! set if it shall avoid intersection (polygon layers only) + void setAvoidIntersection( bool avoidIntersection ); + + /** + * Compare this configuration to other. + */ + //bool operator!= ( const QgsSnappingConfig::IndividualLayerSettings& other ) const; + + //bool operator== ( const QgsSnappingConfig::IndividualLayerSettings& other ) const; + }; + + /** + * Constructor with default parameters defined in global settings + */ + explicit QgsSnappingConfig(); + + ~QgsSnappingConfig(); + + //! reset to default values + void reset(); + + //! return if snaping is enbaled + bool enabled() const; + + //! enables the snapping + void setEnabled( bool enabled ); + + //! return the mode (all layers, active layer, per layer settings) + QgsSnappingConfig::SnappingMode mode() const; + + //! define the mode of snapping + void setMode( QgsSnappingConfig::SnappingMode mode ); + + //! return the type (vertices and/or segments) + QgsSnappingConfig::SnappingType type() const; + + //! define the type of snapping + void setType( QgsSnappingConfig::SnappingType type ); + + //! return the tolerance + double tolerance() const; + + //! set the tolerance + void setTolerance( double tolerance ); + + //! return the type of units + QgsTolerance::UnitType units() const; + + //! set the type of units + void setUnits( QgsTolerance::UnitType units ); + + //! return if the topological editing is enabled + bool topologicalEditing() const; + + //! set if the topological editing is enabled + void setTopologicalEditing( bool enabled ); + + //! return if the snapping on intersection is enabled + bool intersectionSnapping() const; + + //! set if the snapping on intersection is enabled + void setIntersectionSnapping( bool enabled ); + + //! return individual snapping settings for all layers + SIP_PYDICT individualLayerSettings() const; +%MethodCode + // Create the dictionary. + PyObject* d = PyDict_New(); + if (!d) + return nullptr; + // Set the dictionary elements. + QHash container = sipCpp->individualLayerSettings(); + QHash::const_iterator i = container.constBegin(); + while ( i != container.constEnd() ) + { + QgsVectorLayer* vl = i.key(); + QgsSnappingConfig::IndividualLayerSettings* ils = new QgsSnappingConfig::IndividualLayerSettings( i.value() ); + + PyObject* vlobj = sipConvertFromType( vl, sipType_QgsVectorLayer, nullptr ); + PyObject* ilsobj = sipConvertFromType( ils, sipType_QgsSnappingConfig_IndividualLayerSettings, Py_None ); + + if ( !vlobj || !ilsobj || PyDict_SetItem( d, vlobj, ilsobj) < 0) + { + Py_DECREF( d ); + if ( vlobj ) + { + Py_DECREF( vlobj ); + } + if ( ilsobj ) + { + Py_DECREF( ilsobj ); + } + else + { + delete ils; + } + PyErr_SetString(PyExc_StopIteration,""); + } + Py_DECREF( vlobj ); + Py_DECREF( ilsobj ); + ++i; + } + sipRes = d; +%End + + //! return individual layer snappings settings (applied if mode is AdvancedConfiguration) + QgsSnappingConfig::IndividualLayerSettings individualLayerSettings( QgsVectorLayer* vl ) const; + + //! set individual layer snappings settings (applied if mode is AdvancedConfiguration) + void setIndividualLayerSettings( QgsVectorLayer* vl, QgsSnappingConfig::IndividualLayerSettings individualLayerSettings ); + + /** + * Compare this configuration to other. + */ + bool operator!= ( const QgsSnappingConfig& other ) const; + + public: + /** + * Reads the configuration from the specified QGIS project document. + * + * @note Added in QGIS 3.0 + */ + void readProject( const QDomDocument& doc ); + + /** + * Writes the configuration to the specified QGIS project document. + * + * @note Added in QGIS 3.0 + */ + void writeProject( QDomDocument& doc ); + + /** + * Adds the specified layers as individual layers to the configuration + * with standard configuration. + * When implementing a long-living QgsSnappingConfig (like the one in QgsProject) + * it is best to directly feed this with information from the layer registry. + * + * @return True if changes have been done. + * + * @note Added in QGIS 3.0 + */ + bool addLayers( const QList& layers ); + + + /** + * Removes the specified layers from the individual layer configuration. + * When implementing a long-living QgsSnappingConfig (like the one in QgsProject) + * it is best to directly feed this with information from the layer registry. + * + * @return True if changes have been done. + * + * @note Added in QGIS 3.0 + */ + bool removeLayers( const QList& layers ); + +}; diff --git a/python/core/qgssnappingutils.sip b/python/core/qgssnappingutils.sip index 56e064f961f..4dcaff53494 100644 --- a/python/core/qgssnappingutils.sip +++ b/python/core/qgssnappingutils.sip @@ -25,10 +25,11 @@ class QgsSnappingUtils : QObject /** Assign current map settings to the utils - used for conversion between screen coords to map coords */ void setMapSettings( const QgsMapSettings& settings ); - const QgsMapSettings& mapSettings() const; + QgsMapSettings mapSettings() const; /** Set current layer so that if mode is SnapCurrentLayer we know which layer to use */ void setCurrentLayer( QgsVectorLayer* layer ); + /** The current layer used if mode is SnapCurrentLayer */ QgsVectorLayer* currentLayer() const; @@ -42,11 +43,6 @@ class QgsSnappingUtils : QObject SnapAdvanced, //!< snap according to the configuration set in setLayers() }; - /** Set how the snapping to map is done */ - void setSnapToMapMode( SnapToMapMode mode ); - /** Find out how the snapping to map is done */ - SnapToMapMode snapToMapMode() const; - enum IndexingStrategy { IndexAlwaysFull, //!< For all layers build index of full extent. Uses more memory, but queries are faster. @@ -59,11 +55,6 @@ class QgsSnappingUtils : QObject /** Find out which strategy is used for indexing - by default hybrid indexing is used */ IndexingStrategy indexingStrategy() const; - /** Configure options used when the mode is snap to current layer or to all layers */ - void setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit ); - /** Query options used when the mode is snap to current layer or to all layers */ - void defaultSettings( int& type /Out/, double& tolerance /Out/, QgsTolerance::UnitType& unit /Out/ ); - /** * Configures how a certain layer should be handled in a snapping operation */ @@ -84,28 +75,27 @@ class QgsSnappingUtils : QObject QgsTolerance::UnitType unit; }; - /** Set layers which will be used for snapping */ - void setLayers( const QList& layers ); /** Query layers used for snapping */ QList layers() const; - /** Set whether to consider intersections of nearby segments for snapping */ - void setSnapOnIntersections( bool enabled ); - /** Query whether to consider intersections of nearby segments for snapping */ - bool snapOnIntersections() const; - /** Get extra information about the instance * @note added in QGIS 2.14 */ QString dump(); - public slots: - /** Read snapping configuration from the project */ - void readConfigFromProject(); + /** + * The snapping configuration controls the behavior of this object + */ + QgsSnappingConfig config() const; + + /** + * The snapping configuration controls the behavior of this object + */ + void setConfig( const QgsSnappingConfig& snappingConfig ); signals: - /** Emitted when snapping configuration has been changed - * @note added in QGIS 2.14 + /** + * Emitted when the snapping settings object changes. */ void configChanged(); diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index f452f162870..c0a95ef9f0c 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -49,6 +49,8 @@ SET(QGIS_APP_SRCS qgsmaplayerstyleguiutils.cpp qgsrulebasedlabelingwidget.cpp qgssavestyletodbdialog.cpp + qgssnappinglayertreemodel.cpp + qgssnappingwidget.cpp qgsstatusbarcoordinateswidget.cpp qgsstatusbarmagnifierwidget.cpp qgsstatusbarscalewidget.cpp @@ -228,6 +230,8 @@ SET (QGIS_APP_MOC_HDRS qgsmaplayerstyleguiutils.h qgsrulebasedlabelingwidget.h qgssavestyletodbdialog.h + qgssnappinglayertreemodel.h + qgssnappingwidget.h qgsstatusbarcoordinateswidget.h qgsstatusbarmagnifierwidget.h qgsstatusbarscalewidget.h diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index d5601a6829d..f4d3092d6b5 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -215,6 +216,7 @@ #include "qgsshortcutsmanager.h" #include "qgssinglebandgrayrenderer.h" #include "qgssnappingdialog.h" +#include "qgssnappingwidget.h" #include "qgssourceselectdialog.h" #include "qgssponsors.h" #include "qgsstatisticalsummarydockwidget.h" @@ -589,6 +591,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh , mInternalClipboard( nullptr ) , mShowProjectionTab( false ) , mPythonUtils( nullptr ) + , mSnappingWidget( nullptr ) , mMapStylingDock( nullptr ) , mComposerManager( nullptr ) , mpTileScaleWidget( nullptr ) @@ -758,8 +761,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh startProfile( "Snapping utils" ); mSnappingUtils = new QgsMapCanvasSnappingUtils( mMapCanvas, this ); mMapCanvas->setSnappingUtils( mSnappingUtils ); - connect( QgsProject::instance(), SIGNAL( snapSettingsChanged() ), mSnappingUtils, SLOT( readConfigFromProject() ) ); - connect( this, SIGNAL( projectRead() ), mSnappingUtils, SLOT( readConfigFromProject() ) ); + connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, &QgisApp::onSnappingConfigChanged ); + endProfile(); functionProfile( &QgisApp::createActions, this, "Create actions" ); @@ -807,8 +810,27 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh endProfile(); startProfile( "Snapping dialog" ); - mSnappingDialog = new QgsSnappingDialog( this, mMapCanvas ); - mSnappingDialog->setObjectName( "SnappingOption" ); + mSnappingDialogWidget = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, this ); + QString mainSnappingWidgetMode = QSettings().value( "/qgis/mainSnappingWidgetMode", "dialog" ).toString(); + if ( mainSnappingWidgetMode == "dock" ) + { + QgsSnappingDock* dock = new QgsSnappingDock( tr( "Snapping and Digitizing Options" ), QgisApp::instance() ); + dock->setAllowedAreas( Qt::AllDockWidgetAreas ); + dock->setWidget( mSnappingDialogWidget ); + addDockWidget( Qt::LeftDockWidgetArea, dock ); + mSnappingDialogContainer = dock; + dock->hide(); + } + else + { + QDialog* dialog = new QDialog( this ); + dialog->setWindowTitle( tr( "Project snapping settings" ) ); + QVBoxLayout* layout = new QVBoxLayout( dialog ); + QDialogButtonBox* button = new QDialogButtonBox( QDialogButtonBox::Close ); + layout->addWidget( mSnappingDialogWidget ); + layout->addWidget( button ); + mSnappingDialogContainer = dialog; + } endProfile(); mBrowserWidget = new QgsBrowserDockWidget( tr( "Browser Panel" ), this ); @@ -1160,7 +1182,8 @@ QgisApp::QgisApp() , mAdvancedDigitizingDockWidget( nullptr ) , mStatisticalSummaryDockWidget( nullptr ) , mBookMarksDockWidget( nullptr ) - , mSnappingDialog( nullptr ) + , mSnappingWidget( nullptr ) + , mSnappingDialogContainer( nullptr ) , mPluginManager( nullptr ) , mMapStylingDock( nullptr ) , mMapStyleWidget( nullptr ) @@ -2048,6 +2071,18 @@ void QgisApp::createToolBars() << mWebToolBar << mLabelToolBar; + + // snapping widget as tool bar + QString simpleSnappingWidgetMode = QSettings().value( "/qgis/simpleSnappingWidgetMode", "toolbar" ).toString(); + if ( simpleSnappingWidgetMode != "statusbar" ) + { + mSnappingWidget = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, mSnappingToolBar ); + mSnappingWidget->setObjectName( "mSnappingWidget" ); + //mSnappingWidget->setFont( myFont ); + connect( mSnappingWidget, SIGNAL( snapSettingsChanged() ), QgsProject::instance(), SIGNAL( snapSettingsChanged() ) ); + mSnappingToolBar->addWidget( mSnappingWidget ); + } + QList toolbarMenuActions; // Set action names so that they can be used in customization Q_FOREACH ( QToolBar *toolBar, toolbarMenuToolBars ) @@ -2395,6 +2430,17 @@ void QgisApp::createStatusBar() mRotationLabel->setToolTip( tr( "Current clockwise map rotation in degrees" ) ); statusBar()->addPermanentWidget( mRotationLabel, 0 ); + // snapping widget + QString simpleSnappingWidgetMode = QSettings().value( "/qgis/simpleSnappingWidgetMode", "toolbar" ).toString(); + if ( simpleSnappingWidgetMode == "statusbar" ) + { + mSnappingWidget = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, statusBar() ); + mSnappingWidget->setObjectName( "mSnappingWidget" ); + mSnappingWidget->setFont( myFont ); + connect( mSnappingWidget, SIGNAL( snapSettingsChanged() ), QgsProject::instance(), SIGNAL( snapSettingsChanged() ) ); + statusBar()->addPermanentWidget( mSnappingWidget, 0 ); + } + mRotationEdit = new QgsDoubleSpinBox( statusBar() ); mRotationEdit->setObjectName( "mRotationEdit" ); mRotationEdit->setClearValue( 0.0 ); @@ -7179,7 +7225,7 @@ void QgisApp::offsetPointSymbol() void QgisApp::snappingOptions() { - mSnappingDialog->show(); + mSnappingDialogContainer->show(); } void QgisApp::splitFeatures() @@ -11211,6 +11257,11 @@ void QgisApp::onTransactionGroupsChanged() } } +void QgisApp::onSnappingConfigChanged() +{ + mSnappingUtils->setConfig( QgsProject::instance()->snappingConfig() ); +} + void QgisApp::startProfile( const QString& name ) { mProfiler->start( name ); diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 61b79b7ddad..e1dd8e889fd 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -40,49 +40,50 @@ class QValidator; class QgisAppInterface; class QgisAppStyleSheet; class QgsAnnotationItem; +class QgsAuthManager; +class QgsBookmarks; class QgsClipboard; class QgsComposer; class QgsComposerManager; class QgsComposerView; -class QgsCustomDropHandler; -class QgsStatusBarCoordinatesWidget; -class QgsStatusBarMagnifierWidget; -class QgsStatusBarScaleWidget; class QgsContrastEnhancement; +class QgsCoordinateReferenceSystem; +class QgsCustomDropHandler; class QgsCustomLayerOrderWidget; class QgsDockWidget; class QgsDoubleSpinBox; class QgsFeature; +class QgsFeatureStore; class QgsGeometry; class QgsLayerTreeMapCanvasBridge; class QgsLayerTreeView; class QgsMapCanvas; class QgsMapLayer; class QgsMapLayerConfigWidgetFactory; +class QgsMapOverviewCanvas; class QgsMapTip; class QgsMapTool; class QgsMapToolAdvancedDigitizing; -class QgsMapOverviewCanvas; class QgsPluginLayer; +class QgsPluginLayer; +class QgsPluginManager; class QgsPoint; class QgsProviderRegistry; class QgsPythonUtils; +class QgsRasterLayer; class QgsRectangle; +class QgsRuntimeProfiler; class QgsSnappingUtils; +class QgsSnappingWidget; +class QgsStatusBarCoordinatesWidget; +class QgsStatusBarMagnifierWidget; +class QgsStatusBarScaleWidget; class QgsTransactionGroup; class QgsUndoWidget; class QgsUserInputDockWidget; class QgsVectorLayer; class QgsVectorLayerTools; class QgsWelcomePage; -class QgsRasterLayer; -class QgsPluginLayer; -class QgsCoordinateReferenceSystem; -class QgsFeatureStore; -class QgsAuthManager; -class QgsPluginManager; -class QgsRuntimeProfiler; -class QgsBookmarks; class QDomDocument; class QNetworkReply; @@ -728,6 +729,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow private slots: void onTransactionGroupsChanged(); + void onSnappingConfigChanged(); + //! validate a SRS void validateCrs( QgsCoordinateReferenceSystem &crs ); @@ -1741,7 +1744,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QgsStatisticalSummaryDockWidget* mStatisticalSummaryDockWidget; QgsBookmarks* mBookMarksDockWidget; - QgsSnappingDialog *mSnappingDialog; + //! snapping widget + QgsSnappingWidget *mSnappingWidget; + QWidget *mSnappingDialogContainer; + QgsSnappingWidget *mSnappingDialogWidget; QgsPluginManager *mPluginManager; QgsDockWidget *mMapStylingDock; diff --git a/src/app/qgsmaptooloffsetcurve.cpp b/src/app/qgsmaptooloffsetcurve.cpp index e0e8a5d9481..8722764e1b9 100644 --- a/src/app/qgsmaptooloffsetcurve.cpp +++ b/src/app/qgsmaptooloffsetcurve.cpp @@ -22,6 +22,7 @@ #include "qgssnappingutils.h" #include "qgsvectorlayer.h" #include "qgsvertexmarker.h" +#include "qgssnappingconfig.h" #include #include @@ -84,24 +85,20 @@ void QgsMapToolOffsetCurve::canvasReleaseEvent( QgsMapMouseEvent* e ) QgsSnappingUtils* snapping = mCanvas->snappingUtils(); // store previous settings - int oldType; - double oldSearchRadius; - QgsTolerance::UnitType oldSearchRadiusUnit; - QgsSnappingUtils::SnapToMapMode oldMode = snapping->snapToMapMode(); - snapping->defaultSettings( oldType, oldSearchRadius, oldSearchRadiusUnit ); - + QgsSnappingConfig oldConfig = snapping->config(); + QgsSnappingConfig config = snapping->config(); // setup new settings (temporary) QSettings settings; - snapping->setSnapToMapMode( QgsSnappingUtils::SnapAllLayers ); - snapping->setDefaultSettings( QgsPointLocator::Edge, - settings.value( "/qgis/digitizing/search_radius_vertex_edit", 10 ).toDouble(), - ( QgsTolerance::UnitType ) settings.value( "/qgis/digitizing/search_radius_vertex_edit_unit", QgsTolerance::Pixels ).toInt() ); + config.setMode( QgsSnappingConfig::AllLayers ); + config.setType( QgsSnappingConfig::Segment ); + config.setTolerance( settings.value( "/qgis/digitizing/search_radius_vertex_edit", 10 ).toDouble() ); + config.setUnits( static_cast( settings.value( "/qgis/digitizing/search_radius_vertex_edit_unit", QgsTolerance::Pixels ).toInt() ) ); + snapping->setConfig( config ); QgsPointLocator::Match match = snapping->snapToMap( e->pos() ); // restore old settings - snapping->setSnapToMapMode( oldMode ); - snapping->setDefaultSettings( oldType, oldSearchRadius, oldSearchRadiusUnit ); + snapping->setConfig( oldConfig ); if ( match.hasEdge() && match.layer() ) { diff --git a/src/app/qgsoptions.cpp b/src/app/qgsoptions.cpp index cb7576b04cc..f954d7efe23 100644 --- a/src/app/qgsoptions.cpp +++ b/src/app/qgsoptions.cpp @@ -625,7 +625,6 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) cbxShowTips->setChecked( mSettings->value( QString( "/qgis/showTips%1" ).arg( Qgis::QGIS_VERSION_INT / 100 ), true ).toBool() ); cbxCheckVersion->setChecked( mSettings->value( "/qgis/checkVersion", true ).toBool() ); cbxAttributeTableDocked->setChecked( mSettings->value( "/qgis/dockAttributeTable", false ).toBool() ); - cbxSnappingOptionsDocked->setChecked( mSettings->value( "/qgis/dockSnapping", false ).toBool() ); cbxAddPostgisDC->setChecked( mSettings->value( "/qgis/addPostgisDC", false ).toBool() ); cbxAddOracleDC->setChecked( mSettings->value( "/qgis/addOracleDC", false ).toBool() ); cbxCompileExpressions->setChecked( mSettings->value( "/qgis/compileExpressions", true ).toBool() ); @@ -912,6 +911,15 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) chkDisableAttributeValuesDlg->setChecked( mSettings->value( "/qgis/digitizing/disable_enter_attribute_values_dialog", false ).toBool() ); mValidateGeometries->setCurrentIndex( mSettings->value( "/qgis/digitizing/validate_geometries", 1 ).toInt() ); + mSnappingMainDialogComboBox->clear(); + mSnappingMainDialogComboBox->addItem( tr( "dialog" ), "dialog" ); + mSnappingMainDialogComboBox->addItem( tr( "dock" ), "dock" ); + mSnappingMainDialogComboBox->setCurrentIndex( mSnappingMainDialogComboBox->findData( mSettings->value( "/qgis/mainSnappingWidgetMode", "dialog" ).toString() ) ); + mSnappingSimplePanelComboBox->clear(); + mSnappingSimplePanelComboBox->addItem( tr( "tool bar" ), "toolbar" ); + mSnappingSimplePanelComboBox->addItem( tr( "status bar" ), "statusbar" ); + mSnappingSimplePanelComboBox->setCurrentIndex( mSnappingSimplePanelComboBox->findData( mSettings->value( "/qgis/simpleSnappingWidgetMode", "toolbar" ).toString() ) ); + mOffsetJoinStyleComboBox->addItem( tr( "Round" ), 0 ); mOffsetJoinStyleComboBox->addItem( tr( "Mitre" ), 1 ); mOffsetJoinStyleComboBox->addItem( tr( "Bevel" ), 2 ); @@ -1182,7 +1190,9 @@ void QgsOptions::saveOptions() mSettings->setValue( "/qgis/scanZipInBrowser2", cmbScanZipInBrowser->currentData().toString() ); mSettings->setValue( "/qgis/ignoreShapeEncoding", cbxIgnoreShapeEncoding->isChecked() ); - mSettings->setValue( "/qgis/dockSnapping", cbxSnappingOptionsDocked->isChecked() ); + mSettings->setValue( "/qgis/mainSnappingWidgetMode", mSnappingMainDialogComboBox->currentData() ); + mSettings->setValue( "/qgis/simpleSnappingWidgetMode", mSnappingSimplePanelComboBox->currentData() ); + mSettings->setValue( "/qgis/addPostgisDC", cbxAddPostgisDC->isChecked() ); mSettings->setValue( "/qgis/addOracleDC", cbxAddOracleDC->isChecked() ); mSettings->setValue( "/qgis/compileExpressions", cbxCompileExpressions->isChecked() ); diff --git a/src/app/qgssnappingdialog.cpp b/src/app/qgssnappingdialog.cpp index d61aeab89dd..145a295698a 100644 --- a/src/app/qgssnappingdialog.cpp +++ b/src/app/qgssnappingdialog.cpp @@ -15,22 +15,24 @@ * * ***************************************************************************/ -#include "qgssnappingdialog.h" +#include "qgisapp.h" +#include "qgsdockwidget.h" +#include "qgslogger.h" #include "qgsmapcanvas.h" #include "qgsmaplayer.h" -#include "qgsvectorlayer.h" #include "qgsmaplayerregistry.h" -#include "qgisapp.h" #include "qgsproject.h" -#include "qgslogger.h" -#include "qgsdockwidget.h" +#include "qgssnappingdialog.h" +#include "qgsvectorlayer.h" + #include -#include #include +#include +#include #include #include -#include + QgsSnappingDialog::QgsSnappingDialog( QWidget* parent, QgsMapCanvas* canvas ) : QDialog( parent ) diff --git a/src/app/qgssnappinglayertreemodel.cpp b/src/app/qgssnappinglayertreemodel.cpp new file mode 100644 index 00000000000..8007309e5cf --- /dev/null +++ b/src/app/qgssnappinglayertreemodel.cpp @@ -0,0 +1,636 @@ +/*************************************************************************** + qgssnappinglayertreemodel.cpp - QgsSnappingLayerTreeView + + --------------------- + begin : 31.8.2016 + copyright : (C) 2016 by Denis Rouzaud + email : denis.rouzaud@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 +#include + +#include "qgssnappinglayertreemodel.h" + +#include "qgslayertree.h" +#include "qgsmapcanvas.h" +#include "qgsproject.h" +#include "qgssnappingconfig.h" +#include "qgsvectorlayer.h" + + +QgsSnappingLayerDelegate::QgsSnappingLayerDelegate( QgsMapCanvas* canvas, QObject* parent ) + : QItemDelegate( parent ) + , mCanvas( canvas ) +{ +} + +QWidget* QgsSnappingLayerDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + Q_UNUSED( option ); + Q_UNUSED( index ); + + if ( index.column() == QgsSnappingLayerTreeModel::TypeColumn ) + { + QComboBox* w = new QComboBox( parent ); + w->addItem( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingVertex.svg" ) ), "Vertex", QgsSnappingConfig::Vertex ); + w->addItem( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingVertexAndSegment.svg" ) ), "Vertex and segment", QgsSnappingConfig::VertexAndSegment ); + w->addItem( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingSegment.svg" ) ), "Segment", QgsSnappingConfig::Segment ); + return w; + } + + if ( index.column() == QgsSnappingLayerTreeModel::ToleranceColumn ) + { + QDoubleSpinBox* w = new QDoubleSpinBox( parent ); + QVariant val = index.model()->data( index.model()->sibling( index.row(), QgsSnappingLayerTreeModel::UnitsColumn, index ), Qt::UserRole ); + if ( val.isValid() ) + { + QgsTolerance::UnitType units = ( QgsTolerance::UnitType )val.toInt(); + if ( units == QgsTolerance::Pixels ) + { + w->setDecimals( 0 ); + } + else + { + QgsUnitTypes::DistanceUnitType type = QgsUnitTypes::unitType( mCanvas->mapUnits() ); + w->setDecimals( type == QgsUnitTypes::Standard ? 2 : 5 ); + } + } + else + { + w->setDecimals( 5 ); + } + return w; + } + + if ( index.column() == QgsSnappingLayerTreeModel::UnitsColumn ) + { + QComboBox* w = new QComboBox( parent ); + w->addItem( tr( "px" ), QgsTolerance::Pixels ); + w->addItem( QgsUnitTypes::toString( QgsProject::instance()->distanceUnits() ), QgsTolerance::ProjectUnits ); + return w; + } + + return nullptr; +} + +void QgsSnappingLayerDelegate::setEditorData( QWidget* editor, const QModelIndex& index ) const +{ + QVariant val = index.model()->data( index, Qt::UserRole ); + if ( !val.isValid() ) + return; + + if ( index.column() == QgsSnappingLayerTreeModel::TypeColumn ) + { + QgsSnappingConfig::SnappingType type = ( QgsSnappingConfig::SnappingType )val.toInt(); + QComboBox *cb = qobject_cast( editor ); + if ( cb ) + { + cb->setCurrentIndex( cb->findData( type ) ); + } + } + else if ( index.column() == QgsSnappingLayerTreeModel::ToleranceColumn ) + { + QDoubleSpinBox *w = qobject_cast( editor ); + if ( w ) + { + w->setValue( val.toDouble() ); + } + } + else if ( index.column() == QgsSnappingLayerTreeModel::UnitsColumn ) + { + QgsTolerance::UnitType units = ( QgsTolerance::UnitType )val.toInt(); + QComboBox *w = qobject_cast( editor ); + if ( w ) + { + w->setCurrentIndex( w->findData( units ) ); + } + } +} + +void QgsSnappingLayerDelegate::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const +{ + if ( index.column() == QgsSnappingLayerTreeModel::TypeColumn || + index.column() == QgsSnappingLayerTreeModel::UnitsColumn ) + { + QComboBox *w = qobject_cast( editor ); + if ( w ) + { + model->setData( index, w->currentData(), Qt::EditRole ); + } + } + else if ( index.column() == QgsSnappingLayerTreeModel::ToleranceColumn ) + { + QDoubleSpinBox *w = qobject_cast( editor ); + if ( w ) + { + model->setData( index, w->value(), Qt::EditRole ); + } + } +} + + +QgsSnappingLayerTreeModel::QgsSnappingLayerTreeModel( QgsProject* project, QObject *parent ) + : QSortFilterProxyModel( parent ) + , mProject( project ) + , mIndividualLayerSettings( project->snappingConfig().individualLayerSettings() ) +{ + connect( project, &QgsProject::snappingConfigChanged, this, &QgsSnappingLayerTreeModel::onSnappingSettingsChanged ); +} + +QgsSnappingLayerTreeModel::~QgsSnappingLayerTreeModel() +{ +} + +int QgsSnappingLayerTreeModel::columnCount( const QModelIndex& parent ) const +{ + Q_UNUSED( parent ); + return 5; +} + +Qt::ItemFlags QgsSnappingLayerTreeModel::flags( const QModelIndex& idx ) const +{ + if ( idx.column() == LayerColumn ) + { + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + } + + QgsVectorLayer* vl = vectorLayer( idx ); + if ( !vl ) + { + return Qt::NoItemFlags; + } + else + { + const QModelIndex layerIndex = sibling( idx.row(), LayerColumn, idx ); + if ( data( layerIndex, Qt::CheckStateRole ) == Qt::Checked ) + { + if ( idx.column() == AvoidIntersectionColumn ) + { + if ( vl->geometryType() == QgsWkbTypes::PolygonGeometry ) + { + return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; + } + else + { + return Qt::NoItemFlags; + } + } + else + { + return Qt::ItemIsEnabled | Qt::ItemIsEditable; + } + } + } + return Qt::NoItemFlags; +} + +QModelIndex QgsSnappingLayerTreeModel::index( int row, int column, const QModelIndex& parent ) const +{ + QModelIndex newIndex = QSortFilterProxyModel::index( row, 0, parent ); + if ( column == LayerColumn ) + return newIndex; + + return createIndex( row, column, newIndex.internalId() ); +} + +QModelIndex QgsSnappingLayerTreeModel::parent( const QModelIndex& child ) const +{ + return QSortFilterProxyModel::parent( createIndex( child.row(), 0, child.internalId() ) ); +} + +QModelIndex QgsSnappingLayerTreeModel::sibling( int row, int column, const QModelIndex& idx ) const +{ + QModelIndex parent = idx.parent(); + return index( row, column, parent ); +} + +QgsVectorLayer* QgsSnappingLayerTreeModel::vectorLayer( const QModelIndex& idx ) const +{ + QgsLayerTreeNode* node; + if ( idx.column() == LayerColumn ) + { + node = mLayerTreeModel->index2node( mapToSource( idx ) ); + } + else + { + node = mLayerTreeModel->index2node( mapToSource( index( idx.row(), 0, idx.parent() ) ) ); + } + + if ( !node || !QgsLayerTree::isLayer( node ) ) + return nullptr; + + return qobject_cast( QgsLayerTree::toLayer( node )->layer() ); +} + +void QgsSnappingLayerTreeModel::onSnappingSettingsChanged() +{ + const QHash oldSettings = mIndividualLayerSettings; + + Q_FOREACH ( QgsVectorLayer* vl, oldSettings.keys() ) + { + if ( !mProject->snappingConfig().individualLayerSettings().contains( vl ) ) + { + beginResetModel(); + mIndividualLayerSettings = mProject->snappingConfig().individualLayerSettings(); + endResetModel(); + return; + } + } + Q_FOREACH ( QgsVectorLayer* vl, mProject->snappingConfig().individualLayerSettings().keys() ) + { + if ( !oldSettings.contains( vl ) ) + { + beginResetModel(); + mIndividualLayerSettings = mProject->snappingConfig().individualLayerSettings(); + endResetModel(); + return; + } + } + + hasRowchanged( mLayerTreeModel->rootGroup(), oldSettings ); +} + +void QgsSnappingLayerTreeModel::hasRowchanged( QgsLayerTreeNode* node, const QHash &oldSettings ) +{ + if ( node->nodeType() == QgsLayerTreeNode::NodeGroup ) + { + Q_FOREACH ( QgsLayerTreeNode *child, node->children() ) + { + hasRowchanged( child, oldSettings ); + } + } + else + { + QModelIndex idx = mapFromSource( mLayerTreeModel->node2index( node ) ); + QgsVectorLayer* vl = vectorLayer( idx ); + if ( !vl ) + { + emit dataChanged( QModelIndex(), idx ); + } + if ( oldSettings.value( vl ) != mProject->snappingConfig().individualLayerSettings().value( vl ) ) + { + mIndividualLayerSettings.insert( vl, mProject->snappingConfig().individualLayerSettings().value( vl ) ); + emit dataChanged( idx, index( idx.row(), columnCount( idx ) - 1 ) ); + } + } +} + +QgsLayerTreeModel* QgsSnappingLayerTreeModel::layerTreeModel() const +{ + return mLayerTreeModel; +} + +void QgsSnappingLayerTreeModel::setLayerTreeModel( QgsLayerTreeModel* layerTreeModel ) +{ + mLayerTreeModel = layerTreeModel; + QSortFilterProxyModel::setSourceModel( layerTreeModel ); +} + +bool QgsSnappingLayerTreeModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const +{ + QgsLayerTreeNode* node = mLayerTreeModel->index2node( mLayerTreeModel->index( sourceRow, 0, sourceParent ) ); + return nodeShown( node ); +} + +bool QgsSnappingLayerTreeModel::nodeShown( QgsLayerTreeNode* node ) const +{ + if ( !node ) + return false; + if ( node->nodeType() == QgsLayerTreeNode::NodeGroup ) + { + Q_FOREACH ( QgsLayerTreeNode *child, node->children() ) + { + if ( nodeShown( child ) ) + { + return true; + } + } + return false; + } + else + { + QgsMapLayer* layer = QgsLayerTree::toLayer( node )->layer(); + if ( layer && layer->type() == QgsMapLayer::VectorLayer ) + return true; + } + return false; +} + +QVariant QgsSnappingLayerTreeModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + if ( orientation == Qt::Horizontal ) + { + if ( role == Qt::DisplayRole ) + { + switch ( section ) + { + case 0: + return tr( "Layer" ); + case 1: + return tr( "Type" ); + case 2: + return tr( "Tolerance" ); + case 3: + return tr( "Units" ); + case 4: + return tr( "Avoid intersection" ); + default: + return QVariant(); + } + } + } + return mLayerTreeModel->headerData( section, orientation, role ); +} + +QVariant QgsSnappingLayerTreeModel::data( const QModelIndex& idx, int role ) const +{ + if ( idx.column() == LayerColumn ) + { + if ( role == Qt::CheckStateRole ) + { + QgsVectorLayer *vl = vectorLayer( idx ); + if ( vl && mIndividualLayerSettings.contains( vl ) ) + { + const QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl ); + if ( !ls.valid() ) + { + return QVariant(); + } + if ( ls.enabled() ) + { + return Qt::Checked; + } + else + { + return Qt::Unchecked; + } + } + else + { + // i.e. this is a group, analyse its children + bool hasChecked = false, hasUnchecked = false; + int n; + for ( n = 0; !hasChecked || !hasUnchecked; n++ ) + { + QVariant v = data( idx.child( n, 0 ), role ); + if ( !v.isValid() ) + break; + + switch ( v.toInt() ) + { + case Qt::PartiallyChecked: + // parent of partially checked child shared state + return Qt::PartiallyChecked; + + case Qt::Checked: + hasChecked = true; + break; + + case Qt::Unchecked: + hasUnchecked = true; + break; + } + } + + // unchecked leaf + if ( n == 0 ) + return Qt::Unchecked; + + // both + if ( hasChecked && hasUnchecked ) + return Qt::PartiallyChecked; + + if ( hasChecked ) + return Qt::Checked; + + Q_ASSERT( hasUnchecked ); + return Qt::Unchecked; + } + } + else + { + return mLayerTreeModel->data( mapToSource( idx ), role ); + } + } + else + { + QgsVectorLayer *vl = vectorLayer( idx ); + + if ( !vl || !mIndividualLayerSettings.contains( vl ) ) + { + return QVariant(); + } + + const QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl ); + + // type + if ( idx.column() == TypeColumn ) + { + if ( role == Qt::DisplayRole ) + { + switch ( ls.type() ) + { + case QgsSnappingConfig::Vertex: + return tr( "vertex" ); + case QgsSnappingConfig::VertexAndSegment: + return tr( "vertex and segment" ); + case QgsSnappingConfig::Segment: + return tr( "segment" ); + default: + return tr( "N/A" ); + } + } + + if ( role == Qt::UserRole ) + return ls.type(); + } + + // tolerance + if ( idx.column() == ToleranceColumn ) + { + if ( role == Qt::DisplayRole ) + { + return QString::number( ls.tolerance() ); + } + + if ( role == Qt::UserRole ) + { + return ls.tolerance(); + } + } + + // units + if ( idx.column() == UnitsColumn ) + { + if ( role == Qt::DisplayRole ) + { + switch ( ls.units() ) + { + case QgsTolerance::Pixels: + return tr( "pixels" ); + case QgsTolerance::ProjectUnits: + return QgsUnitTypes::toString( QgsProject::instance()->distanceUnits() ); + default: + return QVariant(); + } + } + + if ( role == Qt::UserRole ) + { + return ls.units(); + } + } + + // avoid intersection + if ( idx.column() == AvoidIntersectionColumn ) + { + if ( role == Qt::CheckStateRole && vl->geometryType() == QgsWkbTypes::PolygonGeometry ) + { + if ( ls.avoidIntersection() ) + { + return Qt::Checked; + } + else + { + return Qt::Unchecked; + } + } + } + } + + return QVariant(); +} + +bool QgsSnappingLayerTreeModel::setData( const QModelIndex& index, const QVariant& value, int role ) +{ + if ( index.column() == LayerColumn ) + { + if ( role == Qt::CheckStateRole ) + { + int i = 0; + for ( i = 0; ; i++ ) + { + QModelIndex child = index.child( i, 0 ); + if ( !child.isValid() ) + break; + + setData( child, value, role ); + } + + if ( i == 0 ) + { + QgsVectorLayer* vl = vectorLayer( index ); + if ( !vl || !mIndividualLayerSettings.contains( vl ) ) + { + return false; + } + QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl ); + if ( !ls.valid() ) + return false; + if ( value.toInt() == Qt::Checked ) + ls.setEnabled( true ); + else if ( value.toInt() == Qt::Unchecked ) + ls.setEnabled( false ); + else + Q_ASSERT( "expected checked or unchecked" ); + + QgsSnappingConfig config = mProject->snappingConfig(); + config.setIndividualLayerSettings( vl, ls ); + mProject->setSnappingConfig( config ); + } + return true; + } + + return mLayerTreeModel->setData( mapToSource( index ), value, role ); + } + + if ( index.column() == TypeColumn && role == Qt::EditRole ) + { + QgsVectorLayer *vl = vectorLayer( index ); + if ( vl ) + { + if ( !mIndividualLayerSettings.contains( vl ) ) + return false; + + QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl ); + if ( !ls.valid() ) + return false; + + ls.setType(( QgsSnappingConfig::SnappingType )value.toInt() ); + QgsSnappingConfig config = mProject->snappingConfig(); + config.setIndividualLayerSettings( vl, ls ); + mProject->setSnappingConfig( config ); + return true; + } + } + + if ( index.column() == ToleranceColumn && role == Qt::EditRole ) + { + QgsVectorLayer *vl = vectorLayer( index ); + if ( vl ) + { + if ( !mIndividualLayerSettings.contains( vl ) ) + return false; + + QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl ); + if ( !ls.valid() ) + return false; + + ls.setTolerance( value.toDouble() ); + QgsSnappingConfig config = mProject->snappingConfig(); + config.setIndividualLayerSettings( vl, ls ); + mProject->setSnappingConfig( config ); + return true; + } + } + + if ( index.column() == UnitsColumn && role == Qt::EditRole ) + { + QgsVectorLayer *vl = vectorLayer( index ); + if ( vl ) + { + if ( !mIndividualLayerSettings.contains( vl ) ) + return false; + + QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl ); + if ( !ls.valid() ) + return false; + + ls.setUnits(( QgsTolerance::UnitType )value.toInt() ); + QgsSnappingConfig config = mProject->snappingConfig(); + config.setIndividualLayerSettings( vl, ls ); + mProject->setSnappingConfig( config ); + return true; + } + } + + if ( index.column() == AvoidIntersectionColumn && role == Qt::CheckStateRole ) + { + QgsVectorLayer *vl = vectorLayer( index ); + if ( vl ) + { + if ( !mIndividualLayerSettings.contains( vl ) ) + return false; + + QgsSnappingConfig::IndividualLayerSettings ls = mIndividualLayerSettings.value( vl ); + if ( !ls.valid() ) + return false; + + ls.setAvoidIntersection( value.toInt() == Qt::Checked ); + QgsSnappingConfig config = mProject->snappingConfig(); + config.setIndividualLayerSettings( vl, ls ); + mProject->setSnappingConfig( config ); + return true; + } + } + + return false; +} diff --git a/src/app/qgssnappinglayertreemodel.h b/src/app/qgssnappinglayertreemodel.h new file mode 100644 index 00000000000..e54890b0516 --- /dev/null +++ b/src/app/qgssnappinglayertreemodel.h @@ -0,0 +1,94 @@ +/*************************************************************************** + qgssnappinglayertreemodel.h - QgsSnappingLayerTreeModel + + --------------------- + begin : 31.8.2016 + copyright : (C) 2016 by Denis Rouzaud + email : denis.rouzaud@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 QGSSNAPPINGLAYERTREEVIEW_H +#define QGSSNAPPINGLAYERTREEVIEW_H + + + +#include +#include + +#include "qgslayertreemodel.h" +#include "qgssnappingconfig.h" + +class QgsMapCanvas; +class QgsProject; + + +class APP_EXPORT QgsSnappingLayerDelegate : public QItemDelegate +{ + Q_OBJECT + + public: + explicit QgsSnappingLayerDelegate( QgsMapCanvas* canvas, QObject *parent = nullptr ); + + QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const override; + void setEditorData( QWidget *editor, const QModelIndex &index ) const override; + void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const override; + + private: + QgsMapCanvas* mCanvas; +}; + + +class APP_EXPORT QgsSnappingLayerTreeModel : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + enum Columns + { + LayerColumn = 0, + TypeColumn, + ToleranceColumn, + UnitsColumn, + AvoidIntersectionColumn + }; + + QgsSnappingLayerTreeModel( QgsProject* project, QObject* parent = nullptr ); + ~QgsSnappingLayerTreeModel(); + + int columnCount( const QModelIndex& parent ) const override; + QVariant headerData( int section, Qt::Orientation orientation, int role ) const override; + Qt::ItemFlags flags( const QModelIndex& idx ) const override; + QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override; + QModelIndex parent( const QModelIndex& child ) const override; + QModelIndex sibling( int row, int column, const QModelIndex &idx ) const override; + QVariant data( const QModelIndex& index, int role ) const override; + bool setData( const QModelIndex& index, const QVariant& value, int role ) override; + + QgsLayerTreeModel* layerTreeModel() const; + void setLayerTreeModel( QgsLayerTreeModel* layerTreeModel ); + + QgsVectorLayer* vectorLayer( const QModelIndex& idx ) const; + + protected: + bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const override; + + private slots: + void onSnappingSettingsChanged(); + + private: + bool nodeShown( QgsLayerTreeNode* node ) const; + + QgsProject* mProject; + QHash mIndividualLayerSettings; + QgsLayerTreeModel* mLayerTreeModel; + + void hasRowchanged( QgsLayerTreeNode* node, const QHash& oldSettings ); +}; + +#endif // QGSSNAPPINGLAYERTREEVIEW_H diff --git a/src/app/qgssnappingwidget.cpp b/src/app/qgssnappingwidget.cpp new file mode 100644 index 00000000000..3fbd47181cf --- /dev/null +++ b/src/app/qgssnappingwidget.cpp @@ -0,0 +1,476 @@ +/*************************************************************************** + qgssnappingwidget.cpp + begin : August 2016 + copyright : (C) 2016 Denis Rouzaud + email : denis.rouzaud@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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qgsapplication.h" +#include "qgslayertreegroup.h" +#include "qgslayertree.h" +#include "qgslayertreeview.h" +#include "qgsmapcanvas.h" +#include "qgsmaplayer.h" +#include "qgsproject.h" +#include "qgssnappingconfig.h" +#include "qgssnappinglayertreemodel.h" +#include "qgssnappingwidget.h" +#include "qgsunittypes.h" + + + +QgsSnappingWidget::QgsSnappingWidget( QgsProject* project, QgsMapCanvas *canvas, QWidget* parent ) + : QWidget( parent ) + , mProject( project ) + , mCanvas( canvas ) + , mModeAction( nullptr ) + , mTypeAction( nullptr ) + , mToleranceAction( nullptr ) + , mUnitAction( nullptr ) + , mLayerTreeView( nullptr ) +{ + // detect the type of display + QToolBar* tb = qobject_cast( parent ); + if ( tb ) + { + mDisplayMode = ToolBar; + setObjectName( "SnappingOptionToolBar" ); + } + else + { + QStatusBar *sb = qobject_cast( parent ); + if ( sb ) + { + mDisplayMode = StatusBar; + setObjectName( "SnappingOptionStatusBar" ); + } + else + { + mDisplayMode = Widget; + setObjectName( "SnappingOptionDialog" ); + } + } + + // enable button + mEnabledAction = new QAction( this ); + mEnabledAction->setCheckable( true ); + mEnabledAction->setIcon( QIcon( QgsApplication::getThemeIcon( "/mIconSnapping.svg" ) ) ); + mEnabledAction->setToolTip( tr( "Enable snapping" ) ); + mEnabledAction->setObjectName( "EnableSnappingAction" ); + connect( mEnabledAction, SIGNAL( toggled( bool ) ) , this, SLOT( enableSnapping( bool ) ) ); + + // mode button + mModeButton = new QToolButton(); + mModeButton->setToolTip( tr( "Snapping mode" ) ); + mModeButton->setPopupMode( QToolButton::InstantPopup ); + QMenu *modeMenu = new QMenu( tr( "Set snapping mode" ), this ); + mAllLayersAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingAllLayers.svg" ) ), "All layers", modeMenu ); + mActiveLayerAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingActiveLayer.svg" ) ), "Active layer", modeMenu ); + mAdvancedModeAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingAdvanced.svg" ) ), "Advanced configuration", modeMenu ); + modeMenu->addAction( mAllLayersAction ); + modeMenu->addAction( mActiveLayerAction ); + modeMenu->addAction( mAdvancedModeAction ); + mModeButton->setMenu( modeMenu ); + mModeButton->setObjectName( "SnappingModeButton" ); + if ( mDisplayMode == Widget ) + { + mModeButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); + } + connect( mModeButton, SIGNAL( triggered( QAction* ) ), this, SLOT( modeButtonTriggered( QAction* ) ) ); + + // type button + mTypeButton = new QToolButton(); + mTypeButton->setToolTip( tr( "Snapping type" ) ); + mTypeButton->setPopupMode( QToolButton::InstantPopup ); + QMenu *typeMenu = new QMenu( tr( "Set snapping mode" ), this ); + mVertexAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingVertex.svg" ) ), "Vertex", typeMenu ); + mVertexAndSegmentAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingVertexAndSegment.svg" ) ), "Vertex and segment", typeMenu ); + mSegmentAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingSegment.svg" ) ), "Segment", typeMenu ); + typeMenu->addAction( mVertexAction ); + typeMenu->addAction( mVertexAndSegmentAction ); + typeMenu->addAction( mSegmentAction ); + mTypeButton->setMenu( typeMenu ); + mTypeButton->setObjectName( "SnappingTypeButton" ); + if ( mDisplayMode == Widget ) + { + mTypeButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); + } + connect( mTypeButton, SIGNAL( triggered( QAction* ) ), this, SLOT( typeButtonTriggered( QAction* ) ) ); + + // tolerance + mToleranceSpinBox = new QDoubleSpinBox(); + mToleranceSpinBox->setToolTip( tr( "Snapping tolerance in defined units" ) ); + mToleranceSpinBox->setObjectName( "SnappingToleranceSpinBox" ); + connect( mToleranceSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( changeTolerance( double ) ) ); + + // units + mUnitsComboBox = new QComboBox(); + mUnitsComboBox->addItem( tr( "px" ), QgsTolerance::Pixels ); + mUnitsComboBox->addItem( QgsUnitTypes::toString( QgsProject::instance()->distanceUnits() ), QgsTolerance::ProjectUnits ); + mUnitsComboBox->setToolTip( tr( "Snapping unit type: pixels (px) or map units (mu)" ) ); + mUnitsComboBox->setObjectName( "SnappingUnitComboBox" ); + connect( mUnitsComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( changeUnit( int ) ) ); + + // topological editing button + mTopologicalEditingAction = new QAction( tr( "topological editing" ), this ); + mTopologicalEditingAction->setCheckable( true ); + mTopologicalEditingAction->setIcon( QIcon( QgsApplication::getThemeIcon( "/mIconTopologicalEditing.svg" ) ) ); + mTopologicalEditingAction->setToolTip( tr( "Enable topological editing" ) ); + mTopologicalEditingAction->setObjectName( "TopologicalEditingAction" ); + connect( mTopologicalEditingAction, SIGNAL( toggled( bool ) ) , this, SLOT( enableTopologicalEditing( bool ) ) ); + + // snapping on intersection button + mIntersectionSnappingAction = new QAction( tr( "snapping on intersection" ), this ); + mIntersectionSnappingAction->setCheckable( true ); + mIntersectionSnappingAction->setIcon( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingIntersection.svg" ) ) ); + mIntersectionSnappingAction->setToolTip( tr( "Enable snapping on intersection" ) ); + mIntersectionSnappingAction->setObjectName( "IntersectionSnappingAction" ); + connect( mIntersectionSnappingAction, SIGNAL( toggled( bool ) ) , this, SLOT( enableIntersectionSnapping( bool ) ) ); + + // layout + if ( mDisplayMode == ToolBar ) + { + // hiding widget in a toolbar is not possible, actions are required + tb->addAction( mEnabledAction ); + mModeAction = tb->addWidget( mModeButton ); + mTypeAction = tb->addWidget( mTypeButton ); + mToleranceAction = tb->addWidget( mToleranceSpinBox ); + mUnitAction = tb->addWidget( mUnitsComboBox ); + tb->addAction( mTopologicalEditingAction ); + tb->addAction( mIntersectionSnappingAction ); + } + else + { + // mode = widget or status bar + QHBoxLayout* layout = new QHBoxLayout(); + + QToolButton* enabledButton = new QToolButton(); + enabledButton->addAction( mEnabledAction ); + enabledButton->setDefaultAction( mEnabledAction ); + layout->addWidget( enabledButton ); + + layout->addWidget( mModeButton ); + layout->addWidget( mTypeButton ); + layout->addWidget( mToleranceSpinBox ) ; + layout->addWidget( mUnitsComboBox ) ; + + QToolButton* topoButton = new QToolButton(); + topoButton->addAction( mTopologicalEditingAction ); + topoButton->setDefaultAction( mTopologicalEditingAction ); + if ( mDisplayMode == Widget ) + { + topoButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); + } + layout->addWidget( topoButton ); + QToolButton* interButton = new QToolButton(); + interButton->addAction( mIntersectionSnappingAction ); + interButton->setDefaultAction( mIntersectionSnappingAction ); + if ( mDisplayMode == Widget ) + { + interButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); + } + layout->addWidget( interButton ); + + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->setAlignment( Qt::AlignRight ); + layout->setSpacing( mDisplayMode == Widget ? 3 : 0 ); + + if ( mDisplayMode == Widget ) + { + mLayerTreeView = new QTreeView(); + QgsSnappingLayerTreeModel* model = new QgsSnappingLayerTreeModel( mProject, this ); + model->setLayerTreeModel( new QgsLayerTreeModel( QgsProject::instance()->layerTreeRoot(), model ) ); + // model->setFlags( 0 ); + mLayerTreeView->setModel( model ); + mLayerTreeView->resizeColumnToContents( 0 ); + mLayerTreeView->header()->show(); + mLayerTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents ); + mLayerTreeView->header()->setSectionResizeMode( QHeaderView::Interactive ); + mLayerTreeView->setSelectionMode( QAbstractItemView::NoSelection ); + + // item delegates + mLayerTreeView->setEditTriggers( QAbstractItemView::AllEditTriggers ); + mLayerTreeView->setItemDelegate( new QgsSnappingLayerDelegate( mCanvas, this ) ); + + QGridLayout* topLayout = new QGridLayout(); + topLayout->addLayout( layout, 0, 0, Qt::AlignLeft | Qt::AlignTop ); + topLayout->addWidget( mLayerTreeView, 1, 0 ); + setLayout( topLayout ); + } + else + { + // mode = status bar + setLayout( layout ); + } + } + + // connect settings changed and map units changed to properly update the widget + connect( project, &QgsProject::snappingConfigChanged, this, &QgsSnappingWidget::projectSnapSettingsChanged ); + connect( mCanvas, SIGNAL( mapUnitsChanged() ), this, SLOT( updateToleranceDecimals() ) ); + + // modeChanged determines if widget are visible or not based on mode + modeChanged(); + updateToleranceDecimals(); +} + +QgsSnappingWidget::~QgsSnappingWidget() +{ + if ( mDisplayMode == Widget ) + { + QSettings().setValue( "/Windows/SnappingWidget/geometry", saveGeometry() ); + } +} + +void QgsSnappingWidget::projectSnapSettingsChanged() +{ + QgsSnappingConfig config = mProject->snappingConfig(); + if ( mConfig == config ) + return; + mConfig = config; + + mEnabledAction->setChecked( config.enabled() ); + + if ( config.mode() == QgsSnappingConfig::AllLayers && mModeButton->defaultAction() != mActiveLayerAction ) + { + mModeButton->setDefaultAction( mAllLayersAction ); + modeChanged(); + updateToleranceDecimals(); + } + if ( config.mode() == QgsSnappingConfig::ActiveLayer && mModeButton->defaultAction() != mActiveLayerAction ) + { + mModeButton->setDefaultAction( mActiveLayerAction ); + modeChanged(); + updateToleranceDecimals(); + } + if ( config.mode() == QgsSnappingConfig::AdvancedConfiguration && mModeButton->defaultAction() != mAdvancedModeAction ) + { + mModeButton->setDefaultAction( mAdvancedModeAction ); + modeChanged(); + updateToleranceDecimals(); + } + + if ( config.type() == QgsSnappingConfig::Vertex && mTypeButton->defaultAction() != mVertexAction ) + { + mTypeButton->setDefaultAction( mVertexAction ); + } + if ( config.type() == QgsSnappingConfig::VertexAndSegment && mTypeButton->defaultAction() != mVertexAndSegmentAction ) + { + mTypeButton->setDefaultAction( mVertexAndSegmentAction ); + } + if ( config.type() == QgsSnappingConfig::Segment && mTypeButton->defaultAction() != mSegmentAction ) + { + mTypeButton->setDefaultAction( mSegmentAction ); + } + + if ( mToleranceSpinBox->value() != config.tolerance() ) + { + mToleranceSpinBox->setValue( config.tolerance() ); + } + + if (( QgsTolerance::UnitType )mUnitsComboBox->currentData().toInt() != config.units() ) + { + mUnitsComboBox->setCurrentIndex( mUnitsComboBox->findData( config.units() ) ); + } + + if ( config.topologicalEditing() != mTopologicalEditingAction->isChecked() ) + { + mTopologicalEditingAction->setChecked( config.topologicalEditing() ); + } + + if ( config.intersectionSnapping() != mIntersectionSnappingAction->isChecked() ) + { + mIntersectionSnappingAction->setChecked( config.intersectionSnapping() ); + } +} + +void QgsSnappingWidget::enableSnapping( bool checked ) +{ + mModeButton->setEnabled( checked ); + mTypeButton->setEnabled( checked ); + mToleranceSpinBox->setEnabled( checked ); + mUnitsComboBox->setEnabled( checked ); + if ( mLayerTreeView ) + { + mLayerTreeView->setEnabled( checked ); + } + mTopologicalEditingAction->setEnabled( checked ); + mIntersectionSnappingAction->setEnabled( checked ); + + mConfig.setEnabled( checked ); + mProject->setSnappingConfig( mConfig ); +} + +void QgsSnappingWidget::changeTolerance( double tolerance ) +{ + mConfig.setTolerance( tolerance ); + mProject->setSnappingConfig( mConfig ); +} + +void QgsSnappingWidget::changeUnit( int idx ) +{ + QgsTolerance::UnitType unit = ( QgsTolerance::UnitType )mUnitsComboBox->itemData( idx ).toInt(); + mConfig.setUnits( unit ); + mProject->setSnappingConfig( mConfig ); + + updateToleranceDecimals(); +} + +void QgsSnappingWidget::enableTopologicalEditing( bool enabled ) +{ + mConfig.setTopologicalEditing( enabled ); + mProject->setSnappingConfig( mConfig ); +} + +void QgsSnappingWidget::enableIntersectionSnapping( bool enabled ) +{ + mConfig.setIntersectionSnapping( enabled ); + mProject->setSnappingConfig( mConfig ); +} + +void QgsSnappingWidget::modeButtonTriggered( QAction* action ) +{ + if ( action != mModeButton->defaultAction() ) + { + mModeButton->setDefaultAction( action ); + if ( action == mAllLayersAction ) + { + mConfig.setMode( QgsSnappingConfig::AllLayers ); + } + else if ( action == mActiveLayerAction ) + { + mConfig.setMode( QgsSnappingConfig::ActiveLayer ); + } + else if ( action == mAdvancedModeAction ) + { + mConfig.setMode( QgsSnappingConfig::AdvancedConfiguration ); + } + mProject->setSnappingConfig( mConfig ); + updateToleranceDecimals(); + modeChanged(); + } +} + +void QgsSnappingWidget::typeButtonTriggered( QAction* action ) +{ + if ( action != mTypeButton->defaultAction() ) + { + mTypeButton->setDefaultAction( action ); + if ( action == mVertexAction ) + { + mConfig.setType( QgsSnappingConfig::Vertex ); + } + else if ( action == mVertexAndSegmentAction ) + { + mConfig.setType( QgsSnappingConfig::VertexAndSegment ); + } + else if ( action == mSegmentAction ) + { + mConfig.setType( QgsSnappingConfig::Segment ); + } + mProject->setSnappingConfig( mConfig ); + } +} + +void QgsSnappingWidget::updateToleranceDecimals() +{ + if ( mConfig.units() == QgsTolerance::Pixels ) + { + mToleranceSpinBox->setDecimals( 0 ); + } + else + { + QgsUnitTypes::DistanceUnit mapUnit = mCanvas->mapUnits(); + QgsUnitTypes::DistanceUnitType type = QgsUnitTypes::unitType( mapUnit ); + if ( type == QgsUnitTypes::Standard ) + { + mToleranceSpinBox->setDecimals( 2 ); + } + else + { + mToleranceSpinBox->setDecimals( 5 ); + } + } +} + +void QgsSnappingWidget::modeChanged() +{ + bool advanced = mConfig.mode() == QgsSnappingConfig::AdvancedConfiguration; + + if ( mDisplayMode == ToolBar ) + { + mTypeAction->setVisible( !advanced ); + mToleranceAction->setVisible( !advanced ); + mUnitAction->setVisible( !advanced ); + } + else + { + mTypeButton->setVisible( !advanced ); + mToleranceSpinBox->setVisible( !advanced ); + mUnitsComboBox->setVisible( !advanced ); + if ( mDisplayMode == Widget && mLayerTreeView ) + { + mLayerTreeView->setVisible( advanced ); + } + } +} + +QgsSnappingConfig QgsSnappingWidget::config() const +{ + return mConfig; +} + +void QgsSnappingWidget::setConfig( const QgsSnappingConfig& config ) +{ + if ( mConfig == config ) + return; + + mConfig = config; +} + + + +void QgsSnappingWidget::cleanGroup( QgsLayerTreeNode *node ) +{ + QgsLayerTreeGroup *group = QgsLayerTree::isGroup( node ) ? QgsLayerTree::toGroup( node ) : nullptr; + if ( !group ) + return; + + QList toRemove; + Q_FOREACH ( QgsLayerTreeNode *child, node->children() ) + { + if ( QgsLayerTree::isLayer( child ) && QgsLayerTree::toLayer( child )->layer()->type() != QgsMapLayer::VectorLayer ) + { + toRemove << child; + continue; + } + + cleanGroup( child ); + + if ( QgsLayerTree::isGroup( child ) && child->children().isEmpty() ) + toRemove << child; + } + + Q_FOREACH ( QgsLayerTreeNode *child, toRemove ) + group->removeChildNode( child ); +} diff --git a/src/app/qgssnappingwidget.h b/src/app/qgssnappingwidget.h new file mode 100644 index 00000000000..0a3223cfb3f --- /dev/null +++ b/src/app/qgssnappingwidget.h @@ -0,0 +1,130 @@ +/*************************************************************************** + qgssnappingwidget.h + begin : August 2016 + copyright : (C) 2016 Denis Rouzaud + email : denis.rouzaud@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 QGSSNAPPINGWIDGET_H +#define QGSSNAPPINGWIDGET_H + +class QAction; +class QComboBox; +class QDoubleSpinBox; +class QFont; +class QToolButton; +class QTreeView; + +class QgsLayerTreeGroup; +class QgsLayerTreeNode; +class QgsLayerTreeView; +class QgsMapCanvas; +class QgsProject; + +#include "qgssnappingconfig.h" + +#include + +/** + * A widget which lets the user defines settings for snapping on a project + * The widget can be displayed as a toolbar, in the status bar or as dialog/widget. + * The display mode is automatically chose based on the parent widget type. + */ +class APP_EXPORT QgsSnappingWidget : public QWidget +{ + Q_OBJECT + + public: + + /** + * Constructor + * @param project The project with which this widget configuration will be synchronized + * @param canvas the map canvas (used for map units) + * @param parent is the parent widget. Based on the type of parent, it will + * be displayed a tool bar, in the status bar or as a widget/dialog. + */ + QgsSnappingWidget( QgsProject* project, QgsMapCanvas* canvas, QWidget* parent = nullptr ); + + /** Destructor */ + virtual ~QgsSnappingWidget(); + + /** + * The snapping configuration is what is managed by this widget. + */ + QgsSnappingConfig config() const; + + /** + * The snapping configuration is what is managed by this widget. + */ + void setConfig( const QgsSnappingConfig& config ); + + signals: + void snappingConfigChanged( ); + + private slots: + void projectSnapSettingsChanged(); + + void enableSnapping( bool checked ); + + void changeTolerance( double tolerance ); + + void changeUnit( int idx ); + + void enableTopologicalEditing( bool enabled ); + + void enableIntersectionSnapping( bool enabled ); + + void modeButtonTriggered( QAction* action ); + void typeButtonTriggered( QAction* action ); + + //! number of decimals of the tolerance spin box depends on map units + void updateToleranceDecimals(); + + private: + enum DisplayMode + { + ToolBar, + StatusBar, + Widget + }; + DisplayMode mDisplayMode; + + //! modeChanged determines if widget are visible or not based on mode + void modeChanged(); + + QgsProject* mProject; + QgsSnappingConfig mConfig; + QgsMapCanvas* mCanvas; + + QAction* mEnabledAction; + QToolButton* mModeButton; + QAction* mModeAction; // hide widget does not work on toolbar, action needed + QAction* mAllLayersAction; + QAction* mActiveLayerAction; + QAction* mAdvancedModeAction; + QToolButton* mTypeButton; + QAction* mTypeAction; // hide widget does not work on toolbar, action needed + QAction* mVertexAction; + QAction* mSegmentAction; + QAction* mVertexAndSegmentAction; + QDoubleSpinBox* mToleranceSpinBox; + QAction* mToleranceAction; // hide widget does not work on toolbar, action needed + QComboBox* mUnitsComboBox; + QAction* mUnitAction; // hide widget does not work on toolbar, action needed + QAction* mTopologicalEditingAction; + QAction* mIntersectionSnappingAction; + QTreeView* mLayerTreeView; + + void cleanGroup( QgsLayerTreeNode* node ); +}; + +#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e09cccab821..b518d1c80d3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -179,6 +179,7 @@ SET(QGIS_CORE_SRCS qgspointlocator.cpp qgsproject.cpp qgsprojectfiletransform.cpp + qgssnappingconfig.cpp qgsprojectproperty.cpp qgsprojectversion.cpp qgsprovidermetadata.cpp @@ -678,6 +679,7 @@ SET(QGIS_CORE_HDRS qgsproject.h qgsprojectfiletransform.h qgsprojectproperty.h + qgssnappingconfig.h qgsprojectversion.h qgsprovidermetadata.h qgsproviderregistry.h @@ -692,6 +694,7 @@ SET(QGIS_CORE_HDRS qgsscaleutils.h qgssimplifymethod.h qgssnapper.h + qgssnappingconfig.h qgssnappingutils.h qgsspatialindex.h qgssqlexpressioncompiler.h diff --git a/src/core/qgsproject.cpp b/src/core/qgsproject.cpp index 48cbfb9f660..533de5cbe8b 100644 --- a/src/core/qgsproject.cpp +++ b/src/core/qgsproject.cpp @@ -28,6 +28,7 @@ #include "qgspluginlayer.h" #include "qgspluginlayerregistry.h" #include "qgsprojectfiletransform.h" +#include "qgssnappingconfig.h" #include "qgsprojectproperty.h" #include "qgsprojectversion.h" #include "qgsrasterlayer.h" @@ -368,6 +369,7 @@ QgsProject::QgsProject() mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this ); connect( QgsMapLayerRegistry::instance(), SIGNAL( layersAdded( QList ) ), this, SLOT( onMapLayersAdded( QList ) ) ); connect( QgsMapLayerRegistry::instance(), SIGNAL( layersRemoved( QStringList ) ), this, SLOT( cleanTransactionGroups() ) ); + connect( QgsMapLayerRegistry::instance(), SIGNAL( layersWillBeRemoved( QList ) ), this, SLOT( onMapLayersRemoved( QList ) ) ); } @@ -479,6 +481,7 @@ void QgsProject::clear() imp_->clear(); mEmbeddedLayers.clear(); mRelationManager->clear(); + mSnappingConfig.reset(); mMapThemeCollection.reset( new QgsMapThemeCollection() ); @@ -632,6 +635,21 @@ void QgsProject::processLayerJoins( QgsVectorLayer* layer ) layer->updateFields(); } +QgsSnappingConfig QgsProject::snappingConfig() const +{ + return mSnappingConfig; +} + +void QgsProject::setSnappingConfig( const QgsSnappingConfig& snappingConfig ) +{ + if ( mSnappingConfig == snappingConfig ) + return; + + mSnappingConfig = snappingConfig; + setDirty(); + emit snappingConfigChanged(); +} + bool QgsProject::_getMapLayers( const QDomDocument& doc, QList& brokenNodes ) { // Layer order is set by the restoring the legend settings from project file. @@ -926,6 +944,9 @@ bool QgsProject::read() it.value()->setDependencies( it.value()->dependencies() ); } + mSnappingConfig.readProject( *doc ); + emit snappingConfigChanged(); + // read the project: used by map canvas and legend emit readProject( *doc ); @@ -1039,6 +1060,15 @@ void QgsProject::onMapLayersAdded( const QList& layers ) } } } + + if ( mSnappingConfig.addLayers( layers ) ) + emit snappingConfigChanged(); +} + +void QgsProject::onMapLayersRemoved( const QList& layers ) +{ + if ( mSnappingConfig.removeLayers( layers ) ) + emit snappingConfigChanged(); } void QgsProject::cleanTransactionGroups( bool force ) @@ -1143,6 +1173,8 @@ bool QgsProject::write() clonedRoot->writeXml( qgisNode ); delete clonedRoot; + mSnappingConfig.writeProject( *doc ); + // let map canvas and legend write their information emit writeProject( *doc ); @@ -1975,134 +2007,6 @@ void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsL } } -void QgsProject::setSnapSettingsForLayer( const QString &layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection ) -{ - QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList; - snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList ); - int idx = layerIdList.indexOf( layerId ); - if ( idx != -1 ) - { - layerIdList.removeAt( idx ); - enabledList.removeAt( idx ); - snapTypeList.removeAt( idx ); - toleranceUnitList.removeAt( idx ); - toleranceList.removeAt( idx ); - avoidIntersectionList.removeOne( layerId ); - } - - layerIdList.append( layerId ); - - // enabled - enabledList.append( enabled ? "enabled" : "disabled" ); - - // snap type - QString typeString; - if ( type == QgsSnapper::SnapToSegment ) - { - typeString = "to_segment"; - } - else if ( type == QgsSnapper::SnapToVertexAndSegment ) - { - typeString = "to_vertex_and_segment"; - } - else - { - typeString = "to_vertex"; - } - snapTypeList.append( typeString ); - - // units - toleranceUnitList.append( QString::number( unit ) ); - - // tolerance - toleranceList.append( QString::number( tolerance ) ); - - // avoid intersection - if ( avoidIntersection ) - { - avoidIntersectionList.append( layerId ); - } - - writeEntry( "Digitizing", "/LayerSnappingList", layerIdList ); - writeEntry( "Digitizing", "/LayerSnappingEnabledList", enabledList ); - writeEntry( "Digitizing", "/LayerSnappingToleranceList", toleranceList ); - writeEntry( "Digitizing", "/LayerSnappingToleranceUnitList", toleranceUnitList ); - writeEntry( "Digitizing", "/LayerSnapToList", snapTypeList ); - writeEntry( "Digitizing", "/AvoidIntersectionsList", avoidIntersectionList ); - emit snapSettingsChanged(); -} - -bool QgsProject::snapSettingsForLayer( const QString &layerId, bool &enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType &units, double &tolerance, - bool &avoidIntersection ) const -{ - QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList; - snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList ); - int idx = layerIdList.indexOf( layerId ); - if ( idx == -1 ) - { - return false; - } - - // make sure all lists are long enough - int minListEntries = idx + 1; - if ( layerIdList.size() < minListEntries || enabledList.size() < minListEntries || snapTypeList.size() < minListEntries || - toleranceUnitList.size() < minListEntries || toleranceList.size() < minListEntries ) - { - return false; - } - - // enabled - enabled = enabledList.at( idx ) == "enabled"; - - // snap type - QString snapType = snapTypeList.at( idx ); - if ( snapType == "to_segment" ) - { - type = QgsSnapper::SnapToSegment; - } - else if ( snapType == "to_vertex_and_segment" ) - { - type = QgsSnapper::SnapToVertexAndSegment; - } - else // to vertex - { - type = QgsSnapper::SnapToVertex; - } - - // units - if ( toleranceUnitList.at( idx ) == "1" ) - { - units = QgsTolerance::Pixels; - } - else if ( toleranceUnitList.at( idx ) == "2" ) - { - units = QgsTolerance::ProjectUnits; - } - else - { - units = QgsTolerance::LayerUnits; - } - - // tolerance - tolerance = toleranceList.at( idx ).toDouble(); - - // avoid intersection - avoidIntersection = ( avoidIntersectionList.indexOf( layerId ) != -1 ); - - return true; -} - -void QgsProject::snapSettings( QStringList &layerIdList, QStringList &enabledList, QStringList &snapTypeList, QStringList &toleranceUnitList, QStringList &toleranceList, - QStringList &avoidIntersectionList ) const -{ - layerIdList = readListEntry( "Digitizing", "/LayerSnappingList" ); - enabledList = readListEntry( "Digitizing", "/LayerSnappingEnabledList" ); - toleranceList = readListEntry( "Digitizing", "/LayerSnappingToleranceList" ); - toleranceUnitList = readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList" ); - snapTypeList = readListEntry( "Digitizing", "/LayerSnapToList" ); - avoidIntersectionList = readListEntry( "Digitizing", "/AvoidIntersectionsList" ); -} - bool QgsProject::evaluateDefaultValues() const { return imp_->evaluateDefaultValues; @@ -2125,7 +2029,7 @@ void QgsProject::setEvaluateDefaultValues( bool evaluateDefaultValues ) void QgsProject::setTopologicalEditing( bool enabled ) { QgsProject::instance()->writeEntry( "Digitizing", "/TopologicalEditing", ( enabled ? 1 : 0 ) ); - emit snapSettingsChanged(); + // todo emit snapSettingsChanged(); } bool QgsProject::topologicalEditing() const diff --git a/src/core/qgsproject.h b/src/core/qgsproject.h index 5905fba5868..3e0590779c8 100644 --- a/src/core/qgsproject.h +++ b/src/core/qgsproject.h @@ -32,6 +32,7 @@ //#include #include "qgssnapper.h" #include "qgsunittypes.h" +#include "qgssnappingconfig.h" #include "qgsprojectversion.h" #include "qgsexpressioncontextgenerator.h" #include "qgscoordinatereferencesystem.h" @@ -44,12 +45,13 @@ class QDomNode; class QgsLayerTreeGroup; class QgsLayerTreeRegistryBridge; class QgsMapLayer; +class QgsMapThemeCollection; class QgsProjectBadLayerHandler; class QgsRelationManager; -class QgsVectorLayer; -class QgsMapThemeCollection; -class QgsTransactionGroup; class QgsTolerance; +class QgsTransactionGroup; +class QgsVectorLayer; + /** \ingroup core * Reads and writes project states. @@ -77,6 +79,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera Q_PROPERTY( QString homePath READ homePath NOTIFY homePathChanged ) Q_PROPERTY( QgsCoordinateReferenceSystem crs READ crs WRITE setCrs ) Q_PROPERTY( QgsMapThemeCollection* mapThemeCollection READ mapThemeCollection ) + Q_PROPERTY( QgsSnappingConfig snappingConfig READ snappingConfig WRITE setSnappingConfig NOTIFY snappingConfigChanged ) public: @@ -333,14 +336,6 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera */ QgsLayerTreeGroup* createEmbeddedGroup( const QString& groupName, const QString& projectFilePath, const QStringList &invisibleLayers ); - /** Convenience function to set snap settings per layer */ - void setSnapSettingsForLayer( const QString& layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, - bool avoidIntersection ); - - /** Convenience function to query snap settings of a layer */ - bool snapSettingsForLayer( const QString& layerId, bool& enabled, QgsSnapper::SnappingType& type, QgsTolerance::UnitType& units, double& tolerance, - bool& avoidIntersection ) const; - /** Convenience function to set topological editing */ void setTopologicalEditing( bool enabled ); @@ -460,33 +455,26 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera QgsExpressionContext createExpressionContext() const override; - protected: - /** Set error message from read/write operation - * @note not available in Python bindings + /** + * The snapping configuration for this project. + * + * @note Added in QGIS 3.0 */ - void setError( const QString& errorMessage ); + QgsSnappingConfig snappingConfig() const; - /** Clear error message - * @note not available in Python bindings + /** + * The snapping configuration for this project. + * + * @note Added in QGIS 3.0 */ - void clearError(); - - //! Creates layer and adds it to maplayer registry - //! @note not available in python bindings - bool addLayer( const QDomElement& layerElem, QList& brokenNodes, QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList ); - - //! @note not available in python bindings - void initializeEmbeddedSubtree( const QString& projectFilePath, QgsLayerTreeGroup* group ); - - //! @note not available in python bindings - void loadEmbeddedNodes( QgsLayerTreeGroup* group ); + void setSnappingConfig( const QgsSnappingConfig& snappingConfig ); signals: //! emitted when project is being read - void readProject( const QDomDocument & ); + void readProject( const QDomDocument& ); //! emitted when project is being written - void writeProject( QDomDocument & ); + void writeProject( QDomDocument& ); /** * Emitted, after the basic initialization of a layer from the project @@ -496,7 +484,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera * @param mapLayer The map layer which is being initialized * @param layerNode The layer node from the project file */ - void readMapLayer( QgsMapLayer *mapLayer, const QDomElement &layerNode ); + void readMapLayer( QgsMapLayer* mapLayer, const QDomElement& layerNode ); /** * Emitted, when a layer is being saved. You can use this method to save @@ -523,8 +511,6 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera void loadingLayer( const QString& ); - void snapSettingsChanged(); - //! Emitted when the list of layer which are excluded from map identification changes void nonIdentifiableLayersChanged( QStringList nonIdentifiableLayers ); @@ -534,6 +520,9 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera //! Emitted when the home path of the project changes void homePathChanged(); + //! emitted whenever the configuration for snapping has changed + void snappingConfigChanged(); + /** Emitted whenever the expression variables stored in the project have been changed. * @note added in QGIS 3.0 */ @@ -566,6 +555,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera private slots: void onMapLayersAdded( const QList& layers ); + void onMapLayersRemoved( const QList& layers ); void cleanTransactionGroups( bool force = false ); private: @@ -594,6 +584,26 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera */ void processLayerJoins( QgsVectorLayer* layer ); + /** Set error message from read/write operation + * @note not available in Python bindings + */ + void setError( const QString& errorMessage ); + + /** Clear error message + * @note not available in Python bindings + */ + void clearError(); + + //! Creates layer and adds it to maplayer registry + //! @note not available in python bindings + bool addLayer( const QDomElement& layerElem, QList& brokenNodes, QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList ); + + //! @note not available in python bindings + void initializeEmbeddedSubtree( const QString& projectFilePath, QgsLayerTreeGroup* group ); + + //! @note not available in python bindings + void loadEmbeddedNodes( QgsLayerTreeGroup* group ); + QString mErrorMessage; QgsProjectBadLayerHandler* mBadLayerHandler; @@ -604,8 +614,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera */ QHash< QString, QPair< QString, bool> > mEmbeddedLayers; - void snapSettings( QStringList& layerIdList, QStringList& enabledList, QStringList& snapTypeList, QStringList& snapUnitList, QStringList& toleranceUnitList, - QStringList& avoidIntersectionList ) const; + QgsSnappingConfig mSnappingConfig; QgsRelationManager* mRelationManager; diff --git a/src/core/qgssnappingconfig.cpp b/src/core/qgssnappingconfig.cpp new file mode 100644 index 00000000000..04bd356c83d --- /dev/null +++ b/src/core/qgssnappingconfig.cpp @@ -0,0 +1,509 @@ +/*************************************************************************** + qgsprojectsnappingsettings.cpp - QgsProjectSnappingSettings + + --------------------- + begin : 29.8.2016 + copyright : (C) 2016 by Denis Rouzaud + email : denis.rouzaud@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 "qgssnappingconfig.h" + +#include +#include +#include + +#include "qgslogger.h" +#include "qgsmaplayerregistry.h" +#include "qgsvectorlayer.h" +#include "qgsproject.h" + +QgsSnappingConfig::IndividualLayerSettings::IndividualLayerSettings() + : mValid( false ) + , mEnabled( false ) + , mType( Vertex ) + , mTolerance( 0 ) + , mUnits( QgsTolerance::Pixels ) + , mAvoidIntersection( false ) +{} + + +QgsSnappingConfig::IndividualLayerSettings::IndividualLayerSettings( bool enabled, SnappingType type, double tolerance, QgsTolerance::UnitType units, bool avoidIntersection ) + : mValid( true ) + , mEnabled( enabled ) + , mType( type ) + , mTolerance( tolerance ) + , mUnits( units ) + , mAvoidIntersection( avoidIntersection ) +{} + +bool QgsSnappingConfig::IndividualLayerSettings::valid() const +{ + return mValid; +} + +bool QgsSnappingConfig::IndividualLayerSettings::enabled() const +{ + return mEnabled; +} + +void QgsSnappingConfig::IndividualLayerSettings::setEnabled( bool enabled ) +{ + mEnabled = enabled; +} + +QgsSnappingConfig::SnappingType QgsSnappingConfig::IndividualLayerSettings::type() const +{ + return mType; +} + +void QgsSnappingConfig::IndividualLayerSettings::setType( SnappingType type ) +{ + mType = type; +} + +double QgsSnappingConfig::IndividualLayerSettings::tolerance() const +{ + return mTolerance; +} + +void QgsSnappingConfig::IndividualLayerSettings::setTolerance( double tolerance ) +{ + mTolerance = tolerance; +} + +QgsTolerance::UnitType QgsSnappingConfig::IndividualLayerSettings::units() const +{ + return mUnits; +} + +void QgsSnappingConfig::IndividualLayerSettings::setUnits( QgsTolerance::UnitType units ) +{ + mUnits = units; +} + +bool QgsSnappingConfig::IndividualLayerSettings::avoidIntersection() const +{ + return mAvoidIntersection; +} + +void QgsSnappingConfig::IndividualLayerSettings::setAvoidIntersection( bool avoidIntersection ) +{ + mAvoidIntersection = avoidIntersection; +} + +bool QgsSnappingConfig::IndividualLayerSettings::operator !=( const QgsSnappingConfig::IndividualLayerSettings& other ) const +{ + return mValid != other.mValid + || mEnabled != other.mEnabled + || mType != other.mType + || mTolerance != other.mTolerance + || mUnits != other.mUnits + || mAvoidIntersection != other.mAvoidIntersection; +} + +bool QgsSnappingConfig::IndividualLayerSettings::operator ==( const QgsSnappingConfig::IndividualLayerSettings& other ) const +{ + return mValid == other.mValid + && mEnabled == other.mEnabled + && mType == other.mType + && mTolerance == other.mTolerance + && mUnits == other.mUnits + && mAvoidIntersection == other.mAvoidIntersection; +} + + +QgsSnappingConfig::QgsSnappingConfig() +{ + reset(); +} + +QgsSnappingConfig::~QgsSnappingConfig() +{ +} + +bool QgsSnappingConfig::operator==( const QgsSnappingConfig& other ) const +{ + return mEnabled == other.mEnabled + && mMode == other.mMode + && mType == other.mType + && mTolerance == other.mTolerance + && mUnits == other.mUnits + && mTopologicalEditing == other.mTopologicalEditing + && mIntersectionSnapping == other.mIntersectionSnapping + && mIndividualLayerSettings == other.mIndividualLayerSettings; +} + +void QgsSnappingConfig::reset() +{ + // get defaults values. They are both used for standard and advanced configuration (per layer) + bool enabled = QSettings().value( "/qgis/digitizing/default_advanced_snap_enabled", true ).toBool(); + SnappingMode mode = ( SnappingMode )QSettings().value( "/qgis/digitizing/default_snap_mode", ( int )AllLayers ).toInt(); + if ( mMode == 0 ) + { + // backward compatibility with QGIS 2.x + // could be removed in 3.4+ + mMode = AllLayers; + } + SnappingType type = ( SnappingType )QSettings().value( "/qgis/digitizing/default_snap_type", ( int )Vertex ).toInt(); + double tolerance = QSettings().value( "/qgis/digitizing/default_snapping_tolerance", 0 ).toDouble(); + QgsTolerance::UnitType units = ( QgsTolerance::UnitType )QSettings().value( "/qgis/digitizing/default_snapping_tolerance_unit", ( int )QgsTolerance::ProjectUnits ).toInt(); + + // assign main (standard) config + mEnabled = enabled; + mMode = mode; + mType = type; + mTolerance = tolerance; + // do not allow unit to be "layer" if not in advanced configuration + if ( mMode != AdvancedConfiguration ) + { + mUnits = QgsTolerance::ProjectUnits; + } + else + { + mUnits = units; + } + mTopologicalEditing = false; + mIntersectionSnapping = false; + + // set advanced config + mIndividualLayerSettings = QHash(); + Q_FOREACH ( QgsMapLayer *ml, QgsMapLayerRegistry::instance()->mapLayers() ) + { + QgsVectorLayer* vl = dynamic_cast( ml ); + if ( vl ) + { + mIndividualLayerSettings.insert( vl, IndividualLayerSettings( enabled, type, tolerance, units ) ); + } + } +} + +bool QgsSnappingConfig::enabled() const +{ + return mEnabled; +} + +void QgsSnappingConfig::setEnabled( bool enabled ) +{ + if ( mEnabled == enabled ) + { + return; + } + mEnabled = enabled; +} + +QgsSnappingConfig::SnappingMode QgsSnappingConfig::mode() const +{ + return mMode; +} + +void QgsSnappingConfig::setMode( QgsSnappingConfig::SnappingMode mode ) +{ + if ( mMode == mode ) + { + return; + } + mMode = mode; +} + +QgsSnappingConfig::SnappingType QgsSnappingConfig::type() const +{ + return mType; +} + +void QgsSnappingConfig::setType( QgsSnappingConfig::SnappingType type ) +{ + if ( mType == type ) + { + return; + } + mType = type; +} + +double QgsSnappingConfig::tolerance() const +{ + return mTolerance; +} + +void QgsSnappingConfig::setTolerance( double tolerance ) +{ + if ( mTolerance == tolerance ) + { + return; + } + mTolerance = tolerance; +} + +QgsTolerance::UnitType QgsSnappingConfig::units() const +{ + return mUnits; +} + +void QgsSnappingConfig::setUnits( QgsTolerance::UnitType units ) +{ + if ( mUnits == units ) + { + return; + } + mUnits = units; +} + +bool QgsSnappingConfig::topologicalEditing() const +{ + return mTopologicalEditing; +} + +void QgsSnappingConfig::setTopologicalEditing( bool enabled ) +{ + mTopologicalEditing = enabled; +} + +bool QgsSnappingConfig::intersectionSnapping() const +{ + return mIntersectionSnapping; +} + +void QgsSnappingConfig::setIntersectionSnapping( bool enabled ) +{ + mIntersectionSnapping = enabled; +} + +QHash QgsSnappingConfig::individualLayerSettings() const +{ + return mIndividualLayerSettings; +} + +QgsSnappingConfig::IndividualLayerSettings QgsSnappingConfig::individualLayerSettings( QgsVectorLayer* vl ) const +{ + if ( vl && mIndividualLayerSettings.contains( vl ) ) + { + return mIndividualLayerSettings.value( vl ); + } + else + { + // return invalid settings + return IndividualLayerSettings(); + } +} + +void QgsSnappingConfig::setIndividualLayerSettings( QgsVectorLayer* vl, IndividualLayerSettings individualLayerSettings ) +{ + if ( !vl || mIndividualLayerSettings.value( vl ) == individualLayerSettings ) + { + return; + } + mIndividualLayerSettings.insert( vl, individualLayerSettings ); +} + +bool QgsSnappingConfig::operator!=( const QgsSnappingConfig& other ) const +{ + return mEnabled != other.mEnabled + || mMode != other.mMode + || mType != other.mType + || mTolerance != other.mTolerance + || mUnits != other.mUnits + || mIndividualLayerSettings != other.mIndividualLayerSettings; +} + +void QgsSnappingConfig::readProject( const QDomDocument& doc ) +{ + QDomElement snapSettingsElem = doc.firstChildElement( "qgis" ).firstChildElement( "snapping-settings" ); + if ( snapSettingsElem.isNull() ) + { + readLegacySettings(); + return; + } + + if ( snapSettingsElem.hasAttribute( "enabled" ) ) + mEnabled = snapSettingsElem.attribute( "enabled" ) == "1"; + + if ( snapSettingsElem.hasAttribute( "mode" ) ) + mMode = ( SnappingMode )snapSettingsElem.attribute( "mode" ).toInt(); + + if ( snapSettingsElem.hasAttribute( "type" ) ) + mType = ( SnappingType )snapSettingsElem.attribute( "type" ).toInt(); + + if ( snapSettingsElem.hasAttribute( "tolerance" ) ) + mTolerance = snapSettingsElem.attribute( "tolerance" ).toDouble(); + + if ( snapSettingsElem.hasAttribute( "unit" ) ) + mUnits = ( QgsTolerance::UnitType )snapSettingsElem.attribute( "unit" ).toInt(); + + if ( snapSettingsElem.hasAttribute( "topological-editing" ) ) + mTopologicalEditing = snapSettingsElem.attribute( "topological-editing" ) == "1"; + + if ( snapSettingsElem.hasAttribute( "intersection-snapping" ) ) + mIntersectionSnapping = snapSettingsElem.attribute( "intersection-snapping" ) == "1"; + + // do not clear the settings as they must be automatically synchronized with current layers + QDomNodeList nodes = snapSettingsElem.elementsByTagName( "individual-layer-settings" ); + if ( nodes.count() ) + { + QDomNode node = nodes.item( 0 ); + QDomNodeList settingNodes = node.childNodes(); + int layerCount = settingNodes.count(); + for ( int i = 0; i < layerCount; ++i ) + { + QDomElement settingElement = settingNodes.at( i ).toElement(); + if ( settingElement.tagName() != "layer-setting" ) + { + QgsLogger::warning( QApplication::translate( "QgsProjectSnappingSettings", "Cannot read individual settings. Unexpected tag '%1'" ).arg( settingElement.tagName() ) ); + continue; + } + + QString layerId = settingElement.attribute( "id" ); + bool enabled = settingElement.attribute( "enabled" ) == "1"; + SnappingType type = ( SnappingType )settingElement.attribute( "type" ).toInt(); + double tolerance = settingElement.attribute( "tolerance" ).toDouble(); + QgsTolerance::UnitType units = ( QgsTolerance::UnitType )settingElement.attribute( "units" ).toInt(); + bool avoidIntersection = settingElement.attribute( "avoid-intersection" ) == "1"; + + QgsMapLayer* ml = QgsMapLayerRegistry::instance()->mapLayer( layerId ); + if ( !ml || ml->type() != QgsMapLayer::VectorLayer ) + continue; + + QgsVectorLayer* vl = qobject_cast( ml ); + + IndividualLayerSettings setting = IndividualLayerSettings( enabled, type, tolerance, units, avoidIntersection ); + mIndividualLayerSettings.insert( vl, setting ); + } + } +} + +void QgsSnappingConfig::writeProject( QDomDocument& doc ) +{ + QDomElement snapSettingsElem = doc.createElement( "snapping-settings" ); + snapSettingsElem.setAttribute( "enabled", QString::number( mEnabled ) ); + snapSettingsElem.setAttribute( "mode", ( int )mMode ); + snapSettingsElem.setAttribute( "type", ( int )mType ); + snapSettingsElem.setAttribute( "tolerance", mTolerance ); + snapSettingsElem.setAttribute( "unit", ( int )mUnits ); + snapSettingsElem.setAttribute( "topological-editing", QString::number( mTopologicalEditing ) ); + snapSettingsElem.setAttribute( "intersection-snapping", QString::number( mIntersectionSnapping ) ); + + QDomElement ilsElement = doc.createElement( "individual-layer-settings" ); + Q_FOREACH ( QgsVectorLayer* vl, mIndividualLayerSettings.keys() ) + { + IndividualLayerSettings setting = mIndividualLayerSettings.value( vl ); + + QDomElement layerElement = doc.createElement( "layer-setting" ); + layerElement.setAttribute( "id", vl->id() ); + layerElement.setAttribute( "enabled", QString::number( setting.enabled() ) ); + layerElement.setAttribute( "type", ( int )setting.type() ); + layerElement.setAttribute( "tolerance", setting.tolerance() ); + layerElement.setAttribute( "units", ( int )setting.units() ); + layerElement.setAttribute( "avoid-intersection", QString::number( setting.avoidIntersection() ) ); + ilsElement.appendChild( layerElement ); + } + snapSettingsElem.appendChild( ilsElement ); + + doc.firstChildElement( "qgis" ).appendChild( snapSettingsElem ); +} + +bool QgsSnappingConfig::addLayers( const QList& layers ) +{ + bool changed = false; + bool enabled = QSettings().value( "/qgis/digitizing/default_advanced_snap_enabled", true ).toBool(); + SnappingType type = ( SnappingType )QSettings().value( "/qgis/digitizing/default_snap_type", Vertex ).toInt(); + double tolerance = QSettings().value( "/qgis/digitizing/default_snapping_tolerance", 0 ).toDouble(); + QgsTolerance::UnitType units = ( QgsTolerance::UnitType )QSettings().value( "/qgis/digitizing/default_snapping_tolerance_unit", QgsTolerance::ProjectUnits ).toInt(); + + Q_FOREACH ( QgsMapLayer* ml, layers ) + { + QgsVectorLayer* vl = qobject_cast( ml ); + if ( vl ) + { + mIndividualLayerSettings.insert( vl, IndividualLayerSettings( enabled, type, tolerance, units ) ); + changed = true; + } + } + return changed; +} + +bool QgsSnappingConfig::removeLayers( const QList& layers ) +{ + bool changed = false; + Q_FOREACH ( QgsMapLayer* ml, layers ) + { + QgsVectorLayer* vl = qobject_cast( ml ); + if ( vl ) + { + mIndividualLayerSettings.remove( vl ); + changed = true; + } + } + return changed; +} + +void QgsSnappingConfig::readLegacySettings() +{ + mMode = ActiveLayer; + mIndividualLayerSettings.clear(); + + QString snapMode = QgsProject::instance()->readEntry( "Digitizing", "/SnappingMode" ); + + QString snapType = QgsProject::instance()->readEntry( "Digitizing", "/DefaultSnapType", QString( "off" ) ); + if ( snapType == "to segment" ) + mType = Segment; + else if ( snapType == "to vertex and segment" ) + mType = VertexAndSegment; + else if ( snapType == "to vertex" ) + mType = Vertex; + mTolerance = QgsProject::instance()->readDoubleEntry( "Digitizing", "/DefaultSnapTolerance", 0 ); + mUnits = static_cast< QgsTolerance::UnitType >( QgsProject::instance()->readNumEntry( "Digitizing", "/DefaultSnapToleranceUnit", QgsTolerance::ProjectUnits ) ); + + mIntersectionSnapping = QgsProject::instance()->readNumEntry( "Digitizing", "/IntersectionSnapping", 0 ); + + //read snapping settings from project + bool snappingDefinedInProject, ok; + QStringList layerIdList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingList", QStringList(), &snappingDefinedInProject ); + QStringList enabledList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingEnabledList", QStringList(), &ok ); + QStringList toleranceList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceList", QStringList(), &ok ); + QStringList toleranceUnitList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList", QStringList(), &ok ); + QStringList snapToList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnapToList", QStringList(), &ok ); + + // lists must have the same size, otherwise something is wrong + if ( layerIdList.size() != enabledList.size() || + layerIdList.size() != toleranceList.size() || + layerIdList.size() != toleranceUnitList.size() || + layerIdList.size() != snapToList.size() ) + return; + + if ( !snappingDefinedInProject ) + return; // nothing defined in project - use current layer + + // Use snapping information from the project + if ( snapMode == "current_layer" ) + mMode = ActiveLayer; + else if ( snapMode == "all_layers" ) + mMode = AllLayers; + else // either "advanced" or empty (for background compatibility) + mMode = AdvancedConfiguration; + + // load layers, tolerances, snap type + QStringList::const_iterator layerIt( layerIdList.constBegin() ); + QStringList::const_iterator tolIt( toleranceList.constBegin() ); + QStringList::const_iterator tolUnitIt( toleranceUnitList.constBegin() ); + QStringList::const_iterator snapIt( snapToList.constBegin() ); + QStringList::const_iterator enabledIt( enabledList.constBegin() ); + for ( ; layerIt != layerIdList.constEnd(); ++layerIt, ++tolIt, ++tolUnitIt, ++snapIt, ++enabledIt ) + { + QgsVectorLayer* vlayer = qobject_cast( QgsMapLayerRegistry::instance()->mapLayer( *layerIt ) ); + if ( !vlayer || !vlayer->hasGeometryType() ) + continue; + + SnappingType t( *snapIt == "to_vertex" ? Vertex : + ( *snapIt == "to_segment" ? Segment : + VertexAndSegment + ) + ); + + mIndividualLayerSettings.insert( vlayer, IndividualLayerSettings( *enabledIt == "enabled", t, tolIt->toDouble(), static_cast( tolUnitIt->toInt() ) ) ); + } +} diff --git a/src/core/qgssnappingconfig.h b/src/core/qgssnappingconfig.h new file mode 100644 index 00000000000..2a3c6e9d491 --- /dev/null +++ b/src/core/qgssnappingconfig.h @@ -0,0 +1,245 @@ +/*************************************************************************** + qgssnappingconfig.h - QgsSnappingConfig + + --------------------- + begin : 29.8.2016 + copyright : (C) 2016 by Denis Rouzaud + email : denis.rouzaud@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 QGSPROJECTSNAPPINGSETTINGS_H +#define QGSPROJECTSNAPPINGSETTINGS_H + +#include "qgstolerance.h" + +class QDomDocument; +class QgsVectorLayer; + + +/** \ingroup core + * This is a container for configuration of the snapping of the project + * @note added in 3.0 + */ +class CORE_EXPORT QgsSnappingConfig +{ + public: + /** + * SnappingMode defines on which layer the snapping is performed + */ + enum SnappingMode + { + ActiveLayer = 1, /*!< on the active layer */ + AllLayers = 2, /*!< on all vector layers */ + AdvancedConfiguration = 3, /*!< on a per layer configuration basis */ + }; + + /** + * SnappingType defines on what object the snapping is performed + */ + enum SnappingType + { + Vertex = 1, /*!< on vertices only */ + VertexAndSegment = 2, /*!< both on vertices and segments */ + Segment = 3, /*!< on segments only */ + }; + + /** \ingroup core + * This is a container of advanced configuration (per layer) of the snapping of the project + * @note added in 3.0 + */ + class CORE_EXPORT IndividualLayerSettings + { + public: + /** + * @brief IndividualLayerSettings + * @param enabled + * @param type + * @param tolerance + * @param units + * @param avoidIntersection + */ + IndividualLayerSettings( bool enabled, QgsSnappingConfig::SnappingType type, double tolerance, QgsTolerance::UnitType units, bool avoidIntersection = false ); + + /** + * Constructs an invalid setting + */ + IndividualLayerSettings(); + + //! return if settings are valid + bool valid() const; + + //! return if snaping is enbaled + bool enabled() const; + + //! enables the snapping + void setEnabled( bool enabled ); + + //! return the type (vertices and/or segments) + QgsSnappingConfig::SnappingType type() const; + + //! define the type of snapping + void setType( QgsSnappingConfig::SnappingType type ); + + //! return the tolerance + double tolerance() const; + + //! set the tolerance + void setTolerance( double tolerance ); + + //! return the type of units + QgsTolerance::UnitType units() const; + + //! set the type of units + void setUnits( QgsTolerance::UnitType units ); + + //! return if it shall avoid intersection (polygon layers only) + bool avoidIntersection() const; + + //! set if it shall avoid intersection (polygon layers only) + void setAvoidIntersection( bool avoidIntersection ); + + /** + * Compare this configuration to other. + */ + bool operator!= ( const IndividualLayerSettings& other ) const; + + bool operator== ( const IndividualLayerSettings& other ) const; + + private: + bool mValid; + bool mEnabled; + SnappingType mType; + double mTolerance; + QgsTolerance::UnitType mUnits; + bool mAvoidIntersection; + }; + + /** + * Constructor with default parameters defined in global settings + */ + explicit QgsSnappingConfig(); + + ~QgsSnappingConfig(); + + bool operator==( const QgsSnappingConfig& other ) const; + + //! reset to default values + void reset(); + + //! return if snapping is enbaled + bool enabled() const; + + //! enables the snapping + void setEnabled( bool enabled ); + + //! return the mode (all layers, active layer, per layer settings) + SnappingMode mode() const; + + //! define the mode of snapping + void setMode( SnappingMode mode ); + + //! return the type (vertices and/or segments) + SnappingType type() const; + + //! define the type of snapping + void setType( SnappingType type ); + + //! return the tolerance + double tolerance() const; + + //! set the tolerance + void setTolerance( double tolerance ); + + //! return the type of units + QgsTolerance::UnitType units() const; + + //! set the type of units + void setUnits( QgsTolerance::UnitType units ); + + //! return if the topological editing is enabled + bool topologicalEditing() const; + + //! set if the topological editing is enabled + void setTopologicalEditing( bool enabled ); + + //! return if the snapping on intersection is enabled + bool intersectionSnapping() const; + + //! set if the snapping on intersection is enabled + void setIntersectionSnapping( bool enabled ); + + //! return individual snapping settings for all layers + QHash individualLayerSettings() const; + + //! return individual layer snappings settings (applied if mode is AdvancedConfiguration) + QgsSnappingConfig::IndividualLayerSettings individualLayerSettings( QgsVectorLayer* vl ) const; + + //! set individual layer snappings settings (applied if mode is AdvancedConfiguration) + void setIndividualLayerSettings( QgsVectorLayer* vl, QgsSnappingConfig::IndividualLayerSettings individualLayerSettings ); + + /** + * Compare this configuration to other. + */ + bool operator!= ( const QgsSnappingConfig& other ) const; + + public: + /** + * Reads the configuration from the specified QGIS project document. + * + * @note Added in QGIS 3.0 + */ + void readProject( const QDomDocument& doc ); + + /** + * Writes the configuration to the specified QGIS project document. + * + * @note Added in QGIS 3.0 + */ + void writeProject( QDomDocument& doc ); + + /** + * Adds the specified layers as individual layers to the configuration + * with standard configuration. + * When implementing a long-living QgsSnappingConfig (like the one in QgsProject) + * it is best to directly feed this with information from the layer registry. + * + * @return True if changes have been done. + * + * @note Added in QGIS 3.0 + */ + bool addLayers( const QList& layers ); + + + /** + * Removes the specified layers from the individual layer configuration. + * When implementing a long-living QgsSnappingConfig (like the one in QgsProject) + * it is best to directly feed this with information from the layer registry. + * + * @return True if changes have been done. + * + * @note Added in QGIS 3.0 + */ + bool removeLayers( const QList& layers ); + + private: + void readLegacySettings(); + + bool mEnabled; + SnappingMode mMode; + SnappingType mType; + double mTolerance; + QgsTolerance::UnitType mUnits; + bool mTopologicalEditing; + bool mIntersectionSnapping; + + QHash mIndividualLayerSettings; + +}; + +#endif // QGSPROJECTSNAPPINGSETTINGS_H diff --git a/src/core/qgssnappingutils.cpp b/src/core/qgssnappingutils.cpp index 1f205ecca37..f478404fe34 100644 --- a/src/core/qgssnappingutils.cpp +++ b/src/core/qgssnappingutils.cpp @@ -24,16 +24,10 @@ QgsSnappingUtils::QgsSnappingUtils( QObject* parent ) : QObject( parent ) , mCurrentLayer( nullptr ) - , mSnapToMapMode( SnapCurrentLayer ) , mStrategy( IndexHybrid ) - , mDefaultType( QgsPointLocator::Vertex ) - , mDefaultTolerance( 10 ) - , mDefaultUnit( QgsTolerance::Pixels ) - , mSnapOnIntersection( false ) , mHybridPerLayerFeatureLimit( 50000 ) , mIsIndexing( false ) { - connect( QgsMapLayerRegistry::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ), this, SLOT( onLayersWillBeRemoved( QStringList ) ) ); } QgsSnappingUtils::~QgsSnappingUtils() @@ -57,12 +51,10 @@ QgsPointLocator* QgsSnappingUtils::locatorForLayer( QgsVectorLayer* vl ) void QgsSnappingUtils::clearAllLocators() { - Q_FOREACH ( QgsPointLocator* vlpl, mLocators ) - delete vlpl; + qDeleteAll( mLocators ); mLocators.clear(); - Q_FOREACH ( QgsPointLocator* vlpl, mTemporaryLocators ) - delete vlpl; + qDeleteAll( mTemporaryLocators ); mTemporaryLocators.clear(); } @@ -214,17 +206,17 @@ inline QgsRectangle _areaOfInterest( const QgsPoint& point, double tolerance ) QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter ) { - if ( !mMapSettings.hasValidSettings() ) + if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() ) return QgsPointLocator::Match(); - if ( mSnapToMapMode == SnapCurrentLayer ) + if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer ) { - if ( !mCurrentLayer || mDefaultType == 0 ) + if ( !mCurrentLayer || mSnappingConfig.type() == 0 ) return QgsPointLocator::Match(); // data from project - double tolerance = QgsTolerance::toleranceInProjectUnits( mDefaultTolerance, mCurrentLayer, mMapSettings, mDefaultUnit ); - int type = mDefaultType; + double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() ); + int type = mSnappingConfig.type(); prepareIndex( QList() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ) ); @@ -236,7 +228,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg QgsPointLocator::Match bestMatch; _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter ); - if ( mSnapOnIntersection ) + if ( mSnappingConfig.intersectionSnapping() ) { QgsPointLocator* locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance ); QgsPointLocator::MatchList edges = locEdges->edgesInRect( pointMap, tolerance ); @@ -245,7 +237,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg return bestMatch; } - else if ( mSnapToMapMode == SnapAdvanced ) + else if ( mSnappingConfig.mode() == QgsSnappingConfig::AdvancedConfiguration ) { QList layers; Q_FOREACH ( const LayerConfig& layerConfig, mLayers ) @@ -266,7 +258,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg { _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter ); - if ( mSnapOnIntersection ) + if ( mSnappingConfig.intersectionSnapping() ) { edges << loc->edgesInRect( pointMap, tolerance ); maxSnapIntTolerance = qMax( maxSnapIntTolerance, tolerance ); @@ -274,16 +266,16 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg } } - if ( mSnapOnIntersection ) + if ( mSnappingConfig.intersectionSnapping() ) _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxSnapIntTolerance ); return bestMatch; } - else if ( mSnapToMapMode == SnapAllLayers ) + else if ( mSnappingConfig.mode() == QgsSnappingConfig::AllLayers ) { // data from project - double tolerance = QgsTolerance::toleranceInProjectUnits( mDefaultTolerance, nullptr, mMapSettings, mDefaultUnit ); - int type = mDefaultType; + double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), nullptr, mMapSettings, mSnappingConfig.units() ); + int type = mSnappingConfig.type(); QgsRectangle aoi = _areaOfInterest( pointMap, tolerance ); QList layers; @@ -302,12 +294,12 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg { _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter ); - if ( mSnapOnIntersection ) + if ( mSnappingConfig.intersectionSnapping() ) edges << loc->edgesInRect( pointMap, tolerance ); } } - if ( mSnapOnIntersection ) + if ( mSnappingConfig.intersectionSnapping() ) _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance ); return bestMatch; @@ -404,6 +396,22 @@ void QgsSnappingUtils::prepareIndex( const QList& layers mIsIndexing = false; } +QgsSnappingConfig QgsSnappingUtils::config() const +{ + return mSnappingConfig; +} + +void QgsSnappingUtils::setConfig( const QgsSnappingConfig& config ) +{ + if ( mSnappingConfig == config ) + return; + + if ( mSnappingConfig.individualLayerSettings() != config.individualLayerSettings() ) + onIndividualLayerSettingsChanged( config.individualLayerSettings() ); + + mSnappingConfig = config; + emit configChanged(); +} QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( QPoint point, int type, QgsPointLocator::MatchFilter* filter ) { @@ -437,58 +445,6 @@ void QgsSnappingUtils::setCurrentLayer( QgsVectorLayer* layer ) mCurrentLayer = layer; } -void QgsSnappingUtils::setSnapToMapMode( QgsSnappingUtils::SnapToMapMode mode ) -{ - if ( mSnapToMapMode == mode ) - return; - - mSnapToMapMode = mode; - emit configChanged(); -} - -void QgsSnappingUtils::setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit ) -{ - // force map units - can't use layer units for just any layer - if ( unit == QgsTolerance::LayerUnits ) - unit = QgsTolerance::ProjectUnits; - - if ( mDefaultType == type && mDefaultTolerance == tolerance && mDefaultUnit == unit ) - return; - - mDefaultType = type; - mDefaultTolerance = tolerance; - mDefaultUnit = unit; - - if ( mSnapToMapMode != SnapAdvanced ) // does not affect advanced mode - emit configChanged(); -} - -void QgsSnappingUtils::defaultSettings( int& type, double& tolerance, QgsTolerance::UnitType& unit ) -{ - type = mDefaultType; - tolerance = mDefaultTolerance; - unit = mDefaultUnit; -} - -void QgsSnappingUtils::setLayers( const QList& layers ) -{ - if ( mLayers == layers ) - return; - - mLayers = layers; - if ( mSnapToMapMode == SnapAdvanced ) // only affects advanced mode - emit configChanged(); -} - -void QgsSnappingUtils::setSnapOnIntersections( bool enabled ) -{ - if ( mSnapOnIntersection == enabled ) - return; - - mSnapOnIntersection = enabled; - emit configChanged(); -} - QString QgsSnappingUtils::dump() { QString msg = "--- SNAPPING UTILS DUMP ---\n"; @@ -501,25 +457,25 @@ QString QgsSnappingUtils::dump() QList layers; - if ( mSnapToMapMode == SnapCurrentLayer ) + if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer ) { - if ( mSnapToMapMode == SnapCurrentLayer && !mCurrentLayer ) + if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer && !mCurrentLayer ) { msg += "no current layer!"; return msg; } - layers << LayerConfig( mCurrentLayer, QgsPointLocator::Types( mDefaultType ), mDefaultTolerance, mDefaultUnit ); + layers << LayerConfig( mCurrentLayer, QgsPointLocator::Types( mSnappingConfig.type() ), mSnappingConfig.tolerance(), mSnappingConfig.units() ); } - else if ( mSnapToMapMode == SnapAllLayers ) + else if ( mSnappingConfig.mode() == QgsSnappingConfig::AllLayers ) { Q_FOREACH ( const QString& layerID, mMapSettings.layers() ) { if ( QgsVectorLayer* vl = qobject_cast( QgsMapLayerRegistry::instance()->mapLayer( layerID ) ) ) - layers << LayerConfig( vl, QgsPointLocator::Types( mDefaultType ), mDefaultTolerance, mDefaultUnit ); + layers << LayerConfig( vl, QgsPointLocator::Types( mSnappingConfig.type() ), mSnappingConfig.tolerance(), mSnappingConfig.units() ); } } - else if ( mSnapToMapMode == SnapAdvanced ) + else if ( mSnappingConfig.mode() == QgsSnappingConfig::AdvancedConfiguration ) { layers = mLayers; } @@ -575,117 +531,23 @@ QgsCoordinateReferenceSystem QgsSnappingUtils::destinationCrs() const return mMapSettings.hasCrsTransformEnabled() ? mMapSettings.destinationCrs() : QgsCoordinateReferenceSystem(); } - -void QgsSnappingUtils::readConfigFromProject() +void QgsSnappingUtils::onIndividualLayerSettingsChanged( const QHash layerSettings ) { - mSnapToMapMode = SnapCurrentLayer; mLayers.clear(); - QString snapMode = QgsProject::instance()->readEntry( "Digitizing", "/SnappingMode" ); + QHash::const_iterator i; - int type = 0; - QString snapType = QgsProject::instance()->readEntry( "Digitizing", "/DefaultSnapType", QString( "off" ) ); - if ( snapType == "to segment" ) - type = QgsPointLocator::Edge; - else if ( snapType == "to vertex and segment" ) - type = QgsPointLocator::Vertex | QgsPointLocator::Edge; - else if ( snapType == "to vertex" ) - type = QgsPointLocator::Vertex; - double tolerance = QgsProject::instance()->readDoubleEntry( "Digitizing", "/DefaultSnapTolerance", 0 ); - QgsTolerance::UnitType unit = static_cast< QgsTolerance::UnitType >( QgsProject::instance()->readNumEntry( "Digitizing", "/DefaultSnapToleranceUnit", QgsTolerance::ProjectUnits ) ); - setDefaultSettings( type, tolerance, unit ); - - //snapping on intersection on? - setSnapOnIntersections( QgsProject::instance()->readNumEntry( "Digitizing", "/IntersectionSnapping", 0 ) ); - - //read snapping settings from project - bool snappingDefinedInProject, ok; - QStringList layerIdList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingList", QStringList(), &snappingDefinedInProject ); - QStringList enabledList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingEnabledList", QStringList(), &ok ); - QStringList toleranceList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceList", QStringList(), &ok ); - QStringList toleranceUnitList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList", QStringList(), &ok ); - QStringList snapToList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnapToList", QStringList(), &ok ); - - // lists must have the same size, otherwise something is wrong - if ( layerIdList.size() != enabledList.size() || - layerIdList.size() != toleranceList.size() || - layerIdList.size() != toleranceUnitList.size() || - layerIdList.size() != snapToList.size() ) - return; - - if ( !snappingDefinedInProject ) - return; // nothing defined in project - use current layer - - // Use snapping information from the project - if ( snapMode == "current_layer" ) - mSnapToMapMode = SnapCurrentLayer; - else if ( snapMode == "all_layers" ) - mSnapToMapMode = SnapAllLayers; - else // either "advanced" or empty (for background compatibility) - mSnapToMapMode = SnapAdvanced; - - - - // load layers, tolerances, snap type - QStringList::const_iterator layerIt( layerIdList.constBegin() ); - QStringList::const_iterator tolIt( toleranceList.constBegin() ); - QStringList::const_iterator tolUnitIt( toleranceUnitList.constBegin() ); - QStringList::const_iterator snapIt( snapToList.constBegin() ); - QStringList::const_iterator enabledIt( enabledList.constBegin() ); - for ( ; layerIt != layerIdList.constEnd(); ++layerIt, ++tolIt, ++tolUnitIt, ++snapIt, ++enabledIt ) + for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i ) { - // skip layer if snapping is not enabled - if ( *enabledIt != "enabled" ) - continue; - - QgsVectorLayer *vlayer = qobject_cast( QgsMapLayerRegistry::instance()->mapLayer( *layerIt ) ); - if ( !vlayer || !vlayer->hasGeometryType() ) - continue; - - QgsPointLocator::Types t( *snapIt == "to_vertex" ? QgsPointLocator::Vertex : - ( *snapIt == "to_segment" ? QgsPointLocator::Edge : - QgsPointLocator::Vertex | QgsPointLocator::Edge - ) - ); - mLayers.append( LayerConfig( vlayer, t, tolIt->toDouble(), static_cast< QgsTolerance::UnitType >( tolUnitIt->toInt() ) ) ); - } - - emit configChanged(); -} - -void QgsSnappingUtils::onLayersWillBeRemoved( const QStringList& layerIds ) -{ - // remove locators for layers that are going to be deleted - Q_FOREACH ( const QString& layerId, layerIds ) - { - if ( mHybridMaxAreaPerLayer.contains( layerId ) ) - mHybridMaxAreaPerLayer.remove( layerId ); - - for ( LocatorsMap::iterator it = mLocators.begin(); it != mLocators.end(); ) + if ( i->enabled() ) { - if ( it.key()->id() == layerId ) - { - delete it.value(); - it = mLocators.erase( it ); - } - else - { - ++it; - } - } + QgsPointLocator::Types t( i->type() == QgsSnappingConfig::Vertex ? QgsPointLocator::Vertex : + ( i->type() == QgsSnappingConfig::Segment ? QgsPointLocator::Edge : + QgsPointLocator::Vertex | QgsPointLocator::Edge + ) + ); - for ( LocatorsMap::iterator it = mTemporaryLocators.begin(); it != mTemporaryLocators.end(); ) - { - if ( it.key()->id() == layerId ) - { - delete it.value(); - it = mTemporaryLocators.erase( it ); - } - else - { - ++it; - } + mLayers.append( LayerConfig( i.key(), t, i->tolerance(), i->units() ) ); } } } - diff --git a/src/core/qgssnappingutils.h b/src/core/qgssnappingutils.h index 82f74523c28..c968d7e965e 100644 --- a/src/core/qgssnappingutils.h +++ b/src/core/qgssnappingutils.h @@ -20,6 +20,9 @@ #include "qgsmapsettings.h" #include "qgstolerance.h" #include "qgspointlocator.h" +#include "qgssnappingconfig.h" + +class QgsSnappingConfig; /** \ingroup core * This class has all the configuration of snapping and can return answers to snapping queries. @@ -41,6 +44,9 @@ class CORE_EXPORT QgsSnappingUtils : public QObject { Q_OBJECT + + Q_PROPERTY( QgsSnappingConfig config READ config WRITE setConfig NOTIFY configChanged ) + public: QgsSnappingUtils( QObject* parent = nullptr ); ~QgsSnappingUtils(); @@ -61,7 +67,7 @@ class CORE_EXPORT QgsSnappingUtils : public QObject /** Assign current map settings to the utils - used for conversion between screen coords to map coords */ void setMapSettings( const QgsMapSettings& settings ); - const QgsMapSettings& mapSettings() const { return mMapSettings; } + QgsMapSettings mapSettings() const { return mMapSettings; } /** Set current layer so that if mode is SnapCurrentLayer we know which layer to use */ void setCurrentLayer( QgsVectorLayer* layer ); @@ -79,11 +85,6 @@ class CORE_EXPORT QgsSnappingUtils : public QObject SnapAdvanced, //!< snap according to the configuration set in setLayers() }; - /** Set how the snapping to map is done */ - void setSnapToMapMode( SnapToMapMode mode ); - /** Find out how the snapping to map is done */ - SnapToMapMode snapToMapMode() const { return mSnapToMapMode; } - enum IndexingStrategy { IndexAlwaysFull, //!< For all layers build index of full extent. Uses more memory, but queries are faster. @@ -96,11 +97,6 @@ class CORE_EXPORT QgsSnappingUtils : public QObject /** Find out which strategy is used for indexing - by default hybrid indexing is used */ IndexingStrategy indexingStrategy() const { return mStrategy; } - /** Configure options used when the mode is snap to current layer or to all layers */ - void setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit ); - /** Query options used when the mode is snap to current layer or to all layers */ - void defaultSettings( int& type, double& tolerance, QgsTolerance::UnitType& unit ); - /** * Configures how a certain layer should be handled in a snapping operation */ @@ -150,28 +146,27 @@ class CORE_EXPORT QgsSnappingUtils : public QObject QgsTolerance::UnitType unit; }; - /** Set layers which will be used for snapping */ - void setLayers( const QList& layers ); /** Query layers used for snapping */ QList layers() const { return mLayers; } - /** Set whether to consider intersections of nearby segments for snapping */ - void setSnapOnIntersections( bool enabled ); - /** Query whether to consider intersections of nearby segments for snapping */ - bool snapOnIntersections() const { return mSnapOnIntersection; } - /** Get extra information about the instance * @note added in QGIS 2.14 */ QString dump(); - public slots: - /** Read snapping configuration from the project */ - void readConfigFromProject(); + /** + * The snapping configuration controls the behavior of this object + */ + QgsSnappingConfig config() const; + + /** + * The snapping configuration controls the behavior of this object + */ + void setConfig( const QgsSnappingConfig& snappingConfig ); signals: - /** Emitted when snapping configuration has been changed - * @note added in QGIS 2.14 + /** + * Emitted when the snapping settings object changes. */ void configChanged(); @@ -181,10 +176,8 @@ class CORE_EXPORT QgsSnappingUtils : public QObject //! Called when finished indexing a layer. When index == count the indexing is complete virtual void prepareIndexProgress( int index ) { Q_UNUSED( index ); } - private slots: - void onLayersWillBeRemoved( const QStringList& layerIds ); - private: + void onIndividualLayerSettingsChanged( const QHash layerSettings ); //! Get destination CRS from map settings, or an invalid CRS if projections are disabled QgsCoordinateReferenceSystem destinationCrs() const; @@ -208,14 +201,11 @@ class CORE_EXPORT QgsSnappingUtils : public QObject QgsMapSettings mMapSettings; QgsVectorLayer* mCurrentLayer; + QgsSnappingConfig mSnappingConfig; + // configuration - SnapToMapMode mSnapToMapMode; IndexingStrategy mStrategy; - int mDefaultType; - double mDefaultTolerance; - QgsTolerance::UnitType mDefaultUnit; QList mLayers; - bool mSnapOnIntersection; // internal data typedef QMap LocatorsMap; diff --git a/src/gui/qgsmapcanvastracer.cpp b/src/gui/qgsmapcanvastracer.cpp index c4d5b3b259c..e0473457c93 100644 --- a/src/gui/qgsmapcanvastracer.cpp +++ b/src/gui/qgsmapcanvastracer.cpp @@ -21,6 +21,7 @@ #include "qgsmessagebaritem.h" #include "qgssnappingutils.h" #include "qgsvectorlayer.h" +#include "qgssnappingconfig.h" #include @@ -101,17 +102,17 @@ void QgsMapCanvasTracer::configure() QList layers; QStringList visibleLayerIds = mCanvas->mapSettings().layers(); - switch ( mCanvas->snappingUtils()->snapToMapMode() ) + switch ( mCanvas->snappingUtils()->config().mode() ) { default: - case QgsSnappingUtils::SnapCurrentLayer: + case QgsSnappingConfig::ActiveLayer: { QgsVectorLayer* vl = qobject_cast( mCanvas->currentLayer() ); if ( vl && visibleLayerIds.contains( vl->id() ) ) layers << vl; } break; - case QgsSnappingUtils::SnapAllLayers: + case QgsSnappingConfig::AllLayers: Q_FOREACH ( const QString& layerId, visibleLayerIds ) { QgsVectorLayer* vl = qobject_cast( QgsMapLayerRegistry::instance()->mapLayer( layerId ) ); @@ -119,7 +120,7 @@ void QgsMapCanvasTracer::configure() layers << vl; } break; - case QgsSnappingUtils::SnapAdvanced: + case QgsSnappingConfig::AdvancedConfiguration: Q_FOREACH ( const QgsSnappingUtils::LayerConfig& cfg, mCanvas->snappingUtils()->layers() ) { if ( visibleLayerIds.contains( cfg.layer->id() ) ) @@ -134,6 +135,6 @@ void QgsMapCanvasTracer::configure() void QgsMapCanvasTracer::onCurrentLayerChanged() { // no need to bother if we are not snapping - if ( mCanvas->snappingUtils()->snapToMapMode() == QgsSnappingUtils::SnapCurrentLayer ) + if ( mCanvas->snappingUtils()->config().mode() == QgsSnappingConfig::ActiveLayer ) invalidateGraph(); } diff --git a/src/gui/qgsmapmouseevent.cpp b/src/gui/qgsmapmouseevent.cpp index b1cb728c5ad..8c46c66f740 100644 --- a/src/gui/qgsmapmouseevent.cpp +++ b/src/gui/qgsmapmouseevent.cpp @@ -18,6 +18,7 @@ #include "qgsmapcanvas.h" #include "qgssnappingutils.h" +#include "qgssnappingconfig.h" /// @cond PRIVATE struct EdgesOnlyFilter : public QgsPointLocator::MatchFilter @@ -62,18 +63,18 @@ QgsPoint QgsMapMouseEvent::snapPoint( SnappingMode snappingMode ) } QgsSnappingUtils* snappingUtils = mMapCanvas->snappingUtils(); - QgsSnappingUtils::SnapToMapMode canvasMode = snappingUtils->snapToMapMode(); if ( snappingMode == SnapAllLayers ) { - int type; - double tolerance; - QgsTolerance::UnitType unit; - snappingUtils->defaultSettings( type, tolerance, unit ); - snappingUtils->setSnapToMapMode( QgsSnappingUtils::SnapAllLayers ); - snappingUtils->setDefaultSettings( QgsPointLocator::Vertex | QgsPointLocator::Edge, tolerance, unit ); + QgsSnappingConfig canvasConfig = snappingUtils->config(); + QgsSnappingConfig localConfig = snappingUtils->config(); + + localConfig.setMode( QgsSnappingConfig::AllLayers ); + localConfig.setType( QgsSnappingConfig::VertexAndSegment ); + snappingUtils->setConfig( localConfig ); + mSnapMatch = snappingUtils->snapToMap( mMapPoint ); - snappingUtils->setSnapToMapMode( canvasMode ); - snappingUtils->setDefaultSettings( type, tolerance, unit ); + + snappingUtils->setConfig( canvasConfig ); } else { @@ -119,16 +120,17 @@ QList QgsMapMouseEvent::snapSegment( SnappingMode snappingMode, bool* { // run snapToMap with only edges on all layers QgsSnappingUtils* snappingUtils = mMapCanvas->snappingUtils(); - QgsSnappingUtils::SnapToMapMode canvasMode = snappingUtils->snapToMapMode(); - int type; - double tolerance; - QgsTolerance::UnitType unit; - snappingUtils->defaultSettings( type, tolerance, unit ); - snappingUtils->setSnapToMapMode( QgsSnappingUtils::SnapAllLayers ); - snappingUtils->setDefaultSettings( QgsPointLocator::Edge, tolerance, unit ); + + QgsSnappingConfig canvasConfig = snappingUtils->config(); + QgsSnappingConfig localConfig = snappingUtils->config(); + + localConfig.setMode( QgsSnappingConfig::AllLayers ); + localConfig.setType( QgsSnappingConfig::Segment ); + snappingUtils->setConfig( localConfig ); + match = snappingUtils->snapToMap( mOriginalMapPoint ); - snappingUtils->setSnapToMapMode( canvasMode ); - snappingUtils->setDefaultSettings( type, tolerance, unit ); + + snappingUtils->setConfig( canvasConfig ); } if ( match.isValid() && match.hasEdge() ) { diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 255a649af09..3e24abf6205 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -17,7 +17,7 @@ 0 0 1018 - 25 + 19 @@ -570,6 +570,17 @@ false + + + Snapping toolbar + + + TopToolBarArea + + + false + + diff --git a/src/ui/qgsoptionsbase.ui b/src/ui/qgsoptionsbase.ui index 36007432399..2ac1c1a094c 100644 --- a/src/ui/qgsoptionsbase.ui +++ b/src/ui/qgsoptionsbase.ui @@ -6,7 +6,7 @@ 0 0 - 791 + 857 658 @@ -308,7 +308,7 @@ - 0 + 13 @@ -337,8 +337,8 @@ 0 0 - 723 - 640 + 685 + 612 @@ -1023,8 +1023,8 @@ 0 0 - 607 - 850 + 671 + 869 @@ -1456,8 +1456,8 @@ 0 0 - 601 - 694 + 671 + 659 @@ -1814,8 +1814,8 @@ 0 0 - 747 - 972 + 684 + 924 @@ -2612,8 +2612,8 @@ 0 0 - 617 - 607 + 685 + 612 @@ -2759,8 +2759,8 @@ 0 0 - 617 - 607 + 685 + 612 @@ -3102,7 +3102,7 @@ 0 0 - 650 + 685 612 @@ -3533,8 +3533,8 @@ 0 0 - 617 - 607 + 685 + 612 @@ -3802,8 +3802,8 @@ 0 0 - 601 - 668 + 671 + 669 @@ -4003,14 +4003,7 @@ Snapping - - - - Default snap mode - - - - + @@ -4020,14 +4013,7 @@ - - - - Default snapping tolerance - - - - + 5 @@ -4037,14 +4023,7 @@ - - - - Search radius for vertex edits - - - - + 5 @@ -4054,7 +4033,7 @@ - + @@ -4074,7 +4053,7 @@ - + @@ -4088,7 +4067,7 @@ - + Qt::Horizontal @@ -4101,7 +4080,7 @@ - + Qt::Horizontal @@ -4114,7 +4093,7 @@ - + Qt::Horizontal @@ -4127,13 +4106,47 @@ - - + + - Open snapping options in a dock window (QGIS restart required) + Search radius for vertex edits + + + + Display main dialog as (restart required) + + + + + + + Default snap mode + + + + + + + Display simplified panel in (restart required) + + + + + + + Default snapping tolerance + + + + + + + + + @@ -4347,8 +4360,8 @@ 0 0 - 617 - 607 + 685 + 612 @@ -4486,8 +4499,8 @@ 0 0 - 601 - 644 + 671 + 638 @@ -4732,8 +4745,8 @@ 0 0 - 617 - 607 + 685 + 612 @@ -4841,8 +4854,8 @@ 0 0 - 601 - 737 + 671 + 689 @@ -5540,7 +5553,6 @@ mLineColorToolButton mFillColorToolButton mLineGhostCheckBox - cbxSnappingOptionsDocked mDefaultSnapModeComboBox mDefaultSnappingToleranceSpinBox mDefaultSnappingToleranceComboBox diff --git a/tests/src/core/testqgssnappingutils.cpp b/tests/src/core/testqgssnappingutils.cpp index 61057d6e029..fd8ed395dae 100644 --- a/tests/src/core/testqgssnappingutils.cpp +++ b/tests/src/core/testqgssnappingutils.cpp @@ -23,6 +23,7 @@ #include "qgsgeometry.h" #include "qgsmaplayerregistry.h" #include "qgssnappingutils.h" +#include "qgssnappingconfig.h" struct FilterExcludePoint : public QgsPointLocator::MatchFilter @@ -93,14 +94,21 @@ class TestQgsSnappingUtils : public QObject u.setCurrentLayer( mVL ); // first try with no snapping enabled - u.setDefaultSettings( 0, 10, QgsTolerance::Pixels ); + QgsSnappingConfig snappingConfig = u.config(); + snappingConfig.setEnabled( false ); + snappingConfig.setTolerance( 10 ); + snappingConfig.setUnits( QgsTolerance::Pixels ); + snappingConfig.setMode( QgsSnappingConfig::ActiveLayer ); + u.setConfig( snappingConfig ); QgsPointLocator::Match m0 = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( !m0.isValid() ); QVERIFY( !m0.hasVertex() ); // now enable snapping - u.setDefaultSettings( QgsPointLocator::Vertex | QgsPointLocator::Edge, 10, QgsTolerance::Pixels ); + snappingConfig.setEnabled( true ); + snappingConfig.setType( QgsSnappingConfig::Vertex ); + u.setConfig( snappingConfig ); QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( m.isValid() ); @@ -113,7 +121,9 @@ class TestQgsSnappingUtils : public QObject // do not consider edges in the following test - on 32-bit platforms // result was an edge match very close to (1,0) instead of being exactly (1,0) - u.setDefaultSettings( QgsPointLocator::Vertex, 10, QgsTolerance::Pixels ); + + snappingConfig.setType( QgsSnappingConfig::Vertex ); + u.setConfig( snappingConfig ); // test with filtering FilterExcludePoint myFilter( QgsPoint( 1, 0 ) ); @@ -129,8 +139,10 @@ class TestQgsSnappingUtils : public QObject QVERIFY( mapSettings.hasValidSettings() ); QgsSnappingUtils u; + QgsSnappingConfig snappingConfig = u.config(); u.setMapSettings( mapSettings ); - u.setSnapToMapMode( QgsSnappingUtils::SnapAllLayers ); + snappingConfig.setMode( QgsSnappingConfig::AllLayers ); + u.setConfig( snappingConfig ); // right now there are no layers in map settings - snapping will fail @@ -154,11 +166,11 @@ class TestQgsSnappingUtils : public QObject QVERIFY( mapSettings.hasValidSettings() ); QgsSnappingUtils u; + QgsSnappingConfig snappingConfig = u.config(); u.setMapSettings( mapSettings ); - u.setSnapToMapMode( QgsSnappingUtils::SnapAdvanced ); - QList layers; - layers << QgsSnappingUtils::LayerConfig( mVL, QgsPointLocator::Vertex, 10, QgsTolerance::Pixels ); - u.setLayers( layers ); + snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration ); + snappingConfig.setIndividualLayerSettings( mVL, QgsSnappingConfig::IndividualLayerSettings( true, QgsSnappingConfig::Vertex, 10, QgsTolerance::Pixels ) ); + u.setConfig( snappingConfig ); QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( m.isValid() ); @@ -201,16 +213,18 @@ class TestQgsSnappingUtils : public QObject QgsSnappingUtils u; u.setMapSettings( mapSettings ); - u.setSnapToMapMode( QgsSnappingUtils::SnapAdvanced ); - QList layers; - layers << QgsSnappingUtils::LayerConfig( vl, QgsPointLocator::Vertex, 0.1, QgsTolerance::ProjectUnits ); - u.setLayers( layers ); + QgsSnappingConfig snappingConfig = u.config(); + snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration ); + QgsSnappingConfig::IndividualLayerSettings layerSettings( true, QgsSnappingConfig::Vertex, 0.1, QgsTolerance::ProjectUnits ); + snappingConfig.setIndividualLayerSettings( vl, layerSettings ); + u.setConfig( snappingConfig ); // no snapping on intersections by default - should find nothing QgsPointLocator::Match m = u.snapToMap( QgsPoint( 0.45, 0.5 ) ); QVERIFY( !m.isValid() ); - u.setSnapOnIntersections( true ); + snappingConfig.setIntersectionSnapping( true ); + u.setConfig( snappingConfig ); QgsPointLocator::Match m2 = u.snapToMap( QgsPoint( 0.45, 0.5 ) ); QVERIFY( m2.isValid() ); diff --git a/tests/src/python/test_layer_dependencies.py b/tests/src/python/test_layer_dependencies.py index da70df132ae..39f99b5a73f 100644 --- a/tests/src/python/test_layer_dependencies.py +++ b/tests/src/python/test_layer_dependencies.py @@ -19,6 +19,7 @@ from qgis.core import (QgsMapLayerRegistry, QgsVectorLayer, QgsMapSettings, QgsSnappingUtils, + QgsSnappingConfig, QgsPointLocator, QgsTolerance, QgsRectangle, @@ -111,9 +112,12 @@ class TestLayerDependencies(unittest.TestCase): u = QgsSnappingUtils() u.setMapSettings(ms) - u.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced) - layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels)] - u.setLayers(layers) + cfg = u.config() + cfg.setMode(QgsSnappingConfig.AdvancedConfiguration) + cfg.setIndividualLayerSettings(self.pointsLayer, + QgsSnappingConfig.IndividualLayerSettings(True, + QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) + u.setConfig(cfg) m = u.snapToMap(QPoint(95, 100)) self.assertTrue(m.isValid()) @@ -153,10 +157,10 @@ class TestLayerDependencies(unittest.TestCase): self.pointsLayer.setDependencies([]) # test chained layer dependencies A -> B -> C - layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels), - QgsSnappingUtils.LayerConfig(self.pointsLayer2, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels) - ] - u.setLayers(layers) + cfg.setIndividualLayerSettings(self.pointsLayer2, + QgsSnappingConfig.IndividualLayerSettings(True, + QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) + u.setConfig(cfg) self.pointsLayer.setDependencies([QgsMapLayerDependency(self.linesLayer.id())]) self.pointsLayer2.setDependencies([QgsMapLayerDependency(self.pointsLayer.id())]) # add another line @@ -230,11 +234,15 @@ class TestLayerDependencies(unittest.TestCase): u = QgsSnappingUtils() u.setMapSettings(ms) - u.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced) - layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels), - QgsSnappingUtils.LayerConfig(self.pointsLayer2, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels) - ] - u.setLayers(layers) + cfg = u.config() + cfg.setMode(QgsSnappingConfig.AdvancedConfiguration) + cfg.setIndividualLayerSettings(self.pointsLayer, + QgsSnappingConfig.IndividualLayerSettings(True, + QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) + cfg.setIndividualLayerSettings(self.pointsLayer2, + QgsSnappingConfig.IndividualLayerSettings(True, + QgsSnappingConfig.Vertex, 20, QgsTolerance.Pixels)) + u.setConfig(cfg) # add another line f = QgsFeature(self.linesLayer.fields()) f.setFeatureId(4)