Use toolbar or status bar for snapping config

This commit is contained in:
Denis Rouzaud 2016-08-26 15:36:07 +02:00 committed by Matthias Kuhn
parent f3482d2ce2
commit 64d3c788f1
38 changed files with 12138 additions and 585 deletions

View File

@ -574,6 +574,15 @@
<file>themes/default/mIconClearText.svg</file>
<file>themes/default/mIconClearTextHover.svg</file>
<file>themes/default/rendererPointClusterSymbol.svg</file>
<file>themes/default/mIconSnapping.svg</file>
<file>themes/default/mIconSnappingActiveLayer.svg</file>
<file>themes/default/mIconSnappingAdvanced.svg</file>
<file>themes/default/mIconSnappingAllLayers.svg</file>
<file>themes/default/mIconSnappingVertexAndSegment.svg</file>
<file>themes/default/mIconSnappingVertex.svg</file>
<file>themes/default/mIconSnappingSegment.svg</file>
<file>themes/default/mIconTopologicalEditing.svg</file>
<file>themes/default/mIconSnappingIntersection.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generated by IcoMoon.io -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg4136"
inkscape:version="0.91 r13725"
sodipodi:docname="mIconSnapping.svg">
<metadata
id="metadata4154">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4152" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1343"
id="namedview4150"
showgrid="true"
inkscape:zoom="25.875689"
inkscape:cx="-2.0772384"
inkscape:cy="12.333477"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg4136">
<inkscape:grid
type="xygrid"
id="grid5544" />
</sodipodi:namedview>
<g
id="g4138"
transform="translate(0,-488)" />
<path
d="m 366.643,-210.599 c 0,29.563 -11.51,57.365 -32.379,78.244 -20.941,20.921 -48.712,32.44 -78.285,32.44 -61.02,0 -110.663,-49.644 -110.663,-110.684 l 0,-124.099 -99.052,0 0,124.099 c 0,115.682 94.085,209.715 209.715,209.715 56.033,0 108.718,-21.781 148.326,-61.379 39.608,-39.639 61.43,-92.324 61.43,-148.337 l 0,-124.099 -99.103,0 0,124.099 z"
id="path4140"
inkscape:connector-curvature="0"
style="fill:#000000" />
<path
d="m 384.645,-379.6 0,-9.922 -9.882,0 z"
id="path4142"
inkscape:connector-curvature="0"
style="fill:#000000" />
<path
d="m 454.523,-349.422 9.913,0 0,-113.695 -97.577,0 0,113.695 87.664,0 z m -67.84,-91.935 57.938,0 0,72.837 -57.938,0 0,-72.837 z"
id="path4144"
inkscape:connector-curvature="0"
style="fill:#000000" />
<path
d="m 251.453,-361.382 99.871,-84.582 -84.316,44.667 -24.852,-47.698 -83.978,67.154 75.111,-25.794 z"
id="path4146"
inkscape:connector-curvature="0"
style="fill:#000000" />
<path
d="m 136.233,-349.422 9.892,0 0,-113.695 -97.546,0 0,113.695 87.654,0 z m -67.86,-91.935 57.948,0 0,72.837 -57.948,0 0,-72.837 z"
id="path4148"
inkscape:connector-curvature="0"
style="fill:#000000" />
<rect
style="display:inline;opacity:1;fill:#bebebe;fill-opacity:1;stroke:#8c8c8c;stroke-width:1.24999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4563-1"
width="3.75"
height="3.75"
x="2.625"
y="2.375" />
<path
style="fill:none;fill-rule:evenodd;stroke:#8c8c8c;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 19.5,8.75 0,6 c 0,3 -2,5 -5,5 l -5,0 c -3,0 -5,-2 -5,-5 l 0,-6"
id="path5546"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<rect
style="display:inline;opacity:1;fill:#bebebe;fill-opacity:1;stroke:#8c8c8c;stroke-width:1.24999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4563-1-3"
width="3.75"
height="3.75"
x="17.625"
y="2.375" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 37 KiB

View File

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

View File

@ -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<int, QByteArray> roleNames() const;

View File

@ -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<QDomNode>& 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
*/

View File

@ -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 <qgssnappingconfig.h>
%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<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> container = sipCpp->individualLayerSettings();
QHash<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings>::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<QgsMapLayer*>& 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<QgsMapLayer*>& layers );
};

View File

