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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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)