@ -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<QgsSnappingUtils::LayerConfig>& layers );
/** Query layers used for snapping */
QList<QgsSnappingUtils::LayerConfig> 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();

View File

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

View File

@ -30,6 +30,7 @@
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDir>
#include <QEvent>
#include <QFile>
@ -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<QAction*> 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 );

View File

@ -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;

View File

@ -22,6 +22,7 @@
#include "qgssnappingutils.h"
#include "qgsvectorlayer.h"
#include "qgsvertexmarker.h"
#include "qgssnappingconfig.h"
#include <QGraphicsProxyWidget>
#include <QMouseEvent>
@ -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<QgsTolerance::UnitType>( 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() )
{

View File

@ -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() );

View File

@ -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 <QCheckBox>
#include <QDoubleValidator>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QDoubleValidator>
#include <QLineEdit>
#include <QPushButton>
#include <QDoubleSpinBox>
QgsSnappingDialog::QgsSnappingDialog( QWidget* parent, QgsMapCanvas* canvas )
: QDialog( parent )

View File

@ -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 <QComboBox>
#include <QDoubleSpinBox>
#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<QComboBox *>( editor );
if ( cb )
{
cb->setCurrentIndex( cb->findData( type ) );
}
}
else if ( index.column() == QgsSnappingLayerTreeModel::ToleranceColumn )
{
QDoubleSpinBox *w = qobject_cast<QDoubleSpinBox *>( editor );
if ( w )
{
w->setValue( val.toDouble() );
}
}
else if ( index.column() == QgsSnappingLayerTreeModel::UnitsColumn )
{
QgsTolerance::UnitType units = ( QgsTolerance::UnitType )val.toInt();
QComboBox *w = qobject_cast<QComboBox *>( 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<QComboBox *>( editor );
if ( w )
{
model->setData( index, w->currentData(), Qt::EditRole );
}
}
else if ( index.column() == QgsSnappingLayerTreeModel::ToleranceColumn )
{
QDoubleSpinBox *w = qobject_cast<QDoubleSpinBox *>( 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<QgsVectorLayer*>( QgsLayerTree::toLayer( node )->layer() );
}
void QgsSnappingLayerTreeModel::onSnappingSettingsChanged()
{
const QHash<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> 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<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> &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;
}

View File

@ -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 <QSortFilterProxyModel>
#include <QItemDelegate>
#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<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> mIndividualLayerSettings;
QgsLayerTreeModel* mLayerTreeModel;
void hasRowchanged( QgsLayerTreeNode* node, const QHash<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings>& oldSettings );
};
#endif // QGSSNAPPINGLAYERTREEVIEW_H

View File

@ -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 <QAction>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QFont>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QMenu>
#include <QSettings>
#include <QStatusBar>
#include <QToolBar>
#include <QToolButton>
#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<QToolBar*>( parent );
if ( tb )
{
mDisplayMode = ToolBar;
setObjectName( "SnappingOptionToolBar" );
}
else
{
QStatusBar *sb = qobject_cast<QStatusBar*>( 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<QgsLayerTreeNode *> 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 );
}

130
src/app/qgssnappingwidget.h Normal file
View File

@ -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 <QWidget>
/**
* 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

View File

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

View File

@ -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<QgsMapLayer*> ) ), this, SLOT( onMapLayersAdded( QList<QgsMapLayer*> ) ) );
connect( QgsMapLayerRegistry::instance(), SIGNAL( layersRemoved( QStringList ) ), this, SLOT( cleanTransactionGroups() ) );
connect( QgsMapLayerRegistry::instance(), SIGNAL( layersWillBeRemoved( QList<QgsMapLayer*> ) ), this, SLOT( onMapLayersRemoved( QList<QgsMapLayer*> ) ) );
}
@ -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<QDomNode>& 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<QgsMapLayer*>& layers )
}
}
}
if ( mSnappingConfig.addLayers( layers ) )
emit snappingConfigChanged();
}
void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer*>& 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

View File

@ -32,6 +32,7 @@
//#include <QDomDocument>
#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<QDomNode>& 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<QgsMapLayer*>& layers );
void onMapLayersRemoved( const QList<QgsMapLayer*>& 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<QDomNode>& 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;

View File

@ -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 <QDomElement>
#include <QHeaderView>
#include <QSettings>
#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<QgsVectorLayer *, IndividualLayerSettings>();
Q_FOREACH ( QgsMapLayer *ml, QgsMapLayerRegistry::instance()->mapLayers() )
{
QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( 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<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> 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<QgsVectorLayer*>( 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<QgsMapLayer*>& 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<QgsVectorLayer*>( ml );
if ( vl )
{
mIndividualLayerSettings.insert( vl, IndividualLayerSettings( enabled, type, tolerance, units ) );
changed = true;
}
}
return changed;
}
bool QgsSnappingConfig::removeLayers( const QList<QgsMapLayer*>& layers )
{
bool changed = false;
Q_FOREACH ( QgsMapLayer* ml, layers )
{
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( 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<QgsVectorLayer *>( 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<QgsTolerance::UnitType>( tolUnitIt->toInt() ) ) );
}
}

View File

@ -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<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> 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<QgsMapLayer*>& 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<QgsMapLayer*>& layers );
private:
void readLegacySettings();
bool mEnabled;
SnappingMode mMode;
SnappingType mType;
double mTolerance;
QgsTolerance::UnitType mUnits;
bool mTopologicalEditing;
bool mIntersectionSnapping;
QHash<QgsVectorLayer*, IndividualLayerSettings> mIndividualLayerSettings;
};
#endif // QGSPROJECTSNAPPINGSETTINGS_H

View File

@ -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<LayerAndAreaOfInterest>() << 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<LayerAndAreaOfInterest> 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<LayerAndAreaOfInterest> 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<LayerAndAreaOfInterest>& 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<QgsSnappingUtils::LayerConfig>& 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<LayerConfig> 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<QgsVectorLayer*>( 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<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> layerSettings )
{
mSnapToMapMode = SnapCurrentLayer;
mLayers.clear();
QString snapMode = QgsProject::instance()->readEntry( "Digitizing", "/SnappingMode" );
QHash<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings>::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<QgsVectorLayer *>( 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() ) );
}
}
}

View File

@ -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<LayerConfig>& layers );
/** Query layers used for snapping */
QList<LayerConfig> 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<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> 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<LayerConfig> mLayers;
bool mSnapOnIntersection;
// internal data
typedef QMap<QgsVectorLayer*, QgsPointLocator*> LocatorsMap;

View File

@ -21,6 +21,7 @@
#include "qgsmessagebaritem.h"
#include "qgssnappingutils.h"
#include "qgsvectorlayer.h"
#include "qgssnappingconfig.h"
#include <QAction>
@ -101,17 +102,17 @@ void QgsMapCanvasTracer::configure()
QList<QgsVectorLayer*> 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<QgsVectorLayer*>( 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<QgsVectorLayer*>( 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();
}

View File

@ -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<QgsPoint> 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() )
{

View File

@ -17,7 +17,7 @@
<x>0</x>
<y>0</y>
<width>1018</width>
<height>25</height>
<height>19</height>
</rect>
</property>
<property name="toolTip">
@ -570,6 +570,17 @@
<bool>false</bool>
</attribute>
</widget>
<widget class="QToolBar" name="mSnappingToolBar">
<property name="windowTitle">
<string>Snapping toolbar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<action name="mActionNewProject">
<property name="icon">
<iconset resource="../../images/images.qrc">

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>791</width>
<width>857</width>
<height>658</height>
</rect>
</property>
@ -308,7 +308,7 @@
<item>
<widget class="QStackedWidget" name="mOptionsStackedWidget">
<property name="currentIndex">
<number>0</number>
<number>13</number>
</property>
<widget class="QWidget" name="mOptionsPageGeneral">
<layout class="QVBoxLayout" name="verticalLayout_3">
@ -337,8 +337,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>723</width>
<height>640</height>
<width>685</width>
<height>612</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_28">
@ -1023,8 +1023,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>607</width>
<height>850</height>
<width>671</width>
<height>869</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_22">
@ -1456,8 +1456,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>694</height>
<width>671</width>
<height>659</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_27">
@ -1814,8 +1814,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>747</width>
<height>972</height>
<width>684</width>
<height>924</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_22">
@ -2612,8 +2612,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>617</width>
<height>607</height>
<width>685</width>
<height>612</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_46">
@ -2759,8 +2759,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>617</width>
<height>607</height>
<width>685</width>
<height>612</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_25">
@ -3102,7 +3102,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<width>685</width>
<height>612</height>
</rect>
</property>
@ -3533,8 +3533,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>617</width>
<height>607</height>
<width>685</width>
<height>612</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_39">
@ -3802,8 +3802,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>668</height>
<width>671</width>
<height>669</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_31">
@ -4003,14 +4003,7 @@
<string>Snapping</string>
</property>
<layout class="QGridLayout" name="_10">
<item row="1" column="0">
<widget class="QLabel" name="mDefaultSnapModeLabel">
<property name="text">
<string>Default snap mode</string>
</property>
</widget>
</item>
<item row="1" column="4" colspan="2">
<item row="3" column="5" colspan="2">
<widget class="QComboBox" name="mDefaultSnapModeComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -4020,14 +4013,7 @@
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="mDefaultSnappingToleranceTextLabel">
<property name="text">
<string>Default snapping tolerance</string>
</property>
</widget>
</item>
<item row="2" column="4">
<item row="4" column="5">
<widget class="QDoubleSpinBox" name="mDefaultSnappingToleranceSpinBox">
<property name="decimals">
<number>5</number>
@ -4037,14 +4023,7 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="mVertexSearchRadiusVertexEditLabel">
<property name="text">
<string>Search radius for vertex edits</string>
</property>
</widget>
</item>
<item row="3" column="4">
<item row="5" column="5">
<widget class="QDoubleSpinBox" name="mSearchRadiusVertexEditSpinBox">
<property name="decimals">
<number>5</number>
@ -4054,7 +4033,7 @@
</property>
</widget>
</item>
<item row="2" column="5">
<item row="4" column="6">
<widget class="QComboBox" name="mDefaultSnappingToleranceComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -4074,7 +4053,7 @@
</item>
</widget>
</item>
<item row="3" column="5">
<item row="5" column="6">
<widget class="QComboBox" name="mSearchRadiusVertexEditComboBox">
<item>
<property name="text">
@ -4088,7 +4067,7 @@
</item>
</widget>
</item>
<item row="3" column="3">
<item row="5" column="4">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -4101,7 +4080,7 @@
</property>
</spacer>
</item>
<item row="2" column="2" colspan="2">
<item row="4" column="3" colspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -4114,7 +4093,7 @@
</property>
</spacer>
</item>
<item row="1" column="1" colspan="3">
<item row="3" column="2" colspan="3">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -4127,13 +4106,47 @@
</property>
</spacer>
</item>
<item row="0" column="0" colspan="6">
<widget class="QCheckBox" name="cbxSnappingOptionsDocked">
<item row="5" column="1" colspan="3">
<widget class="QLabel" name="mVertexSearchRadiusVertexEditLabel">
<property name="text">
<string>Open snapping options in a dock window (QGIS restart required)</string>
<string>Search radius for vertex edits</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_49">
<property name="text">
<string>Display main dialog as (restart required)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="mDefaultSnapModeLabel">
<property name="text">
<string>Default snap mode</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Display simplified panel in (restart required)</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLabel" name="mDefaultSnappingToleranceTextLabel">
<property name="text">
<string>Default snapping tolerance</string>
</property>
</widget>
</item>
<item row="2" column="5" colspan="2">
<widget class="QComboBox" name="mSnappingSimplePanelComboBox"/>
</item>
<item row="1" column="5" colspan="2">
<widget class="QComboBox" name="mSnappingMainDialogComboBox"/>
</item>
</layout>
</widget>
</item>
@ -4347,8 +4360,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>617</width>
<height>607</height>
<width>685</width>
<height>612</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
@ -4486,8 +4499,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>644</height>
<width>671</width>
<height>638</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_15">
@ -4732,8 +4745,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>617</width>
<height>607</height>
<width>685</width>
<height>612</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
@ -4841,8 +4854,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>737</height>
<width>671</width>
<height>689</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_33">
@ -5540,7 +5553,6 @@
<tabstop>mLineColorToolButton</tabstop>
<tabstop>mFillColorToolButton</tabstop>
<tabstop>mLineGhostCheckBox</tabstop>
<tabstop>cbxSnappingOptionsDocked</tabstop>
<tabstop>mDefaultSnapModeComboBox</tabstop>
<tabstop>mDefaultSnappingToleranceSpinBox</tabstop>
<tabstop>mDefaultSnappingToleranceComboBox</tabstop>

View File

@ -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<QgsSnappingUtils::LayerConfig> 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<QgsSnappingUtils::LayerConfig> 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() );

View File

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